Several spec fixes for HistoricalSummaries + add test-vector test (#2217)
This commit is contained in:
parent
1d4eb78bcf
commit
72912626a2
|
@ -6,15 +6,21 @@
|
||||||
# at your option. This file may not be copied, modified, or distributed except according to those terms.
|
# at your option. This file may not be copied, modified, or distributed except according to those terms.
|
||||||
|
|
||||||
#
|
#
|
||||||
# Example of how the beacon state historical_summaries field could be provided
|
# Implementation of the beacon state historical_summaries provided with a Merkle
|
||||||
# with a Merkle proof that can be verified against the right beacon state root.
|
# proof that can be verified against the right beacon state root.
|
||||||
# These historical_summaries with their proof could for example be provided over
|
#
|
||||||
# the network and verified on the receivers end.
|
# As per spec:
|
||||||
|
# https://github.com/ethereum/portal-network-specs/blob/master/beacon-chain/beacon-network.md#historicalsummaries
|
||||||
#
|
#
|
||||||
|
|
||||||
{.push raises: [].}
|
{.push raises: [].}
|
||||||
|
|
||||||
import stew/results, beacon_chain/spec/forks, beacon_chain/spec/datatypes/capella
|
import
|
||||||
|
stew/arrayops,
|
||||||
|
results,
|
||||||
|
beacon_chain/spec/forks,
|
||||||
|
beacon_chain/spec/datatypes/capella,
|
||||||
|
../../common/common_types
|
||||||
|
|
||||||
export results
|
export results
|
||||||
|
|
||||||
|
@ -22,10 +28,47 @@ type
|
||||||
HistoricalSummaries* = HashList[HistoricalSummary, Limit HISTORICAL_ROOTS_LIMIT]
|
HistoricalSummaries* = HashList[HistoricalSummary, Limit HISTORICAL_ROOTS_LIMIT]
|
||||||
HistoricalSummariesProof* = array[5, Digest]
|
HistoricalSummariesProof* = array[5, Digest]
|
||||||
HistoricalSummariesWithProof* = object
|
HistoricalSummariesWithProof* = object
|
||||||
finalized_slot*: Slot
|
epoch*: Epoch
|
||||||
|
# TODO:
|
||||||
|
# can epoch instead of slot cause any issues? E.g. to verify with right state?
|
||||||
|
# To revise when we fully implement this in validateHistoricalSummaries,
|
||||||
|
# for now follow specification.
|
||||||
|
# finalized_slot*: Slot
|
||||||
historical_summaries*: HistoricalSummaries
|
historical_summaries*: HistoricalSummaries
|
||||||
proof*: HistoricalSummariesProof
|
proof*: HistoricalSummariesProof
|
||||||
|
|
||||||
|
# TODO: prefixing the summaries with the forkDigest is currently not necessary
|
||||||
|
# and might never be. Perhaps we should drop this until it is needed. Propose
|
||||||
|
# spec change?
|
||||||
|
func encodeSsz*(obj: HistoricalSummariesWithProof, forkDigest: ForkDigest): seq[byte] =
|
||||||
|
var res: seq[byte]
|
||||||
|
res.add(distinctBase(forkDigest))
|
||||||
|
res.add(SSZ.encode(obj))
|
||||||
|
|
||||||
|
res
|
||||||
|
|
||||||
|
func decodeSsz*(
|
||||||
|
forkDigests: ForkDigests,
|
||||||
|
data: openArray[byte],
|
||||||
|
T: type HistoricalSummariesWithProof,
|
||||||
|
): Result[HistoricalSummariesWithProof, string] =
|
||||||
|
if len(data) < 4:
|
||||||
|
return
|
||||||
|
Result[HistoricalSummariesWithProof, string].err("Not enough data for forkDigest")
|
||||||
|
|
||||||
|
let
|
||||||
|
forkDigest = ForkDigest(array[4, byte].initCopyFrom(data))
|
||||||
|
contextFork = forkDigests.consensusForkForDigest(forkDigest).valueOr:
|
||||||
|
return Result[HistoricalSummariesWithProof, string].err("Unknown fork")
|
||||||
|
|
||||||
|
if contextFork > ConsensusFork.Bellatrix:
|
||||||
|
# There is only one version of HistoricalSummaries starting since Capella
|
||||||
|
decodeSsz(data.toOpenArray(4, len(data) - 1), HistoricalSummariesWithProof)
|
||||||
|
else:
|
||||||
|
Result[HistoricalSummariesWithProof, string].err(
|
||||||
|
"Invalid Fork for HistoricalSummaries"
|
||||||
|
)
|
||||||
|
|
||||||
func buildProof*(
|
func buildProof*(
|
||||||
state: ForkedHashedBeaconState
|
state: ForkedHashedBeaconState
|
||||||
): Result[HistoricalSummariesProof, string] =
|
): Result[HistoricalSummariesProof, string] =
|
||||||
|
|
|
@ -388,15 +388,19 @@ proc createStoreHandler*(db: BeaconDb): DbStoreHandler =
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
of beacon_content.ContentType.historicalSummaries:
|
of beacon_content.ContentType.historicalSummaries:
|
||||||
# TODO: Its probably better to use the kvstore here and instead use a sql
|
# TODO: Its probably better to not use the kvstore here and instead use a sql
|
||||||
# table with slot as index and move the slot logic to the db store handler.
|
# table with slot as index and move the slot logic to the db store handler.
|
||||||
let current = db.get(contentId)
|
let current = db.get(contentId)
|
||||||
if current.isSome():
|
if current.isSome():
|
||||||
let summariesWithProof =
|
let summariesWithProof = decodeSsz(
|
||||||
decodeSszOrRaise(current.get(), HistoricalSummariesWithProof)
|
db.forkDigests, current.get(), HistoricalSummariesWithProof
|
||||||
let newSummariesWithProof = decodeSsz(content, HistoricalSummariesWithProof).valueOr:
|
).valueOr:
|
||||||
|
raiseAssert error
|
||||||
|
let newSummariesWithProof = decodeSsz(
|
||||||
|
db.forkDigests, content, HistoricalSummariesWithProof
|
||||||
|
).valueOr:
|
||||||
return
|
return
|
||||||
if newSummariesWithProof.finalized_slot > summariesWithProof.finalized_slot:
|
if newSummariesWithProof.epoch > summariesWithProof.epoch:
|
||||||
db.put(contentId, content)
|
db.put(contentId, content)
|
||||||
else:
|
else:
|
||||||
db.put(contentId, content)
|
db.put(contentId, content)
|
||||||
|
|
|
@ -167,7 +167,7 @@ proc getHistoricalSummaries*(
|
||||||
contentKey = historicalSummariesContentKey(epoch)
|
contentKey = historicalSummariesContentKey(epoch)
|
||||||
content = ?await n.getContent(contentKey)
|
content = ?await n.getContent(contentKey)
|
||||||
|
|
||||||
summariesWithProof = decodeSsz(content, HistoricalSummariesWithProof).valueOr:
|
summariesWithProof = decodeSsz(n.forkDigests, content, HistoricalSummariesWithProof).valueOr:
|
||||||
return Opt.none(HistoricalSummaries)
|
return Opt.none(HistoricalSummaries)
|
||||||
|
|
||||||
if n.validateHistoricalSummaries(summariesWithProof).isOk():
|
if n.validateHistoricalSummaries(summariesWithProof).isOk():
|
||||||
|
@ -272,7 +272,8 @@ proc validateContent(
|
||||||
else:
|
else:
|
||||||
ok()
|
ok()
|
||||||
of beacon_content.ContentType.historicalSummaries:
|
of beacon_content.ContentType.historicalSummaries:
|
||||||
let summariesWithProof = ?decodeSsz(content, HistoricalSummariesWithProof)
|
let summariesWithProof =
|
||||||
|
?decodeSsz(n.forkDigests, content, HistoricalSummariesWithProof)
|
||||||
|
|
||||||
n.validateHistoricalSummaries(summariesWithProof)
|
n.validateHistoricalSummaries(summariesWithProof)
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
# Nimbus
|
# fluffy
|
||||||
# Copyright (c) 2022-2024 Status Research & Development GmbH
|
# Copyright (c) 2022-2024 Status Research & Development GmbH
|
||||||
# Licensed under either of
|
# Licensed under either of
|
||||||
# * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0)
|
# * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0)
|
||||||
|
@ -7,4 +7,8 @@
|
||||||
|
|
||||||
{.warning[UnusedImport]: off.}
|
{.warning[UnusedImport]: off.}
|
||||||
|
|
||||||
import ./test_beacon_content, ./test_beacon_network, ./test_beacon_light_client
|
import
|
||||||
|
./test_beacon_content,
|
||||||
|
./test_beacon_historical_summaries,
|
||||||
|
./test_beacon_network,
|
||||||
|
./test_beacon_light_client
|
||||||
|
|
|
@ -0,0 +1,84 @@
|
||||||
|
# fluffy
|
||||||
|
# Copyright (c) 2024 Status Research & Development GmbH
|
||||||
|
# Licensed and distributed under either of
|
||||||
|
# * MIT license (license terms in the root directory or at https://opensource.org/licenses/MIT).
|
||||||
|
# * Apache v2 license (license terms in the root directory or at https://www.apache.org/licenses/LICENSE-2.0).
|
||||||
|
# at your option. This file may not be copied, modified, or distributed except according to those terms.
|
||||||
|
|
||||||
|
{.used.}
|
||||||
|
|
||||||
|
import
|
||||||
|
unittest2,
|
||||||
|
stew/byteutils,
|
||||||
|
results,
|
||||||
|
beacon_chain/networking/network_metadata,
|
||||||
|
beacon_chain/spec/forks,
|
||||||
|
../../network/beacon/[beacon_chain_historical_summaries, beacon_content],
|
||||||
|
../../eth_data/yaml_utils
|
||||||
|
|
||||||
|
type YamlHistoricalSummariesWithProof* = object
|
||||||
|
content_key*: string
|
||||||
|
content_value*: string
|
||||||
|
beacon_state_root*: string
|
||||||
|
historical_summaries_root*: string
|
||||||
|
historical_summaries_state_proof*: array[5, string]
|
||||||
|
epoch*: uint64
|
||||||
|
|
||||||
|
suite "Beacon HistoricalSummariesWithProof":
|
||||||
|
const testVectorDir =
|
||||||
|
"./vendor/portal-spec-tests/tests/mainnet/beacon_chain/historical_summaries_with_proof/deneb/"
|
||||||
|
|
||||||
|
let
|
||||||
|
metadata = getMetadataForNetwork("mainnet")
|
||||||
|
genesisState =
|
||||||
|
try:
|
||||||
|
template genesisData(): auto =
|
||||||
|
metadata.genesis.bakedBytes
|
||||||
|
|
||||||
|
newClone(
|
||||||
|
readSszForkedHashedBeaconState(
|
||||||
|
metadata.cfg, genesisData.toOpenArray(genesisData.low, genesisData.high)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
except CatchableError as err:
|
||||||
|
raiseAssert "Invalid baked-in state: " & err.msg
|
||||||
|
|
||||||
|
# Although the test data is generated from a test state, we need to use the
|
||||||
|
# forkDigests of mainnet as apparently these are used in the generated test vector.
|
||||||
|
genesis_validators_root = getStateField(genesisState[], genesis_validators_root)
|
||||||
|
# genesis_validators_root = Digest.fromHex(
|
||||||
|
# "0x2170688a9e92595fb353c0a2ad6733431a8066c7ecb48ab3b2aaf9091a1722b1"
|
||||||
|
# )
|
||||||
|
forkDigests = newClone ForkDigests.init(metadata.cfg, genesis_validators_root)
|
||||||
|
|
||||||
|
test "HistoricalSummaries Encoding/Decoding and Verification":
|
||||||
|
const file = testVectorDir & "historical_summaries_with_proof.yaml"
|
||||||
|
let
|
||||||
|
testCase = YamlHistoricalSummariesWithProof.loadFromYaml(file).valueOr:
|
||||||
|
raiseAssert "Invalid test vector file: " & error
|
||||||
|
|
||||||
|
contentKeyEncoded = testCase.content_key.hexToSeqByte()
|
||||||
|
contentValueEncoded = testCase.content_value.hexToSeqByte()
|
||||||
|
|
||||||
|
# Decode content and content key
|
||||||
|
contentKey = decodeSsz(contentKeyEncoded, ContentKey)
|
||||||
|
contentValue =
|
||||||
|
decodeSsz(forkDigests[], contentValueEncoded, HistoricalSummariesWithProof)
|
||||||
|
check:
|
||||||
|
contentKey.isOk()
|
||||||
|
contentValue.isOk()
|
||||||
|
|
||||||
|
let summariesWithProof = contentValue.value()
|
||||||
|
let root = hash_tree_root(summariesWithProof.historical_summaries)
|
||||||
|
|
||||||
|
check:
|
||||||
|
root.data == testCase.historical_summaries_root.hexToSeqByte()
|
||||||
|
summariesWithProof.epoch == testCase.epoch
|
||||||
|
verifyProof(summariesWithProof, Digest.fromHex(testCase.beacon_state_root))
|
||||||
|
|
||||||
|
# Encode content and content key
|
||||||
|
let consensusFork = consensusForkAtEpoch(metadata.cfg, summariesWithProof.epoch)
|
||||||
|
let forkDigest = atConsensusFork(forkDigests[], consensusFork)
|
||||||
|
check:
|
||||||
|
encodeSsz(summariesWithProof, forkDigest) == contentValueEncoded
|
||||||
|
encode(contentKey.value()).asSeq() == contentKeyEncoded
|
|
@ -192,6 +192,9 @@ procSuite "Beacon Content Network":
|
||||||
let
|
let
|
||||||
cfg = genesisTestRuntimeConfig(ConsensusFork.Capella)
|
cfg = genesisTestRuntimeConfig(ConsensusFork.Capella)
|
||||||
state = newClone(initGenesisState(cfg = cfg))
|
state = newClone(initGenesisState(cfg = cfg))
|
||||||
|
networkData = loadNetworkData("mainnet")
|
||||||
|
forkDigests = (newClone networkData.forks)[]
|
||||||
|
|
||||||
var cache = StateCache()
|
var cache = StateCache()
|
||||||
|
|
||||||
var blocks: seq[capella.SignedBeaconBlock]
|
var blocks: seq[capella.SignedBeaconBlock]
|
||||||
|
@ -214,21 +217,20 @@ procSuite "Beacon Content Network":
|
||||||
proof = res.get()
|
proof = res.get()
|
||||||
|
|
||||||
historicalSummariesWithProof = HistoricalSummariesWithProof(
|
historicalSummariesWithProof = HistoricalSummariesWithProof(
|
||||||
finalized_slot: forkyState.data.slot,
|
epoch: epoch(forkyState.data.slot),
|
||||||
historical_summaries: historical_summaries,
|
historical_summaries: historical_summaries,
|
||||||
proof: proof,
|
proof: proof,
|
||||||
)
|
)
|
||||||
|
forkDigest = atConsensusFork(forkDigests, consensusFork)
|
||||||
|
|
||||||
content = SSZ.encode(historicalSummariesWithProof)
|
content = encodeSsz(historicalSummariesWithProof, forkDigest)
|
||||||
|
|
||||||
(content, forkyState.data.slot, forkyState.root)
|
(content, forkyState.data.slot, forkyState.root)
|
||||||
else:
|
else:
|
||||||
raiseAssert("Not implemented pre-Capella")
|
raiseAssert("Not implemented pre-Capella")
|
||||||
let
|
let
|
||||||
networkData = loadNetworkData("mainnet")
|
|
||||||
lcNode1 = newLCNode(rng, 20302, networkData)
|
lcNode1 = newLCNode(rng, 20302, networkData)
|
||||||
lcNode2 = newLCNode(rng, 20303, networkData)
|
lcNode2 = newLCNode(rng, 20303, networkData)
|
||||||
forkDigests = (newClone networkData.forks)[]
|
|
||||||
|
|
||||||
check:
|
check:
|
||||||
lcNode1.portalProtocol().addNode(lcNode2.localNode()) == Added
|
lcNode1.portalProtocol().addNode(lcNode2.localNode()) == Added
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
Subproject commit f88b457027f57322f9db5b632547035c4ed9555b
|
Subproject commit 0c2434bd7876673f22f660c69c147c9300acf719
|
Loading…
Reference in New Issue