diff --git a/evmcore/dummy_block.go b/evmcore/dummy_block.go index 314b4de6..4314413f 100644 --- a/evmcore/dummy_block.go +++ b/evmcore/dummy_block.go @@ -17,7 +17,6 @@ package evmcore import ( - "encoding/binary" "math" "math/big" "time" @@ -135,9 +134,6 @@ func (h *EvmHeader) EthHeader() *types.Header { if h == nil { return nil } - extra := make([]byte, 16) - binary.BigEndian.PutUint64(extra[:8], uint64(h.Time)) - binary.BigEndian.PutUint64(extra[8:], uint64(h.Duration)) // NOTE: incomplete conversion ethHeader := &types.Header{ Number: h.Number, @@ -148,7 +144,7 @@ func (h *EvmHeader) EthHeader() *types.Header { TxHash: h.TxHash, ParentHash: h.ParentHash, Time: uint64(h.Time.Unix()), - Extra: extra, + Extra: inter.EncodeExtraData(h.Time.Time(), h.Duration), BaseFee: h.BaseFee, Difficulty: new(big.Int), @@ -195,7 +191,6 @@ type EvmBlockJson struct { } func (h *EvmHeader) ToJson(receipts types.Receipts) *EvmHeaderJson { - ethHeader := h.EthHeader() enc := &EvmHeaderJson{ Number: (*hexutil.Big)(h.Number), Miner: h.Coinbase, @@ -207,7 +202,7 @@ func (h *EvmHeader) ToJson(receipts types.Receipts) *EvmHeaderJson { UncleHash: types.EmptyUncleHash, Time: hexutil.Uint64(h.Time.Unix()), TimeNano: hexutil.Uint64(h.Time), - Extra: ethHeader.Extra, + Extra: inter.EncodeExtraData(h.Time.Time(), h.Duration), BaseFee: (*hexutil.Big)(h.BaseFee), Difficulty: new(hexutil.Big), PrevRandao: h.PrevRandao, diff --git a/gossip/apply_genesis.go b/gossip/apply_genesis.go index ae7baa8c..863abb19 100644 --- a/gossip/apply_genesis.go +++ b/gossip/apply_genesis.go @@ -77,14 +77,11 @@ func (s *Store) ApplyGenesis(g genesis.Genesis) (err error) { if rules.Upgrades.Sonic { block := s.GetBlock(br.Idx - 1) if block == nil { - block = &inter.Block{ - BaseFee: big.NewInt(0), - } + block = &inter.Block{BaseFee: new(big.Int)} } header := &evmcore.EvmHeader{ - GasUsed: block.GasUsed, - GasLimit: block.GasLimit, - BaseFee: block.BaseFee, + GasUsed: block.GasUsed, + BaseFee: block.BaseFee, } baseFee = gasprice.GetBaseFeeForNextBlock(header, rules.Economy) duration = time.Duration(br.Time-block.Time) * time.Nanosecond diff --git a/gossip/c_block_callbacks.go b/gossip/c_block_callbacks.go index 831af91c..add9d865 100644 --- a/gossip/c_block_callbacks.go +++ b/gossip/c_block_callbacks.go @@ -2,13 +2,14 @@ package gossip import ( "fmt" - "github.com/Fantom-foundation/go-opera/utils/signers/gsignercache" "math/big" "sort" "sync" "sync/atomic" "time" + "github.com/Fantom-foundation/go-opera/utils/signers/gsignercache" + "github.com/Fantom-foundation/lachesis-base/hash" "github.com/Fantom-foundation/lachesis-base/inter/dag" "github.com/Fantom-foundation/lachesis-base/inter/idx" @@ -381,17 +382,13 @@ func consensusCallbackBeginBlockFn( feed.newLogs.Send(logs) } - lastBlockTime := evmStateReader.GetHeader(common.Hash{}, uint64(blockCtx.Idx-1)).Time.Time() - thisBlockTime := block.Time.Time() - blockTime := thisBlockTime.Sub(lastBlockTime) - now := time.Now() blockAge := now.Sub(block.Time.Time()) log.Info("New block", "index", blockCtx.Idx, "id", block.Hash(), "gas_used", evmBlock.GasUsed, - "gas_rate", float64(evmBlock.GasUsed)/blockTime.Seconds(), + "gas_rate", float64(evmBlock.GasUsed)/blockDuration.Seconds(), "base_fee", evmBlock.BaseFee.String(), "txs", fmt.Sprintf("%d/%d", len(evmBlock.Transactions), len(skippedTxs)), "age", utils.PrettyDuration(blockAge), diff --git a/inter/block.go b/inter/block.go index d6e8e548..6e5c007f 100644 --- a/inter/block.go +++ b/inter/block.go @@ -2,6 +2,7 @@ package inter import ( "encoding/binary" + "errors" "math/big" "slices" "time" @@ -63,10 +64,6 @@ func (b *Block) Hash() common.Hash { // GetEthereumHeader returns the Ethereum header corresponding to this block. func (b *Block) GetEthereumHeader() *types.Header { - // TODO: consider condensing the extra data into a single 8-byte field. - extra := make([]byte, 16) - binary.BigEndian.PutUint64(extra[:8], uint64(b.Time)) // < only nano-second part needed - binary.BigEndian.PutUint64(extra[8:], uint64(b.Duration)) // < could be measured in microseconds instead of nanoseconds return &types.Header{ ParentHash: b.ParentHash, UncleHash: types.EmptyUncleHash, @@ -80,10 +77,13 @@ func (b *Block) GetEthereumHeader() *types.Header { GasLimit: b.GasLimit, GasUsed: b.GasUsed, Time: uint64(b.Time.Time().Unix()), - Extra: extra, - MixDigest: b.PrevRandao, - Nonce: types.BlockNonce{}, // constant 0 in Ethereum - BaseFee: b.BaseFee, + Extra: EncodeExtraData( + b.Time.Time(), + time.Duration(b.Duration)*time.Nanosecond, + ), + MixDigest: b.PrevRandao, + Nonce: types.BlockNonce{}, // constant 0 in Ethereum + BaseFee: b.BaseFee, // Sonic does not have a beacon chain and no withdrawals. WithdrawalsHash: &types.EmptyWithdrawalsHash, @@ -95,6 +95,37 @@ func (b *Block) GetEthereumHeader() *types.Header { } } +// EncodeExtraData produces the ExtraData field encoding Sonic-specific data +// in the Ethereum block header. This data includes: +// - the nano-second part of the block's timestamp, for sub-second precision; +// - the duration of the block, in nanoseconds, defined as the time elapsed +// between the predecessor block's timestamp and this block's timestamp. +// This is used for the computation of gas rates to adjust the base fee. +func EncodeExtraData(time time.Time, duration time.Duration) []byte { + if duration < 0 { + duration = 0 + } + extra := make([]byte, 12) + binary.BigEndian.PutUint32(extra[:4], uint32(time.Nanosecond())) + binary.BigEndian.PutUint64(extra[4:], uint64(duration.Nanoseconds())) + return extra +} + +// DecodeExtraData decodes the ExtraData field encoding Sonic-specific data +// in the Ethereum block header. See EncodeExtraData for details. +func DecodeExtraData(extra []byte) ( + nanos int, + duration time.Duration, + err error, +) { + if len(extra) != 12 { + return 0, 0, errors.New("extra data must be 12 bytes long") + } + return int(binary.BigEndian.Uint32(extra[:4])), + time.Duration(binary.BigEndian.Uint64(extra[4:])), + nil +} + func (b *Block) EstimateSize() int { return int(unsafe.Sizeof(*b)) + len(b.TransactionHashes)*int(unsafe.Sizeof(common.Hash{})) diff --git a/tests/block_header_test.go b/tests/block_header_test.go index 0eddb87c..15b79212 100644 --- a/tests/block_header_test.go +++ b/tests/block_header_test.go @@ -2,13 +2,13 @@ package tests import ( "context" - "encoding/binary" "math/big" "testing" "time" "github.com/Fantom-foundation/go-opera/evmcore" "github.com/Fantom-foundation/go-opera/gossip/gasprice" + "github.com/Fantom-foundation/go-opera/inter" "github.com/Fantom-foundation/go-opera/opera" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/types" @@ -109,13 +109,22 @@ func testHeaders_GasUsedIsBelowGasLimit(t *testing.T, headers []*types.Header) { func testHeaders_EncodesDurationAndNanoTimeInExtraData(t *testing.T, headers []*types.Header) { require := require.New(t) + + getUnixTime := func(header *types.Header) time.Time { + t.Helper() + nanos, _, err := inter.DecodeExtraData(header.Extra) + require.NoError(err) + return time.Unix(int64(header.Time), int64(nanos)) + } + // Check the nano-time and duration encoded in the extra data field. for i := 1; i < len(headers); i++ { - require.Equal(len(headers[i].Extra), 16, "extra data length of block %d", i) - lastTime := binary.BigEndian.Uint64(headers[i-1].Extra[:8]) - currentTime := binary.BigEndian.Uint64(headers[i].Extra[:8]) - wantedDuration := currentTime - lastTime - gotDuration := binary.BigEndian.Uint64(headers[i].Extra[8:]) + require.Equal(len(headers[i].Extra), 12, "extra data length of block %d", i) + lastTime := getUnixTime(headers[i-1]) + currentTime := getUnixTime(headers[i]) + wantedDuration := currentTime.Sub(lastTime) + _, gotDuration, err := inter.DecodeExtraData(headers[i].Extra) + require.NoError(err, "decoding extra data of block %d", i) require.Equal(wantedDuration, gotDuration, "duration of block %d", i) } } @@ -132,13 +141,12 @@ func testHeaders_BaseFeeEvolutionFollowsPricingRules(t *testing.T, headers []*ty // All other blocks compute the base-fee based on the previous block. for i := 1; i < len(headers); i++ { + _, duration, err := inter.DecodeExtraData(headers[i-1].Extra) + require.NoError(err, "decoding extra data of block %d", i-1) last := &evmcore.EvmHeader{ BaseFee: headers[i-1].BaseFee, - GasLimit: headers[i-1].GasLimit, GasUsed: headers[i-1].GasUsed, - Duration: time.Duration( - binary.BigEndian.Uint64(headers[i-1].Extra[8:]), - ), + Duration: duration, } require.Equal( gasprice.GetBaseFeeForNextBlock(last, rules),