Skip to content

Commit

Permalink
Merge pull request #171 from lidofinance/feat/rpc_log_request
Browse files Browse the repository at this point in the history
feat: update work with metadata to detect when rpc provider lost log events
  • Loading branch information
AnnaSila authored Apr 23, 2024
2 parents b2df151 + c5bb099 commit 7d16966
Show file tree
Hide file tree
Showing 5 changed files with 65 additions and 72 deletions.
1 change: 0 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -92,4 +92,3 @@ To create a new release:
1. When you need to release, go to Repo → Releases.
1. Publish the desired release draft manually by clicking the edit button - this release is now the `Latest Published`.
1. After publication, the action to create a release bump will be triggered automatically.
1.
12 changes: 10 additions & 2 deletions modules/votes/ui/VoteDescription/VoteDescription.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { fetcherIPFS } from 'modules/network/utils/fetcherIPFS'
import { useSWR } from 'modules/network/hooks/useSwr'

type Props = {
metadata: string | undefined
metadata?: string | undefined
allowMD?: boolean
}

Expand All @@ -25,8 +25,16 @@ export function VoteDescription({ metadata, allowMD }: Props) {
initialLoading,
} = useSWR(cid, fetcherIPFS, { onError: noop })

if (metadata === '') {
return <DescriptionText>No description.</DescriptionText>
}

if (!metadata) {
return <DescriptionText>Failed to fetch vote description.</DescriptionText>
return (
<DescriptionText>
Failed to fetch vote description from RPC provider.
</DescriptionText>
)
}

if (!cid && metadata) {
Expand Down
2 changes: 1 addition & 1 deletion modules/votes/ui/VoteDetails/VoteDetails.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ export function VoteDetails({
voteTime,
objectionPhaseTime,
creator,
metadata = '',
metadata,
isEnded,
executedTxHash,
}: Props) {
Expand Down
2 changes: 1 addition & 1 deletion modules/votes/ui/VoteScript/VoteScript.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import { VoteScriptBody } from './VoteScriptBody'

type Props = {
script: string
metadata: string
metadata?: string
}

export function VoteScript({ script, metadata = '' }: Props) {
Expand Down
120 changes: 53 additions & 67 deletions pages/api/rpc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ import { CHAINS } from '@lido-sdk/constants'
import { parseChainId } from 'modules/blockChain/chains'
import { fetchWithFallback } from 'modules/network/utils/fetchWithFallback'
import type { NextApiRequest, NextApiResponse } from 'next'
import { ethers } from 'ethers'
import { AragonVoting } from '../../modules/blockChain/contractAddresses'
import { AragonVotingAbi__factory } from '../../generated'

const { serverRuntimeConfig } = getConfig()
const { rpcUrls_1, rpcUrls_5, rpcUrls_17000 } = serverRuntimeConfig
Expand All @@ -19,8 +22,20 @@ interface IRpcResponse {
id: number
result: any[]
}
const isLogsRequest = (item: any) => item?.method === 'eth_getLogs'
const hasResult = (item: any) => item.data?.result?.length > 0
interface IStat {
req: IRpcRequest
res?: IRpcResponse
date: string
trace: string
voteId: number
}

const isStartFromTopic = (topic: string) => (req: IRpcRequest) =>
req.method === 'eth_getLogs' && req.params[0]?.topics?.[0] === topic

const getVoteIdFromRequest = (req: any) => Number(req?.params?.[0]?.topics?.[1])

const noResult = (item: any) => item?.res?.result?.length === 0

export default async function rpc(req: NextApiRequest, res: NextApiResponse) {
const RPC_URLS: Record<number, string[]> = {
Expand All @@ -44,73 +59,44 @@ export default async function rpc(req: NextApiRequest, res: NextApiResponse) {
const chainId = parseChainId(String(req.query.chainId))
const urls = RPC_URLS[chainId]

const rpcReq = (chainUrls: string[]) =>
fetchWithFallback(chainUrls, chainId, {
method: 'POST',
// Next by default parses our body for us, we don't want that here
body: JSON.stringify(req.body),
headers: {
'Content-type': 'application/json',
},
})

const needCompareWithFallback = req.body.some(isLogsRequest) // assign false to off comparison
const requests: [Promise<Response>, Promise<Response | null>] =
urls[1] && needCompareWithFallback
? [rpcReq(urls), rpcReq([urls[1]]).catch(() => null)]
: [rpcReq(urls), Promise.resolve(null)]

const [requested, requestedFb] = await Promise.all(requests)
const requested = await fetchWithFallback(urls, chainId, {
method: 'POST',
// Next by default parses our body for us, we don't want that here
body: JSON.stringify(req.body),
headers: {
'Content-type': 'application/json',
},
})

const responded: IRpcResponse[] = await requested.json()

// this part for detecting issue with loose answer to eth_getLogs request
if (requestedFb) {
const respondedFb: IRpcResponse[] = await requestedFb.json()
const mixed = req.body.filter(isLogsRequest).map((item: IRpcRequest) => ({
req: item,
res: [
{
data: responded.find(resp => resp.id === item.id),
date: requested.headers.get('date'),
trace: requested.headers.get('x-drpc-trace-id'),
},
{
data: respondedFb.find(resp => resp.id === item.id),
date: requestedFb.headers.get('date'),
trace: requestedFb.headers.get('x-drpc-trace-id'),
},
],
}))

mixed.forEach((item: any) => {
let index = -1
let message = 'no blockers'
index = !hasResult(item.res[0]) && hasResult(item.res[1]) ? 0 : index
index = hasResult(item.res[0]) && !hasResult(item.res[1]) ? 1 : index

if (index != -1) {
message = 'One chain loose rpc logs'
}

if (!hasResult(item.res[0]) && !hasResult(item.res[1])) {
index = 1
message = 'Both chains loose rpc logs'
}

if (index != -1) {
const { data, date, trace } = item.res[index]
console.info({
message,
urlIndex: index,
chainId,
date,
trace,
req: item.req,
res: data,
})
}
try {
const contractVoting = new ethers.Contract(
AragonVoting[chainId] ?? '',
AragonVotingAbi__factory.abi,
)
const filter = contractVoting.filters.StartVote(Number(0))
const startVoteTopic = String(filter.topics?.[0])
const voteEvents: IStat[] = req.body
.filter(isStartFromTopic(startVoteTopic))
.map((item: IRpcRequest) => ({
req: item,
res: responded.find(resp => resp.id === item.id),
date: requested.headers.get('date') ?? '',
trace: requested.headers.get('x-drpc-trace-id') ?? '',
voteId: getVoteIdFromRequest(item),
}))

const lostVoteEvents = voteEvents.filter(noResult)

lostVoteEvents.forEach(item => {
console.error({
message: `Lost log event for vote #${item.voteId}`,
chainId,
...item,
})
})
} catch (err) {
console.error(`Failed on empty log response verification`)
}

res.status(requested.status).json(responded)
Expand All @@ -124,6 +110,6 @@ export default async function rpc(req: NextApiRequest, res: NextApiResponse) {
error instanceof Error ? error.message : 'Something went wrong',
error,
)
res.status(500).send({ error: 'Something went wrong' })
res.status(500).send({ error: 'Something went wrong!' })
}
}

0 comments on commit 7d16966

Please sign in to comment.