Skip to content
Open
Show file tree
Hide file tree
Changes from 14 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
67 changes: 67 additions & 0 deletions core/types/block.go
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,12 @@ type BlockExtraData struct {

// BaseFeeChangeDenominator is the EIP-1559 base fee change denominator used by the block producer (post-Giugliano)
BaseFeeChangeDenominator *uint64 `rlp:"optional"`

// ReservedGasUsed is the sum of actual gas used by the block's
// reserved-region transactions. A pointer so that an explicit zero
// (reserved blockspace active, no reserved gas used) stays distinct from
// the field being absent on the wire (pre-activation blocks).
ReservedGasUsed *uint64 `rlp:"optional"`
}

// blockExtraDataRawTxDeps mirrors BlockExtraData but keeps TxDependency as an
Expand All @@ -146,6 +152,7 @@ type blockExtraDataRawTxDeps struct {
TxDependency rlp.RawValue
GasTarget *uint64 `rlp:"optional"`
BaseFeeChangeDenominator *uint64 `rlp:"optional"`
ReservedGasUsed *uint64 `rlp:"optional"`
}

// field type overrides for gencodec
Expand Down Expand Up @@ -589,6 +596,66 @@ func (h *Header) GetValidatorBytesAndBaseFeeParams(chainConfig *params.ChainConf
return blockExtraData.ValidatorBytes, blockExtraData.GasTarget, blockExtraData.BaseFeeChangeDenominator
}

// GetReservedGasUsed extracts the reserved-region gas used from the header's
// Extra field. Returns nil for pre-Cancun blocks, on decode error, or when the
// field is absent (blocks produced before reserved blockspace activated). If
// you need multiple fields from BlockExtraData, prefer DecodeBlockExtraData to
// avoid redundant RLP decodes.
func (h *Header) GetReservedGasUsed(chainConfig *params.ChainConfig) *uint64 {
if !chainConfig.IsCancun(h.Number) {
return nil
}

if len(h.Extra) < ExtraVanityLength+ExtraSealLength {
return nil
}

var blockExtraData blockExtraDataRawTxDeps
if err := rlp.DecodeBytes(h.Extra[ExtraVanityLength:len(h.Extra)-ExtraSealLength], &blockExtraData); err != nil {
return nil
}

if !txDependencyValidPreValencia(chainConfig, h.Number, blockExtraData.TxDependency) {
return nil
}

return blockExtraData.ReservedGasUsed
}

// SetReservedGasUsed records the reserved-region gas total in the header's
// BlockExtraData, re-encoding Header.Extra in place. TxDependency is carried
// through as a raw value, so the rewrite doesn't expand it. The field is
// consensus-visible: callers gate the write on reserved blockspace being
// active, this only performs the encoding.
func (h *Header) SetReservedGasUsed(reservedGasUsed uint64) error {
if len(h.Extra) < ExtraVanityLength+ExtraSealLength {
return fmt.Errorf("header extra data too short to carry BlockExtraData: %d bytes", len(h.Extra))
}

vanity := h.Extra[:ExtraVanityLength]
seal := h.Extra[len(h.Extra)-ExtraSealLength:]

var blockExtraData blockExtraDataRawTxDeps
if err := rlp.DecodeBytes(h.Extra[ExtraVanityLength:len(h.Extra)-ExtraSealLength], &blockExtraData); err != nil {
return fmt.Errorf("decode block extra data: %w", err)
}

blockExtraData.ReservedGasUsed = &reservedGasUsed

encoded, err := rlp.EncodeToBytes(&blockExtraData)
if err != nil {
return fmt.Errorf("encode block extra data: %w", err)
}

extra := make([]byte, 0, len(vanity)+len(encoded)+len(seal))
extra = append(extra, vanity...)
extra = append(extra, encoded...)
extra = append(extra, seal...)
h.Extra = extra

return nil
}

func txDependencyValidPreValencia(chainConfig *params.ChainConfig, number *big.Int, raw rlp.RawValue) bool {
if chainConfig.Bor != nil && chainConfig.Bor.IsValencia(number) {
return true
Expand Down
262 changes: 262 additions & 0 deletions core/types/block_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -787,6 +787,268 @@
if decoded2.BaseFeeChangeDenominator == nil || *decoded2.BaseFeeChangeDenominator != bfcd {
t.Errorf("BaseFeeChangeDenominator mismatch: got %v, want %d", decoded2.BaseFeeChangeDenominator, bfcd)
}
if decoded2.ReservedGasUsed != nil {
t.Errorf("ReservedGasUsed should be nil when unset, got %d", *decoded2.ReservedGasUsed)
}
}

func TestBlockExtraDataReservedGasUsedRLPCompat(t *testing.T) {

Check failure on line 795 in core/types/block_test.go

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Refactor this method to reduce its Cognitive Complexity from 26 to the 15 allowed.

See more on https://sonarcloud.io/project/issues?id=0xPolygon_bor&issues=AZ9gWeCe522HQpBMHMuX&open=AZ9gWeCe522HQpBMHMuX&pullRequest=2302
t.Parallel()

gasTarget := uint64(15000000)
bfcd := uint64(64)

roundtrip := func(in *BlockExtraData) (BlockExtraData, blockExtraDataRawTxDeps) {
t.Helper()
encoded, err := rlp.EncodeToBytes(in)
if err != nil {
t.Fatalf("encode: %v", err)
}
var full BlockExtraData
if err := rlp.DecodeBytes(encoded, &full); err != nil {
t.Fatalf("decode full: %v", err)
}
var raw blockExtraDataRawTxDeps
if err := rlp.DecodeBytes(encoded, &raw); err != nil {
t.Fatalf("decode raw mirror: %v", err)
}
return full, raw
}

// Blocks produced before reserved blockspace: field absent, decodes nil.
full, raw := roundtrip(&BlockExtraData{
ValidatorBytes: []byte{0x01},
TxDependency: [][]uint64{{1}},
GasTarget: &gasTarget,
BaseFeeChangeDenominator: &bfcd,
})
if full.ReservedGasUsed != nil || raw.ReservedGasUsed != nil {
t.Errorf("absent ReservedGasUsed must decode nil, got full=%v raw=%v", full.ReservedGasUsed, raw.ReservedGasUsed)
}

// Populated field survives the roundtrip in both structs.
reserved := uint64(12_345_678)
full, raw = roundtrip(&BlockExtraData{
ValidatorBytes: []byte{0x01},
TxDependency: [][]uint64{{1}},
GasTarget: &gasTarget,
BaseFeeChangeDenominator: &bfcd,
ReservedGasUsed: &reserved,
})
if full.ReservedGasUsed == nil || *full.ReservedGasUsed != reserved {
t.Errorf("ReservedGasUsed mismatch in full decode: got %v, want %d", full.ReservedGasUsed, reserved)
}
if raw.ReservedGasUsed == nil || *raw.ReservedGasUsed != reserved {
t.Errorf("ReservedGasUsed mismatch in raw-mirror decode: got %v, want %d", raw.ReservedGasUsed, reserved)
}

// An explicit zero must stay distinct from absent on the wire — this is
// why the field is a pointer (a plain uint64 zero would be omitted).
zero := uint64(0)
full, raw = roundtrip(&BlockExtraData{
ValidatorBytes: []byte{0x01},
TxDependency: [][]uint64{{1}},
ReservedGasUsed: &zero,
})
if full.ReservedGasUsed == nil || *full.ReservedGasUsed != 0 {
t.Errorf("explicit zero ReservedGasUsed must survive the wire, got %v", full.ReservedGasUsed)
}
if raw.ReservedGasUsed == nil || *raw.ReservedGasUsed != 0 {
t.Errorf("explicit zero ReservedGasUsed must survive the raw mirror, got %v", raw.ReservedGasUsed)
}
// Skipped middle optionals are encoded as their zero values, not elided.
if full.GasTarget == nil || *full.GasTarget != 0 || full.BaseFeeChangeDenominator == nil || *full.BaseFeeChangeDenominator != 0 {
t.Errorf("unset middle optionals should decode as zero when a later optional is set: gt=%v bfcd=%v", full.GasTarget, full.BaseFeeChangeDenominator)
}

// Wire-neutrality: while ReservedGasUsed is nil (every producer until the
// reserved-blockspace fork activates), the encoding must be the same
// 4-element list as before the field existed — adding the struct field
// alone must not change any header bytes.
countElems := func(bed *BlockExtraData) int {
t.Helper()
encoded, err := rlp.EncodeToBytes(bed)
if err != nil {
t.Fatalf("encode: %v", err)
}
content, _, err := rlp.SplitList(encoded)
if err != nil {
t.Fatalf("split list: %v", err)
}
n, err := rlp.CountValues(content)
if err != nil {
t.Fatalf("count values: %v", err)
}
return n
}
unwritten := &BlockExtraData{
ValidatorBytes: []byte{0x01},
TxDependency: [][]uint64{{1}},
GasTarget: &gasTarget,
BaseFeeChangeDenominator: &bfcd,
}
if n := countElems(unwritten); n != 4 {
t.Errorf("nil ReservedGasUsed must encode as the pre-change 4-element list, got %d elements", n)
}
written := &BlockExtraData{
ValidatorBytes: []byte{0x01},
TxDependency: [][]uint64{{1}},
GasTarget: &gasTarget,
BaseFeeChangeDenominator: &bfcd,
ReservedGasUsed: &reserved,
}
if n := countElems(written); n != 5 {
t.Errorf("set ReservedGasUsed must encode as a 5-element list, got %d elements", n)
}
}

func TestGetReservedGasUsed(t *testing.T) {
t.Parallel()

chainConfig := &params.ChainConfig{
ChainID: big.NewInt(137),
CancunBlock: big.NewInt(100),
}

buildExtra := func(bed *BlockExtraData) []byte {
vanity := make([]byte, ExtraVanityLength)
seal := make([]byte, ExtraSealLength)
encoded, _ := rlp.EncodeToBytes(bed)
extra := append(vanity, encoded...)
extra = append(extra, seal...)
return extra
}

reserved := uint64(5_000_000)
header := &Header{
Number: big.NewInt(200),
Extra: buildExtra(&BlockExtraData{ReservedGasUsed: &reserved}),
}
if got := header.GetReservedGasUsed(chainConfig); got == nil || *got != reserved {
t.Errorf("expected ReservedGasUsed %d, got %v", reserved, got)
}

// Pre-Cancun header returns nil.
preCancun := &Header{Number: big.NewInt(50), Extra: header.Extra}
if got := preCancun.GetReservedGasUsed(chainConfig); got != nil {
t.Errorf("expected nil pre-Cancun, got %v", got)
}

// Field absent (pre-activation block) returns nil.
absent := &Header{Number: big.NewInt(200), Extra: buildExtra(&BlockExtraData{})}
if got := absent.GetReservedGasUsed(chainConfig); got != nil {
t.Errorf("expected nil for absent field, got %v", got)
}

// Short extra data returns nil.
short := &Header{Number: big.NewInt(200), Extra: []byte{0x01}}
if got := short.GetReservedGasUsed(chainConfig); got != nil {
t.Errorf("expected nil for short extra, got %v", got)
}

// A decode error must return nil even when the field itself was already
// decoded: this payload carries all five fields plus a sixth junk element,
// so decoding fails only after ReservedGasUsed has been populated.
sixFields, err := rlp.EncodeToBytes(&struct {
ValidatorBytes []byte
TxDependency [][]uint64
GasTarget *uint64
BaseFeeChangeDenominator *uint64
ReservedGasUsed *uint64
Junk uint64
}{TxDependency: [][]uint64{{1}}, ReservedGasUsed: &reserved, Junk: 1})
if err != nil {
t.Fatalf("encode six-field payload: %v", err)
}
wrap := func(payload []byte) []byte {
extra := make([]byte, ExtraVanityLength, ExtraVanityLength+len(payload)+ExtraSealLength)
extra = append(extra, payload...)
return append(extra, make([]byte, ExtraSealLength)...)
}
tooMany := &Header{Number: big.NewInt(200), Extra: wrap(sixFields)}
if got := tooMany.GetReservedGasUsed(chainConfig); got != nil {
t.Errorf("expected nil on decode error, got %v", got)
}

// Pre-Valencia, a malformed TxDependency invalidates the whole payload —
// the field must not be returned from it. chainConfig has no Bor config,
// so the pre-Valencia strictness applies.
badDeps, err := rlp.EncodeToBytes(&blockExtraDataRawTxDeps{
TxDependency: rlp.RawValue{0x81, 0xff},
ReservedGasUsed: &reserved,
})
if err != nil {
t.Fatalf("encode malformed-txdep payload: %v", err)
}
malformed := &Header{Number: big.NewInt(200), Extra: wrap(badDeps)}
if got := malformed.GetReservedGasUsed(chainConfig); got != nil {
t.Errorf("expected nil for malformed TxDependency, got %v", got)
}
}

func TestSetReservedGasUsed(t *testing.T) {
t.Parallel()

chainConfig := &params.ChainConfig{
ChainID: big.NewInt(137),
CancunBlock: big.NewInt(0),
}

gasTarget := uint64(30_000_000)
bfcd := uint64(64)
body, err := rlp.EncodeToBytes(&BlockExtraData{
ValidatorBytes: []byte("validator-bytes"),
TxDependency: [][]uint64{{0}, {0, 1}},
GasTarget: &gasTarget,
BaseFeeChangeDenominator: &bfcd,
})
if err != nil {
t.Fatalf("encode: %v", err)
}
extra := make([]byte, ExtraVanityLength, ExtraVanityLength+len(body)+ExtraSealLength)
extra = append(extra, body...)
extra = append(extra, make([]byte, ExtraSealLength)...)
header := &Header{Number: big.NewInt(1), Extra: extra}

if err := header.SetReservedGasUsed(7_500_000); err != nil {
t.Fatalf("SetReservedGasUsed: %v", err)
}

if got := header.GetReservedGasUsed(chainConfig); got == nil || *got != 7_500_000 {
t.Errorf("round-trip mismatch: got %v, want 7500000", got)
}

// Every sibling field must survive the rewrite untouched.
decoded := header.DecodeBlockExtraData(chainConfig)
if decoded == nil {
t.Fatal("extra no longer decodes")
}
if !bytes.Equal(decoded.ValidatorBytes, []byte("validator-bytes")) {
t.Errorf("ValidatorBytes changed: %q", decoded.ValidatorBytes)
}
if !reflect.DeepEqual(decoded.TxDependency, [][]uint64{{0}, {0, 1}}) {
t.Errorf("TxDependency changed: %v", decoded.TxDependency)
}
if decoded.GasTarget == nil || *decoded.GasTarget != gasTarget || decoded.BaseFeeChangeDenominator == nil || *decoded.BaseFeeChangeDenominator != bfcd {
t.Errorf("Giugliano fields changed: gt=%v bfcd=%v", decoded.GasTarget, decoded.BaseFeeChangeDenominator)
}

// The write is idempotent-by-overwrite: setting again replaces the value.
if err := header.SetReservedGasUsed(0); err != nil {
t.Fatalf("second SetReservedGasUsed: %v", err)
}
if got := header.GetReservedGasUsed(chainConfig); got == nil || *got != 0 {
t.Errorf("overwrite mismatch: got %v, want explicit 0", got)
}

// Too-short extra is rejected without touching the header.
short := &Header{Number: big.NewInt(1), Extra: []byte{0x01}}
if err := short.SetReservedGasUsed(1); err == nil {
t.Error("expected error for short extra data")
}
if !bytes.Equal(short.Extra, []byte{0x01}) {
t.Error("short extra must be left untouched on error")
}
}

func TestGetBaseFeeParams(t *testing.T) {
Expand Down
9 changes: 9 additions & 0 deletions miner/miner.go
Original file line number Diff line number Diff line change
Expand Up @@ -285,6 +285,15 @@ func (miner *Miner) SetPrioAddresses(prio []common.Address) {
miner.worker.setPrio(prio)
}

// SetReservedRegistry installs the reserved-blockspace registry the miner
// consults during block building. Production callers leave it unset (nil) until
// the registry module and its hardfork gating land — the reserved pass is a
// no-op in that case and block building is byte-identical to before
// reserved-blockspace existed. Tests inject a *MockRegistry.
func (miner *Miner) SetReservedRegistry(r ReservedRegistry) {
miner.worker.setReservedRegistry(r)
}

// SetGasCeil sets the gaslimit to strive for when mining blocks post 1559.
// For pre-1559 blocks, it sets the ceiling.
func (miner *Miner) SetGasCeil(ceil uint64) {
Expand Down
Loading
Loading