diff --git a/internal/chain/engine.go b/internal/chain/engine.go index 75abf36ecd..c601019dae 100644 --- a/internal/chain/engine.go +++ b/internal/chain/engine.go @@ -124,6 +124,13 @@ func (e *engineImpl) VerifyShardState( if len(headerShardStateBytes) == 0 { return nil } + // Only scheduled epoch-ending blocks may carry shard state. + // A non-last block with non-empty shard-state bytes would be accepted by + // SuperCommitteeForNextEpoch (which returns shard.State{} for non-last blocks), + // causing the chain to persist an empty next-epoch committee and halt consensus. + if bc.Config().IsShardStateValidation(header.Epoch()) && !shard.Schedule.IsLastBlock(header.Number().Uint64()) { + return errors.New("[VerifyShardState] non-epoch-ending block must not carry shard state") + } shardState, err := bc.SuperCommitteeForNextEpoch(beacon, header, true) if err != nil { return err diff --git a/internal/params/config.go b/internal/params/config.go index b98f43b834..b6a3a89c39 100644 --- a/internal/params/config.go +++ b/internal/params/config.go @@ -391,6 +391,7 @@ var ( EIP3860Epoch: EpochTBD, PragueEpoch: EpochTBD, EIP8024Epoch: EpochTBD, + ShardStateValidationEpoch: EpochTBD, } // AllProtocolChanges ... @@ -453,6 +454,7 @@ var ( big.NewInt(0), // TimestampValidationEpoch big.NewInt(0), // PragueEpoch big.NewInt(0), // EIP8024Epoch + EpochTBD, // ShardStateValidationEpoch } // TestChainConfig ... @@ -515,6 +517,7 @@ var ( big.NewInt(0), // TimestampValidationEpoch big.NewInt(0), // PragueEpoch big.NewInt(0), // EIP8024Epoch + EpochTBD, // ShardStateValidationEpoch } // TestRules ... @@ -726,6 +729,9 @@ type ChainConfig struct { PragueEpoch *big.Int `json:"prague-epoch,omitempty"` // EIP8024Epoch is the first epoch to support EIP-8024 (DUPN, SWAPN, EXCHANGE opcodes) EIP8024Epoch *big.Int `json:"eip8024-epoch,omitempty"` + // ShardStateValidationEpoch is the first epoch to enforce that only scheduled + // epoch-ending blocks may carry shard state bytes in their header + ShardStateValidationEpoch *big.Int `json:"shard-state-validation-epoch,omitempty"` } // String implements the fmt.Stringer interface. @@ -1012,6 +1018,11 @@ func (c *ChainConfig) IsEIP8024(epoch *big.Int) bool { return isForked(c.EIP8024Epoch, epoch) } +// IsShardStateValidation returns true when non-epoch-ending blocks are rejected if they carry shard state bytes +func (c *ChainConfig) IsShardStateValidation(epoch *big.Int) bool { + return isForked(c.ShardStateValidationEpoch, epoch) +} + // IsChainIdFix returns whether epoch is either equal to the ChainId Fix fork epoch or greater. func (c *ChainConfig) IsChainIdFix(epoch *big.Int) bool { return isForked(c.ChainIdFixEpoch, epoch)