Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
53 commits
Select commit Hold shift + click to select a range
3dfe0af
execution: implement EIP-7976
taratorio Apr 16, 2026
f18393c
Merge branch 'main' of github.com:erigontech/erigon into worktree-imp…
taratorio Apr 16, 2026
d89a9e1
Merge branch 'main' of github.com:erigontech/erigon into worktree-imp…
taratorio Apr 17, 2026
fcaa52e
execution: remove EIP-7976 spec doc from repo
taratorio Apr 17, 2026
a750d6a
txnprovider/txpool: cache isAmsterdam() in TxPool.best loop
taratorio Apr 17, 2026
f980e98
txnprovider/txpool: cache isAmsterdam() in validateTx
taratorio Apr 17, 2026
4fc458d
empty test
taratorio Apr 17, 2026
1e6237f
claude: add skill for implementing a new eip
taratorio Apr 17, 2026
2872ec8
Merge branch 'worktree-implement-eip-skill' of github.com:erigontech/…
taratorio Apr 17, 2026
492ed43
bal-devnet-4: enable ExperimentalBAL and Exec3Parallel by default
yperbasis Apr 17, 2026
43c4036
execution: implement EIP-7981
taratorio Apr 20, 2026
55de1a9
Merge branch 'main' of github.com:erigontech/erigon into worktree-imp…
taratorio Apr 20, 2026
190cdb6
Merge branch 'worktree-implement-eip-skill' of github.com:erigontech/…
taratorio Apr 20, 2026
859a150
Merge branch 'main' into bal-devnet-4
yperbasis Apr 20, 2026
06b325e
Merge branch 'main' into bal-devnet-4
yperbasis Apr 21, 2026
972dd53
Merge branch 'main' into bal-devnet-4
yperbasis Apr 21, 2026
75c3978
Merge branch 'main' into bal-devnet-4
yperbasis Apr 22, 2026
1203b58
Merge branch 'main' of github.com:erigontech/erigon into worktree-imp…
taratorio Apr 22, 2026
f7966be
Merge branch 'main' of github.com:erigontech/erigon into worktree-imp…
taratorio Apr 22, 2026
9ebc449
address reviews
taratorio Apr 22, 2026
cd78c84
update skips
taratorio Apr 22, 2026
9866c61
Merge branch 'worktree-impl-eip7976-v2' of github.com:erigontech/erig…
taratorio Apr 22, 2026
6f9bfb1
Merge branch 'main' of github.com:erigontech/erigon into worktree-imp…
taratorio Apr 22, 2026
a89c2ab
Merge branch 'worktree-impl-eip7976-v2' of github.com:erigontech/erig…
taratorio Apr 22, 2026
ee3c6fe
update skips
taratorio Apr 23, 2026
7bb89df
Merge branch 'worktree-impl-eip-7981' of github.com:erigontech/erigon…
taratorio Apr 23, 2026
c5af150
Merge branch 'main' into worktree-impl-eip7976-v2
taratorio Apr 23, 2026
399c05f
add back reverted submodule version update
taratorio Apr 23, 2026
ca77d4f
Merge branch 'worktree-impl-eip7976-v2' of github.com:erigontech/erig…
taratorio Apr 23, 2026
9b96071
Merge branch 'main' into bal-devnet-4
taratorio Apr 23, 2026
2423c7b
Merge branch 'main' of github.com:erigontech/erigon into worktree-imp…
taratorio Apr 25, 2026
c13e8a2
fix
taratorio Apr 26, 2026
ec5145b
fix
taratorio Apr 26, 2026
8460cea
Merge branch 'main' of github.com:erigontech/erigon into worktree-imp…
taratorio Apr 26, 2026
1d42a39
update skips
taratorio Apr 26, 2026
a273904
execution: implement EIP-8037 changes for bal-devnet-4
taratorio Apr 26, 2026
c3e7b5d
Merge branch 'worktree-impl-eip-7981' of github.com:erigontech/erigon…
taratorio Apr 26, 2026
b876ed7
execution: implement EIP-8037 changes for bal-devnet-4
taratorio Apr 26, 2026
61d8c84
Merge branch 'worktree-impl-8037-bal-devnet-4' of github.com:erigonte…
taratorio Apr 26, 2026
721e444
8037: go back to static cpsb=1174
taratorio Apr 27, 2026
1e6b7d2
update fixtures to snobal-devnet-4
taratorio Apr 28, 2026
e771b51
plan ready
taratorio Apr 28, 2026
3063507
plan ready
taratorio Apr 28, 2026
18dfcf1
initial version of changes
taratorio Apr 28, 2026
6024774
add found test challenges
taratorio Apr 28, 2026
02753ba
update plan
taratorio Apr 28, 2026
eba8988
update system txns logic
taratorio Apr 28, 2026
3f95dba
refill state gas for halt/revert
taratorio Apr 28, 2026
c80263b
cleanup TxnExecutor.Execute
taratorio Apr 28, 2026
24556a1
update plan
taratorio Apr 29, 2026
4ebc93f
update fixtures to fixtures_snobal-devnet-5
taratorio Apr 29, 2026
3284f21
update plan
taratorio Apr 29, 2026
23d4c81
test fixes
taratorio Apr 30, 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/erigon/node/config_snapshot_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ func TestConfigDefaults(t *testing.T) {
require.Equal(t, uint64(1), snap.NetworkID, "default network should be mainnet")
require.True(t, snap.StateStream, "state stream should be enabled by default")
require.True(t, snap.InternalCL, "internal CL (Caplin) is on by default")
require.False(t, snap.ExperimentalBAL, "experimental BAL should be off by default")
require.True(t, snap.ExperimentalBAL, "experimental BAL should be on by default on bal-devnet branches")
require.False(t, snap.KeepExecutionProofs, "keep execution proofs should be off by default")

// Snapshot defaults
Expand Down Expand Up @@ -210,6 +210,6 @@ func TestConfigSnapshotStability(t *testing.T) {
require.True(t, snap.StateStream)
require.True(t, snap.SnapProduceE2)
require.True(t, snap.SnapProduceE3)
require.False(t, snap.ExperimentalBAL)
require.True(t, snap.ExperimentalBAL)
require.False(t, snap.NoDownloader)
}
2 changes: 1 addition & 1 deletion common/dbg/experiments.go
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ var (

CaplinSyncedDataMangerDeadlockDetection = EnvBool("CAPLIN_SYNCED_DATA_MANAGER_DEADLOCK_DETECTION", false)

Exec3Parallel = EnvBool("EXEC3_PARALLEL", false)
Exec3Parallel = EnvBool("EXEC3_PARALLEL", true)
numWorkers = runtime.NumCPU() / 2
Exec3Workers = EnvInt("EXEC3_WORKERS", numWorkers)
ExecTerseLoggerLevel = EnvInt("EXEC_TERSE_LOGGER_LEVEL", int(log.LvlWarn))
Expand Down
724 changes: 724 additions & 0 deletions docs/plans/20260427-eip8037-journal-state-gas-spec.md

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion execution/engineapi/engineapitester/engine_api_tester.go
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ func DefaultEngineApiTesterGenesis(t *testing.T) (*types.Genesis, *ecdsa.Private
Config: &chainConfig,
Coinbase: coinbaseAddr,
Difficulty: merge.ProofOfStakeDifficulty,
GasLimit: 1_000_000_000,
GasLimit: 100_000_000,
Alloc: types.GenesisAlloc{
coinbaseAddr: {
Balance: new(big.Int).Exp(big.NewInt(10), big.NewInt(21), nil), // 1_000 ETH
Expand Down
119 changes: 87 additions & 32 deletions execution/execmodule/exec_module_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1108,10 +1108,12 @@ func drainHeaders(t *testing.T, ch <-chan [][]byte, timeout time.Duration) {
// TestAssembleBlockStateGasLimit verifies that the builder respects the EIP-8037
// block validity invariant: gas_used = max(regular, state) <= gas_limit.
//
// Contract creations have high intrinsic state gas (~131K per create at
// CostPerStateByte=1174) but low regular gas (~30K). With a 500K gas limit,
// about 4 creates would push state gas past the limit even though regular gas
// has room. Without the fix the builder would produce an invalid block.
// Under EIP-8037's dynamic cost_per_state_byte, ~730 creates (at any
// gas limit) are needed to fill intrinsic state gas via account creation
// alone. To exercise the check in a small number of txns we pad each create
// with large initcode so that code deposit state gas (L × cpsb) dominates.
// With a 120M gas limit (cpsb = 1174) and ~12 KiB initcode each create costs
// ~14.5M state gas, so ~8 fit before state gas trips the block limit.
func TestAssembleBlockStateGasLimit(t *testing.T) {
t.Parallel()
ctx := t.Context()
Expand All @@ -1122,7 +1124,7 @@ func TestAssembleBlockStateGasLimit(t *testing.T) {

genesis := &types.Genesis{
Config: chain.AllProtocolChanges,
GasLimit: 500_000, // low limit so state gas from a few creates exceeds it
GasLimit: 120_000_000, // cpsb = 1174 at this limit
Alloc: types.GenesisAlloc{
senderAddr: {Balance: new(big.Int).Exp(big.NewInt(10), big.NewInt(18), nil)},
},
Expand All @@ -1144,13 +1146,32 @@ func TestAssembleBlockStateGasLimit(t *testing.T) {
require.NoError(t, err)

// Submit 10 contract creation txns to the pool.
// Each has ~131K intrinsic state gas but only ~30K regular gas.
// Each deploys a ~12 KiB contract so state gas (code deposit + account
// creation) per create is ~14.5M at cpsb=1174.
// Initcode layout: 12-byte prefix (CODECOPY+RETURN) that deploys the
// `runtimeSize` bytes of JUMPDEST that follow it.
const runtimeSize = 12 * 1024
initCode := make([]byte, 12+runtimeSize)
initCode[0] = 0x61 // PUSH2
initCode[1] = byte(runtimeSize >> 8) // size hi
initCode[2] = byte(runtimeSize & 0xff) // size lo
initCode[3] = 0x80 // DUP1
initCode[4] = 0x60 // PUSH1
initCode[5] = 0x0c // srcOffset = 12
initCode[6] = 0x60 // PUSH1
initCode[7] = 0x00 // dstOffset = 0
initCode[8] = 0x39 // CODECOPY
initCode[9] = 0x60 // PUSH1
initCode[10] = 0x00 // retOffset = 0
initCode[11] = 0xf3 // RETURN
for i := 12; i < len(initCode); i++ {
initCode[i] = 0x5b // JUMPDEST — valid-as-code filler; value doesn't matter for state gas
}
baseFee := chainPack.TopBlock.BaseFee().Uint64()
deployCode := []byte{0x60, 0x00} // PUSH1 0x00 — minimal contract
rlpTxs := make([][]byte, 10)
for i := range rlpTxs {
tx, txErr := types.SignTx(
types.NewContractCreation(uint64(i), uint256.NewInt(0), 200_000, uint256.NewInt(baseFee), deployCode),
types.NewContractCreation(uint64(i), uint256.NewInt(0), 16_000_000, uint256.NewInt(baseFee), initCode),
*types.LatestSignerForChainID(m.ChainConfig.ChainID), privKey,
)
require.NoError(t, txErr)
Expand Down Expand Up @@ -1198,11 +1219,11 @@ func TestAssembleBlockStateGasLimit(t *testing.T) {
// invariant for execution-time state gas (SSTOREs), as opposed to intrinsic
// state gas (contract creations tested above).
//
// A deployed contract writes 4 new storage slots per call (~150K execution
// state gas, ~41K regular gas, 0 intrinsic state gas). The txpool cannot
// filter these by state gas — only the check inside applyTransaction
// (between ApplyMessage and FinalizeTx) prevents the block from exceeding
// gas_limit.
// A deployed contract writes 4 new storage slots per call. At cpsb=1174
// (120M gas limit) that costs ~150K execution state gas per call. The
// txpool cannot filter these by state gas — only the check inside
// applyTransaction (between ApplyMessage and FinalizeTx) prevents the
// block from exceeding gas_limit.
func TestAssembleBlockStateGasLimitSSTORE(t *testing.T) {
t.Parallel()
ctx := t.Context()
Expand All @@ -1213,7 +1234,7 @@ func TestAssembleBlockStateGasLimitSSTORE(t *testing.T) {

genesis := &types.Genesis{
Config: chain.AllProtocolChanges,
GasLimit: 500_000,
GasLimit: 120_000_000,
Alloc: types.GenesisAlloc{
senderAddr: {Balance: new(big.Int).Exp(big.NewInt(10), big.NewInt(18), nil)},
},
Expand All @@ -1227,17 +1248,49 @@ func TestAssembleBlockStateGasLimitSSTORE(t *testing.T) {
exec := m.ExecModule
txpool := m.TxPoolGrpcServer

// Deploy a contract whose runtime writes to 4 storage slots per call.
// Runtime: base = calldataload(0); sstore(base+i, 1) for i in 0..3.
deployCode, err := hex.DecodeString(
"601d600c600039601d6000f3" + // initcode: deploy 29-byte runtime
"6000356001815560018160010155600181600201556001816003015500") // runtime
require.NoError(t, err)
// Deploy a contract whose runtime writes to `slotsPerCall` NEW storage
// slots per call. Slot index is base + calldataload(0), so different
// callers/calldata write to non-overlapping regions. At cpsb=1174 each
// slot costs 32 × 1174 = 37,568 state gas.
const slotsPerCall = 100
// Runtime layout (unrolled):
// PUSH1 0; CALLDATALOAD → stack: [base]
// repeat slotsPerCall times:
// DUP1; PUSH2 i; ADD; PUSH1 1; SWAP1; SSTORE // write 1 at base+i
// STOP
runtime := []byte{0x60, 0x00, 0x35} // PUSH1 0; CALLDATALOAD
for i := 0; i < slotsPerCall; i++ {
runtime = append(runtime,
0x80, // DUP1 (base)
0x61, byte(i>>8), byte(i&0xff), // PUSH2 i
0x01, // ADD
0x60, 0x01, // PUSH1 1 (value)
0x90, // SWAP1 (swap value and key)
0x55, // SSTORE
)
}
runtime = append(runtime, 0x00) // STOP
runtimeSize := len(runtime)
// Initcode prefix: CODECOPY the runtime into memory then RETURN it.
deployCode := make([]byte, 12+runtimeSize)
deployCode[0] = 0x61 // PUSH2
deployCode[1] = byte(runtimeSize >> 8) // size hi
deployCode[2] = byte(runtimeSize & 0xff) // size lo
deployCode[3] = 0x80 // DUP1
deployCode[4] = 0x60 // PUSH1
deployCode[5] = 0x0c // srcOffset = 12
deployCode[6] = 0x60 // PUSH1
deployCode[7] = 0x00 // dstOffset = 0
deployCode[8] = 0x39 // CODECOPY
deployCode[9] = 0x60 // PUSH1
deployCode[10] = 0x00 // retOffset = 0
deployCode[11] = 0xf3 // RETURN
copy(deployCode[12:], runtime)

signer := *types.LatestSignerForChainID(m.ChainConfig.ChainID)
baseFee := m.Genesis.BaseFee().Uint64()
deployTx, err := types.SignTx(
types.NewContractCreation(0, uint256.NewInt(0), 300_000, uint256.NewInt(baseFee), deployCode),
types.NewContractCreation(0, uint256.NewInt(0), 16_000_000, uint256.NewInt(baseFee), deployCode),
signer, privKey,
)
require.NoError(t, err)
Expand All @@ -1250,18 +1303,20 @@ func TestAssembleBlockStateGasLimitSSTORE(t *testing.T) {
err = m.InsertChain(chainPack)
require.NoError(t, err)

// Submit 10 call txns. Each writes 4 new slots (~150K state gas, ~41K
// regular gas). With a 500K gas limit, 3 calls fit (~451K state gas)
// but the 4th would push to ~601K. Intrinsic state gas is 0 for all
// calls, so the txpool's regular-gas filter lets them all through —
// the applyTransaction check is the only defense.
// Submit 50 call txns. Each writes 100 new slots (~3.76M state gas at
// cpsb=1174, negligible regular gas compared to state). With a 120M
// gas limit, ~32 calls fit before state gas trips the block limit.
// Intrinsic state gas is 0 for all calls, so the txpool's regular-gas
// filter lets them all through — the applyTransaction check is the
// only defense.
const txCount = 50
baseFee = chainPack.TopBlock.BaseFee().Uint64()
rlpTxs := make([][]byte, 10)
rlpTxs := make([][]byte, txCount)
for i := range rlpTxs {
var calldata [32]byte
binary.BigEndian.PutUint64(calldata[24:], uint64(i*4))
binary.BigEndian.PutUint64(calldata[24:], uint64(i*slotsPerCall))
tx, txErr := types.SignTx(
types.NewTransaction(uint64(i+1), contractAddr, uint256.NewInt(0), 300_000, uint256.NewInt(baseFee), calldata[:]),
types.NewTransaction(uint64(i+1), contractAddr, uint256.NewInt(0), 16_000_000, uint256.NewInt(baseFee), calldata[:]),
signer, privKey,
)
require.NoError(t, txErr)
Expand Down Expand Up @@ -1291,9 +1346,9 @@ func TestAssembleBlockStateGasLimitSSTORE(t *testing.T) {
block, err := getAssembledBlock(ctx, exec, payloadId)
require.NoError(t, err)

txCount := len(block.Transactions())
require.Greater(t, txCount, 0, "block should contain at least one tx")
require.Less(t, txCount, 10, "builder should stop before all 10 calls fit")
actualTxCount := len(block.Transactions())
require.Greater(t, actualTxCount, 0, "block should contain at least one tx")
require.Less(t, actualTxCount, txCount, "builder should stop before all calls fit")

// EIP-8037 invariant: gas_used <= gas_limit.
require.LessOrEqual(t, block.GasUsed(), block.GasLimit(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -366,7 +366,7 @@ func applyOptions(opts []Option) options {
pruneMode: &defaultPruneMode,
blockBufferSize: 128,
chainConfig: chain.TestChainBerlinConfig,
experimentalBAL: false,
experimentalBAL: true,
}
for _, o := range opts {
o(&opt)
Expand Down
11 changes: 10 additions & 1 deletion execution/protocol/block_exec.go
Original file line number Diff line number Diff line change
Expand Up @@ -270,9 +270,18 @@ func SysCallContractWithBlockContext(contract accounts.Address, data []byte, cha
txContext = NewEVMTxContext(msg)
}
evm := vm.NewEVM(blockContext, txContext, ibs, chainConfig, vmConfig)
// EIP-8037 (PR 11573 commit d2a0230): system calls are not subject to the
// TX_MAX_GAS_LIMIT cap, do not count against the block gas limit, and do
// not contribute to either block_regular_gas_used or block_state_gas_used.
// On Amsterdam, they get a dedicated state-gas reservoir sized for up to
// SystemMaxSstoresPerCall (=16) fresh storage slots; the regular budget
// (msg.Gas() = SysCallGasLimit = 30M) sits alongside it as gas_left.
mdGas := mdgas.MdGas{
Regular: msg.Gas(),
State: 0, // state gas reservoir will consume from regular gas for sys calls
State: 0,
}
if evm.ChainRules().IsAmsterdam {
mdGas.State = params.StateBytesPerStorageSlot * evm.Context.CostPerStateByte * params.SystemMaxSstoresPerCall
}
ret, _, err := evm.Call(
msg.From(),
Expand Down
Loading