diff --git a/beacon-chain/blockchain/chain_info_forkchoice.go b/beacon-chain/blockchain/chain_info_forkchoice.go index 95aaaf444193..c3d382c4c37c 100644 --- a/beacon-chain/blockchain/chain_info_forkchoice.go +++ b/beacon-chain/blockchain/chain_info_forkchoice.go @@ -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" ) @@ -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) +} diff --git a/beacon-chain/blockchain/receive_execution_payload_envelope.go b/beacon-chain/blockchain/receive_execution_payload_envelope.go index f3c4f7735b03..43f834b3c34e 100644 --- a/beacon-chain/blockchain/receive_execution_payload_envelope.go +++ b/beacon-chain/blockchain/receive_execution_payload_envelope.go @@ -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 diff --git a/beacon-chain/forkchoice/BUILD.bazel b/beacon-chain/forkchoice/BUILD.bazel index 8dc67d81a000..1af632ea520a 100644 --- a/beacon-chain/forkchoice/BUILD.bazel +++ b/beacon-chain/forkchoice/BUILD.bazel @@ -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", ], diff --git a/beacon-chain/forkchoice/doubly-linked-tree/BUILD.bazel b/beacon-chain/forkchoice/doubly-linked-tree/BUILD.bazel index ad6e27e52596..1b10c7cef2bd 100644 --- a/beacon-chain/forkchoice/doubly-linked-tree/BUILD.bazel +++ b/beacon-chain/forkchoice/doubly-linked-tree/BUILD.bazel @@ -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", diff --git a/beacon-chain/forkchoice/doubly-linked-tree/epbs.go b/beacon-chain/forkchoice/doubly-linked-tree/epbs.go index 2996667f8fc3..62c5a1bc2f37 100644 --- a/beacon-chain/forkchoice/doubly-linked-tree/epbs.go +++ b/beacon-chain/forkchoice/doubly-linked-tree/epbs.go @@ -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" ) @@ -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 +} diff --git a/beacon-chain/forkchoice/doubly-linked-tree/epbs_test.go b/beacon-chain/forkchoice/doubly-linked-tree/epbs_test.go index 5d4e9e2dc9f9..b1527ff21314 100644 --- a/beacon-chain/forkchoice/doubly-linked-tree/epbs_test.go +++ b/beacon-chain/forkchoice/doubly-linked-tree/epbs_test.go @@ -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" ) @@ -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) diff --git a/beacon-chain/forkchoice/doubly-linked-tree/forkchoice.go b/beacon-chain/forkchoice/doubly-linked-tree/forkchoice.go index 3ad054f10d6b..bdd2abb105f6 100644 --- a/beacon-chain/forkchoice/doubly-linked-tree/forkchoice.go +++ b/beacon-chain/forkchoice/doubly-linked-tree/forkchoice.go @@ -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 { diff --git a/beacon-chain/forkchoice/doubly-linked-tree/metrics.go b/beacon-chain/forkchoice/doubly-linked-tree/metrics.go index ed6d4ad1eb81..663a6cc74b8a 100644 --- a/beacon-chain/forkchoice/doubly-linked-tree/metrics.go +++ b/beacon-chain/forkchoice/doubly-linked-tree/metrics.go @@ -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( @@ -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", diff --git a/beacon-chain/forkchoice/interfaces.go b/beacon-chain/forkchoice/interfaces.go index c5269218c987..f26496f0ebec 100644 --- a/beacon-chain/forkchoice/interfaces.go +++ b/beacon-chain/forkchoice/interfaces.go @@ -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" ) @@ -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. @@ -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)