Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(node-api): proof of proposer index in beacon block #2099

Merged
merged 2 commits into from
Oct 25, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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.
calbera marked this conversation as resolved.
Show resolved Hide resolved
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
calbera marked this conversation as resolved.
Show resolved Hide resolved

// 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(
calbera marked this conversation as resolved.
Show resolved Hide resolved
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
}
calbera marked this conversation as resolved.
Show resolved Hide resolved

// verifyProposerIndexInBlock verifies the proposer index proof in the block.
//
// TODO: verifying the proof is not absolutely necessary.
calbera marked this conversation as resolved.
Show resolved Hide resolved
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",
)
}
calbera marked this conversation as resolved.
Show resolved Hide resolved

return beaconRoot, nil
}
calbera marked this conversation as resolved.
Show resolved Hide resolved
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.
calbera marked this conversation as resolved.
Show resolved Hide resolved
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)
calbera marked this conversation as resolved.
Show resolved Hide resolved
})
calbera marked this conversation as resolved.
Show resolved Hide resolved
}
}
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
Loading