diff --git a/beacon/engine/gen_blockparams.go b/beacon/engine/gen_blockparams.go index c343e58906..0b8df2b1d6 100644 --- a/beacon/engine/gen_blockparams.go +++ b/beacon/engine/gen_blockparams.go @@ -24,6 +24,7 @@ func (p PayloadAttributes) MarshalJSON() ([]byte, error) { Transactions []hexutil.Bytes `json:"transactions,omitempty" gencodec:"optional"` NoTxPool bool `json:"noTxPool,omitempty" gencodec:"optional"` GasLimit *hexutil.Uint64 `json:"gasLimit,omitempty" gencodec:"optional"` + Milliseconds hexutil.Uint64 `json:"milliseconds" gencodec:"optional"` } var enc PayloadAttributes enc.Timestamp = hexutil.Uint64(p.Timestamp) @@ -39,6 +40,7 @@ func (p PayloadAttributes) MarshalJSON() ([]byte, error) { } enc.NoTxPool = p.NoTxPool enc.GasLimit = (*hexutil.Uint64)(p.GasLimit) + enc.Milliseconds = hexutil.Uint64(p.Milliseconds) return json.Marshal(&enc) } @@ -53,6 +55,7 @@ func (p *PayloadAttributes) UnmarshalJSON(input []byte) error { Transactions []hexutil.Bytes `json:"transactions,omitempty" gencodec:"optional"` NoTxPool *bool `json:"noTxPool,omitempty" gencodec:"optional"` GasLimit *hexutil.Uint64 `json:"gasLimit,omitempty" gencodec:"optional"` + Milliseconds *hexutil.Uint64 `json:"milliseconds" gencodec:"optional"` } var dec PayloadAttributes if err := json.Unmarshal(input, &dec); err != nil { @@ -88,5 +91,8 @@ func (p *PayloadAttributes) UnmarshalJSON(input []byte) error { if dec.GasLimit != nil { p.GasLimit = (*uint64)(dec.GasLimit) } + if dec.Milliseconds != nil { + p.Milliseconds = uint64(*dec.Milliseconds) + } return nil } diff --git a/beacon/engine/gen_ed.go b/beacon/engine/gen_ed.go index 6893d64a16..4900dda8b1 100644 --- a/beacon/engine/gen_ed.go +++ b/beacon/engine/gen_ed.go @@ -34,6 +34,7 @@ func (e ExecutableData) MarshalJSON() ([]byte, error) { Withdrawals []*types.Withdrawal `json:"withdrawals"` BlobGasUsed *hexutil.Uint64 `json:"blobGasUsed"` ExcessBlobGas *hexutil.Uint64 `json:"excessBlobGas"` + Milliseconds hexutil.Uint64 `json:"milliseconds" gencodec:"optional"` } var enc ExecutableData enc.ParentHash = e.ParentHash @@ -58,6 +59,7 @@ func (e ExecutableData) MarshalJSON() ([]byte, error) { enc.Withdrawals = e.Withdrawals enc.BlobGasUsed = (*hexutil.Uint64)(e.BlobGasUsed) enc.ExcessBlobGas = (*hexutil.Uint64)(e.ExcessBlobGas) + enc.Milliseconds = hexutil.Uint64(e.Milliseconds) return json.Marshal(&enc) } @@ -81,6 +83,7 @@ func (e *ExecutableData) UnmarshalJSON(input []byte) error { Withdrawals []*types.Withdrawal `json:"withdrawals"` BlobGasUsed *hexutil.Uint64 `json:"blobGasUsed"` ExcessBlobGas *hexutil.Uint64 `json:"excessBlobGas"` + Milliseconds *hexutil.Uint64 `json:"milliseconds" gencodec:"optional"` } var dec ExecutableData if err := json.Unmarshal(input, &dec); err != nil { @@ -154,5 +157,8 @@ func (e *ExecutableData) UnmarshalJSON(input []byte) error { if dec.ExcessBlobGas != nil { e.ExcessBlobGas = (*uint64)(dec.ExcessBlobGas) } + if dec.Milliseconds != nil { + e.Milliseconds = uint64(*dec.Milliseconds) + } return nil } diff --git a/beacon/engine/types.go b/beacon/engine/types.go index 9a3ea8d077..46b25957d6 100644 --- a/beacon/engine/types.go +++ b/beacon/engine/types.go @@ -54,6 +54,8 @@ type PayloadAttributes struct { NoTxPool bool `json:"noTxPool,omitempty" gencodec:"optional"` // GasLimit is a field for rollups: if set, this sets the exact gas limit the block produced with. GasLimit *uint64 `json:"gasLimit,omitempty" gencodec:"optional"` + + Milliseconds uint64 `json:"milliseconds" gencodec:"optional"` } // JSON type overrides for PayloadAttributes. @@ -62,6 +64,7 @@ type payloadAttributesMarshaling struct { Transactions []hexutil.Bytes GasLimit *hexutil.Uint64 + Milliseconds hexutil.Uint64 } //go:generate go run github.com/fjl/gencodec -type ExecutableData -field-override executableDataMarshaling -out gen_ed.go @@ -85,6 +88,7 @@ type ExecutableData struct { Withdrawals []*types.Withdrawal `json:"withdrawals"` BlobGasUsed *uint64 `json:"blobGasUsed"` ExcessBlobGas *uint64 `json:"excessBlobGas"` + Milliseconds uint64 `json:"milliseconds" gencodec:"optional"` } // JSON type overrides for executableData. @@ -99,6 +103,7 @@ type executableDataMarshaling struct { Transactions []hexutil.Bytes BlobGasUsed *hexutil.Uint64 ExcessBlobGas *hexutil.Uint64 + Milliseconds hexutil.Uint64 } //go:generate go run github.com/fjl/gencodec -type ExecutionPayloadEnvelope -field-override executionPayloadEnvelopeMarshaling -out gen_epe.go @@ -267,6 +272,7 @@ func ExecutableDataToBlock(params ExecutableData, versionedHashes []common.Hash, ExcessBlobGas: params.ExcessBlobGas, BlobGasUsed: params.BlobGasUsed, ParentBeaconRoot: beaconRoot, + Milliseconds: params.Milliseconds, } block := types.NewBlockWithHeader(header).WithBody(txs, nil /* uncles */).WithWithdrawals(params.Withdrawals) if block.Hash() != params.BlockHash { @@ -296,6 +302,7 @@ func BlockToExecutableData(block *types.Block, fees *big.Int, sidecars []*types. Withdrawals: block.Withdrawals(), BlobGasUsed: block.BlobGasUsed(), ExcessBlobGas: block.ExcessBlobGas(), + Milliseconds: block.Milliseconds(), } bundle := BlobsBundleV1{ Commitments: make([]hexutil.Bytes, 0), diff --git a/core/chain_makers.go b/core/chain_makers.go index 9da48c447a..6a5d5be21a 100644 --- a/core/chain_makers.go +++ b/core/chain_makers.go @@ -280,14 +280,18 @@ func (b *BlockGen) PrevBlock(index int) *types.Block { return b.cm.chain[index] } -// OffsetTime modifies the time instance of a block, implicitly changing its +// OffsetTime modifies the time and milliseconds instance of a block, implicitly changing its // associated difficulty. It's useful to test scenarios where forking is not // tied to chain length directly. func (b *BlockGen) OffsetTime(seconds int64) { b.header.Time += uint64(seconds) + b.header.Milliseconds += uint64(seconds * 1000) if b.header.Time <= b.cm.bottom.Header().Time { panic("block time out of range") } + if b.header.Milliseconds <= b.cm.bottom.Header().Milliseconds { + panic("block time in milliseconds out of range") + } b.header.Difficulty = b.engine.CalcDifficulty(b.cm, b.header.Time, b.parent.Header()) } @@ -420,13 +424,14 @@ func GenerateChainWithGenesis(genesis *Genesis, engine consensus.Engine, n int, func (cm *chainMaker) makeHeader(parent *types.Block, state *state.StateDB, engine consensus.Engine) *types.Header { time := parent.Time() + 10 // block time is fixed at 10 seconds header := &types.Header{ - Root: state.IntermediateRoot(cm.config.IsEIP158(parent.Number())), - ParentHash: parent.Hash(), - Coinbase: parent.Coinbase(), - Difficulty: engine.CalcDifficulty(cm, time, parent.Header()), - GasLimit: parent.GasLimit(), - Number: new(big.Int).Add(parent.Number(), common.Big1), - Time: time, + Root: state.IntermediateRoot(cm.config.IsEIP158(parent.Number())), + ParentHash: parent.Hash(), + Coinbase: parent.Coinbase(), + Difficulty: engine.CalcDifficulty(cm, time, parent.Header()), + GasLimit: parent.GasLimit(), + Number: new(big.Int).Add(parent.Number(), common.Big1), + Time: time, + Milliseconds: parent.Milliseconds() + 10*1000, } if cm.config.IsLondon(header.Number) { diff --git a/core/gen_genesis.go b/core/gen_genesis.go index 3a57ec65e8..8d7fcaf969 100644 --- a/core/gen_genesis.go +++ b/core/gen_genesis.go @@ -19,22 +19,23 @@ var _ = (*genesisSpecMarshaling)(nil) // MarshalJSON marshals as JSON. func (g Genesis) MarshalJSON() ([]byte, error) { type Genesis struct { - Config *params.ChainConfig `json:"config"` - Nonce math.HexOrDecimal64 `json:"nonce"` - Timestamp math.HexOrDecimal64 `json:"timestamp"` - ExtraData hexutil.Bytes `json:"extraData"` - GasLimit math.HexOrDecimal64 `json:"gasLimit" gencodec:"required"` - Difficulty *math.HexOrDecimal256 `json:"difficulty" gencodec:"required"` - Mixhash common.Hash `json:"mixHash"` - Coinbase common.Address `json:"coinbase"` + Config *params.ChainConfig `json:"config"` + Nonce math.HexOrDecimal64 `json:"nonce"` + Timestamp math.HexOrDecimal64 `json:"timestamp"` + ExtraData hexutil.Bytes `json:"extraData"` + GasLimit math.HexOrDecimal64 `json:"gasLimit" gencodec:"required"` + Difficulty *math.HexOrDecimal256 `json:"difficulty" gencodec:"required"` + Mixhash common.Hash `json:"mixHash"` + Coinbase common.Address `json:"coinbase"` Alloc map[common.UnprefixedAddress]types.Account `json:"alloc" gencodec:"required"` - Number math.HexOrDecimal64 `json:"number"` - GasUsed math.HexOrDecimal64 `json:"gasUsed"` - ParentHash common.Hash `json:"parentHash"` - BaseFee *math.HexOrDecimal256 `json:"baseFeePerGas"` - ExcessBlobGas *math.HexOrDecimal64 `json:"excessBlobGas"` - BlobGasUsed *math.HexOrDecimal64 `json:"blobGasUsed"` - StateHash *common.Hash `json:"stateHash,omitempty"` + Number math.HexOrDecimal64 `json:"number"` + GasUsed math.HexOrDecimal64 `json:"gasUsed"` + ParentHash common.Hash `json:"parentHash"` + BaseFee *math.HexOrDecimal256 `json:"baseFeePerGas"` + ExcessBlobGas *math.HexOrDecimal64 `json:"excessBlobGas"` + BlobGasUsed *math.HexOrDecimal64 `json:"blobGasUsed"` + StateHash *common.Hash `json:"stateHash,omitempty"` + Milliseconds uint64 `json:"milliseconds"` } var enc Genesis enc.Config = g.Config @@ -58,28 +59,30 @@ func (g Genesis) MarshalJSON() ([]byte, error) { enc.ExcessBlobGas = (*math.HexOrDecimal64)(g.ExcessBlobGas) enc.BlobGasUsed = (*math.HexOrDecimal64)(g.BlobGasUsed) enc.StateHash = g.StateHash + enc.Milliseconds = g.Milliseconds return json.Marshal(&enc) } // UnmarshalJSON unmarshals from JSON. func (g *Genesis) UnmarshalJSON(input []byte) error { type Genesis struct { - Config *params.ChainConfig `json:"config"` - Nonce *math.HexOrDecimal64 `json:"nonce"` - Timestamp *math.HexOrDecimal64 `json:"timestamp"` - ExtraData *hexutil.Bytes `json:"extraData"` - GasLimit *math.HexOrDecimal64 `json:"gasLimit" gencodec:"required"` - Difficulty *math.HexOrDecimal256 `json:"difficulty" gencodec:"required"` - Mixhash *common.Hash `json:"mixHash"` - Coinbase *common.Address `json:"coinbase"` - Alloc map[common.UnprefixedAddress]GenesisAccount `json:"alloc" gencodec:"required"` - Number *math.HexOrDecimal64 `json:"number"` - GasUsed *math.HexOrDecimal64 `json:"gasUsed"` - ParentHash *common.Hash `json:"parentHash"` - BaseFee *math.HexOrDecimal256 `json:"baseFeePerGas"` - ExcessBlobGas *math.HexOrDecimal64 `json:"excessBlobGas"` - BlobGasUsed *math.HexOrDecimal64 `json:"blobGasUsed"` - StateHash *common.Hash `json:"stateHash,omitempty"` + Config *params.ChainConfig `json:"config"` + Nonce *math.HexOrDecimal64 `json:"nonce"` + Timestamp *math.HexOrDecimal64 `json:"timestamp"` + ExtraData *hexutil.Bytes `json:"extraData"` + GasLimit *math.HexOrDecimal64 `json:"gasLimit" gencodec:"required"` + Difficulty *math.HexOrDecimal256 `json:"difficulty" gencodec:"required"` + Mixhash *common.Hash `json:"mixHash"` + Coinbase *common.Address `json:"coinbase"` + Alloc map[common.UnprefixedAddress]types.Account `json:"alloc" gencodec:"required"` + Number *math.HexOrDecimal64 `json:"number"` + GasUsed *math.HexOrDecimal64 `json:"gasUsed"` + ParentHash *common.Hash `json:"parentHash"` + BaseFee *math.HexOrDecimal256 `json:"baseFeePerGas"` + ExcessBlobGas *math.HexOrDecimal64 `json:"excessBlobGas"` + BlobGasUsed *math.HexOrDecimal64 `json:"blobGasUsed"` + StateHash *common.Hash `json:"stateHash,omitempty"` + Milliseconds *uint64 `json:"milliseconds"` } var dec Genesis if err := json.Unmarshal(input, &dec); err != nil { @@ -139,5 +142,8 @@ func (g *Genesis) UnmarshalJSON(input []byte) error { if dec.StateHash != nil { g.StateHash = dec.StateHash } + if dec.Milliseconds != nil { + g.Milliseconds = *dec.Milliseconds + } return nil } diff --git a/core/genesis.go b/core/genesis.go index 1df8934a17..586540628a 100644 --- a/core/genesis.go +++ b/core/genesis.go @@ -79,6 +79,8 @@ type Genesis struct { // Chains with history pruning, or extraordinarily large genesis allocation (e.g. after a regenesis event) // may utilize this to get started, and then state-sync the latest state, while still verifying the header chain. StateHash *common.Hash `json:"stateHash,omitempty"` + + Milliseconds uint64 `json:"milliseconds"` } func ReadGenesis(db ethdb.Database) (*Genesis, error) { @@ -462,18 +464,19 @@ func (g *Genesis) ToBlock() *types.Block { panic(err) } head := &types.Header{ - Number: new(big.Int).SetUint64(g.Number), - Nonce: types.EncodeNonce(g.Nonce), - Time: g.Timestamp, - ParentHash: g.ParentHash, - Extra: g.ExtraData, - GasLimit: g.GasLimit, - GasUsed: g.GasUsed, - BaseFee: g.BaseFee, - Difficulty: g.Difficulty, - MixDigest: g.Mixhash, - Coinbase: g.Coinbase, - Root: root, + Number: new(big.Int).SetUint64(g.Number), + Nonce: types.EncodeNonce(g.Nonce), + Time: g.Timestamp, + ParentHash: g.ParentHash, + Extra: g.ExtraData, + GasLimit: g.GasLimit, + GasUsed: g.GasUsed, + BaseFee: g.BaseFee, + Difficulty: g.Difficulty, + MixDigest: g.Mixhash, + Coinbase: g.Coinbase, + Root: root, + Milliseconds: g.Milliseconds, } if g.GasLimit == 0 { head.GasLimit = params.GenesisGasLimit diff --git a/core/types/block.go b/core/types/block.go index 1a357baa3a..1d838443f9 100644 --- a/core/types/block.go +++ b/core/types/block.go @@ -63,22 +63,22 @@ func (n *BlockNonce) UnmarshalText(input []byte) error { // Header represents a block header in the Ethereum blockchain. type Header struct { - ParentHash common.Hash `json:"parentHash" gencodec:"required"` - UncleHash common.Hash `json:"sha3Uncles" gencodec:"required"` - Coinbase common.Address `json:"miner"` - Root common.Hash `json:"stateRoot" gencodec:"required"` - TxHash common.Hash `json:"transactionsRoot" gencodec:"required"` - ReceiptHash common.Hash `json:"receiptsRoot" gencodec:"required"` - Bloom Bloom `json:"logsBloom" gencodec:"required"` - Difficulty *big.Int `json:"difficulty" gencodec:"required"` - Number *big.Int `json:"number" gencodec:"required"` - GasLimit uint64 `json:"gasLimit" gencodec:"required"` - GasUsed uint64 `json:"gasUsed" gencodec:"required"` - Time uint64 `json:"timestamp" gencodec:"required"` - Extra []byte `json:"extraData" gencodec:"required"` - MixDigest common.Hash `json:"mixHash"` - Nonce BlockNonce `json:"nonce"` - + ParentHash common.Hash `json:"parentHash" gencodec:"required"` + UncleHash common.Hash `json:"sha3Uncles" gencodec:"required"` + Coinbase common.Address `json:"miner"` + Root common.Hash `json:"stateRoot" gencodec:"required"` + TxHash common.Hash `json:"transactionsRoot" gencodec:"required"` + ReceiptHash common.Hash `json:"receiptsRoot" gencodec:"required"` + Bloom Bloom `json:"logsBloom" gencodec:"required"` + Difficulty *big.Int `json:"difficulty" gencodec:"required"` + Number *big.Int `json:"number" gencodec:"required"` + GasLimit uint64 `json:"gasLimit" gencodec:"required"` + GasUsed uint64 `json:"gasUsed" gencodec:"required"` + Time uint64 `json:"timestamp" gencodec:"required"` + Extra []byte `json:"extraData" gencodec:"required"` + MixDigest common.Hash `json:"mixHash"` + Nonce BlockNonce `json:"nonce"` + Milliseconds uint64 `json:"milliseconds" gencodec:"optional"` // BaseFee was added by EIP-1559 and is ignored in legacy headers. BaseFee *big.Int `json:"baseFeePerGas" rlp:"optional"` @@ -107,6 +107,7 @@ type headerMarshaling struct { Hash common.Hash `json:"hash"` // adds call to Hash() in MarshalJSON BlobGasUsed *hexutil.Uint64 ExcessBlobGas *hexutil.Uint64 + Milliseconds hexutil.Uint64 } // Hash returns the block hash of the header, which is simply the keccak256 hash of its @@ -363,6 +364,7 @@ func (b *Block) GasLimit() uint64 { return b.header.GasLimit } func (b *Block) GasUsed() uint64 { return b.header.GasUsed } func (b *Block) Difficulty() *big.Int { return new(big.Int).Set(b.header.Difficulty) } func (b *Block) Time() uint64 { return b.header.Time } +func (b *Block) Milliseconds() uint64 { return b.header.Milliseconds } func (b *Block) NumberU64() uint64 { return b.header.Number.Uint64() } func (b *Block) MixDigest() common.Hash { return b.header.MixDigest } diff --git a/core/types/gen_header_json.go b/core/types/gen_header_json.go index fb1f915d01..c84308848c 100644 --- a/core/types/gen_header_json.go +++ b/core/types/gen_header_json.go @@ -31,6 +31,7 @@ func (h Header) MarshalJSON() ([]byte, error) { Extra hexutil.Bytes `json:"extraData" gencodec:"required"` MixDigest common.Hash `json:"mixHash"` Nonce BlockNonce `json:"nonce"` + Milliseconds hexutil.Uint64 `json:"milliseconds" gencodec:"optional"` BaseFee *hexutil.Big `json:"baseFeePerGas" rlp:"optional"` WithdrawalsHash *common.Hash `json:"withdrawalsRoot" rlp:"optional"` BlobGasUsed *hexutil.Uint64 `json:"blobGasUsed" rlp:"optional"` @@ -54,6 +55,7 @@ func (h Header) MarshalJSON() ([]byte, error) { enc.Extra = h.Extra enc.MixDigest = h.MixDigest enc.Nonce = h.Nonce + enc.Milliseconds = hexutil.Uint64(h.Milliseconds) enc.BaseFee = (*hexutil.Big)(h.BaseFee) enc.WithdrawalsHash = h.WithdrawalsHash enc.BlobGasUsed = (*hexutil.Uint64)(h.BlobGasUsed) @@ -81,6 +83,7 @@ func (h *Header) UnmarshalJSON(input []byte) error { Extra *hexutil.Bytes `json:"extraData" gencodec:"required"` MixDigest *common.Hash `json:"mixHash"` Nonce *BlockNonce `json:"nonce"` + Milliseconds *hexutil.Uint64 `json:"milliseconds" gencodec:"optional"` BaseFee *hexutil.Big `json:"baseFeePerGas" rlp:"optional"` WithdrawalsHash *common.Hash `json:"withdrawalsRoot" rlp:"optional"` BlobGasUsed *hexutil.Uint64 `json:"blobGasUsed" rlp:"optional"` @@ -148,6 +151,9 @@ func (h *Header) UnmarshalJSON(input []byte) error { if dec.Nonce != nil { h.Nonce = *dec.Nonce } + if dec.Milliseconds != nil { + h.Milliseconds = uint64(*dec.Milliseconds) + } if dec.BaseFee != nil { h.BaseFee = (*big.Int)(dec.BaseFee) } diff --git a/core/types/gen_header_rlp.go b/core/types/gen_header_rlp.go index ed6a1a002c..f5546c6b6c 100644 --- a/core/types/gen_header_rlp.go +++ b/core/types/gen_header_rlp.go @@ -37,6 +37,7 @@ func (obj *Header) EncodeRLP(_w io.Writer) error { w.WriteBytes(obj.Extra) w.WriteBytes(obj.MixDigest[:]) w.WriteBytes(obj.Nonce[:]) + w.WriteUint64(obj.Milliseconds) _tmp1 := obj.BaseFee != nil _tmp2 := obj.WithdrawalsHash != nil _tmp3 := obj.BlobGasUsed != nil diff --git a/eth/catalyst/api.go b/eth/catalyst/api.go index 18862bad63..3857ed4a86 100644 --- a/eth/catalyst/api.go +++ b/eth/catalyst/api.go @@ -393,6 +393,7 @@ func (api *ConsensusAPI) forkchoiceUpdated(update engine.ForkchoiceStateV1, payl Transactions: transactions, GasLimit: payloadAttributes.GasLimit, Version: payloadVersion, + Milliseconds: payloadAttributes.Milliseconds, } id := args.Id() // If we already are busy generating this work, then we do not need @@ -615,6 +616,10 @@ func (api *ConsensusAPI) newPayload(params engine.ExecutableData, versionedHashe log.Warn("Invalid timestamp", "parent", block.Time(), "block", block.Time()) return api.invalid(errors.New("invalid timestamp"), parent.Header()), nil } + if block.Milliseconds() <= parent.Milliseconds() { + log.Warn("Invalid milliseconds timestamp", "parent", block.Milliseconds(), "block", block.Milliseconds()) + return api.invalid(errors.New("invalid milliseconds timestamp"), parent.Header()), nil + } // Another corner case: if the node is in snap sync mode, but the CL client // tries to make it import a block. That should be denied as pushing something // into the database directly will conflict with the assumptions of snap sync diff --git a/eth/catalyst/api_test.go b/eth/catalyst/api_test.go index 3e0f3df405..b60c4959f6 100644 --- a/eth/catalyst/api_test.go +++ b/eth/catalyst/api_test.go @@ -77,10 +77,11 @@ func generateMergeChain(n int, merged bool) (*core.Genesis, []*types.Block) { testAddr: {Balance: testBalance}, params.BeaconRootsStorageAddress: {Balance: common.Big0, Code: common.Hex2Bytes("3373fffffffffffffffffffffffffffffffffffffffe14604457602036146024575f5ffd5b620180005f350680545f35146037575f5ffd5b6201800001545f5260205ff35b6201800042064281555f359062018000015500")}, }, - ExtraData: []byte("test genesis"), - Timestamp: 9000, - BaseFee: big.NewInt(params.InitialBaseFee), - Difficulty: big.NewInt(0), + ExtraData: []byte("test genesis"), + Timestamp: 9000, + BaseFee: big.NewInt(params.InitialBaseFee), + Difficulty: big.NewInt(0), + Milliseconds: 9000 * 1000, } testNonce := uint64(0) generate := func(i int, g *core.BlockGen) { @@ -116,7 +117,8 @@ func TestEth2AssembleBlock(t *testing.T) { } ethservice.TxPool().Add([]*types.Transaction{tx}, true, false) blockParams := engine.PayloadAttributes{ - Timestamp: blocks[9].Time() + 5, + Timestamp: blocks[9].Time() + 5, + Milliseconds: blocks[9].Milliseconds() + 5000, } // The miner needs to pick up on the txs in the pool, so a few retries might be // needed. @@ -153,7 +155,8 @@ func TestEth2AssembleBlockWithAnotherBlocksTxs(t *testing.T) { txs := blocks[9].Transactions() api.eth.TxPool().Add(txs, false, true) blockParams := engine.PayloadAttributes{ - Timestamp: blocks[8].Time() + 5, + Timestamp: blocks[8].Time() + 5, + Milliseconds: blocks[8].Milliseconds() + 5000, } // The miner needs to pick up on the txs in the pool, so a few retries might be // needed. @@ -193,7 +196,8 @@ func TestEth2PrepareAndGetPayload(t *testing.T) { txs := blocks[9].Transactions() ethservice.TxPool().Add(txs, true, false) blockParams := engine.PayloadAttributes{ - Timestamp: blocks[8].Time() + 5, + Timestamp: blocks[8].Time() + 5, + Milliseconds: blocks[8].Milliseconds() + 5000, } fcState := engine.ForkchoiceStateV1{ HeadBlockHash: blocks[8].Hash(), @@ -211,6 +215,7 @@ func TestEth2PrepareAndGetPayload(t *testing.T) { Random: blockParams.Random, BeaconRoot: blockParams.BeaconRoot, Version: engine.PayloadV1, + Milliseconds: blockParams.Milliseconds, }).Id() require.Equal(t, payloadID, *resp.PayloadID) require.NoError(t, waitForApiPayloadToBuild(api, *resp.PayloadID)) @@ -274,6 +279,7 @@ func TestInvalidPayloadTimestamp(t *testing.T) { Timestamp: test.time, Random: crypto.Keccak256Hash([]byte{byte(123)}), SuggestedFeeRecipient: parent.Coinbase, + Milliseconds: test.time * 1000, } fcState := engine.ForkchoiceStateV1{ HeadBlockHash: parent.Hash(), @@ -315,7 +321,8 @@ func TestEth2NewBlock(t *testing.T) { ethservice.TxPool().Add([]*types.Transaction{tx}, true, false) execData, err := assembleWithTransactions(api, parent.Hash(), &engine.PayloadAttributes{ - Timestamp: parent.Time() + 5, + Timestamp: parent.Time() + 5, + Milliseconds: parent.Milliseconds() + 5000, }, 1) if err != nil { t.Fatalf("Failed to create the executable data %v", err) @@ -357,7 +364,8 @@ func TestEth2NewBlock(t *testing.T) { parent = preMergeBlocks[len(preMergeBlocks)-1] for i := 0; i < 10; i++ { execData, err := assembleBlock(api, parent.Hash(), &engine.PayloadAttributes{ - Timestamp: parent.Time() + 6, + Timestamp: parent.Time() + 6, + Milliseconds: parent.Milliseconds() + 6000, }) if err != nil { t.Fatalf("Failed to create the executable data %v", err) @@ -618,6 +626,7 @@ func TestNewPayloadOnInvalidChain(t *testing.T) { Timestamp: parent.Time + 1, Random: crypto.Keccak256Hash([]byte{byte(i)}), SuggestedFeeRecipient: parent.Coinbase, + Milliseconds: parent.Milliseconds + 1000, } fcState = engine.ForkchoiceStateV1{ HeadBlockHash: parent.Hash(), @@ -644,6 +653,7 @@ func TestNewPayloadOnInvalidChain(t *testing.T) { } // No luck this time we need to update the params and try again. params.Timestamp = params.Timestamp + 1 + params.Milliseconds = params.Milliseconds + 1000 if i > 10 { t.Fatalf("payload should not be empty") } @@ -678,6 +688,7 @@ func assembleBlock(api *ConsensusAPI, parentHash common.Hash, params *engine.Pay Random: params.Random, Withdrawals: params.Withdrawals, BeaconRoot: params.BeaconRoot, + Milliseconds: params.Milliseconds, } payload, err := api.eth.Miner().BuildPayload(args) if err != nil { @@ -753,6 +764,7 @@ func getNewPayload(t *testing.T, api *ConsensusAPI, parent *types.Header, withdr Random: crypto.Keccak256Hash([]byte{byte(1)}), SuggestedFeeRecipient: parent.Coinbase, Withdrawals: withdrawals, + Milliseconds: parent.Milliseconds + 1000, } payload, err := assembleBlock(api, parent.Hash(), ¶ms) @@ -769,21 +781,22 @@ func setBlockhash(data *engine.ExecutableData) *engine.ExecutableData { number := big.NewInt(0) number.SetUint64(data.Number) header := &types.Header{ - ParentHash: data.ParentHash, - UncleHash: types.EmptyUncleHash, - Coinbase: data.FeeRecipient, - Root: data.StateRoot, - TxHash: types.DeriveSha(types.Transactions(txs), trie.NewStackTrie(nil)), - ReceiptHash: data.ReceiptsRoot, - Bloom: types.BytesToBloom(data.LogsBloom), - Difficulty: common.Big0, - Number: number, - GasLimit: data.GasLimit, - GasUsed: data.GasUsed, - Time: data.Timestamp, - BaseFee: data.BaseFeePerGas, - Extra: data.ExtraData, - MixDigest: data.Random, + ParentHash: data.ParentHash, + UncleHash: types.EmptyUncleHash, + Coinbase: data.FeeRecipient, + Root: data.StateRoot, + TxHash: types.DeriveSha(types.Transactions(txs), trie.NewStackTrie(nil)), + ReceiptHash: data.ReceiptsRoot, + Bloom: types.BytesToBloom(data.LogsBloom), + Difficulty: common.Big0, + Number: number, + GasLimit: data.GasLimit, + GasUsed: data.GasUsed, + Time: data.Timestamp, + BaseFee: data.BaseFeePerGas, + Extra: data.ExtraData, + MixDigest: data.Random, + Milliseconds: data.Milliseconds, } block := types.NewBlockWithHeader(header).WithBody(txs, nil /* uncles */) data.BlockHash = block.Hash() @@ -917,6 +930,7 @@ func TestNewPayloadOnInvalidTerminalBlock(t *testing.T) { Timestamp: parent.Time() + 1, Random: crypto.Keccak256Hash([]byte{byte(1)}), FeeRecipient: parent.Coinbase(), + Milliseconds: parent.Milliseconds() + 1000, } payload, err := api.eth.Miner().BuildPayload(args) if err != nil { @@ -927,21 +941,22 @@ func TestNewPayloadOnInvalidTerminalBlock(t *testing.T) { // We need to recompute the blockhash, since the miner computes a wrong (correct) blockhash txs, _ := decodeTransactions(data.Transactions) header := &types.Header{ - ParentHash: data.ParentHash, - UncleHash: types.EmptyUncleHash, - Coinbase: data.FeeRecipient, - Root: data.StateRoot, - TxHash: types.DeriveSha(types.Transactions(txs), trie.NewStackTrie(nil)), - ReceiptHash: data.ReceiptsRoot, - Bloom: types.BytesToBloom(data.LogsBloom), - Difficulty: common.Big0, - Number: new(big.Int).SetUint64(data.Number), - GasLimit: data.GasLimit, - GasUsed: data.GasUsed, - Time: data.Timestamp, - BaseFee: data.BaseFeePerGas, - Extra: data.ExtraData, - MixDigest: data.Random, + ParentHash: data.ParentHash, + UncleHash: types.EmptyUncleHash, + Coinbase: data.FeeRecipient, + Root: data.StateRoot, + TxHash: types.DeriveSha(types.Transactions(txs), trie.NewStackTrie(nil)), + ReceiptHash: data.ReceiptsRoot, + Bloom: types.BytesToBloom(data.LogsBloom), + Difficulty: common.Big0, + Number: new(big.Int).SetUint64(data.Number), + GasLimit: data.GasLimit, + GasUsed: data.GasUsed, + Time: data.Timestamp, + BaseFee: data.BaseFeePerGas, + Extra: data.ExtraData, + MixDigest: data.Random, + Milliseconds: data.Milliseconds, } block := types.NewBlockWithHeader(header).WithBody(txs, nil /* uncles */) data.BlockHash = block.Hash() @@ -969,7 +984,8 @@ func TestSimultaneousNewBlock(t *testing.T) { ) for i := 0; i < 10; i++ { execData, err := assembleBlock(api, parent.Hash(), &engine.PayloadAttributes{ - Timestamp: parent.Time() + 5, + Timestamp: parent.Time() + 5, + Milliseconds: parent.Milliseconds() + 5000, }) if err != nil { t.Fatalf("Failed to create the executable data %v", err) @@ -1060,8 +1076,9 @@ func TestWithdrawals(t *testing.T) { // 10: Build Shanghai block with no withdrawals. parent := ethservice.BlockChain().CurrentHeader() blockParams := engine.PayloadAttributes{ - Timestamp: parent.Time + 5, - Withdrawals: make([]*types.Withdrawal, 0), + Timestamp: parent.Time + 5, + Withdrawals: make([]*types.Withdrawal, 0), + Milliseconds: parent.Milliseconds + 5000, } fcState := engine.ForkchoiceStateV1{ HeadBlockHash: parent.Hash(), @@ -1083,6 +1100,7 @@ func TestWithdrawals(t *testing.T) { Withdrawals: blockParams.Withdrawals, BeaconRoot: blockParams.BeaconRoot, Version: engine.PayloadV2, + Milliseconds: blockParams.Milliseconds, }).Id() require.Equal(t, payloadID, *resp.PayloadID) require.NoError(t, waitForApiPayloadToBuild(api, payloadID)) @@ -1118,6 +1136,7 @@ func TestWithdrawals(t *testing.T) { Amount: 33, }, }, + Milliseconds: execData.ExecutionPayload.Milliseconds + 5000, } fcState.HeadBlockHash = execData.ExecutionPayload.BlockHash resp, err = api.ForkchoiceUpdatedV2(fcState, &blockParams) @@ -1134,6 +1153,7 @@ func TestWithdrawals(t *testing.T) { Withdrawals: blockParams.Withdrawals, BeaconRoot: blockParams.BeaconRoot, Version: engine.PayloadV2, + Milliseconds: blockParams.Milliseconds, }).Id() require.Equal(t, payloadID, *resp.PayloadID) require.NoError(t, waitForApiPayloadToBuild(api, payloadID)) @@ -1189,15 +1209,17 @@ func TestNilWithdrawals(t *testing.T) { // Before Shanghai { blockParams: engine.PayloadAttributes{ - Timestamp: parent.Time + 2, - Withdrawals: nil, + Timestamp: parent.Time + 2, + Withdrawals: nil, + Milliseconds: parent.Milliseconds + 2000, }, wantErr: false, }, { blockParams: engine.PayloadAttributes{ - Timestamp: parent.Time + 2, - Withdrawals: make([]*types.Withdrawal, 0), + Timestamp: parent.Time + 2, + Withdrawals: make([]*types.Withdrawal, 0), + Milliseconds: parent.Milliseconds + 2000, }, wantErr: true, }, @@ -1211,21 +1233,24 @@ func TestNilWithdrawals(t *testing.T) { Amount: 32, }, }, + Milliseconds: parent.Milliseconds + 2000, }, wantErr: true, }, // After Shanghai { blockParams: engine.PayloadAttributes{ - Timestamp: parent.Time + 5, - Withdrawals: nil, + Timestamp: parent.Time + 5, + Withdrawals: nil, + Milliseconds: parent.Milliseconds + 5000, }, wantErr: true, }, { blockParams: engine.PayloadAttributes{ - Timestamp: parent.Time + 5, - Withdrawals: make([]*types.Withdrawal, 0), + Timestamp: parent.Time + 5, + Withdrawals: make([]*types.Withdrawal, 0), + Milliseconds: parent.Milliseconds + 5000, }, wantErr: false, }, @@ -1239,6 +1264,7 @@ func TestNilWithdrawals(t *testing.T) { Amount: 32, }, }, + Milliseconds: parent.Milliseconds + 5000, }, wantErr: false, }, @@ -1280,6 +1306,7 @@ func TestNilWithdrawals(t *testing.T) { BeaconRoot: test.blockParams.BeaconRoot, Withdrawals: test.blockParams.Withdrawals, Version: payloadVersion, + Milliseconds: test.blockParams.Milliseconds, }).Id() execData, err := api.GetPayloadV2(payloadID) if err != nil { @@ -1611,9 +1638,10 @@ func TestParentBeaconBlockRoot(t *testing.T) { // 11: Build Shanghai block with no withdrawals. parent := ethservice.BlockChain().CurrentHeader() blockParams := engine.PayloadAttributes{ - Timestamp: parent.Time + 5, - Withdrawals: make([]*types.Withdrawal, 0), - BeaconRoot: &common.Hash{42}, + Timestamp: parent.Time + 5, + Withdrawals: make([]*types.Withdrawal, 0), + BeaconRoot: &common.Hash{42}, + Milliseconds: parent.Milliseconds + 5000, } fcState := engine.ForkchoiceStateV1{ HeadBlockHash: parent.Hash(), @@ -1635,6 +1663,7 @@ func TestParentBeaconBlockRoot(t *testing.T) { Withdrawals: blockParams.Withdrawals, BeaconRoot: blockParams.BeaconRoot, Version: engine.PayloadV3, + Milliseconds: blockParams.Milliseconds, }).Id() require.Equal(t, payloadID, *resp.PayloadID) require.NoError(t, waitForApiPayloadToBuild(api, *resp.PayloadID)) diff --git a/internal/ethapi/api.go b/internal/ethapi/api.go index a0b64c86e3..6707c3c7af 100644 --- a/internal/ethapi/api.go +++ b/internal/ethapi/api.go @@ -1352,6 +1352,7 @@ func RPCMarshalHeader(head *types.Header) map[string]interface{} { "timestamp": hexutil.Uint64(head.Time), "transactionsRoot": head.TxHash, "receiptsRoot": head.ReceiptHash, + "milliseconds": hexutil.Uint64(head.Milliseconds), } if head.BaseFee != nil { result["baseFeePerGas"] = (*hexutil.Big)(head.BaseFee) diff --git a/miner/payload_building.go b/miner/payload_building.go index 833652669a..052bd850df 100644 --- a/miner/payload_building.go +++ b/miner/payload_building.go @@ -48,6 +48,7 @@ type BuildPayloadArgs struct { NoTxPool bool // Optimism addition: option to disable tx pool contents from being included Transactions []*types.Transaction // Optimism addition: txs forced into the block via engine API GasLimit *uint64 // Optimism addition: override gas limit of the block to build + Milliseconds uint64 } // Id computes an 8-byte identifier by hashing the components of the payload arguments. @@ -55,7 +56,7 @@ func (args *BuildPayloadArgs) Id() engine.PayloadID { // Hash hasher := sha256.New() hasher.Write(args.Parent[:]) - binary.Write(hasher, binary.BigEndian, args.Timestamp) + binary.Write(hasher, binary.BigEndian, args.Milliseconds) hasher.Write(args.Random[:]) hasher.Write(args.FeeRecipient[:]) rlp.Encode(hasher, args.Withdrawals) @@ -261,16 +262,17 @@ func (w *worker) buildPayload(args *BuildPayloadArgs) (*Payload, error) { // to deliver for not missing slot. // In OP-Stack, the "empty" block is constructed from provided txs only, i.e. no tx-pool usage. emptyParams := &generateParams{ - timestamp: args.Timestamp, - forceTime: true, - parentHash: args.Parent, - coinbase: args.FeeRecipient, - random: args.Random, - withdrawals: args.Withdrawals, - beaconRoot: args.BeaconRoot, - noTxs: true, - txs: args.Transactions, - gasLimit: args.GasLimit, + timestamp: args.Timestamp, + forceTime: true, + parentHash: args.Parent, + coinbase: args.FeeRecipient, + random: args.Random, + withdrawals: args.Withdrawals, + beaconRoot: args.BeaconRoot, + noTxs: true, + txs: args.Transactions, + gasLimit: args.GasLimit, + Milliseconds: args.Milliseconds, } empty := w.getSealingBlock(emptyParams) if empty.err != nil { @@ -285,16 +287,17 @@ func (w *worker) buildPayload(args *BuildPayloadArgs) (*Payload, error) { } fullParams := &generateParams{ - timestamp: args.Timestamp, - forceTime: true, - parentHash: args.Parent, - coinbase: args.FeeRecipient, - random: args.Random, - withdrawals: args.Withdrawals, - beaconRoot: args.BeaconRoot, - noTxs: false, - txs: args.Transactions, - gasLimit: args.GasLimit, + timestamp: args.Timestamp, + forceTime: true, + parentHash: args.Parent, + coinbase: args.FeeRecipient, + random: args.Random, + withdrawals: args.Withdrawals, + beaconRoot: args.BeaconRoot, + noTxs: false, + txs: args.Transactions, + gasLimit: args.GasLimit, + Milliseconds: args.Milliseconds, } // Since we skip building the empty block when using the tx pool, we need to explicitly diff --git a/miner/payload_building_test.go b/miner/payload_building_test.go index 3696a60f45..b4bc448288 100644 --- a/miner/payload_building_test.go +++ b/miner/payload_building_test.go @@ -62,6 +62,7 @@ func testBuildPayload(t *testing.T, noTxPool, interrupt bool) { Random: common.Hash{}, FeeRecipient: recipient, NoTxPool: noTxPool, + Milliseconds: timestamp * 1000, } // payload resolution now interrupts block building, so we have to // wait for the payloading building process to build its first block diff --git a/miner/worker.go b/miner/worker.go index a69e564410..09be2ba792 100644 --- a/miner/worker.go +++ b/miner/worker.go @@ -980,6 +980,8 @@ type generateParams struct { gasLimit *uint64 // Optional gas limit override interrupt *atomic.Int32 // Optional interruption signal to pass down to worker.generateWork isUpdate bool // Optional flag indicating that this is building a discardable update + + Milliseconds uint64 // The timestamp in milliseconds for sealing task } // validateParams validates the given parameters. @@ -1007,6 +1009,12 @@ func (w *worker) validateParams(genParams *generateParams) (time.Duration, error return 0, fmt.Errorf("invalid timestamp, parent %d given %d", parent.Time, genParams.timestamp) } + // Sanity check the milliseconds correctness + blockTimeMs := int64(genParams.Milliseconds) - int64(parent.Milliseconds) + if blockTimeMs <= 0 { + return 0, fmt.Errorf("invalid milliseconds, parent %d given %d", parent.Milliseconds, genParams.Milliseconds) + } + // minimum payload build time of 2s if blockTime < 2 { blockTime = 2 @@ -1039,13 +1047,21 @@ func (w *worker) prepareWork(genParams *generateParams) (*environment, error) { } timestamp = parent.Time + 1 } + milliseconds := genParams.Milliseconds + if parent.Milliseconds >= milliseconds { + if genParams.forceTime { + return nil, fmt.Errorf("invalid milliseconds timestamp, parent %d given %d", parent.Milliseconds, milliseconds) + } + milliseconds = parent.Milliseconds + 1000 + } // Construct the sealing block header. header := &types.Header{ - ParentHash: parent.Hash(), - Number: new(big.Int).Add(parent.Number, common.Big1), - GasLimit: core.CalcGasLimit(parent.GasLimit, w.config.GasCeil), - Time: timestamp, - Coinbase: genParams.coinbase, + ParentHash: parent.Hash(), + Number: new(big.Int).Add(parent.Number, common.Big1), + GasLimit: core.CalcGasLimit(parent.GasLimit, w.config.GasCeil), + Time: timestamp, + Coinbase: genParams.coinbase, + Milliseconds: milliseconds, } // Set the extra field. if len(w.extra) != 0 && w.chainConfig.Optimism == nil { // Optimism chains must not set any extra data. @@ -1243,6 +1259,7 @@ func (w *worker) commitWork(interrupt *atomic.Int32, timestamp int64) { work, err := w.prepareWork(&generateParams{ timestamp: uint64(timestamp), coinbase: coinbase, + // milliseconds? }) if err != nil { return