Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: implement quicknode geth vs reth output comparison #775

Prev Previous commit
Next Next commit
fix the send method comparison
  • Loading branch information
jsy1218 committed Jul 15, 2024
commit ba0393b233e973dde9f6ed690793e314414d622e
109 changes: 60 additions & 49 deletions lib/rpc/UniJsonRpcProvider.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import {
CALL_METHOD_NAME,
CallType,
GET_BLOCK_NUMBER_METHOD_NAME,
MAJOR_METHOD_NAMES,
SEND_METHOD_NAME,
SingleJsonRpcProvider,
Expand All @@ -22,7 +21,7 @@ import { BigNumber, BigNumberish } from '@ethersproject/bignumber'
import { Deferrable } from '@ethersproject/properties'
import Logger from 'bunyan'
import { UniJsonRpcProviderConfig } from './config'
import { EthFeeHistory } from '../../test/utils/eth_feeHistory'
import { EthFeeHistory } from '../util/eth_feeHistory'

export class UniJsonRpcProvider extends StaticJsonRpcProvider {
readonly chainId: ChainId = ChainId.MAINNET
Expand Down Expand Up @@ -272,60 +271,72 @@ export class UniJsonRpcProvider extends StaticJsonRpcProvider {
methodName: string,
args: any[]
) {
if (methodName === GET_BLOCK_NUMBER_METHOD_NAME) {
// if it's get block number, there's no guarantee that two providers will return the same block number
// since the node might be syncing, so we don't need to compare the response
return
} else if (methodName === CALL_METHOD_NAME) {
// if it's eth_call, then we know the response data type is string, so we can compare directly
if (providerResponse !== evaluatedProviderResponse) {
this.log.error(
{ methodName, args },
`Provider response mismatch: ${providerResponse} from ${selectedProvider.providerId} vs ${evaluatedProviderResponse} from ${otherProvider.providerId}`
)
selectedProvider.logRpcResponseMismatch(methodName, otherProvider)
} else {
selectedProvider.logRpcResponseMatch(methodName, otherProvider)
}
} else if (methodName === SEND_METHOD_NAME) {
// send is complicated, because it could be eth_call, eth_blockNumber, eth_feeHistory, eth_estimateGas
// so we need to compare the response based on the method name
const underlyingMethodName = args[0]
const stitchedMethodName = `${SEND_METHOD_NAME}_${underlyingMethodName}`
if (underlyingMethodName === 'eth_call' || underlyingMethodName === 'eth_estimateGas') {
switch (methodName) {
case CALL_METHOD_NAME:
// if it's eth_call, then we know the response data type is string, so we can compare directly
if (providerResponse !== evaluatedProviderResponse) {
this.log.error(
{ stitchedMethodName, args },
{ methodName, args },
`Provider response mismatch: ${providerResponse} from ${selectedProvider.providerId} vs ${evaluatedProviderResponse} from ${otherProvider.providerId}`
)
selectedProvider.logRpcResponseMismatch(stitchedMethodName, otherProvider)
selectedProvider.logRpcResponseMismatch(methodName, otherProvider)
} else {
selectedProvider.logRpcResponseMatch(stitchedMethodName, otherProvider)
selectedProvider.logRpcResponseMatch(methodName, otherProvider)
}
} else if (underlyingMethodName === 'eth_feeHistory') {
const castedProviderResponse = providerResponse as EthFeeHistory
const castedEvaluatedProviderResponse = evaluatedProviderResponse as EthFeeHistory
const mismatch =
castedProviderResponse.oldestBlock !== castedEvaluatedProviderResponse.oldestBlock ||
JSON.stringify(castedProviderResponse.reward) !== JSON.stringify(castedEvaluatedProviderResponse.reward) ||
JSON.stringify(castedProviderResponse.baseFeePerGas) !==
JSON.stringify(castedEvaluatedProviderResponse.baseFeePerGas) ||
JSON.stringify(castedProviderResponse.gasUsedRatio) !==
JSON.stringify(castedEvaluatedProviderResponse.gasUsedRatio) ||
JSON.stringify(castedProviderResponse.baseFeePerBlobGas) !==
JSON.stringify(castedEvaluatedProviderResponse.baseFeePerBlobGas) ||
JSON.stringify(castedProviderResponse.blobGasUsedRatio) !==
JSON.stringify(castedEvaluatedProviderResponse.blobGasUsedRatio)
if (mismatch) {
this.log.error(
{ stitchedMethodName, args },
`Provider response mismatch: ${providerResponse} from ${selectedProvider.providerId} vs ${evaluatedProviderResponse} from ${otherProvider.providerId}`
)
selectedProvider.logRpcResponseMismatch(stitchedMethodName, otherProvider)
} else {
selectedProvider.logRpcResponseMatch(stitchedMethodName, otherProvider)
break
case SEND_METHOD_NAME:
// send is complicated, because it could be eth_call, eth_blockNumber, eth_feeHistory, eth_estimateGas
// so we need to compare the response based on the method name
const underlyingMethodName = args[0]
const stitchedMethodName = `${SEND_METHOD_NAME}_${underlyingMethodName}`
switch (underlyingMethodName) {
case 'eth_call':
case 'eth_estimateGas':
if (providerResponse !== evaluatedProviderResponse) {
this.log.error(
{ stitchedMethodName, args },
`Provider response mismatch: ${providerResponse} from ${selectedProvider.providerId} vs ${evaluatedProviderResponse} from ${otherProvider.providerId}`
)
selectedProvider.logRpcResponseMismatch(stitchedMethodName, otherProvider)
} else {
selectedProvider.logRpcResponseMatch(stitchedMethodName, otherProvider)
}
break
case 'eth_feeHistory':
const castedProviderResponse = providerResponse as EthFeeHistory
const castedEvaluatedProviderResponse = evaluatedProviderResponse as EthFeeHistory
const mismatch =
castedProviderResponse.oldestBlock !== castedEvaluatedProviderResponse.oldestBlock ||
JSON.stringify(castedProviderResponse.reward) !==
JSON.stringify(castedEvaluatedProviderResponse.reward) ||
JSON.stringify(castedProviderResponse.baseFeePerGas) !==
JSON.stringify(castedEvaluatedProviderResponse.baseFeePerGas) ||
JSON.stringify(castedProviderResponse.gasUsedRatio) !==
JSON.stringify(castedEvaluatedProviderResponse.gasUsedRatio) ||
JSON.stringify(castedProviderResponse.baseFeePerBlobGas) !==
JSON.stringify(castedEvaluatedProviderResponse.baseFeePerBlobGas) ||
JSON.stringify(castedProviderResponse.blobGasUsedRatio) !==
JSON.stringify(castedEvaluatedProviderResponse.blobGasUsedRatio)
if (mismatch) {
this.log.error(
{ stitchedMethodName, args },
`Provider response mismatch: ${providerResponse} from ${selectedProvider.providerId} vs ${evaluatedProviderResponse} from ${otherProvider.providerId}`
)
selectedProvider.logRpcResponseMismatch(stitchedMethodName, otherProvider)
} else {
selectedProvider.logRpcResponseMatch(stitchedMethodName, otherProvider)
}
break
default:
// if it's get block number, there's no guarantee that two providers will return the same block number
// since the node might be syncing, so we don't need to compare the response
return
}
}
break
default:
// if it's get block number, there's no guarantee that two providers will return the same block number
// since the node might be syncing, so we don't need to compare the response
return
}
}

Expand Down
File renamed without changes.
11 changes: 11 additions & 0 deletions test/mocha/unit/rpc/UniJsonRpcProvider.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1007,4 +1007,15 @@ describe('UniJsonRpcProvider', () => {
expect(spy1.callCount).to.equal(1)
expect(spy2.callCount).to.equal(1)
})

it('Test compare RPC response for eth_blockNumber', async () => {
uniProvider = new UniJsonRpcProvider(
ChainId.MAINNET,
SINGLE_RPC_PROVIDERS[ChainId.MAINNET],
log,
UNI_PROVIDER_TEST_CONFIG,
1.0,
1
)
})
})