diff --git a/package-lock.json b/package-lock.json index 9a73a108f..57ed5307e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10260,7 +10260,6 @@ "@types/ws": "^7.4.7", "@vitest/coverage-v8": "^3.0.2", "@vitest/ui": "^3.0.2", - "eslint": "^8.6.0", "js-yaml": "^4.1.0", "tslib": "^2.3.1", "typedoc": "^0.28.0", @@ -10299,223 +10298,6 @@ "optional": true } } - }, - "packages/portalnetwork/node_modules/@eslint/eslintrc": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz", - "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ajv": "^6.12.4", - "debug": "^4.3.2", - "espree": "^9.6.0", - "globals": "^13.19.0", - "ignore": "^5.2.0", - "import-fresh": "^3.2.1", - "js-yaml": "^4.1.0", - "minimatch": "^3.1.2", - "strip-json-comments": "^3.1.1" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "packages/portalnetwork/node_modules/@eslint/js": { - "version": "8.57.1", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.1.tgz", - "integrity": "sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - } - }, - "packages/portalnetwork/node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "packages/portalnetwork/node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "packages/portalnetwork/node_modules/eslint": { - "version": "8.57.1", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.1.tgz", - "integrity": "sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA==", - "deprecated": "This version is no longer supported. Please see https://eslint.org/version-support for other options.", - "dev": true, - "license": "MIT", - "dependencies": { - "@eslint-community/eslint-utils": "^4.2.0", - "@eslint-community/regexpp": "^4.6.1", - "@eslint/eslintrc": "^2.1.4", - "@eslint/js": "8.57.1", - "@humanwhocodes/config-array": "^0.13.0", - "@humanwhocodes/module-importer": "^1.0.1", - "@nodelib/fs.walk": "^1.2.8", - "@ungap/structured-clone": "^1.2.0", - "ajv": "^6.12.4", - "chalk": "^4.0.0", - "cross-spawn": "^7.0.2", - "debug": "^4.3.2", - "doctrine": "^3.0.0", - "escape-string-regexp": "^4.0.0", - "eslint-scope": "^7.2.2", - "eslint-visitor-keys": "^3.4.3", - "espree": "^9.6.1", - "esquery": "^1.4.2", - "esutils": "^2.0.2", - "fast-deep-equal": "^3.1.3", - "file-entry-cache": "^6.0.1", - "find-up": "^5.0.0", - "glob-parent": "^6.0.2", - "globals": "^13.19.0", - "graphemer": "^1.4.0", - "ignore": "^5.2.0", - "imurmurhash": "^0.1.4", - "is-glob": "^4.0.0", - "is-path-inside": "^3.0.3", - "js-yaml": "^4.1.0", - "json-stable-stringify-without-jsonify": "^1.0.1", - "levn": "^0.4.1", - "lodash.merge": "^4.6.2", - "minimatch": "^3.1.2", - "natural-compare": "^1.4.0", - "optionator": "^0.9.3", - "strip-ansi": "^6.0.1", - "text-table": "^0.2.0" - }, - "bin": { - "eslint": "bin/eslint.js" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "packages/portalnetwork/node_modules/eslint-scope": { - "version": "7.2.2", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", - "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "esrecurse": "^4.3.0", - "estraverse": "^5.2.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "packages/portalnetwork/node_modules/espree": { - "version": "9.6.1", - "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", - "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "acorn": "^8.9.0", - "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^3.4.1" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "packages/portalnetwork/node_modules/file-entry-cache": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", - "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", - "dev": true, - "license": "MIT", - "dependencies": { - "flat-cache": "^3.0.4" - }, - "engines": { - "node": "^10.12.0 || >=12.0.0" - } - }, - "packages/portalnetwork/node_modules/flat-cache": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz", - "integrity": "sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==", - "dev": true, - "license": "MIT", - "dependencies": { - "flatted": "^3.2.9", - "keyv": "^4.5.3", - "rimraf": "^3.0.2" - }, - "engines": { - "node": "^10.12.0 || >=12.0.0" - } - }, - "packages/portalnetwork/node_modules/globals": { - "version": "13.24.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", - "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "type-fest": "^0.20.2" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "packages/portalnetwork/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "packages/portalnetwork/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } } } } diff --git a/packages/portal-spec-tests b/packages/portal-spec-tests index 90b2c7f58..9ca6dfb5e 160000 --- a/packages/portal-spec-tests +++ b/packages/portal-spec-tests @@ -1 +1 @@ -Subproject commit 90b2c7f589d1bd499b7784aa742c6ceebfcf73b8 +Subproject commit 9ca6dfb5e4ae623be4d7d5a4ac5b80dcd2bdde2d diff --git a/packages/portalnetwork/package.json b/packages/portalnetwork/package.json index 4980f34aa..33427a7a3 100644 --- a/packages/portalnetwork/package.json +++ b/packages/portalnetwork/package.json @@ -68,7 +68,6 @@ "@types/ws": "^7.4.7", "@vitest/coverage-v8": "^3.0.2", "@vitest/ui": "^3.0.2", - "eslint": "^8.6.0", "js-yaml": "^4.1.0", "tslib": "^2.3.1", "typedoc": "^0.28.0", @@ -104,4 +103,4 @@ "optional": true } } -} +} \ No newline at end of file diff --git a/packages/portalnetwork/src/networks/history/history.ts b/packages/portalnetwork/src/networks/history/history.ts index a36db0219..e9ab4b7cd 100644 --- a/packages/portalnetwork/src/networks/history/history.ts +++ b/packages/portalnetwork/src/networks/history/history.ts @@ -3,21 +3,20 @@ import { Block, createBlockHeaderFromRLP } from '@ethereumjs/block' import { bytesToHex, bytesToInt, concatBytes, equalsBytes, hexToBytes } from '@ethereumjs/util' import debug from 'debug' -import type { - BaseNetworkConfig, - ContentLookupResponse, - EphemeralHeaderKeyValues, - FindContentMessage, - INodeAddress, -} from '../../index.js' import { + type BaseNetworkConfig, BasicRadius, BiMap, ClientInfoAndCapabilities, + type ContentLookupResponse, ContentMessageType, + type EphemeralHeaderKeyValues, + type FindContentMessage, FoundContent, - HistoricalSummariesBlockProof, + HistoricalSummariesBlockProofCapella, + HistoricalSummariesBlockProofDeneb, HistoryRadius, + type INodeAddress, MAX_UDP_PACKET_SIZE, MessageCodes, PortalWireMessageType, @@ -39,6 +38,7 @@ import { AccumulatorProofType, BlockHeaderWithProof, BlockNumberKey, + CANCUN_BLOCK, EphemeralHeaderPayload, HistoricalRootsBlockProof, HistoryNetworkContentType, @@ -187,7 +187,7 @@ export class HistoryNetwork extends BaseNetwork { } const proof = headerProof.proof - if (header.number < MERGE_BLOCK) { + if (header.number <= MERGE_BLOCK) { let deserializedProof: Uint8Array[] try { deserializedProof = AccumulatorProofType.deserialize(proof) @@ -235,10 +235,16 @@ export class HistoryNetwork extends BaseNetwork { } } else { // TODO: Check proof slot to ensure header is from previous sync period and handle ephemeral headers separately - - let deserializedProof: ReturnType + let deserializedProof: ReturnType< + | typeof HistoricalSummariesBlockProofCapella.deserialize + | typeof HistoricalSummariesBlockProofDeneb.deserialize + > try { - deserializedProof = HistoricalSummariesBlockProof.deserialize(proof) + if (header.number < CANCUN_BLOCK) { + deserializedProof = HistoricalSummariesBlockProofCapella.deserialize(proof) + } else { + deserializedProof = HistoricalSummariesBlockProofDeneb.deserialize(proof) + } } catch (err: any) { this.logger(`invalid proof for block ${bytesToHex(header.hash())}`) throw new Error(`invalid proof for block ${bytesToHex(header.hash())}`) diff --git a/packages/portalnetwork/src/networks/history/types.ts b/packages/portalnetwork/src/networks/history/types.ts index e9851adce..7d10633ce 100644 --- a/packages/portalnetwork/src/networks/history/types.ts +++ b/packages/portalnetwork/src/networks/history/types.ts @@ -27,6 +27,7 @@ export const MAX_ENCODED_UNCLES_LENGTH = 131072 // MAX_HEADER_LENGTH * 2 ** 4 export const MAX_HEADER_PROOF_LENGTH = 1024 export const MERGE_BLOCK = 15537393n export const SHANGHAI_BLOCK = 17034871n +export const CANCUN_BLOCK = 19426587n export const CAPELLA_ERA = 758 // The era/period in which the Capella fork happened on CL @@ -190,22 +191,30 @@ export const BlockNumberKey = new ContainerType({ /** Post-merge pre-Capella block header proof types */ export const SlotType = new UintBigintType(8) export const BeaconBlockProofHistoricalRoots = new VectorCompositeType(Bytes32Type, 14) -export const PostMergeExecutionBlockProof = new VectorCompositeType(Bytes32Type, 11) - +export const ExecutionBlockProofBellatrix = new VectorCompositeType(Bytes32Type, 11) export const HistoricalRootsBlockProof = new ContainerType({ - historicalRootsProof: BeaconBlockProofHistoricalRoots, + beaconBlockProof: BeaconBlockProofHistoricalRoots, beaconBlockRoot: Bytes32Type, - beaconBlockProof: PostMergeExecutionBlockProof, + executionBlockProof: ExecutionBlockProofBellatrix, slot: SlotType, }) /** Post-Capella block header proof types */ -export const PostCapellaExecutionBlockProof = new ListCompositeType(Bytes32Type, 12) export const BeaconBlockProofHistoricalSummaries = new VectorCompositeType(Bytes32Type, 13) -export const HistoricalSummariesBlockProof = new ContainerType({ - historicalSummariesProof: BeaconBlockProofHistoricalSummaries, + +export const HistoricalSummariesBlockProofCapella = new ContainerType({ + beaconBlockProof: BeaconBlockProofHistoricalSummaries, + beaconBlockRoot: Bytes32Type, + executionBlockProof: ExecutionBlockProofBellatrix, + slot: SlotType, +}) + +/** Post-Deneb block header proof types */ +export const PostDenebExecutionBlockProof = new VectorCompositeType(Bytes32Type, 12) +export const HistoricalSummariesBlockProofDeneb = new ContainerType({ + beaconBlockProof: BeaconBlockProofHistoricalSummaries, beaconBlockRoot: Bytes32Type, - beaconBlockProof: PostCapellaExecutionBlockProof, + executionBlockProof: PostDenebExecutionBlockProof, slot: SlotType, }) diff --git a/packages/portalnetwork/src/networks/history/util.ts b/packages/portalnetwork/src/networks/history/util.ts index d21c93b8a..7a370e64f 100644 --- a/packages/portalnetwork/src/networks/history/util.ts +++ b/packages/portalnetwork/src/networks/history/util.ts @@ -19,6 +19,7 @@ import { CAPELLA_ERA, EphemeralHeaderKey, EpochAccumulator, + HistoricalRootsBlockProof, HistoryNetworkContentType, MERGE_BLOCK, PostShanghaiBlockBody, @@ -322,13 +323,9 @@ export const verifyPreMergeHeaderProof = ( } } +HistoricalRootsBlockProof.fields export const verifyPreCapellaHeaderProof = ( - proof: ValueOfFields<{ - beaconBlockProof: VectorCompositeType - beaconBlockRoot: ByteVectorType - historicalRootsProof: VectorCompositeType - slot: UintBigintType - }>, + proof: ValueOfFields, elBlockHash: Uint8Array, ) => { const batchIndex = slotToHistoricalBatchIndex(proof.slot) @@ -337,7 +334,7 @@ export const verifyPreCapellaHeaderProof = ( Number(batchIndex), ]) const reconstructedBatch = ssz.phase0.HistoricalBatch.createFromProof({ - witnesses: proof.historicalRootsProof, + witnesses: proof.beaconBlockProof, type: ProofType.single, gindex: historicalRootsPath.gindex, leaf: proof.beaconBlockRoot, // This should be the leaf value this proof is verifying @@ -347,8 +344,9 @@ export const verifyPreCapellaHeaderProof = ( reconstructedBatch.hashTreeRoot(), hexToBytes(historicalRoots[Number(slotToHistoricalBatch(proof.slot))]), ) === false - ) + ) { return false + } const elBlockHashPath = ssz.bellatrix.BeaconBlock.getPathInfo([ 'body', @@ -362,15 +360,17 @@ export const verifyPreCapellaHeaderProof = ( leaf: elBlockHash, }) - if (equalsBytes(reconstructedBlock.hashTreeRoot(), proof.beaconBlockRoot) === false) return false + if (equalsBytes(reconstructedBlock.hashTreeRoot(), proof.beaconBlockRoot) === false) { + return false + } return true } export const verifyPostCapellaHeaderProof = ( proof: ValueOfFields<{ - beaconBlockProof: ListCompositeType + executionBlockProof: ListCompositeType beaconBlockRoot: ByteVectorType - historicalSummariesProof: VectorCompositeType + beaconBlockProof: VectorCompositeType slot: UintBigintType }>, elBlockHash: Uint8Array, @@ -383,7 +383,7 @@ export const verifyPostCapellaHeaderProof = ( Number(eraIndex), ]) const reconstructedBatch = ssz[forkName].BeaconState.fields.blockRoots.createFromProof({ - witnesses: proof.historicalSummariesProof, + witnesses: proof.beaconBlockProof, type: ProofType.single, gindex: historicalSummariesPath.gindex, leaf: proof.beaconBlockRoot, // This should be the leaf value this proof is verifying diff --git a/packages/portalnetwork/test/networks/history/headerProof.spec.ts b/packages/portalnetwork/test/networks/history/headerProof.spec.ts index f98d5e725..234481d13 100644 --- a/packages/portalnetwork/test/networks/history/headerProof.spec.ts +++ b/packages/portalnetwork/test/networks/history/headerProof.spec.ts @@ -1,12 +1,12 @@ -import { readFileSync } from 'fs' -import { createRequire } from 'module' -import { resolve } from 'path' import { ProofType, createProof } from '@chainsafe/persistent-merkle-tree' import { createBlockHeaderFromRLP } from '@ethereumjs/block' import { bytesToHex, hexToBytes } from '@ethereumjs/util' import { createChainForkConfig } from '@lodestar/config' import { ssz } from '@lodestar/types' +import { readFileSync } from 'fs' import yaml from 'js-yaml' +import { createRequire } from 'module' +import { resolve } from 'path' import { assert, beforeAll, describe, it } from 'vitest' import { @@ -14,15 +14,12 @@ import { HeaderRecordType, HistoricalEpochsType, HistoricalRootsBlockProof, - HistoricalSummariesBlockProof, + HistoricalSummariesBlockProofDeneb, blockNumberToGindex, blockNumberToLeafIndex, - slotToHistoricalBatch, - slotToHistoricalBatchIndex, verifyPostCapellaHeaderProof, verifyPreCapellaHeaderProof, } from '../../../src/index.js' -import { historicalRoots } from '../../../src/networks/history/data/historicalRoots.js' import type { SingleProof } from '@chainsafe/persistent-merkle-tree' import type { ByteVectorType, ContainerType, UintBigintType } from '@chainsafe/ssz' @@ -156,39 +153,13 @@ describe('Bellatrix - Capella header proof tests', () => { it('should deserialize proof', () => { const postMergeProof = HistoricalRootsBlockProof.fromJson(postMergeProofJson) assert.equal(postMergeProof.slot, 4700013n) - const batchIndex = slotToHistoricalBatchIndex(postMergeProof.slot) - // The index of the merge block blockRoot in the historical batch for historical batch/era 574 (where the merge occurred) - const historicalRootsPath = ssz.phase0.HistoricalBatch.getPathInfo([ - 'blockRoots', - Number(batchIndex), - ]) - const reconstructedBatch = ssz.phase0.HistoricalBatch.createFromProof({ - witnesses: postMergeProof.historicalRootsProof, - type: ProofType.single, - gindex: historicalRootsPath.gindex, - leaf: postMergeProof.beaconBlockRoot, // This should be the leaf value this proof is verifying - }) - assert.deepEqual( - reconstructedBatch.hashTreeRoot(), - hexToBytes(historicalRoots[Number(slotToHistoricalBatch(postMergeProof.slot))]), - ) - const elBlockHashPath = ssz.bellatrix.BeaconBlock.getPathInfo([ - 'body', - 'executionPayload', - 'blockHash', - ]) - const mergeBlockElBlockHash = hexToBytes( - '0x56a9bb0302da44b8c0b3df540781424684c3af04d0b7a38d72842b762076a664', + assert.isTrue( + verifyPreCapellaHeaderProof( + postMergeProof, + hexToBytes('0x56a9bb0302da44b8c0b3df540781424684c3af04d0b7a38d72842b762076a664'), // Merge Block EL Blockhash + ), ) - const reconstructedBlock = ssz.bellatrix.BeaconBlock.createFromProof({ - witnesses: postMergeProof.beaconBlockProof, - type: ProofType.single, - gindex: elBlockHashPath.gindex, - leaf: mergeBlockElBlockHash, - }) - - assert.deepEqual(reconstructedBlock.hashTreeRoot(), postMergeProof.beaconBlockRoot) }) it('should verify a fluffy proof', () => { @@ -203,13 +174,13 @@ describe('Bellatrix - Capella header proof tests', () => { slot: string } = yaml.load(testString) as any const fluffyProof = HistoricalRootsBlockProof.fromJson({ - beaconBlockProof: testVector.beacon_block_proof, - historicalRootsProof: testVector.historical_roots_proof, + executionBlockProof: testVector.beacon_block_proof, + beaconBlockProof: testVector.historical_roots_proof, slot: testVector.slot, beaconBlockRoot: testVector.beacon_block_root, executionBlockHeader: testVector.execution_block_header, }) - assert.ok( + assert.isTrue( verifyPreCapellaHeaderProof(fluffyProof, hexToBytes(testVector.execution_block_header)), ) }) @@ -222,7 +193,7 @@ describe('it should verify a post-Capella header proof', () => { proof = await import('./testData/slot9682944Proof.json') }) it('should instantiate a proof from json', () => { - const headerProof = HistoricalSummariesBlockProof.fromJson(proof) + const headerProof = HistoricalSummariesBlockProofDeneb.fromJson(proof) assert.equal(headerProof.slot, proof.slot) }) it('should verify a post-capella header proof', async () => { @@ -232,8 +203,8 @@ describe('it should verify a post-Capella header proof', () => { historicalSummariesJson.default, ) - const headerProof = HistoricalSummariesBlockProof.fromJson(proof) - assert.ok( + const headerProof = HistoricalSummariesBlockProofDeneb.fromJson(proof) + assert.isTrue( verifyPostCapellaHeaderProof( headerProof, hexToBytes('0xb2044cada59c3479ed264454466610e84fa852547138ccc12a874e921779a983'), diff --git a/packages/portalnetwork/test/networks/history/spec-test-runner.spec.ts b/packages/portalnetwork/test/networks/history/spec-test-runner.spec.ts index a87690fd6..dcc949c67 100644 --- a/packages/portalnetwork/test/networks/history/spec-test-runner.spec.ts +++ b/packages/portalnetwork/test/networks/history/spec-test-runner.spec.ts @@ -5,13 +5,26 @@ import yaml from 'js-yaml' import { afterAll, beforeAll, describe, it } from 'vitest' import type { HistoryNetwork } from '../../../src/index.js' import { + HistoricalRootsBlockProof, + HistoricalSummariesBlockProofCapella, + HistoricalSummariesBlockProofDeneb, HistoryNetworkContentType, createPortalNetwork, decodeHistoryNetworkContentKey, getContentKey, + verifyPostCapellaHeaderProof, + verifyPreCapellaHeaderProof, } from '../../../src/index.js' +import { createChainForkConfig } from '@lodestar/config' +import type { BeaconBlock } from '@lodestar/types' +import { ssz } from '@lodestar/types' +import type { ForkName } from '@lodestar/params' +import { + createBlockFromExecutionPayload, + executionPayloadFromBeaconPayload, +} from '@ethereumjs/block' -describe.skip('should run all spec tests', () => { +describe('should run all spec tests', () => { // This retrieves all the yaml files from the spec tests directory const getAllYamlFiles = (dir: string): string[] => { const files: string[] = [] @@ -29,7 +42,7 @@ describe.skip('should run all spec tests', () => { return files } - const runHistoryTest = async ( + const runHistorySerializedTestVectorTest = async ( history: HistoryNetwork, contentKey: Uint8Array, contentValue: Uint8Array, @@ -67,6 +80,109 @@ describe.skip('should run all spec tests', () => { } } + const runHistoryJsonTestVectorTest = async ( + fileName: string, + testVector: any, + ): Promise => { + try { + // 1. Extract block number from filename + const blockNumberMatch = fileName.match(/beacon_block_proof-(\d+)./) + if (!blockNumberMatch) { + throw new Error(`Could not extract block number from file name: ${fileName}`) + } + const blockNumber = blockNumberMatch[1] + const forkMatch = fileName.match( + /headers_with_proof\/block_proofs_([^/]+)\/beacon_block_proof/, + ) + const hardfork = (forkMatch ? forkMatch[1] : null) as + | ForkName.bellatrix + | ForkName.capella + | ForkName.deneb + if (hardfork === null) { + throw new Error(`Could not extract hardfork from file name: ${fileName}`) + } + // 2. Extract parent directory and construct beacon_data path + const parentDirMatch = fileName.match(/(.*headers_with_proof)/) + if (!parentDirMatch) { + throw new Error(`Could not extract parent directory from file name: ${fileName}`) + } + const beaconDataPath = `${parentDirMatch[1]}/beacon_data/${blockNumber}` + + // 3. Load the appropriate files based on the beacon data path + const blockSszPath = resolve(beaconDataPath, 'block.ssz') + const blockSsz = readFileSync(blockSszPath) + const beaconBlockData = ssz[hardfork].BeaconBlock.deserialize(blockSsz) as BeaconBlock< + ForkName.bellatrix | ForkName.capella | ForkName.deneb + > + const executionPayload = executionPayloadFromBeaconPayload( + ssz[hardfork].ExecutionPayload.toJson(beaconBlockData.body.executionPayload), + ) + const executionHeader = ( + await createBlockFromExecutionPayload(executionPayload, { + setHardfork: true, + }) + ).header.serialize() + const historicalBatchPath = resolve(beaconDataPath, 'historicalBatch.ssz') + const beaconStatePath = resolve(beaconDataPath, 'beacon_state.ssz') + + // Determine the type of proof by checking which files exist + const isPostCapella = + ['deneb', 'capella'].includes(hardfork.toLowerCase()) || + (statSync(beaconStatePath, { throwIfNoEntry: false })?.isFile() ?? false) + + const isPreCapella = ['bellatrix', 'merge'].includes(hardfork.toLowerCase()) + + const hasHistoricalBatch = + isPreCapella || + (statSync(historicalBatchPath, { throwIfNoEntry: false })?.isFile() ?? false) + + // 4. Load the proof from test data + if (isPostCapella) { + // Post-Capella proof + const proofData = testVector + proofData.historicalSummariesProof = proofData.beacon_block_proof + proofData.beaconBlockRoot = proofData.beacon_block_root + proofData.beaconBlockProof = proofData.execution_block_proof + const proof = hardfork === 'capella' ? HistoricalSummariesBlockProofCapella.fromJson(proofData) : HistoricalSummariesBlockProofDeneb.fromJson(proofData) + + // Load beacon state + const stateBytes = readFileSync(beaconStatePath) + const historicalSummaries = + ssz.deneb.BeaconState.fields.historicalSummaries.deserialize(stateBytes) + + // 5. Verify the proof + const forkConfig = createChainForkConfig({}) + + if (verifyPostCapellaHeaderProof(proof, executionHeader, historicalSummaries, forkConfig)) { + return true + } else { + return `Failed to verify post-Capella proof for ${fileName}` + } + } else if (hasHistoricalBatch) { + // Pre-Capella (post-Merge) proof + const proofData = testVector + proofData.historicalRootsProof = proofData.beacon_block_proof + proofData.beaconBlockRoot = proofData.beacon_block_root + proofData.beaconBlockProof = proofData.execution_block_proof + console.log(proofData) + const proof = HistoricalRootsBlockProof.fromJson(proofData) + + // 5. Verify the proof + if (verifyPreCapellaHeaderProof(proof, executionHeader)) { + return true + } else { + return `Failed to verify pre-Capella proof for ${fileName}` + } + } else { + // Unknown proof type + return `Unknown proof type for ${fileName}` + } + } catch (error) { + console.log(error) + return `Error processing ${fileName}: ${error.message}` + } + } + const networkFiles = { history: {}, state: {}, @@ -77,6 +193,7 @@ describe.skip('should run all spec tests', () => { history: { passed: 0, failed: 0, + unknown: [] as string[], errors: [] as string[], }, state: { @@ -127,7 +244,7 @@ describe.skip('should run all spec tests', () => { if ('content_key' in vector && 'content_value' in vector) { const key = hexToBytes(vector.content_key) const value = hexToBytes(vector.content_value) - const result = await runHistoryTest(history, key, value) + const result = await runHistorySerializedTestVectorTest(history, key, value) if (result === true) { results.history.passed++ } else { @@ -146,7 +263,7 @@ describe.skip('should run all spec tests', () => { // Some tests are stored as a tuple of [file name, test vector] const key = hexToBytes(testData[1].content_key as string) // Content key is stored as a hex string const value = hexToBytes(testData[1].content_value as string) // Content value is stored as a hex string - const result = await runHistoryTest(history, key, value) + const result = await runHistorySerializedTestVectorTest(history, key, value) if (result === true) { results.history.passed++ } else { @@ -157,6 +274,16 @@ describe.skip('should run all spec tests', () => { ) } } + } else if ('execution_block_header' in testData[1]) { + // const result = await runHistoryJsonTestVectorTest(testData[0], testData[1]) + // if (result === true) { + // results.history.passed++ + // } else { + // results.history.failed++ + // results.history.errors.push(result) + // } + } else { + results.history.unknown.push(testData[0]) } } }) diff --git a/packages/portalnetwork/test/networks/history/testData/mergeBlockHeaderProof.json b/packages/portalnetwork/test/networks/history/testData/mergeBlockHeaderProof.json index f150d1a17..13f4ff7c1 100644 --- a/packages/portalnetwork/test/networks/history/testData/mergeBlockHeaderProof.json +++ b/packages/portalnetwork/test/networks/history/testData/mergeBlockHeaderProof.json @@ -1,5 +1,5 @@ { - "beaconBlockProof": [ + "executionBlockProof": [ "0xf9ef008aaf996dccd1c871c7e937f25d66e057e52773fbe2497090c114231acf", "0xf5a5fd42d16a20302798ef6ed309979b43003d2320d9f0e8ea9831a92759fb4b", "0xc509278046c6beda4e1bd6ad1fc633a601c165d0e4d2e785779cb7795b46e973", @@ -13,7 +13,7 @@ "0xdddc5486061cefe3acad95f32d73ebf53a2ad92b59d1ef346f8f983192f3090a" ], "beaconBlockRoot": "0x810a00400a80cdffc11ffdcf17ac404ac4dba215b95221955a9dfddf163d0b0d", - "historicalRootsProof": [ + "beaconBlockProof": [ "0x60e751f7d2cf0ae24b195bda37e9add56a7d8c4b75469c018c0f912518c3bae8", "0x6c4c3a3055eec20335724f435b2d663c45f0c2fb43e5da8fef7e57e2568dcc27", "0x534654a8d9c0f2f0634323a5a7e67005827cb3b6dbb90a4d8b7f2d38f187f255", @@ -30,4 +30,4 @@ "0x817d7dd0e5dfd75df1adc31cb2ed462e373f8dbec954ae3031ae5b8ff98a419a" ], "slot": "4700013" -} +} \ No newline at end of file