Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
d0c3069
chore: initial commit
kamuikatsurgi May 6, 2026
73120f7
Merge remote-tracking branch 'origin' into kamui/registry-contract
kamuikatsurgi May 26, 2026
c25899e
chore: expose reserved registry reader across filtering modules
kamuikatsurgi May 26, 2026
3bcb710
core/types, params, consensus/bor: reserved-blockspace header fields …
marcello33 Jun 23, 2026
28d9d34
core: skip in-protocol fees for reserved-blockspace transactions (POS…
marcello33 Jun 23, 2026
2090e68
eip1559: price the public base fee on the normal region only (POS-3638)
marcello33 Jun 23, 2026
0be3547
txpool: admit and keep zero-fee reserved-blockspace transactions (POS…
marcello33 Jun 23, 2026
2143558
bor, core: extract reserved-blockspace fee/verify helpers (POS-3570)
marcello33 Jun 23, 2026
89df6ec
params: assert ReservedBlockspace appears in the chain config banner …
marcello33 Jun 23, 2026
948036d
miner, txpool: include reserved zero-fee transactions in produced blo…
marcello33 Jun 23, 2026
67dd1ab
bor, eip1559, core: mark reserved-blockspace enforcement as deferred …
marcello33 Jun 23, 2026
5db8c57
Merge kamui/registry-contract: bring the reserved-blockspace registry…
marcello33 Jun 27, 2026
378148e
registry: add feeMode, effectiveFrom, and root() to the reserved-bloc…
marcello33 Jun 27, 2026
b031d42
registryreader: add per-block reserved-set Snapshot cache (POS-3574)
marcello33 Jun 27, 2026
db3cd07
txpool: classify reserved senders from the registry snapshot, not con…
marcello33 Jun 29, 2026
a813d25
core, vm, miner: classify reserved senders from the registry snapshot…
marcello33 Jun 29, 2026
f16aacc
eip1559: document why reserved base-fee capacity stays on the config …
marcello33 Jun 29, 2026
dde6dcb
core, registryreader: make registry-backed reserved classification co…
marcello33 Jun 29, 2026
447baaf
core, ethapi, params, bor: address reserved-blockspace review finding…
marcello33 Jun 29, 2026
e7b2cf6
consensus, core, params, eip1559: tidy reserved-blockspace comments
marcello33 Jul 3, 2026
7bb2856
Merge branch 'develop' into mardizzone/reserved-blockspace
manav2401 Jul 16, 2026
f6ba804
Merge branch 'mardizzone/reserved-blockspace' into manav/reserved-blo…
manav2401 Jul 16, 2026
426a938
resolve conflicts and some improvements
manav2401 Jul 16, 2026
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
4 changes: 2 additions & 2 deletions cmd/keeper/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -48,9 +48,9 @@ require (
github.com/tklauser/go-sysconf v0.3.16 // indirect
github.com/tklauser/numcpus v0.11.0 // indirect
github.com/yusufpapurcu/wmi v1.2.4 // indirect
golang.org/x/crypto v0.50.0 // indirect
golang.org/x/crypto v0.51.0 // indirect
golang.org/x/sync v0.20.0 // indirect
golang.org/x/sys v0.43.0 // indirect
golang.org/x/sys v0.45.0 // indirect
golang.org/x/time v0.12.0 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
)
Expand Down
10 changes: 8 additions & 2 deletions consensus/bor/abi/common.go

Large diffs are not rendered by default.

61 changes: 61 additions & 0 deletions consensus/bor/bor.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import (
"github.com/ethereum/go-ethereum/consensus/bor/api"
"github.com/ethereum/go-ethereum/consensus/bor/clerk"
borSpan "github.com/ethereum/go-ethereum/consensus/bor/heimdall/span"
"github.com/ethereum/go-ethereum/consensus/bor/registryreader"
"github.com/ethereum/go-ethereum/consensus/bor/statefull"
"github.com/ethereum/go-ethereum/consensus/bor/valset"
"github.com/ethereum/go-ethereum/consensus/misc"
Expand Down Expand Up @@ -106,6 +107,15 @@ var (
// the gas target or base fee change denominator in its extra data.
errMissingGiuglianoFields = errors.New("missing gas target or base fee change denominator in extra data")

// errMissingReservedBlockspaceFields is returned if a post-ReservedBlockspace
// block is missing the reserved tx count or reserved gas used in its extra data.
errMissingReservedBlockspaceFields = errors.New("missing reserved tx count or reserved gas used in extra data")

// errReservedGasUsedExceedsBlock is returned if a header's reserved gas used
// is larger than the block's total gas used — an impossible value, since the
// reserved region is a subset of the block.
errReservedGasUsedExceedsBlock = errors.New("reserved gas used exceeds block gas used")

// errInvalidMixDigest is returned if a block's mix digest is non-zero.
errInvalidMixDigest = errors.New("non-zero mix digest")

Expand Down Expand Up @@ -393,6 +403,17 @@ func (c *Bor) GetSpanner() Spanner {
return c.spanner
}

// ReservedRegistry returns the read-only handle to the reserved blockspace
// registry, or nil when the GenesisContractsClient does not implement the
// reader (e.g. test mocks). Callers must tolerate a nil return.
func (c *Bor) ReservedRegistry() registryreader.Reader {
if c == nil {
return nil
}
reader, _ := c.GenesisContractsClient.(registryreader.Reader)
return reader
}

func (c *Bor) SetSpanner(spanner Spanner) {
c.spanner = spanner
}
Expand Down Expand Up @@ -506,6 +527,10 @@ func (c *Bor) verifyHeader(chain consensus.ChainHeaderReader, header *types.Head
}
}

if err := c.verifyReservedFields(header); err != nil {
return err
}

// Ensure that the mix digest is zero as we don't have fork protection currently
if header.MixDigest != (common.Hash{}) {
return errInvalidMixDigest
Expand Down Expand Up @@ -1021,6 +1046,40 @@ func (c *Bor) setGiuglianoExtraFields(header *types.Header, parent *types.Header
}
}

// setReservedBlockspaceExtraFields initializes the reserved-region header field
// for post-ReservedBlockspace blocks: a placeholder zero so the field is
// present (non-nil) and the header passes the verifyReservedFields presence
// check even when there are no reserved transactions. The miner overwrites it
// with the block's real reserved gas total at the end of block building
// (worker.writeReservedGasUsed).
func (c *Bor) setReservedBlockspaceExtraFields(header *types.Header, blockExtraData *types.BlockExtraData) {
if c.config.IsReservedBlockspace(header.Number) {
var zeroGas uint64
blockExtraData.ReservedGasUsed = &zeroGas
}
}

// verifyReservedFields checks that post-ReservedBlockspace headers carry the
// reserved-region gas field, plus a header-only bound on its value. Value
// correctness (the sum over the block's reserved transactions, per-client
// quota) is a body-level check that lands with the block-validation slice.
func (c *Bor) verifyReservedFields(header *types.Header) error {
if !c.config.IsReservedBlockspace(header.Number) {
return nil
}
reservedGasUsed := header.GetReservedGasUsed(c.chainConfig)
if reservedGasUsed == nil {
return errMissingReservedBlockspaceFields
}
// The reserved region is a subset of the block, so its gas cannot exceed the
// block's gas used. Reject the impossible value rather than letting CalcBaseFee
// silently clamp it (the base fee consumes parent.ReservedGasUsed).
if *reservedGasUsed > header.GasUsed {
return errReservedGasUsedExceedsBlock
}
return nil
}

// Prepare implements consensus.Engine, preparing all the consensus fields of the
// header for running the transactions on top.
func (c *Bor) Prepare(chain consensus.ChainHeaderReader, header *types.Header, waitOnPrepare bool) error {
Expand Down Expand Up @@ -1076,6 +1135,7 @@ func (c *Bor) Prepare(chain consensus.ChainHeaderReader, header *types.Header, w
}

c.setGiuglianoExtraFields(header, parent, blockExtraData)
c.setReservedBlockspaceExtraFields(header, blockExtraData)

blockExtraDataBytes, err := rlp.EncodeToBytes(blockExtraData)
if err != nil {
Expand All @@ -1096,6 +1156,7 @@ func (c *Bor) Prepare(chain consensus.ChainHeaderReader, header *types.Header, w
}

c.setGiuglianoExtraFields(header, parent, blockExtraData)
c.setReservedBlockspaceExtraFields(header, blockExtraData)

blockExtraDataBytes, err := rlp.EncodeToBytes(blockExtraData)
if err != nil {
Expand Down
83 changes: 83 additions & 0 deletions consensus/bor/bor_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5911,6 +5911,89 @@ func TestVerifyHeader_PreGiugliano_NoCheck(t *testing.T) {
}
}

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

// Pre-fork: the reserved field is left nil.
bPre := &Bor{config: &params.BorConfig{ReservedBlockspaceBlock: big.NewInt(100)}}
bedPre := &types.BlockExtraData{}
bPre.setReservedBlockspaceExtraFields(&types.Header{Number: big.NewInt(99)}, bedPre)
require.Nil(t, bedPre.ReservedGasUsed)

// Post-fork: the field is initialized to non-nil zero so the block is valid
// even before the miner writes the real value.
bPost := &Bor{config: &params.BorConfig{ReservedBlockspaceBlock: big.NewInt(100)}}
bedPost := &types.BlockExtraData{}
bPost.setReservedBlockspaceExtraFields(&types.Header{Number: big.NewInt(100)}, bedPost)
require.NotNil(t, bedPost.ReservedGasUsed)
require.Equal(t, uint64(0), *bedPost.ReservedGasUsed)
}

func TestVerifyHeader_ReservedBlockspaceMissingFields(t *testing.T) {
t.Parallel()
s := newGiuglianoVerifySetup(t, true)
s.b.config.ReservedBlockspaceBlock = big.NewInt(0) // reserved-blockspace active from genesis

// Giugliano fields present (passes that check) but reserved fields absent.
gasTarget := uint64(15_000_000)
bfcd := uint64(64)
extra := buildBlockExtraBytes(&types.BlockExtraData{
GasTarget: &gasTarget,
BaseFeeChangeDenominator: &bfcd,
})
h := s.makeSignedChild(t, extra, big.NewInt(params.InitialBaseFee))

chain := newRawDBChain(s.db, s.cfg, h, nil, nil)
err := s.b.verifyHeader(chain, h, nil)
require.ErrorIs(t, err, errMissingReservedBlockspaceFields)
}

func TestVerifyHeader_ReservedBlockspaceFieldsPresent(t *testing.T) {
t.Parallel()
s := newGiuglianoVerifySetup(t, true)
s.b.config.ReservedBlockspaceBlock = big.NewInt(0)

gasTarget := uint64(15_000_000)
bfcd := uint64(64)
gasUsed := uint64(0)
extra := buildBlockExtraBytes(&types.BlockExtraData{
GasTarget: &gasTarget,
BaseFeeChangeDenominator: &bfcd,
ReservedGasUsed: &gasUsed,
})
h := s.makeSignedChild(t, extra, big.NewInt(params.InitialBaseFee))

chain := newRawDBChain(s.db, s.cfg, h, nil, nil)
err := s.b.verifyHeader(chain, h, nil)
if err != nil {
require.NotErrorIs(t, err, errMissingReservedBlockspaceFields)
require.NotErrorIs(t, err, errMissingGiuglianoFields)
}
}

func TestVerifyReservedFields_GasUsedBound(t *testing.T) {
t.Parallel()
s := newGiuglianoVerifySetup(t, true)
s.b.config.ReservedBlockspaceBlock = big.NewInt(0)

gasTarget := uint64(15_000_000)
bfcd := uint64(64)
reservedGasUsed := uint64(5_000)
extra := buildBlockExtraBytes(&types.BlockExtraData{
GasTarget: &gasTarget,
BaseFeeChangeDenominator: &bfcd,
ReservedGasUsed: &reservedGasUsed,
})

// Reserved gas used exceeds the block's gas used — impossible, must be rejected.
h := &types.Header{Number: big.NewInt(1), GasUsed: 4_000, Extra: extra}
require.ErrorIs(t, s.b.verifyReservedFields(h), errReservedGasUsedExceedsBlock)

// Equal-or-below the block's gas used passes the bound.
h.GasUsed = 5_000
require.NoError(t, s.b.verifyReservedFields(h))
}

// TestApplyMessage_StateSyncTxContext validates if TxContext is correctly
// set for state-sync transactions.
func TestApplyMessage_StateSyncTxContext(t *testing.T) {
Expand Down
28 changes: 16 additions & 12 deletions consensus/bor/contract/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,14 @@ import (
)

type GenesisContractsClient struct {
validatorSetABI abi.ABI
stateReceiverABI abi.ABI
ValidatorContract string
StateReceiverContract string
chainConfig *params.ChainConfig
ethAPI api.Caller
validatorSetABI abi.ABI
stateReceiverABI abi.ABI
reservedRegistryABI abi.ABI
ValidatorContract string
StateReceiverContract string
ReservedRegistryContract string
chainConfig *params.ChainConfig
ethAPI api.Caller
}

func NewGenesisContractsClient(
Expand All @@ -37,12 +39,14 @@ func NewGenesisContractsClient(
ethAPI api.Caller,
) *GenesisContractsClient {
return &GenesisContractsClient{
validatorSetABI: borabi.ValidatorSet(),
stateReceiverABI: borabi.StateReceiver(),
ValidatorContract: validatorContract,
StateReceiverContract: stateReceiverContract,
chainConfig: chainConfig,
ethAPI: ethAPI,
validatorSetABI: borabi.ValidatorSet(),
stateReceiverABI: borabi.StateReceiver(),
reservedRegistryABI: borabi.ReservedBlockspaceRegistry(),
ValidatorContract: validatorContract,
StateReceiverContract: stateReceiverContract,
ReservedRegistryContract: chainConfig.Bor.ReservedRegistryContract,
chainConfig: chainConfig,
ethAPI: ethAPI,
}
}

Expand Down
Loading
Loading