mirror of
https://github.com/status-im/nimbus-eth1.git
synced 2025-01-11 21:04:11 +00:00
Add experimental beacon chain block proofs for Portal network (#1376)
* Add experimental beacon chain block proofs for Portal network * Add Caveat on proving recent blocks
This commit is contained in:
parent
1bfbf4f02f
commit
624c87d8b3
207
fluffy/network/history/experimental/beacon_chain_block_proof.nim
Normal file
207
fluffy/network/history/experimental/beacon_chain_block_proof.nim
Normal file
@ -0,0 +1,207 @@
|
||||
# Nimbus
|
||||
# Copyright (c) 2022 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.
|
||||
|
||||
# This is a PoC of how execution block headers in the Portal history network
|
||||
# could be proven to be part of the canonical chain by means of a proof that
|
||||
# exists out a chain of proofs.
|
||||
#
|
||||
# To verify this proof you need access to the BeaconState field
|
||||
# historical_roots and the block hash of the execution block.
|
||||
#
|
||||
# The chain traverses from proving that the block hash is the one of the
|
||||
# ExecutionPayload in the BeaconBlockBody, to proving that this BeaconBlockBody
|
||||
# is the one that is rooted in the BeaconBlockHeader, to proving that this
|
||||
# BeaconBlockHeader is rooted in the historical_roots.
|
||||
#
|
||||
# TODO: The middle proof is perhaps a bit silly as it doesn't win much space
|
||||
# compared to just providing the BeaconHeader.
|
||||
#
|
||||
# Requirements:
|
||||
#
|
||||
# For building the proofs:
|
||||
# - Node that has access to all the beacon chain data (state and blocks) and
|
||||
# - it will be required to rebuild the HistoricalBatches.
|
||||
#
|
||||
# For verifying the proofs:
|
||||
# - As mentioned, the historical_roots field of the state is required. This
|
||||
# is currently in no way available over any of the consensus layer libp2p
|
||||
# protocols. Thus a light client cannot really be build using these proofs,
|
||||
# which makes it rather useless for now.
|
||||
#
|
||||
# Caveat:
|
||||
#
|
||||
# Roots in historical_roots are only added every `SLOTS_PER_HISTORICAL_ROOT`
|
||||
# slots. Recent blocks that are not part of a historical_root cannot be proven
|
||||
# through this mechanism. They need to be directly looked up in the block_roots
|
||||
# BeaconState field.
|
||||
#
|
||||
# Alternative:
|
||||
#
|
||||
# This PoC is written with the idea of keeping execution BlockHeaders and
|
||||
# BlockBodies available in the Portal history network in the same way post-merge
|
||||
# as it is pre-merge. One could also simply decide to store the BeaconBlocks or
|
||||
# BeaconBlockHeaders and BeaconBlockBodies directly. And get the execution
|
||||
# payloads from there. This would require only 1 (or two, depending on what you
|
||||
# store) of the proofs and might be more convenient if you want to / need to
|
||||
# store the beacon data also on the network. It would require some rebuilding
|
||||
# the structure of the Execution BlockHeader.
|
||||
#
|
||||
# Alternative ii:
|
||||
#
|
||||
# Verifying a specific block could also be done by making use of the
|
||||
# LightClientUpdates. Picking the closest update, and walking back blocks from
|
||||
# that block to the specific block. How much data is required to download would
|
||||
# depend on the location of the block, but it could be quite significant.
|
||||
# Of course, this again could be thrown in some accumulator, but that would
|
||||
# then be required to be stored on the state to make it easy verifiable.
|
||||
# A PoC of this process would be nice and it could be more useful for a system
|
||||
# like the Nimbus verified proxy.
|
||||
#
|
||||
#
|
||||
# The usage of this PoC can be seen in
|
||||
# ./fluffy/tests/test_beacon_chain_block_proof.nim
|
||||
#
|
||||
# TODO: Probably needs to make usage of forks instead of just bellatrix.
|
||||
#
|
||||
|
||||
{.used.}
|
||||
|
||||
{.push raises: [Defect].}
|
||||
|
||||
import
|
||||
stew/results,
|
||||
ssz_serialization, ssz_serialization/[proofs, merkleization],
|
||||
beacon_chain/spec/eth2_ssz_serialization,
|
||||
beacon_chain/spec/datatypes/bellatrix
|
||||
|
||||
type
|
||||
BeaconBlockBodyProof* = array[8, Digest]
|
||||
BeaconBlockHeaderProof* = array[3, Digest]
|
||||
HistoricalRootsProof* = array[14, Digest]
|
||||
|
||||
BeaconChainBlockProof* = object
|
||||
# Total size (8 + 1 + 3 + 1 + 14) * 32 bytes + 4 bytes = 868 bytes
|
||||
beaconBlockBodyProof: BeaconBlockBodyProof
|
||||
beaconBlockBodyRoot: Digest
|
||||
beaconBlockHeaderProof: BeaconBlockHeaderProof
|
||||
beaconBlockHeaderRoot: Digest
|
||||
historicalRootsProof: HistoricalRootsProof
|
||||
slot: Slot
|
||||
|
||||
func getHistoricalRootsIndex*(slot: Slot): uint64 =
|
||||
slot div SLOTS_PER_HISTORICAL_ROOT
|
||||
|
||||
func getHistoricalRootsIndex*(blockHeader: BeaconBlockHeader): uint64 =
|
||||
getHistoricalRootsIndex(blockHeader.slot)
|
||||
|
||||
func getBlockRootsIndex*(
|
||||
slot: Slot, historicalRootIndex: uint64): uint64 =
|
||||
uint64(slot - historicalRootIndex * SLOTS_PER_HISTORICAL_ROOT)
|
||||
|
||||
func getBlockRootsIndex*(
|
||||
blockHeader: BeaconBlockHeader, historicalRootIndex: uint64): uint64 =
|
||||
getBlockRootsIndex(blockHeader.slot, historicalRootIndex)
|
||||
|
||||
func buildProof*(
|
||||
blockBody: bellatrix.BeaconBlockBody): Result[BeaconBlockBodyProof, string] =
|
||||
# 16 as there are 10 fields
|
||||
# 9 as index (pos) of field = 9
|
||||
let gIndexTopLevel = (1 * 1 * 16 + 9)
|
||||
# 16 as there are 14 fields
|
||||
# 12 as pos of field = 12
|
||||
let gIndex = GeneralizedIndex(gIndexTopLevel * 1 * 16 + 12)
|
||||
|
||||
var proof: BeaconBlockBodyProof
|
||||
? blockBody.build_proof(gIndex, proof)
|
||||
|
||||
ok(proof)
|
||||
|
||||
func buildProof*(
|
||||
blockHeader: BeaconBlockHeader): Result[BeaconBlockHeaderProof, string] =
|
||||
# 5th field of container with 5 fields -> 7 + 5
|
||||
let gIndex = GeneralizedIndex(12)
|
||||
|
||||
var proof: BeaconBlockHeaderProof
|
||||
? blockHeader.build_proof(gIndex, proof)
|
||||
|
||||
ok(proof)
|
||||
|
||||
func buildProof*(
|
||||
batch: HistoricalBatch, blockRootIndex: uint64):
|
||||
Result[HistoricalRootsProof, string] =
|
||||
# max list size * 2 is start point of leaves
|
||||
let gIndex = GeneralizedIndex(2 * SLOTS_PER_HISTORICAL_ROOT + blockRootIndex)
|
||||
|
||||
var proof: HistoricalRootsProof
|
||||
? batch.build_proof(gIndex, proof)
|
||||
|
||||
ok(proof)
|
||||
|
||||
func buildProof*(
|
||||
batch: HistoricalBatch,
|
||||
blockHeader: BeaconBlockHeader,
|
||||
blockBody: bellatrix.BeaconBlockBody):
|
||||
Result[BeaconChainBlockProof, string] =
|
||||
let
|
||||
historicalRootsIndex = getHistoricalRootsIndex(blockHeader)
|
||||
blockRootIndex = getBlockRootsIndex(blockHeader, historicalRootsIndex)
|
||||
|
||||
beaconBlockBodyProof = ? blockBody.buildProof()
|
||||
beaconBlockHeaderProof = ? blockHeader.buildProof()
|
||||
historicalRootsProof = ? batch.buildProof(blockRootIndex)
|
||||
|
||||
ok(BeaconChainBlockProof(
|
||||
beaconBlockBodyProof: beaconBlockBodyProof,
|
||||
beaconBlockBodyRoot: hash_tree_root(blockBody),
|
||||
beaconBlockHeaderProof: beaconBlockHeaderProof,
|
||||
beaconBlockHeaderRoot: hash_tree_root(blockHeader),
|
||||
historicalRootsProof: historicalRootsProof,
|
||||
slot: blockHeader.slot
|
||||
))
|
||||
|
||||
func verifyProof*(
|
||||
blockHash: Digest,
|
||||
proof: BeaconBlockBodyProof,
|
||||
blockBodyRoot: Digest): bool =
|
||||
let
|
||||
gIndexTopLevel = (1 * 1 * 16 + 9)
|
||||
gIndex = GeneralizedIndex(gIndexTopLevel * 1 * 16 + 12)
|
||||
|
||||
verify_merkle_multiproof(@[blockHash], proof, @[gIndex], blockBodyRoot)
|
||||
|
||||
func verifyProof*(
|
||||
blockBodyRoot: Digest,
|
||||
proof: BeaconBlockHeaderProof,
|
||||
blockHeaderRoot: Digest): bool =
|
||||
let gIndex = GeneralizedIndex(12)
|
||||
|
||||
verify_merkle_multiproof(@[blockBodyRoot], proof, @[gIndex], blockHeaderRoot)
|
||||
|
||||
func verifyProof*(
|
||||
blockHeaderRoot: Digest,
|
||||
proof: HistoricalRootsProof,
|
||||
historicalRoot: Digest,
|
||||
blockRootIndex: uint64): bool =
|
||||
let gIndex = GeneralizedIndex(2 * SLOTS_PER_HISTORICAL_ROOT + blockRootIndex)
|
||||
|
||||
verify_merkle_multiproof(@[blockHeaderRoot], proof, @[gIndex], historicalRoot)
|
||||
|
||||
func verifyProof*(
|
||||
historical_roots: HashList[Eth2Digest, Limit HISTORICAL_ROOTS_LIMIT],
|
||||
proof: BeaconChainBlockProof,
|
||||
blockHash: Digest): bool =
|
||||
let
|
||||
historicalRootsIndex = getHistoricalRootsIndex(proof.slot)
|
||||
blockRootIndex = getBlockRootsIndex(proof.slot, historicalRootsIndex)
|
||||
|
||||
blockHash.verifyProof(
|
||||
proof.beaconBlockBodyProof, proof.beaconBlockBodyRoot) and
|
||||
proof.beaconBlockBodyRoot.verifyProof(
|
||||
proof.beaconBlockHeaderProof, proof.beaconBlockHeaderRoot) and
|
||||
proof.beaconBlockHeaderRoot.verifyProof(
|
||||
proof.historicalRootsProof, historical_roots[historicalRootsIndex],
|
||||
blockRootIndex)
|
@ -16,4 +16,5 @@ import
|
||||
./test_history_network,
|
||||
./test_content_db,
|
||||
./test_discovery_rpc,
|
||||
./test_bridge_parser
|
||||
./test_bridge_parser,
|
||||
./test_beacon_chain_block_proof
|
||||
|
157
fluffy/tests/test_beacon_chain_block_proof.nim
Normal file
157
fluffy/tests/test_beacon_chain_block_proof.nim
Normal file
@ -0,0 +1,157 @@
|
||||
# Nimbus
|
||||
# Copyright (c) 2022 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.}
|
||||
|
||||
{.push raises: [Defect].}
|
||||
|
||||
import
|
||||
unittest2,
|
||||
beacon_chain/spec/forks,
|
||||
beacon_chain/spec/datatypes/bellatrix,
|
||||
beacon_chain/../tests/testblockutil,
|
||||
# Mock helpers
|
||||
beacon_chain/../tests/mocking/mock_genesis,
|
||||
|
||||
../network/history/experimental/beacon_chain_block_proof
|
||||
|
||||
# Test suite for the proofs:
|
||||
# - HistoricalRootsProof
|
||||
# - BeaconBlockHeaderProof
|
||||
# - BeaconBlockBodyProof
|
||||
# and as last
|
||||
# - the chain of proofs, BeaconChainBlockProof:
|
||||
# BlockHash || BlockHeader
|
||||
# -> BeaconBlockBodyProof
|
||||
# -> BeaconBlockHeaderProof
|
||||
# -> HistoricalRootsProof
|
||||
# historical_roots
|
||||
#
|
||||
# Note: The last test makes the others redundant, but keeping them all around
|
||||
# for now as it might be sufficient to go with just HistoricalRootsProof (and
|
||||
# perhaps BeaconBlockHeaderProof), see comments in beacon_chain_proofs.nim.
|
||||
#
|
||||
# TODO: Add more blocks to reach 1+ historical roots, to make sure that indexing
|
||||
# is properly tested.
|
||||
|
||||
suite "Beacon Chain Block Proofs":
|
||||
let
|
||||
cfg = block:
|
||||
var res = defaultRuntimeConfig
|
||||
res.ALTAIR_FORK_EPOCH = GENESIS_EPOCH
|
||||
res.BELLATRIX_FORK_EPOCH = GENESIS_EPOCH
|
||||
res
|
||||
state = newClone(initGenesisState(cfg = cfg))
|
||||
var cache = StateCache()
|
||||
|
||||
var blocks: seq[bellatrix.SignedBeaconBlock]
|
||||
# Note:
|
||||
# Adding 8192 blocks. First block is genesis block and not one of these.
|
||||
# Then one extra block is needed to get the historical roots, block
|
||||
# roots and state roots processed.
|
||||
# index i = 0 is second block.
|
||||
# index i = 8190 is 8192th block and last one that is part of the first
|
||||
# historical root
|
||||
for i in 0..<SLOTS_PER_HISTORICAL_ROOT:
|
||||
blocks.add(addTestBlock(state[], cache, cfg = cfg).bellatrixData)
|
||||
|
||||
# Starts from the block after genesis.
|
||||
const blocksToTest = [
|
||||
0'u64, 1, 2, 3,
|
||||
SLOTS_PER_HISTORICAL_ROOT div 2,
|
||||
SLOTS_PER_HISTORICAL_ROOT - 3,
|
||||
SLOTS_PER_HISTORICAL_ROOT - 2
|
||||
]
|
||||
|
||||
test "HistoricalRootsProof for BeaconBlockHeader":
|
||||
let
|
||||
# Historical batch of first historical root
|
||||
batch = HistoricalBatch(
|
||||
block_roots: getStateField(state[], block_roots).data,
|
||||
state_roots: getStateField(state[], state_roots).data)
|
||||
historical_roots = getStateField(state[], historical_roots)
|
||||
|
||||
# for i in 0..<(SLOTS_PER_HISTORICAL_ROOT - 1): # Test all blocks
|
||||
for i in blocksToTest:
|
||||
let
|
||||
beaconBlock = blocks[i].message
|
||||
historicalRootsIndex = getHistoricalRootsIndex(beaconBlock.slot)
|
||||
blockRootIndex = getBlockRootsIndex(beaconBlock.slot, historicalRootsIndex)
|
||||
|
||||
let res = buildProof(batch, blockRootIndex)
|
||||
check res.isOk()
|
||||
let proof = res.get()
|
||||
|
||||
check verifyProof(
|
||||
blocks[i].root, proof,
|
||||
historical_roots[historicalRootsIndex], blockRootIndex)
|
||||
|
||||
test "BeaconBlockHeaderProof for BeaconBlockBody":
|
||||
# for i in 0..<(SLOTS_PER_HISTORICAL_ROOT - 1): # Test all blocks
|
||||
for i in blocksToTest:
|
||||
let
|
||||
beaconBlock = blocks[i].message
|
||||
beaconBlockHeader = BeaconBlockHeader(
|
||||
slot: beaconBlock.slot,
|
||||
proposer_index: beaconBlock.proposer_index,
|
||||
parent_root: beaconBlock.parent_root,
|
||||
state_root: beaconBlock.state_root,
|
||||
body_root: hash_tree_root(beaconBlock.body)
|
||||
)
|
||||
beaconBlockBody = beaconBlock.body
|
||||
|
||||
let res = buildProof(beaconBlockHeader)
|
||||
check res.isOk()
|
||||
let proof = res.get()
|
||||
|
||||
let leave = hash_tree_root(beaconBlockBody)
|
||||
check verifyProof(leave, proof, blocks[i].root)
|
||||
|
||||
test "BeaconBlockBodyProof for Execution BlockHeader":
|
||||
# for i in 0..<(SLOTS_PER_HISTORICAL_ROOT - 1): # Test all blocks
|
||||
for i in blocksToTest:
|
||||
let beaconBlockBody = blocks[i].message.body
|
||||
|
||||
let res = buildProof(beaconBlockBody)
|
||||
check res.isOk()
|
||||
let proof = res.get()
|
||||
|
||||
let leave = beaconBlockBody.execution_payload.block_hash
|
||||
let root = hash_tree_root(beaconBlockBody)
|
||||
check verifyProof(leave, proof, root)
|
||||
|
||||
test "BeaconChainBlockProof for Execution BlockHeader":
|
||||
let
|
||||
# Historical batch of first historical root
|
||||
batch = HistoricalBatch(
|
||||
block_roots: getStateField(state[], block_roots).data,
|
||||
state_roots: getStateField(state[], state_roots).data)
|
||||
historical_roots = getStateField(state[], historical_roots)
|
||||
|
||||
# for i in 0..<(SLOTS_PER_HISTORICAL_ROOT - 1): # Test all blocks
|
||||
for i in blocksToTest:
|
||||
let
|
||||
beaconBlock = blocks[i].message
|
||||
beaconBlockHeader = BeaconBlockHeader(
|
||||
slot: beaconBlock.slot,
|
||||
proposer_index: beaconBlock.proposer_index,
|
||||
parent_root: beaconBlock.parent_root,
|
||||
state_root: beaconBlock.state_root,
|
||||
body_root: hash_tree_root(beaconBlock.body)
|
||||
)
|
||||
beaconBlockBody = beaconBlock.body
|
||||
|
||||
# Normally we would have an execution BlockHeader that holds this
|
||||
# value, but we skip the creation of that header for now and just take
|
||||
# the blockHash from the execution payload.
|
||||
blockHash = beaconBlockBody.execution_payload.block_hash
|
||||
|
||||
let proofRes = buildProof(batch, beaconBlockHeader, beaconBlockBody)
|
||||
check proofRes.isOk()
|
||||
let proof = proofRes.get()
|
||||
|
||||
check verifyProof(historical_roots, proof, blockHash)
|
Loading…
x
Reference in New Issue
Block a user