mirror of
https://github.com/OffchainLabs/prysm.git
synced 2026-02-17 08:21:27 -05:00
Compare commits
2 Commits
remove_hea
...
gloas/fork
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
32f8013c09 | ||
|
|
bb81a0a780 |
@@ -46,6 +46,7 @@ type ForkchoiceFetcher interface {
|
||||
HighestReceivedBlockSlot() primitives.Slot
|
||||
ReceivedBlocksLastEpoch() (uint64, error)
|
||||
InsertNode(context.Context, state.BeaconState, consensus_blocks.ROBlock) error
|
||||
InsertPayload(context.Context, interfaces.ROExecutionPayloadEnvelope) error
|
||||
ForkChoiceDump(context.Context) (*forkchoice.Dump, error)
|
||||
NewSlot(context.Context, primitives.Slot) error
|
||||
ProposerBoost() [32]byte
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"github.com/OffchainLabs/prysm/v7/beacon-chain/state"
|
||||
consensus_blocks "github.com/OffchainLabs/prysm/v7/consensus-types/blocks"
|
||||
"github.com/OffchainLabs/prysm/v7/consensus-types/forkchoice"
|
||||
"github.com/OffchainLabs/prysm/v7/consensus-types/interfaces"
|
||||
"github.com/OffchainLabs/prysm/v7/consensus-types/primitives"
|
||||
"github.com/OffchainLabs/prysm/v7/encoding/bytesutil"
|
||||
"github.com/OffchainLabs/prysm/v7/runtime/version"
|
||||
@@ -55,6 +56,13 @@ func (s *Service) InsertNode(ctx context.Context, st state.BeaconState, block co
|
||||
return s.cfg.ForkChoiceStore.InsertNode(ctx, st, block)
|
||||
}
|
||||
|
||||
// InsertPayload is a wrapper for payload insertion which is self locked
|
||||
func (s *Service) InsertPayload(ctx context.Context, pe interfaces.ROExecutionPayloadEnvelope) error {
|
||||
s.cfg.ForkChoiceStore.Lock()
|
||||
defer s.cfg.ForkChoiceStore.Unlock()
|
||||
return s.cfg.ForkChoiceStore.InsertPayload(ctx, pe)
|
||||
}
|
||||
|
||||
// ForkChoiceDump returns the corresponding value from forkchoice
|
||||
func (s *Service) ForkChoiceDump(ctx context.Context) (*forkchoice.Dump, error) {
|
||||
s.cfg.ForkChoiceStore.RLock()
|
||||
|
||||
@@ -136,6 +136,7 @@ func (s *Service) notifyForkchoiceUpdate(ctx context.Context, arg *fcuConfig) (*
|
||||
return nil, nil
|
||||
}
|
||||
pid, err := s.notifyForkchoiceUpdate(ctx, &fcuConfig{
|
||||
headState: st,
|
||||
headRoot: r,
|
||||
headBlock: b,
|
||||
attributes: arg.attributes,
|
||||
|
||||
@@ -11,6 +11,7 @@ import (
|
||||
mockExecution "github.com/OffchainLabs/prysm/v7/beacon-chain/execution/testing"
|
||||
forkchoicetypes "github.com/OffchainLabs/prysm/v7/beacon-chain/forkchoice/types"
|
||||
bstate "github.com/OffchainLabs/prysm/v7/beacon-chain/state"
|
||||
state_native "github.com/OffchainLabs/prysm/v7/beacon-chain/state/state-native"
|
||||
"github.com/OffchainLabs/prysm/v7/config/features"
|
||||
fieldparams "github.com/OffchainLabs/prysm/v7/config/fieldparams"
|
||||
"github.com/OffchainLabs/prysm/v7/config/params"
|
||||
@@ -72,7 +73,11 @@ func Test_NotifyForkchoiceUpdate_GetPayloadAttrErrorCanContinue(t *testing.T) {
|
||||
require.NoError(t, beaconDB.SaveState(ctx, st, bellatrixBlkRoot))
|
||||
require.NoError(t, beaconDB.SaveGenesisBlockRoot(ctx, bellatrixBlkRoot))
|
||||
|
||||
// Intentionally generate a bad state such that `hash_tree_root` fails during `process_slot`
|
||||
s, err := state_native.InitializeFromProtoPhase0(ðpb.BeaconState{})
|
||||
require.NoError(t, err)
|
||||
arg := &fcuConfig{
|
||||
headState: s,
|
||||
headRoot: [32]byte{},
|
||||
headBlock: b,
|
||||
}
|
||||
@@ -228,7 +233,8 @@ func Test_NotifyForkchoiceUpdate(t *testing.T) {
|
||||
require.NoError(t, beaconDB.SaveState(ctx, st, tt.finalizedRoot))
|
||||
require.NoError(t, beaconDB.SaveGenesisBlockRoot(ctx, tt.finalizedRoot))
|
||||
arg := &fcuConfig{
|
||||
headRoot: tt.headRoot,
|
||||
headState: st,
|
||||
headRoot: tt.headRoot,
|
||||
headBlock: tt.blk,
|
||||
}
|
||||
_, err = service.notifyForkchoiceUpdate(ctx, arg)
|
||||
@@ -308,6 +314,7 @@ func Test_NotifyForkchoiceUpdate_NIlLVH(t *testing.T) {
|
||||
require.NoError(t, beaconDB.SaveState(ctx, st, bra))
|
||||
require.NoError(t, beaconDB.SaveGenesisBlockRoot(ctx, bra))
|
||||
a := &fcuConfig{
|
||||
headState: st,
|
||||
headBlock: wbd,
|
||||
headRoot: brd,
|
||||
}
|
||||
@@ -445,6 +452,7 @@ func Test_NotifyForkchoiceUpdateRecursive_DoublyLinkedTree(t *testing.T) {
|
||||
require.NoError(t, beaconDB.SaveState(ctx, st, bra))
|
||||
require.NoError(t, beaconDB.SaveGenesisBlockRoot(ctx, bra))
|
||||
a := &fcuConfig{
|
||||
headState: st,
|
||||
headBlock: wbg,
|
||||
headRoot: brg,
|
||||
}
|
||||
|
||||
@@ -46,6 +46,7 @@ func (s *Service) getStateAndBlock(ctx context.Context, r [32]byte) (state.Beaco
|
||||
}
|
||||
|
||||
type fcuConfig struct {
|
||||
headState state.BeaconState
|
||||
headBlock interfaces.ReadOnlySignedBeaconBlock
|
||||
headRoot [32]byte
|
||||
proposingSlot primitives.Slot
|
||||
@@ -63,8 +64,7 @@ func (s *Service) sendFCU(cfg *postBlockProcessConfig, fcuArgs *fcuConfig) {
|
||||
s.updateCachesPostBlockProcessing(cfg)
|
||||
s.ForkChoicer().Lock()
|
||||
}
|
||||
headState, err := s.getFCUArgs(cfg, fcuArgs)
|
||||
if err != nil {
|
||||
if err := s.getFCUArgs(cfg, fcuArgs); err != nil {
|
||||
log.WithError(err).Error("Could not get forkchoice update argument")
|
||||
return
|
||||
}
|
||||
@@ -81,10 +81,10 @@ func (s *Service) sendFCU(cfg *postBlockProcessConfig, fcuArgs *fcuConfig) {
|
||||
}
|
||||
|
||||
if s.isNewHead(fcuArgs.headRoot) {
|
||||
if err := s.saveHead(cfg.ctx, fcuArgs.headRoot, fcuArgs.headBlock, headState); err != nil {
|
||||
if err := s.saveHead(cfg.ctx, fcuArgs.headRoot, fcuArgs.headBlock, fcuArgs.headState); err != nil {
|
||||
log.WithError(err).Error("Could not save head")
|
||||
}
|
||||
s.pruneAttsFromPool(s.ctx, headState, fcuArgs.headBlock)
|
||||
s.pruneAttsFromPool(s.ctx, fcuArgs.headState, fcuArgs.headBlock)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -92,6 +92,7 @@ func TestService_forkchoiceUpdateWithExecution_exceptionalCases(t *testing.T) {
|
||||
}
|
||||
service.cfg.PayloadIDCache.Set(2, [32]byte{2}, [8]byte{1})
|
||||
args := &fcuConfig{
|
||||
headState: st,
|
||||
headRoot: r1,
|
||||
headBlock: wsb,
|
||||
proposingSlot: service.CurrentSlot() + 1,
|
||||
@@ -145,6 +146,7 @@ func TestService_forkchoiceUpdateWithExecution_SameHeadRootNewProposer(t *testin
|
||||
service.head.state = st
|
||||
service.cfg.PayloadIDCache.Set(service.CurrentSlot()+1, [32]byte{} /* root */, [8]byte{})
|
||||
args := &fcuConfig{
|
||||
headState: st,
|
||||
headBlock: sb,
|
||||
headRoot: r,
|
||||
proposingSlot: service.CurrentSlot() + 1,
|
||||
|
||||
@@ -170,7 +170,7 @@ func (s *Service) saveHead(ctx context.Context, newHeadRoot [32]byte, headBlock
|
||||
// Forward an event capturing a new chain head over a common event feed
|
||||
// done in a goroutine to avoid blocking the critical runtime main routine.
|
||||
go func() {
|
||||
if err := s.notifyNewHeadEvent(ctx, newHeadSlot, newStateRoot[:], newHeadRoot[:]); err != nil {
|
||||
if err := s.notifyNewHeadEvent(ctx, newHeadSlot, headState, newStateRoot[:], newHeadRoot[:]); err != nil {
|
||||
log.WithError(err).Error("Could not notify event feed of new chain head")
|
||||
}
|
||||
}()
|
||||
@@ -322,6 +322,7 @@ func (s *Service) hasHeadState() bool {
|
||||
func (s *Service) notifyNewHeadEvent(
|
||||
ctx context.Context,
|
||||
newHeadSlot primitives.Slot,
|
||||
newHeadState state.BeaconState,
|
||||
newHeadStateRoot,
|
||||
newHeadRoot []byte,
|
||||
) error {
|
||||
|
||||
@@ -152,6 +152,7 @@ func TestSaveHead_Different_Reorg(t *testing.T) {
|
||||
|
||||
func Test_notifyNewHeadEvent(t *testing.T) {
|
||||
t.Run("genesis_state_root", func(t *testing.T) {
|
||||
bState, _ := util.DeterministicGenesisState(t, 10)
|
||||
srv := testServiceWithDB(t)
|
||||
srv.SetGenesisTime(time.Now())
|
||||
notifier := srv.cfg.StateNotifier.(*mock.MockStateNotifier)
|
||||
@@ -164,7 +165,7 @@ func Test_notifyNewHeadEvent(t *testing.T) {
|
||||
st, blk, err = prepareForkchoiceState(t.Context(), 1, newHeadRoot, [32]byte{}, [32]byte{}, ðpb.Checkpoint{}, ðpb.Checkpoint{})
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, srv.cfg.ForkChoiceStore.InsertNode(t.Context(), st, blk))
|
||||
require.NoError(t, srv.notifyNewHeadEvent(t.Context(), 1, newHeadStateRoot[:], newHeadRoot[:]))
|
||||
require.NoError(t, srv.notifyNewHeadEvent(t.Context(), 1, bState, newHeadStateRoot[:], newHeadRoot[:]))
|
||||
events := notifier.ReceivedEvents()
|
||||
require.Equal(t, 1, len(events))
|
||||
|
||||
@@ -201,7 +202,7 @@ func Test_notifyNewHeadEvent(t *testing.T) {
|
||||
st, blk, err = prepareForkchoiceState(t.Context(), 0, newHeadRoot, [32]byte{}, [32]byte{}, ðpb.Checkpoint{}, ðpb.Checkpoint{})
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, srv.cfg.ForkChoiceStore.InsertNode(t.Context(), st, blk))
|
||||
err = srv.notifyNewHeadEvent(t.Context(), epoch2Start, newHeadStateRoot[:], newHeadRoot[:])
|
||||
err = srv.notifyNewHeadEvent(t.Context(), epoch2Start, bState, newHeadStateRoot[:], newHeadRoot[:])
|
||||
require.NoError(t, err)
|
||||
events := notifier.ReceivedEvents()
|
||||
require.Equal(t, 1, len(events))
|
||||
@@ -219,6 +220,7 @@ func Test_notifyNewHeadEvent(t *testing.T) {
|
||||
require.DeepSSZEqual(t, wanted, eventHead)
|
||||
})
|
||||
t.Run("epoch transition", func(t *testing.T) {
|
||||
bState, _ := util.DeterministicGenesisState(t, 10)
|
||||
srv := testServiceWithDB(t)
|
||||
srv.SetGenesisTime(time.Now())
|
||||
notifier := srv.cfg.StateNotifier.(*mock.MockStateNotifier)
|
||||
@@ -232,7 +234,7 @@ func Test_notifyNewHeadEvent(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, srv.cfg.ForkChoiceStore.InsertNode(t.Context(), st, blk))
|
||||
newHeadSlot := params.BeaconConfig().SlotsPerEpoch
|
||||
require.NoError(t, srv.notifyNewHeadEvent(t.Context(), newHeadSlot, newHeadStateRoot[:], newHeadRoot[:]))
|
||||
require.NoError(t, srv.notifyNewHeadEvent(t.Context(), newHeadSlot, bState, newHeadStateRoot[:], newHeadRoot[:]))
|
||||
events := notifier.ReceivedEvents()
|
||||
require.Equal(t, 1, len(events))
|
||||
|
||||
|
||||
@@ -970,6 +970,7 @@ func (s *Service) lateBlockTasks(ctx context.Context) {
|
||||
s.headLock.RUnlock()
|
||||
|
||||
fcuArgs := &fcuConfig{
|
||||
headState: headState,
|
||||
headRoot: headRoot,
|
||||
headBlock: headBlock,
|
||||
attributes: attribute,
|
||||
|
||||
@@ -37,24 +37,22 @@ func (s *Service) CurrentSlot() primitives.Slot {
|
||||
return slots.CurrentSlot(s.genesisTime)
|
||||
}
|
||||
|
||||
// getFCUArgs returns the arguments to call forkchoice update.
|
||||
// It returns the head state separately so it is not pinned by goroutines
|
||||
// that only need the fcuConfig for the engine call.
|
||||
func (s *Service) getFCUArgs(cfg *postBlockProcessConfig, fcuArgs *fcuConfig) (state.BeaconState, error) {
|
||||
headState, err := s.getFCUArgsEarlyBlock(cfg, fcuArgs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
// getFCUArgs returns the arguments to call forkchoice update
|
||||
func (s *Service) getFCUArgs(cfg *postBlockProcessConfig, fcuArgs *fcuConfig) error {
|
||||
if err := s.getFCUArgsEarlyBlock(cfg, fcuArgs); err != nil {
|
||||
return err
|
||||
}
|
||||
fcuArgs.attributes = s.getPayloadAttribute(cfg.ctx, headState, fcuArgs.proposingSlot, cfg.headRoot[:])
|
||||
return headState, nil
|
||||
fcuArgs.attributes = s.getPayloadAttribute(cfg.ctx, fcuArgs.headState, fcuArgs.proposingSlot, cfg.headRoot[:])
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Service) getFCUArgsEarlyBlock(cfg *postBlockProcessConfig, fcuArgs *fcuConfig) (state.BeaconState, error) {
|
||||
func (s *Service) getFCUArgsEarlyBlock(cfg *postBlockProcessConfig, fcuArgs *fcuConfig) error {
|
||||
if cfg.roblock.Root() == cfg.headRoot {
|
||||
fcuArgs.headState = cfg.postState
|
||||
fcuArgs.headBlock = cfg.roblock
|
||||
fcuArgs.headRoot = cfg.headRoot
|
||||
fcuArgs.proposingSlot = s.CurrentSlot() + 1
|
||||
return cfg.postState, nil
|
||||
return nil
|
||||
}
|
||||
return s.fcuArgsNonCanonicalBlock(cfg, fcuArgs)
|
||||
}
|
||||
@@ -81,15 +79,16 @@ func (s *Service) logNonCanonicalBlockReceived(blockRoot [32]byte, headRoot [32]
|
||||
|
||||
// fcuArgsNonCanonicalBlock returns the arguments to the FCU call when the
|
||||
// incoming block is non-canonical, that is, based on the head root.
|
||||
func (s *Service) fcuArgsNonCanonicalBlock(cfg *postBlockProcessConfig, fcuArgs *fcuConfig) (state.BeaconState, error) {
|
||||
func (s *Service) fcuArgsNonCanonicalBlock(cfg *postBlockProcessConfig, fcuArgs *fcuConfig) error {
|
||||
headState, headBlock, err := s.getStateAndBlock(cfg.ctx, cfg.headRoot)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return err
|
||||
}
|
||||
fcuArgs.headState = headState
|
||||
fcuArgs.headBlock = headBlock
|
||||
fcuArgs.headRoot = cfg.headRoot
|
||||
fcuArgs.proposingSlot = s.CurrentSlot() + 1
|
||||
return headState, nil
|
||||
return nil
|
||||
}
|
||||
|
||||
// sendStateFeedOnBlock sends an event that a new block has been synced
|
||||
|
||||
@@ -2418,13 +2418,13 @@ func Test_getFCUArgs(t *testing.T) {
|
||||
}
|
||||
// error branch
|
||||
fcuArgs := &fcuConfig{}
|
||||
_, err = s.getFCUArgs(cfg, fcuArgs)
|
||||
err = s.getFCUArgs(cfg, fcuArgs)
|
||||
require.ErrorContains(t, "block does not exist", err)
|
||||
|
||||
// canonical branch
|
||||
cfg.headRoot = cfg.roblock.Root()
|
||||
fcuArgs = &fcuConfig{}
|
||||
_, err = s.getFCUArgs(cfg, fcuArgs)
|
||||
err = s.getFCUArgs(cfg, fcuArgs)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, cfg.roblock.Root(), fcuArgs.headRoot)
|
||||
}
|
||||
|
||||
@@ -150,6 +150,7 @@ func (s *Service) UpdateHead(ctx context.Context, proposingSlot primitives.Slot)
|
||||
}
|
||||
newAttHeadElapsedTime.Observe(float64(time.Since(start).Milliseconds()))
|
||||
fcuArgs := &fcuConfig{
|
||||
headState: headState,
|
||||
headRoot: newHeadRoot,
|
||||
headBlock: headBlock,
|
||||
proposingSlot: proposingSlot,
|
||||
@@ -161,10 +162,10 @@ func (s *Service) UpdateHead(ctx context.Context, proposingSlot primitives.Slot)
|
||||
}
|
||||
go s.forkchoiceUpdateWithExecution(s.ctx, fcuArgs)
|
||||
}
|
||||
if err := s.saveHead(s.ctx, newHeadRoot, headBlock, headState); err != nil {
|
||||
if err := s.saveHead(s.ctx, fcuArgs.headRoot, fcuArgs.headBlock, fcuArgs.headState); err != nil {
|
||||
log.WithError(err).Error("Could not save head")
|
||||
}
|
||||
s.pruneAttsFromPool(s.ctx, headState, headBlock)
|
||||
s.pruneAttsFromPool(s.ctx, fcuArgs.headState, fcuArgs.headBlock)
|
||||
}
|
||||
|
||||
// This processes fork choice attestations from the pool to account for validator votes and fork choice.
|
||||
|
||||
@@ -700,6 +700,14 @@ func (s *ChainService) InsertNode(ctx context.Context, st state.BeaconState, blo
|
||||
return nil
|
||||
}
|
||||
|
||||
// InsertPayload mocks the same method in the chain service
|
||||
func (s *ChainService) InsertPayload(ctx context.Context, pe interfaces.ROExecutionPayloadEnvelope) error {
|
||||
if s.ForkChoiceStore != nil {
|
||||
return s.ForkChoiceStore.InsertPayload(ctx, pe)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ForkChoiceDump mocks the same method in the chain service
|
||||
func (s *ChainService) ForkChoiceDump(ctx context.Context) (*forkchoice2.Dump, error) {
|
||||
if s.ForkChoiceStore != nil {
|
||||
|
||||
@@ -112,34 +112,6 @@ func ProcessExecutionPayload(
|
||||
return errors.Wrap(err, "signature verification failed")
|
||||
}
|
||||
|
||||
envelope, err := signedEnvelope.Envelope()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "could not get envelope from signed envelope")
|
||||
}
|
||||
|
||||
if err := ApplyExecutionPayload(ctx, st, envelope); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
r, err := st.HashTreeRoot(ctx)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "could not get hash tree root")
|
||||
}
|
||||
if r != envelope.StateRoot() {
|
||||
return fmt.Errorf("state root mismatch: expected %#x, got %#x", envelope.StateRoot(), r)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ApplyExecutionPayload applies the execution payload envelope to the state and performs the same
|
||||
// consistency checks as the full processing path. This keeps the post-payload state root computation
|
||||
// on a shared code path, even though some bid/payload checks are not strictly required for the root itself.
|
||||
func ApplyExecutionPayload(
|
||||
ctx context.Context,
|
||||
st state.BeaconState,
|
||||
envelope interfaces.ROExecutionPayloadEnvelope,
|
||||
) error {
|
||||
latestHeader := st.LatestBlockHeader()
|
||||
if len(latestHeader.StateRoot) == 0 || bytes.Equal(latestHeader.StateRoot, make([]byte, 32)) {
|
||||
previousStateRoot, err := st.HashTreeRoot(ctx)
|
||||
@@ -156,6 +128,10 @@ func ApplyExecutionPayload(
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "could not compute block header root")
|
||||
}
|
||||
envelope, err := signedEnvelope.Envelope()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "could not get envelope from signed envelope")
|
||||
}
|
||||
|
||||
beaconBlockRoot := envelope.BeaconBlockRoot()
|
||||
if !bytes.Equal(beaconBlockRoot[:], blockHeaderRoot[:]) {
|
||||
@@ -241,6 +217,14 @@ func ApplyExecutionPayload(
|
||||
return errors.Wrap(err, "could not set latest block hash")
|
||||
}
|
||||
|
||||
r, err := st.HashTreeRoot(ctx)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "could not get hash tree root")
|
||||
}
|
||||
if r != envelope.StateRoot() {
|
||||
return fmt.Errorf("state root mismatch: expected %#x, got %#x", envelope.StateRoot(), r)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -20,6 +20,7 @@ go_library(
|
||||
"//config/fieldparams:go_default_library",
|
||||
"//consensus-types/blocks: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",
|
||||
],
|
||||
|
||||
@@ -52,6 +52,7 @@ go_test(
|
||||
srcs = [
|
||||
"ffg_update_test.go",
|
||||
"forkchoice_test.go",
|
||||
"gloas_test.go",
|
||||
"no_vote_test.go",
|
||||
"node_test.go",
|
||||
"on_tick_test.go",
|
||||
@@ -71,6 +72,7 @@ go_test(
|
||||
"//config/params:go_default_library",
|
||||
"//consensus-types/blocks:go_default_library",
|
||||
"//consensus-types/forkchoice:go_default_library",
|
||||
"//consensus-types/interfaces:go_default_library",
|
||||
"//consensus-types/primitives:go_default_library",
|
||||
"//crypto/hash:go_default_library",
|
||||
"//encoding/bytesutil:go_default_library",
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"bytes"
|
||||
"context"
|
||||
"slices"
|
||||
"time"
|
||||
|
||||
"github.com/OffchainLabs/prysm/v7/config/params"
|
||||
"github.com/OffchainLabs/prysm/v7/consensus-types/blocks"
|
||||
@@ -287,3 +288,26 @@ func (s *Store) nodeTreeDump(ctx context.Context, n *Node, nodes []*forkchoice2.
|
||||
}
|
||||
return nodes, nil
|
||||
}
|
||||
|
||||
// InsertPayload inserts a full node into forkchoice after the Gloas fork.
|
||||
func (f *ForkChoice) InsertPayload(ctx context.Context, pe interfaces.ROExecutionPayloadEnvelope) error {
|
||||
s := f.store
|
||||
root := pe.BeaconBlockRoot()
|
||||
en := s.emptyNodeByRoot[root]
|
||||
if en == nil {
|
||||
return errors.Wrap(ErrNilNode, "cannot insert full node without an empty one")
|
||||
}
|
||||
if _, ok := s.fullNodeByRoot[root]; ok {
|
||||
// We don't import two payloads for the same root
|
||||
return nil
|
||||
}
|
||||
fn := &PayloadNode{
|
||||
node: en.node,
|
||||
optimistic: true,
|
||||
timestamp: time.Now(),
|
||||
full: true,
|
||||
children: make([]*Node, 0),
|
||||
}
|
||||
s.fullNodeByRoot[root] = fn
|
||||
return nil
|
||||
}
|
||||
|
||||
300
beacon-chain/forkchoice/doubly-linked-tree/gloas_test.go
Normal file
300
beacon-chain/forkchoice/doubly-linked-tree/gloas_test.go
Normal file
@@ -0,0 +1,300 @@
|
||||
package doublylinkedtree
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/OffchainLabs/prysm/v7/beacon-chain/state"
|
||||
state_native "github.com/OffchainLabs/prysm/v7/beacon-chain/state/state-native"
|
||||
"github.com/OffchainLabs/prysm/v7/config/params"
|
||||
"github.com/OffchainLabs/prysm/v7/consensus-types/blocks"
|
||||
"github.com/OffchainLabs/prysm/v7/consensus-types/interfaces"
|
||||
"github.com/OffchainLabs/prysm/v7/consensus-types/primitives"
|
||||
enginev1 "github.com/OffchainLabs/prysm/v7/proto/engine/v1"
|
||||
ethpb "github.com/OffchainLabs/prysm/v7/proto/prysm/v1alpha1"
|
||||
"github.com/OffchainLabs/prysm/v7/testing/assert"
|
||||
"github.com/OffchainLabs/prysm/v7/testing/require"
|
||||
"github.com/OffchainLabs/prysm/v7/testing/util"
|
||||
)
|
||||
|
||||
func prepareGloasForkchoiceState(
|
||||
_ context.Context,
|
||||
slot primitives.Slot,
|
||||
blockRoot [32]byte,
|
||||
parentRoot [32]byte,
|
||||
blockHash [32]byte,
|
||||
parentBlockHash [32]byte,
|
||||
justifiedEpoch primitives.Epoch,
|
||||
finalizedEpoch primitives.Epoch,
|
||||
) (state.BeaconState, blocks.ROBlock, error) {
|
||||
blockHeader := ðpb.BeaconBlockHeader{
|
||||
ParentRoot: parentRoot[:],
|
||||
}
|
||||
|
||||
justifiedCheckpoint := ðpb.Checkpoint{
|
||||
Epoch: justifiedEpoch,
|
||||
}
|
||||
|
||||
finalizedCheckpoint := ðpb.Checkpoint{
|
||||
Epoch: finalizedEpoch,
|
||||
}
|
||||
|
||||
builderPendingPayments := make([]*ethpb.BuilderPendingPayment, 64)
|
||||
for i := range builderPendingPayments {
|
||||
builderPendingPayments[i] = ðpb.BuilderPendingPayment{
|
||||
Withdrawal: ðpb.BuilderPendingWithdrawal{
|
||||
FeeRecipient: make([]byte, 20),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
base := ðpb.BeaconStateGloas{
|
||||
Slot: slot,
|
||||
RandaoMixes: make([][]byte, params.BeaconConfig().EpochsPerHistoricalVector),
|
||||
CurrentJustifiedCheckpoint: justifiedCheckpoint,
|
||||
FinalizedCheckpoint: finalizedCheckpoint,
|
||||
LatestBlockHeader: blockHeader,
|
||||
LatestExecutionPayloadBid: ðpb.ExecutionPayloadBid{
|
||||
BlockHash: blockHash[:],
|
||||
ParentBlockHash: parentBlockHash[:],
|
||||
ParentBlockRoot: make([]byte, 32),
|
||||
PrevRandao: make([]byte, 32),
|
||||
FeeRecipient: make([]byte, 20),
|
||||
BlobKzgCommitments: [][]byte{make([]byte, 48)},
|
||||
},
|
||||
Builders: make([]*ethpb.Builder, 0),
|
||||
BuilderPendingPayments: builderPendingPayments,
|
||||
ExecutionPayloadAvailability: make([]byte, 1024),
|
||||
LatestBlockHash: make([]byte, 32),
|
||||
PayloadExpectedWithdrawals: make([]*enginev1.Withdrawal, 0),
|
||||
ProposerLookahead: make([]uint64, 64),
|
||||
}
|
||||
|
||||
st, err := state_native.InitializeFromProtoUnsafeGloas(base)
|
||||
if err != nil {
|
||||
return nil, blocks.ROBlock{}, err
|
||||
}
|
||||
|
||||
bid := util.HydrateSignedExecutionPayloadBid(ðpb.SignedExecutionPayloadBid{
|
||||
Message: ðpb.ExecutionPayloadBid{
|
||||
BlockHash: blockHash[:],
|
||||
ParentBlockHash: parentBlockHash[:],
|
||||
},
|
||||
})
|
||||
|
||||
blk := util.HydrateSignedBeaconBlockGloas(ðpb.SignedBeaconBlockGloas{
|
||||
Block: ðpb.BeaconBlockGloas{
|
||||
Slot: slot,
|
||||
ParentRoot: parentRoot[:],
|
||||
Body: ðpb.BeaconBlockBodyGloas{
|
||||
SignedExecutionPayloadBid: bid,
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
signed, err := blocks.NewSignedBeaconBlock(blk)
|
||||
if err != nil {
|
||||
return nil, blocks.ROBlock{}, err
|
||||
}
|
||||
roblock, err := blocks.NewROBlockWithRoot(signed, blockRoot)
|
||||
return st, roblock, err
|
||||
}
|
||||
|
||||
func prepareGloasForkchoicePayload(
|
||||
blockRoot [32]byte,
|
||||
) (interfaces.ROExecutionPayloadEnvelope, error) {
|
||||
env := ðpb.ExecutionPayloadEnvelope{
|
||||
BeaconBlockRoot: blockRoot[:],
|
||||
Payload: &enginev1.ExecutionPayloadDeneb{},
|
||||
}
|
||||
return blocks.WrappedROExecutionPayloadEnvelope(env)
|
||||
}
|
||||
|
||||
func TestInsertGloasBlock_EmptyNodeOnly(t *testing.T) {
|
||||
f := setup(0, 0)
|
||||
ctx := t.Context()
|
||||
|
||||
root := indexToHash(1)
|
||||
blockHash := indexToHash(100)
|
||||
st, roblock, err := prepareGloasForkchoiceState(ctx, 1, root, params.BeaconConfig().ZeroHash, blockHash, params.BeaconConfig().ZeroHash, 0, 0)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, f.InsertNode(ctx, st, roblock))
|
||||
|
||||
// Empty node should exist.
|
||||
en := f.store.emptyNodeByRoot[root]
|
||||
require.NotNil(t, en)
|
||||
|
||||
// Full node should NOT exist.
|
||||
_, hasFull := f.store.fullNodeByRoot[root]
|
||||
assert.Equal(t, false, hasFull)
|
||||
|
||||
// Parent should be the genesis full node.
|
||||
genesisRoot := params.BeaconConfig().ZeroHash
|
||||
genesisFull := f.store.fullNodeByRoot[genesisRoot]
|
||||
require.NotNil(t, genesisFull)
|
||||
assert.Equal(t, genesisFull, en.node.parent)
|
||||
}
|
||||
|
||||
func TestInsertPayload_CreatesFullNode(t *testing.T) {
|
||||
f := setup(0, 0)
|
||||
ctx := t.Context()
|
||||
|
||||
root := indexToHash(1)
|
||||
blockHash := indexToHash(100)
|
||||
st, roblock, err := prepareGloasForkchoiceState(ctx, 1, root, params.BeaconConfig().ZeroHash, blockHash, params.BeaconConfig().ZeroHash, 0, 0)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, f.InsertNode(ctx, st, roblock))
|
||||
require.Equal(t, 2, len(f.store.emptyNodeByRoot))
|
||||
require.Equal(t, 1, len(f.store.fullNodeByRoot))
|
||||
|
||||
pe, err := prepareGloasForkchoicePayload(root)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, f.InsertPayload(ctx, pe))
|
||||
require.Equal(t, 2, len(f.store.fullNodeByRoot))
|
||||
|
||||
fn := f.store.fullNodeByRoot[root]
|
||||
require.NotNil(t, fn)
|
||||
|
||||
en := f.store.emptyNodeByRoot[root]
|
||||
require.NotNil(t, en)
|
||||
|
||||
// Empty and full share the same *Node.
|
||||
assert.Equal(t, en.node, fn.node)
|
||||
assert.Equal(t, true, fn.optimistic)
|
||||
assert.Equal(t, true, fn.full)
|
||||
}
|
||||
|
||||
func TestInsertPayload_DuplicateIsNoop(t *testing.T) {
|
||||
f := setup(0, 0)
|
||||
ctx := t.Context()
|
||||
|
||||
root := indexToHash(1)
|
||||
blockHash := indexToHash(100)
|
||||
st, roblock, err := prepareGloasForkchoiceState(ctx, 1, root, params.BeaconConfig().ZeroHash, blockHash, params.BeaconConfig().ZeroHash, 0, 0)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, f.InsertNode(ctx, st, roblock))
|
||||
|
||||
pe, err := prepareGloasForkchoicePayload(root)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, f.InsertPayload(ctx, pe))
|
||||
require.Equal(t, 2, len(f.store.fullNodeByRoot))
|
||||
|
||||
fn := f.store.fullNodeByRoot[root]
|
||||
require.NotNil(t, fn)
|
||||
|
||||
// Insert again — should be a no-op.
|
||||
require.NoError(t, f.InsertPayload(ctx, pe))
|
||||
assert.Equal(t, fn, f.store.fullNodeByRoot[root])
|
||||
require.Equal(t, 2, len(f.store.fullNodeByRoot))
|
||||
}
|
||||
|
||||
func TestInsertPayload_WithoutEmptyNode_Errors(t *testing.T) {
|
||||
f := setup(0, 0)
|
||||
ctx := t.Context()
|
||||
|
||||
root := indexToHash(99)
|
||||
pe, err := prepareGloasForkchoicePayload(root)
|
||||
require.NoError(t, err)
|
||||
|
||||
err = f.InsertPayload(ctx, pe)
|
||||
require.ErrorContains(t, ErrNilNode.Error(), err)
|
||||
}
|
||||
|
||||
func TestGloasBlock_ChildBuildsOnEmpty(t *testing.T) {
|
||||
f := setup(0, 0)
|
||||
ctx := t.Context()
|
||||
|
||||
// Insert Gloas block A (empty only).
|
||||
rootA := indexToHash(1)
|
||||
blockHashA := indexToHash(100)
|
||||
st, roblock, err := prepareGloasForkchoiceState(ctx, 1, rootA, params.BeaconConfig().ZeroHash, blockHashA, params.BeaconConfig().ZeroHash, 0, 0)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, f.InsertNode(ctx, st, roblock))
|
||||
|
||||
// Insert Gloas block B as child of (A, empty)
|
||||
rootB := indexToHash(2)
|
||||
blockHashB := indexToHash(200)
|
||||
nonMatchingParentHash := indexToHash(999)
|
||||
st, roblock, err = prepareGloasForkchoiceState(ctx, 2, rootB, rootA, blockHashB, nonMatchingParentHash, 0, 0)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, f.InsertNode(ctx, st, roblock))
|
||||
|
||||
emptyA := f.store.emptyNodeByRoot[rootA]
|
||||
require.NotNil(t, emptyA)
|
||||
nodeB := f.store.emptyNodeByRoot[rootB]
|
||||
require.NotNil(t, nodeB)
|
||||
require.Equal(t, emptyA, nodeB.node.parent)
|
||||
}
|
||||
|
||||
func TestGloasBlock_ChildrenOfEmptyAndFull(t *testing.T) {
|
||||
f := setup(0, 0)
|
||||
ctx := t.Context()
|
||||
|
||||
// Insert Gloas block A (empty only).
|
||||
rootA := indexToHash(1)
|
||||
blockHashA := indexToHash(100)
|
||||
st, roblock, err := prepareGloasForkchoiceState(ctx, 1, rootA, params.BeaconConfig().ZeroHash, blockHashA, params.BeaconConfig().ZeroHash, 0, 0)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, f.InsertNode(ctx, st, roblock))
|
||||
// Insert payload for A
|
||||
pe, err := prepareGloasForkchoicePayload(rootA)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, f.InsertPayload(ctx, pe))
|
||||
|
||||
// Insert Gloas block B as child of (A, empty)
|
||||
rootB := indexToHash(2)
|
||||
blockHashB := indexToHash(200)
|
||||
nonMatchingParentHash := indexToHash(999)
|
||||
st, roblock, err = prepareGloasForkchoiceState(ctx, 2, rootB, rootA, blockHashB, nonMatchingParentHash, 0, 0)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, f.InsertNode(ctx, st, roblock))
|
||||
|
||||
// Insert Gloas block C as child of (A, full)
|
||||
rootC := indexToHash(3)
|
||||
blockHashC := indexToHash(201)
|
||||
st, roblock, err = prepareGloasForkchoiceState(ctx, 3, rootC, rootA, blockHashC, blockHashA, 0, 0)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, f.InsertNode(ctx, st, roblock))
|
||||
|
||||
emptyA := f.store.emptyNodeByRoot[rootA]
|
||||
require.NotNil(t, emptyA)
|
||||
nodeB := f.store.emptyNodeByRoot[rootB]
|
||||
require.NotNil(t, nodeB)
|
||||
require.Equal(t, emptyA, nodeB.node.parent)
|
||||
nodeC := f.store.emptyNodeByRoot[rootC]
|
||||
require.NotNil(t, nodeC)
|
||||
fullA := f.store.fullNodeByRoot[rootA]
|
||||
require.NotNil(t, fullA)
|
||||
require.Equal(t, fullA, nodeC.node.parent)
|
||||
}
|
||||
|
||||
func TestGloasBlock_ChildBuildsOnFull(t *testing.T) {
|
||||
f := setup(0, 0)
|
||||
ctx := t.Context()
|
||||
|
||||
// Insert Gloas block A (empty only).
|
||||
rootA := indexToHash(1)
|
||||
blockHashA := indexToHash(100)
|
||||
st, roblock, err := prepareGloasForkchoiceState(ctx, 1, rootA, params.BeaconConfig().ZeroHash, blockHashA, params.BeaconConfig().ZeroHash, 0, 0)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, f.InsertNode(ctx, st, roblock))
|
||||
|
||||
// Insert payload for A → creates the full node.
|
||||
pe, err := prepareGloasForkchoicePayload(rootA)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, f.InsertPayload(ctx, pe))
|
||||
|
||||
fullA := f.store.fullNodeByRoot[rootA]
|
||||
require.NotNil(t, fullA)
|
||||
|
||||
// Child for (A, full)
|
||||
rootB := indexToHash(2)
|
||||
blockHashB := indexToHash(200)
|
||||
st, roblock, err = prepareGloasForkchoiceState(ctx, 2, rootB, rootA, blockHashB, blockHashA, 0, 0)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, f.InsertNode(ctx, st, roblock))
|
||||
|
||||
nodeB := f.store.emptyNodeByRoot[rootB]
|
||||
require.NotNil(t, nodeB)
|
||||
assert.Equal(t, fullA, nodeB.node.parent)
|
||||
}
|
||||
@@ -136,6 +136,7 @@ func (s *Store) insert(ctx context.Context,
|
||||
node: n,
|
||||
optimistic: optimistic,
|
||||
timestamp: time.Now(),
|
||||
children: make([]*Node, 0),
|
||||
}
|
||||
s.emptyNodeByRoot[root] = pn
|
||||
ret = pn
|
||||
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
fieldparams "github.com/OffchainLabs/prysm/v7/config/fieldparams"
|
||||
consensus_blocks "github.com/OffchainLabs/prysm/v7/consensus-types/blocks"
|
||||
forkchoice2 "github.com/OffchainLabs/prysm/v7/consensus-types/forkchoice"
|
||||
"github.com/OffchainLabs/prysm/v7/consensus-types/interfaces"
|
||||
"github.com/OffchainLabs/prysm/v7/consensus-types/primitives"
|
||||
)
|
||||
|
||||
@@ -23,6 +24,7 @@ type ForkChoicer interface {
|
||||
Unlock()
|
||||
HeadRetriever // to compute head.
|
||||
BlockProcessor // to track new block for fork choice.
|
||||
PayloadProcessor // to track new payloads for fork choice.
|
||||
AttestationProcessor // to track new attestation for fork choice.
|
||||
Getter // to retrieve fork choice information.
|
||||
Setter // to set fork choice information.
|
||||
@@ -47,6 +49,11 @@ type BlockProcessor interface {
|
||||
InsertChain(context.Context, []*forkchoicetypes.BlockAndCheckpoints) error
|
||||
}
|
||||
|
||||
// PayloadProcessor processes a payload envelope
|
||||
type PayloadProcessor interface {
|
||||
InsertPayload(context.Context, interfaces.ROExecutionPayloadEnvelope) error
|
||||
}
|
||||
|
||||
// AttestationProcessor processes the attestation that's used for accounting fork choice.
|
||||
type AttestationProcessor interface {
|
||||
ProcessAttestation(context.Context, []uint64, [32]byte, primitives.Epoch)
|
||||
|
||||
2
changelog/potuz_gloas_forkchoice_insert_payload.md
Normal file
2
changelog/potuz_gloas_forkchoice_insert_payload.md
Normal file
@@ -0,0 +1,2 @@
|
||||
### Added
|
||||
- Added an InsertPayload method to allow full node insertion after gloas.
|
||||
@@ -1,2 +0,0 @@
|
||||
### Changed
|
||||
- Remove headState from fcuArgs.
|
||||
@@ -1,2 +0,0 @@
|
||||
### Ignored
|
||||
- Refactor ProcessExecutionPayload to ApplyExecutionPayload
|
||||
Reference in New Issue
Block a user