mirror of
https://github.com/OffchainLabs/prysm.git
synced 2026-01-08 04:54:05 -05:00
This PR introduces several simplifications to block processing. It calls to notify the engine in the background when forkchoice needs to be updated. It no longer updates the caches and process epoch transition before computing payload attributes, since this is no longer needed after Fulu. It removes a complicated second call to FCU with the same head after processing the last slot of the epoch. Some checks for reviewers: - the single caller of sendFCU held a lock to forkchoice. Since the call now is in the background this helper can aquire the lock. - All paths to handleEpochBoundary are now **NOT** locked. This allows the lock to get the target root to be taken locally in place. - The checkpoint cache is completely useless and thus the target root call could be removed. But removing the proposer ID cache is more complicated and out of scope for this PR. - lateBlockTasks has pre and post-fulu cased, we could remove pre-fulu checks and defer to the update function if deemed cleaner. - Conversely, postBlockProcess does not have this casing and thus pre-Fulu blocks on gossip may fail to get proposed correctly because of the lack of the proposer being correctly computed.
208 lines
9.1 KiB
Go
208 lines
9.1 KiB
Go
package blockchain
|
|
|
|
import (
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/OffchainLabs/prysm/v7/beacon-chain/core/helpers"
|
|
"github.com/OffchainLabs/prysm/v7/beacon-chain/core/transition"
|
|
forkchoicetypes "github.com/OffchainLabs/prysm/v7/beacon-chain/forkchoice/types"
|
|
"github.com/OffchainLabs/prysm/v7/config/params"
|
|
"github.com/OffchainLabs/prysm/v7/consensus-types/blocks"
|
|
"github.com/OffchainLabs/prysm/v7/consensus-types/primitives"
|
|
"github.com/OffchainLabs/prysm/v7/encoding/bytesutil"
|
|
ethpb "github.com/OffchainLabs/prysm/v7/proto/prysm/v1alpha1"
|
|
"github.com/OffchainLabs/prysm/v7/testing/require"
|
|
"github.com/OffchainLabs/prysm/v7/testing/util"
|
|
prysmTime "github.com/OffchainLabs/prysm/v7/time"
|
|
"github.com/OffchainLabs/prysm/v7/time/slots"
|
|
logTest "github.com/sirupsen/logrus/hooks/test"
|
|
)
|
|
|
|
var (
|
|
_ = AttestationReceiver(&Service{})
|
|
_ = AttestationStateFetcher(&Service{})
|
|
)
|
|
|
|
func TestAttestationCheckPtState_FarFutureSlot(t *testing.T) {
|
|
helpers.ClearCache()
|
|
service, _ := minimalTestService(t)
|
|
|
|
service.genesisTime = time.Now()
|
|
|
|
e := primitives.Epoch(slots.MaxSlotBuffer/uint64(params.BeaconConfig().SlotsPerEpoch) + 1)
|
|
_, err := service.AttestationTargetState(t.Context(), ðpb.Checkpoint{Epoch: e})
|
|
require.ErrorContains(t, "exceeds max allowed value relative to the local clock", err)
|
|
}
|
|
|
|
func TestVerifyLMDFFGConsistent(t *testing.T) {
|
|
service, tr := minimalTestService(t)
|
|
ctx := tr.ctx
|
|
|
|
f := service.cfg.ForkChoiceStore
|
|
fc := ðpb.Checkpoint{Root: params.BeaconConfig().ZeroHash[:]}
|
|
state, r32, err := prepareForkchoiceState(ctx, 32, [32]byte{'a'}, params.BeaconConfig().ZeroHash, params.BeaconConfig().ZeroHash, fc, fc)
|
|
require.NoError(t, err)
|
|
require.NoError(t, f.InsertNode(ctx, state, r32))
|
|
|
|
state, r33, err := prepareForkchoiceState(ctx, 33, [32]byte{'b'}, r32.Root(), params.BeaconConfig().ZeroHash, fc, fc)
|
|
require.NoError(t, err)
|
|
require.NoError(t, f.InsertNode(ctx, state, r33))
|
|
|
|
wanted := "FFG and LMD votes are not consistent"
|
|
a := util.NewAttestation()
|
|
a.Data.Target.Epoch = 1
|
|
a.Data.Target.Root = []byte{'c'}
|
|
r33Root := r33.Root()
|
|
a.Data.BeaconBlockRoot = r33Root[:]
|
|
require.ErrorContains(t, wanted, service.VerifyLmdFfgConsistency(t.Context(), a))
|
|
|
|
r32Root := r32.Root()
|
|
a.Data.Target.Root = r32Root[:]
|
|
err = service.VerifyLmdFfgConsistency(t.Context(), a)
|
|
require.NoError(t, err, "Could not verify LMD and FFG votes to be consistent")
|
|
}
|
|
|
|
func TestProcessAttestations_Ok(t *testing.T) {
|
|
service, tr := minimalTestService(t)
|
|
hook := logTest.NewGlobal()
|
|
ctx := tr.ctx
|
|
|
|
service.genesisTime = prysmTime.Now().Add(-1 * time.Duration(params.BeaconConfig().SecondsPerSlot) * time.Second)
|
|
genesisState, pks := util.DeterministicGenesisState(t, 64)
|
|
require.NoError(t, genesisState.SetGenesisTime(time.Now().Add(-1*time.Duration(params.BeaconConfig().SecondsPerSlot)*time.Second)))
|
|
require.NoError(t, service.saveGenesisData(ctx, genesisState))
|
|
atts, err := util.GenerateAttestations(genesisState, pks, 1, 0, false)
|
|
require.NoError(t, err)
|
|
tRoot := bytesutil.ToBytes32(atts[0].GetData().Target.Root)
|
|
copied := genesisState.Copy()
|
|
copied, err = transition.ProcessSlots(ctx, copied, 1)
|
|
require.NoError(t, err)
|
|
require.NoError(t, service.cfg.BeaconDB.SaveState(ctx, copied, tRoot))
|
|
ofc := ðpb.Checkpoint{Root: params.BeaconConfig().ZeroHash[:]}
|
|
ojc := ðpb.Checkpoint{Root: params.BeaconConfig().ZeroHash[:]}
|
|
state, blkRoot, err := prepareForkchoiceState(ctx, 0, tRoot, tRoot, params.BeaconConfig().ZeroHash, ojc, ofc)
|
|
require.NoError(t, err)
|
|
require.NoError(t, service.cfg.ForkChoiceStore.InsertNode(ctx, state, blkRoot))
|
|
attsToSave := make([]ethpb.Att, len(atts))
|
|
copy(attsToSave, atts)
|
|
require.NoError(t, service.cfg.AttPool.SaveForkchoiceAttestations(attsToSave))
|
|
service.processAttestations(ctx, 0)
|
|
require.Equal(t, 0, len(service.cfg.AttPool.ForkchoiceAttestations()))
|
|
require.LogsDoNotContain(t, hook, "Could not process attestation for fork choice")
|
|
}
|
|
|
|
func TestService_ProcessAttestationsAndUpdateHead(t *testing.T) {
|
|
service, tr := minimalTestService(t)
|
|
ctx, fcs := tr.ctx, tr.fcs
|
|
|
|
service.genesisTime = prysmTime.Now().Add(-2 * time.Duration(params.BeaconConfig().SecondsPerSlot) * time.Second)
|
|
genesisState, pks := util.DeterministicGenesisState(t, 64)
|
|
require.NoError(t, service.saveGenesisData(ctx, genesisState))
|
|
ojc := ðpb.Checkpoint{Epoch: 0, Root: service.originBlockRoot[:]}
|
|
require.NoError(t, fcs.UpdateJustifiedCheckpoint(ctx, &forkchoicetypes.Checkpoint{Epoch: 0, Root: service.originBlockRoot}))
|
|
copied := genesisState.Copy()
|
|
// Generate a new block for attesters to attest
|
|
blk, err := util.GenerateFullBlock(copied, pks, util.DefaultBlockGenConfig(), 1)
|
|
require.NoError(t, err)
|
|
tRoot, err := blk.Block.HashTreeRoot()
|
|
require.NoError(t, err)
|
|
wsb, err := blocks.NewSignedBeaconBlock(blk)
|
|
require.NoError(t, err)
|
|
|
|
preState, err := service.getBlockPreState(ctx, wsb.Block())
|
|
require.NoError(t, err)
|
|
postState, err := service.validateStateTransition(ctx, preState, wsb)
|
|
require.NoError(t, err)
|
|
require.NoError(t, service.savePostStateInfo(ctx, tRoot, wsb, postState))
|
|
roblock, err := blocks.NewROBlockWithRoot(wsb, tRoot)
|
|
require.NoError(t, err)
|
|
service.cfg.ForkChoiceStore.Lock()
|
|
require.NoError(t, service.postBlockProcess(&postBlockProcessConfig{ctx, roblock, [32]byte{}, postState, false}))
|
|
service.cfg.ForkChoiceStore.Unlock()
|
|
copied, err = service.cfg.StateGen.StateByRoot(ctx, tRoot)
|
|
require.NoError(t, err)
|
|
require.Equal(t, 2, fcs.NodeCount())
|
|
require.NoError(t, service.cfg.BeaconDB.SaveBlock(ctx, wsb))
|
|
|
|
// Generate attestations for this block in Slot 1
|
|
atts, err := util.GenerateAttestations(copied, pks, 1, 1, false)
|
|
require.NoError(t, err)
|
|
attsToSave := make([]ethpb.Att, len(atts))
|
|
copy(attsToSave, atts)
|
|
require.NoError(t, service.cfg.AttPool.SaveForkchoiceAttestations(attsToSave))
|
|
// Verify the target is in forkchoice
|
|
require.Equal(t, true, fcs.HasNode(bytesutil.ToBytes32(atts[0].GetData().BeaconBlockRoot)))
|
|
require.Equal(t, tRoot, bytesutil.ToBytes32(atts[0].GetData().BeaconBlockRoot))
|
|
require.Equal(t, true, fcs.HasNode(service.originBlockRoot))
|
|
|
|
// Insert a new block to forkchoice
|
|
b, err := util.GenerateFullBlock(genesisState, pks, util.DefaultBlockGenConfig(), 2)
|
|
require.NoError(t, err)
|
|
b.Block.ParentRoot = service.originBlockRoot[:]
|
|
r, err := b.Block.HashTreeRoot()
|
|
require.NoError(t, err)
|
|
util.SaveBlock(t, ctx, service.cfg.BeaconDB, b)
|
|
state, blkRoot, err := prepareForkchoiceState(ctx, 2, r, service.originBlockRoot, [32]byte{'b'}, ojc, ojc)
|
|
require.NoError(t, err)
|
|
require.NoError(t, service.cfg.ForkChoiceStore.InsertNode(ctx, state, blkRoot))
|
|
require.Equal(t, 3, fcs.NodeCount())
|
|
service.head.root = r // Old head
|
|
|
|
require.Equal(t, 1, len(service.cfg.AttPool.ForkchoiceAttestations()))
|
|
service.UpdateHead(ctx, 0)
|
|
require.Equal(t, tRoot, service.headRoot())
|
|
require.Equal(t, 0, len(service.cfg.AttPool.ForkchoiceAttestations())) // Validate att pool is empty
|
|
}
|
|
|
|
func TestService_UpdateHead_NoAtts(t *testing.T) {
|
|
service, tr := minimalTestService(t)
|
|
ctx, fcs := tr.ctx, tr.fcs
|
|
|
|
service.genesisTime = prysmTime.Now().Add(-2 * time.Duration(params.BeaconConfig().SecondsPerSlot) * time.Second)
|
|
genesisState, pks := util.DeterministicGenesisState(t, 64)
|
|
require.NoError(t, service.saveGenesisData(ctx, genesisState))
|
|
require.NoError(t, fcs.UpdateJustifiedCheckpoint(ctx, &forkchoicetypes.Checkpoint{Epoch: 0, Root: service.originBlockRoot}))
|
|
copied := genesisState.Copy()
|
|
// Generate a new block
|
|
blk, err := util.GenerateFullBlock(copied, pks, util.DefaultBlockGenConfig(), 1)
|
|
require.NoError(t, err)
|
|
tRoot, err := blk.Block.HashTreeRoot()
|
|
require.NoError(t, err)
|
|
wsb, err := blocks.NewSignedBeaconBlock(blk)
|
|
require.NoError(t, err)
|
|
|
|
preState, err := service.getBlockPreState(ctx, wsb.Block())
|
|
require.NoError(t, err)
|
|
postState, err := service.validateStateTransition(ctx, preState, wsb)
|
|
require.NoError(t, err)
|
|
require.NoError(t, service.savePostStateInfo(ctx, tRoot, wsb, postState))
|
|
roblock, err := blocks.NewROBlockWithRoot(wsb, tRoot)
|
|
require.NoError(t, err)
|
|
service.cfg.ForkChoiceStore.Lock()
|
|
require.NoError(t, service.postBlockProcess(&postBlockProcessConfig{ctx, roblock, [32]byte{}, postState, false}))
|
|
service.cfg.ForkChoiceStore.Unlock()
|
|
require.Equal(t, 2, fcs.NodeCount())
|
|
require.NoError(t, service.cfg.BeaconDB.SaveBlock(ctx, wsb))
|
|
require.Equal(t, tRoot, service.head.root)
|
|
|
|
// Insert a new block to forkchoice
|
|
ojc := ðpb.Checkpoint{Epoch: 0, Root: params.BeaconConfig().ZeroHash[:]}
|
|
b, err := util.GenerateFullBlock(genesisState, pks, util.DefaultBlockGenConfig(), 2)
|
|
require.NoError(t, err)
|
|
b.Block.ParentRoot = service.originBlockRoot[:]
|
|
r, err := b.Block.HashTreeRoot()
|
|
require.NoError(t, err)
|
|
util.SaveBlock(t, ctx, service.cfg.BeaconDB, b)
|
|
state, blkRoot, err := prepareForkchoiceState(ctx, 2, r, service.originBlockRoot, [32]byte{'b'}, ojc, ojc)
|
|
require.NoError(t, err)
|
|
require.NoError(t, service.cfg.ForkChoiceStore.InsertNode(ctx, state, blkRoot))
|
|
require.Equal(t, 3, fcs.NodeCount())
|
|
|
|
require.Equal(t, 0, service.cfg.AttPool.ForkchoiceAttestationCount())
|
|
service.UpdateHead(ctx, 0)
|
|
require.Equal(t, r, service.headRoot())
|
|
|
|
require.Equal(t, 0, len(service.cfg.AttPool.ForkchoiceAttestations())) // Validate att pool is empty
|
|
}
|