Skip to content

Commit

Permalink
Handle execution payload insertion in forkchoice (#14422)
Browse files Browse the repository at this point in the history
  • Loading branch information
potuz committed Sep 13, 2024
1 parent dddf6d7 commit 2486a22
Show file tree
Hide file tree
Showing 9 changed files with 197 additions and 16 deletions.
9 changes: 9 additions & 0 deletions beacon-chain/blockchain/chain_info_forkchoice.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (

"github.com/prysmaticlabs/prysm/v5/beacon-chain/state"
"github.com/prysmaticlabs/prysm/v5/consensus-types/forkchoice"
"github.com/prysmaticlabs/prysm/v5/consensus-types/interfaces"
"github.com/prysmaticlabs/prysm/v5/consensus-types/primitives"
)

Expand Down Expand Up @@ -118,3 +119,11 @@ func (s *Service) GetPTCVote(root [32]byte) primitives.PTCStatus {
}
return primitives.PAYLOAD_ABSENT
}

// insertPayloadEnvelope wraps a locked call to the corresponding method in
// forkchoice
func (s *Service) insertPayloadEnvelope(envelope interfaces.ROExecutionPayloadEnvelope) error {
s.cfg.ForkChoiceStore.Lock()
defer s.cfg.ForkChoiceStore.Unlock()
return s.cfg.ForkChoiceStore.InsertPayloadEnvelope(envelope)
}
3 changes: 3 additions & 0 deletions beacon-chain/blockchain/receive_execution_payload_envelope.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,9 @@ func (s *Service) ReceiveExecutionPayloadEnvelope(ctx context.Context, envelope
daWaitedTime := time.Since(daStartTime)
dataAvailWaitedTime.Observe(float64(daWaitedTime.Milliseconds()))
// TODO: Add Head update, cache handling, postProcessing
if err := s.insertPayloadEnvelope(envelope); err != nil {
return errors.Wrap(err, "could not insert payload to forkchoice")
}
timeWithoutDaWait := time.Since(receivedTime) - daWaitedTime
executionEngineProcessingTime.Observe(float64(timeWithoutDaWait.Milliseconds()))
return nil
Expand Down
1 change: 1 addition & 0 deletions beacon-chain/forkchoice/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ go_library(
"//beacon-chain/state:go_default_library",
"//config/fieldparams:go_default_library",
"//consensus-types/forkchoice:go_default_library",
"//consensus-types/interfaces:go_default_library",
"//consensus-types/primitives:go_default_library",
"@com_github_pkg_errors//:go_default_library",
],
Expand Down
1 change: 1 addition & 0 deletions beacon-chain/forkchoice/doubly-linked-tree/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ go_library(
"//config/fieldparams:go_default_library",
"//config/params:go_default_library",
"//consensus-types/forkchoice:go_default_library",
"//consensus-types/interfaces:go_default_library",
"//consensus-types/primitives:go_default_library",
"//encoding/bytesutil:go_default_library",
"//monitoring/tracing/trace:go_default_library",
Expand Down
49 changes: 49 additions & 0 deletions beacon-chain/forkchoice/doubly-linked-tree/epbs.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
package doublylinkedtree

import (
"time"

"github.com/prysmaticlabs/prysm/v5/consensus-types/interfaces"
"github.com/prysmaticlabs/prysm/v5/consensus-types/primitives"
"github.com/prysmaticlabs/prysm/v5/time/slots"
)
Expand Down Expand Up @@ -29,3 +32,49 @@ func (f *ForkChoice) GetPTCVote() primitives.PTCStatus {
}
return primitives.PAYLOAD_PRESENT
}

// InsertPayloadEnvelope adds a full node to forkchoice from the given payload
// envelope.
func (f *ForkChoice) InsertPayloadEnvelope(envelope interfaces.ROExecutionPayloadEnvelope) error {
s := f.store
b, ok := s.nodeByRoot[envelope.BeaconBlockRoot()]
if !ok {
return ErrNilNode
}
e, err := envelope.Execution()
if err != nil {
return err
}
hash := [32]byte(e.BlockHash())
if _, ok = s.nodeByPayload[hash]; ok {
// We ignore nodes with the give payload hash already included
return nil
}
n := &Node{
slot: b.slot,
root: b.root,
payloadHash: hash,
parent: b.parent,
target: b.target,
children: make([]*Node, 0),
justifiedEpoch: b.justifiedEpoch,
unrealizedJustifiedEpoch: b.unrealizedJustifiedEpoch,
finalizedEpoch: b.finalizedEpoch,
unrealizedFinalizedEpoch: b.unrealizedFinalizedEpoch,
timestamp: uint64(time.Now().Unix()),
ptcVote: make([]primitives.PTCStatus, 0),
withheld: envelope.PayloadWithheld(),
optimistic: true,
}
if n.parent != nil {
n.parent.children = append(n.parent.children, n)
}
s.nodeByPayload[hash] = n
processedPayloadCount.Inc()
payloadCount.Set(float64(len(s.nodeByPayload)))

if b.slot == s.highestReceivedNode.slot {
s.highestReceivedNode = n
}
return nil
}
113 changes: 113 additions & 0 deletions beacon-chain/forkchoice/doubly-linked-tree/epbs_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@ import (
"context"
"testing"

"github.com/prysmaticlabs/prysm/v5/consensus-types/blocks"
"github.com/prysmaticlabs/prysm/v5/consensus-types/primitives"
enginev1 "github.com/prysmaticlabs/prysm/v5/proto/engine/v1"
"github.com/prysmaticlabs/prysm/v5/testing/require"
)

Expand Down Expand Up @@ -79,6 +81,117 @@ func TestStore_Insert_PayloadContent(t *testing.T) {
require.Equal(t, n, n2)
}

func TestStore_Insert_PayloadEnvelope(t *testing.T) {
ctx := context.Background()
f := setup(0, 0)
s := f.store
// The tree root is full
fr := [32]byte{}
n := s.nodeByRoot[fr]
require.Equal(t, true, n.isParentFull())

// Insert a child
cr := [32]byte{'a'}
cp := [32]byte{'p'}
n, err := s.insert(ctx, 1, cr, fr, [32]byte{}, fr, 0, 0)
require.NoError(t, err)
require.Equal(t, true, n.isParentFull())
require.Equal(t, s.treeRootNode, n.parent)
require.Equal(t, s.nodeByRoot[cr], n)
// Insert its payload
p := &enginev1.ExecutionPayloadEnvelope{
Payload: &enginev1.ExecutionPayloadElectra{
BlockHash: cp[:],
},
BeaconBlockRoot: cr[:],
PayloadWithheld: false,
StateRoot: fr[:],
BlobKzgCommitments: make([][]byte, 0),
}
e, err := blocks.WrappedROExecutionPayloadEnvelope(p)
require.NoError(t, err)
require.NoError(t, f.InsertPayloadEnvelope(e))
np := s.nodeByPayload[cp]
require.Equal(t, np.root, n.root)
require.NotEqual(t, np, n)

// Insert a grandchild without a payload, it's parent is the full node,
// which is not the empty node
gr := [32]byte{'b'}
gn, err := s.insert(ctx, 2, gr, cr, fr, cp, 0, 0)
require.NoError(t, err)
require.Equal(t, true, gn.isParentFull())
require.Equal(t, np, gn.parent)

// Insert the payload of the same grandchild
gp := [32]byte{'q'}
p = &enginev1.ExecutionPayloadEnvelope{
Payload: &enginev1.ExecutionPayloadElectra{
BlockHash: gp[:],
},
BeaconBlockRoot: gr[:],
PayloadWithheld: false,
StateRoot: fr[:],
BlobKzgCommitments: make([][]byte, 0),
}
e, err = blocks.WrappedROExecutionPayloadEnvelope(p)
require.NoError(t, err)
require.NoError(t, f.InsertPayloadEnvelope(e))
gfn := s.nodeByPayload[gp]
require.Equal(t, true, gfn.isParentFull())
require.Equal(t, np, gfn.parent)

// Insert an empty great grandchild based on empty
ggr := [32]byte{'c'}
ggn, err := s.insert(ctx, 3, ggr, gr, fr, cp, 0, 0)
require.NoError(t, err)
require.Equal(t, false, ggn.isParentFull())
require.Equal(t, gn, ggn.parent)

// Insert an empty great grandchild based on full
ggfr := [32]byte{'d'}
ggfn, err := s.insert(ctx, 3, ggfr, gr, fr, gp, 0, 0)
require.NoError(t, err)
require.Equal(t, gfn, ggfn.parent)
require.Equal(t, true, ggfn.isParentFull())

// Insert the payload for the great grandchild based on empty
ggp := [32]byte{'r'}
p = &enginev1.ExecutionPayloadEnvelope{
Payload: &enginev1.ExecutionPayloadElectra{
BlockHash: ggp[:],
},
BeaconBlockRoot: ggr[:],
PayloadWithheld: false,
StateRoot: fr[:],
BlobKzgCommitments: make([][]byte, 0),
}
e, err = blocks.WrappedROExecutionPayloadEnvelope(p)
require.NoError(t, err)
require.NoError(t, f.InsertPayloadEnvelope(e))
n = s.nodeByPayload[ggp]
require.Equal(t, false, n.isParentFull())
require.Equal(t, gn, n.parent)

// Insert the payload for the great grandchild based on full
ggfp := [32]byte{'s'}
p = &enginev1.ExecutionPayloadEnvelope{
Payload: &enginev1.ExecutionPayloadElectra{
BlockHash: ggfp[:],
},
BeaconBlockRoot: ggfr[:],
PayloadWithheld: false,
StateRoot: fr[:],
BlobKzgCommitments: make([][]byte, 0),
}
e, err = blocks.WrappedROExecutionPayloadEnvelope(p)
require.NoError(t, err)
require.NoError(t, f.InsertPayloadEnvelope(e))
n = s.nodeByPayload[ggfp]
require.Equal(t, true, n.isParentFull())
require.Equal(t, gfn, n.parent)
}

func TestGetPTCVote(t *testing.T) {
ctx := context.Background()
f := setup(0, 0)
Expand Down
17 changes: 2 additions & 15 deletions beacon-chain/forkchoice/doubly-linked-tree/forkchoice.go
Original file line number Diff line number Diff line change
Expand Up @@ -117,24 +117,11 @@ func (f *ForkChoice) InsertNode(ctx context.Context, state state.BeaconState, ro
parentRoot := bytesutil.ToBytes32(bh.ParentRoot)
var payloadHash, parentHash [32]byte
if state.Version() >= version.EPBS {
slot, err := state.LatestFullSlot()
latestHash, err := state.LatestBlockHash()
if err != nil {
return err
}
if slot == state.Slot() {
latestHeader, err := state.LatestExecutionPayloadHeaderEPBS()
if err != nil {
return err
}
copy(payloadHash[:], latestHeader.BlockHash)
copy(parentHash[:], latestHeader.ParentBlockHash)
} else {
latestHash, err := state.LatestBlockHash()
if err != nil {
return err
}
copy(parentHash[:], latestHash)
}
copy(parentHash[:], latestHash)
} else if state.Version() >= version.Bellatrix {
ph, err := state.LatestExecutionPayloadHeader()
if err != nil {
Expand Down
14 changes: 13 additions & 1 deletion beacon-chain/forkchoice/doubly-linked-tree/metrics.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,13 @@ var (
nodeCount = promauto.NewGauge(
prometheus.GaugeOpts{
Name: "doublylinkedtree_node_count",
Help: "The number of nodes in the doubly linked tree based store structure.",
Help: "The number of nodes for blocks in the doubly linked tree based store structure.",
},
)
payloadCount = promauto.NewGauge(
prometheus.GaugeOpts{
Name: "doublylinkedtree_payload_count",
Help: "The number of nodes for execution payloads in the doubly linked tree based store structure.",
},
)
headChangesCount = promauto.NewCounter(
Expand All @@ -39,6 +45,12 @@ var (
Help: "The number of times a block is processed for fork choice.",
},
)
processedPayloadCount = promauto.NewCounter(
prometheus.CounterOpts{
Name: "doublylinkedtree_payload_processed_count",
Help: "The number of times an execution payload is processed for fork choice.",
},
)
processedAttestationCount = promauto.NewCounter(
prometheus.CounterOpts{
Name: "doublylinkedtree_attestation_processed_count",
Expand Down
6 changes: 6 additions & 0 deletions beacon-chain/forkchoice/interfaces.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"github.com/prysmaticlabs/prysm/v5/beacon-chain/state"
fieldparams "github.com/prysmaticlabs/prysm/v5/config/fieldparams"
forkchoice2 "github.com/prysmaticlabs/prysm/v5/consensus-types/forkchoice"
"github.com/prysmaticlabs/prysm/v5/consensus-types/interfaces"
"github.com/prysmaticlabs/prysm/v5/consensus-types/primitives"
)

Expand All @@ -21,6 +22,7 @@ type ForkChoicer interface {
Unlock()
HeadRetriever // to compute head.
BlockProcessor // to track new block for fork choice.
PayloadProcessor // to track new execution payload envelopes for forkchoice
AttestationProcessor // to track new attestation for fork choice.
Getter // to retrieve fork choice information.
Setter // to set fork choice information.
Expand All @@ -45,6 +47,10 @@ type BlockProcessor interface {
InsertChain(context.Context, []*forkchoicetypes.BlockAndCheckpoints) error
}

type PayloadProcessor interface {
InsertPayloadEnvelope(interfaces.ROExecutionPayloadEnvelope) error
}

// AttestationProcessor processes the attestation that's used for accounting fork choice.
type AttestationProcessor interface {
ProcessAttestation(context.Context, []uint64, [32]byte, primitives.Slot)
Expand Down

0 comments on commit 2486a22

Please sign in to comment.