-
Notifications
You must be signed in to change notification settings - Fork 11
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
fix: prevent crash when returning large JSON responses #569
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
--- | ||
"@nomicfoundation/edr": patch | ||
--- | ||
|
||
Fixed crash when returning large JSON responses |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -38,6 +38,7 @@ | |
"@types/node": "^18.0.0", | ||
"chai": "^4.3.6", | ||
"chai-as-promised": "^7.1.1", | ||
"json-stream-stringify": "^3.1.4", | ||
"mocha": "^10.0.0", | ||
"ts-node": "^10.8.0", | ||
"typescript": "~4.5.2" | ||
|
@@ -55,8 +56,8 @@ | |
"universal": "napi universal", | ||
"version": "napi version", | ||
"pretest": "pnpm build", | ||
"test": "pnpm tsc && mocha --recursive \"test/**/*.ts\"", | ||
"testNoBuild": "pnpm tsc && mocha --recursive \"test/**/*.ts\"", | ||
"test": "pnpm tsc && node --max-old-space-size=8192 node_modules/mocha/bin/_mocha --recursive \"test/**/*.ts\"", | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I needed to introduce a larger heap size to prevent the newly added test from crashing in V8 with an OOM error |
||
"testNoBuild": "pnpm tsc && node --max-old-space-size=8192 node_modules/mocha/bin/_mocha --recursive \"test/**/*.ts\"", | ||
"clean": "rm -rf @nomicfoundation/edr.node" | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,109 @@ | ||
import chai, { assert } from "chai"; | ||
import { JsonStreamStringify } from "json-stream-stringify"; | ||
|
||
import { | ||
ContractAndFunctionName, | ||
EdrContext, | ||
MineOrdering, | ||
Provider, | ||
SpecId, | ||
SubscriptionEvent, | ||
} from ".."; | ||
import { ALCHEMY_URL, isCI } from "./helpers"; | ||
|
||
describe("Provider", () => { | ||
const context = new EdrContext(); | ||
const providerConfig = { | ||
allowBlocksWithSameTimestamp: false, | ||
allowUnlimitedContractSize: true, | ||
bailOnCallFailure: false, | ||
bailOnTransactionFailure: false, | ||
blockGasLimit: 300_000_000n, | ||
chainId: 1n, | ||
chains: [], | ||
coinbase: Buffer.from("0000000000000000000000000000000000000000", "hex"), | ||
enableRip7212: false, | ||
genesisAccounts: [ | ||
{ | ||
secretKey: | ||
"0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80", | ||
balance: 1000n * 10n ** 18n, | ||
}, | ||
], | ||
hardfork: SpecId.Latest, | ||
initialBlobGas: { | ||
gasUsed: 0n, | ||
excessGas: 0n, | ||
}, | ||
initialParentBeaconBlockRoot: Buffer.from( | ||
"0000000000000000000000000000000000000000000000000000000000000000", | ||
"hex", | ||
), | ||
minGasPrice: 0n, | ||
mining: { | ||
autoMine: true, | ||
memPool: { | ||
order: MineOrdering.Priority, | ||
}, | ||
}, | ||
networkId: 123n, | ||
}; | ||
|
||
const loggerConfig = { | ||
enable: false, | ||
decodeConsoleLogInputsCallback: (inputs: Buffer[]): string[] => { | ||
return []; | ||
}, | ||
getContractAndFunctionNameCallback: ( | ||
_code: Buffer, | ||
_calldata?: Buffer, | ||
): ContractAndFunctionName => { | ||
return { | ||
contractName: "", | ||
}; | ||
}, | ||
printLineCallback: (message: string, replace: boolean) => {}, | ||
}; | ||
|
||
it("issue 543", async function () { | ||
if (ALCHEMY_URL === undefined || !isCI()) { | ||
this.skip(); | ||
} | ||
|
||
// This test is slow because the debug_traceTransaction is performed on a large transaction. | ||
this.timeout(240_000); | ||
|
||
const provider = await Provider.withConfig( | ||
context, | ||
{ | ||
fork: { | ||
jsonRpcUrl: ALCHEMY_URL, | ||
}, | ||
initialBaseFeePerGas: 0n, | ||
...providerConfig, | ||
}, | ||
loggerConfig, | ||
(_event: SubscriptionEvent) => {}, | ||
); | ||
|
||
const debugTraceTransaction = `{ | ||
"jsonrpc": "2.0", | ||
"method": "debug_traceTransaction", | ||
"params": ["0x7e460f200343e5ab6653a8857cc5ef798e3f5bea6a517b156f90c77ef311a57c"], | ||
"id": 1 | ||
}`; | ||
|
||
const response = await provider.handleRequest(debugTraceTransaction); | ||
|
||
let responseData; | ||
|
||
if (typeof response.data === "string") { | ||
responseData = JSON.parse(response.data); | ||
} else { | ||
responseData = response.data; | ||
} | ||
|
||
// Validate that we can query the response data without crashing. | ||
const _json = new JsonStreamStringify(responseData); | ||
Wodann marked this conversation as resolved.
Show resolved
Hide resolved
|
||
}); | ||
}); |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -90,10 +90,9 @@ async function report(benchmarkResultPath) { | |
async function verify(benchmarkResultPath) { | ||
let success = true; | ||
const benchmarkResult = require(benchmarkResultPath); | ||
const snapshotResult = require(path.join( | ||
getScenariosDir(), | ||
SCENARIO_SNAPSHOT_NAME | ||
)); | ||
const snapshotResult = require( | ||
path.join(getScenariosDir(), SCENARIO_SNAPSHOT_NAME), | ||
); | ||
|
||
for (let scenarioName in snapshotResult) { | ||
// TODO https://github.com/NomicFoundation/edr/issues/365 | ||
|
@@ -107,7 +106,7 @@ async function verify(benchmarkResultPath) { | |
if (ratio > NEPTUNE_MAX_MIN_FAILURES) { | ||
console.error( | ||
`Snapshot failure for ${scenarioName} with max/min failure ratio`, | ||
ratio | ||
ratio, | ||
); | ||
success = false; | ||
} | ||
|
@@ -129,16 +128,16 @@ async function verify(benchmarkResultPath) { | |
if (shouldFail.size > 0) { | ||
console.error( | ||
`Scenario ${scenarioName} should fail at indexes ${Array.from( | ||
shouldFail | ||
).sort()}` | ||
shouldFail, | ||
).sort()}`, | ||
); | ||
} | ||
|
||
if (shouldNotFail.size > 0) { | ||
console.error( | ||
`Scenario ${scenarioName} should not fail at indexes ${Array.from( | ||
shouldNotFail | ||
).sort()}` | ||
shouldNotFail, | ||
).sort()}`, | ||
); | ||
} | ||
} | ||
|
@@ -207,7 +206,7 @@ async function benchmarkAllScenarios(outPath, useAnvil) { | |
console.error( | ||
`Total time ${ | ||
Math.round(100 * (totalTime / 1000)) / 100 | ||
} seconds with ${totalFailures} failures.` | ||
} seconds with ${totalFailures} failures.`, | ||
); | ||
|
||
console.error(`Benchmark results written to ${outPath}`); | ||
|
@@ -275,7 +274,7 @@ async function benchmarkScenario(scenarioFileName, useAnvil) { | |
console.error( | ||
`${name} finished in ${ | ||
Math.round(100 * (timeMs / 1000)) / 100 | ||
} seconds with ${failures.length} failures.` | ||
} seconds with ${failures.length} failures.`, | ||
); | ||
|
||
const result = { | ||
|
@@ -331,11 +330,11 @@ function preprocessConfig(config) { | |
config = removeNull(config); | ||
|
||
config.providerConfig.initialDate = new Date( | ||
config.providerConfig.initialDate.secsSinceEpoch * 1000 | ||
config.providerConfig.initialDate.secsSinceEpoch * 1000, | ||
); | ||
|
||
config.providerConfig.hardfork = normalizeHardfork( | ||
config.providerConfig.hardfork | ||
config.providerConfig.hardfork, | ||
); | ||
|
||
// "accounts" in EDR are "genesisAccounts" in Hardhat | ||
|
@@ -345,7 +344,7 @@ function preprocessConfig(config) { | |
config.providerConfig.genesisAccounts = config.providerConfig.accounts.map( | ||
({ balance, secretKey }) => { | ||
return { balance, privateKey: secretKey }; | ||
} | ||
}, | ||
); | ||
delete config.providerConfig.accounts; | ||
|
||
|
@@ -380,6 +379,7 @@ function preprocessConfig(config) { | |
} | ||
|
||
config.providerConfig.minGasPrice = BigInt(config.providerConfig.minGasPrice); | ||
config.providerConfig.enableRip7212 = false; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. All other changes apart from this line were a result of the formatter. I needed to make this change to allow old snapshots to continue working with the newly added configuration field. cc: @agostbiro |
||
|
||
return config; | ||
} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -16,6 +16,9 @@ | |
"pnpm": { | ||
"overrides": { | ||
"hardhat>@nomicfoundation/edr": "workspace:*" | ||
}, | ||
"patchedDependencies": { | ||
"[email protected]": "patches/[email protected]" | ||
} | ||
}, | ||
"engines": { | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In TypeScript, any type in a union with
any
becomesany
(like multiplying by 0 or, more precisely, doing a set union with the universal set). So this is justany
and not ideal.I'm not 100% what the right type would be here. I think
string | object
would work. But if that's not easily doable with N-API, then it's fine for this to beany
for now.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I can look into it but the
serde_json::Value
translates to any