diff --git a/beacon/blockchain/errors.go b/beacon/blockchain/errors.go index b7124c3d5d..44055e23a3 100644 --- a/beacon/blockchain/errors.go +++ b/beacon/blockchain/errors.go @@ -31,6 +31,9 @@ var ( ErrNilBlk = errors.New("nil beacon block") // ErrNilBlob is an error for when the BlobSidecars is nil. ErrNilBlob = errors.New("nil blob") + // ErrVersionMismatch is an error for when the fork for the block timestamp does not match the fork + // for the ABCI timestamp. + ErrVersionMismatch = errors.New("ABCI fork version mismatch") // ErrDataNotAvailable indicates that the required data is not available. ErrDataNotAvailable = errors.New("data not available") // ErrSidecarCommitmentMismatch indicates that the BeaconBlockBody commitments do not match the sidecars. diff --git a/beacon/blockchain/execution_engine.go b/beacon/blockchain/execution_engine.go index 0df88627ad..06c7299d2b 100644 --- a/beacon/blockchain/execution_engine.go +++ b/beacon/blockchain/execution_engine.go @@ -25,7 +25,6 @@ import ( "fmt" ctypes "github.com/berachain/beacon-kit/consensus-types/types" - contypes "github.com/berachain/beacon-kit/consensus/types" engineprimitives "github.com/berachain/beacon-kit/engine-primitives/engine-primitives" statedb "github.com/berachain/beacon-kit/state-transition/core/state" ) @@ -38,7 +37,6 @@ import ( func (s *Service) sendPostBlockFCU( ctx context.Context, st *statedb.StateDB, - blk *contypes.ConsensusBlock, ) error { lph, err := st.GetLatestExecutionPayloadHeader() if err != nil { @@ -47,7 +45,6 @@ func (s *Service) sendPostBlockFCU( // Send a forkchoice update without payload attributes to notify // EL of the new head. - beaconBlk := blk.GetBeaconBlock() // TODO: Switch to New(). req := ctypes.BuildForkchoiceUpdateRequestNoAttrs( &engineprimitives.ForkchoiceStateV1{ @@ -55,7 +52,7 @@ func (s *Service) sendPostBlockFCU( SafeBlockHash: lph.GetParentHash(), FinalizedBlockHash: lph.GetParentHash(), }, - s.chainSpec.ActiveForkVersionForSlot(beaconBlk.GetSlot()), + lph.GetForkVersion(), ) if _, err = s.executionEngine.NotifyForkchoiceUpdate(ctx, req); err != nil { return fmt.Errorf("failed forkchoice update, head %s: %w", diff --git a/beacon/blockchain/finalize_block.go b/beacon/blockchain/finalize_block.go index 70a58fbb66..d607c29db4 100644 --- a/beacon/blockchain/finalize_block.go +++ b/beacon/blockchain/finalize_block.go @@ -25,6 +25,7 @@ import ( "fmt" "time" + "github.com/berachain/beacon-kit/config/spec" "github.com/berachain/beacon-kit/consensus/cometbft/service/encoding" "github.com/berachain/beacon-kit/consensus/types" "github.com/berachain/beacon-kit/primitives/math" @@ -38,12 +39,23 @@ func (s *Service) FinalizeBlock( ctx sdk.Context, req *cmtabci.FinalizeBlockRequest, ) (transition.ValidatorUpdates, error) { + cometTime := math.U64(req.GetTime().Unix()) //#nosec: G115 + if s.chainSpec.DepositEth1ChainID() == spec.DevnetEth1ChainID { + state := s.storageBackend.StateFromContext(ctx) + lph, err := state.GetLatestExecutionPayloadHeader() + if err != nil { + return nil, err + } + cometTime = lph.GetTimestamp() + math.U64(s.chainSpec.TargetSecondsPerEth1Block()) + } // STEP 1: Decode block and blobs. signedBlk, blobs, err := encoding.ExtractBlobsAndBlockFromRequest( req, BeaconBlockTxIndex, BlobSidecarsTxIndex, - s.chainSpec.ActiveForkVersionForSlot(math.Slot(req.Height))) // #nosec G115 + // While req.GetTime() and blk.GetTimestamp() may be different, they are guaranteed + // to map to the same forkVersion due to checks during ProcessProposal. + s.chainSpec.ActiveForkVersionForTimestamp(cometTime)) if err != nil { s.logger.Error("Failed to decode block and blobs", "error", err) return nil, fmt.Errorf("failed to decode block and blobs: %w", err) @@ -88,7 +100,7 @@ func (s *Service) FinalizeBlock( } // STEP 3: Finalize the block. - consensusBlk := types.NewConsensusBlock(blk, req.GetProposerAddress(), req.GetTime()) + consensusBlk := types.NewConsensusBlock(blk, req.GetProposerAddress(), cometTime) st := s.storageBackend.StateFromContext(ctx) valUpdates, err := s.finalizeBeaconBlock(ctx, st, consensusBlk) if err != nil { @@ -98,14 +110,12 @@ func (s *Service) FinalizeBlock( return nil, err } - // STEP 4: Post Finalizations cleanups. - + // STEP 4: Post Finalization cleanups. // Fetch and store the deposit for the block. blockNum := blk.GetBody().GetExecutionPayload().GetNumber() s.depositFetcher(ctx, blockNum) // Store the finalized block in the KVStore. - // // TODO: Store full SignedBeaconBlock with all data in storage slot := blk.GetSlot() if err = s.storageBackend.BlockStore().Set(blk); err != nil { @@ -121,10 +131,9 @@ func (s *Service) FinalizeBlock( s.logger.Error("failed to processPruning", "error", err) } - if err = s.sendPostBlockFCU(ctx, st, consensusBlk); err != nil { + if err = s.sendPostBlockFCU(ctx, st); err != nil { return nil, fmt.Errorf("sendPostBlockFCU failed: %w", err) } - return valUpdates, nil } diff --git a/beacon/blockchain/interfaces.go b/beacon/blockchain/interfaces.go index 3c1d057658..2a051f3745 100644 --- a/beacon/blockchain/interfaces.go +++ b/beacon/blockchain/interfaces.go @@ -67,11 +67,11 @@ type LocalBuilder interface { ctx context.Context, st *statedb.StateDB, slot math.Slot, - timestamp uint64, + timestamp math.U64, parentBlockRoot common.Root, headEth1BlockHash common.ExecutionHash, finalEth1BlockHash common.ExecutionHash, - ) (*engineprimitives.PayloadID, error) + ) (*engineprimitives.PayloadID, common.Version, error) } // StateProcessor defines the interface for processing various state transitions @@ -167,5 +167,7 @@ type PruningChainSpec interface { type ServiceChainSpec interface { PruningChainSpec chain.BlobSpec - ActiveForkVersionForSlot(slot math.Slot) common.Version + ActiveForkVersionForTimestamp(timestamp math.U64) common.Version + DepositEth1ChainID() uint64 + TargetSecondsPerEth1Block() uint64 } diff --git a/beacon/blockchain/payload.go b/beacon/blockchain/payload.go index 3f42b62002..741d6d8238 100644 --- a/beacon/blockchain/payload.go +++ b/beacon/blockchain/payload.go @@ -37,19 +37,6 @@ func (s *Service) forceSyncUponProcess( ctx context.Context, st *statedb.StateDB, ) { - slot, err := st.GetSlot() - if err != nil { - s.logger.Error( - "failed to get slot for force startup head", - "error", err, - ) - return - } - - // TODO: Verify if the slot number is correct here, I believe in current - // form it should be +1'd. Not a big deal until hardforks are in play though. - slot++ - lph, err := st.GetLatestExecutionPayloadHeader() if err != nil { s.logger.Error( @@ -64,7 +51,7 @@ func (s *Service) forceSyncUponProcess( "head_eth1_hash", lph.GetBlockHash(), "safe_eth1_hash", lph.GetParentHash(), "finalized_eth1_hash", lph.GetParentHash(), - "for_slot", slot.Base10(), + "for_slot", lph.GetNumber(), ) // Submit the forkchoice update to the execution client. @@ -74,7 +61,7 @@ func (s *Service) forceSyncUponProcess( SafeBlockHash: lph.GetParentHash(), FinalizedBlockHash: lph.GetParentHash(), }, - s.chainSpec.ActiveForkVersionForSlot(slot), + s.chainSpec.ActiveForkVersionForTimestamp(lph.GetTimestamp()), ) if _, err = s.executionEngine.NotifyForkchoiceUpdate(ctx, req); err != nil { s.logger.Error( @@ -117,7 +104,7 @@ func (s *Service) forceSyncUponFinalize( SafeBlockHash: executionPayload.GetParentHash(), FinalizedBlockHash: executionPayload.GetParentHash(), }, - s.chainSpec.ActiveForkVersionForSlot(beaconBlock.GetSlot()), + s.chainSpec.ActiveForkVersionForTimestamp(executionPayload.GetTimestamp()), ) switch _, err = s.executionEngine.NotifyForkchoiceUpdate(ctx, req); { @@ -196,12 +183,12 @@ func (s *Service) rebuildPayloadForRejectedBlock( } // Submit a request for a new payload. - if _, err = s.localBuilder.RequestPayloadAsync( + if _, _, err = s.localBuilder.RequestPayloadAsync( ctx, st, // We are rebuilding for the current slot. stateSlot, - nextPayloadTimestamp.Unwrap(), + nextPayloadTimestamp, // We set the parent root to the previous block root. The HashTreeRoot // of the header is the same as the HashTreeRoot of the block. latestHeader.HashTreeRoot(), @@ -264,10 +251,10 @@ func (s *Service) optimisticPayloadBuild( // We then trigger a request for the next payload. payload := blk.GetBody().GetExecutionPayload() - if _, err := s.localBuilder.RequestPayloadAsync( + if _, _, err := s.localBuilder.RequestPayloadAsync( ctx, st, slot, - nextPayloadTimestamp.Unwrap(), + nextPayloadTimestamp, // The previous block root is simply the root of the block we just // processed. blk.HashTreeRoot(), diff --git a/beacon/blockchain/process_proposal.go b/beacon/blockchain/process_proposal.go index 7a1c510a70..6d8d3d3cb1 100644 --- a/beacon/blockchain/process_proposal.go +++ b/beacon/blockchain/process_proposal.go @@ -27,6 +27,7 @@ import ( "time" payloadtime "github.com/berachain/beacon-kit/beacon/payload-time" + "github.com/berachain/beacon-kit/config/spec" ctypes "github.com/berachain/beacon-kit/consensus-types/types" "github.com/berachain/beacon-kit/consensus/cometbft/service/encoding" "github.com/berachain/beacon-kit/consensus/types" @@ -37,6 +38,7 @@ import ( "github.com/berachain/beacon-kit/primitives/eip4844" "github.com/berachain/beacon-kit/primitives/math" "github.com/berachain/beacon-kit/primitives/transition" + "github.com/berachain/beacon-kit/primitives/version" "github.com/berachain/beacon-kit/state-transition/core" statedb "github.com/berachain/beacon-kit/state-transition/core/state" cmtabci "github.com/cometbft/cometbft/abci/types" @@ -67,12 +69,23 @@ func (s *Service) ProcessProposal( ) } + cometTime := math.U64(req.GetTime().Unix()) //#nosec: G115 + if s.chainSpec.DepositEth1ChainID() == spec.DevnetEth1ChainID { + state := s.storageBackend.StateFromContext(ctx) + lph, err := state.GetLatestExecutionPayloadHeader() + if err != nil { + return err + } + cometTime = lph.GetTimestamp() + math.U64(s.chainSpec.TargetSecondsPerEth1Block()) + } + forkVersion := s.chainSpec.ActiveForkVersionForTimestamp(cometTime) // Decode signed block and sidecars. signedBlk, sidecars, err := encoding.ExtractBlobsAndBlockFromRequest( req, BeaconBlockTxIndex, BlobSidecarsTxIndex, - s.chainSpec.ActiveForkVersionForSlot(math.Slot(req.Height))) // #nosec G115 + forkVersion, + ) if err != nil { return err } @@ -90,24 +103,41 @@ func (s *Service) ProcessProposal( } blk := signedBlk.GetBeaconBlock() + + // There are two different timestamps: + // - The "consensus time" is determined by CometBFT consensus and can be retrieved with `req.GetTime()` + // - The "block time" is determined by beacon-kit consensus and can be retrieved with `blk.GetTimestamp()` + // The "consensus time" is what the network agrees the current time is based on CometBFT PBTS. + // This "consensus time" is used to constrain the timestamp set as the "block time" by the + // beacon-kit app, but they are not always equal in value. The "block time" is used by the + // beacon-kit consensus and execution layers to determine the active fork version. + // + // When unmarshaling the BeaconBlock, we do not yet have access to the "block time", so we + // must rely on the "consensus time" as our best estimation of the "block time" needed to + // determine the current fork version. Since the two timestamps could be different, we need to + // ensure that the fork version for these timestamps are the same. This may result in a failed + // proposal or two at the start of the fork. + blkVersion := s.chainSpec.ActiveForkVersionForTimestamp(blk.GetTimestamp()) + if !version.Equals(blkVersion, forkVersion) { + return fmt.Errorf("CometBFT version %v, BeaconBlock version %v: %w", + forkVersion, blkVersion, + ErrVersionMismatch, + ) + } // Make sure we have the right number of BlobSidecars blobKzgCommitments := blk.GetBody().GetBlobKzgCommitments() numCommitments := len(blobKzgCommitments) if numCommitments != len(sidecars) { - err = fmt.Errorf("expected %d sidecars, got %d: %w", + return fmt.Errorf("expected %d sidecars, got %d: %w", numCommitments, len(sidecars), ErrSidecarCommitmentMismatch, ) - s.logger.Warn(err.Error()) - return err } if uint64(numCommitments) > s.chainSpec.MaxBlobsPerBlock() { - err = fmt.Errorf("expected less than %d sidecars, got %d: %w", + return fmt.Errorf("expected less than %d sidecars, got %d: %w", s.chainSpec.MaxBlobsPerBlock(), numCommitments, core.ErrExceedsBlockBlobLimit, ) - s.logger.Warn(err.Error()) - return err } // Verify the block and sidecar signatures. We can simply verify the block @@ -140,11 +170,11 @@ func (s *Service) ProcessProposal( } } - // Process the block + // Process the block. consensusBlk := types.NewConsensusBlock( blk, req.GetProposerAddress(), - req.GetTime(), + cometTime, ) err = s.VerifyIncomingBlock( ctx, diff --git a/beacon/validator/block_builder.go b/beacon/validator/block_builder.go index fe038c6d31..ce7f07f345 100644 --- a/beacon/validator/block_builder.go +++ b/beacon/validator/block_builder.go @@ -26,6 +26,7 @@ import ( "time" payloadtime "github.com/berachain/beacon-kit/beacon/payload-time" + "github.com/berachain/beacon-kit/config/spec" ctypes "github.com/berachain/beacon-kit/consensus-types/types" "github.com/berachain/beacon-kit/consensus/types" "github.com/berachain/beacon-kit/errors" @@ -55,6 +56,15 @@ func (s *Service) BuildBlockAndSidecars( return nil, nil, builder.ErrPayloadBuilderDisabled } + if s.chainSpec.DepositEth1ChainID() == spec.DevnetEth1ChainID { + state := s.sb.StateFromContext(ctx) + lph, err := state.GetLatestExecutionPayloadHeader() + if err != nil { + return nil, nil, err + } + slotData.SetConsensusTime(lph.GetTimestamp() + math.U64(s.chainSpec.TargetSecondsPerEth1Block())) + } + // The goal here is to acquire a payload whose parent is the previously // finalized block, such that, if this payload is accepted, it will be // the next finalized block in the chain. A byproduct of this design @@ -62,10 +72,7 @@ func (s *Service) BuildBlockAndSidecars( // and safe block hashes to the execution client. st := s.sb.StateFromContext(ctx) - // we introduce hard forks with the expectation that the height set for the - // hard fork is the first height at which new rules apply. So we need to make - // sure that when building blocks, we pick the right height. blkSlots is the - // height for the next block, which consensus is requesting BeaconKit to build. + // blkSlot is the height for the next block, which consensus is requesting BeaconKit to build. blkSlot := slotData.GetSlot() // Prepare the state such that it is ready to build a block for @@ -74,29 +81,44 @@ func (s *Service) BuildBlockAndSidecars( return nil, nil, err } - // Build forkdata used for the signing root of the reveal and the sidecars - forkData, err := s.buildForkData(st, blkSlot) + // Grab parent block root for payload request. + parentBlockRoot, err := st.GetBlockRootAtIndex( + (blkSlot.Unwrap() - 1) % s.chainSpec.SlotsPerHistoricalRoot(), + ) if err != nil { return nil, nil, err } - // Build the reveal for the current slot. - // TODO: We can optimize to pre-compute this in parallel? - reveal, err := s.buildRandaoReveal(forkData, blkSlot) + // Get the payload for the block. + envelope, err := s.retrieveExecutionPayload(ctx, st, parentBlockRoot, slotData) + if err != nil { + return nil, nil, fmt.Errorf("failed retrieving execution payload: %w", err) + } + + // We introduce hard forks with the expectation that the first block proposed after the + // hard fork timestamp is when new rules apply. When building blocks, we provide the Execution + // Layer client with a timestamp, and it will create its payload based on that timestamp. We + // must use this same timestamp from the payload to build the beacon block. This ensures that + // we are building on the same fork version as the Execution Layer. + timestamp := envelope.GetExecutionPayload().GetTimestamp() + + // Build forkdata used for the signing root of the reveal and the sidecars + forkData, err := s.buildForkData(st, timestamp) if err != nil { return nil, nil, err } // Create a new empty block from the current state. - blk, err := s.getEmptyBeaconBlockForSlot(st, blkSlot) + blk, err := s.getEmptyBeaconBlockForSlot(st, blkSlot, forkData.CurrentVersion, parentBlockRoot) if err != nil { return nil, nil, err } - // Get the payload for the block. - envelope, err := s.retrieveExecutionPayload(ctx, st, blk, slotData) + // Build the reveal for the current slot. + // TODO: We can optimize to pre-compute this in parallel? + reveal, err := s.buildRandaoReveal(forkData, blkSlot) if err != nil { - return nil, nil, fmt.Errorf("failed retrieving execution payload: %w", err) + return nil, nil, err } // We have to assemble the block body prior to producing the sidecars @@ -150,15 +172,8 @@ func (s *Service) BuildBlockAndSidecars( // getEmptyBeaconBlockForSlot creates a new empty block. func (s *Service) getEmptyBeaconBlockForSlot( st *statedb.StateDB, requestedSlot math.Slot, + forkVersion common.Version, parentBlockRoot common.Root, ) (*ctypes.BeaconBlock, error) { - // Create a new block. - parentBlockRoot, err := st.GetBlockRootAtIndex( - (requestedSlot.Unwrap() - 1) % s.chainSpec.SlotsPerHistoricalRoot(), - ) - if err != nil { - return nil, err - } - // Get the proposer index for the slot. proposerIndex, err := st.ValidatorIndexByPubkey( s.signer.PublicKey(), @@ -167,22 +182,23 @@ func (s *Service) getEmptyBeaconBlockForSlot( return nil, err } + // Create a new block. return ctypes.NewBeaconBlockWithVersion( requestedSlot, proposerIndex, parentBlockRoot, - s.chainSpec.ActiveForkVersionForSlot(requestedSlot), + forkVersion, ) } -func (s *Service) buildForkData(st *statedb.StateDB, slot math.Slot) (*ctypes.ForkData, error) { +func (s *Service) buildForkData(st *statedb.StateDB, timestamp math.U64) (*ctypes.ForkData, error) { genesisValidatorsRoot, err := st.GetGenesisValidatorsRoot() if err != nil { return nil, err } return ctypes.NewForkData( - s.chainSpec.ActiveForkVersionForSlot(slot), + s.chainSpec.ActiveForkVersionForTimestamp(timestamp), genesisValidatorsRoot, ), nil } @@ -206,7 +222,7 @@ func (s *Service) buildRandaoReveal( func (s *Service) retrieveExecutionPayload( ctx context.Context, st *statedb.StateDB, - blk *ctypes.BeaconBlock, + parentBlockRoot common.Root, slotData *types.SlotData, ) (ctypes.BuiltExecutionPayloadEnv, error) { // @@ -215,8 +231,8 @@ func (s *Service) retrieveExecutionPayload( // Get the payload for the block. envelope, err := s.localPayloadBuilder.RetrievePayload( ctx, - blk.GetSlot(), - blk.GetParentBlockRoot(), + slotData.GetSlot(), + parentBlockRoot, ) if err == nil { return envelope, nil @@ -232,7 +248,7 @@ func (s *Service) retrieveExecutionPayload( // this less confusing. s.metrics.failedToRetrievePayload( - blk.GetSlot(), + slotData.GetSlot(), err, ) @@ -246,13 +262,13 @@ func (s *Service) retrieveExecutionPayload( return s.localPayloadBuilder.RequestPayloadSync( ctx, st, - blk.GetSlot(), + slotData.GetSlot(), payloadtime.Next( slotData.GetConsensusTime(), lph.GetTimestamp(), false, // buildOptimistically - ).Unwrap(), - blk.GetParentBlockRoot(), + ), + parentBlockRoot, lph.GetBlockHash(), lph.GetParentHash(), ) diff --git a/beacon/validator/interfaces.go b/beacon/validator/interfaces.go index 5d7a386121..8e3a54bf17 100644 --- a/beacon/validator/interfaces.go +++ b/beacon/validator/interfaces.go @@ -63,7 +63,7 @@ type PayloadBuilder interface { ctx context.Context, st *statedb.StateDB, slot math.Slot, - timestamp uint64, + timestamp math.U64, parentBlockRoot common.Root, headEth1BlockHash common.ExecutionHash, finalEth1BlockHash common.ExecutionHash, @@ -111,10 +111,12 @@ type BlockBuilderI interface { // ChainSpec defines an interface for accessing chain-specific parameters. type ChainSpec interface { - SlotsPerHistoricalRoot() uint64 + ActiveForkVersionForTimestamp(timestamp math.U64) common.Version + DepositEth1ChainID() uint64 DomainTypeRandao() common.DomainType MaxDepositsPerBlock() uint64 - ActiveForkVersionForSlot(slot math.Slot) common.Version SlotToEpoch(slot math.Slot) math.Epoch + SlotsPerHistoricalRoot() uint64 + TargetSecondsPerEth1Block() uint64 ctypes.ProposerDomain } diff --git a/chain/data.go b/chain/data.go index bd6db20ef5..ae6d252e1a 100644 --- a/chain/data.go +++ b/chain/data.go @@ -90,10 +90,10 @@ type SpecData struct { // Fork-related values. // - // Deneb1ForkEpoch is the epoch at which the Deneb1 fork is activated. - Deneb1ForkEpoch uint64 `mapstructure:"deneb-one-fork-epoch"` - // ElectraForkEpoch is the epoch at which the Electra fork is activated. - ElectraForkEpoch uint64 `mapstructure:"electra-fork-epoch"` + // Deneb1ForkTime is the time at which the Deneb1 fork is activated. + Deneb1ForkTime uint64 `mapstructure:"deneb-one-fork-time"` + // ElectraForkTime is the time at which the Electra fork is activated. + ElectraForkTime uint64 `mapstructure:"electra-fork-time"` // State list lengths // diff --git a/chain/helpers.go b/chain/helpers.go index 16e910e21d..c962476ee6 100644 --- a/chain/helpers.go +++ b/chain/helpers.go @@ -26,17 +26,13 @@ import ( "github.com/berachain/beacon-kit/primitives/version" ) -// ActiveForkVersionForSlot returns the active fork version for a given slot. -func (s spec) ActiveForkVersionForSlot(slot math.Slot) common.Version { - return s.ActiveForkVersionForEpoch(s.SlotToEpoch(slot)) -} - -// ActiveForkVersionForEpoch returns the active fork version for a given epoch. -func (s spec) ActiveForkVersionForEpoch(epoch math.Epoch) common.Version { - if epoch >= s.ElectraForkEpoch() { +// ActiveForkVersionForTimestamp returns the active fork version for a given timestamp. +func (s spec) ActiveForkVersionForTimestamp(timestamp math.U64) common.Version { + time := timestamp.Unwrap() + if time >= s.ElectraForkTime() { return version.Electra() } - if epoch >= s.Deneb1ForkEpoch() { + if time >= s.Deneb1ForkTime() { return version.Deneb1() } return version.Deneb() diff --git a/chain/helpers_test.go b/chain/helpers_test.go index d157d909ac..78bd26c88a 100644 --- a/chain/helpers_test.go +++ b/chain/helpers_test.go @@ -35,33 +35,33 @@ import ( // Create an instance of chainSpec with test data. var spec, _ = chain.NewSpec( &chain.SpecData{ - Deneb1ForkEpoch: 9, - ElectraForkEpoch: 10, + Deneb1ForkTime: 9 * 32 * 2, + ElectraForkTime: 10 * 32 * 2, SlotsPerEpoch: 32, MinEpochsForBlobsSidecarsRequest: 5, MaxWithdrawalsPerPayload: 2, }, ) -// TestActiveForkVersionForEpoch tests the ActiveForkVersionForEpoch method. -func TestActiveForkVersionForEpoch(t *testing.T) { +// TestActiveForkVersionForTimestamp tests the ActiveForkVersionForTimestamp method. +func TestActiveForkVersionForTimestamp(t *testing.T) { t.Parallel() // Define test cases tests := []struct { - name string - epoch math.Epoch - expected common.Version + name string + timestamp uint64 + expected common.Version }{ - {name: "Before Electra Fork", epoch: 9, expected: version.Deneb1()}, - {name: "At Electra Fork", epoch: 10, expected: version.Electra()}, - {name: "After Electra Fork", epoch: 11, expected: version.Electra()}, + {name: "Before Electra Fork", timestamp: spec.ElectraForkTime() - 1, expected: version.Deneb1()}, + {name: "At Electra Fork", timestamp: spec.ElectraForkTime(), expected: version.Electra()}, + {name: "After Electra Fork", timestamp: spec.ElectraForkTime() + 1, expected: version.Electra()}, } // Run test cases for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { t.Parallel() - result := spec.ActiveForkVersionForEpoch(tt.epoch) + result := spec.ActiveForkVersionForTimestamp(math.U64(tt.timestamp)) require.Equal(t, tt.expected, result, "Test case : %s", tt.name) }) } @@ -94,35 +94,6 @@ func TestSlotToEpoch(t *testing.T) { } } -// TestActiveForkVersionForSlot tests the ActiveForkVersionForSlot method. -func TestActiveForkVersionForSlot(t *testing.T) { - t.Parallel() - // Define test cases - tests := []struct { - name string - slot math.Slot - expected common.Version - }{ - {name: "Before Electra Fork", slot: 0, expected: version.Deneb()}, - { - name: "Just Before Electra Fork", - slot: 319, - expected: version.Deneb1(), - }, - {name: "At Electra Fork", slot: 320, expected: version.Electra()}, - {name: "After Electra Fork", slot: 640, expected: version.Electra()}, - } - - // Run test cases - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - t.Parallel() - result := spec.ActiveForkVersionForSlot(tt.slot) - require.Equal(t, tt.expected, result, "Test case : %s", tt.name) - }) - } -} - // TestWithinDAPeriod tests the WithinDAPeriod method. func TestWithinDAPeriod(t *testing.T) { t.Parallel() diff --git a/chain/spec.go b/chain/spec.go index ddbbb2947e..f07f0b10fa 100644 --- a/chain/spec.go +++ b/chain/spec.go @@ -95,11 +95,11 @@ type DomainTypeSpec interface { type ForkSpec interface { // Fork-related values. - // Deneb1ForkEpoch returns the epoch at which the Deneb1 fork takes effect. - Deneb1ForkEpoch() math.Epoch + // Deneb1ForkTime returns the time at which the Deneb1 fork takes effect. + Deneb1ForkTime() uint64 - // ElectraForkEpoch returns the epoch at which the Electra fork takes effect. - ElectraForkEpoch() math.Epoch + // ElectraForkTime returns the time at which the Electra fork takes effect. + ElectraForkTime() uint64 } type BlobSpec interface { @@ -127,22 +127,19 @@ type BlobSpec interface { type ForkVersionSpec interface { // Helpers for ChainSpecData - // ActiveForkVersionForSlot returns the active fork version for a given slot. - ActiveForkVersionForSlot(slot math.Slot) common.Version - - // ActiveForkVersionForEpoch returns the active fork version for a given epoch. - ActiveForkVersionForEpoch(epoch math.Epoch) common.Version + // ActiveForkVersionForTimestamp returns the active fork version for a given timestamp. + ActiveForkVersionForTimestamp(timestamp math.U64) common.Version } type EVMInflationSpec interface { // EVMInflationAddress returns the address on the EVM which will receive // the inflation amount of native EVM balance through a withdrawal every // block. - EVMInflationAddress(slot math.Slot) common.ExecutionAddress + EVMInflationAddress(timestamp math.U64) common.ExecutionAddress // EVMInflationPerBlock returns the amount of native EVM balance (in Gwei) // to be minted to the EVMInflationAddress via a withdrawal every block. - EVMInflationPerBlock(slot math.Slot) uint64 + EVMInflationPerBlock(timestamp math.U64) uint64 } type WithdrawalsSpec interface { @@ -363,14 +360,14 @@ func (s spec) TargetSecondsPerEth1Block() uint64 { return s.Data.TargetSecondsPerEth1Block } -// Deneb1ForkEpoch returns the epoch of the Deneb1 fork. -func (s spec) Deneb1ForkEpoch() math.Epoch { - return math.Epoch(s.Data.Deneb1ForkEpoch) +// Deneb1ForkTime returns the epoch of the Deneb1 fork. +func (s spec) Deneb1ForkTime() uint64 { + return s.Data.Deneb1ForkTime } -// ElectraForkEpoch returns the epoch of the Electra fork. -func (s spec) ElectraForkEpoch() math.Epoch { - return math.Epoch(s.Data.ElectraForkEpoch) +// ElectraForkTime returns the epoch of the Electra fork. +func (s spec) ElectraForkTime() uint64 { + return s.Data.ElectraForkTime } // EpochsPerHistoricalVector returns the number of epochs per historical vector. @@ -448,8 +445,8 @@ func (s spec) ValidatorSetCap() uint64 { // EVMInflationAddress returns the address on the EVM which will receive the // inflation amount of native EVM balance through a withdrawal every block. -func (s spec) EVMInflationAddress(slot math.Slot) common.ExecutionAddress { - fv := s.ActiveForkVersionForSlot(slot) +func (s spec) EVMInflationAddress(timestamp math.U64) common.ExecutionAddress { + fv := s.ActiveForkVersionForTimestamp(timestamp) switch fv { case version.Deneb1(), version.Electra(): return s.Data.EVMInflationAddressDeneb1 @@ -462,8 +459,8 @@ func (s spec) EVMInflationAddress(slot math.Slot) common.ExecutionAddress { // EVMInflationPerBlock returns the amount of native EVM balance (in Gwei) to // be minted to the EVMInflationAddress via a withdrawal every block. -func (s spec) EVMInflationPerBlock(slot math.Slot) uint64 { - fv := s.ActiveForkVersionForSlot(slot) +func (s spec) EVMInflationPerBlock(timestamp math.U64) uint64 { + fv := s.ActiveForkVersionForTimestamp(timestamp) switch fv { case version.Deneb1(), version.Electra(): return s.Data.EVMInflationPerBlockDeneb1 diff --git a/cli/builder/builder.go b/cli/builder/builder.go index c8a072d5a8..af451d9e78 100644 --- a/cli/builder/builder.go +++ b/cli/builder/builder.go @@ -24,7 +24,6 @@ import ( "os" "cosmossdk.io/depinject" - "github.com/berachain/beacon-kit/chain" cmdlib "github.com/berachain/beacon-kit/cli/commands" servertypes "github.com/berachain/beacon-kit/cli/commands/server/types" "github.com/berachain/beacon-kit/cli/config" @@ -46,7 +45,8 @@ type CLIBuilder struct { // nodeBuilderFunc is a function that builds the Node, // eventually called by the cosmos-sdk. // TODO: CLI should not know about the AppCreator - nodeBuilderFunc servertypes.AppCreator + nodeBuilderFunc servertypes.AppCreator + chainSpecBuilderFunc servertypes.ChainSpecCreator } // New returns a new CLIBuilder with the given options. @@ -67,7 +67,6 @@ func (cb *CLIBuilder) Build() (*cmdlib.Root, error) { // allocate memory to hold the dependencies var ( clientCtx client.Context - chainSpec chain.Spec logger *phuslu.Logger ) @@ -81,7 +80,6 @@ func (cb *CLIBuilder) Build() (*cmdlib.Root, error) { ), &logger, &clientCtx, - &chainSpec, ); err != nil { return nil, err } @@ -99,7 +97,7 @@ func (cb *CLIBuilder) Build() (*cmdlib.Root, error) { rootCmd, &cometbft.Service{}, cb.nodeBuilderFunc, - chainSpec, + cb.chainSpecBuilderFunc, ) return rootCmd, nil diff --git a/cli/builder/options.go b/cli/builder/options.go index da488c6afd..5b948ced3f 100644 --- a/cli/builder/options.go +++ b/cli/builder/options.go @@ -54,3 +54,10 @@ func WithNodeBuilderFunc(nodeBuilderFunc servertypes.AppCreator) Opt { cb.nodeBuilderFunc = nodeBuilderFunc } } + +// WithChainSpecBuilderFunc sets the chainspec builder +func WithChainSpecBuilderFunc(chainBuilderFunc servertypes.ChainSpecCreator) Opt { + return func(cb *CLIBuilder) { + cb.chainSpecBuilderFunc = chainBuilderFunc + } +} diff --git a/cli/commands/deposit/commands.go b/cli/commands/deposit/commands.go index 46815509e5..142b1d50a2 100644 --- a/cli/commands/deposit/commands.go +++ b/cli/commands/deposit/commands.go @@ -27,7 +27,7 @@ import ( ) // Commands creates a new command for deposit related actions. -func Commands(chainSpec ChainSpec, appCreator servertypes.AppCreator) *cobra.Command { +func Commands(chainSpecCreator servertypes.ChainSpecCreator, appCreator servertypes.AppCreator) *cobra.Command { cmd := &cobra.Command{ Use: "deposit", Short: "deposit subcommands", @@ -37,8 +37,8 @@ func Commands(chainSpec ChainSpec, appCreator servertypes.AppCreator) *cobra.Com } cmd.AddCommand( - GetValidateDepositCmd(chainSpec), - GetCreateValidatorCmd(chainSpec), + GetValidateDepositCmd(chainSpecCreator), + GetCreateValidatorCmd(chainSpecCreator), GetValidatorKeysCmd(), GetDBCheckCmd(appCreator), ) diff --git a/cli/commands/deposit/create.go b/cli/commands/deposit/create.go index c142ab1c6f..c9a7306b39 100644 --- a/cli/commands/deposit/create.go +++ b/cli/commands/deposit/create.go @@ -23,6 +23,7 @@ package deposit import ( "fmt" + clitypes "github.com/berachain/beacon-kit/cli/commands/server/types" clicontext "github.com/berachain/beacon-kit/cli/context" "github.com/berachain/beacon-kit/cli/utils/parser" "github.com/berachain/beacon-kit/consensus-types/types" @@ -56,14 +57,14 @@ const ( // //nolint:lll // Reads better if long description is one line. func GetCreateValidatorCmd( - chainSpec ChainSpec, + chainSpecCreator clitypes.ChainSpecCreator, ) *cobra.Command { cmd := &cobra.Command{ Use: "create-validator [withdrawal-address] [amount] ?[beacond/genesis.json]", Short: "Creates a validator deposit message", Long: `Creates a validator deposit message with the necessary credentials. The arguments are expected in the order of withdrawal address, deposit amount, and optionally the beacond genesis file. If the genesis validator root flag is NOT set, the beacond genesis file MUST be provided as the last argument. If the override flag is set to true, a private key must be provided to sign the message.`, Args: cobra.RangeArgs(minArgsCreateDeposit, maxArgsCreateDeposit), - RunE: createValidatorCmd(chainSpec), + RunE: createValidatorCmd(chainSpecCreator), } cmd.Flags().BoolP( @@ -89,9 +90,14 @@ func GetCreateValidatorCmd( // createValidatorCmd returns a command that builds a create validator request. func createValidatorCmd( - chainSpec ChainSpec, + chainSpecCreator clitypes.ChainSpecCreator, ) func(*cobra.Command, []string) error { return func(cmd *cobra.Command, args []string) error { + appOpts := clicontext.GetViperFromCmd(cmd) + chainSpec, err := chainSpecCreator(appOpts) + if err != nil { + return err + } // Get the BLS signer. blsSigner, err := getBLSSigner(cmd) if err != nil { diff --git a/cli/commands/deposit/validate.go b/cli/commands/deposit/validate.go index 318852e022..7cb2d97b0a 100644 --- a/cli/commands/deposit/validate.go +++ b/cli/commands/deposit/validate.go @@ -21,6 +21,8 @@ package deposit import ( + clitypes "github.com/berachain/beacon-kit/cli/commands/server/types" + "github.com/berachain/beacon-kit/cli/context" "github.com/berachain/beacon-kit/cli/utils/parser" "github.com/berachain/beacon-kit/consensus-types/types" "github.com/berachain/beacon-kit/node-core/components/signer" @@ -41,16 +43,16 @@ const ( maxArgsValidateDeposit = 5 ) -// NewValidateDeposit creates a new command for validating a deposit message. +// GetValidateDepositCmd creates a new command for validating a deposit message. // //nolint:lll // Reads better if long description is one line. -func GetValidateDepositCmd(chainSpec ChainSpec) *cobra.Command { +func GetValidateDepositCmd(chainSpecCreator clitypes.ChainSpecCreator) *cobra.Command { cmd := &cobra.Command{ Use: "validate [pubkey] [withdrawal-credentials] [amount] [signature] ?[beacond/genesis.json]", Short: "Validates a deposit message for creating a new validator", Long: `Validates a deposit message (public key, withdrawal credentials, deposit amount) for creating a new validator. The args taken are in the order of the public key, withdrawal credentials, deposit amount, signature, and optionally the beacond genesis file. If the genesis validator root flag is NOT set, the beacond genesis file MUST be provided as the last argument.`, Args: cobra.RangeArgs(minArgsValidateDeposit, maxArgsValidateDeposit), - RunE: validateDepositMessage(chainSpec), + RunE: validateDepositMessage(chainSpecCreator), } cmd.Flags().StringP( @@ -64,8 +66,13 @@ func GetValidateDepositCmd(chainSpec ChainSpec) *cobra.Command { } // validateDepositMessage validates a deposit message for creating a new validator. -func validateDepositMessage(chainSpec ChainSpec) func(cmd *cobra.Command, args []string) error { +func validateDepositMessage(chainSpecCreator clitypes.ChainSpecCreator) func(cmd *cobra.Command, args []string) error { return func(cmd *cobra.Command, args []string) error { + v := context.GetViperFromCmd(cmd) + chainSpec, err := chainSpecCreator(v) + if err != nil { + return err + } pubKeyStr := args[validatePubKey0] pubkey, err := parser.ConvertPubkey(pubKeyStr) if err != nil { diff --git a/cli/commands/genesis/deposit.go b/cli/commands/genesis/deposit.go index ec7ab08044..f38167571c 100644 --- a/cli/commands/genesis/deposit.go +++ b/cli/commands/genesis/deposit.go @@ -25,6 +25,7 @@ import ( "os" "path/filepath" + servertypes "github.com/berachain/beacon-kit/cli/commands/server/types" "github.com/berachain/beacon-kit/cli/context" "github.com/berachain/beacon-kit/cli/utils/parser" "github.com/berachain/beacon-kit/consensus-types/types" @@ -47,7 +48,7 @@ import ( // add a premined deposit to the genesis file. // //nolint:lll // reads better if long description is one line. -func AddGenesisDepositCmd(cs ChainSpec) *cobra.Command { +func AddGenesisDepositCmd(chainSpecCreator servertypes.ChainSpecCreator) *cobra.Command { cmd := &cobra.Command{ Use: "add-premined-deposit", Short: "adds a validator to the genesis file", @@ -74,7 +75,11 @@ func AddGenesisDepositCmd(cs ChainSpec) *cobra.Command { if err != nil { return err } - return AddGenesisDeposit(cs, cometConfig, blsSigner, depositAmount, withdrawalAddress, outputDocument) + chainSpec, err := chainSpecCreator(appOpts) + if err != nil { + return err + } + return AddGenesisDeposit(chainSpec, cometConfig, blsSigner, depositAmount, withdrawalAddress, outputDocument) }, } return cmd diff --git a/cli/commands/genesis/genesis.go b/cli/commands/genesis/genesis.go index c1a5032f9e..5bbfa517f5 100644 --- a/cli/commands/genesis/genesis.go +++ b/cli/commands/genesis/genesis.go @@ -21,6 +21,7 @@ package genesis import ( + servertypes "github.com/berachain/beacon-kit/cli/commands/server/types" "github.com/cosmos/cosmos-sdk/client" "github.com/spf13/cobra" ) @@ -28,7 +29,7 @@ import ( // Commands builds the genesis-related command. Users may // provide application specific commands as a parameter. func Commands( - cs ChainSpec, + csc servertypes.ChainSpecCreator, cmds ...*cobra.Command, ) *cobra.Command { cmd := &cobra.Command{ @@ -41,11 +42,11 @@ func Commands( // Adding subcommands for genesis-related operations. cmd.AddCommand( - AddGenesisDepositCmd(cs), + AddGenesisDepositCmd(csc), CollectGenesisDepositsCmd(), - AddExecutionPayloadCmd(cs), - GetGenesisValidatorRootCmd(cs), - SetDepositStorageCmd(cs), + AddExecutionPayloadCmd(csc), + GetGenesisValidatorRootCmd(csc), + SetDepositStorageCmd(csc), ) // Add additional commands diff --git a/cli/commands/genesis/payload.go b/cli/commands/genesis/payload.go index 970d34ced7..123600d42c 100644 --- a/cli/commands/genesis/payload.go +++ b/cli/commands/genesis/payload.go @@ -24,6 +24,7 @@ import ( "fmt" "unsafe" + servertypes "github.com/berachain/beacon-kit/cli/commands/server/types" "github.com/berachain/beacon-kit/cli/context" "github.com/berachain/beacon-kit/consensus-types/types" engineprimitives "github.com/berachain/beacon-kit/engine-primitives/engine-primitives" @@ -41,7 +42,7 @@ import ( "github.com/spf13/cobra" ) -func AddExecutionPayloadCmd(chainSpec ChainSpec) *cobra.Command { +func AddExecutionPayloadCmd(chainSpecCreator servertypes.ChainSpecCreator) *cobra.Command { cmd := &cobra.Command{ Use: "execution-payload [eth/genesis/file.json]", Short: "adds the eth1 genesis execution payload to the genesis file", @@ -50,6 +51,11 @@ func AddExecutionPayloadCmd(chainSpec ChainSpec) *cobra.Command { // Read the genesis file. elGenesisPath := args[0] config := context.GetConfigFromCmd(cmd) + v := context.GetViperFromCmd(cmd) + chainSpec, err := chainSpecCreator(v) + if err != nil { + return err + } return AddExecutionPayload(chainSpec, elGenesisPath, config) }, } diff --git a/cli/commands/genesis/root.go b/cli/commands/genesis/root.go index a0708fb8c9..4affa74057 100644 --- a/cli/commands/genesis/root.go +++ b/cli/commands/genesis/root.go @@ -21,19 +21,26 @@ package genesis import ( + "github.com/berachain/beacon-kit/cli/commands/server/types" + "github.com/berachain/beacon-kit/cli/context" "github.com/berachain/beacon-kit/cli/utils/genesis" "github.com/spf13/cobra" ) // GetGenesisValidatorRootCmd returns a command that gets the genesis validator root from a given // beacond genesis file. -func GetGenesisValidatorRootCmd(cs ChainSpec) *cobra.Command { +func GetGenesisValidatorRootCmd(chainSpecCreator types.ChainSpecCreator) *cobra.Command { cmd := &cobra.Command{ Use: "validator-root [beacond/genesis.json]", Short: "gets and returns the genesis validator root", Args: cobra.ExactArgs(1), RunE: func(cmd *cobra.Command, args []string) error { - genesisValidatorsRoot, err := genesis.ComputeValidatorsRootFromFile(args[0], cs) + v := context.GetViperFromCmd(cmd) + chainSpec, err := chainSpecCreator(v) + if err != nil { + return err + } + genesisValidatorsRoot, err := genesis.ComputeValidatorsRootFromFile(args[0], chainSpec) if err != nil { return err } diff --git a/cli/commands/genesis/storage.go b/cli/commands/genesis/storage.go index d39dc7024d..7d9c917230 100644 --- a/cli/commands/genesis/storage.go +++ b/cli/commands/genesis/storage.go @@ -25,6 +25,7 @@ import ( "path/filepath" "github.com/berachain/beacon-kit/cli/commands/genesis/types" + clitypes "github.com/berachain/beacon-kit/cli/commands/server/types" "github.com/berachain/beacon-kit/cli/context" ctypes "github.com/berachain/beacon-kit/consensus-types/types" "github.com/berachain/beacon-kit/errors" @@ -41,7 +42,7 @@ import ( // SetDepositStorageCmd sets deposit contract storage in genesis alloc file. // //nolint:lll // reads better if long description is one line -func SetDepositStorageCmd(chainSpec ChainSpec) *cobra.Command { +func SetDepositStorageCmd(chainSpecCreator clitypes.ChainSpecCreator) *cobra.Command { cmd := &cobra.Command{ Use: "set-deposit-storage [eth/genesis/file.json]", Short: "sets deposit contract storage in eth genesis", @@ -56,6 +57,11 @@ func SetDepositStorageCmd(chainSpec ChainSpec) *cobra.Command { } // Get the deposits from the beacon chain genesis appstate. config := context.GetConfigFromCmd(cmd) + appOpts := context.GetViperFromCmd(cmd) + chainSpec, err := chainSpecCreator(appOpts) + if err != nil { + return err + } return SetDepositStorage(chainSpec, config, elGenesisFilePath, isNethermind) }, } diff --git a/cli/commands/genesis/storage_test.go b/cli/commands/genesis/storage_test.go index 385428a727..3d490b2a54 100644 --- a/cli/commands/genesis/storage_test.go +++ b/cli/commands/genesis/storage_test.go @@ -26,7 +26,9 @@ import ( "path/filepath" "testing" + "github.com/berachain/beacon-kit/chain" "github.com/berachain/beacon-kit/cli/commands/genesis" + servertypes "github.com/berachain/beacon-kit/cli/commands/server/types" "github.com/berachain/beacon-kit/config/spec" "github.com/berachain/beacon-kit/primitives/encoding/json" "github.com/cosmos/cosmos-sdk/client" @@ -41,7 +43,9 @@ func TestSetDepositStorageCmd(t *testing.T) { t.Parallel() chainSpec, err := spec.DevnetChainSpec() require.NoError(t, err) - cmd := genesis.SetDepositStorageCmd(chainSpec) + cmd := genesis.SetDepositStorageCmd(func(_ servertypes.AppOptions) (chain.Spec, error) { + return chainSpec, nil + }) require.Equal(t, "set-deposit-storage [eth/genesis/file.json]", cmd.Use) }) @@ -63,7 +67,9 @@ func TestSetDepositStorageCmd(t *testing.T) { // Create and execute the command chainSpec, err := spec.DevnetChainSpec() require.NoError(t, err) - cmd := genesis.SetDepositStorageCmd(chainSpec) + cmd := genesis.SetDepositStorageCmd(func(_ servertypes.AppOptions) (chain.Spec, error) { + return chainSpec, nil + }) cmd.SetContext(ctx) // Change working directory to tmpDir for the test currentDir, err := os.Getwd() diff --git a/cli/commands/server/cmd/execute.go b/cli/commands/server/cmd/execute.go index 33604765e3..76b393899a 100644 --- a/cli/commands/server/cmd/execute.go +++ b/cli/commands/server/cmd/execute.go @@ -25,6 +25,7 @@ import ( "context" "strings" + "github.com/berachain/beacon-kit/cli/commands/server/types" "github.com/cosmos/cosmos-sdk/client" "github.com/cosmos/cosmos-sdk/client/flags" "github.com/spf13/cobra" @@ -47,6 +48,10 @@ func Execute(rootCmd *cobra.Command, envPrefix, defaultHome string) error { ctx := CreateExecuteContext(context.Background()) rootCmd.PersistentFlags(). StringP(flags.FlagHome, "", defaultHome, "directory for config and data") + rootCmd.PersistentFlags().String( + types.FlagConfigurableChainSpecPath, + "", + "Path to custom chain spec if CHAIN_SPEC=configurable envar is set") // update the global viper with the root command's configuration viper.SetEnvPrefix(envPrefix) diff --git a/cli/commands/server/types/app.go b/cli/commands/server/types/app.go index c1ae573c9b..282f8a279c 100644 --- a/cli/commands/server/types/app.go +++ b/cli/commands/server/types/app.go @@ -30,18 +30,6 @@ import ( ) type ( - // AppOptions defines an interface that is passed into an application - // constructor, typically used to set BaseApp options that are either - // supplied via config file or through CLI arguments/flags. The underlying - // implementation - // is defined by the server package and is typically implemented via a Viper - // literal defined on the server Context. Note, casting Get calls may not - // yield the expected types and could result in type assertion errors. It is - // recommended to either use the cast package or perform manual conversion for safety. - AppOptions interface { - Get(string) interface{} - } - // AppCreator is a function that allows us to lazily initialize an // application using various configurations. AppCreator func( diff --git a/cli/commands/server/types/chainspec.go b/cli/commands/server/types/chainspec.go new file mode 100644 index 0000000000..6f4f9544db --- /dev/null +++ b/cli/commands/server/types/chainspec.go @@ -0,0 +1,205 @@ +// SPDX-License-Identifier: BUSL-1.1 +// +// Copyright (C) 2025, Berachain Foundation. All rights reserved. +// Use of this software is governed by the Business Source License included +// in the LICENSE file of this repository and at www.mariadb.com/bsl11. +// +// ANY USE OF THE LICENSED WORK IN VIOLATION OF THIS LICENSE WILL AUTOMATICALLY +// TERMINATE YOUR RIGHTS UNDER THIS LICENSE FOR THE CURRENT AND ALL OTHER +// VERSIONS OF THE LICENSED WORK. +// +// THIS LICENSE DOES NOT GRANT YOU ANY RIGHT IN ANY TRADEMARK OR LOGO OF +// LICENSOR OR ITS AFFILIATES (PROVIDED THAT YOU MAY USE A TRADEMARK OR LOGO OF +// LICENSOR AS EXPRESSLY REQUIRED BY THIS LICENSE). +// +// TO THE EXTENT PERMITTED BY APPLICABLE LAW, THE LICENSED WORK IS PROVIDED ON +// AN “AS IS” BASIS. LICENSOR HEREBY DISCLAIMS ALL WARRANTIES AND CONDITIONS, +// EXPRESS OR IMPLIED, INCLUDING (WITHOUT LIMITATION) WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, NON-INFRINGEMENT, AND +// TITLE. + +package types + +import ( + "errors" + "fmt" + "os" + "reflect" + + "github.com/berachain/beacon-kit/chain" + "github.com/berachain/beacon-kit/config/spec" + "github.com/berachain/beacon-kit/primitives/bytes" + "github.com/berachain/beacon-kit/primitives/common" + "github.com/mitchellh/mapstructure" + "github.com/spf13/cast" + "github.com/spf13/viper" +) + +const ( + ChainSpecTypeEnvVar = "CHAIN_SPEC" + DevnetChainSpecType = "devnet" + MainnetChainSpecType = "mainnet" + TestnetChainSpecType = "testnet" + ConfigurableChainSpecType = "configurable" + FlagConfigurableChainSpecPath = "spec" +) + +// ChainSpecCreator is a function that allows us to lazily initialize the ChainSpec +type ChainSpecCreator func(AppOptions) (chain.Spec, error) + +func CreateChainSpec(appOpts AppOptions) (chain.Spec, error) { + var ( + chainSpec chain.Spec + err error + ) + switch os.Getenv(ChainSpecTypeEnvVar) { + case ConfigurableChainSpecType: + chainSpec, err = handleConfigurableChainSpec(appOpts) + case DevnetChainSpecType: + chainSpec, err = spec.DevnetChainSpec() + case TestnetChainSpecType: + chainSpec, err = spec.TestnetChainSpec() + case MainnetChainSpecType: + chainSpec, err = spec.MainnetChainSpec() + default: + chainSpec, err = spec.MainnetChainSpec() + } + if err != nil { + return nil, err + } + if chainSpec == nil { + return nil, errors.New("no chain spec found") + } + return chainSpec, nil +} + +func handleConfigurableChainSpec(appOpts AppOptions) (chain.Spec, error) { + specPath := cast.ToString(appOpts.Get(FlagConfigurableChainSpecPath)) + if specPath == "" { + return nil, fmt.Errorf("expected flag '%s' for chain spec", FlagConfigurableChainSpecPath) + } + specData, err := loadSpecData(specPath) + if err != nil { + return nil, err + } + return chain.NewSpec(specData) +} + +// loadSpecData reads the YAML configuration file from the given path using Viper, +// unmarshals it into a SpecData, and then validates that all required fields are set. +func loadSpecData(path string) (*chain.SpecData, error) { + v := viper.New() + v.SetConfigFile(path) + + // Tell Viper we're using toml. + v.SetConfigType("toml") + + if err := v.ReadInConfig(); err != nil { + return nil, fmt.Errorf("failed to read config: %w", err) + } + + // List of required keys as defined by your mapstructure tags. + requiredKeys := []string{ + "max-effective-balance", + "ejection-balance", + "effective-balance-increment", + "hysteresis-quotient", + "hysteresis-downward-multiplier", + "hysteresis-upward-multiplier", + "slots-per-epoch", + "slots-per-historical-root", + "min-epochs-to-inactivity-penalty", + "domain-type-beacon-proposer", + "domain-type-beacon-attester", + "domain-type-randao", + "domain-type-deposit", + "domain-type-voluntary-exit", + "domain-type-selection-proof", + "domain-type-aggregate-and-proof", + "domain-type-application-mask", + "deposit-contract-address", + "max-deposits-per-block", + "deposit-eth1-chain-id", + "eth1-follow-distance", + "target-seconds-per-eth1-block", + "deneb-one-fork-epoch", + "electra-fork-epoch", + "epochs-per-historical-vector", + "epochs-per-slashings-vector", + "historical-roots-limit", + "validator-registry-limit", + "max-withdrawals-per-payload", + "max-validators-per-withdrawals-sweep", + "min-epochs-for-blobs-sidecars-request", + "max-blob-commitments-per-block", + "max-blobs-per-block", + "field-elements-per-blob", + "bytes-per-blob", + "kzg-commitment-inclusion-proof-depth", + "validator-set-cap", + "evm-inflation-address", + "evm-inflation-per-block", + "evm-inflation-address-deneb-one", + "evm-inflation-per-block-deneb-one", + } + + // Check if all required keys are set in the config. + for _, key := range requiredKeys { + if !v.IsSet(key) { + return nil, fmt.Errorf("missing required configuration key: %s", key) + } + } + + var specData chain.SpecData + + // Define a decode hook to convert hex string to ExecutionAddress. + decodeHookFunc := mapstructure.ComposeDecodeHookFunc(simpleDecodeHook) + if err := v.Unmarshal(&specData, viper.DecodeHook(decodeHookFunc)); err != nil { + return nil, fmt.Errorf("failed to unmarshal config into SpecData: %w", err) + } + + return &specData, nil +} + +// simpleDecodeHook is a decode hook that does two things: +// 1. Converts a string into a common.ExecutionAddress (when target type is ExecutionAddress). +// 2. Converts numeric values into a [4]byte value using bytes.FromUint32. +// Only numeric values are allowed for the domain types. +func simpleDecodeHook( + f reflect.Type, + t reflect.Type, + data interface{}, +) (interface{}, error) { + // Convert string to ExecutionAddress. + if f.Kind() == reflect.String && t == reflect.TypeOf(common.ExecutionAddress{}) { + s, ok := data.(string) + if !ok { + return nil, fmt.Errorf("expected string for ExecutionAddress but got %T", data) + } + // Assume NewExecutionAddressFromHex returns (common.ExecutionAddress, error) + addr := common.NewExecutionAddressFromHex(s) + return addr, nil + } + + // Convert numeric values to a 4-byte domain type (common.DomainType is an alias for [4]byte). + if t == reflect.TypeOf(bytes.B4{}) { + var num uint64 + switch v := data.(type) { + case int: + num = uint64(v) // #nosec G115: Conversion is not safe but is a trusted config file. + case int64: + num = uint64(v) // #nosec G115: Conversion is not safe but is a trusted config file. + case uint64: + num = v + case float64: + num = uint64(v) + default: + return nil, fmt.Errorf("expected numeric value for [4]byte conversion, got %T", data) + } + // Use FromUint32 to convert the number to a little-endian [4]byte. + // #nosec G115: Conversion is not safe but is a trusted config file. + return bytes.FromUint32(uint32(num)), nil + } + + return data, nil +} diff --git a/cli/commands/server/types/chainspec_test.go b/cli/commands/server/types/chainspec_test.go new file mode 100644 index 0000000000..b85122dc64 --- /dev/null +++ b/cli/commands/server/types/chainspec_test.go @@ -0,0 +1,106 @@ +// SPDX-License-Identifier: BUSL-1.1 +// +// Copyright (C) 2025, Berachain Foundation. All rights reserved. +// Use of this software is governed by the Business Source License included +// in the LICENSE file of this repository and at www.mariadb.com/bsl11. +// +// ANY USE OF THE LICENSED WORK IN VIOLATION OF THIS LICENSE WILL AUTOMATICALLY +// TERMINATE YOUR RIGHTS UNDER THIS LICENSE FOR THE CURRENT AND ALL OTHER +// VERSIONS OF THE LICENSED WORK. +// +// THIS LICENSE DOES NOT GRANT YOU ANY RIGHT IN ANY TRADEMARK OR LOGO OF +// LICENSOR OR ITS AFFILIATES (PROVIDED THAT YOU MAY USE A TRADEMARK OR LOGO OF +// LICENSOR AS EXPRESSLY REQUIRED BY THIS LICENSE). +// +// TO THE EXTENT PERMITTED BY APPLICABLE LAW, THE LICENSED WORK IS PROVIDED ON +// AN “AS IS” BASIS. LICENSOR HEREBY DISCLAIMS ALL WARRANTIES AND CONDITIONS, +// EXPRESS OR IMPLIED, INCLUDING (WITHOUT LIMITATION) WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, NON-INFRINGEMENT, AND +// TITLE. + +package types_test + +import ( + "os" + "testing" + + "github.com/berachain/beacon-kit/cli/commands/server/types" + "github.com/berachain/beacon-kit/config/spec" + "github.com/stretchr/testify/require" +) + +// dummyAppOptions is a simple implementation of the AppOptions interface for testing. +type dummyAppOptions struct { + values map[string]interface{} +} + +func (d dummyAppOptions) Get(key string) interface{} { + return d.values[key] +} + +func TestCreateChainSpec_Devnet(t *testing.T) { + // Set the env variable to force the devnet branch. + t.Setenv(types.ChainSpecTypeEnvVar, types.DevnetChainSpecType) + opts := dummyAppOptions{values: map[string]interface{}{}} + cs, err := types.CreateChainSpec(opts) + require.NoError(t, err) + require.NotNil(t, cs) + devnetSpec, err := spec.DevnetChainSpec() + require.NoError(t, err) + require.Equal(t, cs, devnetSpec, "expected devnet chain spec to match") +} + +func TestCreateChainSpec_Testnet(t *testing.T) { + // Set the env variable to force the testnet branch. + t.Setenv(types.ChainSpecTypeEnvVar, types.TestnetChainSpecType) + opts := dummyAppOptions{values: map[string]interface{}{}} + cs, err := types.CreateChainSpec(opts) + require.NoError(t, err) + require.NotNil(t, cs) + testnetSpec, err := spec.TestnetChainSpec() + require.NoError(t, err) + require.Equal(t, cs, testnetSpec, "expected testnet chain spec to match") +} + +func TestCreateChainSpec_Mainnet(t *testing.T) { + // Set the env variable to force the mainnet branch. + t.Setenv(types.ChainSpecTypeEnvVar, types.MainnetChainSpecType) + opts := dummyAppOptions{values: map[string]interface{}{}} + cs, err := types.CreateChainSpec(opts) + require.NoError(t, err) + require.NotNil(t, cs) + mainnetSpec, err := spec.MainnetChainSpec() + require.NoError(t, err) + require.Equal(t, cs, mainnetSpec, "expected mainnet chain spec to match") +} + +//nolint:paralleltest // uses envars +func TestCreateChainSpec_Default_NoSpecFlag(t *testing.T) { + // Ensure the env variable is unset so that the default branch is taken. + err := os.Unsetenv(types.ChainSpecTypeEnvVar) + require.NoError(t, err) + // Provide an empty AppOptions so that no spec flag is present. + opts := dummyAppOptions{values: map[string]interface{}{}} + cs, err := types.CreateChainSpec(opts) + require.NoError(t, err) + mainnetSpec, err := spec.MainnetChainSpec() + require.NoError(t, err) + require.Equal(t, cs, mainnetSpec, "expected mainnet chain spec to match") +} + +//nolint:paralleltest // uses envars +func TestCreateChainSpec_ConfigurableEnvar_WithSpecFlag(t *testing.T) { + // Ensure the env variable is unset so that the default branch is taken. + err := os.Unsetenv(types.ChainSpecTypeEnvVar) + require.NoError(t, err) + // Provide a non-empty value for the configurable spec flag. + opts := dummyAppOptions{values: map[string]interface{}{ + types.FlagConfigurableChainSpecPath: "mainnet_spec.toml", + }} + cs, err := types.CreateChainSpec(opts) + require.NoError(t, err) + + mainnetSpec, err := spec.MainnetChainSpec() + require.NoError(t, err) + require.Equal(t, cs, mainnetSpec, "the chain spec loaded from TOML does not match the mainnet spec") +} diff --git a/cli/commands/server/types/interfaces.go b/cli/commands/server/types/interfaces.go new file mode 100644 index 0000000000..86bf41e6d7 --- /dev/null +++ b/cli/commands/server/types/interfaces.go @@ -0,0 +1,33 @@ +// SPDX-License-Identifier: BUSL-1.1 +// +// Copyright (C) 2025, Berachain Foundation. All rights reserved. +// Use of this software is governed by the Business Source License included +// in the LICENSE file of this repository and at www.mariadb.com/bsl11. +// +// ANY USE OF THE LICENSED WORK IN VIOLATION OF THIS LICENSE WILL AUTOMATICALLY +// TERMINATE YOUR RIGHTS UNDER THIS LICENSE FOR THE CURRENT AND ALL OTHER +// VERSIONS OF THE LICENSED WORK. +// +// THIS LICENSE DOES NOT GRANT YOU ANY RIGHT IN ANY TRADEMARK OR LOGO OF +// LICENSOR OR ITS AFFILIATES (PROVIDED THAT YOU MAY USE A TRADEMARK OR LOGO OF +// LICENSOR AS EXPRESSLY REQUIRED BY THIS LICENSE). +// +// TO THE EXTENT PERMITTED BY APPLICABLE LAW, THE LICENSED WORK IS PROVIDED ON +// AN “AS IS” BASIS. LICENSOR HEREBY DISCLAIMS ALL WARRANTIES AND CONDITIONS, +// EXPRESS OR IMPLIED, INCLUDING (WITHOUT LIMITATION) WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, NON-INFRINGEMENT, AND +// TITLE. + +package types + +// AppOptions defines an interface that is passed into an application +// constructor, typically used to set BaseApp options that are either +// supplied via config file or through CLI arguments/flags. The underlying +// implementation +// is defined by the server package and is typically implemented via a Viper +// literal defined on the server Context. Note, casting Get calls may not +// yield the expected types and could result in type assertion errors. It is +// recommended to either use the cast package or perform manual conversion for safety. +type AppOptions interface { + Get(string) interface{} +} diff --git a/cli/commands/server/types/mainnet_spec.toml b/cli/commands/server/types/mainnet_spec.toml new file mode 100644 index 0000000000..c2c48149be --- /dev/null +++ b/cli/commands/server/types/mainnet_spec.toml @@ -0,0 +1,69 @@ +# Mainnet Chain Spec Configuration + +# Gwei value constants +max-effective-balance = 10000000000000000 +ejection-balance = 240000000000000 +effective-balance-increment = 10000000000000 + +# Hysteresis parameters +hysteresis-quotient = 4 +hysteresis-downward-multiplier = 1 +hysteresis-upward-multiplier = 5 + +# Time parameters +slots-per-epoch = 192 +slots-per-historical-root = 8 +min-epochs-to-inactivity-penalty = 4 + +# Signature domains (each will be interpreted as a numeric value) +domain-type-beacon-proposer = 0 +domain-type-beacon-attester = 1 +domain-type-randao = 2 +domain-type-deposit = 3 +domain-type-voluntary-exit = 4 +domain-type-selection-proof = 5 +domain-type-aggregate-and-proof = 6 +domain-type-application-mask = 16777216 + + +# Eth1-related values +deposit-contract-address = "0x4242424242424242424242424242424242424242" +max-deposits-per-block = 16 +deposit-eth1-chain-id = 80094 +eth1-follow-distance = 1 +target-seconds-per-eth1-block = 2 + +# Fork-related values +deneb-one-fork-epoch = 2855 +electra-fork-epoch = 9999999999999999 + +# State list lengths +epochs-per-historical-vector = 8 +epochs-per-slashings-vector = 8 +historical-roots-limit = 8 +validator-registry-limit = 1099511627776 + +# Rewards and penalties constants +#inactivity-penalty-quotient = 33554432 +#proportional-slashing-multiplier = 2 + +# Capella values +max-withdrawals-per-payload = 16 +max-validators-per-withdrawals-sweep = 31 + +# Deneb values +min-epochs-for-blobs-sidecars-request = 4096 +max-blob-commitments-per-block = 4096 +max-blobs-per-block = 6 +field-elements-per-blob = 4096 +bytes-per-blob = 131072 +kzg-commitment-inclusion-proof-depth = 17 + +# Berachain genesis values +validator-set-cap = 69 +evm-inflation-address = "0x0000000000000000000000000000000000000000" +evm-inflation-per-block = 0 + +# Deneb1 value changes +evm-inflation-address-deneb-one = "0x656b95E550C07a9ffe548bd4085c72418Ceb1dba" +evm-inflation-per-block-deneb-one = 5750000000 diff --git a/cli/commands/setup.go b/cli/commands/setup.go index 7928183104..c7e7d65e17 100644 --- a/cli/commands/setup.go +++ b/cli/commands/setup.go @@ -21,7 +21,6 @@ package commands import ( - "github.com/berachain/beacon-kit/chain" "github.com/berachain/beacon-kit/cli/commands/deposit" "github.com/berachain/beacon-kit/cli/commands/genesis" "github.com/berachain/beacon-kit/cli/commands/initialize" @@ -36,7 +35,10 @@ import ( // DefaultRootCommandSetup sets up the default commands for the root command. func DefaultRootCommandSetup( - root *Root, mm *cometbft.Service, appCreator servertypes.AppCreator, chainSpec chain.Spec, + root *Root, + mm *cometbft.Service, + appCreator servertypes.AppCreator, + chainSpecCreator servertypes.ChainSpecCreator, ) { // Add all the commands to the root command. root.cmd.AddCommand( @@ -45,9 +47,9 @@ func DefaultRootCommandSetup( // `init` initialize.InitCmd(mm), // `genesis` - genesis.Commands(chainSpec), + genesis.Commands(chainSpecCreator), // `deposit` - deposit.Commands(chainSpec, appCreator), + deposit.Commands(chainSpecCreator, appCreator), // `jwt` jwt.Commands(), // `rollback` diff --git a/cmd/beacond/defaults.go b/cmd/beacond/defaults.go index a2e64d7b8c..774a811cbc 100644 --- a/cmd/beacond/defaults.go +++ b/cmd/beacond/defaults.go @@ -35,7 +35,6 @@ func DefaultComponents() []any { components.ProvideBlobProofVerifier, components.ProvideChainService, components.ProvideNode, - components.ProvideChainSpec, components.ProvideConfig, components.ProvideServerConfig, components.ProvideDepositStore, diff --git a/cmd/beacond/main.go b/cmd/beacond/main.go index a18235a83f..96ec4baed4 100644 --- a/cmd/beacond/main.go +++ b/cmd/beacond/main.go @@ -25,15 +25,12 @@ import ( "os" clibuilder "github.com/berachain/beacon-kit/cli/builder" + "github.com/berachain/beacon-kit/cli/commands/server/types" clicomponents "github.com/berachain/beacon-kit/cli/components" nodebuilder "github.com/berachain/beacon-kit/node-core/builder" - nodecomponents "github.com/berachain/beacon-kit/node-core/components" - nodetypes "github.com/berachain/beacon-kit/node-core/types" "go.uber.org/automaxprocs/maxprocs" ) -type Node = nodetypes.Node - // run runs the beacon node. func run() error { // Set the uber max procs @@ -61,15 +58,11 @@ func run() error { ), // Set the Runtime Components to the Default. clibuilder.WithComponents( - append( - clicomponents.DefaultClientComponents(), - // TODO: remove these, and eventually pull cfg and chainspec - // from built node - nodecomponents.ProvideChainSpec, - ), + clicomponents.DefaultClientComponents(), ), // Set the NodeBuilderFunc to the NodeBuilder Build. clibuilder.WithNodeBuilderFunc(nb.Build), + clibuilder.WithChainSpecBuilderFunc(types.CreateChainSpec), ) cmd, err := cb.Build() diff --git a/config/spec/defaults.go b/config/spec/defaults.go index 5e9de70b42..ada790254c 100644 --- a/config/spec/defaults.go +++ b/config/spec/defaults.go @@ -57,8 +57,7 @@ const ( defaultTargetSecondsPerEth1Block = 2 // Berachain specific. // Fork-related values. - defaultDeneb1ForkEpoch = 9999999999999998 // Set as a future epoch as not yet determined. - defaultElectraForkEpoch = 9999999999999999 // Set as a future epoch as not yet determined. + defaultElectraForkTime = 9999999999999999 // Set as a future timestamp as not yet determined. // State list length constants. defaultEpochsPerHistoricalVector = 8 diff --git a/config/spec/devnet.go b/config/spec/devnet.go index 2a09031434..9a34616864 100644 --- a/config/spec/devnet.go +++ b/config/spec/devnet.go @@ -38,8 +38,18 @@ const ( // of Gwei) that can be staked. devnetMaxStakeAmount = 4000 * params.GWei - // devnetDeneb1ForkEpoch is the epoch at which the Deneb1 fork occurs. - devnetDeneb1ForkEpoch = 1 + // devnetSlotsPerEpoch is the number of slots in an epoch. This is set to + // keep consistency with usage in the devnet fork time calculations. + devnetSlotsPerEpoch = defaultSlotsPerEpoch + + // devnetDeneb1ForkTime is the timestamp at which the Deneb1 fork occurs. + // Devnet time begins at 0 and increments deterministically by + // TargetSecondsPerEth1Block every block. A fork time of 64 is set for the + // fork to occur at exactly the first epoch. + devnetDeneb1ForkTime = 1 * devnetSlotsPerEpoch * defaultTargetSecondsPerEth1Block + + // devnetElectraForkTime is the timestamp at which the Electra fork occurs. + devnetElectraForkTime = defaultElectraForkTime // devnetEVMInflationAddressDeneb1 is the address of the EVM inflation contract // after the Deneb1 fork. @@ -58,8 +68,9 @@ func DevnetChainSpecData() *chain.SpecData { specData := MainnetChainSpecData() specData.DepositEth1ChainID = DevnetEth1ChainID - // Deneb1 fork takes place at epoch 1. - specData.Deneb1ForkEpoch = devnetDeneb1ForkEpoch + // Fork timings are set to facilitate local testing across fork versions. + specData.Deneb1ForkTime = devnetDeneb1ForkTime + specData.ElectraForkTime = devnetElectraForkTime // EVM inflation is different from mainnet to test. specData.EVMInflationAddressGenesis = common.NewExecutionAddressFromHex(devnetEVMInflationAddress) @@ -73,7 +84,7 @@ func DevnetChainSpecData() *chain.SpecData { specData.MaxEffectiveBalance = devnetMaxStakeAmount specData.EjectionBalance = defaultEjectionBalance specData.EffectiveBalanceIncrement = defaultEffectiveBalanceIncrement - specData.SlotsPerEpoch = defaultSlotsPerEpoch + specData.SlotsPerEpoch = devnetSlotsPerEpoch return specData } diff --git a/config/spec/mainnet.go b/config/spec/mainnet.go index 8f61b12d09..f7995b1465 100644 --- a/config/spec/mainnet.go +++ b/config/spec/mainnet.go @@ -73,8 +73,10 @@ const ( // default deposit contract address. mainnetDepositContractAddress = defaultDepositContractAddress - // mainnetDeneb1ForkEpoch is the epoch at which the Deneb1 fork occurs. - mainnetDeneb1ForkEpoch = 2855 + // mainnetDeneb1ForkTime is the timestamp at which the Deneb1 fork occurs. + // This is calculated based on the timestamp of the 2855th mainnet epoch, block 548160, which + // was used to initiate the fork when beacon-kit forked by epoch instead of by timestamp. + mainnetDeneb1ForkTime = 1738415507 // mainnetEVMInflationAddressDeneb1 is the address on the EVM which will receive the // inflation amount of native EVM balance through a withdrawal every block in the Deneb1 fork. @@ -120,8 +122,8 @@ func MainnetChainSpecData() *chain.SpecData { TargetSecondsPerEth1Block: defaultTargetSecondsPerEth1Block, // Fork-related values. - Deneb1ForkEpoch: mainnetDeneb1ForkEpoch, - ElectraForkEpoch: defaultElectraForkEpoch, + Deneb1ForkTime: mainnetDeneb1ForkTime, + ElectraForkTime: defaultElectraForkTime, // State list length constants. EpochsPerHistoricalVector: defaultEpochsPerHistoricalVector, diff --git a/config/spec/testnet.go b/config/spec/testnet.go index dca9574a76..cf1fd8ab0a 100644 --- a/config/spec/testnet.go +++ b/config/spec/testnet.go @@ -29,9 +29,10 @@ func TestnetChainSpecData() *chain.SpecData { // Testnet uses chain ID of 80069. specData.DepositEth1ChainID = TestnetEth1ChainID - // Genesis values of EVM inflation are consistent with those of mainnet. - // Testnet activates Deneb1 for BERA minting at epoch 1. - specData.Deneb1ForkEpoch = 1 + // Deneb1 fork timing on Bepolia. This is calculated based on the timestamp of the first bepolia + // epoch, block 192, which was used to initiate the fork when beacon-kit forked by epoch instead + // of by timestamp. + specData.Deneb1ForkTime = 1740090694 return specData } diff --git a/consensus-types/types/payload_requests_test.go b/consensus-types/types/payload_requests_test.go index 72c2cc9375..cc860a774c 100644 --- a/consensus-types/types/payload_requests_test.go +++ b/consensus-types/types/payload_requests_test.go @@ -28,6 +28,7 @@ import ( engineprimitives "github.com/berachain/beacon-kit/engine-primitives/engine-primitives" "github.com/berachain/beacon-kit/primitives/common" "github.com/berachain/beacon-kit/primitives/eip4844" + "github.com/berachain/beacon-kit/primitives/math" "github.com/berachain/beacon-kit/primitives/version" "github.com/berachain/beacon-kit/testing/utils" "github.com/stretchr/testify/require" @@ -66,7 +67,7 @@ func TestBuildForkchoiceUpdateRequest(t *testing.T) { ) payloadAttributes, err := engineprimitives.NewPayloadAttributes( forkVersion, - uint64(time.Now().Truncate(time.Second).Unix()), + math.U64(time.Now().Truncate(time.Second).Unix()), common.Bytes32{0x01}, common.ExecutionAddress{}, engineprimitives.Withdrawals{}, diff --git a/consensus/types/common.go b/consensus/types/common.go index bb257f8705..ec3d473b0f 100644 --- a/consensus/types/common.go +++ b/consensus/types/common.go @@ -41,3 +41,8 @@ func (c *commonConsensusData) GetProposerAddress() []byte { func (c *commonConsensusData) GetConsensusTime() math.U64 { return c.consensusTime } + +// SetConsensusTime sets the consensusTime. +func (c *commonConsensusData) SetConsensusTime(consensusTime math.U64) { + c.consensusTime = consensusTime +} diff --git a/consensus/types/consensus_block.go b/consensus/types/consensus_block.go index f76fee008f..f84ffc81b5 100644 --- a/consensus/types/consensus_block.go +++ b/consensus/types/consensus_block.go @@ -21,8 +21,6 @@ package types import ( - "time" - "github.com/berachain/beacon-kit/consensus-types/types" "github.com/berachain/beacon-kit/primitives/math" ) @@ -34,17 +32,17 @@ type ConsensusBlock struct { *commonConsensusData } -// New creates a new ConsensusBlock instance. +// NewConsensusBlock creates a new ConsensusBlock instance. func NewConsensusBlock( beaconBlock *types.BeaconBlock, proposerAddress []byte, - consensusTime time.Time, + consensusTime math.U64, ) *ConsensusBlock { return &ConsensusBlock{ blk: beaconBlock, commonConsensusData: &commonConsensusData{ proposerAddress: proposerAddress, - consensusTime: math.U64(consensusTime.Unix()), // #nosec G115 + consensusTime: consensusTime, }, } } diff --git a/engine-primitives/engine-primitives/attributes.go b/engine-primitives/engine-primitives/attributes.go index 0ead2d43ce..2dbc58fbaf 100644 --- a/engine-primitives/engine-primitives/attributes.go +++ b/engine-primitives/engine-primitives/attributes.go @@ -53,14 +53,14 @@ type PayloadAttributes struct { // NewPayloadAttributes creates a new empty PayloadAttributes. func NewPayloadAttributes( forkVersion common.Version, - timestamp uint64, + timestamp math.U64, prevRandao common.Bytes32, suggestedFeeRecipient common.ExecutionAddress, withdrawals Withdrawals, parentBeaconBlockRoot common.Root, ) (*PayloadAttributes, error) { pa := &PayloadAttributes{ - Timestamp: math.U64(timestamp), + Timestamp: timestamp, PrevRandao: prevRandao, SuggestedFeeRecipient: suggestedFeeRecipient, Withdrawals: withdrawals, diff --git a/engine-primitives/engine-primitives/attributes_test.go b/engine-primitives/engine-primitives/attributes_test.go index 00d53226e4..374a046a20 100644 --- a/engine-primitives/engine-primitives/attributes_test.go +++ b/engine-primitives/engine-primitives/attributes_test.go @@ -25,13 +25,14 @@ import ( engineprimitives "github.com/berachain/beacon-kit/engine-primitives/engine-primitives" "github.com/berachain/beacon-kit/primitives/common" + "github.com/berachain/beacon-kit/primitives/math" "github.com/berachain/beacon-kit/primitives/version" "github.com/stretchr/testify/require" ) type payloadAttributesInput struct { forkVersion common.Version - timestamp uint64 + timestamp math.U64 prevRandao common.Bytes32 suggestedFeeRecipient common.ExecutionAddress withdrawals engineprimitives.Withdrawals @@ -43,7 +44,7 @@ func TestPayloadAttributes(t *testing.T) { // default valid data validInput := payloadAttributesInput{ forkVersion: version.Altair(), - timestamp: uint64(123456789), + timestamp: math.U64(123456789), prevRandao: common.Bytes32{1, 2, 3}, suggestedFeeRecipient: common.ExecutionAddress{}, withdrawals: engineprimitives.Withdrawals{}, diff --git a/node-core/builder/builder.go b/node-core/builder/builder.go index f824754135..0b20f2c0d5 100644 --- a/node-core/builder/builder.go +++ b/node-core/builder/builder.go @@ -71,8 +71,13 @@ func (nb *NodeBuilder) Build( config *config.Config ) + chainSpec, err := servertypes.CreateChainSpec(appOpts) + if err != nil { + panic(err) + } + // build all node components using depinject - if err := depinject.Inject( + if err = depinject.Inject( depinject.Configs( depinject.Provide( nb.components..., @@ -82,6 +87,7 @@ func (nb *NodeBuilder) Build( logger, db, cmtCfg, + chainSpec, ), ), &apiBackend, diff --git a/node-core/components/chain_spec.go b/node-core/components/chain_spec.go deleted file mode 100644 index 3417634174..0000000000 --- a/node-core/components/chain_spec.go +++ /dev/null @@ -1,64 +0,0 @@ -// SPDX-License-Identifier: BUSL-1.1 -// -// Copyright (C) 2025, Berachain Foundation. All rights reserved. -// Use of this software is governed by the Business Source License included -// in the LICENSE file of this repository and at www.mariadb.com/bsl11. -// -// ANY USE OF THE LICENSED WORK IN VIOLATION OF THIS LICENSE WILL AUTOMATICALLY -// TERMINATE YOUR RIGHTS UNDER THIS LICENSE FOR THE CURRENT AND ALL OTHER -// VERSIONS OF THE LICENSED WORK. -// -// THIS LICENSE DOES NOT GRANT YOU ANY RIGHT IN ANY TRADEMARK OR LOGO OF -// LICENSOR OR ITS AFFILIATES (PROVIDED THAT YOU MAY USE A TRADEMARK OR LOGO OF -// LICENSOR AS EXPRESSLY REQUIRED BY THIS LICENSE). -// -// TO THE EXTENT PERMITTED BY APPLICABLE LAW, THE LICENSED WORK IS PROVIDED ON -// AN “AS IS” BASIS. LICENSOR HEREBY DISCLAIMS ALL WARRANTIES AND CONDITIONS, -// EXPRESS OR IMPLIED, INCLUDING (WITHOUT LIMITATION) WARRANTIES OF -// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, NON-INFRINGEMENT, AND -// TITLE. - -package components - -import ( - "os" - - "github.com/berachain/beacon-kit/chain" - "github.com/berachain/beacon-kit/config/spec" -) - -const ( - ChainSpecTypeEnvVar = "CHAIN_SPEC" - DevnetChainSpecType = "devnet" - MainnetChainSpecType = "mainnet" - TestnetChainSpecType = "testnet" -) - -// ProvideChainSpec provides the chain spec based on the environment variable. -// Defaults to use Mainnet if no valid chain spec environment variable is set. -func ProvideChainSpec() (chain.Spec, error) { - var ( - chainSpec chain.Spec - err error - ) - - // TODO: replace reading env var with config value. - switch os.Getenv(ChainSpecTypeEnvVar) { - case DevnetChainSpecType: - chainSpec, err = spec.DevnetChainSpec() - case TestnetChainSpecType: - chainSpec, err = spec.TestnetChainSpec() - case MainnetChainSpecType: - fallthrough - default: - chainSpec, err = spec.MainnetChainSpec() - } - - if err != nil { - return nil, err - } - if chainSpec == nil { - panic("chain spec is nil") - } - return chainSpec, nil -} diff --git a/node-core/components/interfaces.go b/node-core/components/interfaces.go index c4b110c37f..e770125b7f 100644 --- a/node-core/components/interfaces.go +++ b/node-core/components/interfaces.go @@ -49,7 +49,7 @@ type ( BuildPayloadAttributes( st *statedb.StateDB, slot math.Slot, - timestamp uint64, + timestamp math.U64, prevHeadRoot [32]byte, ) (*engineprimitives.PayloadAttributes, error) } @@ -81,11 +81,11 @@ type ( ctx context.Context, st *statedb.StateDB, slot math.Slot, - timestamp uint64, + timestamp math.U64, parentBlockRoot common.Root, headEth1BlockHash common.ExecutionHash, finalEth1BlockHash common.ExecutionHash, - ) (*engineprimitives.PayloadID, error) + ) (*engineprimitives.PayloadID, common.Version, error) // RetrievePayload retrieves the payload for the given slot. RetrievePayload( ctx context.Context, @@ -98,7 +98,7 @@ type ( ctx context.Context, st *statedb.StateDB, slot math.Slot, - timestamp uint64, + timestamp math.U64, parentBlockRoot common.Root, headEth1BlockHash common.ExecutionHash, finalEth1BlockHash common.ExecutionHash, diff --git a/payload/attributes/factory.go b/payload/attributes/factory.go index a0f136a403..9040c43b66 100644 --- a/payload/attributes/factory.go +++ b/payload/attributes/factory.go @@ -56,7 +56,7 @@ func NewAttributesFactory( func (f *Factory) BuildPayloadAttributes( st *statedb.StateDB, slot math.Slot, - timestamp uint64, + timestamp math.U64, prevHeadRoot [32]byte, ) (*engineprimitives.PayloadAttributes, error) { var ( @@ -66,7 +66,7 @@ func (f *Factory) BuildPayloadAttributes( ) // Get the expected withdrawals to include in this payload. - withdrawals, err := st.ExpectedWithdrawals() + withdrawals, err := st.ExpectedWithdrawals(timestamp) if err != nil { f.logger.Error( "Could not get expected withdrawals to get payload attribute", @@ -84,7 +84,7 @@ func (f *Factory) BuildPayloadAttributes( } return engineprimitives.NewPayloadAttributes( - f.chainSpec.ActiveForkVersionForEpoch(epoch), + f.chainSpec.ActiveForkVersionForTimestamp(timestamp), timestamp, prevRandao, f.suggestedFeeRecipient, diff --git a/payload/attributes/interfaces.go b/payload/attributes/interfaces.go index 141fe1f8e2..f0bec61418 100644 --- a/payload/attributes/interfaces.go +++ b/payload/attributes/interfaces.go @@ -26,7 +26,7 @@ import ( ) type ChainSpec interface { - ActiveForkVersionForEpoch(epoch math.Epoch) common.Version + ActiveForkVersionForTimestamp(timestamp math.U64) common.Version EpochsPerHistoricalVector() uint64 SlotToEpoch(slot math.Slot) math.Epoch } diff --git a/payload/builder/interfaces.go b/payload/builder/interfaces.go index e469602d88..bdfb0fd33d 100644 --- a/payload/builder/interfaces.go +++ b/payload/builder/interfaces.go @@ -25,14 +25,15 @@ import ( ctypes "github.com/berachain/beacon-kit/consensus-types/types" engineprimitives "github.com/berachain/beacon-kit/engine-primitives/engine-primitives" + "github.com/berachain/beacon-kit/payload/cache" "github.com/berachain/beacon-kit/primitives/common" "github.com/berachain/beacon-kit/primitives/math" statedb "github.com/berachain/beacon-kit/state-transition/core/state" ) type PayloadCache interface { - GetAndEvict(slot math.Slot, stateRoot common.Root) (engineprimitives.PayloadID, bool) - Set(slot math.Slot, stateRoot common.Root, pid engineprimitives.PayloadID) + GetAndEvict(slot math.Slot, stateRoot common.Root) (cache.PayloadIDCacheResult, bool) + Set(slot math.Slot, stateRoot common.Root, pid engineprimitives.PayloadID, version common.Version) } // AttributesFactory is the interface for the attributes factory. @@ -40,7 +41,7 @@ type AttributesFactory interface { BuildPayloadAttributes( st *statedb.StateDB, slot math.U64, - timestamp uint64, + timestamp math.U64, prevHeadRoot [32]byte, ) (*engineprimitives.PayloadAttributes, error) } @@ -61,5 +62,5 @@ type ExecutionEngine interface { } type ChainSpec interface { - ActiveForkVersionForSlot(slot math.Slot) common.Version + ActiveForkVersionForTimestamp(timestamp math.U64) common.Version } diff --git a/payload/builder/payload.go b/payload/builder/payload.go index fad2267024..e0ca0cf014 100644 --- a/payload/builder/payload.go +++ b/payload/builder/payload.go @@ -38,13 +38,13 @@ func (pb *PayloadBuilder) RequestPayloadAsync( ctx context.Context, st *statedb.StateDB, slot math.Slot, - timestamp uint64, + timestamp math.U64, parentBlockRoot common.Root, headEth1BlockHash common.ExecutionHash, finalEth1BlockHash common.ExecutionHash, -) (*engineprimitives.PayloadID, error) { +) (*engineprimitives.PayloadID, common.Version, error) { if !pb.Enabled() { - return nil, ErrPayloadBuilderDisabled + return nil, common.Version{}, ErrPayloadBuilderDisabled } if payloadID, found := pb.pc.GetAndEvict(slot, parentBlockRoot); found { @@ -53,7 +53,7 @@ func (pb *PayloadBuilder) RequestPayloadAsync( "for_slot", slot.Base10(), "parent_block_root", parentBlockRoot, ) - return &payloadID, nil + return &payloadID.PayloadID, payloadID.ForkVersion, nil } // Assemble the payload attributes. @@ -64,9 +64,10 @@ func (pb *PayloadBuilder) RequestPayloadAsync( parentBlockRoot, ) if err != nil { - return nil, err + return nil, common.Version{}, err } + forkVersion := pb.chainSpec.ActiveForkVersionForTimestamp(timestamp) // Submit the forkchoice update to the execution client. req := ctypes.BuildForkchoiceUpdateRequest( &engineprimitives.ForkchoiceStateV1{ @@ -75,19 +76,19 @@ func (pb *PayloadBuilder) RequestPayloadAsync( FinalizedBlockHash: finalEth1BlockHash, }, attrs, - pb.chainSpec.ActiveForkVersionForSlot(slot), + forkVersion, ) payloadID, err := pb.ee.NotifyForkchoiceUpdate(ctx, req) if err != nil { - return nil, fmt.Errorf("RequestPayloadAsync failed sending forkchoice update: %w", err) + return nil, common.Version{}, fmt.Errorf("RequestPayloadAsync failed sending forkchoice update: %w", err) } // Only add to cache if we received back a payload ID. if payloadID != nil { - pb.pc.Set(slot, parentBlockRoot, *payloadID) + pb.pc.Set(slot, parentBlockRoot, *payloadID, forkVersion) } - return payloadID, nil + return payloadID, forkVersion, nil } // RequestPayloadSync request a payload for the given slot and @@ -96,7 +97,7 @@ func (pb *PayloadBuilder) RequestPayloadSync( ctx context.Context, st *statedb.StateDB, slot math.Slot, - timestamp uint64, + timestamp math.U64, parentBlockRoot common.Root, parentEth1Hash common.ExecutionHash, finalBlockHash common.ExecutionHash, @@ -107,7 +108,7 @@ func (pb *PayloadBuilder) RequestPayloadSync( // Build the payload and wait for the execution client to // return the payload ID. - payloadID, err := pb.RequestPayloadAsync( + payloadID, forkVersion, err := pb.RequestPayloadAsync( ctx, st, slot, @@ -138,7 +139,7 @@ func (pb *PayloadBuilder) RequestPayloadSync( } // Get the payload from the execution client. - return pb.getPayload(ctx, *payloadID, slot) + return pb.getPayload(ctx, *payloadID, forkVersion) } // RetrievePayload attempts to pull a previously built payload @@ -162,7 +163,7 @@ func (pb *PayloadBuilder) RetrievePayload( } // Get the payload from the execution client. - envelope, err := pb.getPayload(ctx, payloadID, slot) + envelope, err := pb.getPayload(ctx, payloadID.PayloadID, payloadID.ForkVersion) if err != nil { return nil, err } @@ -197,13 +198,13 @@ func (pb *PayloadBuilder) RetrievePayload( func (pb *PayloadBuilder) getPayload( ctx context.Context, payloadID engineprimitives.PayloadID, - slot math.U64, + forkVersion common.Version, ) (ctypes.BuiltExecutionPayloadEnv, error) { envelope, err := pb.ee.GetPayload( ctx, &ctypes.GetPayloadRequest{ PayloadID: payloadID, - ForkVersion: pb.chainSpec.ActiveForkVersionForSlot(slot), + ForkVersion: forkVersion, }, ) if err != nil { diff --git a/payload/builder/payload_test.go b/payload/builder/payload_test.go index a68494217e..d784959ffb 100644 --- a/payload/builder/payload_test.go +++ b/payload/builder/payload_test.go @@ -33,6 +33,7 @@ import ( "github.com/berachain/beacon-kit/payload/cache" "github.com/berachain/beacon-kit/primitives/common" "github.com/berachain/beacon-kit/primitives/math" + "github.com/berachain/beacon-kit/primitives/version" statedb "github.com/berachain/beacon-kit/state-transition/core/state" "github.com/stretchr/testify/require" ) @@ -105,7 +106,7 @@ func TestRetrievePayloadSunnyPath(t *testing.T) { ) // set expectations - cache.Set(slot, parentBlockRoot, dummyPayloadID) + cache.Set(slot, parentBlockRoot, dummyPayloadID, version.Deneb()) ee.payloadEnvToReturn = expectedPayload // test and checks @@ -153,7 +154,7 @@ func TestRetrievePayloadNilWithdrawalsListRejected(t *testing.T) { ) // set expectations - cache.Set(slot, parentBlockRoot, dummyPayloadID) + cache.Set(slot, parentBlockRoot, dummyPayloadID, version.Deneb()) ee.payloadEnvToReturn = faultyPayload // test and checks @@ -186,7 +187,7 @@ type stubAttributesFactory struct{} func (ee *stubAttributesFactory) BuildPayloadAttributes( *statedb.StateDB, math.U64, - uint64, [32]byte, + math.U64, [32]byte, ) (*engineprimitives.PayloadAttributes, error) { return nil, errStubNotImplemented } diff --git a/payload/cache/payload_id.go b/payload/cache/payload_id.go index 4fab01d8c3..4b4cd6abe1 100644 --- a/payload/cache/payload_id.go +++ b/payload/cache/payload_id.go @@ -40,7 +40,7 @@ type PayloadIDCache struct { // mu protects access to the slotToBlockRootToPayloadID map. mu sync.RWMutex // slotToBlockRootToPayloadID is used for storing payload ID mappings - slotToBlockRootToPayloadID map[payloadIDCacheKey]engineprimitives.PayloadID + slotToBlockRootToPayloadID map[payloadIDCacheKey]PayloadIDCacheResult } // payloadIDCacheKey is the (slot, root) tuple that is used to access a @@ -50,13 +50,18 @@ type payloadIDCacheKey struct { root common.Root } +type PayloadIDCacheResult struct { + PayloadID engineprimitives.PayloadID + ForkVersion common.Version +} + // NewPayloadIDCache initializes and returns a new instance of PayloadIDCache. // It prepares the internal data structures for storing payload ID mappings. func NewPayloadIDCache() *PayloadIDCache { return &PayloadIDCache{ mu: sync.RWMutex{}, slotToBlockRootToPayloadID: make( - map[payloadIDCacheKey]engineprimitives.PayloadID, + map[payloadIDCacheKey]PayloadIDCacheResult, ), } } @@ -78,13 +83,13 @@ func (p *PayloadIDCache) Has( func (p *PayloadIDCache) GetAndEvict( slot math.Slot, blockRoot common.Root, -) (engineprimitives.PayloadID, bool) { +) (PayloadIDCacheResult, bool) { p.mu.Lock() defer p.mu.Unlock() key := payloadIDCacheKey{slot, blockRoot} pid, ok := p.slotToBlockRootToPayloadID[key] if !ok { - return engineprimitives.PayloadID{}, false + return PayloadIDCacheResult{}, false } // Successfully retrieved. Remove from cache. @@ -96,7 +101,8 @@ func (p *PayloadIDCache) GetAndEvict( // It also prunes entries in the cache that are older than the // historicalPayloadIDCacheSize limit. func (p *PayloadIDCache) Set( - slot math.Slot, blockRoot common.Root, pid engineprimitives.PayloadID, + slot math.Slot, blockRoot common.Root, + pid engineprimitives.PayloadID, version common.Version, ) { p.mu.Lock() defer p.mu.Unlock() @@ -107,7 +113,10 @@ func (p *PayloadIDCache) Set( } // Update the cache with the new payload ID. - p.slotToBlockRootToPayloadID[payloadIDCacheKey{slot, blockRoot}] = pid + p.slotToBlockRootToPayloadID[payloadIDCacheKey{slot, blockRoot}] = PayloadIDCacheResult{ + PayloadID: pid, + ForkVersion: version, + } } // prunePrior removes payload IDs from the cache for slots less than diff --git a/payload/cache/payload_id_fuzz_test.go b/payload/cache/payload_id_fuzz_test.go index 333d2e587f..05224efd2c 100644 --- a/payload/cache/payload_id_fuzz_test.go +++ b/payload/cache/payload_id_fuzz_test.go @@ -28,6 +28,7 @@ import ( engineprimitives "github.com/berachain/beacon-kit/engine-primitives/engine-primitives" "github.com/berachain/beacon-kit/payload/cache" "github.com/berachain/beacon-kit/primitives/math" + "github.com/berachain/beacon-kit/primitives/version" "github.com/stretchr/testify/require" ) @@ -41,23 +42,23 @@ func FuzzPayloadIDCacheBasic(f *testing.F) { slot := math.Slot(s) pid := engineprimitives.PayloadID(_p[:8]) cacheUnderTest := cache.NewPayloadIDCache() - cacheUnderTest.Set(slot, r, pid) + cacheUnderTest.Set(slot, r, pid, version.Deneb()) p, ok := cacheUnderTest.GetAndEvict(slot, r) require.True(t, ok) - require.Equal(t, pid, p) + require.Equal(t, pid, p.PayloadID) // Test overwriting the same slot and root with a different PayloadID newPid := engineprimitives.PayloadID{} for i := range pid { newPid[i] = pid[i] + 1 // Simple mutation for a new PayloadID } - cacheUnderTest.Set((slot), r, newPid) + cacheUnderTest.Set(slot, r, newPid, version.Deneb()) p, ok = cacheUnderTest.GetAndEvict(slot, r) require.True(t, ok) require.Equal( - t, newPid, p, "PayloadID should be overwritten with the new value") + t, newPid, p.PayloadID, "PayloadID should be overwritten with the new value") // Verify deletion ok = cacheUnderTest.Has(slot, r) @@ -83,7 +84,7 @@ func FuzzPayloadIDInvalidInput(f *testing.F) { copy(paddedPayload[:], _p[:min(len(_p), 8)]) pid := [8]byte(paddedPayload[:]) cacheUnderTest := cache.NewPayloadIDCache() - cacheUnderTest.Set(slot, r, pid) + cacheUnderTest.Set(slot, r, pid, version.Deneb()) _, ok := cacheUnderTest.GetAndEvict(slot, r) require.True(t, ok) @@ -107,7 +108,7 @@ func FuzzPayloadIDCacheConcurrency(f *testing.F) { var paddedPayload [8]byte copy(paddedPayload[:], _p[:min(len(_p), 8)]) pid := [8]byte(paddedPayload[:]) - cacheUnderTest.Set((slot), r, pid) + cacheUnderTest.Set(slot, r, pid, version.Deneb()) }() // Get operation in another goroutine diff --git a/payload/cache/payload_id_test.go b/payload/cache/payload_id_test.go index 2fcfd789a6..f2838470a4 100644 --- a/payload/cache/payload_id_test.go +++ b/payload/cache/payload_id_test.go @@ -26,6 +26,7 @@ import ( engineprimitives "github.com/berachain/beacon-kit/engine-primitives/engine-primitives" "github.com/berachain/beacon-kit/payload/cache" "github.com/berachain/beacon-kit/primitives/math" + "github.com/berachain/beacon-kit/primitives/version" "github.com/stretchr/testify/require" ) @@ -37,29 +38,29 @@ func TestPayloadIDCache(t *testing.T) { var r [32]byte p, ok := cacheUnderTest.GetAndEvict(0, r) require.False(t, ok) - require.Equal(t, engineprimitives.PayloadID{}, p) + require.Equal(t, engineprimitives.PayloadID{}, p.PayloadID) }) t.Run("Set and Get", func(t *testing.T) { slot := math.Slot(1234) r := [32]byte{1, 2, 3} pid := engineprimitives.PayloadID{1, 2, 3, 3, 7, 8, 7, 8} - cacheUnderTest.Set(slot, r, pid) + cacheUnderTest.Set(slot, r, pid, version.Deneb()) p, ok := cacheUnderTest.GetAndEvict(slot, r) require.True(t, ok) - require.Equal(t, pid, p) + require.Equal(t, pid, p.PayloadID) }) t.Run("Overwrite existing", func(t *testing.T) { slot := math.Slot(1234) r := [32]byte{1, 2, 3} newPid := engineprimitives.PayloadID{9, 9, 9, 9, 9, 9, 9, 9} - cacheUnderTest.Set(slot, r, newPid) + cacheUnderTest.Set(slot, r, newPid, version.Deneb()) p, ok := cacheUnderTest.GetAndEvict(slot, r) require.True(t, ok) - require.Equal(t, newPid, p) + require.Equal(t, newPid, p.PayloadID) }) t.Run("Prune and verify deletion", func(t *testing.T) { @@ -67,13 +68,13 @@ func TestPayloadIDCache(t *testing.T) { r := [32]byte{4, 5, 6} pid := engineprimitives.PayloadID{4, 5, 6, 6, 9, 0, 9, 0} // Set pid for slot. - cacheUnderTest.Set(slot, r, pid) + cacheUnderTest.Set(slot, r, pid, version.Deneb()) // Set historicalPayloadIDCacheSize+1 number of pids. This should // prune the first slot from the cache. - cacheUnderTest.Set(slot+1, r, pid) - cacheUnderTest.Set(slot+2, r, pid) - cacheUnderTest.Set(slot+3, r, pid) + cacheUnderTest.Set(slot+1, r, pid, version.Deneb()) + cacheUnderTest.Set(slot+2, r, pid, version.Deneb()) + cacheUnderTest.Set(slot+3, r, pid, version.Deneb()) // Attempt to retrieve pruned slot. ok := cacheUnderTest.Has(slot, r) @@ -90,7 +91,7 @@ func TestPayloadIDCache(t *testing.T) { pid := [8]byte{ i, i, i, i, i, i, i, i, } - cacheUnderTest.Set(slot, r, pid) + cacheUnderTest.Set(slot, r, pid, version.Deneb()) } // Only the last historicalPayloadIDCacheSize+1 number of entries diff --git a/scripts/build/testing.mk b/scripts/build/testing.mk index f9f7c5c17a..95b84aedf3 100644 --- a/scripts/build/testing.mk +++ b/scripts/build/testing.mk @@ -57,6 +57,11 @@ start-ipc: ## start a local ephemeral `beacond` node with IPC RPC_PREFIX=${IPC_PREFIX} \ ${TESTAPP_FILES_DIR}/entrypoint.sh +start-configurable: + @JWT_SECRET_PATH=$(JWT_PATH) \ + CHAIN_SPEC=configurable \ + ${TESTAPP_FILES_DIR}/entrypoint.sh + ## Start an ephemeral `reth` node start-reth: $(call ask_reset_dir_func, $(ETH_DATA_DIR)) diff --git a/state-transition/core/core_test.go b/state-transition/core/core_test.go index 0110483878..2bde06a587 100644 --- a/state-transition/core/core_test.go +++ b/state-transition/core/core_test.go @@ -28,9 +28,9 @@ import ( "testing" "github.com/berachain/beacon-kit/chain" + "github.com/berachain/beacon-kit/config/spec" "github.com/berachain/beacon-kit/consensus-types/types" engineprimitives "github.com/berachain/beacon-kit/engine-primitives/engine-primitives" - "github.com/berachain/beacon-kit/node-core/components" "github.com/berachain/beacon-kit/primitives/bytes" "github.com/berachain/beacon-kit/primitives/common" "github.com/berachain/beacon-kit/primitives/math" @@ -42,12 +42,9 @@ import ( func setupChain(t *testing.T) chain.Spec { t.Helper() - - t.Setenv(components.ChainSpecTypeEnvVar, components.DevnetChainSpecType) - cs, err := components.ProvideChainSpec() + chainSpec, err := spec.DevnetChainSpec() require.NoError(t, err) - - return cs + return chainSpec } //nolint:unused // may be used in the future. @@ -164,13 +161,14 @@ func moveToEndOfEpoch( blk := tip currEpoch := cs.SlotToEpoch(blk.GetSlot()) for currEpoch == cs.SlotToEpoch(blk.GetSlot()+1) { + timestamp := blk.Body.ExecutionPayload.Timestamp + 1 blk = buildNextBlock( t, st, types.NewEth1Data(depRoot), - blk.Body.ExecutionPayload.Timestamp+1, + timestamp, []*types.Deposit{}, - st.EVMInflationWithdrawal(blk.GetSlot()+1), + st.EVMInflationWithdrawal(timestamp), ) vals, err := sp.Transition(ctx, st, blk) diff --git a/state-transition/core/interfaces.go b/state-transition/core/interfaces.go index b1181299ec..27bc08c0f5 100644 --- a/state-transition/core/interfaces.go +++ b/state-transition/core/interfaces.go @@ -74,8 +74,7 @@ type ChainSpec interface { SlotToEpoch(slot math.Slot) math.Epoch SlotsPerHistoricalRoot() uint64 EpochsPerHistoricalVector() uint64 - ActiveForkVersionForSlot(slot math.Slot) common.Version - ActiveForkVersionForEpoch(epoch math.Epoch) common.Version + ActiveForkVersionForTimestamp(timestamp math.U64) common.Version ValidatorSetCap() uint64 HistoricalRootsLimit() uint64 } diff --git a/state-transition/core/state/statedb.go b/state-transition/core/state/statedb.go index 5278b27c3a..a4de914e15 100644 --- a/state-transition/core/state/statedb.go +++ b/state-transition/core/state/statedb.go @@ -101,7 +101,7 @@ func (s *StateDB) UpdateSlashingAtIndex(index uint64, amount math.Gwei) error { // // NOTE: This function is modified from the spec to allow a fixed withdrawal // (as the first withdrawal) used for EVM inflation. -func (s *StateDB) ExpectedWithdrawals() (engineprimitives.Withdrawals, error) { +func (s *StateDB) ExpectedWithdrawals(timestamp math.U64) (engineprimitives.Withdrawals, error) { var ( validator *ctypes.Validator balance math.Gwei @@ -117,7 +117,7 @@ func (s *StateDB) ExpectedWithdrawals() (engineprimitives.Withdrawals, error) { withdrawals := make([]*engineprimitives.Withdrawal, 0, maxWithdrawals) // The first withdrawal is fixed to be the EVM inflation withdrawal. - withdrawals = append(withdrawals, s.EVMInflationWithdrawal(slot)) + withdrawals = append(withdrawals, s.EVMInflationWithdrawal(timestamp)) withdrawalIndex, err := s.GetNextWithdrawalIndex() if err != nil { @@ -199,12 +199,12 @@ func (s *StateDB) ExpectedWithdrawals() (engineprimitives.Withdrawals, error) { // // NOTE: The withdrawal index and validator index are both set to max(uint64) as // they are not used during processing. -func (s *StateDB) EVMInflationWithdrawal(slot math.Slot) *engineprimitives.Withdrawal { +func (s *StateDB) EVMInflationWithdrawal(timestamp math.U64) *engineprimitives.Withdrawal { return engineprimitives.NewWithdrawal( EVMInflationWithdrawalIndex, EVMInflationWithdrawalValidatorIndex, - s.cs.EVMInflationAddress(slot), - math.Gwei(s.cs.EVMInflationPerBlock(slot)), + s.cs.EVMInflationAddress(timestamp), + math.Gwei(s.cs.EVMInflationPerBlock(timestamp)), ) } diff --git a/state-transition/core/state_processor_payload_test.go b/state-transition/core/state_processor_payload_test.go index 88b0ed40a2..46fb52b76c 100644 --- a/state-transition/core/state_processor_payload_test.go +++ b/state-transition/core/state_processor_payload_test.go @@ -32,7 +32,6 @@ import ( payloadtime "github.com/berachain/beacon-kit/beacon/payload-time" "github.com/berachain/beacon-kit/consensus-types/types" "github.com/berachain/beacon-kit/primitives/common" - "github.com/berachain/beacon-kit/primitives/constants" "github.com/berachain/beacon-kit/primitives/math" "github.com/berachain/beacon-kit/primitives/transition" "github.com/berachain/beacon-kit/primitives/version" @@ -147,7 +146,7 @@ func TestPayloadTimestampVerification(t *testing.T) { types.NewEth1Data(genDeposits.HashTreeRoot()), math.U64(tt.payloadTime.Unix()), nil, - testSt.EVMInflationWithdrawal(constants.GenesisSlot+1), + testSt.EVMInflationWithdrawal(math.U64(tt.payloadTime.Unix())), ) _, err = sp.Transition(tCtx, testSt, blk) diff --git a/state-transition/core/state_processor_randao.go b/state-transition/core/state_processor_randao.go index 1430caa80d..34c852a9d6 100644 --- a/state-transition/core/state_processor_randao.go +++ b/state-transition/core/state_processor_randao.go @@ -58,7 +58,8 @@ func (sp *StateProcessor) processRandaoReveal( epoch := sp.cs.SlotToEpoch(slot) body := blk.GetBody() - fd := ctypes.NewForkData(sp.cs.ActiveForkVersionForEpoch(epoch), genesisValidatorsRoot) + timestamp := blk.GetTimestamp() + fd := ctypes.NewForkData(sp.cs.ActiveForkVersionForTimestamp(timestamp), genesisValidatorsRoot) if ctx.VerifyRandao() { signingRoot := fd.ComputeRandaoSigningRoot(sp.cs.DomainTypeRandao(), epoch) diff --git a/state-transition/core/state_processor_signature.go b/state-transition/core/state_processor_signature.go index d92ac2fccc..0e2a2b9f47 100644 --- a/state-transition/core/state_processor_signature.go +++ b/state-transition/core/state_processor_signature.go @@ -37,7 +37,8 @@ func (sp *StateProcessor) GetSignatureVerifierFn(st *statedb.StateDB) ( return func(blk *ctypes.BeaconBlock, signature crypto.BLSSignature) error { fd := ctypes.NewForkData( - sp.cs.ActiveForkVersionForSlot(blk.GetSlot()), genesisValidatorsRoot, + sp.cs.ActiveForkVersionForTimestamp(blk.GetTimestamp()), + genesisValidatorsRoot, ) domain := fd.ComputeDomain(sp.cs.DomainTypeProposer()) diff --git a/state-transition/core/state_processor_staking_test.go b/state-transition/core/state_processor_staking_test.go index 9883d2635f..1744df5fed 100644 --- a/state-transition/core/state_processor_staking_test.go +++ b/state-transition/core/state_processor_staking_test.go @@ -106,7 +106,7 @@ func TestTransitionUpdateValidators(t *testing.T) { types.NewEth1Data(depRoot), 10, []*types.Deposit{blkDeposit}, - st.EVMInflationWithdrawal(constants.GenesisSlot+1), + st.EVMInflationWithdrawal(10), ) // make sure included deposit is already available in deposit store @@ -146,9 +146,9 @@ func TestTransitionUpdateValidators(t *testing.T) { t, st, types.NewEth1Data(depRoot), - blk.Body.ExecutionPayload.Timestamp+1, + blk.GetTimestamp()+1, []*types.Deposit{}, - st.EVMInflationWithdrawal(blk.GetSlot()+1), + st.EVMInflationWithdrawal(blk.GetTimestamp()+1), ) valDiff, err = sp.Transition(ctx, st, blk) @@ -230,7 +230,7 @@ func TestTransitionCreateValidator(t *testing.T) { types.NewEth1Data(depRoot), 10, []*types.Deposit{blkDeposit}, - st.EVMInflationWithdrawal(constants.GenesisSlot+1), + st.EVMInflationWithdrawal(10), ) // make sure included deposit is already available in deposit store @@ -271,9 +271,9 @@ func TestTransitionCreateValidator(t *testing.T) { t, st, types.NewEth1Data(depRoot), - blk.Body.ExecutionPayload.Timestamp+1, + blk.GetTimestamp()+1, []*types.Deposit{}, - st.EVMInflationWithdrawal(blk.GetSlot()+1), + st.EVMInflationWithdrawal(blk.GetTimestamp()+1), ) valDiff, err = sp.Transition(ctx, st, blk) @@ -306,9 +306,9 @@ func TestTransitionCreateValidator(t *testing.T) { t, st, types.NewEth1Data(depRoot), - blk.Body.ExecutionPayload.Timestamp+1, + blk.GetTimestamp()+1, []*types.Deposit{}, - st.EVMInflationWithdrawal(blk.GetSlot()+1), + st.EVMInflationWithdrawal(blk.GetTimestamp()+1), ) // run the test @@ -394,7 +394,7 @@ func TestTransitionWithdrawals(t *testing.T) { // Create test inputs. withdrawals := []*engineprimitives.Withdrawal{ // The first withdrawal is always for EVM inflation. - st.EVMInflationWithdrawal(constants.GenesisSlot + 1), + st.EVMInflationWithdrawal(10), // Partially withdraw validator 1 by minBalance. { Index: 0, @@ -484,7 +484,7 @@ func TestTransitionMaxWithdrawals(t *testing.T) { depRoot := genDeposits.HashTreeRoot() withdrawals := []*engineprimitives.Withdrawal{ // The first withdrawal is always for EVM inflation. - st.EVMInflationWithdrawal(constants.GenesisSlot + 1), + st.EVMInflationWithdrawal(10), // Partially withdraw validator 0 by minBalance. { Index: 0, @@ -525,7 +525,7 @@ func TestTransitionMaxWithdrawals(t *testing.T) { withdrawals = []*engineprimitives.Withdrawal{ // The first withdrawal is always for EVM inflation. - st.EVMInflationWithdrawal(blk.GetSlot() + 1), + st.EVMInflationWithdrawal(blk.GetTimestamp() + 1), // Partially withdraw validator 1 by minBalance. { Index: 1, @@ -538,7 +538,7 @@ func TestTransitionMaxWithdrawals(t *testing.T) { t, st, types.NewEth1Data(depRoot), - blk.Body.ExecutionPayload.Timestamp+1, + blk.GetTimestamp()+1, []*types.Deposit{}, withdrawals..., ) @@ -628,7 +628,7 @@ func TestTransitionHittingValidatorsCap_ExtraSmall(t *testing.T) { types.NewEth1Data(depRoot), 10, []*types.Deposit{extraValDeposit}, - st.EVMInflationWithdrawal(constants.GenesisSlot+1), + st.EVMInflationWithdrawal(10), ) // make sure included deposit is already available in deposit store @@ -669,9 +669,9 @@ func TestTransitionHittingValidatorsCap_ExtraSmall(t *testing.T) { t, st, types.NewEth1Data(depRoot), - blk.Body.ExecutionPayload.Timestamp+1, + blk.GetTimestamp()+1, []*types.Deposit{}, - st.EVMInflationWithdrawal(blk.GetSlot()+1), + st.EVMInflationWithdrawal(blk.GetTimestamp()+1), ) // run the test @@ -704,9 +704,9 @@ func TestTransitionHittingValidatorsCap_ExtraSmall(t *testing.T) { t, st, types.NewEth1Data(depRoot), - blk.Body.ExecutionPayload.Timestamp+1, + blk.GetTimestamp()+1, []*types.Deposit{}, - st.EVMInflationWithdrawal(blk.GetSlot()+1), + st.EVMInflationWithdrawal(blk.GetTimestamp()+1), ) // run the test @@ -733,9 +733,9 @@ func TestTransitionHittingValidatorsCap_ExtraSmall(t *testing.T) { t, st, types.NewEth1Data(depRoot), - blk.Body.ExecutionPayload.Timestamp+1, + blk.GetTimestamp()+1, []*types.Deposit{}, - st.EVMInflationWithdrawal(blk.GetSlot()+1), + st.EVMInflationWithdrawal(blk.GetTimestamp()+1), ) _, err = sp.Transition(ctx, st, blk) require.NoError(t, err) @@ -744,15 +744,15 @@ func TestTransitionHittingValidatorsCap_ExtraSmall(t *testing.T) { t, st, types.NewEth1Data(depRoot), - blk.Body.ExecutionPayload.Timestamp+1, + blk.GetTimestamp()+1, []*types.Deposit{}, - st.EVMInflationWithdrawal(blk.GetSlot()+1), + st.EVMInflationWithdrawal(blk.GetTimestamp()+1), ) _, err = sp.Transition(ctx, st, blk) require.NoError(t, err) withdrawals := []*engineprimitives.Withdrawal{ - st.EVMInflationWithdrawal(blk.GetSlot()), + st.EVMInflationWithdrawal(blk.GetTimestamp() + 1), { Index: 0, Validator: extraValIdx, @@ -764,7 +764,7 @@ func TestTransitionHittingValidatorsCap_ExtraSmall(t *testing.T) { t, st, types.NewEth1Data(depRoot), - blk.Body.ExecutionPayload.Timestamp+1, + blk.GetTimestamp()+1, []*types.Deposit{}, withdrawals..., ) @@ -847,7 +847,7 @@ func TestTransitionHittingValidatorsCap_ExtraBig(t *testing.T) { types.NewEth1Data(depRoot), 10, []*types.Deposit{extraValDeposit}, - st.EVMInflationWithdrawal(constants.GenesisSlot+1), + st.EVMInflationWithdrawal(10), ) // make sure included deposit is already available in deposit store @@ -905,9 +905,9 @@ func TestTransitionHittingValidatorsCap_ExtraBig(t *testing.T) { t, st, types.NewEth1Data(depRoot), - blk.Body.ExecutionPayload.Timestamp+1, + blk.GetTimestamp()+1, []*types.Deposit{}, - st.EVMInflationWithdrawal(blk.GetSlot()+1), + st.EVMInflationWithdrawal(blk.GetTimestamp()+1), ) // run the test @@ -955,9 +955,9 @@ func TestTransitionHittingValidatorsCap_ExtraBig(t *testing.T) { t, st, types.NewEth1Data(depRoot), - blk.Body.ExecutionPayload.Timestamp+1, + blk.GetTimestamp()+1, []*types.Deposit{}, - st.EVMInflationWithdrawal(blk.GetSlot()+1), + st.EVMInflationWithdrawal(blk.GetTimestamp()+1), ) // run the test @@ -1013,9 +1013,9 @@ func TestTransitionHittingValidatorsCap_ExtraBig(t *testing.T) { t, st, types.NewEth1Data(depRoot), - blk.Body.ExecutionPayload.Timestamp+1, + blk.GetTimestamp()+1, []*types.Deposit{}, - st.EVMInflationWithdrawal(blk.GetSlot()), + st.EVMInflationWithdrawal(blk.GetTimestamp()+1), ) _, err = sp.Transition(ctx, st, blk) require.NoError(t, err) @@ -1024,15 +1024,15 @@ func TestTransitionHittingValidatorsCap_ExtraBig(t *testing.T) { t, st, types.NewEth1Data(depRoot), - blk.Body.ExecutionPayload.Timestamp+1, + blk.GetTimestamp()+1, []*types.Deposit{}, - st.EVMInflationWithdrawal(blk.GetSlot()+1), + st.EVMInflationWithdrawal(blk.GetTimestamp()+1), ) _, err = sp.Transition(ctx, st, blk) require.NoError(t, err) withdrawals := []*engineprimitives.Withdrawal{ - st.EVMInflationWithdrawal(blk.GetSlot() + 1), + st.EVMInflationWithdrawal(blk.GetTimestamp() + 1), { Index: 0, Validator: smallestValIdx, @@ -1044,7 +1044,7 @@ func TestTransitionHittingValidatorsCap_ExtraBig(t *testing.T) { t, st, types.NewEth1Data(depRoot), - blk.Body.ExecutionPayload.Timestamp+1, + blk.GetTimestamp()+1, []*types.Deposit{}, withdrawals..., ) @@ -1103,7 +1103,7 @@ func TestValidatorNotWithdrawable(t *testing.T) { types.NewEth1Data(depRoot), 10, blockDeposits, - st.EVMInflationWithdrawal(constants.GenesisSlot+1), + st.EVMInflationWithdrawal(10), ) require.NoError(t, ds.EnqueueDeposits(ctx.ConsensusCtx(), blockDeposits)) diff --git a/state-transition/core/state_processor_withdrawals.go b/state-transition/core/state_processor_withdrawals.go index 4de87aeb67..f191793aae 100644 --- a/state-transition/core/state_processor_withdrawals.go +++ b/state-transition/core/state_processor_withdrawals.go @@ -49,7 +49,7 @@ func (sp *StateProcessor) processWithdrawals( ) // Get the expected withdrawals. - expectedWithdrawals, err := st.ExpectedWithdrawals() + expectedWithdrawals, err := st.ExpectedWithdrawals(blk.GetTimestamp()) if err != nil { return err } @@ -67,7 +67,7 @@ func (sp *StateProcessor) processWithdrawals( if len(payloadWithdrawals) == 0 { return ErrZeroWithdrawals } - if !payloadWithdrawals[0].Equals(st.EVMInflationWithdrawal(blk.GetSlot())) { + if !payloadWithdrawals[0].Equals(st.EVMInflationWithdrawal(blk.GetTimestamp())) { return ErrFirstWithdrawalNotEVMInflation } numWithdrawals := len(expectedWithdrawals) diff --git a/state-transition/core/validation_deposits_test.go b/state-transition/core/validation_deposits_test.go index 643f439211..d82a2363e5 100644 --- a/state-transition/core/validation_deposits_test.go +++ b/state-transition/core/validation_deposits_test.go @@ -30,7 +30,6 @@ import ( "github.com/berachain/beacon-kit/config/spec" "github.com/berachain/beacon-kit/consensus-types/types" "github.com/berachain/beacon-kit/primitives/common" - "github.com/berachain/beacon-kit/primitives/constants" "github.com/berachain/beacon-kit/primitives/math" "github.com/berachain/beacon-kit/primitives/version" statetransition "github.com/berachain/beacon-kit/testing/state-transition" @@ -95,7 +94,7 @@ func TestInvalidDeposits(t *testing.T) { types.NewEth1Data(depRoot), 10, []*types.Deposit{invalidDeposit}, - st.EVMInflationWithdrawal(constants.GenesisSlot+1), + st.EVMInflationWithdrawal(10), ) // Add correct deposit to local store (honest validator will see this locally). @@ -161,7 +160,7 @@ func TestInvalidDepositsCount(t *testing.T) { types.NewEth1Data(depRoot), 10, correctDeposits, - st.EVMInflationWithdrawal(constants.GenesisSlot+1), + st.EVMInflationWithdrawal(10), ) // Add JUST 1 correct deposit to local store. This node SHOULD fail to verify. @@ -224,7 +223,7 @@ func TestLocalDepositsExceedBlockDeposits(t *testing.T) { types.NewEth1Data(depRoot), 10, blockDeposits, - st.EVMInflationWithdrawal(constants.GenesisSlot+1), + st.EVMInflationWithdrawal(10), ) extraLocalDeposit := &types.Deposit{ @@ -301,7 +300,7 @@ func TestLocalDepositsExceedBlockDepositsBadRoot(t *testing.T) { types.NewEth1Data(badDepRoot), 10, blockDeposits, - st.EVMInflationWithdrawal(constants.GenesisSlot+1), + st.EVMInflationWithdrawal(10), ) // Add both deposits to local store (which includes more than what's in the block). diff --git a/testing/e2e/e2e_inflation_test.go b/testing/e2e/e2e_inflation_test.go index be6d9ea5c5..817a5d8d45 100644 --- a/testing/e2e/e2e_inflation_test.go +++ b/testing/e2e/e2e_inflation_test.go @@ -22,8 +22,10 @@ package e2e_test import ( "math/big" + "sync" "github.com/berachain/beacon-kit/config/spec" + "github.com/berachain/beacon-kit/primitives/common" "github.com/berachain/beacon-kit/primitives/math" gethcommon "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/params" @@ -36,64 +38,69 @@ func (s *BeaconKitE2ESuite) TestEVMInflation() { chainspec, err := spec.DevnetChainSpec() s.Require().NoError(err) - deneb1ForkSlot := chainspec.SlotsPerEpoch() * uint64(chainspec.Deneb1ForkEpoch()) - - // Check over the first epoch before Deneb1, the balance of the Devnet EVM inflation address - // increases by DevnetEVMInflationPerBlock. - preForkInflation := chainspec.EVMInflationPerBlock(math.Slot(0)) - preForkAddress := chainspec.EVMInflationAddress(math.Slot(0)) - for blkNum := range int64(deneb1ForkSlot) { + var ( + inflationPerBlock uint64 + inflationAddress common.ExecutionAddress + oldInflationAddress common.ExecutionAddress + preForkAddressFinalBalance *big.Int + preForkLatestBalance *big.Int + balance *big.Int + expectedBalance *big.Int + forkSlot int64 + onceOnFork sync.Once + ) + // Arbitrarily run test for 2 epochs. + for blkNum := range int64(2 * chainspec.SlotsPerEpoch()) { err = s.WaitForFinalizedBlockNumber(uint64(blkNum)) s.Require().NoError(err) + payload, errBlk := s.JSONRPCBalancer().BlockByNumber(s.Ctx(), big.NewInt(blkNum)) + s.Require().NoError(errBlk) - expectedBalance := new(big.Int).Mul( - new(big.Int).SetUint64(preForkInflation*params.GWei), - big.NewInt(blkNum), - ) + payloadTime := payload.Time() + inflationPerBlock = chainspec.EVMInflationPerBlock(math.U64(payloadTime)) + inflationAddress = chainspec.EVMInflationAddress(math.U64(payloadTime)) + if chainspec.Deneb1ForkTime() > 0 && payloadTime >= chainspec.Deneb1ForkTime() { + // If we have passed the Deneb1 fork, do some verifications and update inflation values. + onceOnFork.Do(func() { + oldInflationPerBlock := chainspec.EVMInflationPerBlock(math.U64(chainspec.Deneb1ForkTime() - 1)) + oldInflationAddress = chainspec.EVMInflationAddress(math.U64(chainspec.Deneb1ForkTime() - 1)) - var balance *big.Int - balance, err = s.JSONRPCBalancer().BalanceAt( - s.Ctx(), - gethcommon.Address(preForkAddress), - big.NewInt(blkNum), - ) - s.Require().NoError(err) - s.Require().Zero(balance.Cmp(expectedBalance), - "height", blkNum, - "balance", balance, - "expectedBalance", expectedBalance, - ) - } - - // Check over the first epoch after Deneb1, the balance of the Devnet EVM inflation address - // post Deneb1 increases by DevnetEVMInflationPerBlockDeneb1. - postForkInflation := chainspec.EVMInflationPerBlock(math.Slot(deneb1ForkSlot)) - s.Require().NotEqual(preForkInflation, postForkInflation) + // Verify the post fork inflation changes + s.Require().NotEqual(oldInflationPerBlock, inflationPerBlock) + s.Require().NotEqual(oldInflationAddress, inflationAddress) + forkSlot = blkNum - postForkAddress := chainspec.EVMInflationAddress(math.Slot(deneb1ForkSlot)) - s.Require().NotEqual(preForkAddress, postForkAddress) - - // take the snapshot of balance right before the fork and check it won't change anymore - var preForkAddressFinalBalance *big.Int - preForkAddressFinalBalance, err = s.JSONRPCBalancer().BalanceAt( - s.Ctx(), gethcommon.Address(preForkAddress), big.NewInt(int64(deneb1ForkSlot-1)), - ) - s.Require().NoError(err) + // take the snapshot of balance right before the fork and check it won't change anymore + preForkAddressFinalBalance, err = s.JSONRPCBalancer().BalanceAt( + s.Ctx(), gethcommon.Address(oldInflationAddress), big.NewInt(blkNum-1), + ) + s.Require().NoError(err) + }) - for blkNum := deneb1ForkSlot; blkNum < deneb1ForkSlot+chainspec.SlotsPerEpoch(); blkNum++ { - err = s.WaitForFinalizedBlockNumber(blkNum) - s.Require().NoError(err) + // Enforce that the balance of the EVM inflation address + // prior to the hardfork is the same as it is now. + preForkLatestBalance, err = s.JSONRPCBalancer().BalanceAt( + s.Ctx(), gethcommon.Address(oldInflationAddress), nil, // at the current block + ) + s.Require().NoError(err) + s.Require().Zero(preForkAddressFinalBalance.Cmp(preForkLatestBalance)) - expectedBalance := new(big.Int).Mul( - new(big.Int).SetUint64(postForkInflation*params.GWei), - big.NewInt(int64(blkNum-(deneb1ForkSlot-1))), - ) + expectedBalance = new(big.Int).Mul( + new(big.Int).SetUint64(inflationPerBlock*params.GWei), + big.NewInt(blkNum-forkSlot+1), + ) + } else { + // Pre-Deneb1 + expectedBalance = new(big.Int).Mul( + new(big.Int).SetUint64(inflationPerBlock*params.GWei), + big.NewInt(blkNum), + ) + } - var balance *big.Int balance, err = s.JSONRPCBalancer().BalanceAt( s.Ctx(), - gethcommon.Address(postForkAddress), - big.NewInt(int64(blkNum)), + gethcommon.Address(inflationAddress), + big.NewInt(blkNum), ) s.Require().NoError(err) s.Require().Zero(balance.Cmp(expectedBalance), @@ -101,14 +108,5 @@ func (s *BeaconKitE2ESuite) TestEVMInflation() { "balance", balance, "expectedBalance", expectedBalance, ) - - // Enforce that the balance of the EVM inflation address - // prior to the hardfork is the same as it is now. - var preForkLatestBalance *big.Int - preForkLatestBalance, err = s.JSONRPCBalancer().BalanceAt( - s.Ctx(), gethcommon.Address(preForkAddress), nil, // at the current block - ) - s.Require().NoError(err) - s.Require().Zero(preForkAddressFinalBalance.Cmp(preForkLatestBalance)) } } diff --git a/testing/files/entrypoint.sh b/testing/files/entrypoint.sh index 9a398deaf0..2ddc3c6a03 100755 --- a/testing/files/entrypoint.sh +++ b/testing/files/entrypoint.sh @@ -63,10 +63,16 @@ else overwrite="Y" fi +CHAIN_SPEC_ARG="" +if [ "$CHAIN_SPEC" == "configurable" ]; then + CHAIN_SPEC_ARG="--spec=$(resolve_path "./cli/commands/server/types/mainnet_spec.toml")" +fi + + # Setup local node if overwrite is set to Yes, otherwise skip setup if [[ $overwrite == "y" || $overwrite == "Y" ]]; then rm -rf $HOMEDIR - ./build/bin/beacond init $MONIKER --chain-id $CHAINID --home $HOMEDIR + ./build/bin/beacond init $MONIKER --chain-id $CHAINID --home $HOMEDIR $CHAIN_SPEC_ARG if [ "$CHAIN_SPEC" == "testnet" ]; then network_dir="testing/networks/80069" @@ -76,18 +82,22 @@ if [[ $overwrite == "y" || $overwrite == "Y" ]]; then network_dir="testing/networks/80094" cp -f $network_dir/*.toml $network_dir/genesis.json ${HOMEDIR}/config KZG_PATH=$network_dir/kzg-trusted-setup.json + elif [ "$CHAIN_SPEC" == "configurable" ]; then + network_dir="testing/networks/80094" + cp -f $network_dir/*.toml $network_dir/genesis.json ${HOMEDIR}/config + KZG_PATH=$network_dir/kzg-trusted-setup.json else - ./build/bin/beacond genesis add-premined-deposit --home $HOMEDIR \ + ./build/bin/beacond genesis add-premined-deposit --home $HOMEDIR $CHAIN_SPEC_ARG \ 32000000000 0x20f33ce90a13a4b5e7697e3544c3083b8f8a51d4 - ./build/bin/beacond genesis collect-premined-deposits --home $HOMEDIR - ./build/bin/beacond genesis set-deposit-storage "$ETH_GENESIS" --home $HOMEDIR - ./build/bin/beacond genesis set-deposit-storage "$ETH_NETHER_GENESIS" --nethermind --home $HOMEDIR - ./build/bin/beacond genesis execution-payload "$HOMEDIR/eth-genesis.json" --home $HOMEDIR + ./build/bin/beacond genesis collect-premined-deposits --home $HOMEDIR $CHAIN_SPEC_ARG + ./build/bin/beacond genesis set-deposit-storage "$ETH_GENESIS" --home $HOMEDIR $CHAIN_SPEC_ARG + ./build/bin/beacond genesis set-deposit-storage "$ETH_NETHER_GENESIS" --nethermind --home $HOMEDIR $CHAIN_SPEC_ARG + ./build/bin/beacond genesis execution-payload "$HOMEDIR/eth-genesis.json" --home $HOMEDIR $CHAIN_SPEC_ARG fi fi # Start the node (remove the --pruning=nothing flag if historical queries are not needed) -BEACON_START_CMD="./build/bin/beacond start --pruning=nothing "$TRACE" \ +BEACON_START_CMD="./build/bin/beacond start $CHAIN_SPEC_ARG --pruning=nothing "$TRACE" \ --beacon-kit.logger.log-level $LOGLEVEL --home $HOMEDIR \ --beacon-kit.engine.jwt-secret-path ${JWT_SECRET_PATH} \ --beacon-kit.kzg.trusted-setup-path ${KZG_PATH} \ diff --git a/testing/simulated/chaos_test.go b/testing/simulated/chaos_test.go index a021a505e3..48b579d5e6 100644 --- a/testing/simulated/chaos_test.go +++ b/testing/simulated/chaos_test.go @@ -46,13 +46,16 @@ func (s *SimulatedSuite) TestProcessProposal_CrashedExecutionClient_Errors() { pubkey, err := blsSigner.GetPubKey() s.Require().NoError(err) + // Test happens post Deneb1 fork. + startTime := time.Now() + // Go through 1 iteration of the core loop to bypass any startup specific edge cases such as sync head on startup. - proposals := s.MoveChainToHeight(s.T(), blockHeight, coreLoopIterations, blsSigner) + proposals, proposalTime := s.MoveChainToHeight(s.T(), blockHeight, coreLoopIterations, blsSigner, startTime) s.Require().Len(proposals, coreLoopIterations) currentHeight := int64(blockHeight + coreLoopIterations) + // Prepare a valid block proposal. - proposalTime := time.Now() proposal, err := s.SimComet.Comet.PrepareProposal(s.CtxComet, &types.PrepareProposalRequest{ Height: currentHeight, Time: proposalTime, @@ -91,8 +94,11 @@ func (s *SimulatedSuite) TestContextHandling_SIGINT_SafeShutdown() { pubkey, err := blsSigner.GetPubKey() s.Require().NoError(err) + // Test happens post Deneb1 fork. + startTime := time.Now() + // Run through core loop iterations to bypass any startup edge cases. - proposals := s.MoveChainToHeight(s.T(), blockHeight, coreLoopIterations, blsSigner) + proposals, proposalTime := s.MoveChainToHeight(s.T(), blockHeight, coreLoopIterations, blsSigner, startTime) s.Require().Len(proposals, coreLoopIterations) currentHeight := int64(blockHeight + coreLoopIterations) @@ -108,8 +114,8 @@ func (s *SimulatedSuite) TestContextHandling_SIGINT_SafeShutdown() { } // Capture result of prepare proposal resultCh := make(chan proposalResult, 1) + // Prepare proposal in a separate goroutine since it will block due to retrying on the crashed EL. - proposalTime := time.Now() go func() { proposal, err := s.SimComet.Comet.PrepareProposal(s.CtxComet, &types.PrepareProposalRequest{ Height: currentHeight, @@ -151,8 +157,11 @@ func (s *SimulatedSuite) TestContextHandling_CancelledContext_Rejected() { pubkey, err := blsSigner.GetPubKey() s.Require().NoError(err) + // Test happens post Deneb1 fork. + startTime := time.Now() + // Go through 1 iteration of the core loop to bypass any startup specific edge cases such as sync head on startup. - proposals := s.MoveChainToHeight(s.T(), blockHeight, coreLoopIterations, blsSigner) + proposals, proposalTime := s.MoveChainToHeight(s.T(), blockHeight, coreLoopIterations, blsSigner, startTime) s.Require().Len(proposals, coreLoopIterations) currentHeight := int64(blockHeight + coreLoopIterations) @@ -165,7 +174,6 @@ func (s *SimulatedSuite) TestContextHandling_CancelledContext_Rejected() { s.CtxAppCancelFn() s.LogBuffer.Reset() - proposalTime := time.Now() proposal, err := s.SimComet.Comet.PrepareProposal(s.CtxComet, &types.PrepareProposalRequest{ Height: currentHeight, Time: proposalTime, @@ -187,6 +195,7 @@ func (s *SimulatedSuite) TestContextHandling_CancelledContext_Rejected() { Txs: proposal.Txs, Height: currentHeight, ProposerAddress: pubkey.Address(), + Time: proposalTime, }) s.Require().Error(err, context.Canceled) s.Require().Nil(finalizeResp) diff --git a/testing/simulated/components.go b/testing/simulated/components.go index a503dd5f94..447e856701 100644 --- a/testing/simulated/components.go +++ b/testing/simulated/components.go @@ -82,8 +82,8 @@ func FixedComponents(t *testing.T) []any { func ProvideElectraGenesisChainSpec() (chain.Spec, error) { specData := spec.TestnetChainSpecData() // Both Deneb1 and Electra happen in genesis. - specData.Deneb1ForkEpoch = 0 - specData.ElectraForkEpoch = 0 + specData.Deneb1ForkTime = 0 + specData.ElectraForkTime = 0 chainSpec, err := chain.NewSpec(specData) if err != nil { return nil, err @@ -94,5 +94,12 @@ func ProvideElectraGenesisChainSpec() (chain.Spec, error) { // ProvideSimulationChainSpec provides a default chain-spec equivalent to testnet. // Bypasses the need for environment variables. func ProvideSimulationChainSpec() (chain.Spec, error) { - return spec.TestnetChainSpec() + specData := spec.TestnetChainSpecData() + // Arbitrary number + specData.Deneb1ForkTime = 30 + chainSpec, err := chain.NewSpec(specData) + if err != nil { + return nil, err + } + return chainSpec, nil } diff --git a/testing/simulated/malicious_consensus_test.go b/testing/simulated/malicious_consensus_test.go index a810ef150c..0be7aa6027 100644 --- a/testing/simulated/malicious_consensus_test.go +++ b/testing/simulated/malicious_consensus_test.go @@ -23,6 +23,7 @@ package simulated_test import ( + "github.com/berachain/beacon-kit/primitives/math" "math/big" "time" @@ -31,7 +32,6 @@ import ( "github.com/berachain/beacon-kit/consensus/cometbft/service/encoding" "github.com/berachain/beacon-kit/engine-primitives/errors" gethprimitives "github.com/berachain/beacon-kit/geth-primitives" - "github.com/berachain/beacon-kit/primitives/math" "github.com/berachain/beacon-kit/testing/simulated" "github.com/cometbft/cometbft/abci/types" gethcommon "github.com/ethereum/go-ethereum/common" @@ -52,13 +52,17 @@ func (s *SimulatedSuite) TestFinalizeBlock_BadBlock_Errors() { pubkey, err := blsSigner.GetPubKey() s.Require().NoError(err) + // Test happens on Deneb, pre Deneb1 fork. + startTime := time.Unix(0, 0) + // Go through 1 iteration of the core loop to bypass any startup specific edge cases such as sync head on startup. - proposals := s.MoveChainToHeight(s.T(), blockHeight, coreLoopIterations, blsSigner) + proposals, proposalTime := s.MoveChainToHeight(s.T(), blockHeight, coreLoopIterations, blsSigner, startTime) s.Require().Len(proposals, coreLoopIterations) + // We expected this test to happen during Pre-Deneb1 fork. currentHeight := int64(blockHeight + coreLoopIterations) + // Prepare a block proposal. - proposalTime := time.Now() proposal, err := s.SimComet.Comet.PrepareProposal(s.CtxComet, &types.PrepareProposalRequest{ Height: currentHeight, Time: proposalTime, @@ -71,7 +75,7 @@ func (s *SimulatedSuite) TestFinalizeBlock_BadBlock_Errors() { proposedBlock, err := encoding.UnmarshalBeaconBlockFromABCIRequest( proposal.Txs, blockchain.BeaconBlockTxIndex, - s.TestNode.ChainSpec.ActiveForkVersionForSlot(math.Slot(currentHeight)), + s.TestNode.ChainSpec.ActiveForkVersionForTimestamp(math.U64(proposalTime.Unix())), ) s.Require().NoError(err) @@ -100,7 +104,7 @@ func (s *SimulatedSuite) TestFinalizeBlock_BadBlock_Errors() { maliciousBlockSigned, err := ctypes.NewSignedBeaconBlock( maliciousBlock, &ctypes.ForkData{ - CurrentVersion: s.TestNode.ChainSpec.ActiveForkVersionForSlot(maliciousBlock.GetSlot()), + CurrentVersion: s.TestNode.ChainSpec.ActiveForkVersionForTimestamp(maliciousBlock.GetTimestamp()), GenesisValidatorsRoot: s.GenesisValidatorsRoot, }, s.TestNode.ChainSpec, diff --git a/testing/simulated/malicious_proposer_test.go b/testing/simulated/malicious_proposer_test.go index ef65aa05fb..1746824268 100644 --- a/testing/simulated/malicious_proposer_test.go +++ b/testing/simulated/malicious_proposer_test.go @@ -62,13 +62,17 @@ func (s *SimulatedSuite) TestProcessProposal_BadBlock_IsRejected() { pubkey, err := blsSigner.GetPubKey() s.Require().NoError(err) + // Test happens on Deneb, pre Deneb1 fork. + startTime := time.Unix(0, 0) + // Go through 1 iteration of the core loop to bypass any startup specific edge cases such as sync head on startup. - proposals := s.MoveChainToHeight(s.T(), blockHeight, coreLoopIterations, blsSigner) + proposals, proposalTime := s.MoveChainToHeight(s.T(), blockHeight, coreLoopIterations, blsSigner, startTime) s.Require().Len(proposals, coreLoopIterations) + // We expected this test to happen during Pre-Deneb1 fork. currentHeight := int64(blockHeight + coreLoopIterations) + // Prepare a block proposal. - proposalTime := time.Now() proposal, err := s.SimComet.Comet.PrepareProposal(s.CtxComet, &types.PrepareProposalRequest{ Height: currentHeight, Time: proposalTime, @@ -81,7 +85,7 @@ func (s *SimulatedSuite) TestProcessProposal_BadBlock_IsRejected() { proposedBlock, err := encoding.UnmarshalBeaconBlockFromABCIRequest( proposal.Txs, blockchain.BeaconBlockTxIndex, - s.TestNode.ChainSpec.ActiveForkVersionForSlot(math.Slot(currentHeight)), + s.TestNode.ChainSpec.ActiveForkVersionForTimestamp(math.U64(proposalTime.Unix())), ) s.Require().NoError(err) @@ -111,7 +115,7 @@ func (s *SimulatedSuite) TestProcessProposal_BadBlock_IsRejected() { maliciousBlockSigned, err := ctypes.NewSignedBeaconBlock( maliciousBlock, &ctypes.ForkData{ - CurrentVersion: s.TestNode.ChainSpec.ActiveForkVersionForSlot(maliciousBlock.GetSlot()), + CurrentVersion: s.TestNode.ChainSpec.ActiveForkVersionForTimestamp(maliciousBlock.GetTimestamp()), GenesisValidatorsRoot: s.GenesisValidatorsRoot, }, s.TestNode.ChainSpec, @@ -157,13 +161,15 @@ func (s *SimulatedSuite) TestProcessProposal_InvalidTimestamps_Errors() { pubkey, err := blsSigner.GetPubKey() s.Require().NoError(err) + // Test happens post Deneb1 fork. + startTime := time.Now() + // Go through 1 iteration of the core loop to bypass any startup specific edge cases such as sync head on startup. - proposals := s.MoveChainToHeight(s.T(), blockHeight, coreLoopIterations, blsSigner) + proposals, correctConsensusTime := s.MoveChainToHeight(s.T(), blockHeight, coreLoopIterations, blsSigner, startTime) s.Require().Len(proposals, coreLoopIterations) currentHeight := int64(blockHeight + coreLoopIterations) // Prepare a block proposal, but 2 seconds in the future (i.e. attempt to roll timestamp forward) - correctConsensusTime := time.Now() maliciousProposalTime := correctConsensusTime.Add(2 * time.Second) maliciousProposal, err := s.SimComet.Comet.PrepareProposal(s.CtxComet, &types.PrepareProposalRequest{ Height: currentHeight, @@ -203,14 +209,17 @@ func (s *SimulatedSuite) TestProcessProposal_InvalidBlobCommitment_Errors() { pubkey, err := blsSigner.GetPubKey() s.Require().NoError(err) + // Test happens on Deneb, pre Deneb1 fork. + startTime := time.Unix(0, 0) + // Go through 1 iteration of the core loop to bypass any startup specific edge cases such as sync head on startup. - proposals := s.MoveChainToHeight(s.T(), blockHeight, coreLoopIterations, blsSigner) + proposals, consensusTime := s.MoveChainToHeight(s.T(), blockHeight, coreLoopIterations, blsSigner, startTime) s.Require().Len(proposals, coreLoopIterations) + // We expected this test to happen during Pre-Deneb1 fork. currentHeight := int64(blockHeight + coreLoopIterations) // Prepare a block proposal. - consensusTime := time.Now() proposal, err := s.SimComet.Comet.PrepareProposal(s.CtxComet, &types.PrepareProposalRequest{ Height: currentHeight, Time: consensusTime, @@ -223,7 +232,7 @@ func (s *SimulatedSuite) TestProcessProposal_InvalidBlobCommitment_Errors() { proposedBlock, err := encoding.UnmarshalBeaconBlockFromABCIRequest( proposal.Txs, blockchain.BeaconBlockTxIndex, - s.TestNode.ChainSpec.ActiveForkVersionForSlot(blockHeight), + s.TestNode.ChainSpec.ActiveForkVersionForTimestamp(math.U64(consensusTime.Unix())), ) s.Require().NoError(err) @@ -301,7 +310,7 @@ func (s *SimulatedSuite) TestProcessProposal_InvalidBlobCommitment_Errors() { newSignedBlock, err := ctypes.NewSignedBeaconBlock( proposedBlockMessage, &ctypes.ForkData{ - CurrentVersion: s.TestNode.ChainSpec.ActiveForkVersionForSlot(proposedBlockMessage.GetSlot()), + CurrentVersion: s.TestNode.ChainSpec.ActiveForkVersionForTimestamp(proposedBlockMessage.GetTimestamp()), GenesisValidatorsRoot: s.GenesisValidatorsRoot, }, s.TestNode.ChainSpec, @@ -371,14 +380,17 @@ func (s *SimulatedSuite) TestProcessProposal_InvalidBlobInclusionProof_Errors() pubkey, err := blsSigner.GetPubKey() s.Require().NoError(err) + // Test happens on Deneb, pre Deneb1 fork. + startTime := time.Unix(0, 0) + // Go through 1 iteration of the core loop to bypass any startup specific edge cases such as sync head on startup. - proposals := s.MoveChainToHeight(s.T(), blockHeight, coreLoopIterations, blsSigner) + proposals, consensusTime := s.MoveChainToHeight(s.T(), blockHeight, coreLoopIterations, blsSigner, startTime) s.Require().Len(proposals, coreLoopIterations) + // We expected this test to happen during Pre-Deneb1 fork. currentHeight := int64(blockHeight + coreLoopIterations) // Prepare a block proposal. - consensusTime := time.Now() proposal, err := s.SimComet.Comet.PrepareProposal(s.CtxComet, &types.PrepareProposalRequest{ Height: currentHeight, Time: consensusTime, @@ -391,7 +403,7 @@ func (s *SimulatedSuite) TestProcessProposal_InvalidBlobInclusionProof_Errors() proposedBlock, err := encoding.UnmarshalBeaconBlockFromABCIRequest( proposal.Txs, blockchain.BeaconBlockTxIndex, - s.TestNode.ChainSpec.ActiveForkVersionForSlot(blockHeight), + s.TestNode.ChainSpec.ActiveForkVersionForTimestamp(math.U64(consensusTime.Unix())), ) s.Require().NoError(err) @@ -462,7 +474,7 @@ func (s *SimulatedSuite) TestProcessProposal_InvalidBlobInclusionProof_Errors() newSignedBlock, err := ctypes.NewSignedBeaconBlock( proposedBlockMessage, &ctypes.ForkData{ - CurrentVersion: s.TestNode.ChainSpec.ActiveForkVersionForSlot(proposedBlockMessage.GetSlot()), + CurrentVersion: s.TestNode.ChainSpec.ActiveForkVersionForTimestamp(proposedBlockMessage.GetTimestamp()), GenesisValidatorsRoot: s.GenesisValidatorsRoot, }, s.TestNode.ChainSpec, diff --git a/testing/simulated/pectra_test.go b/testing/simulated/pectra_test.go index 00e2d7471d..b61641a075 100644 --- a/testing/simulated/pectra_test.go +++ b/testing/simulated/pectra_test.go @@ -134,8 +134,11 @@ func (s *PectraSuite) TestFullLifecycle_WithoutRequests_IsSuccessful() { // Retrieve the BLS signer and proposer address. blsSigner := simulated.GetBlsSigner(s.HomeDir) + // Test happens post Electra fork. + startTime := time.Now() + // Go through iterations of the core loop. - proposals := s.MoveChainToHeight(s.T(), blockHeight, coreLoopIterations, blsSigner) + proposals, _ := s.MoveChainToHeight(s.T(), blockHeight, coreLoopIterations, blsSigner, startTime) s.Require().Len(proposals, coreLoopIterations) } @@ -176,7 +179,10 @@ func (s *PectraSuite) TestFullLifecycle_WithRequests_IsSuccessful() { err = s.TestNode.EngineClient.Call(s.CtxApp, &result, "eth_sendRawTransaction", hexutil.Encode(txBytes)) s.Require().NoError(err) + // Test happens post Electra fork. + startTime := time.Now() + // Go through iterations of the core loop. - proposals := s.MoveChainToHeight(s.T(), blockHeight, coreLoopIterations, blsSigner) + proposals, _ := s.MoveChainToHeight(s.T(), blockHeight, coreLoopIterations, blsSigner, startTime) s.Require().Len(proposals, coreLoopIterations) } diff --git a/testing/simulated/utils.go b/testing/simulated/utils.go index 9cbe5e2555..7170c13615 100644 --- a/testing/simulated/utils.go +++ b/testing/simulated/utils.go @@ -106,15 +106,16 @@ func (s *SharedAccessors) MoveChainToHeight( t *testing.T, startHeight, iterations int64, proposer *signer.BLSSigner, -) []*types.PrepareProposalResponse { + startTime time.Time, +) ([]*types.PrepareProposalResponse, time.Time) { // Prepare a block proposal. pubkey, err := proposer.GetPubKey() require.NoError(t, err) var proposedCometBlocks []*types.PrepareProposalResponse + proposalTime := startTime for currentHeight := startHeight; currentHeight < startHeight+iterations; currentHeight++ { - proposalTime := time.Now() proposal, err := s.SimComet.Comet.PrepareProposal(s.CtxComet, &types.PrepareProposalRequest{ Height: currentHeight, Time: proposalTime, @@ -138,6 +139,7 @@ func (s *SharedAccessors) MoveChainToHeight( Txs: proposal.Txs, Height: currentHeight, ProposerAddress: pubkey.Address(), + Time: proposalTime, }) require.NoError(t, err) require.NotEmpty(t, finalizeResp) @@ -148,8 +150,9 @@ func (s *SharedAccessors) MoveChainToHeight( // Record the Commit Block proposedCometBlocks = append(proposedCometBlocks, proposal) + proposalTime = proposalTime.Add(time.Duration(s.TestNode.ChainSpec.TargetSecondsPerEth1Block()) * time.Second) } - return proposedCometBlocks + return proposedCometBlocks, proposalTime } // WaitTillServicesStarted waits until the log buffer contains "All services started". @@ -238,7 +241,7 @@ func ComputeAndSetInvalidExecutionBlock( txs []*gethprimitives.Transaction, ) *ctypes.BeaconBlock { t.Helper() - forkVersion := chainSpec.ActiveForkVersionForSlot(latestBlock.GetSlot()) + forkVersion := chainSpec.ActiveForkVersionForTimestamp(latestBlock.GetTimestamp()) _, sidecars := splitTxs(txs) // Use the current execution payload (e.g. for an invalid block, no simulation is done). executionPayload := latestBlock.GetBody().GetExecutionPayload() @@ -275,7 +278,7 @@ func ComputeAndSetValidExecutionBlock( require.Len(t, simulatedBlocks, 1) simBlock := simulatedBlocks[0] - forkVersion := chainSpec.ActiveForkVersionForSlot(latestBlock.GetSlot()) + forkVersion := chainSpec.ActiveForkVersionForTimestamp(latestBlock.GetTimestamp()) txsNoSidecar, sidecars := splitTxs(txs) origParent := latestBlock.GetParentBlockRoot() diff --git a/testing/simulated/valid_chain_test.go b/testing/simulated/valid_chain_test.go index 2430a96fe7..3a5cbe47d5 100644 --- a/testing/simulated/valid_chain_test.go +++ b/testing/simulated/valid_chain_test.go @@ -57,8 +57,11 @@ func (s *SimulatedSuite) TestFullLifecycle_ValidBlock_IsSuccessful() { // Retrieve the BLS signer and proposer address. blsSigner := simulated.GetBlsSigner(s.HomeDir) + // Test happens post Deneb1 fork. + startTime := time.Now() + // iterate through the core loop `coreLoopIterations` times, i.e. Propose, Process, Finalize and Commit. - proposals := s.MoveChainToHeight(s.T(), blockHeight, coreLoopIterations, blsSigner) + proposals, _ := s.MoveChainToHeight(s.T(), blockHeight, coreLoopIterations, blsSigner, startTime) // We expect that the number of proposals that were finalized should be `coreLoopIterations`. s.Require().Len(proposals, coreLoopIterations) @@ -76,11 +79,14 @@ func (s *SimulatedSuite) TestFullLifecycle_ValidBlock_IsSuccessful() { stateHeader, err := stateDB.GetLatestBlockHeader() s.Require().NoError(err) + lph, err := stateDB.GetLatestExecutionPayloadHeader() + s.Require().NoError(err) + // Unmarshal the beacon block from the ABCI request. proposedBlock, err := encoding.UnmarshalBeaconBlockFromABCIRequest( proposals[len(proposals)-1].Txs, blockchain.BeaconBlockTxIndex, - s.TestNode.ChainSpec.ActiveForkVersionForSlot(slot), + s.TestNode.ChainSpec.ActiveForkVersionForTimestamp(lph.GetTimestamp()), ) s.Require().NoError(err) s.Require().Equal(proposedBlock.GetHeader().GetBodyRoot(), stateHeader.GetBodyRoot()) @@ -100,13 +106,17 @@ func (s *SimulatedSuite) TestFullLifecycle_ValidBlockWithInjectedTransaction_IsS pubkey, err := blsSigner.GetPubKey() s.Require().NoError(err) + // Test happens on Deneb, pre Deneb1 fork. + startTime := time.Unix(0, 0) + // Go through 1 iteration of the core loop to bypass any startup specific edge cases such as sync head on startup. - proposals := s.MoveChainToHeight(s.T(), blockHeight, coreLoopIterations, blsSigner) + proposals, consensusTime := s.MoveChainToHeight(s.T(), blockHeight, coreLoopIterations, blsSigner, startTime) s.Require().Len(proposals, coreLoopIterations) + // We expected this test to happen during Pre-Deneb1 fork. currentHeight := int64(blockHeight + coreLoopIterations) + // Prepare a valid block proposal. - consensusTime := time.Now() proposal, err := s.SimComet.Comet.PrepareProposal(s.CtxComet, &types.PrepareProposalRequest{ Height: currentHeight, Time: consensusTime, @@ -119,7 +129,7 @@ func (s *SimulatedSuite) TestFullLifecycle_ValidBlockWithInjectedTransaction_IsS proposedBlock, err := encoding.UnmarshalBeaconBlockFromABCIRequest( proposal.Txs, blockchain.BeaconBlockTxIndex, - s.TestNode.ChainSpec.ActiveForkVersionForSlot(math.Slot(currentHeight)), + s.TestNode.ChainSpec.ActiveForkVersionForTimestamp(math.U64(consensusTime.Unix())), ) s.Require().NoError(err) @@ -156,7 +166,7 @@ func (s *SimulatedSuite) TestFullLifecycle_ValidBlockWithInjectedTransaction_IsS newSignedBlock, err := ctypes.NewSignedBeaconBlock( finalBlock, &ctypes.ForkData{ - CurrentVersion: s.TestNode.ChainSpec.ActiveForkVersionForSlot(unsignedBlock.GetSlot()), + CurrentVersion: s.TestNode.ChainSpec.ActiveForkVersionForTimestamp(unsignedBlock.GetTimestamp()), GenesisValidatorsRoot: s.GenesisValidatorsRoot, }, s.TestNode.ChainSpec, @@ -187,6 +197,7 @@ func (s *SimulatedSuite) TestFullLifecycle_ValidBlockWithInjectedTransaction_IsS Txs: proposal.Txs, Height: currentHeight, ProposerAddress: pubkey.Address(), + Time: consensusTime, }) s.Require().NoError(err) s.Require().NotEmpty(finalizeResp) @@ -209,14 +220,17 @@ func (s *SimulatedSuite) TestFullLifecycle_ValidBlockAndInjectedBlob_IsSuccessfu pubkey, err := blsSigner.GetPubKey() s.Require().NoError(err) + // Test happens on Deneb, pre Deneb1 fork. + startTime := time.Unix(0, 0) + // Go through 1 iteration of the core loop to bypass any startup specific edge cases such as sync head on startup. - proposals := s.MoveChainToHeight(s.T(), blockHeight, coreLoopIterations, blsSigner) + proposals, consensusTime := s.MoveChainToHeight(s.T(), blockHeight, coreLoopIterations, blsSigner, startTime) s.Require().Len(proposals, coreLoopIterations) + // We expected this test to happen during Pre-Deneb1 fork. currentHeight := int64(blockHeight + coreLoopIterations) // Prepare a valid block proposal. - consensusTime := time.Now() proposal, err := s.SimComet.Comet.PrepareProposal(s.CtxComet, &types.PrepareProposalRequest{ Height: currentHeight, Time: consensusTime, @@ -229,7 +243,7 @@ func (s *SimulatedSuite) TestFullLifecycle_ValidBlockAndInjectedBlob_IsSuccessfu proposedBlock, err := encoding.UnmarshalBeaconBlockFromABCIRequest( proposal.Txs, blockchain.BeaconBlockTxIndex, - s.TestNode.ChainSpec.ActiveForkVersionForSlot(blockHeight), + s.TestNode.ChainSpec.ActiveForkVersionForTimestamp(math.U64(consensusTime.Unix())), ) s.Require().NoError(err) @@ -300,7 +314,7 @@ func (s *SimulatedSuite) TestFullLifecycle_ValidBlockAndInjectedBlob_IsSuccessfu newSignedBlock, err := ctypes.NewSignedBeaconBlock( proposedBlockMessage, &ctypes.ForkData{ - CurrentVersion: s.TestNode.ChainSpec.ActiveForkVersionForSlot(proposedBlockMessage.GetSlot()), + CurrentVersion: s.TestNode.ChainSpec.ActiveForkVersionForTimestamp(proposedBlockMessage.GetTimestamp()), GenesisValidatorsRoot: s.GenesisValidatorsRoot, }, s.TestNode.ChainSpec, @@ -359,6 +373,7 @@ func (s *SimulatedSuite) TestFullLifecycle_ValidBlockAndInjectedBlob_IsSuccessfu Txs: proposal.Txs, Height: currentHeight, ProposerAddress: pubkey.Address(), + Time: consensusTime, }) s.Require().NoError(err) s.Require().NotEmpty(finalizeResp)