Skip to content

Commit 11d0efe

Browse files
authored
Portal Spec Test Runner + SSZ fixes for BlockHeader Proof types (#736)
* Remove union type * fix tests and utils * fix more tests * remove unused method * Add spec test runner * wip * Add results tabulation and error logs * Revert duplicate code * updates * Rework ssz types for post merge header proofs * fix post capella test * turn off spec test runner for now
1 parent a543709 commit 11d0efe

File tree

8 files changed

+447
-229
lines changed

8 files changed

+447
-229
lines changed

packages/cli/scripts/postCapellaBlockBridge.ts

Lines changed: 244 additions & 200 deletions
Large diffs are not rendered by default.

packages/portalnetwork/src/networks/history/history.ts

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -176,8 +176,9 @@ export class HistoryNetwork extends BaseNetwork {
176176
try {
177177
deserializedProof = AccumulatorProofType.deserialize(proof)
178178
} catch (err: any) {
179-
this.logger(`invalid proof for block ${bytesToHex(header.hash())}`)
180-
throw new Error(`invalid proof for block ${bytesToHex(header.hash())}`)
179+
const msg = `invalid proof for block ${header.number} - ${bytesToHex(header.hash())}`
180+
this.logger(msg)
181+
throw new Error(msg)
181182
}
182183
let validated = false
183184
if ('blockHash' in validation) {
@@ -201,14 +202,17 @@ export class HistoryNetwork extends BaseNetwork {
201202
try {
202203
deserializedProof = HistoricalRootsBlockProof.deserialize(proof)
203204
} catch (err: any) {
204-
this.logger(`invalid proof for block ${bytesToHex(header.hash())}`)
205-
throw new Error(`invalid proof for block ${bytesToHex(header.hash())}`)
205+
const msg = `invalid proof for block ${header.number} - ${bytesToHex(header.hash())}`
206+
this.logger(msg)
207+
throw new Error(msg)
206208
}
207209
let validated = false
208210
try {
209211
validated = verifyPreCapellaHeaderProof(deserializedProof, header.hash())
210212
} catch (err: any) {
211-
this.logger(`Unable to validate proof for post-merge header: ${err.message}`)
213+
const msg = `Unable to validate proof for post-merge header: ${err.message}`
214+
this.logger(msg)
215+
throw new Error(msg)
212216
}
213217
if (!validated) {
214218
throw new Error('Unable to validate proof for post-merge header')
@@ -219,6 +223,7 @@ export class HistoryNetwork extends BaseNetwork {
219223
let deserializedProof: ReturnType<typeof HistoricalSummariesBlockProof.deserialize>
220224
try {
221225
deserializedProof = HistoricalSummariesBlockProof.deserialize(proof)
226+
console.log(HistoricalSummariesBlockProof.toJson(deserializedProof))
222227
} catch (err: any) {
223228
this.logger(`invalid proof for block ${bytesToHex(header.hash())}`)
224229
throw new Error(`invalid proof for block ${bytesToHex(header.hash())}`)

packages/portalnetwork/src/networks/history/types.ts

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -185,23 +185,23 @@ export const BlockNumberKey = new ContainerType({
185185

186186
/** Post-merge pre-Capella block header proof types */
187187
export const SlotType = new UintBigintType(8)
188-
export const BeaconBlockProof = new ListCompositeType(Bytes32Type, 12)
189-
export const HistoricalRootsProof = new VectorCompositeType(Bytes32Type, 14)
188+
export const BeaconBlockProofHistoricalRoots = new VectorCompositeType(Bytes32Type, 14)
189+
export const PostMergeExecutionBlockProof = new VectorCompositeType(Bytes32Type, 11)
190190

191191
export const HistoricalRootsBlockProof = new ContainerType({
192-
beaconBlockProof: BeaconBlockProof,
192+
historicalRootsProof: BeaconBlockProofHistoricalRoots,
193193
beaconBlockRoot: Bytes32Type,
194-
historicalRootsProof: HistoricalRootsProof,
194+
beaconBlockProof: PostMergeExecutionBlockProof,
195195
slot: SlotType,
196196
})
197197

198198
/** Post-Capella block header proof types */
199-
export const HistoricalSummariesProof = new VectorCompositeType(Bytes32Type, 13)
200-
199+
export const PostCapellaExecutionBlockProof = new ListCompositeType(Bytes32Type, 12)
200+
export const BeaconBlockProofHistoricalSummaries = new VectorCompositeType(Bytes32Type, 13)
201201
export const HistoricalSummariesBlockProof = new ContainerType({
202-
beaconBlockProof: BeaconBlockProof,
202+
historicalSummariesProof: BeaconBlockProofHistoricalSummaries,
203203
beaconBlockRoot: Bytes32Type,
204-
historicalSummariesProof: HistoricalSummariesProof,
204+
beaconBlockProof: PostCapellaExecutionBlockProof,
205205
slot: SlotType,
206206
})
207207

packages/portalnetwork/test/integration/postCapellaHeaderProof.spec.ts

Lines changed: 11 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,25 @@
1-
import { readFileSync } from 'fs'
1+
import { SignableENR } from '@chainsafe/enr'
2+
import { concatBytes, hexToBytes } from '@ethereumjs/util'
3+
import { keys } from '@libp2p/crypto'
4+
import { createBeaconConfig } from '@lodestar/config'
5+
import { mainnetChainConfig } from '@lodestar/config/configs'
6+
import { genesisData } from '@lodestar/config/networks'
7+
import { computeEpochAtSlot, getChainForkConfigFromNetwork } from '@lodestar/light-client/utils'
28
import { ssz } from '@lodestar/types'
3-
import { bytesToHex, concatBytes, hexToBytes } from '@ethereumjs/util'
9+
import { multiaddr } from '@multiformats/multiaddr'
10+
import { readFileSync } from 'fs'
11+
import { assert, describe, it, vi } from 'vitest'
412
import {
513
BeaconLightClientNetworkContentType,
6-
BlockHeaderWithProof,
714
HistoricalSummariesKey,
815
HistoricalSummariesWithProof,
916
HistoryNetworkContentType,
1017
LightClientBootstrapKey,
1118
NetworkId,
1219
PortalNetwork,
1320
getBeaconContentKey,
14-
getContentKey,
21+
getContentKey
1522
} from '../../src/index.js'
16-
import { createBeaconConfig } from '@lodestar/config'
17-
import { mainnetChainConfig } from '@lodestar/config/configs'
18-
import { genesisData } from '@lodestar/config/networks'
19-
import { computeEpochAtSlot, getChainForkConfigFromNetwork } from '@lodestar/light-client/utils'
20-
import { assert, describe, it, vi } from 'vitest'
21-
import { multiaddr } from '@multiformats/multiaddr'
22-
import { SignableENR } from '@chainsafe/enr'
23-
import { keys } from '@libp2p/crypto'
2423

2524
describe('Block Bridge Data Test', () => {
2625
it('should store and retrieve block header data', async () => {
@@ -109,7 +108,6 @@ describe('Block Bridge Data Test', () => {
109108
const blockHash = fullBlock.data.message.body.execution_payload.block_hash
110109

111110
const headerKey = getContentKey(HistoryNetworkContentType.BlockHeader, hexToBytes(blockHash))
112-
console.log(bytesToHex(BlockHeaderWithProof.deserialize(hexToBytes(headerWithProof)).header))
113111
await history?.store(headerKey, hexToBytes(headerWithProof))
114112

115113
// Verify block header can be retrieved
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
"0x080000006f020000f90264a01624e3d62872568e41233178997c38dd75fa3baccc89351026beff7026179bfba01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347944838b106fce9647bdf1e7877bf73ce8b0bad5f97a02e065520386645261b7a6153924904a31c07bf6d37bca90fe35b21d864499c05a0eaa4d1ba5182915e8732ddd43e557a6ec713732e72964593275124fddf28a2aba0a9f528fe24151b34969ceb581a59f107d7bf38d8f364072dd50908f326226e71b90100f5bbd9423d137076afdb9b31d2f1db9239cff06e951d0dee6e2d8a302cfb14632d9c2725ed7a08043a5f6e97df7dc311867ba9749f08ed735fbb6a03802ce196f5eed77c97d3bd3dc81ce76fdd61b3ec55cc71d511567e685d0c2cec8c24ffee35856338dae6da75f20f9c8efe338fcffb7d515fde9d7fe7f5305bb4da1a3585a3be7e0e378581692dd9bbfa95b1c7565419eed5eff471ff29296fdd7d733efddfec7f62e4e3fa9eb3f159c609c7dd5f23bdd1b4baff8e283f65ea3d24680bd4cb36f61f00dd5b56ff49bdb77a3d98ee56a7b6438c48dfba4aa5034e6ad3e2e63ff7fccb69b5b492c5ed93cffd71a92e5dddf6ff5161ace6da24c0bb9d00ec5f8084014977bd8401c9c38083f94439846780b05798546974616e2028746974616e6275696c6465722e78797a29a07737f9743d49ab14e32b78a4989729b13bfd28f09c7729669d14c35a3e265d5288000000000000000084bc232c85a01fd1ee2a1b4008ac9f8749a3a5980645510788ccbf0922b77efd67224f8a2234830200008403cc0000a0e1c45ed325063d2380fb27927e7dd5024dada52070c9af2889ee51a105040bcfcc0100003a317faa00ffbca7c1fc9464025ada6aa0db71fd9c050ed433bab104834f0f26c2c38585972af1a49f5358e96748e08f62d9e06883d6d3bbc285f5bbdc3fdde7b87b8f3009f2311f723310e9600cd62f3a0ba54257645d03dbb94700997c2ff1bea5a1dbb8434febb2338912ebd94cfb183cc76b79a1449374aca82445a60e1641d6c37404381727480db4e3e124c9ce9345eb3761882ba31a2444e19f7f4be6446d9460a465986305984f731af89318ee7a965c33d5f6e293bd7c77050a7275d50d2f96d67ae9ec187ef09d1c19c8688ad0a9c00675a55bf61e5e412d5b0b309b9b4a4f9baaa4e6d6cd518eb62f41a133594cab49cf290ffa3570b65376b9dab2632457193d929218ee57d4d2dc8df537f9e2929746d934595d00ff258485173737414bac51a48ca8e03d442cca16a9d9d485ff0447a52427aada4c790ac75fdb8c75ab9503bd1eb67bfb5e2f8f403654b57a1e206aac23546088f1d7284b27d0fc56523cfafc7fb5d233f5faee241365fa4cb0c1d90792d8b558a3d0d5a63121993447a9a5d26cda2d9ad060c8d13f66d84623cc800e3dbb00097ce81a4debe6e5bf0ec7b02915aefabb6e7b1fb8468f228ab04e8e789a26ad9d727ac8430400e0a40000000000f5a23c1219f35dd7d4dc73e816727b6062b26168d5de32a8c44a5e91eeca9f1e04a8748db8fd9595375edfe16660a66ca721079109bd18775eeb54ce07b1400f37d1c9f9c839443ae1aff0d273f1801ffe724770a23d81a63f22f7babe39715d5b5687af2260ef0ed892a68d169c580b03ccb5bc59b0596a4221568de8bf1c7091e37807ec7706f1534c3c24b6664110fa6cf57c3512bb58d4ae666eb8aaed347bdd0fcc693ae5642a10264eb24e1274823f434d66307d9c1cd3c15be194a462b19dea48b339562bd54eac0517d81d7c1d6897ccbf3df258cb75677a235768dfdb56114e00fdd4c1f85c892bf35ac9a89289aaecb1ebd0a96cde606a748b5d7134fd95b5494fd350c63f8bff9d7be30f0bfb3a78daae9f2a0f1ef38f358fd6010000000000000000000000000000000000000000000000000000000000000000f5a5fd42d16a20302798ef6ed309979b43003d2320d9f0e8ea9831a92759fb4b6e1e55e9e4793d44338f52522fd838eb70bd3ac0bb6492923efa3cabf6f5cae3"
1+
"0x080000006f020000f90264a01624e3d62872568e41233178997c38dd75fa3baccc89351026beff7026179bfba01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347944838b106fce9647bdf1e7877bf73ce8b0bad5f97a02e065520386645261b7a6153924904a31c07bf6d37bca90fe35b21d864499c05a0eaa4d1ba5182915e8732ddd43e557a6ec713732e72964593275124fddf28a2aba0a9f528fe24151b34969ceb581a59f107d7bf38d8f364072dd50908f326226e71b90100f5bbd9423d137076afdb9b31d2f1db9239cff06e951d0dee6e2d8a302cfb14632d9c2725ed7a08043a5f6e97df7dc311867ba9749f08ed735fbb6a03802ce196f5eed77c97d3bd3dc81ce76fdd61b3ec55cc71d511567e685d0c2cec8c24ffee35856338dae6da75f20f9c8efe338fcffb7d515fde9d7fe7f5305bb4da1a3585a3be7e0e378581692dd9bbfa95b1c7565419eed5eff471ff29296fdd7d733efddfec7f62e4e3fa9eb3f159c609c7dd5f23bdd1b4baff8e283f65ea3d24680bd4cb36f61f00dd5b56ff49bdb77a3d98ee56a7b6438c48dfba4aa5034e6ad3e2e63ff7fccb69b5b492c5ed93cffd71a92e5dddf6ff5161ace6da24c0bb9d00ec5f8084014977bd8401c9c38083f94439846780b05798546974616e2028746974616e6275696c6465722e78797a29a07737f9743d49ab14e32b78a4989729b13bfd28f09c7729669d14c35a3e265d5288000000000000000084bc232c85a01fd1ee2a1b4008ac9f8749a3a5980645510788ccbf0922b77efd67224f8a2234830200008403cc0000a0e1c45ed325063d2380fb27927e7dd5024dada52070c9af2889ee51a105040bcfc2c38585972af1a49f5358e96748e08f62d9e06883d6d3bbc285f5bbdc3fdde7b87b8f3009f2311f723310e9600cd62f3a0ba54257645d03dbb94700997c2ff1bea5a1dbb8434febb2338912ebd94cfb183cc76b79a1449374aca82445a60e1641d6c37404381727480db4e3e124c9ce9345eb3761882ba31a2444e19f7f4be6446d9460a465986305984f731af89318ee7a965c33d5f6e293bd7c77050a7275d50d2f96d67ae9ec187ef09d1c19c8688ad0a9c00675a55bf61e5e412d5b0b309b9b4a4f9baaa4e6d6cd518eb62f41a133594cab49cf290ffa3570b65376b9dab2632457193d929218ee57d4d2dc8df537f9e2929746d934595d00ff258485173737414bac51a48ca8e03d442cca16a9d9d485ff0447a52427aada4c790ac75fdb8c75ab9503bd1eb67bfb5e2f8f403654b57a1e206aac23546088f1d7284b27d0fc56523cfafc7fb5d233f5faee241365fa4cb0c1d90792d8b558a3d0d5a63121993447a9a5d26cda2d9ad060c8d13f66d84623cc800e3dbb00097ce81a4debe6e5bf0ec7b02915aefabb6e7b1fb8468f228ab04e8e789a26ad9d727ac843043a317faa00ffbca7c1fc9464025ada6aa0db71fd9c050ed433bab104834f0f26cc01000000e0a40000000000f5a23c1219f35dd7d4dc73e816727b6062b26168d5de32a8c44a5e91eeca9f1e04a8748db8fd9595375edfe16660a66ca721079109bd18775eeb54ce07b1400f37d1c9f9c839443ae1aff0d273f1801ffe724770a23d81a63f22f7babe39715d5b5687af2260ef0ed892a68d169c580b03ccb5bc59b0596a4221568de8bf1c7091e37807ec7706f1534c3c24b6664110fa6cf57c3512bb58d4ae666eb8aaed347bdd0fcc693ae5642a10264eb24e1274823f434d66307d9c1cd3c15be194a462b19dea48b339562bd54eac0517d81d7c1d6897ccbf3df258cb75677a235768dfdb56114e00fdd4c1f85c892bf35ac9a89289aaecb1ebd0a96cde606a748b5d7134fd95b5494fd350c63f8bff9d7be30f0bfb3a78daae9f2a0f1ef38f358fd6010000000000000000000000000000000000000000000000000000000000000000f5a5fd42d16a20302798ef6ed309979b43003d2320d9f0e8ea9831a92759fb4b6e1e55e9e4793d44338f52522fd838eb70bd3ac0bb6492923efa3cabf6f5cae3"

packages/portalnetwork/test/networks/history/historyNetwork.spec.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -206,7 +206,7 @@ describe('Header Tests', async () => {
206206
await network.store(headerKey, fakeProof)
207207
assert.fail('should have thrown')
208208
} catch (err: any) {
209-
assert.ok(err.message.includes('invalid proof'))
209+
assert.ok(err.message.includes('Unable to validate proof'))
210210
}
211211
})
212212
})
Lines changed: 171 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,171 @@
1+
/* eslint-disable no-console */
2+
import { bytesToHex, hexToBytes } from '@ethereumjs/util'
3+
import { readFileSync, readdirSync, statSync } from 'fs'
4+
import yaml from 'js-yaml'
5+
import { join, resolve } from 'path'
6+
import { afterAll, beforeAll, describe, it } from 'vitest'
7+
import type { HistoryNetwork } from '../../../src/index.js'
8+
import {
9+
HistoryNetworkContentType,
10+
PortalNetwork,
11+
decodeHistoryNetworkContentKey,
12+
getContentKey,
13+
} from '../../../src/index.js'
14+
15+
describe.skip('should run all spec tests', () => {
16+
// This retrieves all the yaml files from the spec tests directory
17+
const getAllYamlFiles = (dir: string): string[] => {
18+
const files: string[] = []
19+
const items = readdirSync(dir)
20+
21+
for (const item of items) {
22+
const fullPath = join(dir, item)
23+
if (statSync(fullPath).isDirectory()) {
24+
files.push(...getAllYamlFiles(fullPath))
25+
} else if (item.endsWith('.yaml') || item.endsWith('.yml')) {
26+
files.push(fullPath)
27+
}
28+
}
29+
30+
return files
31+
}
32+
33+
const runHistoryTest = async (
34+
history: HistoryNetwork,
35+
contentKey: Uint8Array,
36+
contentValue: Uint8Array,
37+
) => {
38+
try {
39+
// Store the content. `store` parses the content key, deserializes per the content type,
40+
// and then validates the content
41+
await history?.store(contentKey, contentValue)
42+
if (contentKey[0] !== HistoryNetworkContentType.BlockHeaderByNumber) {
43+
// BlockHeaderByNumber requires a conversion to blockhash since we store headers by blockhash in the db
44+
const retrieved = await history?.get(contentKey)
45+
if (retrieved === bytesToHex(contentValue)) {
46+
return true
47+
} else {
48+
return false
49+
}
50+
} else {
51+
const blockNumber = decodeHistoryNetworkContentKey(contentKey)
52+
const hash = history?.blockNumberToHash(blockNumber.keyOpt as bigint)
53+
const hashKey = getContentKey(HistoryNetworkContentType.BlockHeader, hash!)
54+
const retrieved = await history?.get(hashKey)
55+
if (retrieved === bytesToHex(contentValue)) {
56+
return true
57+
} else {
58+
return false
59+
}
60+
}
61+
} catch (e) {
62+
if ('message' in e) {
63+
// If we get an error, return it for triage
64+
return e
65+
} else {
66+
return false
67+
}
68+
}
69+
}
70+
71+
const networkFiles = {
72+
history: {},
73+
state: {},
74+
beacon_chain: {},
75+
}
76+
77+
const results = {
78+
history: {
79+
passed: 0,
80+
failed: 0,
81+
errors: [] as string[],
82+
},
83+
state: {
84+
passed: 0,
85+
failed: 0,
86+
errors: [] as string[],
87+
},
88+
beacon_chain: {
89+
passed: 0,
90+
failed: 0,
91+
errors: [] as string[],
92+
},
93+
}
94+
95+
let yamlFiles: string[] = []
96+
beforeAll(() => {
97+
// Parses all yaml files into JSON objects
98+
const testDir = resolve(__dirname, '../../../../portal-spec-tests/tests')
99+
yamlFiles = getAllYamlFiles(testDir)
100+
101+
for (const file of yamlFiles) {
102+
try {
103+
const content = yaml.load(readFileSync(file, 'utf-8'))
104+
// Split test suites up by network
105+
if (file.includes('/history/')) {
106+
networkFiles.history[file] = content
107+
} else if (file.includes('/state/')) {
108+
networkFiles.state[file] = content
109+
} else if (file.includes('/beacon_chain/')) {
110+
networkFiles.beacon_chain[file] = content
111+
}
112+
} catch (error) {
113+
console.error(`Error reading ${file}:`, error)
114+
}
115+
}
116+
})
117+
it('should run all serialized history spec tests', async () => {
118+
// This test inspects all the `history` test inputs and runs all the ones
119+
// with serialized content keys and values
120+
// The basic idea of the test is can we deserialize the content, store it,
121+
// and then retrieve it using the original content key
122+
const client = await PortalNetwork.create({})
123+
const history = client.network()['0x500b']!
124+
for (const testData of Object.entries(networkFiles.history)) {
125+
// Some test vectors are parsed into a tuple of [file name, [test vector]]
126+
if (Array.isArray(testData) && Array.isArray(testData[1])) {
127+
for (const vector of testData[1]) {
128+
if ('content_key' in vector && 'content_value' in vector) {
129+
const key = hexToBytes(vector.content_key)
130+
const value = hexToBytes(vector.content_value)
131+
const result = await runHistoryTest(history, key, value)
132+
if (result === true) {
133+
results.history.passed++
134+
} else {
135+
results.history.failed++
136+
results.history.errors.push(
137+
`Key: ${bytesToHex(key)} in file ${testData[0]} -- Error: ${result ?? 'no error reported'}`,
138+
)
139+
}
140+
}
141+
}
142+
} else if (
143+
Array.isArray(testData) &&
144+
'content_key' in testData[1] &&
145+
'content_value' in testData[1]
146+
) {
147+
// Some tests are stored as a tuple of [file name, test vector]
148+
const key = hexToBytes(testData[1].content_key as string) // Content key is stored as a hex string
149+
const value = hexToBytes(testData[1].content_value as string) // Content value is stored as a hex string
150+
const result = await runHistoryTest(history, key, value)
151+
if (result === true) {
152+
results.history.passed++
153+
} else {
154+
results.history.failed++
155+
if (typeof result !== 'boolean') {
156+
results.history.errors.push(
157+
`Key: ${bytesToHex(key)} in file ${testData[0]} -- ${result}`,
158+
)
159+
}
160+
}
161+
}
162+
}
163+
})
164+
afterAll(() => {
165+
console.log('--------------------------------')
166+
console.log('History Results')
167+
console.log('--------------------------------')
168+
console.log(results.history)
169+
console.log('--------------------------------')
170+
})
171+
})

0 commit comments

Comments
 (0)