Skip to content

Commit

Permalink
chore(core/types): header hooks JSON and RLP serialization
Browse files Browse the repository at this point in the history
- Implement `HeaderExtra` RLP and JSON serialization methods
- remove `BlockNonce`
- remove `EncodeNonce`
- new functions `GetHeaderExtra` and `WithHeaderExtra`
- Migrate existing custom `Header` to `HeaderSerializable` in block_ext.go with only `Hash` method for RLP code generation
  • Loading branch information
qdm12 committed Feb 12, 2025
1 parent 3e8d4e4 commit d52d44f
Show file tree
Hide file tree
Showing 16 changed files with 333 additions and 267 deletions.
38 changes: 20 additions & 18 deletions consensus/dummy/consensus.go
Original file line number Diff line number Diff line change
Expand Up @@ -168,12 +168,13 @@ func (eng *DummyEngine) verifyHeaderGasFields(config *params.ChainConfig, header
}

// Verify BlockGasCost, ExtDataGasUsed not present before AP4
headerExtra := types.GetHeaderExtra(header)
if !configExtra.IsApricotPhase4(header.Time) {
if header.BlockGasCost != nil {
return fmt.Errorf("invalid blockGasCost before fork: have %d, want <nil>", header.BlockGasCost)
if headerExtra.BlockGasCost != nil {
return fmt.Errorf("invalid blockGasCost before fork: have %d, want <nil>", headerExtra.BlockGasCost)
}
if header.ExtDataGasUsed != nil {
return fmt.Errorf("invalid extDataGasUsed before fork: have %d, want <nil>", header.ExtDataGasUsed)
if headerExtra.ExtDataGasUsed != nil {
return fmt.Errorf("invalid extDataGasUsed before fork: have %d, want <nil>", headerExtra.ExtDataGasUsed)
}
return nil
}
Expand All @@ -188,24 +189,24 @@ func (eng *DummyEngine) verifyHeaderGasFields(config *params.ChainConfig, header
ApricotPhase4MinBlockGasCost,
ApricotPhase4MaxBlockGasCost,
blockGasCostStep,
parent.BlockGasCost,
types.GetHeaderExtra(parent).BlockGasCost,
parent.Time, header.Time,
)
if header.BlockGasCost == nil {
if headerExtra.BlockGasCost == nil {
return errBlockGasCostNil
}
if !header.BlockGasCost.IsUint64() {
if !headerExtra.BlockGasCost.IsUint64() {
return errBlockGasCostTooLarge
}
if header.BlockGasCost.Cmp(expectedBlockGasCost) != 0 {
return fmt.Errorf("invalid block gas cost: have %d, want %d", header.BlockGasCost, expectedBlockGasCost)
if headerExtra.BlockGasCost.Cmp(expectedBlockGasCost) != 0 {
return fmt.Errorf("invalid block gas cost: have %d, want %d", headerExtra.BlockGasCost, expectedBlockGasCost)
}
// ExtDataGasUsed correctness is checked during block validation
// (when the validator has access to the block contents)
if header.ExtDataGasUsed == nil {
if headerExtra.ExtDataGasUsed == nil {
return errExtDataGasUsedNil
}
if !header.ExtDataGasUsed.IsUint64() {
if !headerExtra.ExtDataGasUsed.IsUint64() {
return errExtDataGasUsedTooLarge
}
return nil
Expand Down Expand Up @@ -417,7 +418,7 @@ func (eng *DummyEngine) Finalize(chain consensus.ChainHeaderReader, block *types
ApricotPhase4MinBlockGasCost,
ApricotPhase4MaxBlockGasCost,
blockGasCostStep,
parent.BlockGasCost,
types.GetHeaderExtra(parent).BlockGasCost,
parent.Time, block.Time(),
)
// Verify the BlockGasCost set in the header matches the calculated value.
Expand Down Expand Up @@ -454,28 +455,29 @@ func (eng *DummyEngine) FinalizeAndAssemble(chain consensus.ChainHeaderReader, h
}
}
chainConfigExtra := params.GetExtra(chain.Config())
headerExtra := types.GetHeaderExtra(header)
if chainConfigExtra.IsApricotPhase4(header.Time) {
header.ExtDataGasUsed = extDataGasUsed
if header.ExtDataGasUsed == nil {
header.ExtDataGasUsed = new(big.Int).Set(common.Big0)
headerExtra.ExtDataGasUsed = extDataGasUsed
if headerExtra.ExtDataGasUsed == nil {
headerExtra.ExtDataGasUsed = new(big.Int).Set(common.Big0)
}
blockGasCostStep := ApricotPhase4BlockGasCostStep
if chainConfigExtra.IsApricotPhase5(header.Time) {
blockGasCostStep = ApricotPhase5BlockGasCostStep
}
// Calculate the required block gas cost for this block.
header.BlockGasCost = calcBlockGasCost(
headerExtra.BlockGasCost = calcBlockGasCost(
ApricotPhase4TargetBlockRate,
ApricotPhase4MinBlockGasCost,
ApricotPhase4MaxBlockGasCost,
blockGasCostStep,
parent.BlockGasCost,
types.GetHeaderExtra(parent).BlockGasCost,
parent.Time, header.Time,
)
// Verify that this block covers the block fee.
if err := eng.verifyBlockFee(
header.BaseFee,
header.BlockGasCost,
headerExtra.BlockGasCost,
txs,
receipts,
contribution,
Expand Down
20 changes: 11 additions & 9 deletions consensus/dummy/dynamic_fees.go
Original file line number Diff line number Diff line change
Expand Up @@ -90,14 +90,15 @@ func CalcBaseFee(config *params.ChainConfig, parent *types.Header, timestamp uin
// gas in.
if roll < rollupWindow {
var blockGasCost, parentExtraStateGasUsed uint64
parentExtra := types.GetHeaderExtra(parent)
switch {
case isApricotPhase5:
// [blockGasCost] has been removed in AP5, so it is left as 0.

// At the start of a new network, the parent
// may not have a populated [ExtDataGasUsed].
if parent.ExtDataGasUsed != nil {
parentExtraStateGasUsed = parent.ExtDataGasUsed.Uint64()
if parentExtra.ExtDataGasUsed != nil {
parentExtraStateGasUsed = parentExtra.ExtDataGasUsed.Uint64()
}
case isApricotPhase4:
// The [blockGasCost] is paid by the effective tips in the block using
Expand All @@ -107,14 +108,14 @@ func CalcBaseFee(config *params.ChainConfig, parent *types.Header, timestamp uin
ApricotPhase4MinBlockGasCost,
ApricotPhase4MaxBlockGasCost,
ApricotPhase4BlockGasCostStep,
parent.BlockGasCost,
parentExtra.BlockGasCost,
parent.Time, timestamp,
).Uint64()

// On the boundary of AP3 and AP4 or at the start of a new network, the parent
// may not have a populated [ExtDataGasUsed].
if parent.ExtDataGasUsed != nil {
parentExtraStateGasUsed = parent.ExtDataGasUsed.Uint64()
if parentExtra.ExtDataGasUsed != nil {
parentExtraStateGasUsed = parentExtra.ExtDataGasUsed.Uint64()
}
default:
blockGasCost = ApricotPhase3BlockGasFee
Expand Down Expand Up @@ -339,21 +340,22 @@ func MinRequiredTip(config *params.ChainConfig, header *types.Header) (*big.Int,
if header.BaseFee == nil {
return nil, errBaseFeeNil
}
if header.BlockGasCost == nil {
headerExtra := types.GetHeaderExtra(header)
if headerExtra.BlockGasCost == nil {
return nil, errBlockGasCostNil
}
if header.ExtDataGasUsed == nil {
if headerExtra.ExtDataGasUsed == nil {
return nil, errExtDataGasUsedNil
}

// minTip = requiredBlockFee/blockGasUsage
requiredBlockFee := new(big.Int).Mul(
header.BlockGasCost,
headerExtra.BlockGasCost,
header.BaseFee,
)
blockGasUsage := new(big.Int).Add(
new(big.Int).SetUint64(header.GasUsed),
header.ExtDataGasUsed,
headerExtra.ExtDataGasUsed,
)
return new(big.Int).Div(requiredBlockFee, blockGasUsage), nil
}
15 changes: 8 additions & 7 deletions consensus/dummy/dynamic_fees_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -425,14 +425,15 @@ func TestCalcBaseFeeAP4(t *testing.T) {
nextExtraData, nextBaseFee, err = CalcBaseFee(params.TestApricotPhase4Config, extDataHeader, block.timestamp)
assert.NoError(t, err)
log.Info("Update", "baseFee (w/extData)", nextBaseFee)
extDataHeader = &types.Header{
Time: block.timestamp,
GasUsed: block.gasUsed,
Number: big.NewInt(int64(index) + 1),
BaseFee: nextBaseFee,
Extra: nextExtraData,
extDataHeader = types.WithHeaderExtra(&types.Header{
Time: block.timestamp,
GasUsed: block.gasUsed,
Number: big.NewInt(int64(index) + 1),
BaseFee: nextBaseFee,
Extra: nextExtraData,
}, &types.HeaderExtra{
ExtDataGasUsed: block.extDataGasUsed,
}
})

assert.Equal(t, event.extDataFeeGreater, extDataHeader.BaseFee.Cmp(header.BaseFee) == 1, "unexpected cmp for index %d", index)
}
Expand Down
4 changes: 2 additions & 2 deletions core/state_processor_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -376,8 +376,8 @@ func GenerateBadBlock(parent *types.Block, engine consensus.Engine, txs types.Tr
header.Extra, header.BaseFee, _ = dummy.CalcBaseFee(config, parent.Header(), header.Time)
}
if params.GetExtra(config).IsApricotPhase4(header.Time) {
header.BlockGasCost = big.NewInt(0)
header.ExtDataGasUsed = big.NewInt(0)
types.GetHeaderExtra(header).BlockGasCost = big.NewInt(0)
types.GetHeaderExtra(header).ExtDataGasUsed = big.NewInt(0)
}
var receipts []*types.Receipt
// The post-state result doesn't need to be correct (this is a bad block), but we do need something there
Expand Down
138 changes: 12 additions & 126 deletions core/types/block.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,132 +31,12 @@ import (
"encoding/binary"
"io"
"math/big"
"reflect"
"sync/atomic"

"github.com/ava-labs/libevm/common"
"github.com/ava-labs/libevm/common/hexutil"
"github.com/ava-labs/libevm/rlp"
)

// A BlockNonce is a 64-bit hash which proves (combined with the
// mix-hash) that a sufficient amount of computation has been carried
// out on a block.
type BlockNonce [8]byte

// EncodeNonce converts the given integer to a block nonce.
func EncodeNonce(i uint64) BlockNonce {
var n BlockNonce
binary.BigEndian.PutUint64(n[:], i)
return n
}

// Uint64 returns the integer value of a block nonce.
func (n BlockNonce) Uint64() uint64 {
return binary.BigEndian.Uint64(n[:])
}

// MarshalText encodes n as a hex string with 0x prefix.
func (n BlockNonce) MarshalText() ([]byte, error) {
return hexutil.Bytes(n[:]).MarshalText()
}

// UnmarshalText implements encoding.TextUnmarshaler.
func (n *BlockNonce) UnmarshalText(input []byte) error {
return hexutil.UnmarshalFixedText("BlockNonce", input, n[:])
}

//go:generate go run github.com/fjl/gencodec -type Header -field-override headerMarshaling -out gen_header_json.go
//go:generate go run github.com/ava-labs/libevm/rlp/rlpgen -type Header -out gen_header_rlp.go

// 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" gencodec:"required"`
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"`
ExtDataHash common.Hash `json:"extDataHash" gencodec:"required"`

// BaseFee was added by EIP-1559 and is ignored in legacy headers.
BaseFee *big.Int `json:"baseFeePerGas" rlp:"optional"`

// ExtDataGasUsed was added by Apricot Phase 4 and is ignored in legacy
// headers.
//
// It is not a uint64 like GasLimit or GasUsed because it is not possible to
// correctly encode this field optionally with uint64.
ExtDataGasUsed *big.Int `json:"extDataGasUsed" rlp:"optional"`

// BlockGasCost was added by Apricot Phase 4 and is ignored in legacy
// headers.
BlockGasCost *big.Int `json:"blockGasCost" rlp:"optional"`

// BlobGasUsed was added by EIP-4844 and is ignored in legacy headers.
BlobGasUsed *uint64 `json:"blobGasUsed" rlp:"optional"`

// ExcessBlobGas was added by EIP-4844 and is ignored in legacy headers.
ExcessBlobGas *uint64 `json:"excessBlobGas" rlp:"optional"`

// ParentBeaconRoot was added by EIP-4788 and is ignored in legacy headers.
ParentBeaconRoot *common.Hash `json:"parentBeaconBlockRoot" rlp:"optional"`
}

// field type overrides for gencodec
type headerMarshaling struct {
Difficulty *hexutil.Big
Number *hexutil.Big
GasLimit hexutil.Uint64
GasUsed hexutil.Uint64
Time hexutil.Uint64
Extra hexutil.Bytes
BaseFee *hexutil.Big
ExtDataGasUsed *hexutil.Big
BlockGasCost *hexutil.Big
Hash common.Hash `json:"hash"` // adds call to Hash() in MarshalJSON
BlobGasUsed *hexutil.Uint64
ExcessBlobGas *hexutil.Uint64
}

// Hash returns the block hash of the header, which is simply the keccak256 hash of its
// RLP encoding.
func (h *Header) Hash() common.Hash {
return rlpHash(h)
}

var headerSize = common.StorageSize(reflect.TypeOf(Header{}).Size())

// Size returns the approximate memory used by all internal contents. It is used
// to approximate and limit the memory consumption of various caches.
func (h *Header) Size() common.StorageSize {
var baseFeeBits int
if h.BaseFee != nil {
baseFeeBits = h.BaseFee.BitLen()
}
return headerSize + common.StorageSize(len(h.Extra)+(h.Difficulty.BitLen()+h.Number.BitLen()+baseFeeBits)/8)
}

// EmptyBody returns true if there is no additional 'body' to complete the header
// that is: no transactions and no uncles.
func (h *Header) EmptyBody() bool {
return h.TxHash == EmptyTxsHash && h.UncleHash == EmptyUncleHash
}

// EmptyReceipts returns true if there are no receipts for this header/block.
func (h *Header) EmptyReceipts() bool {
return h.ReceiptHash == EmptyReceiptsHash
}

// Body is a simple (mutable, non-safe) data container for storing and moving
// a block's data contents (transactions and uncles) together.
type Body struct {
Expand Down Expand Up @@ -249,6 +129,12 @@ func NewBlock(
// CopyHeader creates a deep copy of a block header.
func CopyHeader(h *Header) *Header {
cpy := *h
hExtra := GetHeaderExtra(h)
cpyExtra := &HeaderExtra{
ExtDataHash: hExtra.ExtDataHash,
}
cpy = *WithHeaderExtra(&cpy, cpyExtra)

if cpy.Difficulty = new(big.Int); h.Difficulty != nil {
cpy.Difficulty.Set(h.Difficulty)
}
Expand All @@ -258,11 +144,11 @@ func CopyHeader(h *Header) *Header {
if h.BaseFee != nil {
cpy.BaseFee = new(big.Int).Set(h.BaseFee)
}
if h.ExtDataGasUsed != nil {
cpy.ExtDataGasUsed = new(big.Int).Set(h.ExtDataGasUsed)
if hExtra.ExtDataGasUsed != nil {
cpyExtra.ExtDataGasUsed = new(big.Int).Set(hExtra.ExtDataGasUsed)
}
if h.BlockGasCost != nil {
cpy.BlockGasCost = new(big.Int).Set(h.BlockGasCost)
if hExtra.BlockGasCost != nil {
cpyExtra.BlockGasCost = new(big.Int).Set(hExtra.BlockGasCost)
}
if len(h.Extra) > 0 {
cpy.Extra = make([]byte, len(h.Extra))
Expand Down Expand Up @@ -380,10 +266,10 @@ func (b *Block) BlobGasUsed() *uint64 {
}

func (b *Block) BlockGasCost() *big.Int {
if b.header.BlockGasCost == nil {
if GetHeaderExtra(b.header).BlockGasCost == nil {
return nil
}
return new(big.Int).Set(b.header.BlockGasCost)
return new(big.Int).Set(GetHeaderExtra(b.header).BlockGasCost)
}

// Size returns the true RLP encoded storage size of the block, either by encoding
Expand Down
6 changes: 3 additions & 3 deletions core/types/block_ext.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ func (b *Block) setExtData(data []byte, recalc bool) {
b.extdata = &_data
copy(*b.extdata, data)
if recalc {
b.header.ExtDataHash = CalcExtDataHash(*b.extdata)
GetHeaderExtra(b.header).ExtDataHash = CalcExtDataHash(*b.extdata)
}
}

Expand All @@ -44,10 +44,10 @@ func (b *Block) Version() uint32 {
}

func (b *Block) ExtDataGasUsed() *big.Int {
if b.header.ExtDataGasUsed == nil {
if GetHeaderExtra(b.header).ExtDataGasUsed == nil {
return nil
}
return new(big.Int).Set(b.header.ExtDataGasUsed)
return new(big.Int).Set(GetHeaderExtra(b.header).ExtDataGasUsed)
}

func CalcExtDataHash(extdata []byte) common.Hash {
Expand Down
Loading

0 comments on commit d52d44f

Please sign in to comment.