Skip to content

Commit

Permalink
feat(node-api): proof of proposer index in beacon block (#2099)
Browse files Browse the repository at this point in the history
  • Loading branch information
calbera authored Oct 25, 2024
1 parent aec2501 commit 0ea12a8
Show file tree
Hide file tree
Showing 19 changed files with 265 additions and 38 deletions.
4 changes: 3 additions & 1 deletion mod/consensus-types/pkg/types/genesis.go
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,9 @@ func DefaultGenesisExecutionPayloadHeaderDeneb() (
return nil, fmt.Errorf("failed generating receipts root: %w", err)
}

baseFeePerGas, err := math.NewU256FromBigInt(big.NewInt(defaultBaseFeePerGas))
baseFeePerGas, err := math.NewU256FromBigInt(
big.NewInt(defaultBaseFeePerGas),
)
if err != nil {
return nil, fmt.Errorf("failed setting base fee per gas: %w", err)
}
Expand Down
24 changes: 15 additions & 9 deletions mod/consensus/pkg/cometbft/service/abci.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,14 +67,16 @@ func (s *Service[LoggerT]) InitChain(
req.ChainId,
)

// Set the initial height, which will be used to determine if we are proposing
// Set the initial height, which will be used to determine if we are
// proposing
// or processing the first block or not.
s.initialHeight = req.InitialHeight
if s.initialHeight == 0 {
s.initialHeight = 1
}

// if req.InitialHeight is > 1, then we set the initial version on all stores
// if req.InitialHeight is > 1, then we set the initial version on all
// stores
if req.InitialHeight > 1 {
if err := s.sm.CommitMultiStore().
SetInitialVersion(req.InitialHeight); err != nil {
Expand Down Expand Up @@ -121,7 +123,8 @@ func (s *Service[LoggerT]) InitChain(
}
}

// NOTE: We don't commit, but FinalizeBlock for block InitialHeight starts from
// NOTE: We don't commit, but FinalizeBlock for block InitialHeight starts
// from
// this FinalizeBlockState.
return &cmtabci.InitChainResponse{
ConsensusParams: req.ConsensusParams,
Expand Down Expand Up @@ -242,10 +245,12 @@ func (s *Service[LoggerT]) ProcessProposal(
)
}

// Since the application can get access to FinalizeBlock state and write to it,
// we must be sure to reset it in case ProcessProposal timeouts and is called
// Since the application can get access to FinalizeBlock state and write to
// it, we must be sure to reset it in case ProcessProposal timeouts and is
// called
// again in a subsequent round. However, we only want to do this after we've
// processed the first block, as we want to avoid overwriting the finalizeState
// processed the first block, as we want to avoid overwriting the
// finalizeState
// after state changes during InitChain.
s.processProposalState = s.resetState()
if req.Height > s.initialHeight {
Expand Down Expand Up @@ -290,9 +295,10 @@ func (s *Service[LoggerT]) internalFinalizeBlock(
return nil, err
}

// finalizeBlockState should be set on InitChain or ProcessProposal. If it is
// nil, it means we are replaying this block and we need to set the state here
// given that during block replay ProcessProposal is not executed by CometBFT.
// finalizeBlockState should be set on InitChain or ProcessProposal. If it
// is nil, it means we are replaying this block and we need to set the state
// here given that during block replay ProcessProposal is not executed by
// CometBFT.
if s.finalizeBlockState == nil {
s.finalizeBlockState = s.resetState()
}
Expand Down
17 changes: 10 additions & 7 deletions mod/consensus/pkg/cometbft/service/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,18 +57,21 @@ type Service[
sm *statem.Manager
Middleware MiddlewareI

// prepareProposalState is used for PrepareProposal, which is set based on the
// previous block's state. This state is never committed. In case of multiple
// consensus rounds, the state is always reset to the previous block's state.
// prepareProposalState is used for PrepareProposal, which is set based on
// the previous block's state. This state is never committed. In case of
// multiple consensus rounds, the state is always reset to the previous
// block's state.
prepareProposalState *state

// processProposalState is used for ProcessProposal, which is set based on the
// previous block's state. This state is never committed. In case of multiple
// consensus rounds, the state is always reset to the previous block's state.
// processProposalState is used for ProcessProposal, which is set based on
// the previous block's state. This state is never committed. In case of
// multiple consensus rounds, the state is always reset to the previous
// block's state.
processProposalState *state

// finalizeBlockState is used for FinalizeBlock, which is set based on the
// previous block's state. This state is committed. finalizeBlockState is set
// previous block's state. This state is committed. finalizeBlockState is
// set
// on InitChain and FinalizeBlock and set to nil on Commit.
finalizeBlockState *state

Expand Down
10 changes: 8 additions & 2 deletions mod/da/pkg/types/sidecar_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,10 @@ func TestHasValidInclusionProof(t *testing.T) {
t.Helper()
inclusionProof := make([]common.Root, 0)
for i := int(1); i <= 8; i++ {
it := byteslib.ExtendToSize([]byte(strconv.Itoa(i)), byteslib.B32Size)
it := byteslib.ExtendToSize(
[]byte(strconv.Itoa(i)),
byteslib.B32Size,
)
proof, err := byteslib.ToBytes32(it)
require.NoError(t, err)
inclusionProof = append(inclusionProof, common.Root(proof))
Expand Down Expand Up @@ -147,7 +150,10 @@ func TestHashTreeRoot(t *testing.T) {
t.Helper()
inclusionProof := make([]common.Root, 0)
for i := int(1); i <= 8; i++ {
it := byteslib.ExtendToSize([]byte(strconv.Itoa(i)), byteslib.B32Size)
it := byteslib.ExtendToSize(
[]byte(strconv.Itoa(i)),
byteslib.B32Size,
)
proof, err := byteslib.ToBytes32(it)
require.NoError(t, err)
inclusionProof = append(inclusionProof, common.Root(proof))
Expand Down
18 changes: 14 additions & 4 deletions mod/node-api/handlers/proof/block_proposer.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ import (

// GetBlockProposer returns the block proposer pubkey for the given timestamp
// id along with a merkle proof that can be verified against the beacon block
// root.
// root. It also returns the merkle proof of the proposer index.
func (h *Handler[
BeaconBlockHeaderT, _, _, ContextT, _, _,
]) GetBlockProposer(c ContextT) (any, error) {
Expand All @@ -45,16 +45,25 @@ func (h *Handler[
return nil, err
}

h.Logger().Info("Generating block proposer proofs", "slot", slot)

// Generate the proof (along with the "correct" beacon block root to
// verify against) for the proposer validator pubkey.
h.Logger().Info("Generating block proposer proof", "slot", slot)
proof, beaconBlockRoot, err := merkle.ProveProposerInBlock(
pubkeyProof, beaconBlockRoot, err := merkle.ProveProposerPubkeyInBlock(
blockHeader, beaconState,
)
if err != nil {
return nil, err
}

// Generate the proof for the proposer index.
proposerIndexProof, _, err := merkle.ProveProposerIndexInBlock(
blockHeader,
)
if err != nil {
return nil, err
}

// Get the pubkey of the proposer validator.
proposerValidator, err := beaconState.ValidatorByIndex(
blockHeader.GetProposerIndex(),
Expand All @@ -67,6 +76,7 @@ func (h *Handler[
BeaconBlockHeader: blockHeader,
BeaconBlockRoot: beaconBlockRoot,
ValidatorPubkey: proposerValidator.GetPubkey(),
ValidatorPubkeyProof: proof,
ValidatorPubkeyProof: pubkeyProof,
ProposerIndexProof: proposerIndexProof,
}, nil
}
4 changes: 1 addition & 3 deletions mod/node-api/handlers/proof/merkle/beacon_state.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,9 +62,7 @@ func ProveBeaconStateInBlock(
//
// TODO: verifying the proof is not absolutely necessary.
func verifyBeaconStateInBlock(
bbh types.BeaconBlockHeader,
proof []common.Root,
leaf common.Root,
bbh types.BeaconBlockHeader, proof []common.Root, leaf common.Root,
) error {
beaconRoot := bbh.HashTreeRoot()
if beaconRootVerified, err := merkle.VerifyProof(
Expand Down
82 changes: 82 additions & 0 deletions mod/node-api/handlers/proof/merkle/block_proposer_index.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
// SPDX-License-Identifier: BUSL-1.1
//
// Copyright (C) 2024, Berachain Foundation. All rights reserved.
// Use of this software is governed by the Business Source License included
// in the LICENSE file of this repository and at www.mariadb.com/bsl11.
//
// ANY USE OF THE LICENSED WORK IN VIOLATION OF THIS LICENSE WILL AUTOMATICALLY
// TERMINATE YOUR RIGHTS UNDER THIS LICENSE FOR THE CURRENT AND ALL OTHER
// VERSIONS OF THE LICENSED WORK.
//
// THIS LICENSE DOES NOT GRANT YOU ANY RIGHT IN ANY TRADEMARK OR LOGO OF
// LICENSOR OR ITS AFFILIATES (PROVIDED THAT YOU MAY USE A TRADEMARK OR LOGO OF
// LICENSOR AS EXPRESSLY REQUIRED BY THIS LICENSE).
//
// TO THE EXTENT PERMITTED BY APPLICABLE LAW, THE LICENSED WORK IS PROVIDED ON
// AN “AS IS” BASIS. LICENSOR HEREBY DISCLAIMS ALL WARRANTIES AND CONDITIONS,
// EXPRESS OR IMPLIED, INCLUDING (WITHOUT LIMITATION) WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, NON-INFRINGEMENT, AND
// TITLE.

package merkle

import (
"github.com/berachain/beacon-kit/mod/errors"
"github.com/berachain/beacon-kit/mod/node-api/handlers/proof/types"
"github.com/berachain/beacon-kit/mod/primitives/pkg/common"
"github.com/berachain/beacon-kit/mod/primitives/pkg/encoding/ssz/merkle"
)

// ProveProposerIndexInBlock generates a proof for the proposer index in the
// beacon block. The proof is then verified against the beacon block root as a
// sanity check. Returns the proof along with the beacon block root. It uses
// the fastssz library to generate the proof.
func ProveProposerIndexInBlock[
BeaconBlockHeaderT types.BeaconBlockHeader,
](bbh BeaconBlockHeaderT) ([]common.Root, common.Root, error) {
blockProofTree, err := bbh.GetTree()
if err != nil {
return nil, common.Root{}, err
}

proposerIndexProof, err := blockProofTree.Prove(
ProposerIndexGIndexDenebBlock,
)
if err != nil {
return nil, common.Root{}, err
}

proof := make([]common.Root, len(proposerIndexProof.Hashes))
for i, hash := range proposerIndexProof.Hashes {
proof[i] = common.NewRootFromBytes(hash)
}

beaconRoot, err := verifyProposerIndexInBlock(
bbh, proof, common.NewRootFromBytes(proposerIndexProof.Leaf),
)
if err != nil {
return nil, common.Root{}, err
}

return proof, beaconRoot, nil
}

// verifyProposerIndexInBlock verifies the proposer index proof in the block.
//
// TODO: verifying the proof is not absolutely necessary.
func verifyProposerIndexInBlock(
bbh types.BeaconBlockHeader, proof []common.Root, leaf common.Root,
) (common.Root, error) {
beaconRoot := bbh.HashTreeRoot()
if beaconRootVerified, err := merkle.VerifyProof(
ProposerIndexGIndexDenebBlock, leaf, proof, beaconRoot,
); err != nil {
return common.Root{}, err
} else if !beaconRootVerified {
return common.Root{}, errors.New(
"proposer index proof failed to verify against beacon root",
)
}

return beaconRoot, nil
}
84 changes: 84 additions & 0 deletions mod/node-api/handlers/proof/merkle/block_proposer_index_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
// SPDX-License-Identifier: BUSL-1.1
//
// Copyright (C) 2024, Berachain Foundation. All rights reserved.
// Use of this software is governed by the Business Source License included
// in the LICENSE file of this repository and at www.mariadb.com/bsl11.
//
// ANY USE OF THE LICENSED WORK IN VIOLATION OF THIS LICENSE WILL AUTOMATICALLY
// TERMINATE YOUR RIGHTS UNDER THIS LICENSE FOR THE CURRENT AND ALL OTHER
// VERSIONS OF THE LICENSED WORK.
//
// THIS LICENSE DOES NOT GRANT YOU ANY RIGHT IN ANY TRADEMARK OR LOGO OF
// LICENSOR OR ITS AFFILIATES (PROVIDED THAT YOU MAY USE A TRADEMARK OR LOGO OF
// LICENSOR AS EXPRESSLY REQUIRED BY THIS LICENSE).
//
// TO THE EXTENT PERMITTED BY APPLICABLE LAW, THE LICENSED WORK IS PROVIDED ON
// AN “AS IS” BASIS. LICENSOR HEREBY DISCLAIMS ALL WARRANTIES AND CONDITIONS,
// EXPRESS OR IMPLIED, INCLUDING (WITHOUT LIMITATION) WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, NON-INFRINGEMENT, AND
// TITLE.

package merkle_test

import (
"testing"

"github.com/berachain/beacon-kit/mod/consensus-types/pkg/types"
"github.com/berachain/beacon-kit/mod/node-api/handlers/proof/merkle"
"github.com/berachain/beacon-kit/mod/primitives/pkg/common"
"github.com/berachain/beacon-kit/mod/primitives/pkg/math"
"github.com/stretchr/testify/require"
)

// TestBlockProposerIndexProof tests the ProveProposerIndexInBlock function
// and that the generated proof correctly verifies.
func TestBlockProposerIndexProof(t *testing.T) {
testCases := []struct {
name string
slot math.Slot
proposerIndex math.ValidatorIndex
parentBlockRoot common.Root
stateRoot common.Root
bodyRoot common.Root
expectedProofFile string
}{
{
name: "1 Validator Set",
slot: 69,
proposerIndex: 0,
parentBlockRoot: common.Root{1, 2, 3},
stateRoot: common.Root{4, 5, 6},
bodyRoot: common.Root{7, 8, 9},
expectedProofFile: "one_validator_proposer_index_proof.json",
},
{
name: "Many Validator Set",
slot: 420,
proposerIndex: 69,
parentBlockRoot: common.Root{1, 2, 3},
stateRoot: common.Root{4, 5, 6},
bodyRoot: common.Root{7, 8, 9},
expectedProofFile: "many_validators_proposer_index_proof.json",
},
}

for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
bbh := (&types.BeaconBlockHeader{}).New(
tc.slot,
tc.proposerIndex,
tc.parentBlockRoot,
tc.stateRoot,
tc.bodyRoot,
)

proof, beaconRoot, err := merkle.ProveProposerIndexInBlock(bbh)
require.NoError(t, err)

require.Equal(t, bbh.HashTreeRoot(), beaconRoot)

expectedProof := ReadProofFromFile(t, tc.expectedProofFile)
require.Equal(t, expectedProof, proof)
})
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,11 @@ import (
"github.com/berachain/beacon-kit/mod/primitives/pkg/math"
)

// ProveProposerInBlock generates a proof for the proposer pubkey in the
// ProveProposerPubkeyInBlock generates a proof for the proposer pubkey in the
// beacon block. The proof is then verified against the beacon block root as a
// sanity check. Returns the proof along with the beacon block root. It uses
// the fastssz library to generate the proof.
func ProveProposerInBlock[
func ProveProposerPubkeyInBlock[
BeaconBlockHeaderT types.BeaconBlockHeader,
BeaconStateMarshallableT types.BeaconStateMarshallable,
ExecutionPayloadHeaderT types.ExecutionPayloadHeader,
Expand Down
Loading

0 comments on commit 0ea12a8

Please sign in to comment.