Compare commits

...

6 Commits

Author SHA1 Message Date
potuz
7880f2887f Add error check on initial sidecar fetching 2025-11-11 10:55:11 -03:00
Potuz
6735c921f8 Dependent root instead of target (#15996)
* Add DepdenentRootForEpoch forkchoice helper

* Use dependent root in helpers to get head
2025-11-08 01:18:44 +00:00
Muzry
02fb1534e1 Improve readability in slashing import and remove duplicated code (#15957)
Co-authored-by: james-prysm <90280386+james-prysm@users.noreply.github.com>
2025-11-07 22:12:40 +00:00
Daniel Briskin
619334115a update interop genesis for fulu (#15948) 2025-11-07 21:49:03 +00:00
Bastin
de0825f880 fulu lc (#15995)
Co-authored-by: james-prysm <90280386+james-prysm@users.noreply.github.com>
2025-11-06 22:47:35 +00:00
Potuz
7794a77ae6 Use Head to validate sidecars when possible (#15977)
* Use Head to validate sidecars when possible

🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>

* LazyHeadStateProvider that avoids service registry

* Remove extra file

---------

Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: james-prysm <90280386+james-prysm@users.noreply.github.com>
Co-authored-by: Kasey Kirkham <kasey@users.noreply.github.com>
2025-11-06 18:43:38 +00:00
41 changed files with 1007 additions and 225 deletions

View File

@@ -79,6 +79,7 @@ type HeadFetcher interface {
HeadPublicKeyToValidatorIndex(pubKey [fieldparams.BLSPubkeyLength]byte) (primitives.ValidatorIndex, bool)
HeadValidatorIndexToPublicKey(ctx context.Context, index primitives.ValidatorIndex) ([fieldparams.BLSPubkeyLength]byte, error)
ChainHeads() ([][32]byte, []primitives.Slot)
DependentRootForEpoch([32]byte, primitives.Epoch) ([32]byte, error)
TargetRootForEpoch([32]byte, primitives.Epoch) ([32]byte, error)
HeadSyncCommitteeFetcher
HeadDomainFetcher
@@ -470,6 +471,13 @@ func (s *Service) IsOptimisticForRoot(ctx context.Context, root [32]byte) (bool,
return !isCanonical, nil
}
// DependentRootForEpoch wraps the corresponding method in forkchoice
func (s *Service) DependentRootForEpoch(root [32]byte, epoch primitives.Epoch) ([32]byte, error) {
s.cfg.ForkChoiceStore.RLock()
defer s.cfg.ForkChoiceStore.RUnlock()
return s.cfg.ForkChoiceStore.DependentRootForEpoch(root, epoch)
}
// TargetRootForEpoch wraps the corresponding method in forkchoice
func (s *Service) TargetRootForEpoch(root [32]byte, epoch primitives.Epoch) ([32]byte, error) {
s.cfg.ForkChoiceStore.RLock()

View File

@@ -1,7 +1,6 @@
package blockchain
import (
"bytes"
"context"
"fmt"
"strconv"
@@ -34,11 +33,15 @@ func (s *Service) getRecentPreState(ctx context.Context, c *ethpb.Checkpoint) st
if err != nil {
return nil
}
headTarget, err := s.cfg.ForkChoiceStore.TargetRootForEpoch([32]byte(headRoot), c.Epoch)
headDependent, err := s.cfg.ForkChoiceStore.DependentRootForEpoch([32]byte(headRoot), c.Epoch)
if err != nil {
return nil
}
if !bytes.Equal(c.Root, headTarget[:]) {
targetDependent, err := s.cfg.ForkChoiceStore.DependentRootForEpoch([32]byte(c.Root), c.Epoch)
if err != nil {
return nil
}
if targetDependent != headDependent {
return nil
}

View File

@@ -2804,7 +2804,7 @@ func TestProcessLightClientUpdate(t *testing.T) {
require.NoError(t, s.cfg.BeaconDB.SaveState(ctx, headState, [32]byte{1, 2}))
require.NoError(t, s.cfg.BeaconDB.SaveHeadBlockRoot(ctx, [32]byte{1, 2}))
for testVersion := version.Altair; testVersion <= version.Electra; testVersion++ {
for _, testVersion := range version.All()[1:] {
t.Run(version.String(testVersion), func(t *testing.T) {
l := util.NewTestLightClient(t, testVersion)

View File

@@ -758,6 +758,11 @@ func (c *ChainService) ReceiveDataColumns(dcs []blocks.VerifiedRODataColumn) err
return nil
}
// DependentRootForEpoch mocks the same method in the chain service
func (c *ChainService) DependentRootForEpoch(_ [32]byte, _ primitives.Epoch) ([32]byte, error) {
return c.TargetRoot, nil
}
// TargetRootForEpoch mocks the same method in the chain service
func (c *ChainService) TargetRootForEpoch(_ [32]byte, _ primitives.Epoch) ([32]byte, error) {
return c.TargetRoot, nil

View File

@@ -181,6 +181,13 @@ func decodeLightClientBootstrap(enc []byte) (interfaces.LightClientBootstrap, []
}
m = bootstrap
syncCommitteeHash = enc[len(ElectraKey) : len(ElectraKey)+32]
case hasFuluKey(enc):
bootstrap := &ethpb.LightClientBootstrapElectra{}
if err := bootstrap.UnmarshalSSZ(enc[len(fuluKey)+32:]); err != nil {
return nil, nil, errors.Wrap(err, "could not unmarshal Electra light client bootstrap")
}
m = bootstrap
syncCommitteeHash = enc[len(fuluKey) : len(fuluKey)+32]
default:
return nil, nil, errors.New("decoding of saved light client bootstrap is unsupported")
}
@@ -296,6 +303,12 @@ func decodeLightClientUpdate(enc []byte) (interfaces.LightClientUpdate, error) {
return nil, errors.Wrap(err, "could not unmarshal Electra light client update")
}
m = update
case hasFuluKey(enc):
update := &ethpb.LightClientUpdateElectra{}
if err := update.UnmarshalSSZ(enc[len(fuluKey):]); err != nil {
return nil, errors.Wrap(err, "could not unmarshal Fulu light client update")
}
m = update
default:
return nil, errors.New("decoding of saved light client update is unsupported")
}
@@ -304,6 +317,8 @@ func decodeLightClientUpdate(enc []byte) (interfaces.LightClientUpdate, error) {
func keyForLightClientUpdate(v int) ([]byte, error) {
switch v {
case version.Fulu:
return fuluKey, nil
case version.Electra:
return ElectraKey, nil
case version.Deneb:

View File

@@ -215,8 +215,7 @@ func TestStore_LightClientUpdate_CanSaveRetrieve(t *testing.T) {
db := setupDB(t)
ctx := t.Context()
for testVersion := version.Altair; testVersion <= version.Electra; testVersion++ {
for _, testVersion := range version.All()[1:] {
t.Run(version.String(testVersion), func(t *testing.T) {
update, err := createUpdate(t, testVersion)
require.NoError(t, err)
@@ -572,8 +571,7 @@ func TestStore_LightClientBootstrap_CanSaveRetrieve(t *testing.T) {
require.NoError(t, err)
require.IsNil(t, retrievedBootstrap)
})
for testVersion := version.Altair; testVersion <= version.Electra; testVersion++ {
for _, testVersion := range version.All()[1:] {
t.Run(version.String(testVersion), func(t *testing.T) {
bootstrap, err := createDefaultLightClientBootstrap(primitives.Slot(uint64(params.BeaconConfig().VersionToForkEpochMap()[testVersion]) * uint64(params.BeaconConfig().SlotsPerEpoch)))
require.NoError(t, err)

View File

@@ -100,6 +100,7 @@ go_test(
deps = [
"//async/event:go_default_library",
"//beacon-chain/blockchain/kzg:go_default_library",
"//beacon-chain/blockchain/testing:go_default_library",
"//beacon-chain/cache/depositsnapshot:go_default_library",
"//beacon-chain/core/feed:go_default_library",
"//beacon-chain/core/feed/state:go_default_library",

View File

@@ -8,6 +8,7 @@ import (
"time"
"github.com/OffchainLabs/prysm/v7/async/event"
chainMock "github.com/OffchainLabs/prysm/v7/beacon-chain/blockchain/testing"
"github.com/OffchainLabs/prysm/v7/beacon-chain/cache/depositsnapshot"
dbutil "github.com/OffchainLabs/prysm/v7/beacon-chain/db/testing"
mockExecution "github.com/OffchainLabs/prysm/v7/beacon-chain/execution/testing"
@@ -99,7 +100,7 @@ func TestStart_OK(t *testing.T) {
c := startup.NewClockSynchronizer()
require.NoError(t, c.SetClock(startup.NewClock(time.Unix(0, 0), [32]byte{})))
waiter := verification.NewInitializerWaiter(
c, forkchoice.NewROForkChoice(nil), nil)
c, forkchoice.NewROForkChoice(nil), nil, &chainMock.ChainService{})
web3Service, err := NewService(t.Context(),
WithHttpEndpoint(endpoint),

View File

@@ -626,21 +626,26 @@ func (f *ForkChoice) Slot(root [32]byte) (primitives.Slot, error) {
// DependentRoot returns the last root of the epoch prior to the requested ecoch in the canonical chain.
func (f *ForkChoice) DependentRoot(epoch primitives.Epoch) ([32]byte, error) {
tr, err := f.TargetRootForEpoch(f.CachedHeadRoot(), epoch)
return f.DependentRootForEpoch(f.CachedHeadRoot(), epoch)
}
// DependentRootForEpoch return the last root of the epoch prior to the requested ecoch for the given root.
func (f *ForkChoice) DependentRootForEpoch(root [32]byte, epoch primitives.Epoch) ([32]byte, error) {
tr, err := f.TargetRootForEpoch(root, epoch)
if err != nil {
return [32]byte{}, err
}
if tr == [32]byte{} {
return [32]byte{}, nil
}
n, ok := f.store.nodeByRoot[tr]
if !ok || n == nil {
node, ok := f.store.nodeByRoot[tr]
if !ok || node == nil {
return [32]byte{}, ErrNilNode
}
if slots.ToEpoch(n.slot) == epoch && n.parent != nil {
n = n.parent
if slots.ToEpoch(node.slot) >= epoch && node.parent != nil {
node = node.parent
}
return n.root, nil
return node.root, nil
}
// TargetRootForEpoch returns the root of the target block for a given epoch.

View File

@@ -608,6 +608,96 @@ func TestStore_TargetRootForEpoch(t *testing.T) {
require.Equal(t, blk4.Root(), target)
}
func TestStore_DependentRootForEpoch(t *testing.T) {
ctx := t.Context()
f := setup(1, 1)
// Build the following tree structure:
// /------------37
// 0<--31<---32 <---33 <--- 35 <-------- 65 <--- 66
// \-- 36 ------------- 38
// Insert block at slot 31 (epoch 0)
state, blk31, err := prepareForkchoiceState(ctx, 31, [32]byte{31}, params.BeaconConfig().ZeroHash, params.BeaconConfig().ZeroHash, 1, 1)
require.NoError(t, err)
require.NoError(t, f.InsertNode(ctx, state, blk31))
// Insert block at slot 32 (epoch 1)
state, blk32, err := prepareForkchoiceState(ctx, 32, [32]byte{32}, blk31.Root(), params.BeaconConfig().ZeroHash, 1, 1)
require.NoError(t, err)
require.NoError(t, f.InsertNode(ctx, state, blk32))
// Insert block at slot 33 (epoch 1)
state, blk33, err := prepareForkchoiceState(ctx, 33, [32]byte{33}, blk32.Root(), params.BeaconConfig().ZeroHash, 1, 1)
require.NoError(t, err)
require.NoError(t, f.InsertNode(ctx, state, blk33))
// Insert block at slot 35 (epoch 1)
state, blk35, err := prepareForkchoiceState(ctx, 35, [32]byte{35}, blk33.Root(), params.BeaconConfig().ZeroHash, 1, 1)
require.NoError(t, err)
require.NoError(t, f.InsertNode(ctx, state, blk35))
// Insert fork: block at slot 36 (epoch 1) descending from block 32
state, blk36, err := prepareForkchoiceState(ctx, 36, [32]byte{36}, blk32.Root(), params.BeaconConfig().ZeroHash, 1, 1)
require.NoError(t, err)
require.NoError(t, f.InsertNode(ctx, state, blk36))
// Insert block at slot 37 (epoch 1) descending from block 33
state, blk37, err := prepareForkchoiceState(ctx, 37, [32]byte{37}, blk33.Root(), params.BeaconConfig().ZeroHash, 1, 1)
require.NoError(t, err)
require.NoError(t, f.InsertNode(ctx, state, blk37))
// Insert block at slot 38 (epoch 1) descending from block 36
state, blk38, err := prepareForkchoiceState(ctx, 38, [32]byte{38}, blk36.Root(), params.BeaconConfig().ZeroHash, 1, 1)
require.NoError(t, err)
require.NoError(t, f.InsertNode(ctx, state, blk38))
// Insert block at slot 65 (epoch 2) descending from block 35
state, blk65, err := prepareForkchoiceState(ctx, 65, [32]byte{65}, blk35.Root(), params.BeaconConfig().ZeroHash, 1, 1)
require.NoError(t, err)
require.NoError(t, f.InsertNode(ctx, state, blk65))
// Insert block at slot 66 (epoch 2) descending from block 65
state, blk66, err := prepareForkchoiceState(ctx, 66, [32]byte{66}, blk65.Root(), params.BeaconConfig().ZeroHash, 1, 1)
require.NoError(t, err)
require.NoError(t, f.InsertNode(ctx, state, blk66))
// Test dependent root for block 32 at epoch 1 - should be block 31
dependent, err := f.DependentRootForEpoch(blk32.Root(), 1)
require.NoError(t, err)
require.Equal(t, blk31.Root(), dependent)
// Test dependent root for block 32 at epoch 2 - should be block 32
dependent, err = f.DependentRootForEpoch(blk32.Root(), 2)
require.NoError(t, err)
require.Equal(t, blk32.Root(), dependent)
// Test dependent root for block 33 at epoch 1 - should be block 31
dependent, err = f.DependentRootForEpoch(blk33.Root(), 1)
require.NoError(t, err)
require.Equal(t, blk31.Root(), dependent)
// Test dependent root for block 38 at epoch 1 - should be block 31
dependent, err = f.DependentRootForEpoch(blk38.Root(), 1)
require.NoError(t, err)
require.Equal(t, blk31.Root(), dependent)
// Test dependent root for block 36 at epoch 2 - should be block 36
dependent, err = f.DependentRootForEpoch(blk36.Root(), 2)
require.NoError(t, err)
require.Equal(t, blk36.Root(), dependent)
// Test dependent root for block 66 at epoch 1 - should be block 31
dependent, err = f.DependentRootForEpoch(blk66.Root(), 1)
require.NoError(t, err)
require.Equal(t, blk31.Root(), dependent)
// Test dependent root for block 66 at epoch 2 - should be block 35
dependent, err = f.DependentRootForEpoch(blk66.Root(), 2)
require.NoError(t, err)
require.Equal(t, blk35.Root(), dependent)
}
func TestStore_CleanupInserting(t *testing.T) {
f := setup(0, 0)
ctx := t.Context()

View File

@@ -81,6 +81,7 @@ type FastGetter interface {
ShouldOverrideFCU() bool
Slot([32]byte) (primitives.Slot, error)
DependentRoot(primitives.Epoch) ([32]byte, error)
DependentRootForEpoch([32]byte, primitives.Epoch) ([32]byte, error)
TargetRootForEpoch([32]byte, primitives.Epoch) ([32]byte, error)
UnrealizedJustifiedPayloadBlockHash() [32]byte
Weight(root [32]byte) (uint64, error)

View File

@@ -177,6 +177,13 @@ func (ro *ROForkChoice) DependentRoot(epoch primitives.Epoch) ([32]byte, error)
return ro.getter.DependentRoot(epoch)
}
// DependentRootForEpoch delegates to the underlying forkchoice call, under a lock.
func (ro *ROForkChoice) DependentRootForEpoch(root [32]byte, epoch primitives.Epoch) ([32]byte, error) {
ro.l.RLock()
defer ro.l.RUnlock()
return ro.getter.DependentRootForEpoch(root, epoch)
}
// TargetRootForEpoch delegates to the underlying forkchoice call, under a lock.
func (ro *ROForkChoice) TargetRootForEpoch(root [32]byte, epoch primitives.Epoch) ([32]byte, error) {
ro.l.RLock()

View File

@@ -40,6 +40,7 @@ const (
targetRootForEpochCalled
parentRootCalled
dependentRootCalled
dependentRootForEpochCalled
)
func _discard(t *testing.T, e error) {
@@ -305,6 +306,12 @@ func (ro *mockROForkchoice) DependentRoot(_ primitives.Epoch) ([32]byte, error)
return [32]byte{}, nil
}
// DependentRootForEpoch implements FastGetter.
func (ro *mockROForkchoice) DependentRootForEpoch(_ [32]byte, _ primitives.Epoch) ([32]byte, error) {
ro.calls = append(ro.calls, dependentRootForEpochCalled)
return [32]byte{}, nil
}
// TargetRootForEpoch implements FastGetter.
func (ro *mockROForkchoice) TargetRootForEpoch(_ [32]byte, _ primitives.Epoch) ([32]byte, error) {
ro.calls = append(ro.calls, targetRootForEpochCalled)

View File

@@ -29,58 +29,22 @@ func TestLightClient_NewLightClientOptimisticUpdateFromBeaconState(t *testing.T)
cfg.CapellaForkEpoch = 3
cfg.DenebForkEpoch = 4
cfg.ElectraForkEpoch = 5
cfg.FuluForkEpoch = 6
params.OverrideBeaconConfig(cfg)
t.Run("Altair", func(t *testing.T) {
l := util.NewTestLightClient(t, version.Altair)
for _, testVersion := range version.All()[1:] {
t.Run(version.String(testVersion), func(t *testing.T) {
l := util.NewTestLightClient(t, testVersion)
update, err := lightClient.NewLightClientOptimisticUpdateFromBeaconState(l.Ctx, l.State, l.Block, l.AttestedState, l.AttestedBlock)
require.NoError(t, err)
require.NotNil(t, update, "update is nil")
require.Equal(t, l.Block.Block().Slot(), update.SignatureSlot(), "Signature slot is not equal")
update, err := lightClient.NewLightClientOptimisticUpdateFromBeaconState(l.Ctx, l.State, l.Block, l.AttestedState, l.AttestedBlock)
require.NoError(t, err)
require.NotNil(t, update, "update is nil")
require.Equal(t, l.Block.Block().Slot(), update.SignatureSlot(), "Signature slot is not equal")
l.CheckSyncAggregate(update.SyncAggregate())
l.CheckAttestedHeader(update.AttestedHeader())
})
t.Run("Capella", func(t *testing.T) {
l := util.NewTestLightClient(t, version.Capella)
update, err := lightClient.NewLightClientOptimisticUpdateFromBeaconState(l.Ctx, l.State, l.Block, l.AttestedState, l.AttestedBlock)
require.NoError(t, err)
require.NotNil(t, update, "update is nil")
require.Equal(t, l.Block.Block().Slot(), update.SignatureSlot(), "Signature slot is not equal")
l.CheckSyncAggregate(update.SyncAggregate())
l.CheckAttestedHeader(update.AttestedHeader())
})
t.Run("Deneb", func(t *testing.T) {
l := util.NewTestLightClient(t, version.Deneb)
update, err := lightClient.NewLightClientOptimisticUpdateFromBeaconState(l.Ctx, l.State, l.Block, l.AttestedState, l.AttestedBlock)
require.NoError(t, err)
require.NotNil(t, update, "update is nil")
require.Equal(t, l.Block.Block().Slot(), update.SignatureSlot(), "Signature slot is not equal")
l.CheckSyncAggregate(update.SyncAggregate())
l.CheckAttestedHeader(update.AttestedHeader())
})
t.Run("Electra", func(t *testing.T) {
l := util.NewTestLightClient(t, version.Electra)
update, err := lightClient.NewLightClientOptimisticUpdateFromBeaconState(l.Ctx, l.State, l.Block, l.AttestedState, l.AttestedBlock)
require.NoError(t, err)
require.NotNil(t, update, "update is nil")
require.Equal(t, l.Block.Block().Slot(), update.SignatureSlot(), "Signature slot is not equal")
l.CheckSyncAggregate(update.SyncAggregate())
l.CheckAttestedHeader(update.AttestedHeader())
})
l.CheckSyncAggregate(update.SyncAggregate())
l.CheckAttestedHeader(update.AttestedHeader())
})
}
}
func TestLightClient_NewLightClientFinalityUpdateFromBeaconState(t *testing.T) {
@@ -91,6 +55,7 @@ func TestLightClient_NewLightClientFinalityUpdateFromBeaconState(t *testing.T) {
cfg.CapellaForkEpoch = 3
cfg.DenebForkEpoch = 4
cfg.ElectraForkEpoch = 5
cfg.FuluForkEpoch = 6
params.OverrideBeaconConfig(cfg)
t.Run("Altair", func(t *testing.T) {
@@ -538,6 +503,157 @@ func TestLightClient_NewLightClientFinalityUpdateFromBeaconState(t *testing.T) {
require.DeepSSZEqual(t, execution, updateExecution.Proto(), "Finalized Block Execution is not equal")
})
})
t.Run("Fulu", func(t *testing.T) {
t.Run("FinalizedBlock Not Nil", func(t *testing.T) {
l := util.NewTestLightClient(t, version.Fulu)
update, err := lightClient.NewLightClientFinalityUpdateFromBeaconState(l.Ctx, l.State, l.Block, l.AttestedState, l.AttestedBlock, l.FinalizedBlock)
require.NoError(t, err)
require.NotNil(t, update, "update is nil")
require.Equal(t, l.Block.Block().Slot(), update.SignatureSlot(), "Signature slot is not equal")
l.CheckSyncAggregate(update.SyncAggregate())
l.CheckAttestedHeader(update.AttestedHeader())
//zeroHash := params.BeaconConfig().ZeroHash[:]
finalizedBlockHeader, err := l.FinalizedBlock.Header()
require.NoError(t, err)
require.NotNil(t, update.FinalizedHeader(), "Finalized header is nil")
updateFinalizedHeaderBeacon := update.FinalizedHeader().Beacon()
require.Equal(t, finalizedBlockHeader.Header.Slot, updateFinalizedHeaderBeacon.Slot, "Finalized header slot is not equal")
require.Equal(t, finalizedBlockHeader.Header.ProposerIndex, updateFinalizedHeaderBeacon.ProposerIndex, "Finalized header proposer index is not equal")
require.DeepSSZEqual(t, finalizedBlockHeader.Header.ParentRoot, updateFinalizedHeaderBeacon.ParentRoot, "Finalized header parent root is not equal")
require.DeepSSZEqual(t, finalizedBlockHeader.Header.StateRoot, updateFinalizedHeaderBeacon.StateRoot, "Finalized header state root is not equal")
require.DeepSSZEqual(t, finalizedBlockHeader.Header.BodyRoot, updateFinalizedHeaderBeacon.BodyRoot, "Finalized header body root is not equal")
fb, err := update.FinalityBranchElectra()
require.NoError(t, err)
proof, err := l.AttestedState.FinalizedRootProof(l.Ctx)
require.NoError(t, err)
for i, leaf := range fb {
require.DeepSSZEqual(t, proof[i], leaf[:], "Leaf is not equal")
}
// Check Execution BlockHash
payloadInterface, err := l.FinalizedBlock.Block().Body().Execution()
require.NoError(t, err)
transactionsRoot, err := payloadInterface.TransactionsRoot()
if errors.Is(err, consensustypes.ErrUnsupportedField) {
transactions, err := payloadInterface.Transactions()
require.NoError(t, err)
transactionsRootArray, err := ssz.TransactionsRoot(transactions)
require.NoError(t, err)
transactionsRoot = transactionsRootArray[:]
} else {
require.NoError(t, err)
}
withdrawalsRoot, err := payloadInterface.WithdrawalsRoot()
if errors.Is(err, consensustypes.ErrUnsupportedField) {
withdrawals, err := payloadInterface.Withdrawals()
require.NoError(t, err)
withdrawalsRootArray, err := ssz.WithdrawalSliceRoot(withdrawals, fieldparams.MaxWithdrawalsPerPayload)
require.NoError(t, err)
withdrawalsRoot = withdrawalsRootArray[:]
} else {
require.NoError(t, err)
}
execution := &v11.ExecutionPayloadHeaderDeneb{
ParentHash: payloadInterface.ParentHash(),
FeeRecipient: payloadInterface.FeeRecipient(),
StateRoot: payloadInterface.StateRoot(),
ReceiptsRoot: payloadInterface.ReceiptsRoot(),
LogsBloom: payloadInterface.LogsBloom(),
PrevRandao: payloadInterface.PrevRandao(),
BlockNumber: payloadInterface.BlockNumber(),
GasLimit: payloadInterface.GasLimit(),
GasUsed: payloadInterface.GasUsed(),
Timestamp: payloadInterface.Timestamp(),
ExtraData: payloadInterface.ExtraData(),
BaseFeePerGas: payloadInterface.BaseFeePerGas(),
BlockHash: payloadInterface.BlockHash(),
TransactionsRoot: transactionsRoot,
WithdrawalsRoot: withdrawalsRoot,
}
updateExecution, err := update.FinalizedHeader().Execution()
require.NoError(t, err)
require.DeepSSZEqual(t, execution, updateExecution.Proto(), "Finalized Block Execution is not equal")
})
t.Run("FinalizedBlock In Previous Fork", func(t *testing.T) {
l := util.NewTestLightClient(t, version.Fulu, util.WithFinalizedCheckpointInPrevFork())
update, err := lightClient.NewLightClientFinalityUpdateFromBeaconState(l.Ctx, l.State, l.Block, l.AttestedState, l.AttestedBlock, l.FinalizedBlock)
require.NoError(t, err)
require.NotNil(t, update, "update is nil")
require.Equal(t, l.Block.Block().Slot(), update.SignatureSlot(), "Signature slot is not equal")
l.CheckSyncAggregate(update.SyncAggregate())
l.CheckAttestedHeader(update.AttestedHeader())
finalizedBlockHeader, err := l.FinalizedBlock.Header()
require.NoError(t, err)
require.NotNil(t, update.FinalizedHeader(), "Finalized header is nil")
updateFinalizedHeaderBeacon := update.FinalizedHeader().Beacon()
require.Equal(t, finalizedBlockHeader.Header.Slot, updateFinalizedHeaderBeacon.Slot, "Finalized header slot is not equal")
require.Equal(t, finalizedBlockHeader.Header.ProposerIndex, updateFinalizedHeaderBeacon.ProposerIndex, "Finalized header proposer index is not equal")
require.DeepSSZEqual(t, finalizedBlockHeader.Header.ParentRoot, updateFinalizedHeaderBeacon.ParentRoot, "Finalized header parent root is not equal")
require.DeepSSZEqual(t, finalizedBlockHeader.Header.StateRoot, updateFinalizedHeaderBeacon.StateRoot, "Finalized header state root is not equal")
require.DeepSSZEqual(t, finalizedBlockHeader.Header.BodyRoot, updateFinalizedHeaderBeacon.BodyRoot, "Finalized header body root is not equal")
fb, err := update.FinalityBranchElectra()
require.NoError(t, err)
proof, err := l.AttestedState.FinalizedRootProof(l.Ctx)
require.NoError(t, err)
for i, leaf := range fb {
require.DeepSSZEqual(t, proof[i], leaf[:], "Leaf is not equal")
}
// Check Execution BlockHash
payloadInterface, err := l.FinalizedBlock.Block().Body().Execution()
require.NoError(t, err)
transactionsRoot, err := payloadInterface.TransactionsRoot()
if errors.Is(err, consensustypes.ErrUnsupportedField) {
transactions, err := payloadInterface.Transactions()
require.NoError(t, err)
transactionsRootArray, err := ssz.TransactionsRoot(transactions)
require.NoError(t, err)
transactionsRoot = transactionsRootArray[:]
} else {
require.NoError(t, err)
}
withdrawalsRoot, err := payloadInterface.WithdrawalsRoot()
if errors.Is(err, consensustypes.ErrUnsupportedField) {
withdrawals, err := payloadInterface.Withdrawals()
require.NoError(t, err)
withdrawalsRootArray, err := ssz.WithdrawalSliceRoot(withdrawals, fieldparams.MaxWithdrawalsPerPayload)
require.NoError(t, err)
withdrawalsRoot = withdrawalsRootArray[:]
} else {
require.NoError(t, err)
}
execution := &v11.ExecutionPayloadHeaderDeneb{
ParentHash: payloadInterface.ParentHash(),
FeeRecipient: payloadInterface.FeeRecipient(),
StateRoot: payloadInterface.StateRoot(),
ReceiptsRoot: payloadInterface.ReceiptsRoot(),
LogsBloom: payloadInterface.LogsBloom(),
PrevRandao: payloadInterface.PrevRandao(),
BlockNumber: payloadInterface.BlockNumber(),
GasLimit: payloadInterface.GasLimit(),
GasUsed: payloadInterface.GasUsed(),
Timestamp: payloadInterface.Timestamp(),
ExtraData: payloadInterface.ExtraData(),
BaseFeePerGas: payloadInterface.BaseFeePerGas(),
BlockHash: payloadInterface.BlockHash(),
TransactionsRoot: transactionsRoot,
WithdrawalsRoot: withdrawalsRoot,
}
updateExecution, err := update.FinalizedHeader().Execution()
require.NoError(t, err)
require.DeepSSZEqual(t, execution, updateExecution.Proto(), "Finalized Block Execution is not equal")
})
})
}
func TestLightClient_BlockToLightClientHeader(t *testing.T) {
@@ -983,6 +1099,138 @@ func TestLightClient_BlockToLightClientHeader(t *testing.T) {
})
})
t.Run("Fulu", func(t *testing.T) {
t.Run("Non-Blinded Beacon Block", func(t *testing.T) {
l := util.NewTestLightClient(t, version.Fulu)
header, err := lightClient.BlockToLightClientHeader(l.Ctx, version.Fulu, l.Block)
require.NoError(t, err)
require.NotNil(t, header, "header is nil")
parentRoot := l.Block.Block().ParentRoot()
stateRoot := l.Block.Block().StateRoot()
bodyRoot, err := l.Block.Block().Body().HashTreeRoot()
require.NoError(t, err)
payload, err := l.Block.Block().Body().Execution()
require.NoError(t, err)
transactionsRoot, err := lightClient.ComputeTransactionsRoot(payload)
require.NoError(t, err)
withdrawalsRoot, err := lightClient.ComputeWithdrawalsRoot(payload)
require.NoError(t, err)
blobGasUsed, err := payload.BlobGasUsed()
require.NoError(t, err)
excessBlobGas, err := payload.ExcessBlobGas()
require.NoError(t, err)
executionHeader := &v11.ExecutionPayloadHeaderDeneb{
ParentHash: payload.ParentHash(),
FeeRecipient: payload.FeeRecipient(),
StateRoot: payload.StateRoot(),
ReceiptsRoot: payload.ReceiptsRoot(),
LogsBloom: payload.LogsBloom(),
PrevRandao: payload.PrevRandao(),
BlockNumber: payload.BlockNumber(),
GasLimit: payload.GasLimit(),
GasUsed: payload.GasUsed(),
Timestamp: payload.Timestamp(),
ExtraData: payload.ExtraData(),
BaseFeePerGas: payload.BaseFeePerGas(),
BlockHash: payload.BlockHash(),
TransactionsRoot: transactionsRoot,
WithdrawalsRoot: withdrawalsRoot,
BlobGasUsed: blobGasUsed,
ExcessBlobGas: excessBlobGas,
}
executionPayloadProof, err := blocks.PayloadProof(l.Ctx, l.Block.Block())
require.NoError(t, err)
require.Equal(t, l.Block.Block().Slot(), header.Beacon().Slot, "Slot is not equal")
require.Equal(t, l.Block.Block().ProposerIndex(), header.Beacon().ProposerIndex, "Proposer index is not equal")
require.DeepSSZEqual(t, parentRoot[:], header.Beacon().ParentRoot, "Parent root is not equal")
require.DeepSSZEqual(t, stateRoot[:], header.Beacon().StateRoot, "State root is not equal")
require.DeepSSZEqual(t, bodyRoot[:], header.Beacon().BodyRoot, "Body root is not equal")
headerExecution, err := header.Execution()
require.NoError(t, err)
require.DeepSSZEqual(t, executionHeader, headerExecution.Proto(), "Execution headers are not equal")
headerExecutionBranch, err := header.ExecutionBranch()
require.NoError(t, err)
require.DeepSSZEqual(t, executionPayloadProof, convertArrayToSlice(headerExecutionBranch), "Execution payload proofs are not equal")
})
t.Run("Blinded Beacon Block", func(t *testing.T) {
l := util.NewTestLightClient(t, version.Fulu, util.WithBlinded())
header, err := lightClient.BlockToLightClientHeader(l.Ctx, version.Fulu, l.Block)
require.NoError(t, err)
require.NotNil(t, header, "header is nil")
parentRoot := l.Block.Block().ParentRoot()
stateRoot := l.Block.Block().StateRoot()
bodyRoot, err := l.Block.Block().Body().HashTreeRoot()
require.NoError(t, err)
payload, err := l.Block.Block().Body().Execution()
require.NoError(t, err)
transactionsRoot, err := payload.TransactionsRoot()
require.NoError(t, err)
withdrawalsRoot, err := payload.WithdrawalsRoot()
require.NoError(t, err)
blobGasUsed, err := payload.BlobGasUsed()
require.NoError(t, err)
excessBlobGas, err := payload.ExcessBlobGas()
require.NoError(t, err)
executionHeader := &v11.ExecutionPayloadHeaderDeneb{
ParentHash: payload.ParentHash(),
FeeRecipient: payload.FeeRecipient(),
StateRoot: payload.StateRoot(),
ReceiptsRoot: payload.ReceiptsRoot(),
LogsBloom: payload.LogsBloom(),
PrevRandao: payload.PrevRandao(),
BlockNumber: payload.BlockNumber(),
GasLimit: payload.GasLimit(),
GasUsed: payload.GasUsed(),
Timestamp: payload.Timestamp(),
ExtraData: payload.ExtraData(),
BaseFeePerGas: payload.BaseFeePerGas(),
BlockHash: payload.BlockHash(),
TransactionsRoot: transactionsRoot,
WithdrawalsRoot: withdrawalsRoot,
BlobGasUsed: blobGasUsed,
ExcessBlobGas: excessBlobGas,
}
executionPayloadProof, err := blocks.PayloadProof(l.Ctx, l.Block.Block())
require.NoError(t, err)
require.Equal(t, l.Block.Block().Slot(), header.Beacon().Slot, "Slot is not equal")
require.Equal(t, l.Block.Block().ProposerIndex(), header.Beacon().ProposerIndex, "Proposer index is not equal")
require.DeepSSZEqual(t, parentRoot[:], header.Beacon().ParentRoot, "Parent root is not equal")
require.DeepSSZEqual(t, stateRoot[:], header.Beacon().StateRoot, "State root is not equal")
require.DeepSSZEqual(t, bodyRoot[:], header.Beacon().BodyRoot, "Body root is not equal")
headerExecution, err := header.Execution()
require.NoError(t, err)
require.DeepSSZEqual(t, executionHeader, headerExecution.Proto(), "Execution headers are not equal")
headerExecutionBranch, err := header.ExecutionBranch()
require.NoError(t, err)
require.DeepSSZEqual(t, executionPayloadProof, convertArrayToSlice(headerExecutionBranch), "Execution payload proofs are not equal")
})
})
t.Run("Capella fork with Altair block", func(t *testing.T) {
l := util.NewTestLightClient(t, version.Altair)

View File

@@ -124,6 +124,7 @@ type BeaconNode struct {
DataColumnStorage *filesystem.DataColumnStorage
DataColumnStorageOptions []filesystem.DataColumnStorageOption
verifyInitWaiter *verification.InitializerWaiter
lhsp *verification.LazyHeadStateProvider
syncChecker *initialsync.SyncChecker
slasherEnabled bool
lcStore *lightclient.Store
@@ -230,8 +231,9 @@ func New(cliCtx *cli.Context, cancel context.CancelFunc, opts ...Option) (*Beaco
return nil, errors.Wrap(err, "could not start modules")
}
beacon.lhsp = &verification.LazyHeadStateProvider{}
beacon.verifyInitWaiter = verification.NewInitializerWaiter(
beacon.clockWaiter, forkchoice.NewROForkChoice(beacon.forkChoicer), beacon.stateGen)
beacon.clockWaiter, forkchoice.NewROForkChoice(beacon.forkChoicer), beacon.stateGen, beacon.lhsp)
beacon.BackfillOpts = append(
beacon.BackfillOpts,
@@ -749,6 +751,7 @@ func (b *BeaconNode) registerBlockchainService(fc forkchoice.ForkChoicer, gs *st
if err != nil {
return errors.Wrap(err, "could not register blockchain service")
}
b.lhsp.HeadStateProvider = blockchainService
return b.services.RegisterService(blockchainService)
}

View File

@@ -46,7 +46,7 @@ func TestLightClientHandler_GetLightClientBootstrap(t *testing.T) {
cfg.FuluForkEpoch = 5
params.OverrideBeaconConfig(cfg)
for testVersion := version.Altair; testVersion <= version.Electra; testVersion++ {
for _, testVersion := range version.All()[1:] {
t.Run(version.String(testVersion), func(t *testing.T) {
l := util.NewTestLightClient(t, testVersion)
@@ -131,7 +131,7 @@ func TestLightClientHandler_GetLightClientBootstrap(t *testing.T) {
resp = &pb.LightClientBootstrapCapella{}
case version.Deneb:
resp = &pb.LightClientBootstrapDeneb{}
case version.Electra:
case version.Electra, version.Fulu:
resp = &pb.LightClientBootstrapElectra{}
default:
t.Fatalf("Unsupported version %s", version.String(testVersion))
@@ -173,10 +173,11 @@ func TestLightClientHandler_GetLightClientByRange(t *testing.T) {
config.CapellaForkEpoch = 2
config.DenebForkEpoch = 3
config.ElectraForkEpoch = 4
config.FuluForkEpoch = 5
params.OverrideBeaconConfig(config)
t.Run("can save retrieve", func(t *testing.T) {
for testVersion := version.Altair; testVersion <= version.Electra; testVersion++ {
for _, testVersion := range version.All()[1:] {
t.Run(version.String(testVersion), func(t *testing.T) {
slot := primitives.Slot(params.BeaconConfig().VersionToForkEpochMap()[testVersion] * primitives.Epoch(config.SlotsPerEpoch)).Add(1)
@@ -252,7 +253,7 @@ func TestLightClientHandler_GetLightClientByRange(t *testing.T) {
resp = &pb.LightClientUpdateCapella{}
case version.Deneb:
resp = &pb.LightClientUpdateDeneb{}
case version.Electra:
case version.Electra, version.Fulu:
resp = &pb.LightClientUpdateElectra{}
default:
t.Fatalf("Unsupported version %s", version.String(testVersion))
@@ -313,7 +314,7 @@ func TestLightClientHandler_GetLightClientByRange(t *testing.T) {
resp = &pb.LightClientUpdateCapella{}
case version.Deneb:
resp = &pb.LightClientUpdateDeneb{}
case version.Electra:
case version.Electra, version.Fulu:
resp = &pb.LightClientUpdateElectra{}
default:
t.Fatalf("Unsupported version %s", version.String(testVersion))
@@ -730,7 +731,7 @@ func TestLightClientHandler_GetLightClientFinalityUpdate(t *testing.T) {
require.Equal(t, http.StatusNotFound, writer.Code)
})
for testVersion := 1; testVersion < 6; testVersion++ {
for _, testVersion := range version.All()[1:] {
t.Run(version.String(testVersion), func(t *testing.T) {
ctx := t.Context()
@@ -793,7 +794,7 @@ func TestLightClientHandler_GetLightClientFinalityUpdate(t *testing.T) {
resp = &pb.LightClientFinalityUpdateCapella{}
case version.Deneb:
resp = &pb.LightClientFinalityUpdateDeneb{}
case version.Electra:
case version.Electra, version.Fulu:
resp = &pb.LightClientFinalityUpdateElectra{}
default:
t.Fatalf("Unsupported version %s", version.String(testVersion))
@@ -825,7 +826,7 @@ func TestLightClientHandler_GetLightClientOptimisticUpdate(t *testing.T) {
require.Equal(t, http.StatusNotFound, writer.Code)
})
for testVersion := 1; testVersion < 6; testVersion++ {
for _, testVersion := range version.All()[1:] {
t.Run(version.String(testVersion), func(t *testing.T) {
ctx := t.Context()
l := util.NewTestLightClient(t, testVersion)
@@ -886,7 +887,7 @@ func TestLightClientHandler_GetLightClientOptimisticUpdate(t *testing.T) {
resp = &pb.LightClientOptimisticUpdateCapella{}
case version.Deneb:
resp = &pb.LightClientOptimisticUpdateDeneb{}
case version.Electra:
case version.Electra, version.Fulu:
resp = &pb.LightClientOptimisticUpdateDeneb{}
default:
t.Fatalf("Unsupported version %s", version.String(testVersion))

View File

@@ -154,7 +154,7 @@ func TestFetchDataColumnSidecars(t *testing.T) {
err = gs.SetClock(startup.NewClock(time.Unix(4113849600, 0), [fieldparams.RootLength]byte{}))
require.NoError(t, err)
waiter := verification.NewInitializerWaiter(gs, nil, nil)
waiter := verification.NewInitializerWaiter(gs, nil, nil, nil)
initializer, err := waiter.WaitForInitializer(t.Context())
require.NoError(t, err)
@@ -787,7 +787,7 @@ func TestVerifyDataColumnSidecarsByPeer(t *testing.T) {
err := gs.SetClock(startup.NewClock(time.Unix(4113849600, 0), [fieldparams.RootLength]byte{}))
require.NoError(t, err)
waiter := verification.NewInitializerWaiter(gs, nil, nil)
waiter := verification.NewInitializerWaiter(gs, nil, nil, nil)
initializer, err := waiter.WaitForInitializer(t.Context())
require.NoError(t, err)
@@ -832,7 +832,7 @@ func TestVerifyDataColumnSidecarsByPeer(t *testing.T) {
err := gs.SetClock(startup.NewClock(time.Unix(4113849600, 0), [fieldparams.RootLength]byte{}))
require.NoError(t, err)
waiter := verification.NewInitializerWaiter(gs, nil, nil)
waiter := verification.NewInitializerWaiter(gs, nil, nil, nil)
initializer, err := waiter.WaitForInitializer(t.Context())
require.NoError(t, err)

View File

@@ -216,6 +216,9 @@ func (s *Service) fetchOriginSidecars(peers []peer.ID) error {
if errors.Is(err, db.ErrNotFoundOriginBlockRoot) {
return nil
}
if err != nil {
return errors.Wrap(err, "error fetching origin checkpoint blockroot")
}
block, err := s.cfg.DB.Block(s.ctx, blockRoot)
if err != nil {

View File

@@ -174,7 +174,7 @@ func TestService_InitStartStop(t *testing.T) {
StateNotifier: &mock.MockStateNotifier{},
InitialSyncComplete: make(chan struct{}),
})
s.verifierWaiter = verification.NewInitializerWaiter(gs, nil, nil)
s.verifierWaiter = verification.NewInitializerWaiter(gs, nil, nil, nil)
time.Sleep(500 * time.Millisecond)
assert.NotNil(t, s)
if tt.setGenesis != nil {
@@ -217,7 +217,7 @@ func TestService_waitForStateInitialization(t *testing.T) {
counter: ratecounter.NewRateCounter(counterSeconds * time.Second),
genesisChan: make(chan time.Time),
}
s.verifierWaiter = verification.NewInitializerWaiter(cs, nil, nil)
s.verifierWaiter = verification.NewInitializerWaiter(cs, nil, nil, nil)
return s, cs
}
@@ -786,7 +786,7 @@ func TestFetchOriginColumns(t *testing.T) {
err = gs.SetClock(startup.NewClock(time.Unix(4113849600, 0), [fieldparams.RootLength]byte{}))
require.NoError(t, err)
waiter := verification.NewInitializerWaiter(gs, nil, nil)
waiter := verification.NewInitializerWaiter(gs, nil, nil, nil)
initializer, err := waiter.WaitForInitializer(t.Context())
require.NoError(t, err)

View File

@@ -332,7 +332,7 @@ func TestHandshakeHandlers_Roundtrip(t *testing.T) {
markInitSyncComplete(t, r)
clock := startup.NewClockSynchronizer()
require.NoError(t, clock.SetClock(startup.NewClock(time.Now(), [32]byte{})))
r.verifierWaiter = verification.NewInitializerWaiter(clock, chain.ForkChoiceStore, r.cfg.stateGen)
r.verifierWaiter = verification.NewInitializerWaiter(clock, chain.ForkChoiceStore, r.cfg.stateGen, chain)
p1.Digest, err = r.currentForkDigest()
require.NoError(t, err)
@@ -354,7 +354,7 @@ func TestHandshakeHandlers_Roundtrip(t *testing.T) {
markInitSyncComplete(t, r2)
clock = startup.NewClockSynchronizer()
require.NoError(t, clock.SetClock(startup.NewClock(time.Now(), [32]byte{})))
r2.verifierWaiter = verification.NewInitializerWaiter(clock, chain2.ForkChoiceStore, r2.cfg.stateGen)
r2.verifierWaiter = verification.NewInitializerWaiter(clock, chain2.ForkChoiceStore, r2.cfg.stateGen, chain2)
p2.Digest, err = r.currentForkDigest()
require.NoError(t, err)
@@ -948,7 +948,7 @@ func TestStatusRPCRequest_BadPeerHandshake(t *testing.T) {
markInitSyncComplete(t, r)
clock := startup.NewClockSynchronizer()
require.NoError(t, clock.SetClock(startup.NewClock(time.Now(), [32]byte{})))
r.verifierWaiter = verification.NewInitializerWaiter(clock, chain.ForkChoiceStore, r.cfg.stateGen)
r.verifierWaiter = verification.NewInitializerWaiter(clock, chain.ForkChoiceStore, r.cfg.stateGen, chain)
go r.Start()

View File

@@ -337,17 +337,17 @@ func (s *Service) blockVerifyingState(ctx context.Context, blk interfaces.ReadOn
}
return transition.ProcessSlotsUsingNextSlotCache(ctx, headState, headRoot, blockSlot)
}
// If head and block are in the same epoch and head is compatible with the parent's target, then use head
// If head and block are in the same epoch and head is compatible with the parent's dependent root, then use head
if blockEpoch == headEpoch {
headTarget, err := s.cfg.chain.TargetRootForEpoch([32]byte(headRoot), blockEpoch)
headDependent, err := s.cfg.chain.DependentRootForEpoch([32]byte(headRoot), blockEpoch)
if err != nil {
return nil, err
}
parentTarget, err := s.cfg.chain.TargetRootForEpoch([32]byte(parentRoot), blockEpoch)
parentDependent, err := s.cfg.chain.DependentRootForEpoch([32]byte(parentRoot), blockEpoch)
if err != nil {
return nil, err
}
if bytes.Equal(headTarget[:], parentTarget[:]) {
if bytes.Equal(headDependent[:], parentDependent[:]) {
return s.cfg.chain.HeadStateReadOnly(ctx)
}
}

View File

@@ -433,7 +433,7 @@ func TestService_ValidateBlsToExecutionChange(t *testing.T) {
tt.clock = startup.NewClock(time.Now(), [32]byte{})
}
require.NoError(t, cw.SetClock(tt.clock))
svc.verifierWaiter = verification.NewInitializerWaiter(cw, chainService.ForkChoiceStore, svc.cfg.stateGen)
svc.verifierWaiter = verification.NewInitializerWaiter(cw, chainService.ForkChoiceStore, svc.cfg.stateGen, chainService)
go svc.Start()
marshalledObj, err := tt.args.msg.MarshalSSZ()

View File

@@ -411,7 +411,7 @@ func TestService_ValidateSyncCommitteeMessage(t *testing.T) {
svc, tt.args.topic, clock = tt.setupSvc(svc, tt.args.msg, tt.args.topic)
markInitSyncComplete(t, svc)
require.NoError(t, cw.SetClock(clock))
svc.verifierWaiter = verification.NewInitializerWaiter(cw, chainService.ForkChoiceStore, svc.cfg.stateGen)
svc.verifierWaiter = verification.NewInitializerWaiter(cw, chainService.ForkChoiceStore, svc.cfg.stateGen, chainService)
go svc.Start()
marshalledObj, err := tt.args.msg.MarshalSSZ()

View File

@@ -855,7 +855,7 @@ func TestService_ValidateSyncContributionAndProof(t *testing.T) {
var clock *startup.Clock
svc, clock = tt.setupSvc(svc, tt.args.msg)
require.NoError(t, cw.SetClock(clock))
svc.verifierWaiter = verification.NewInitializerWaiter(cw, chainService.ForkChoiceStore, svc.cfg.stateGen)
svc.verifierWaiter = verification.NewInitializerWaiter(cw, chainService.ForkChoiceStore, svc.cfg.stateGen, chainService)
markInitSyncComplete(t, svc)
go svc.Start()
marshalledObj, err := tt.args.msg.MarshalSSZ()

View File

@@ -69,6 +69,7 @@ go_test(
"//beacon-chain/forkchoice/types:go_default_library",
"//beacon-chain/startup:go_default_library",
"//beacon-chain/state:go_default_library",
"//beacon-chain/state/state-native:go_default_library",
"//config/fieldparams:go_default_library",
"//config/params:go_default_library",
"//consensus-types/blocks:go_default_library",

View File

@@ -11,6 +11,7 @@ import (
forkchoicetypes "github.com/OffchainLabs/prysm/v7/beacon-chain/forkchoice/types"
"github.com/OffchainLabs/prysm/v7/beacon-chain/startup"
"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/primitives"
@@ -547,11 +548,12 @@ func TestRequirementSatisfaction(t *testing.T) {
}
type mockForkchoicer struct {
FinalizedCheckpointCB func() *forkchoicetypes.Checkpoint
HasNodeCB func([32]byte) bool
IsCanonicalCB func(root [32]byte) bool
SlotCB func([32]byte) (primitives.Slot, error)
TargetRootForEpochCB func([32]byte, primitives.Epoch) ([32]byte, error)
FinalizedCheckpointCB func() *forkchoicetypes.Checkpoint
HasNodeCB func([32]byte) bool
IsCanonicalCB func(root [32]byte) bool
SlotCB func([32]byte) (primitives.Slot, error)
DependentRootForEpochCB func([32]byte, primitives.Epoch) ([32]byte, error)
TargetRootForEpochCB func([32]byte, primitives.Epoch) ([32]byte, error)
}
var _ Forkchoicer = &mockForkchoicer{}
@@ -572,6 +574,10 @@ func (m *mockForkchoicer) Slot(root [32]byte) (primitives.Slot, error) {
return m.SlotCB(root)
}
func (m *mockForkchoicer) DependentRootForEpoch(root [32]byte, epoch primitives.Epoch) ([32]byte, error) {
return m.DependentRootForEpochCB(root, epoch)
}
func (m *mockForkchoicer) TargetRootForEpoch(root [32]byte, epoch primitives.Epoch) ([32]byte, error) {
return m.TargetRootForEpochCB(root, epoch)
}
@@ -626,6 +632,45 @@ func (sbr *mockStateByRooter) StateByRoot(ctx context.Context, root [32]byte) (s
var _ StateByRooter = &mockStateByRooter{}
type mockHeadStateProvider struct {
headRoot []byte
headSlot primitives.Slot
headState state.BeaconState
headStateReadOnly state.ReadOnlyBeaconState
}
func (m *mockHeadStateProvider) HeadRoot(_ context.Context) ([]byte, error) {
if m.headRoot != nil {
return m.headRoot, nil
}
root := make([]byte, 32)
root[0] = 0xff
return root, nil
}
func (m *mockHeadStateProvider) HeadSlot() primitives.Slot {
if m.headSlot == 0 {
return 1000
}
return m.headSlot
}
func (m *mockHeadStateProvider) HeadState(_ context.Context) (state.BeaconState, error) {
if m.headState == nil {
return nil, errors.New("head state not available")
}
return m.headState, nil
}
func (m *mockHeadStateProvider) HeadStateReadOnly(_ context.Context) (state.ReadOnlyBeaconState, error) {
if m.headStateReadOnly == nil {
return nil, errors.New("head state read only not available")
}
return m.headStateReadOnly, nil
}
var _ HeadStateProvider = &mockHeadStateProvider{}
func sbrErrorIfCalled(t *testing.T) sbrfunc {
return func(_ context.Context, _ [32]byte) (state.BeaconState, error) {
t.Error("StateByRoot should not have been called")
@@ -643,15 +688,56 @@ func sbrNotFound(t *testing.T, expectedRoot [32]byte) *mockStateByRooter {
}
func sbrForValOverride(idx primitives.ValidatorIndex, val *ethpb.Validator) *mockStateByRooter {
return sbrForValOverrideWithT(nil, idx, val)
}
func sbrForValOverrideWithT(t testing.TB, idx primitives.ValidatorIndex, val *ethpb.Validator) *mockStateByRooter {
return &mockStateByRooter{sbr: func(_ context.Context, root [32]byte) (state.BeaconState, error) {
return &validxStateOverride{vals: map[primitives.ValidatorIndex]*ethpb.Validator{
idx: val,
}}, nil
// Use a real deterministic state so that helpers.BeaconProposerIndexAtSlot works correctly
numValidators := uint64(idx + 1)
if numValidators < 64 {
numValidators = 64
}
var st state.BeaconState
var err error
if t != nil {
st, _ = util.DeterministicGenesisStateFulu(t, numValidators)
} else {
// Fallback for blob tests that don't need the full state
return &validxStateOverride{
slot: 0,
vals: map[primitives.ValidatorIndex]*ethpb.Validator{
idx: val,
},
}, nil
}
// Override the specific validator if provided
if val != nil {
vals := st.Validators()
if idx < primitives.ValidatorIndex(len(vals)) {
vals[idx] = val
// Ensure the validator is active
if vals[idx].ActivationEpoch > 0 {
vals[idx].ActivationEpoch = 0
}
if vals[idx].ExitEpoch == 0 || vals[idx].ExitEpoch < params.BeaconConfig().FarFutureEpoch {
vals[idx].ExitEpoch = params.BeaconConfig().FarFutureEpoch
}
if vals[idx].EffectiveBalance == 0 {
vals[idx].EffectiveBalance = params.BeaconConfig().MaxEffectiveBalance
}
_ = st.SetValidators(vals)
}
}
return st, err
}}
}
type validxStateOverride struct {
state.BeaconState
slot primitives.Slot
vals map[primitives.ValidatorIndex]*ethpb.Validator
}
@@ -665,6 +751,105 @@ func (v *validxStateOverride) ValidatorAtIndex(idx primitives.ValidatorIndex) (*
return val, nil
}
func (v *validxStateOverride) Slot() primitives.Slot {
return v.slot
}
func (v *validxStateOverride) Version() int {
// Return Fulu version (6) as default for tests
return 6
}
func (v *validxStateOverride) Validators() []*ethpb.Validator {
// Return all validators in the map as a slice
maxIdx := primitives.ValidatorIndex(0)
for idx := range v.vals {
if idx > maxIdx {
maxIdx = idx
}
}
// Ensure we have at least 64 validators for a valid beacon state
numValidators := maxIdx + 1
if numValidators < 64 {
numValidators = 64
}
validators := make([]*ethpb.Validator, numValidators)
for i := range validators {
if val, ok := v.vals[primitives.ValidatorIndex(i)]; ok {
validators[i] = val
} else {
// Default validator for indices we don't care about
validators[i] = &ethpb.Validator{
ActivationEpoch: 0,
ExitEpoch: params.BeaconConfig().FarFutureEpoch,
EffectiveBalance: params.BeaconConfig().MaxEffectiveBalance,
}
}
}
return validators
}
func (v *validxStateOverride) RandaoMixAtIndex(idx uint64) ([]byte, error) {
// Return a zero mix for simplicity in tests
return make([]byte, 32), nil
}
func (v *validxStateOverride) NumValidators() int {
return len(v.Validators())
}
func (v *validxStateOverride) ValidatorAtIndexReadOnly(idx primitives.ValidatorIndex) (state.ReadOnlyValidator, error) {
validators := v.Validators()
if idx >= primitives.ValidatorIndex(len(validators)) {
return nil, fmt.Errorf("validator index %d out of range", idx)
}
return state_native.NewValidator(validators[idx])
}
func (v *validxStateOverride) IsNil() bool {
return false
}
func (v *validxStateOverride) LatestBlockHeader() *ethpb.BeaconBlockHeader {
// Return a minimal block header for tests
return &ethpb.BeaconBlockHeader{
Slot: v.slot,
ProposerIndex: 0,
ParentRoot: make([]byte, 32),
StateRoot: make([]byte, 32),
BodyRoot: make([]byte, 32),
}
}
func (v *validxStateOverride) HashTreeRoot(ctx context.Context) ([32]byte, error) {
// Return a zero hash for tests
return [32]byte{}, nil
}
func (v *validxStateOverride) UpdateStateRootAtIndex(idx uint64, stateRoot [32]byte) error {
// No-op for mock - we don't track state roots
return nil
}
func (v *validxStateOverride) SetLatestBlockHeader(val *ethpb.BeaconBlockHeader) error {
// No-op for mock - we don't track block headers
return nil
}
func (v *validxStateOverride) ReadFromEveryValidator(f func(idx int, val state.ReadOnlyValidator) error) error {
validators := v.Validators()
for i, val := range validators {
rov, err := state_native.NewValidator(val)
if err != nil {
return err
}
if err := f(i, rov); err != nil {
return err
}
}
return nil
}
type mockProposerCache struct {
ComputeProposerCB func(ctx context.Context, root [32]byte, slot primitives.Slot, pst state.BeaconState) (primitives.ValidatorIndex, error)
ProposerCB func(c *forkchoicetypes.Checkpoint, slot primitives.Slot) (primitives.ValidatorIndex, bool)

View File

@@ -1,13 +1,16 @@
package verification
import (
"bytes"
"context"
"crypto/sha256"
"fmt"
"strings"
"time"
"github.com/OffchainLabs/prysm/v7/beacon-chain/core/helpers"
"github.com/OffchainLabs/prysm/v7/beacon-chain/core/peerdas"
"github.com/OffchainLabs/prysm/v7/beacon-chain/core/transition"
forkchoicetypes "github.com/OffchainLabs/prysm/v7/beacon-chain/forkchoice/types"
"github.com/OffchainLabs/prysm/v7/beacon-chain/state"
fieldparams "github.com/OffchainLabs/prysm/v7/config/fieldparams"
@@ -66,6 +69,12 @@ var (
errBadTopic = errors.New("topic is not of the one expected")
)
type LazyHeadStateProvider struct {
HeadStateProvider
}
var _ HeadStateProvider = &LazyHeadStateProvider{}
type (
RODataColumnsVerifier struct {
*sharedResources
@@ -262,14 +271,14 @@ func (dv *RODataColumnsVerifier) ValidProposerSignature(ctx context.Context) (er
if _, err, _ = dv.sg.Do(signatureData.concat(), func() (any, error) {
columnVerificationProposerSignatureCache.WithLabelValues("miss").Inc()
// Retrieve the parent state.
parentState, err := dv.state(ctx, dataColumn.ParentRoot())
// Retrieve a state compatible with the data column for verification.
verifyingState, err := dv.getVerifyingState(ctx, dataColumn)
if err != nil {
return nil, columnErrBuilder(errors.Wrap(err, "parent state"))
return nil, columnErrBuilder(errors.Wrap(err, "verifying state"))
}
// Full verification, which will subsequently be cached for anything sharing the signature cache.
if err = dv.sc.VerifySignature(signatureData, parentState); err != nil {
if err = dv.sc.VerifySignature(signatureData, verifyingState); err != nil {
return nil, columnErrBuilder(errors.Wrap(err, "verify signature"))
}
@@ -282,6 +291,61 @@ func (dv *RODataColumnsVerifier) ValidProposerSignature(ctx context.Context) (er
return nil
}
// getVerifyingState returns a state that is compatible with the column sidecar and can be used to verify signature and proposer index.
// The returned state is guaranteed to be at the same epoch as the data column's epoch, and have the same randao mix and active
// validator indices as the data column's parent state advanced to the data column's slot.
func (dv *RODataColumnsVerifier) getVerifyingState(ctx context.Context, dataColumn blocks.RODataColumn) (state.ReadOnlyBeaconState, error) {
headRoot, err := dv.hsp.HeadRoot(ctx)
if err != nil {
return nil, err
}
parentRoot := dataColumn.ParentRoot()
dataColumnSlot := dataColumn.Slot()
dataColumnEpoch := slots.ToEpoch(dataColumnSlot)
headSlot := dv.hsp.HeadSlot()
headEpoch := slots.ToEpoch(headSlot)
// Use head if it's the parent
if bytes.Equal(parentRoot[:], headRoot) {
// If they are in the same epoch, then we can return the head state directly
if dataColumnEpoch == headEpoch {
return dv.hsp.HeadStateReadOnly(ctx)
}
// Otherwise, we need to process the head state to the data column's slot
headState, err := dv.hsp.HeadState(ctx)
if err != nil {
return nil, err
}
return transition.ProcessSlotsUsingNextSlotCache(ctx, headState, headRoot, dataColumnSlot)
}
// If head and data column are in the same epoch and head is compatible with the parent's depdendent root, then use head
if dataColumnEpoch == headEpoch {
headDependent, err := dv.fc.DependentRootForEpoch(bytesutil.ToBytes32(headRoot), dataColumnEpoch)
if err != nil {
return nil, err
}
parentDependent, err := dv.fc.DependentRootForEpoch(parentRoot, dataColumnEpoch)
if err != nil {
return nil, err
}
if bytes.Equal(headDependent[:], parentDependent[:]) {
return dv.hsp.HeadStateReadOnly(ctx)
}
}
// Otherwise retrieve the parent state and advance it to the data column's slot
parentState, err := dv.sr.StateByRoot(ctx, parentRoot)
if err != nil {
return nil, err
}
parentEpoch := slots.ToEpoch(parentState.Slot())
if dataColumnEpoch == parentEpoch {
return parentState, nil
}
return transition.ProcessSlotsUsingNextSlotCache(ctx, parentState, parentRoot[:], dataColumnSlot)
}
func (dv *RODataColumnsVerifier) SidecarParentSeen(parentSeen func([fieldparams.RootLength]byte) bool) (err error) {
if ok, err := dv.results.cached(RequireSidecarParentSeen); ok {
return err
@@ -482,14 +546,12 @@ func (dv *RODataColumnsVerifier) SidecarProposerExpected(ctx context.Context) (e
// Ensure the expensive index computation is only performed once for
// concurrent requests for the same signature data.
idxAny, err, _ := dv.sg.Do(concatRootSlot(parentRoot, dataColumnSlot), func() (any, error) {
// Retrieve the parent state.
parentState, err := dv.state(ctx, parentRoot)
verifyingState, err := dv.getVerifyingState(ctx, dataColumn)
if err != nil {
return nil, columnErrBuilder(errors.Wrap(err, "parent state"))
return nil, columnErrBuilder(errors.Wrap(err, "verifying state"))
}
// Compute the proposer index.
idx, err = dv.pc.ComputeProposer(ctx, parentRoot, dataColumnSlot, parentState)
idx, err = helpers.BeaconProposerIndexAtSlot(ctx, verifyingState, dataColumnSlot)
if err != nil {
return nil, columnErrBuilder(errors.Wrap(err, "compute proposer"))
}
@@ -514,25 +576,6 @@ func (dv *RODataColumnsVerifier) SidecarProposerExpected(ctx context.Context) (e
return nil
}
// state retrieves the state of the corresponding root from the cache if possible, else retrieves it from the state by rooter.
func (dv *RODataColumnsVerifier) state(ctx context.Context, root [fieldparams.RootLength]byte) (state.BeaconState, error) {
// If the parent root is already in the cache, return it.
if st, ok := dv.stateByRoot[root]; ok {
return st, nil
}
// Retrieve the parent state from the state by rooter.
st, err := dv.sr.StateByRoot(ctx, root)
if err != nil {
return nil, errors.Wrap(err, "state by root")
}
// Store the parent state in the cache.
dv.stateByRoot[root] = st
return st, nil
}
func columnToSignatureData(d blocks.RODataColumn) signatureData {
return signatureData{
Root: d.BlockRoot(),

View File

@@ -1,7 +1,6 @@
package verification
import (
"context"
"reflect"
"sync"
"testing"
@@ -11,7 +10,6 @@ import (
"github.com/OffchainLabs/prysm/v7/beacon-chain/core/peerdas"
forkchoicetypes "github.com/OffchainLabs/prysm/v7/beacon-chain/forkchoice/types"
"github.com/OffchainLabs/prysm/v7/beacon-chain/startup"
"github.com/OffchainLabs/prysm/v7/beacon-chain/state"
fieldparams "github.com/OffchainLabs/prysm/v7/config/fieldparams"
"github.com/OffchainLabs/prysm/v7/config/params"
"github.com/OffchainLabs/prysm/v7/consensus-types/blocks"
@@ -330,7 +328,7 @@ func TestValidProposerSignature(t *testing.T) {
svcbError: nil,
vscbShouldError: false,
vscbError: nil,
stateByRooter: sbrForValOverride(firstColumn.ProposerIndex(), validator),
stateByRooter: sbrForValOverrideWithT(t, firstColumn.ProposerIndex(), validator),
isError: false,
},
{
@@ -348,7 +346,7 @@ func TestValidProposerSignature(t *testing.T) {
svcbError: nil,
vscbShouldError: false,
vscbError: errors.New("signature, not so good!"),
stateByRooter: sbrForValOverride(firstColumn.ProposerIndex(), validator),
stateByRooter: sbrForValOverrideWithT(t, firstColumn.ProposerIndex(), validator),
isError: true,
},
}
@@ -378,8 +376,12 @@ func TestValidProposerSignature(t *testing.T) {
initializer := Initializer{
shared: &sharedResources{
sc: signatureCache,
sr: tc.stateByRooter,
sc: signatureCache,
sr: tc.stateByRooter,
hsp: &mockHeadStateProvider{},
fc: &mockForkchoicer{
TargetRootForEpochCB: fcReturnsTargetRoot([fieldparams.RootLength]byte{}),
},
},
}
@@ -796,20 +798,7 @@ func TestDataColumnsSidecarProposerExpected(t *testing.T) {
parentRoot := [fieldparams.RootLength]byte{}
columns := GenerateTestDataColumns(t, parentRoot, columnSlot, blobCount)
firstColumn := columns[0]
newColumns := GenerateTestDataColumns(t, parentRoot, 2*params.BeaconConfig().SlotsPerEpoch, blobCount)
firstNewColumn := newColumns[0]
validator := &ethpb.Validator{}
commonComputeProposerCB := func(_ context.Context, root [fieldparams.RootLength]byte, slot primitives.Slot, _ state.BeaconState) (primitives.ValidatorIndex, error) {
require.Equal(t, firstColumn.ParentRoot(), root)
require.Equal(t, firstColumn.Slot(), slot)
return firstColumn.ProposerIndex(), nil
}
ctx := t.Context()
testCases := []struct {
name string
stateByRooter StateByRooter
@@ -841,66 +830,7 @@ func TestDataColumnsSidecarProposerExpected(t *testing.T) {
ProposerCB: pcReturnsNotFound(),
},
columns: columns,
error: "state by root",
},
{
name: "Not cached, proposer matches",
stateByRooter: sbrForValOverride(firstColumn.ProposerIndex(), validator),
proposerCache: &mockProposerCache{
ProposerCB: pcReturnsNotFound(),
ComputeProposerCB: commonComputeProposerCB,
},
columns: columns,
},
{
name: "Not cached, proposer matches",
stateByRooter: sbrForValOverride(firstColumn.ProposerIndex(), validator),
proposerCache: &mockProposerCache{
ProposerCB: pcReturnsNotFound(),
ComputeProposerCB: commonComputeProposerCB,
},
columns: columns,
},
{
name: "Not cached, proposer matches for next epoch",
stateByRooter: sbrForValOverride(firstNewColumn.ProposerIndex(), validator),
proposerCache: &mockProposerCache{
ProposerCB: pcReturnsNotFound(),
ComputeProposerCB: func(_ context.Context, root [32]byte, slot primitives.Slot, _ state.BeaconState) (primitives.ValidatorIndex, error) {
require.Equal(t, firstNewColumn.ParentRoot(), root)
require.Equal(t, firstNewColumn.Slot(), slot)
return firstColumn.ProposerIndex(), nil
},
},
columns: newColumns,
},
{
name: "Not cached, proposer does not match",
stateByRooter: sbrForValOverride(firstColumn.ProposerIndex(), validator),
proposerCache: &mockProposerCache{
ProposerCB: pcReturnsNotFound(),
ComputeProposerCB: func(_ context.Context, root [32]byte, slot primitives.Slot, _ state.BeaconState) (primitives.ValidatorIndex, error) {
require.Equal(t, firstColumn.ParentRoot(), root)
require.Equal(t, firstColumn.Slot(), slot)
return firstColumn.ProposerIndex() + 1, nil
},
},
columns: columns,
error: errSidecarUnexpectedProposer.Error(),
},
{
name: "Not cached, ComputeProposer fails",
stateByRooter: sbrForValOverride(firstColumn.ProposerIndex(), validator),
proposerCache: &mockProposerCache{
ProposerCB: pcReturnsNotFound(),
ComputeProposerCB: func(_ context.Context, root [32]byte, slot primitives.Slot, _ state.BeaconState) (primitives.ValidatorIndex, error) {
require.Equal(t, firstColumn.ParentRoot(), root)
require.Equal(t, firstColumn.Slot(), slot)
return 0, errors.New("ComputeProposer failed")
},
},
columns: columns,
error: "compute proposer",
error: "verifying state",
},
}
@@ -908,8 +838,9 @@ func TestDataColumnsSidecarProposerExpected(t *testing.T) {
t.Run(tc.name, func(t *testing.T) {
initializer := Initializer{
shared: &sharedResources{
sr: tc.stateByRooter,
pc: tc.proposerCache,
sr: tc.stateByRooter,
pc: tc.proposerCache,
hsp: &mockHeadStateProvider{},
fc: &mockForkchoicer{
TargetRootForEpochCB: fcReturnsTargetRoot([fieldparams.RootLength]byte{}),
},

View File

@@ -25,6 +25,7 @@ type Forkchoicer interface {
HasNode([32]byte) bool
IsCanonical(root [32]byte) bool
Slot([32]byte) (primitives.Slot, error)
DependentRootForEpoch([32]byte, primitives.Epoch) ([32]byte, error)
TargetRootForEpoch([32]byte, primitives.Epoch) ([32]byte, error)
}
@@ -33,6 +34,16 @@ type StateByRooter interface {
StateByRoot(ctx context.Context, blockRoot [32]byte) (state.BeaconState, error)
}
// HeadStateProvider describes a type that can provide access to the current head state and related methods.
// This interface matches blockchain.HeadFetcher but is defined here to avoid import cycles
// (blockchain package imports verification package).
type HeadStateProvider interface {
HeadRoot(ctx context.Context) ([]byte, error)
HeadSlot() primitives.Slot
HeadState(ctx context.Context) (state.BeaconState, error)
HeadStateReadOnly(ctx context.Context) (state.ReadOnlyBeaconState, error)
}
// sharedResources provides access to resources that are required by different verification types.
// for example, sidecar verification and block verification share the block signature verification cache.
type sharedResources struct {
@@ -41,6 +52,7 @@ type sharedResources struct {
sc signatureCache
pc proposerCache
sr StateByRooter
hsp HeadStateProvider
ic *inclusionProofCache
sg singleflight.Group
}
@@ -96,14 +108,15 @@ func WithForkLookup(fl forkLookup) InitializerOption {
}
// NewInitializerWaiter creates an InitializerWaiter which can be used to obtain an Initializer once async dependencies are ready.
func NewInitializerWaiter(cw startup.ClockWaiter, fc Forkchoicer, sr StateByRooter, opts ...InitializerOption) *InitializerWaiter {
func NewInitializerWaiter(cw startup.ClockWaiter, fc Forkchoicer, sr StateByRooter, hsp HeadStateProvider, opts ...InitializerOption) *InitializerWaiter {
pc := newPropCache()
// signature cache is initialized in WaitForInitializer, since we need the genesis validators root, which can be obtained from startup.Clock.
shared := &sharedResources{
fc: fc,
pc: pc,
sr: sr,
ic: newInclusionProofCache(defaultInclusionProofCacheSize),
fc: fc,
pc: pc,
sr: sr,
hsp: hsp,
ic: newInclusionProofCache(defaultInclusionProofCacheSize),
}
iw := &InitializerWaiter{cw: cw, ini: &Initializer{shared: shared}}
for _, o := range opts {

View File

@@ -18,7 +18,7 @@ func TestInitializerWaiter(t *testing.T) {
cs := startup.NewClockSynchronizer()
require.NoError(t, cs.SetClock(c))
w := NewInitializerWaiter(cs, &mockForkchoicer{}, &mockStateByRooter{})
w := NewInitializerWaiter(cs, &mockForkchoicer{}, &mockStateByRooter{}, &mockHeadStateProvider{})
ini, err := w.WaitForInitializer(ctx)
require.NoError(t, err)
csc, ok := ini.shared.sc.(*sigCache)

View File

@@ -0,0 +1,3 @@
### Added
- add fulu support to light client processing.

View File

@@ -0,0 +1,2 @@
### Ignored
- Add osaka fork timestamp derivation to interop genesis

View File

@@ -0,0 +1,2 @@
### Changed
- Improve readability in slashing import and remove duplicated code

View File

@@ -0,0 +1,3 @@
### Ignored
- Add error check in origin sidecars fetching.

View File

@@ -0,0 +1,3 @@
### Changed
- Use dependent root instead of target when possible.

View File

@@ -0,0 +1,3 @@
### Changed
- Use head state readonly when possible to validate data column sidecars.

View File

@@ -264,6 +264,7 @@ func generateGenesis(ctx context.Context) (state.BeaconState, error) {
gen.Config.ShanghaiTime = interop.GethShanghaiTime(genesis, params.BeaconConfig())
gen.Config.CancunTime = interop.GethCancunTime(genesis, params.BeaconConfig())
gen.Config.PragueTime = interop.GethPragueTime(genesis, params.BeaconConfig())
gen.Config.OsakaTime = interop.GethOsakaTime(genesis, params.BeaconConfig())
fields := logrus.Fields{}
if gen.Config.ShanghaiTime != nil {
@@ -275,6 +276,9 @@ func generateGenesis(ctx context.Context) (state.BeaconState, error) {
if gen.Config.PragueTime != nil {
fields["prague"] = fmt.Sprintf("%d", *gen.Config.PragueTime)
}
if gen.Config.OsakaTime != nil {
fields["osaka"] = fmt.Sprintf("%d", *gen.Config.OsakaTime)
}
log.WithFields(fields).Info("Setting fork geth times")
if v > version.Altair {
// set ttd to zero so EL goes post-merge immediately

View File

@@ -61,20 +61,13 @@ func importSlashingProtectionJSON(cliCtx *cli.Context) error {
if isDatabaseMinimal {
databaseFileDir = filesystem.DatabaseDirName
}
return fmt.Errorf("%s (validator database) was not found at path %s, so nothing to export", databaseFileDir, dataDir)
} else {
if !isDatabaseMinimal {
matchPath = filepath.Dir(matchPath) // strip the file name
}
dataDir = matchPath
log.Infof("Found validator database at path %s", dataDir)
return fmt.Errorf("%s (validator database) was not found at path %s, so nothing to import", databaseFileDir, dataDir)
}
message := "Found existing database inside of %s"
if !found {
message = "Did not find existing database inside of %s, creating a new one"
if !isDatabaseMinimal {
matchPath = filepath.Dir(matchPath) // strip the file name
}
log.Infof(message, dataDir)
dataDir = matchPath
log.Infof("Found validator database at path %s", dataDir)
// Open the validator database.
if isDatabaseMinimal {

View File

@@ -113,6 +113,21 @@ func GethPragueTime(genesisTime time.Time, cfg *clparams.BeaconChainConfig) *uin
return pragueTime
}
// GethOsakaTime calculates the absolute time of the osaka (aka fulu) fork block
// by adding the relative time of the capella the fork epoch to the given genesis timestamp.
func GethOsakaTime(genesisTime time.Time, cfg *clparams.BeaconChainConfig) *uint64 {
var osakaTime *uint64
if cfg.FuluForkEpoch != math.MaxUint64 {
startSlot, err := slots.EpochStart(cfg.FuluForkEpoch)
if err == nil {
startTime := slots.UnsafeStartTime(genesisTime, startSlot)
newTime := uint64(startTime.Unix())
osakaTime = &newTime
}
}
return osakaTime
}
// GethTestnetGenesis creates a genesis.json for eth1 clients with a set of defaults suitable for ephemeral testnets,
// like in an e2e test. The parameters are minimal but the full value is returned unmarshaled so that it can be
// customized as desired.
@@ -130,6 +145,10 @@ func GethTestnetGenesis(genesis time.Time, cfg *clparams.BeaconChainConfig) *cor
if cfg.ElectraForkEpoch == 0 {
pragueTime = &genesisTime
}
osakaTime := GethOsakaTime(genesis, cfg)
if cfg.FuluForkEpoch == 0 {
osakaTime = &genesisTime
}
cc := &params.ChainConfig{
ChainID: big.NewInt(defaultTestChainId),
HomesteadBlock: bigz,
@@ -151,6 +170,7 @@ func GethTestnetGenesis(genesis time.Time, cfg *clparams.BeaconChainConfig) *cor
ShanghaiTime: shanghaiTime,
CancunTime: cancunTime,
PragueTime: pragueTime,
OsakaTime: osakaTime,
DepositContractAddress: common.HexToAddress(cfg.DepositContractAddress),
BlobScheduleConfig: &params.BlobScheduleConfig{
Cancun: &params.BlobConfig{

View File

@@ -41,7 +41,7 @@ func NewBuilder(t testing.TB, initialState state.BeaconState, initialBlock inter
getFork := func(targetEpoch primitives.Epoch) (*ethpb.Fork, error) {
return initialState.Fork(), nil
}
bvw := verification.NewInitializerWaiter(cw, fc, sg, verification.WithForkLookup(getFork))
bvw := verification.NewInitializerWaiter(cw, fc, sg, service, verification.WithForkLookup(getFork))
return &Builder{
service: service,
execMock: execMock,

View File

@@ -62,6 +62,8 @@ func NewTestLightClient(t *testing.T, forkVersion int, options ...LightClientOpt
return l.setupTestDeneb()
case version.Electra:
return l.setupTestElectra()
case version.Fulu:
return l.setupTestFulu()
default:
l.T.Fatalf("Unsupported version %s", version.String(l.version))
return nil
@@ -955,6 +957,184 @@ func (l *TestLightClient) setupTestElectra() *TestLightClient {
return l
}
func (l *TestLightClient) setupTestFulu() *TestLightClient {
ctx := context.Background()
attestedSlot := primitives.Slot(uint64(params.BeaconConfig().FuluForkEpoch) * uint64(params.BeaconConfig().SlotsPerEpoch)).Add(1)
if l.increaseAttestedSlotBy > 0 {
attestedSlot = attestedSlot.Add(l.increaseAttestedSlotBy)
}
signatureSlot := attestedSlot.Add(1)
if l.increaseSignatureSlotBy > 0 {
signatureSlot = signatureSlot.Add(l.increaseSignatureSlotBy)
}
// Attested State & Block
attestedState, err := NewBeaconStateFulu()
require.NoError(l.T, err)
require.NoError(l.T, attestedState.SetSlot(attestedSlot))
var signedFinalizedBlock interfaces.SignedBeaconBlock
var finalizedState state.BeaconState
// Finalized checkpoint
if !l.noFinalizedCheckpoint {
var finalizedSlot primitives.Slot
if l.finalizedCheckpointInPrevFork {
finalizedSlot = primitives.Slot(uint64(params.BeaconConfig().ElectraForkEpoch) * uint64(params.BeaconConfig().SlotsPerEpoch))
if l.increaseFinalizedSlotBy > 0 {
finalizedSlot = finalizedSlot.Add(l.increaseFinalizedSlotBy)
}
finalizedState, err = NewBeaconStateElectra()
require.NoError(l.T, err)
require.NoError(l.T, finalizedState.SetSlot(finalizedSlot))
finalizedBlock := NewBeaconBlockElectra()
require.NoError(l.T, err)
finalizedBlock.Block.Slot = finalizedSlot
signedFinalizedBlock, err = blocks.NewSignedBeaconBlock(finalizedBlock)
require.NoError(l.T, err)
finalizedHeader, err := signedFinalizedBlock.Header()
require.NoError(l.T, err)
require.NoError(l.T, finalizedState.SetLatestBlockHeader(finalizedHeader.Header))
finalizedStateRoot, err := finalizedState.HashTreeRoot(ctx)
require.NoError(l.T, err)
finalizedBlock.Block.StateRoot = finalizedStateRoot[:]
signedFinalizedBlock, err = blocks.NewSignedBeaconBlock(finalizedBlock)
require.NoError(l.T, err)
} else {
finalizedSlot = primitives.Slot(uint64(params.BeaconConfig().FuluForkEpoch) * uint64(params.BeaconConfig().SlotsPerEpoch))
if l.increaseFinalizedSlotBy > 0 {
finalizedSlot = finalizedSlot.Add(l.increaseFinalizedSlotBy)
}
finalizedState, err = NewBeaconStateFulu()
require.NoError(l.T, err)
require.NoError(l.T, finalizedState.SetSlot(finalizedSlot))
finalizedBlock := NewBeaconBlockFulu()
require.NoError(l.T, err)
finalizedBlock.Block.Slot = finalizedSlot
signedFinalizedBlock, err = blocks.NewSignedBeaconBlock(finalizedBlock)
require.NoError(l.T, err)
finalizedHeader, err := signedFinalizedBlock.Header()
require.NoError(l.T, err)
require.NoError(l.T, finalizedState.SetLatestBlockHeader(finalizedHeader.Header))
finalizedStateRoot, err := finalizedState.HashTreeRoot(ctx)
require.NoError(l.T, err)
finalizedBlock.Block.StateRoot = finalizedStateRoot[:]
signedFinalizedBlock, err = blocks.NewSignedBeaconBlock(finalizedBlock)
require.NoError(l.T, err)
}
// Set the finalized checkpoint
finalizedBlockRoot, err := signedFinalizedBlock.Block().HashTreeRoot()
require.NoError(l.T, err)
finalizedCheckpoint := &ethpb.Checkpoint{
Epoch: slots.ToEpoch(finalizedSlot),
Root: finalizedBlockRoot[:],
}
require.NoError(l.T, attestedState.SetFinalizedCheckpoint(finalizedCheckpoint))
}
// Attested Block
attestedBlock := NewBeaconBlockFulu()
attestedBlock.Block.Slot = attestedSlot
attestedBlock.Block.ParentRoot = l.attestedParentRoot[:]
signedAttestedBlock, err := blocks.NewSignedBeaconBlock(attestedBlock)
require.NoError(l.T, err)
attestedBlockHeader, err := signedAttestedBlock.Header()
require.NoError(l.T, err)
require.NoError(l.T, attestedState.SetLatestBlockHeader(attestedBlockHeader.Header))
attestedStateRoot, err := attestedState.HashTreeRoot(ctx)
require.NoError(l.T, err)
attestedBlock.Block.StateRoot = attestedStateRoot[:]
signedAttestedBlock, err = blocks.NewSignedBeaconBlock(attestedBlock)
require.NoError(l.T, err)
// Signature State & Block
signatureState, err := NewBeaconStateFulu()
require.NoError(l.T, err)
require.NoError(l.T, signatureState.SetSlot(signatureSlot))
var signedSignatureBlock interfaces.SignedBeaconBlock
if l.blinded {
signatureBlock := NewBlindedBeaconBlockFulu()
signatureBlock.Message.Slot = signatureSlot
attestedBlockRoot, err := signedAttestedBlock.Block().HashTreeRoot()
require.NoError(l.T, err)
signatureBlock.Message.ParentRoot = attestedBlockRoot[:]
var trueBitNum uint64
if l.supermajority {
trueBitNum = uint64((float64(params.BeaconConfig().SyncCommitteeSize) * 2.0 / 3.0) + 1 + float64(l.increaseActiveParticipantsBy))
} else {
trueBitNum = params.BeaconConfig().MinSyncCommitteeParticipants
}
for i := uint64(0); i < trueBitNum; i++ {
signatureBlock.Message.Body.SyncAggregate.SyncCommitteeBits.SetBitAt(i, true)
}
signedSignatureBlock, err = blocks.NewSignedBeaconBlock(signatureBlock)
require.NoError(l.T, err)
signatureBlockHeader, err := signedSignatureBlock.Header()
require.NoError(l.T, err)
err = signatureState.SetLatestBlockHeader(signatureBlockHeader.Header)
require.NoError(l.T, err)
stateRoot, err := signatureState.HashTreeRoot(ctx)
require.NoError(l.T, err)
signatureBlock.Message.StateRoot = stateRoot[:]
signedSignatureBlock, err = blocks.NewSignedBeaconBlock(signatureBlock)
require.NoError(l.T, err)
} else {
signatureBlock := NewBeaconBlockFulu()
signatureBlock.Block.Slot = signatureSlot
attestedBlockRoot, err := signedAttestedBlock.Block().HashTreeRoot()
require.NoError(l.T, err)
signatureBlock.Block.ParentRoot = attestedBlockRoot[:]
var trueBitNum uint64
if l.supermajority {
trueBitNum = uint64((float64(params.BeaconConfig().SyncCommitteeSize) * 2.0 / 3.0) + 1 + float64(l.increaseActiveParticipantsBy))
} else {
trueBitNum = params.BeaconConfig().MinSyncCommitteeParticipants
}
for i := uint64(0); i < trueBitNum; i++ {
signatureBlock.Block.Body.SyncAggregate.SyncCommitteeBits.SetBitAt(i, true)
}
signedSignatureBlock, err = blocks.NewSignedBeaconBlock(signatureBlock)
require.NoError(l.T, err)
signatureBlockHeader, err := signedSignatureBlock.Header()
require.NoError(l.T, err)
err = signatureState.SetLatestBlockHeader(signatureBlockHeader.Header)
require.NoError(l.T, err)
signatureStateRoot, err := signatureState.HashTreeRoot(ctx)
require.NoError(l.T, err)
signatureBlock.Block.StateRoot = signatureStateRoot[:]
signedSignatureBlock, err = blocks.NewSignedBeaconBlock(signatureBlock)
require.NoError(l.T, err)
}
l.State = signatureState
l.AttestedState = attestedState
l.AttestedBlock = signedAttestedBlock
l.Block = signedSignatureBlock
l.Ctx = ctx
l.FinalizedBlock = signedFinalizedBlock
l.FinalizedState = finalizedState
return l
}
func (l *TestLightClient) CheckAttestedHeader(header interfaces.LightClientHeader) {
updateAttestedHeaderBeacon := header.Beacon()
testAttestedHeader, err := l.AttestedBlock.Header()