diff --git a/cmd/devp2p/internal/ethtest/suite.go b/cmd/devp2p/internal/ethtest/suite.go index 12dc5711ac..2f1731b60c 100644 --- a/cmd/devp2p/internal/ethtest/suite.go +++ b/cmd/devp2p/internal/ethtest/suite.go @@ -743,7 +743,7 @@ func (s *Suite) makeBlobTxs(count, blobs int, discriminator byte) (txs types.Tra GasTipCap: uint256.NewInt(1), GasFeeCap: uint256.MustFromBig(s.chain.Head().BaseFee()), Gas: 100000, - BlobFeeCap: uint256.MustFromBig(eip4844.CalcBlobFee(*s.chain.Head().ExcessBlobGas())), + BlobFeeCap: uint256.MustFromBig(eip4844.CalcBlobFee(s.chain.config, s.chain.Head().Header())), BlobHashes: makeSidecar(blobdata...).BlobHashes(), Sidecar: makeSidecar(blobdata...), } diff --git a/cmd/devp2p/internal/ethtest/testdata/genesis.json b/cmd/devp2p/internal/ethtest/testdata/genesis.json index 4cfebdcac1..ace2f78815 100644 --- a/cmd/devp2p/internal/ethtest/testdata/genesis.json +++ b/cmd/devp2p/internal/ethtest/testdata/genesis.json @@ -18,7 +18,14 @@ "shanghaiTime": 780, "cancunTime": 840, "terminalTotalDifficulty": 9454784, - "ethash": {} + "ethash": {}, + "blobSchedule": { + "cancun": { + "target": 3, + "max": 6, + "baseFeeUpdateFraction": 3338477 + } + } }, "nonce": "0x0", "timestamp": "0x0", @@ -108,4 +115,4 @@ "baseFeePerGas": null, "excessBlobGas": null, "blobGasUsed": null -} \ No newline at end of file +} diff --git a/cmd/evm/internal/t8ntool/execution.go b/cmd/evm/internal/t8ntool/execution.go index 9ff5a05290..9332f4901b 100644 --- a/cmd/evm/internal/t8ntool/execution.go +++ b/cmd/evm/internal/t8ntool/execution.go @@ -178,15 +178,28 @@ func (pre *Prestate) Apply(vmConfig vm.Config, chainConfig *params.ChainConfig, var excessBlobGas uint64 if pre.Env.ExcessBlobGas != nil { excessBlobGas = *pre.Env.ExcessBlobGas - vmContext.BlobBaseFee = eip4844.CalcBlobFee(excessBlobGas) + header := &types.Header{ + Time: pre.Env.Timestamp, + ExcessBlobGas: pre.Env.ExcessBlobGas, + } + vmContext.BlobBaseFee = eip4844.CalcBlobFee(chainConfig, header) } else { // If it is not explicitly defined, but we have the parent values, we try // to calculate it ourselves. parentExcessBlobGas := pre.Env.ParentExcessBlobGas parentBlobGasUsed := pre.Env.ParentBlobGasUsed if parentExcessBlobGas != nil && parentBlobGasUsed != nil { - excessBlobGas = eip4844.CalcExcessBlobGas(*parentExcessBlobGas, *parentBlobGasUsed) - vmContext.BlobBaseFee = eip4844.CalcBlobFee(excessBlobGas) + parent := &types.Header{ + Time: pre.Env.ParentTimestamp, + ExcessBlobGas: pre.Env.ParentExcessBlobGas, + BlobGasUsed: pre.Env.ParentBlobGasUsed, + } + excessBlobGas = eip4844.CalcExcessBlobGas(chainConfig, parent) + header := &types.Header{ + Time: pre.Env.Timestamp, + ExcessBlobGas: &excessBlobGas, + } + vmContext.BlobBaseFee = eip4844.CalcBlobFee(chainConfig, header) } } // If DAO is supported/enabled, we need to handle it here. In geth 'proper', it's @@ -229,7 +242,8 @@ func (pre *Prestate) Apply(vmConfig vm.Config, chainConfig *params.ChainConfig, txBlobGas := uint64(0) if tx.Type() == types.BlobTxType { txBlobGas = uint64(params.BlobTxBlobGasPerBlob * len(tx.BlobHashes())) - if used, max := blobGasUsed+txBlobGas, uint64(params.MaxBlobGasPerBlock); used > max { + max := eip4844.MaxBlobGasPerBlock(chainConfig, pre.Env.Timestamp) + if used := blobGasUsed + txBlobGas; used > max { err := fmt.Errorf("blob gas (%d) would exceed maximum allowance %d", used, max) log.Warn("rejected tx", "index", i, "err", err) rejectedTxs = append(rejectedTxs, &rejectedTx{i, err.Error()}) diff --git a/consensus/beacon/consensus.go b/consensus/beacon/consensus.go index 20df6d0091..bdfddfff06 100644 --- a/consensus/beacon/consensus.go +++ b/consensus/beacon/consensus.go @@ -282,7 +282,7 @@ func (beacon *Beacon) verifyHeader(chain consensus.ChainHeaderReader, header, pa if header.ParentBeaconRoot == nil { return errors.New("header is missing beaconRoot") } - if err := eip4844.VerifyEIP4844Header(parent, header); err != nil { + if err := eip4844.VerifyEIP4844Header(chain.Config(), parent, header); err != nil { return err } } diff --git a/consensus/misc/eip4844/eip4844.go b/consensus/misc/eip4844/eip4844.go index 2dad9a0cd3..1d76b21b30 100644 --- a/consensus/misc/eip4844/eip4844.go +++ b/consensus/misc/eip4844/eip4844.go @@ -23,17 +23,17 @@ import ( "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/params" + "github.com/ethereum/go-ethereum/params/forks" ) var ( - minBlobGasPrice = big.NewInt(params.BlobTxMinBlobGasprice) - blobGaspriceUpdateFraction = big.NewInt(params.BlobTxBlobGaspriceUpdateFraction) + minBlobGasPrice = big.NewInt(params.BlobTxMinBlobGasprice) ) // VerifyEIP4844Header verifies the presence of the excessBlobGas field and that // if the current block contains no transactions, the excessBlobGas is updated // accordingly. -func VerifyEIP4844Header(parent, header *types.Header) error { +func VerifyEIP4844Header(config *params.ChainConfig, parent, header *types.Header) error { // Verify the header is not malformed if header.ExcessBlobGas == nil { return errors.New("header is missing excessBlobGas") @@ -42,14 +42,26 @@ func VerifyEIP4844Header(parent, header *types.Header) error { return errors.New("header is missing blobGasUsed") } // Verify that the blob gas used remains within reasonable limits. - if *header.BlobGasUsed > params.MaxBlobGasPerBlock { - return fmt.Errorf("blob gas used %d exceeds maximum allowance %d", *header.BlobGasUsed, params.MaxBlobGasPerBlock) + maxBlobGas := MaxBlobGasPerBlock(config, header.Time) + if *header.BlobGasUsed > maxBlobGas { + return fmt.Errorf("blob gas used %d exceeds maximum allowance %d", *header.BlobGasUsed, maxBlobGas) } if *header.BlobGasUsed%params.BlobTxBlobGasPerBlob != 0 { return fmt.Errorf("blob gas used %d not a multiple of blob gas per blob %d", header.BlobGasUsed, params.BlobTxBlobGasPerBlob) } // Verify the excessBlobGas is correct based on the parent header + expectedExcessBlobGas := CalcExcessBlobGas(config, parent) + if *header.ExcessBlobGas != expectedExcessBlobGas { + return fmt.Errorf("invalid excessBlobGas: have %d, want %d", *header.ExcessBlobGas, expectedExcessBlobGas) + } + return nil +} + +// CalcExcessBlobGas calculates the excess blob gas after applying the set of +// blobs on top of the excess blob gas. +func CalcExcessBlobGas(config *params.ChainConfig, parent *types.Header) uint64 { var ( + targetGas = uint64(targetBlobsPerBlock(config, parent.Time)) * params.BlobTxBlobGasPerBlob parentExcessBlobGas uint64 parentBlobGasUsed uint64 ) @@ -57,27 +69,85 @@ func VerifyEIP4844Header(parent, header *types.Header) error { parentExcessBlobGas = *parent.ExcessBlobGas parentBlobGasUsed = *parent.BlobGasUsed } - expectedExcessBlobGas := CalcExcessBlobGas(parentExcessBlobGas, parentBlobGasUsed) - if *header.ExcessBlobGas != expectedExcessBlobGas { - return fmt.Errorf("invalid excessBlobGas: have %d, want %d, parent excessBlobGas %d, parent blobDataUsed %d", - *header.ExcessBlobGas, expectedExcessBlobGas, parentExcessBlobGas, parentBlobGasUsed) - } - return nil -} - -// CalcExcessBlobGas calculates the excess blob gas after applying the set of -// blobs on top of the excess blob gas. -func CalcExcessBlobGas(parentExcessBlobGas uint64, parentBlobGasUsed uint64) uint64 { excessBlobGas := parentExcessBlobGas + parentBlobGasUsed - if excessBlobGas < params.BlobTxTargetBlobGasPerBlock { + if excessBlobGas < targetGas { return 0 } - return excessBlobGas - params.BlobTxTargetBlobGasPerBlock + return excessBlobGas - targetGas } // CalcBlobFee calculates the blobfee from the header's excess blob gas field. -func CalcBlobFee(excessBlobGas uint64) *big.Int { - return fakeExponential(minBlobGasPrice, new(big.Int).SetUint64(excessBlobGas), blobGaspriceUpdateFraction) +func CalcBlobFee(config *params.ChainConfig, header *types.Header) *big.Int { + var frac uint64 + switch config.LatestFork(header.Time) { + case forks.Prague: + frac = config.BlobScheduleConfig.Prague.UpdateFraction + case forks.Cancun: + frac = config.BlobScheduleConfig.Cancun.UpdateFraction + default: + panic("calculating blob fee on unsupported fork") + } + return fakeExponential(minBlobGasPrice, new(big.Int).SetUint64(*header.ExcessBlobGas), new(big.Int).SetUint64(frac)) +} + +// MaxBlobsPerBlock returns the max blobs per block for a block at the given timestamp. +func MaxBlobsPerBlock(cfg *params.ChainConfig, time uint64) int { + if cfg.BlobScheduleConfig == nil { + return 0 + } + var ( + london = cfg.LondonBlock + s = cfg.BlobScheduleConfig + ) + switch { + case cfg.IsPrague(london, time) && s.Prague != nil: + return s.Prague.Max + case cfg.IsCancun(london, time) && s.Cancun != nil: + return s.Cancun.Max + default: + return 0 + } +} + +// MaxBlobsPerBlock returns the maximum blob gas that can be spent in a block at the given timestamp. +func MaxBlobGasPerBlock(cfg *params.ChainConfig, time uint64) uint64 { + return uint64(MaxBlobsPerBlock(cfg, time)) * params.BlobTxBlobGasPerBlob +} + +// LatestMaxBlobsPerBlock returns the latest max blobs per block defined by the +// configuration, regardless of the currently active fork. +func LatestMaxBlobsPerBlock(cfg *params.ChainConfig) int { + s := cfg.BlobScheduleConfig + if s == nil { + return 0 + } + switch { + case s.Prague != nil: + return s.Prague.Max + case s.Cancun != nil: + return s.Cancun.Max + default: + return 0 + } +} + +// targetBlobsPerBlock returns the target number of blobs in a block at the given timestamp. +func targetBlobsPerBlock(cfg *params.ChainConfig, time uint64) int { + if cfg.BlobScheduleConfig == nil { + return 0 + } + var ( + london = cfg.LondonBlock + s = cfg.BlobScheduleConfig + ) + switch { + case cfg.IsPrague(london, time) && s.Prague != nil: + return s.Prague.Target + case cfg.IsCancun(london, time) && s.Cancun != nil: + return s.Cancun.Target + default: + return 0 + } } // fakeExponential approximates factor * e ** (numerator / denominator) using diff --git a/consensus/misc/eip4844/eip4844_test.go b/consensus/misc/eip4844/eip4844_test.go index ec417380fc..839ea8df22 100644 --- a/consensus/misc/eip4844/eip4844_test.go +++ b/consensus/misc/eip4844/eip4844_test.go @@ -21,36 +21,48 @@ import ( "math/big" "testing" + "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/params" ) func TestCalcExcessBlobGas(t *testing.T) { + var ( + config = params.MainnetChainConfig + targetBlobs = targetBlobsPerBlock(config, *config.CancunTime) + targetBlobGas = uint64(targetBlobs) * params.BlobTxBlobGasPerBlob + ) var tests = []struct { excess uint64 - blobs uint64 + blobs int want uint64 }{ // The excess blob gas should not increase from zero if the used blob // slots are below - or equal - to the target. {0, 0, 0}, {0, 1, 0}, - {0, params.BlobTxTargetBlobGasPerBlock / params.BlobTxBlobGasPerBlob, 0}, + {0, targetBlobs, 0}, // If the target blob gas is exceeded, the excessBlobGas should increase // by however much it was overshot - {0, (params.BlobTxTargetBlobGasPerBlock / params.BlobTxBlobGasPerBlob) + 1, params.BlobTxBlobGasPerBlob}, - {1, (params.BlobTxTargetBlobGasPerBlock / params.BlobTxBlobGasPerBlob) + 1, params.BlobTxBlobGasPerBlob + 1}, - {1, (params.BlobTxTargetBlobGasPerBlock / params.BlobTxBlobGasPerBlob) + 2, 2*params.BlobTxBlobGasPerBlob + 1}, + {0, targetBlobs + 1, params.BlobTxBlobGasPerBlob}, + {1, targetBlobs + 1, params.BlobTxBlobGasPerBlob + 1}, + {1, targetBlobs + 2, 2*params.BlobTxBlobGasPerBlob + 1}, // The excess blob gas should decrease by however much the target was // under-shot, capped at zero. - {params.BlobTxTargetBlobGasPerBlock, params.BlobTxTargetBlobGasPerBlock / params.BlobTxBlobGasPerBlob, params.BlobTxTargetBlobGasPerBlock}, - {params.BlobTxTargetBlobGasPerBlock, (params.BlobTxTargetBlobGasPerBlock / params.BlobTxBlobGasPerBlob) - 1, params.BlobTxTargetBlobGasPerBlock - params.BlobTxBlobGasPerBlob}, - {params.BlobTxTargetBlobGasPerBlock, (params.BlobTxTargetBlobGasPerBlock / params.BlobTxBlobGasPerBlob) - 2, params.BlobTxTargetBlobGasPerBlock - (2 * params.BlobTxBlobGasPerBlob)}, - {params.BlobTxBlobGasPerBlob - 1, (params.BlobTxTargetBlobGasPerBlock / params.BlobTxBlobGasPerBlob) - 1, 0}, + {targetBlobGas, targetBlobs, targetBlobGas}, + {targetBlobGas, targetBlobs - 1, targetBlobGas - params.BlobTxBlobGasPerBlob}, + {targetBlobGas, targetBlobs - 2, targetBlobGas - (2 * params.BlobTxBlobGasPerBlob)}, + {params.BlobTxBlobGasPerBlob - 1, targetBlobs - 1, 0}, } for i, tt := range tests { - result := CalcExcessBlobGas(tt.excess, tt.blobs*params.BlobTxBlobGasPerBlob) + blobGasUsed := uint64(tt.blobs) * params.BlobTxBlobGasPerBlob + parent := &types.Header{ + Time: *config.CancunTime, + ExcessBlobGas: &tt.excess, + BlobGasUsed: &blobGasUsed, + } + result := CalcExcessBlobGas(config, parent) if result != tt.want { t.Errorf("test %d: excess blob gas mismatch: have %v, want %v", i, result, tt.want) } @@ -58,6 +70,8 @@ func TestCalcExcessBlobGas(t *testing.T) { } func TestCalcBlobFee(t *testing.T) { + zero := uint64(0) + tests := []struct { excessBlobGas uint64 blobfee int64 @@ -68,7 +82,9 @@ func TestCalcBlobFee(t *testing.T) { {10 * 1024 * 1024, 23}, } for i, tt := range tests { - have := CalcBlobFee(tt.excessBlobGas) + config := ¶ms.ChainConfig{LondonBlock: big.NewInt(0), CancunTime: &zero, BlobScheduleConfig: params.DefaultBlobSchedule} + header := &types.Header{ExcessBlobGas: &tt.excessBlobGas} + have := CalcBlobFee(config, header) if have.Int64() != tt.blobfee { t.Errorf("test %d: blobfee mismatch: have %v want %v", i, have, tt.blobfee) } diff --git a/core/blockchain.go b/core/blockchain.go index f097dc9781..b98c2d43aa 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -2139,9 +2139,8 @@ func (bc *BlockChain) recoverAncestors(block *types.Block, makeWitness bool) (co // processing of a block. These logs are later announced as deleted or reborn. func (bc *BlockChain) collectLogs(b *types.Block, removed bool) []*types.Log { var blobGasPrice *big.Int - excessBlobGas := b.ExcessBlobGas() - if excessBlobGas != nil { - blobGasPrice = eip4844.CalcBlobFee(*excessBlobGas) + if b.ExcessBlobGas() != nil { + blobGasPrice = eip4844.CalcBlobFee(bc.chainConfig, b.Header()) } receipts := rawdb.ReadRawReceipts(bc.db, b.Hash(), b.NumberU64()) if err := receipts.DeriveFields(bc.chainConfig, b.Hash(), b.NumberU64(), b.Time(), b.BaseFee(), blobGasPrice, b.Transactions()); err != nil { diff --git a/core/chain_makers.go b/core/chain_makers.go index 1f48f9d128..19f433d7b0 100644 --- a/core/chain_makers.go +++ b/core/chain_makers.go @@ -143,7 +143,9 @@ func (b *BlockGen) addTx(bc *BlockChain, vmConfig vm.Config, tx *types.Transacti // instruction will panic during execution if it attempts to access a block number outside // of the range created by GenerateChain. func (b *BlockGen) AddTx(tx *types.Transaction) { - b.addTx(nil, vm.Config{}, tx) + // Wrap the chain config in an empty BlockChain object to satisfy ChainContext. + bc := &BlockChain{chainConfig: b.cm.config} + b.addTx(bc, vm.Config{}, tx) } // AddTxWithChain adds a transaction to the generated block. If no coinbase has @@ -445,7 +447,7 @@ func GenerateChain(config *params.ChainConfig, parent *types.Block, engine conse } var blobGasPrice *big.Int if block.ExcessBlobGas() != nil { - blobGasPrice = eip4844.CalcBlobFee(*block.ExcessBlobGas()) + blobGasPrice = eip4844.CalcBlobFee(cm.config, block.Header()) } if err := receipts.DeriveFields(config, block.Hash(), block.NumberU64(), block.Time(), block.BaseFee(), blobGasPrice, txs); err != nil { panic(err) @@ -548,7 +550,7 @@ func GenerateVerkleChain(config *params.ChainConfig, parent *types.Block, engine } var blobGasPrice *big.Int if block.ExcessBlobGas() != nil { - blobGasPrice = eip4844.CalcBlobFee(*block.ExcessBlobGas()) + blobGasPrice = eip4844.CalcBlobFee(cm.config, block.Header()) } if err := receipts.DeriveFields(config, block.Hash(), block.NumberU64(), block.Time(), block.BaseFee(), blobGasPrice, txs); err != nil { panic(err) @@ -598,15 +600,7 @@ func (cm *chainMaker) makeHeader(parent *types.Block, state *state.StateDB, engi } } if cm.config.IsCancun(header.Number, header.Time) { - var ( - parentExcessBlobGas uint64 - parentBlobGasUsed uint64 - ) - if parent.ExcessBlobGas() != nil { - parentExcessBlobGas = *parent.ExcessBlobGas() - parentBlobGasUsed = *parent.BlobGasUsed() - } - excessBlobGas := eip4844.CalcExcessBlobGas(parentExcessBlobGas, parentBlobGasUsed) + excessBlobGas := eip4844.CalcExcessBlobGas(cm.config, parent.Header()) header.ExcessBlobGas = &excessBlobGas header.BlobGasUsed = new(uint64) header.ParentBeaconRoot = new(common.Hash) diff --git a/core/chain_makers_test.go b/core/chain_makers_test.go index cb1087b4b3..d81a52e915 100644 --- a/core/chain_makers_test.go +++ b/core/chain_makers_test.go @@ -42,7 +42,7 @@ func TestGeneratePOSChain(t *testing.T) { aa = common.Address{0xaa} bb = common.Address{0xbb} funds = big.NewInt(0).Mul(big.NewInt(1337), big.NewInt(params.Ether)) - config = *params.AllEthashProtocolChanges + config = *params.MergedTestChainConfig gspec = &Genesis{ Config: &config, Alloc: types.GenesisAlloc{ @@ -57,10 +57,6 @@ func TestGeneratePOSChain(t *testing.T) { db = rawdb.NewMemoryDatabase() ) - config.TerminalTotalDifficulty = common.Big0 - config.ShanghaiTime = u64(0) - config.CancunTime = u64(0) - // init 0xaa with some storage elements storage := make(map[common.Hash]common.Hash) storage[common.Hash{0x00}] = common.Hash{0x00} diff --git a/core/evm.go b/core/evm.go index 5d3c454d7c..41b4e6ac58 100644 --- a/core/evm.go +++ b/core/evm.go @@ -25,6 +25,7 @@ import ( "github.com/ethereum/go-ethereum/core/tracing" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/vm" + "github.com/ethereum/go-ethereum/params" "github.com/holiman/uint256" ) @@ -36,6 +37,9 @@ type ChainContext interface { // GetHeader returns the header corresponding to the hash/number argument pair. GetHeader(common.Hash, uint64) *types.Header + + // Config returns the chain's configuration. + Config() *params.ChainConfig } // NewEVMBlockContext creates a new context for use in the EVM. @@ -57,7 +61,7 @@ func NewEVMBlockContext(header *types.Header, chain ChainContext, author *common baseFee = new(big.Int).Set(header.BaseFee) } if header.ExcessBlobGas != nil { - blobBaseFee = eip4844.CalcBlobFee(*header.ExcessBlobGas) + blobBaseFee = eip4844.CalcBlobFee(chain.Config(), header) } if header.Difficulty.Sign() == 0 { random = &header.MixDigest diff --git a/core/genesis_test.go b/core/genesis_test.go index 714dc4d6b3..3e120a9307 100644 --- a/core/genesis_test.go +++ b/core/genesis_test.go @@ -278,6 +278,11 @@ func TestVerkleGenesisCommit(t *testing.T) { EnableVerkleAtGenesis: true, Ethash: nil, Clique: nil, + BlobScheduleConfig: ¶ms.BlobScheduleConfig{ + Cancun: params.DefaultCancunBlobConfig, + Prague: params.DefaultPragueBlobConfig, + Verkle: params.DefaultPragueBlobConfig, + }, } genesis := &Genesis{ diff --git a/core/rawdb/accessors_chain.go b/core/rawdb/accessors_chain.go index a586046d5f..1a2b31b360 100644 --- a/core/rawdb/accessors_chain.go +++ b/core/rawdb/accessors_chain.go @@ -587,7 +587,7 @@ func ReadReceipts(db ethdb.Reader, hash common.Hash, number uint64, time uint64, // Compute effective blob gas price. var blobGasPrice *big.Int if header != nil && header.ExcessBlobGas != nil { - blobGasPrice = eip4844.CalcBlobFee(*header.ExcessBlobGas) + blobGasPrice = eip4844.CalcBlobFee(config, header) } if err := receipts.DeriveFields(config, hash, number, time, baseFee, blobGasPrice, body.Transactions); err != nil { log.Error("Failed to derive block receipts fields", "hash", hash, "number", number, "err", err) diff --git a/core/state_processor_test.go b/core/state_processor_test.go index 750f3708dc..7d62e90ad4 100644 --- a/core/state_processor_test.go +++ b/core/state_processor_test.go @@ -46,25 +46,7 @@ func u64(val uint64) *uint64 { return &val } // contain invalid transactions func TestStateProcessorErrors(t *testing.T) { var ( - config = ¶ms.ChainConfig{ - ChainID: big.NewInt(1), - HomesteadBlock: big.NewInt(0), - EIP150Block: big.NewInt(0), - EIP155Block: big.NewInt(0), - EIP158Block: big.NewInt(0), - ByzantiumBlock: big.NewInt(0), - ConstantinopleBlock: big.NewInt(0), - PetersburgBlock: big.NewInt(0), - IstanbulBlock: big.NewInt(0), - MuirGlacierBlock: big.NewInt(0), - BerlinBlock: big.NewInt(0), - LondonBlock: big.NewInt(0), - Ethash: new(params.EthashConfig), - TerminalTotalDifficulty: big.NewInt(0), - ShanghaiTime: new(uint64), - CancunTime: new(uint64), - PragueTime: new(uint64), - } + config = params.MergedTestChainConfig signer = types.LatestSigner(config) key1, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") key2, _ = crypto.HexToECDSA("0202020202020202020202020202020202020202020202020202002020202020") @@ -425,12 +407,7 @@ func GenerateBadBlock(parent *types.Block, engine consensus.Engine, txs types.Tr } header.Root = common.BytesToHash(hasher.Sum(nil)) if config.IsCancun(header.Number, header.Time) { - var pExcess, pUsed = uint64(0), uint64(0) - if parent.ExcessBlobGas() != nil { - pExcess = *parent.ExcessBlobGas() - pUsed = *parent.BlobGasUsed() - } - excess := eip4844.CalcExcessBlobGas(pExcess, pUsed) + excess := eip4844.CalcExcessBlobGas(config, parent.Header()) used := uint64(nBlobs * params.BlobTxBlobGasPerBlob) header.ExcessBlobGas = &excess header.BlobGasUsed = &used diff --git a/core/txpool/blobpool/blobpool.go b/core/txpool/blobpool/blobpool.go index 4ab14bbcc0..78cf2c05af 100644 --- a/core/txpool/blobpool/blobpool.go +++ b/core/txpool/blobpool/blobpool.go @@ -51,11 +51,6 @@ const ( // transaction. There can be multiple of these embedded into a single tx. blobSize = params.BlobTxFieldElementsPerBlob * params.BlobTxBytesPerFieldElement - // maxBlobsPerTransaction is the maximum number of blobs a single transaction - // is allowed to contain. Whilst the spec states it's unlimited, the block - // data slots are protocol bound, which implicitly also limit this. - maxBlobsPerTransaction = params.MaxBlobGasPerBlock / params.BlobTxBlobGasPerBlob - // txAvgSize is an approximate byte size of a transaction metadata to avoid // tiny overflows causing all txs to move a shelf higher, wasting disk space. txAvgSize = 4 * 1024 @@ -223,6 +218,11 @@ func newBlobTxMeta(id uint64, size uint32, tx *types.Transaction) *blobTxMeta { // very relaxed ones can be included even if the fees go up, when the closer // ones could already be invalid. // +// - Because the maximum number of blobs allowed in a block can change per +// fork, the pool is designed to handle the maximum number of blobs allowed +// in the chain's latest defined fork -- even if it isn't active. This +// avoids needing to upgrade the database around the fork boundary. +// // When the pool eventually reaches saturation, some old transactions - that may // never execute - will need to be evicted in favor of newer ones. The eviction // strategy is quite complex: @@ -387,7 +387,8 @@ func (p *BlobPool) Init(gasTip uint64, head *types.Header, reserve txpool.Addres fails = append(fails, id) } } - store, err := billy.Open(billy.Options{Path: queuedir, Repair: true}, newSlotter(), index) + slotter := newSlotter(eip4844.LatestMaxBlobsPerBlock(p.chain.Config())) + store, err := billy.Open(billy.Options{Path: queuedir, Repair: true}, slotter, index) if err != nil { return err } @@ -414,13 +415,13 @@ func (p *BlobPool) Init(gasTip uint64, head *types.Header, reserve txpool.Addres blobfee = uint256.NewInt(params.BlobTxMinBlobGasprice) ) if p.head.ExcessBlobGas != nil { - blobfee = uint256.MustFromBig(eip4844.CalcBlobFee(*p.head.ExcessBlobGas)) + blobfee = uint256.MustFromBig(eip4844.CalcBlobFee(p.chain.Config(), p.head)) } p.evict = newPriceHeap(basefee, blobfee, p.index) // Pool initialized, attach the blob limbo to it to track blobs included // recently but not yet finalized - p.limbo, err = newLimbo(limbodir) + p.limbo, err = newLimbo(limbodir, eip4844.LatestMaxBlobsPerBlock(p.chain.Config())) if err != nil { p.Close() return err @@ -834,7 +835,7 @@ func (p *BlobPool) Reset(oldHead, newHead *types.Header) { blobfee = uint256.MustFromBig(big.NewInt(params.BlobTxMinBlobGasprice)) ) if newHead.ExcessBlobGas != nil { - blobfee = uint256.MustFromBig(eip4844.CalcBlobFee(*newHead.ExcessBlobGas)) + blobfee = uint256.MustFromBig(eip4844.CalcBlobFee(p.chain.Config(), newHead)) } p.evict.reinit(basefee, blobfee, false) @@ -1598,7 +1599,8 @@ func (p *BlobPool) updateStorageMetrics() { metrics.GetOrRegisterGauge(fmt.Sprintf(shelfSlotusedGaugeName, shelf.SlotSize/blobSize), nil).Update(int64(shelf.FilledSlots)) metrics.GetOrRegisterGauge(fmt.Sprintf(shelfSlotgapsGaugeName, shelf.SlotSize/blobSize), nil).Update(int64(shelf.GappedSlots)) - if shelf.SlotSize/blobSize > maxBlobsPerTransaction { + maxBlobs := eip4844.LatestMaxBlobsPerBlock(p.chain.Config()) + if shelf.SlotSize/blobSize > uint32(maxBlobs) { oversizedDataused += slotDataused oversizedDatagaps += slotDatagaps oversizedSlotused += shelf.FilledSlots diff --git a/core/txpool/blobpool/blobpool_test.go b/core/txpool/blobpool/blobpool_test.go index 3d90ec4412..0187e8d9a0 100644 --- a/core/txpool/blobpool/blobpool_test.go +++ b/core/txpool/blobpool/blobpool_test.go @@ -51,8 +51,10 @@ var ( testBlobVHashes [][32]byte ) +const testMaxBlobsPerBlock = 6 + func init() { - for i := 0; i < 10; i++ { + for i := 0; i < 24; i++ { testBlob := &kzg4844.Blob{byte(i)} testBlobs = append(testBlobs, testBlob) @@ -121,7 +123,12 @@ func (bc *testBlockChain) CurrentBlock() *types.Header { mid := new(big.Int).Add(lo, hi) mid.Div(mid, big.NewInt(2)) - if eip4844.CalcBlobFee(mid.Uint64()).Cmp(bc.blobfee.ToBig()) > 0 { + tmp := mid.Uint64() + if eip4844.CalcBlobFee(bc.Config(), &types.Header{ + Number: blockNumber, + Time: blockTime, + ExcessBlobGas: &tmp, + }).Cmp(bc.blobfee.ToBig()) > 0 { hi = mid } else { lo = mid @@ -194,10 +201,43 @@ func makeTx(nonce uint64, gasTipCap uint64, gasFeeCap uint64, blobFeeCap uint64, return types.MustSignNewTx(key, types.LatestSigner(params.MainnetChainConfig), blobtx) } +// makeMultiBlobTx is a utility method to construct a ramdom blob tx with +// certain number of blobs in its sidecar. +func makeMultiBlobTx(nonce uint64, gasTipCap uint64, gasFeeCap uint64, blobFeeCap uint64, blobCount int, key *ecdsa.PrivateKey) *types.Transaction { + var ( + blobs []kzg4844.Blob + blobHashes []common.Hash + commitments []kzg4844.Commitment + proofs []kzg4844.Proof + ) + for i := 0; i < blobCount; i++ { + blobs = append(blobs, *testBlobs[i]) + commitments = append(commitments, testBlobCommits[i]) + proofs = append(proofs, testBlobProofs[i]) + blobHashes = append(blobHashes, testBlobVHashes[i]) + } + blobtx := &types.BlobTx{ + ChainID: uint256.MustFromBig(params.MainnetChainConfig.ChainID), + Nonce: nonce, + GasTipCap: uint256.NewInt(gasTipCap), + GasFeeCap: uint256.NewInt(gasFeeCap), + Gas: 21000, + BlobFeeCap: uint256.NewInt(blobFeeCap), + BlobHashes: blobHashes, + Value: uint256.NewInt(100), + Sidecar: &types.BlobTxSidecar{ + Blobs: blobs, + Commitments: commitments, + Proofs: proofs, + }, + } + return types.MustSignNewTx(key, types.LatestSigner(params.MainnetChainConfig), blobtx) +} + // makeUnsignedTx is a utility method to construct a random blob transaction // without signing it. func makeUnsignedTx(nonce uint64, gasTipCap uint64, gasFeeCap uint64, blobFeeCap uint64) *types.BlobTx { - return makeUnsignedTxWithTestBlob(nonce, gasTipCap, gasFeeCap, blobFeeCap, rand.Intn(len(testBlobs))) + return makeUnsignedTxWithTestBlob(nonce, gasTipCap, gasFeeCap, blobFeeCap, rnd.Intn(len(testBlobs))) } // makeUnsignedTx is a utility method to construct a random blob transaction @@ -415,7 +455,7 @@ func TestOpenDrops(t *testing.T) { defer os.RemoveAll(storage) os.MkdirAll(filepath.Join(storage, pendingTransactionStore), 0700) - store, _ := billy.Open(billy.Options{Path: filepath.Join(storage, pendingTransactionStore)}, newSlotter(), nil) + store, _ := billy.Open(billy.Options{Path: filepath.Join(storage, pendingTransactionStore)}, newSlotter(testMaxBlobsPerBlock), nil) // Insert a malformed transaction to verify that decoding errors (or format // changes) are handled gracefully (case 1) @@ -738,7 +778,7 @@ func TestOpenIndex(t *testing.T) { defer os.RemoveAll(storage) os.MkdirAll(filepath.Join(storage, pendingTransactionStore), 0700) - store, _ := billy.Open(billy.Options{Path: filepath.Join(storage, pendingTransactionStore)}, newSlotter(), nil) + store, _ := billy.Open(billy.Options{Path: filepath.Join(storage, pendingTransactionStore)}, newSlotter(testMaxBlobsPerBlock), nil) // Insert a sequence of transactions with varying price points to check that // the cumulative minimum will be maintained. @@ -827,7 +867,7 @@ func TestOpenHeap(t *testing.T) { defer os.RemoveAll(storage) os.MkdirAll(filepath.Join(storage, pendingTransactionStore), 0700) - store, _ := billy.Open(billy.Options{Path: filepath.Join(storage, pendingTransactionStore)}, newSlotter(), nil) + store, _ := billy.Open(billy.Options{Path: filepath.Join(storage, pendingTransactionStore)}, newSlotter(testMaxBlobsPerBlock), nil) // Insert a few transactions from a few accounts. To remove randomness from // the heap initialization, use a deterministic account/tx/priority ordering. @@ -914,7 +954,7 @@ func TestOpenCap(t *testing.T) { defer os.RemoveAll(storage) os.MkdirAll(filepath.Join(storage, pendingTransactionStore), 0700) - store, _ := billy.Open(billy.Options{Path: filepath.Join(storage, pendingTransactionStore)}, newSlotter(), nil) + store, _ := billy.Open(billy.Options{Path: filepath.Join(storage, pendingTransactionStore)}, newSlotter(testMaxBlobsPerBlock), nil) // Insert a few transactions from a few accounts var ( @@ -992,6 +1032,108 @@ func TestOpenCap(t *testing.T) { } } +// TestChangingSlotterSize attempts to mimic a scenario where the max blob count +// of the pool is increased. This would happen during a client release where a +// new fork is added with a max blob count higher than the previous fork. We +// want to make sure transactions a persisted between those runs. +func TestChangingSlotterSize(t *testing.T) { + //log.SetDefault(log.NewLogger(log.NewTerminalHandlerWithLevel(os.Stderr, log.LevelTrace, true))) + + // Create a temporary folder for the persistent backend + storage, _ := os.MkdirTemp("", "blobpool-") + defer os.RemoveAll(storage) + + os.MkdirAll(filepath.Join(storage, pendingTransactionStore), 0700) + store, _ := billy.Open(billy.Options{Path: filepath.Join(storage, pendingTransactionStore)}, newSlotter(6), nil) + + // Create transactions from a few accounts. + var ( + key1, _ = crypto.GenerateKey() + key2, _ = crypto.GenerateKey() + key3, _ = crypto.GenerateKey() + + addr1 = crypto.PubkeyToAddress(key1.PublicKey) + addr2 = crypto.PubkeyToAddress(key2.PublicKey) + addr3 = crypto.PubkeyToAddress(key3.PublicKey) + + tx1 = makeMultiBlobTx(0, 1, 1000, 100, 6, key1) + tx2 = makeMultiBlobTx(0, 1, 800, 70, 6, key2) + tx3 = makeMultiBlobTx(0, 1, 800, 110, 24, key3) + + blob1, _ = rlp.EncodeToBytes(tx1) + blob2, _ = rlp.EncodeToBytes(tx2) + ) + + // Write the two safely sized txs to store. note: although the store is + // configured for a blob count of 6, it can also support around ~1mb of call + // data - all this to say that we aren't using the the absolute largest shelf + // available. + store.Put(blob1) + store.Put(blob2) + store.Close() + + // Mimic a blobpool with max blob count of 6 upgrading to a max blob count of 24. + for _, maxBlobs := range []int{6, 24} { + statedb, _ := state.New(types.EmptyRootHash, state.NewDatabaseForTesting()) + statedb.AddBalance(addr1, uint256.NewInt(1_000_000_000), tracing.BalanceChangeUnspecified) + statedb.AddBalance(addr2, uint256.NewInt(1_000_000_000), tracing.BalanceChangeUnspecified) + statedb.AddBalance(addr3, uint256.NewInt(1_000_000_000), tracing.BalanceChangeUnspecified) + statedb.Commit(0, true, false) + + // Make custom chain config where the max blob count changes based on the loop variable. + cancunTime := uint64(0) + config := ¶ms.ChainConfig{ + ChainID: big.NewInt(1), + LondonBlock: big.NewInt(0), + BerlinBlock: big.NewInt(0), + CancunTime: &cancunTime, + BlobScheduleConfig: ¶ms.BlobScheduleConfig{ + Cancun: ¶ms.BlobConfig{ + Target: maxBlobs / 2, + Max: maxBlobs, + UpdateFraction: params.DefaultCancunBlobConfig.UpdateFraction, + }, + }, + } + chain := &testBlockChain{ + config: config, + basefee: uint256.NewInt(1050), + blobfee: uint256.NewInt(105), + statedb: statedb, + } + pool := New(Config{Datadir: storage}, chain) + if err := pool.Init(1, chain.CurrentBlock(), makeAddressReserver()); err != nil { + t.Fatalf("failed to create blob pool: %v", err) + } + + // Try to add the big blob tx. In the initial iteration it should overflow + // the pool. On the subsequent iteration it should be accepted. + errs := pool.Add([]*types.Transaction{tx3}, false, true) + if _, ok := pool.index[addr3]; ok && maxBlobs == 6 { + t.Errorf("expected insert of oversized blob tx to fail: blobs=24, maxBlobs=%d, err=%v", maxBlobs, errs[0]) + } else if !ok && maxBlobs == 10 { + t.Errorf("expected insert of oversized blob tx to succeed: blobs=24, maxBlobs=%d, err=%v", maxBlobs, errs[0]) + } + + // Verify the regular two txs are always available. + if got := pool.Get(tx1.Hash()); got == nil { + t.Errorf("expected tx %s from %s in pool", tx1.Hash(), addr1) + } + if got := pool.Get(tx2.Hash()); got == nil { + t.Errorf("expected tx %s from %s in pool", tx2.Hash(), addr2) + } + + // Verify all the calculated pool internals. Interestingly, this is **not** + // a duplication of the above checks, this actually validates the verifier + // using the above already hard coded checks. + // + // Do not remove this, nor alter the above to be generic. + verifyPoolInternals(t, pool) + + pool.Close() + } +} + // Tests that adding transaction will correctly store it in the persistent store // and update all the indices. // @@ -1369,7 +1511,7 @@ func TestAdd(t *testing.T) { defer os.RemoveAll(storage) // late defer, still ok os.MkdirAll(filepath.Join(storage, pendingTransactionStore), 0700) - store, _ := billy.Open(billy.Options{Path: filepath.Join(storage, pendingTransactionStore)}, newSlotter(), nil) + store, _ := billy.Open(billy.Options{Path: filepath.Join(storage, pendingTransactionStore)}, newSlotter(testMaxBlobsPerBlock), nil) // Insert the seed transactions for the pool startup var ( diff --git a/core/txpool/blobpool/evictheap_test.go b/core/txpool/blobpool/evictheap_test.go index b03dd83d69..e392932401 100644 --- a/core/txpool/blobpool/evictheap_test.go +++ b/core/txpool/blobpool/evictheap_test.go @@ -26,7 +26,7 @@ import ( "github.com/holiman/uint256" ) -var rand = mrand.New(mrand.NewSource(1)) +var rnd = mrand.New(mrand.NewSource(1)) // verifyHeapInternals verifies that all accounts present in the index are also // present in the heap and internals are consistent across various indices. @@ -193,12 +193,12 @@ func benchmarkPriceHeapReinit(b *testing.B, datacap uint64) { index := make(map[common.Address][]*blobTxMeta) for i := 0; i < int(blobs); i++ { var addr common.Address - rand.Read(addr[:]) + rnd.Read(addr[:]) var ( - execTip = uint256.NewInt(rand.Uint64()) - execFee = uint256.NewInt(rand.Uint64()) - blobFee = uint256.NewInt(rand.Uint64()) + execTip = uint256.NewInt(rnd.Uint64()) + execFee = uint256.NewInt(rnd.Uint64()) + blobFee = uint256.NewInt(rnd.Uint64()) basefeeJumps = dynamicFeeJumps(execFee) blobfeeJumps = dynamicFeeJumps(blobFee) @@ -218,13 +218,13 @@ func benchmarkPriceHeapReinit(b *testing.B, datacap uint64) { }} } // Create a price heap and reinit it over and over - heap := newPriceHeap(uint256.NewInt(rand.Uint64()), uint256.NewInt(rand.Uint64()), index) + heap := newPriceHeap(uint256.NewInt(rnd.Uint64()), uint256.NewInt(rnd.Uint64()), index) basefees := make([]*uint256.Int, b.N) blobfees := make([]*uint256.Int, b.N) for i := 0; i < b.N; i++ { - basefees[i] = uint256.NewInt(rand.Uint64()) - blobfees[i] = uint256.NewInt(rand.Uint64()) + basefees[i] = uint256.NewInt(rnd.Uint64()) + blobfees[i] = uint256.NewInt(rnd.Uint64()) } b.ResetTimer() b.ReportAllocs() @@ -269,12 +269,12 @@ func benchmarkPriceHeapOverflow(b *testing.B, datacap uint64) { index := make(map[common.Address][]*blobTxMeta) for i := 0; i < int(blobs); i++ { var addr common.Address - rand.Read(addr[:]) + rnd.Read(addr[:]) var ( - execTip = uint256.NewInt(rand.Uint64()) - execFee = uint256.NewInt(rand.Uint64()) - blobFee = uint256.NewInt(rand.Uint64()) + execTip = uint256.NewInt(rnd.Uint64()) + execFee = uint256.NewInt(rnd.Uint64()) + blobFee = uint256.NewInt(rnd.Uint64()) basefeeJumps = dynamicFeeJumps(execFee) blobfeeJumps = dynamicFeeJumps(blobFee) @@ -294,18 +294,18 @@ func benchmarkPriceHeapOverflow(b *testing.B, datacap uint64) { }} } // Create a price heap and overflow it over and over - evict := newPriceHeap(uint256.NewInt(rand.Uint64()), uint256.NewInt(rand.Uint64()), index) + evict := newPriceHeap(uint256.NewInt(rnd.Uint64()), uint256.NewInt(rnd.Uint64()), index) var ( addrs = make([]common.Address, b.N) metas = make([]*blobTxMeta, b.N) ) for i := 0; i < b.N; i++ { - rand.Read(addrs[i][:]) + rnd.Read(addrs[i][:]) var ( - execTip = uint256.NewInt(rand.Uint64()) - execFee = uint256.NewInt(rand.Uint64()) - blobFee = uint256.NewInt(rand.Uint64()) + execTip = uint256.NewInt(rnd.Uint64()) + execFee = uint256.NewInt(rnd.Uint64()) + blobFee = uint256.NewInt(rnd.Uint64()) basefeeJumps = dynamicFeeJumps(execFee) blobfeeJumps = dynamicFeeJumps(blobFee) diff --git a/core/txpool/blobpool/limbo.go b/core/txpool/blobpool/limbo.go index 32381a3936..d5992f2906 100644 --- a/core/txpool/blobpool/limbo.go +++ b/core/txpool/blobpool/limbo.go @@ -48,7 +48,7 @@ type limbo struct { } // newLimbo opens and indexes a set of limboed blob transactions. -func newLimbo(datadir string) (*limbo, error) { +func newLimbo(datadir string, maxBlobsPerTransaction int) (*limbo, error) { l := &limbo{ index: make(map[common.Hash]uint64), groups: make(map[uint64]map[uint64]common.Hash), @@ -60,7 +60,7 @@ func newLimbo(datadir string) (*limbo, error) { fails = append(fails, id) } } - store, err := billy.Open(billy.Options{Path: datadir, Repair: true}, newSlotter(), index) + store, err := billy.Open(billy.Options{Path: datadir, Repair: true}, newSlotter(maxBlobsPerTransaction), index) if err != nil { return nil, err } diff --git a/core/txpool/blobpool/priority_test.go b/core/txpool/blobpool/priority_test.go index cf0e0454a0..1eaee6d7df 100644 --- a/core/txpool/blobpool/priority_test.go +++ b/core/txpool/blobpool/priority_test.go @@ -52,7 +52,7 @@ func TestPriorityCalculation(t *testing.T) { func BenchmarkDynamicFeeJumpCalculation(b *testing.B) { fees := make([]*uint256.Int, b.N) for i := 0; i < b.N; i++ { - fees[i] = uint256.NewInt(rand.Uint64()) + fees[i] = uint256.NewInt(rnd.Uint64()) } b.ResetTimer() b.ReportAllocs() @@ -76,8 +76,8 @@ func BenchmarkPriorityCalculation(b *testing.B) { txBasefeeJumps := make([]float64, b.N) txBlobfeeJumps := make([]float64, b.N) for i := 0; i < b.N; i++ { - txBasefeeJumps[i] = dynamicFeeJumps(uint256.NewInt(rand.Uint64())) - txBlobfeeJumps[i] = dynamicFeeJumps(uint256.NewInt(rand.Uint64())) + txBasefeeJumps[i] = dynamicFeeJumps(uint256.NewInt(rnd.Uint64())) + txBlobfeeJumps[i] = dynamicFeeJumps(uint256.NewInt(rnd.Uint64())) } b.ResetTimer() b.ReportAllocs() diff --git a/core/txpool/blobpool/slotter.go b/core/txpool/blobpool/slotter.go index 35349c3445..84ccc0f27b 100644 --- a/core/txpool/blobpool/slotter.go +++ b/core/txpool/blobpool/slotter.go @@ -25,13 +25,13 @@ package blobpool // The slotter also creates a shelf for 0-blob transactions. Whilst those are not // allowed in the current protocol, having an empty shelf is not a relevant use // of resources, but it makes stress testing with junk transactions simpler. -func newSlotter() func() (uint32, bool) { +func newSlotter(maxBlobsPerTransaction int) func() (uint32, bool) { slotsize := uint32(txAvgSize) slotsize -= uint32(blobSize) // underflows, it's ok, will overflow back in the first return return func() (size uint32, done bool) { slotsize += blobSize - finished := slotsize > maxBlobsPerTransaction*blobSize+txMaxSize + finished := slotsize > uint32(maxBlobsPerTransaction)*blobSize+txMaxSize return slotsize, finished } diff --git a/core/txpool/blobpool/slotter_test.go b/core/txpool/blobpool/slotter_test.go index a7b43b4d22..8d46f47d2c 100644 --- a/core/txpool/blobpool/slotter_test.go +++ b/core/txpool/blobpool/slotter_test.go @@ -21,7 +21,7 @@ import "testing" // Tests that the slotter creates the expected database shelves. func TestNewSlotter(t *testing.T) { // Generate the database shelve sizes - slotter := newSlotter() + slotter := newSlotter(6) var shelves []uint32 for { diff --git a/core/txpool/validation.go b/core/txpool/validation.go index dec711b173..8cd13776a6 100644 --- a/core/txpool/validation.go +++ b/core/txpool/validation.go @@ -23,6 +23,7 @@ import ( "math/big" "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/consensus/misc/eip4844" "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/core/types" @@ -144,8 +145,9 @@ func ValidateTransaction(tx *types.Transaction, head *types.Header, signer types if len(hashes) == 0 { return errors.New("blobless blob transaction") } - if len(hashes) > params.MaxBlobGasPerBlock/params.BlobTxBlobGasPerBlob { - return fmt.Errorf("too many blobs in transaction: have %d, permitted %d", len(hashes), params.MaxBlobGasPerBlock/params.BlobTxBlobGasPerBlob) + maxBlobs := eip4844.MaxBlobsPerBlock(opts.Config, head.Time) + if len(hashes) > maxBlobs { + return fmt.Errorf("too many blobs in transaction: have %d, permitted %d", len(hashes), maxBlobs) } // Ensure commitments, proofs and hashes are valid if err := validateBlobSidecar(hashes, sidecar); err != nil { diff --git a/core/verkle_witness_test.go b/core/verkle_witness_test.go index 500fb37156..4e873eedff 100644 --- a/core/verkle_witness_test.go +++ b/core/verkle_witness_test.go @@ -58,6 +58,9 @@ var ( VerkleTime: u64(0), TerminalTotalDifficulty: common.Big0, EnableVerkleAtGenesis: true, + BlobScheduleConfig: ¶ms.BlobScheduleConfig{ + Verkle: params.DefaultPragueBlobConfig, + }, // TODO uncomment when proof generation is merged // ProofInBlocks: true, } @@ -79,6 +82,9 @@ var ( VerkleTime: u64(0), TerminalTotalDifficulty: common.Big0, EnableVerkleAtGenesis: true, + BlobScheduleConfig: ¶ms.BlobScheduleConfig{ + Verkle: params.DefaultPragueBlobConfig, + }, } ) @@ -226,11 +232,11 @@ func TestProcessParentBlockHash(t *testing.T) { var num = 2 for i := 1; i <= num; i++ { header := &types.Header{ParentHash: common.Hash{byte(i)}, Number: big.NewInt(int64(i)), Difficulty: new(big.Int)} - vmContext := NewEVMBlockContext(header, nil, new(common.Address)) chainConfig := params.MergedTestChainConfig if isVerkle { chainConfig = testVerkleChainConfig } + vmContext := NewEVMBlockContext(header, nil, new(common.Address)) evm := vm.NewEVM(vmContext, statedb, chainConfig, vm.Config{}) ProcessParentBlockHash(header.ParentHash, evm) } diff --git a/core/vm/runtime/runtime_test.go b/core/vm/runtime/runtime_test.go index 6074e9a096..44f5bc8273 100644 --- a/core/vm/runtime/runtime_test.go +++ b/core/vm/runtime/runtime_test.go @@ -314,6 +314,10 @@ func (d *dummyChain) GetHeader(h common.Hash, n uint64) *types.Header { return fakeHeader(n, parentHash) } +func (d *dummyChain) Config() *params.ChainConfig { + return nil +} + // TestBlockhash tests the blockhash operation. It's a bit special, since it internally // requires access to a chain reader. func TestBlockhash(t *testing.T) { diff --git a/eth/api_backend.go b/eth/api_backend.go index 90439e8eab..52ecd91789 100644 --- a/eth/api_backend.go +++ b/eth/api_backend.go @@ -356,7 +356,7 @@ func (b *EthAPIBackend) FeeHistory(ctx context.Context, blockCount uint64, lastB func (b *EthAPIBackend) BlobBaseFee(ctx context.Context) *big.Int { if excess := b.CurrentHeader().ExcessBlobGas; excess != nil { - return eip4844.CalcBlobFee(*excess) + return eip4844.CalcBlobFee(b.ChainConfig(), b.CurrentHeader()) } return nil } diff --git a/eth/catalyst/api_test.go b/eth/catalyst/api_test.go index 407f0db3eb..9840d9c6ad 100644 --- a/eth/catalyst/api_test.go +++ b/eth/catalyst/api_test.go @@ -1227,6 +1227,7 @@ func setupBodies(t *testing.T) (*node.Node, *eth.Ethereum, []*types.Block) { genesis.Config.ShanghaiTime = &time genesis.Config.CancunTime = &time genesis.Config.PragueTime = &time + genesis.Config.BlobScheduleConfig = params.DefaultBlobSchedule n, ethservice := startEthService(t, genesis, blocks) @@ -1543,6 +1544,7 @@ func TestParentBeaconBlockRoot(t *testing.T) { time := blocks[len(blocks)-1].Time() + 5 genesis.Config.ShanghaiTime = &time genesis.Config.CancunTime = &time + genesis.Config.BlobScheduleConfig = params.DefaultBlobSchedule n, ethservice := startEthService(t, genesis, blocks) defer n.Close() @@ -1625,6 +1627,7 @@ func TestWitnessCreationAndConsumption(t *testing.T) { timestamp := blocks[len(blocks)-2].Time() + 5 genesis.Config.ShanghaiTime = ×tamp genesis.Config.CancunTime = ×tamp + genesis.Config.BlobScheduleConfig = params.DefaultBlobSchedule n, ethservice := startEthService(t, genesis, blocks[:9]) defer n.Close() diff --git a/eth/fetcher/tx_fetcher_test.go b/eth/fetcher/tx_fetcher_test.go index f80b1d6096..52b3591086 100644 --- a/eth/fetcher/tx_fetcher_test.go +++ b/eth/fetcher/tx_fetcher_test.go @@ -1108,7 +1108,7 @@ func TestTransactionFetcherBandwidthLimiting(t *testing.T) { doTxNotify{peer: "C", hashes: []common.Hash{{0x07}, {0x08}}, types: []byte{types.BlobTxType, types.BlobTxType}, - sizes: []uint32{params.MaxBlobGasPerBlock, params.MaxBlobGasPerBlock}, + sizes: []uint32{params.BlobTxBlobGasPerBlob * 10, params.BlobTxBlobGasPerBlob * 10}, }, doWait{time: txArriveTimeout, step: true}, isWaiting(nil), @@ -1125,8 +1125,8 @@ func TestTransactionFetcherBandwidthLimiting(t *testing.T) { {common.Hash{0x06}, types.LegacyTxType, maxTxRetrievalSize}, }, "C": { - {common.Hash{0x07}, types.BlobTxType, params.MaxBlobGasPerBlock}, - {common.Hash{0x08}, types.BlobTxType, params.MaxBlobGasPerBlock}, + {common.Hash{0x07}, types.BlobTxType, params.BlobTxBlobGasPerBlob * 10}, + {common.Hash{0x08}, types.BlobTxType, params.BlobTxBlobGasPerBlob * 10}, }, }, fetching: map[string][]common.Hash{ diff --git a/eth/gasprice/feehistory.go b/eth/gasprice/feehistory.go index 1e625e21c0..697263f20b 100644 --- a/eth/gasprice/feehistory.go +++ b/eth/gasprice/feehistory.go @@ -31,7 +31,6 @@ import ( "github.com/ethereum/go-ethereum/consensus/misc/eip4844" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/log" - "github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/rpc" ) @@ -97,8 +96,10 @@ func (oracle *Oracle) processBlock(bf *blockFees, percentiles []float64) { } // Fill in blob base fee and next blob base fee. if excessBlobGas := bf.header.ExcessBlobGas; excessBlobGas != nil { - bf.results.blobBaseFee = eip4844.CalcBlobFee(*excessBlobGas) - bf.results.nextBlobBaseFee = eip4844.CalcBlobFee(eip4844.CalcExcessBlobGas(*excessBlobGas, *bf.header.BlobGasUsed)) + bf.results.blobBaseFee = eip4844.CalcBlobFee(config, bf.header) + excess := eip4844.CalcExcessBlobGas(config, bf.header) + next := &types.Header{Number: bf.header.Number, Time: bf.header.Time, ExcessBlobGas: &excess} + bf.results.nextBlobBaseFee = eip4844.CalcBlobFee(config, next) } else { bf.results.blobBaseFee = new(big.Int) bf.results.nextBlobBaseFee = new(big.Int) @@ -106,7 +107,8 @@ func (oracle *Oracle) processBlock(bf *blockFees, percentiles []float64) { // Compute gas used ratio for normal and blob gas. bf.results.gasUsedRatio = float64(bf.header.GasUsed) / float64(bf.header.GasLimit) if blobGasUsed := bf.header.BlobGasUsed; blobGasUsed != nil { - bf.results.blobGasUsedRatio = float64(*blobGasUsed) / params.MaxBlobGasPerBlock + maxBlobs := eip4844.MaxBlobsPerBlock(config, bf.header.Time) + bf.results.blobGasUsedRatio = float64(*blobGasUsed) / float64(maxBlobs) } if len(percentiles) == 0 { diff --git a/eth/gasprice/gasprice_test.go b/eth/gasprice/gasprice_test.go index 185224d834..0a32c278cb 100644 --- a/eth/gasprice/gasprice_test.go +++ b/eth/gasprice/gasprice_test.go @@ -157,6 +157,7 @@ func newTestBackend(t *testing.T, londonBlock *big.Int, cancunBlock *big.Int, pe ts := gspec.Timestamp + cancunBlock.Uint64()*10 // fixed 10 sec block time in blockgen config.ShanghaiTime = &ts config.CancunTime = &ts + config.BlobScheduleConfig = params.DefaultBlobSchedule signer = types.LatestSigner(gspec.Config) } diff --git a/eth/tracers/internal/tracetest/supply_test.go b/eth/tracers/internal/tracetest/supply_test.go index d918f5aca4..dc46b6d5ef 100644 --- a/eth/tracers/internal/tracetest/supply_test.go +++ b/eth/tracers/internal/tracetest/supply_test.go @@ -360,6 +360,7 @@ func TestSupplySelfdestruct(t *testing.T) { cancunTime := uint64(0) gspec.Config.ShanghaiTime = &cancunTime gspec.Config.CancunTime = &cancunTime + gspec.Config.BlobScheduleConfig = params.DefaultBlobSchedule postCancunOutput, postCancunChain, err := testSupplyTracer(t, gspec, testBlockGenerationFunc) if err != nil { diff --git a/eth/tracers/internal/tracetest/testdata/call_tracer/blob_tx.json b/eth/tracers/internal/tracetest/testdata/call_tracer/blob_tx.json index 05da3b42e1..f8e08532a4 100644 --- a/eth/tracers/internal/tracetest/testdata/call_tracer/blob_tx.json +++ b/eth/tracers/internal/tracetest/testdata/call_tracer/blob_tx.json @@ -41,7 +41,14 @@ "grayGlacierBlock": 0, "shanghaiTime": 0, "cancunTime": 0, - "terminalTotalDifficulty": 0 + "terminalTotalDifficulty": 0, + "blobSchedule": { + "cancun": { + "target": 3, + "max": 6, + "baseFeeUpdateFraction": 3338477 + } + } } }, "context": { diff --git a/eth/tracers/internal/tracetest/testdata/prestate_tracer/blob_tx.json b/eth/tracers/internal/tracetest/testdata/prestate_tracer/blob_tx.json index f8adbabf63..9f452ca5bd 100644 --- a/eth/tracers/internal/tracetest/testdata/prestate_tracer/blob_tx.json +++ b/eth/tracers/internal/tracetest/testdata/prestate_tracer/blob_tx.json @@ -41,7 +41,14 @@ "grayGlacierBlock": 0, "shanghaiTime": 0, "cancunTime": 0, - "terminalTotalDifficulty": 0 + "terminalTotalDifficulty": 0, + "blobSchedule": { + "cancun": { + "target": 3, + "max": 6, + "baseFeeUpdateFraction": 3338477 + } + } } }, "context": { @@ -54,7 +61,9 @@ }, "input": "0x03f8b1820539806485174876e800825208940c2c51a0990aee1d73c1228de1586883415575088080c083020000f842a00100c9fbdf97f747e85847b4f3fff408f89c26842f77c882858bf2c89923849aa00138e3896f3c27f2389147507f8bcec52028b0efca6ee842ed83c9158873943880a0dbac3f97a532c9b00e6239b29036245a5bfbb96940b9d848634661abee98b945a03eec8525f261c2e79798f7b45a5d6ccaefa24576d53ba5023e919b86841c0675", "result": { - "0x0000000000000000000000000000000000000000": { "balance": "0x272e0528" }, + "0x0000000000000000000000000000000000000000": { + "balance": "0x272e0528" + }, "0x0c2c51a0990aee1d73c1228de158688341557508": { "balance": "0xde0b6b3a7640000" } diff --git a/eth/tracers/internal/tracetest/testdata/prestate_tracer/setcode_tx.json b/eth/tracers/internal/tracetest/testdata/prestate_tracer/setcode_tx.json index b7d5ee1c54..043130a072 100644 --- a/eth/tracers/internal/tracetest/testdata/prestate_tracer/setcode_tx.json +++ b/eth/tracers/internal/tracetest/testdata/prestate_tracer/setcode_tx.json @@ -53,7 +53,19 @@ "shanghaiTime": 0, "cancunTime": 0, "pragueTime": 0, - "terminalTotalDifficulty": 0 + "terminalTotalDifficulty": 0, + "blobSchedule": { + "cancun": { + "target": 3, + "max": 6, + "baseFeeUpdateFraction": 3338477 + }, + "prague": { + "target": 3, + "max": 6, + "baseFeeUpdateFraction": 5007716 + } + } } }, "context": { diff --git a/eth/tracers/internal/tracetest/util.go b/eth/tracers/internal/tracetest/util.go index abc2699498..e29144e04e 100644 --- a/eth/tracers/internal/tracetest/util.go +++ b/eth/tracers/internal/tracetest/util.go @@ -9,6 +9,7 @@ import ( "github.com/ethereum/go-ethereum/common/math" "github.com/ethereum/go-ethereum/consensus/misc/eip4844" "github.com/ethereum/go-ethereum/core" + "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/vm" // Force-load native and js packages, to trigger registration @@ -53,8 +54,9 @@ func (c *callContext) toBlockContext(genesis *core.Genesis) vm.BlockContext { } if genesis.ExcessBlobGas != nil && genesis.BlobGasUsed != nil { - excessBlobGas := eip4844.CalcExcessBlobGas(*genesis.ExcessBlobGas, *genesis.BlobGasUsed) - context.BlobBaseFee = eip4844.CalcBlobFee(excessBlobGas) + excess := eip4844.CalcExcessBlobGas(genesis.Config, genesis.ToBlock().Header()) + header := &types.Header{ExcessBlobGas: &excess, Number: genesis.Config.LondonBlock, Time: *genesis.Config.CancunTime} + context.BlobBaseFee = eip4844.CalcBlobFee(genesis.Config, header) } return context } diff --git a/eth/tracers/live/supply.go b/eth/tracers/live/supply.go index fa4e5b1904..bae7445cb4 100644 --- a/eth/tracers/live/supply.go +++ b/eth/tracers/live/supply.go @@ -31,6 +31,7 @@ import ( "github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/eth/tracers" "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/params" "gopkg.in/natefinch/lumberjack.v2" ) @@ -83,6 +84,7 @@ type supplyTracer struct { delta supplyInfo txCallstack []supplyTxCallstack // Callstack for current transaction logger *lumberjack.Logger + chainConfig *params.ChainConfig } type supplyTracerConfig struct { @@ -112,14 +114,15 @@ func newSupplyTracer(cfg json.RawMessage) (*tracing.Hooks, error) { logger: logger, } return &tracing.Hooks{ - OnBlockStart: t.onBlockStart, - OnBlockEnd: t.onBlockEnd, - OnGenesisBlock: t.onGenesisBlock, - OnTxStart: t.onTxStart, - OnBalanceChange: t.onBalanceChange, - OnEnter: t.onEnter, - OnExit: t.onExit, - OnClose: t.onClose, + OnBlockchainInit: t.onBlockchainInit, + OnBlockStart: t.onBlockStart, + OnBlockEnd: t.onBlockEnd, + OnGenesisBlock: t.onGenesisBlock, + OnTxStart: t.onTxStart, + OnBalanceChange: t.onBalanceChange, + OnEnter: t.onEnter, + OnExit: t.onExit, + OnClose: t.onClose, }, nil } @@ -146,6 +149,10 @@ func (s *supplyTracer) resetDelta() { s.delta = newSupplyInfo() } +func (s *supplyTracer) onBlockchainInit(chainConfig *params.ChainConfig) { + s.chainConfig = chainConfig +} + func (s *supplyTracer) onBlockStart(ev tracing.BlockEvent) { s.resetDelta() @@ -161,8 +168,7 @@ func (s *supplyTracer) onBlockStart(ev tracing.BlockEvent) { // Blob burnt gas if blobGas := ev.Block.BlobGasUsed(); blobGas != nil && *blobGas > 0 && ev.Block.ExcessBlobGas() != nil { var ( - excess = *ev.Block.ExcessBlobGas() - baseFee = eip4844.CalcBlobFee(excess) + baseFee = eip4844.CalcBlobFee(s.chainConfig, ev.Block.Header()) burn = new(big.Int).Mul(new(big.Int).SetUint64(*blobGas), baseFee) ) s.delta.Burn.Blob = burn diff --git a/ethclient/ethclient_test.go b/ethclient/ethclient_test.go index 4ad8a552d2..5fc7ed68f9 100644 --- a/ethclient/ethclient_test.go +++ b/ethclient/ethclient_test.go @@ -29,6 +29,7 @@ import ( "github.com/ethereum/go-ethereum" "github.com/ethereum/go-ethereum/accounts/abi" "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/consensus/beacon" "github.com/ethereum/go-ethereum/consensus/ethash" "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core/types" @@ -65,7 +66,7 @@ var ( ) var genesis = &core.Genesis{ - Config: params.AllEthashProtocolChanges, + Config: params.AllDevChainProtocolChanges, Alloc: types.GenesisAlloc{ testAddr: {Balance: testBalance}, revertContractAddr: {Code: revertCode}, @@ -136,7 +137,7 @@ func generateTestChain() []*types.Block { g.AddTx(testTx2) } } - _, blocks, _ := core.GenerateChainWithGenesis(genesis, ethash.NewFaker(), 2, generate) + _, blocks, _ := core.GenerateChainWithGenesis(genesis, beacon.New(ethash.NewFaker()), 2, generate) return append([]*types.Block{genesis.ToBlock()}, blocks...) } @@ -223,7 +224,7 @@ func testHeader(t *testing.T, chain []*types.Block, client *rpc.Client) { if got != nil && got.Number != nil && got.Number.Sign() == 0 { got.Number = big.NewInt(0) // hack to make DeepEqual work } - if !reflect.DeepEqual(got, tt.want) { + if got.Hash() != tt.want.Hash() { t.Fatalf("HeaderByNumber(%v) got = %v, want %v", tt.block, got, tt.want) } }) @@ -314,7 +315,7 @@ func testChainID(t *testing.T, client *rpc.Client) { if err != nil { t.Fatalf("unexpected error: %v", err) } - if id == nil || id.Cmp(params.AllEthashProtocolChanges.ChainID) != 0 { + if id == nil || id.Cmp(params.AllDevChainProtocolChanges.ChainID) != 0 { t.Fatalf("ChainID returned wrong number: %+v", id) } } diff --git a/internal/ethapi/api.go b/internal/ethapi/api.go index d9cec560ea..c0b37c516b 100644 --- a/internal/ethapi/api.go +++ b/internal/ethapi/api.go @@ -624,6 +624,7 @@ func (api *BlockChainAPI) GetBlockReceipts(ctx context.Context, blockNrOrHash rp type ChainContextBackend interface { Engine() consensus.Engine HeaderByNumber(context.Context, rpc.BlockNumber) (*types.Header, error) + ChainConfig() *params.ChainConfig } // ChainContext is an implementation of core.ChainContext. It's main use-case @@ -652,6 +653,10 @@ func (context *ChainContext) GetHeader(hash common.Hash, number uint64) *types.H return header } +func (context *ChainContext) Config() *params.ChainConfig { + return context.b.ChainConfig() +} + func doCall(ctx context.Context, b Backend, args TransactionArgs, state *state.StateDB, header *types.Header, overrides *override.StateOverride, blockOverrides *override.BlockOverrides, timeout time.Duration, globalGasCap uint64) (*core.ExecutionResult, error) { blockCtx := core.NewEVMBlockContext(header, NewChainContext(ctx, b), nil) if blockOverrides != nil { diff --git a/internal/ethapi/simulate.go b/internal/ethapi/simulate.go index 130eaa9724..d364b80485 100644 --- a/internal/ethapi/simulate.go +++ b/internal/ethapi/simulate.go @@ -159,9 +159,7 @@ func (sim *simulator) processBlock(ctx context.Context, block *simBlock, header, if sim.chainConfig.IsCancun(header.Number, header.Time) { var excess uint64 if sim.chainConfig.IsCancun(parent.Number, parent.Time) { - excess = eip4844.CalcExcessBlobGas(*parent.ExcessBlobGas, *parent.BlobGasUsed) - } else { - excess = eip4844.CalcExcessBlobGas(0, 0) + excess = eip4844.CalcExcessBlobGas(sim.chainConfig, parent) } header.ExcessBlobGas = &excess } @@ -415,3 +413,7 @@ func (b *simBackend) HeaderByNumber(ctx context.Context, number rpc.BlockNumber) } return nil, errors.New("header not found") } + +func (b *simBackend) ChainConfig() *params.ChainConfig { + return b.b.ChainConfig() +} diff --git a/internal/ethapi/transaction_args.go b/internal/ethapi/transaction_args.go index 175ac13a0f..7a7d63c535 100644 --- a/internal/ethapi/transaction_args.go +++ b/internal/ethapi/transaction_args.go @@ -37,10 +37,6 @@ import ( "github.com/holiman/uint256" ) -var ( - maxBlobsPerTransaction = params.MaxBlobGasPerBlock / params.BlobTxBlobGasPerBlob -) - // TransactionArgs represents the arguments to construct a new transaction // or a message call. type TransactionArgs struct { @@ -125,8 +121,9 @@ func (args *TransactionArgs) setDefaults(ctx context.Context, b Backend, skipGas if args.BlobHashes != nil && len(args.BlobHashes) == 0 { return errors.New(`need at least 1 blob for a blob transaction`) } - if args.BlobHashes != nil && len(args.BlobHashes) > maxBlobsPerTransaction { - return fmt.Errorf(`too many blobs in transaction (have=%d, max=%d)`, len(args.BlobHashes), maxBlobsPerTransaction) + maxBlobs := eip4844.MaxBlobsPerBlock(b.ChainConfig(), b.CurrentHeader().Time) + if args.BlobHashes != nil && len(args.BlobHashes) > maxBlobs { + return fmt.Errorf(`too many blobs in transaction (have=%d, max=%d)`, len(args.BlobHashes), maxBlobs) } // create check @@ -191,7 +188,9 @@ func (args *TransactionArgs) setFeeDefaults(ctx context.Context, b Backend, head if args.BlobFeeCap != nil && args.BlobFeeCap.ToInt().Sign() == 0 { return errors.New("maxFeePerBlobGas, if specified, must be non-zero") } - args.setCancunFeeDefaults(head) + if b.ChainConfig().IsCancun(head.Number, head.Time) { + args.setCancunFeeDefaults(b.ChainConfig(), head) + } // If both gasPrice and at least one of the EIP-1559 fee parameters are specified, error. if args.GasPrice != nil && (args.MaxFeePerGas != nil || args.MaxPriorityFeePerGas != nil) { return errors.New("both gasPrice and (maxFeePerGas or maxPriorityFeePerGas) specified") @@ -243,15 +242,10 @@ func (args *TransactionArgs) setFeeDefaults(ctx context.Context, b Backend, head } // setCancunFeeDefaults fills in reasonable default fee values for unspecified fields. -func (args *TransactionArgs) setCancunFeeDefaults(head *types.Header) { +func (args *TransactionArgs) setCancunFeeDefaults(config *params.ChainConfig, head *types.Header) { // Set maxFeePerBlobGas if it is missing. if args.BlobHashes != nil && args.BlobFeeCap == nil { - var excessBlobGas uint64 - if head.ExcessBlobGas != nil { - excessBlobGas = *head.ExcessBlobGas - } - // ExcessBlobGas must be set for a Cancun block. - blobBaseFee := eip4844.CalcBlobFee(excessBlobGas) + blobBaseFee := eip4844.CalcBlobFee(config, head) // Set the max fee to be 2 times larger than the previous block's blob base fee. // The additional slack allows the tx to not become invalidated if the base // fee is rising. diff --git a/internal/ethapi/transaction_args_test.go b/internal/ethapi/transaction_args_test.go index 7355c2463c..e017804861 100644 --- a/internal/ethapi/transaction_args_test.go +++ b/internal/ethapi/transaction_args_test.go @@ -279,6 +279,7 @@ func newBackendMock() *backendMock { BerlinBlock: big.NewInt(0), LondonBlock: big.NewInt(1000), CancunTime: &cancunTime, + BlobScheduleConfig: params.DefaultBlobSchedule, } return &backendMock{ current: &types.Header{ diff --git a/miner/worker.go b/miner/worker.go index b5aa080025..f8f4bae833 100644 --- a/miner/worker.go +++ b/miner/worker.go @@ -210,10 +210,7 @@ func (miner *Miner) prepareWork(genParams *generateParams, witness bool) (*envir if miner.chainConfig.IsCancun(header.Number, header.Time) { var excessBlobGas uint64 if miner.chainConfig.IsCancun(parent.Number, parent.Time) { - excessBlobGas = eip4844.CalcExcessBlobGas(*parent.ExcessBlobGas, *parent.BlobGasUsed) - } else { - // For the first post-fork block, both parent.data_gas_used and parent.excess_data_gas are evaluated as 0 - excessBlobGas = eip4844.CalcExcessBlobGas(0, 0) + excessBlobGas = eip4844.CalcExcessBlobGas(miner.chainConfig, parent) } header.BlobGasUsed = new(uint64) header.ExcessBlobGas = &excessBlobGas @@ -284,7 +281,8 @@ func (miner *Miner) commitBlobTransaction(env *environment, tx *types.Transactio // isn't really a better place right now. The blob gas limit is checked at block validation time // and not during execution. This means core.ApplyTransaction will not return an error if the // tx has too many blobs. So we have to explicitly check it here. - if (env.blobs+len(sc.Blobs))*params.BlobTxBlobGasPerBlob > params.MaxBlobGasPerBlock { + maxBlobs := eip4844.MaxBlobsPerBlock(miner.chainConfig, env.header.Time) + if env.blobs+len(sc.Blobs) > maxBlobs { return errors.New("max data blobs reached") } receipt, err := miner.applyTransaction(env, tx) @@ -333,7 +331,7 @@ func (miner *Miner) commitTransactions(env *environment, plainTxs, blobTxs *tran } // If we don't have enough blob space for any further blob transactions, // skip that list altogether - if !blobTxs.Empty() && env.blobs*params.BlobTxBlobGasPerBlob >= params.MaxBlobGasPerBlock { + if !blobTxs.Empty() && env.blobs >= eip4844.MaxBlobsPerBlock(miner.chainConfig, env.header.Time) { log.Trace("Not enough blob space for further blob transactions") blobTxs.Clear() // Fall though to pick up any plain txs @@ -367,11 +365,19 @@ func (miner *Miner) commitTransactions(env *environment, plainTxs, blobTxs *tran txs.Pop() continue } - if left := uint64(params.MaxBlobGasPerBlock - env.blobs*params.BlobTxBlobGasPerBlob); left < ltx.BlobGas { - log.Trace("Not enough blob gas left for transaction", "hash", ltx.Hash, "left", left, "needed", ltx.BlobGas) - txs.Pop() - continue + + // Most of the blob gas logic here is agnostic as to if the chain supports + // blobs or not, however the max check panics when called on a chain without + // a defined schedule, so we need to verify it's safe to call. + if miner.chainConfig.IsCancun(env.header.Number, env.header.Time) { + left := eip4844.MaxBlobsPerBlock(miner.chainConfig, env.header.Time) - env.blobs + if left < int(ltx.BlobGas/params.BlobTxBlobGasPerBlob) { + log.Trace("Not enough blob space left for transaction", "hash", ltx.Hash, "left", left, "needed", ltx.BlobGas/params.BlobTxBlobGasPerBlob) + txs.Pop() + continue + } } + // Transaction seems to fit, pull it up from the pool tx := ltx.Resolve() if tx == nil { @@ -379,6 +385,7 @@ func (miner *Miner) commitTransactions(env *environment, plainTxs, blobTxs *tran txs.Pop() continue } + // Error may be ignored here. The error has already been checked // during transaction acceptance in the transaction pool. from, _ := types.Sender(env.signer, tx) @@ -430,7 +437,7 @@ func (miner *Miner) fillTransactions(interrupt *atomic.Int32, env *environment) filter.BaseFee = uint256.MustFromBig(env.header.BaseFee) } if env.header.ExcessBlobGas != nil { - filter.BlobFee = uint256.MustFromBig(eip4844.CalcBlobFee(*env.header.ExcessBlobGas)) + filter.BlobFee = uint256.MustFromBig(eip4844.CalcBlobFee(miner.chainConfig, env.header)) } filter.OnlyPlainTxs, filter.OnlyBlobTxs = true, false pendingPlainTxs := miner.txpool.Pending(filter) diff --git a/params/config.go b/params/config.go index 9269cb2310..0dbe4c509a 100644 --- a/params/config.go +++ b/params/config.go @@ -17,6 +17,7 @@ package params import ( + "errors" "fmt" "math" "math/big" @@ -60,6 +61,9 @@ var ( CancunTime: newUint64(1710338135), DepositContractAddress: common.HexToAddress("0x00000000219ab540356cbb839cbe05303d7705fa"), Ethash: new(EthashConfig), + BlobScheduleConfig: &BlobScheduleConfig{ + Cancun: DefaultCancunBlobConfig, + }, } // HoleskyChainConfig contains the chain parameters to run a node on the Holesky test network. HoleskyChainConfig = &ChainConfig{ @@ -84,6 +88,9 @@ var ( ShanghaiTime: newUint64(1696000704), CancunTime: newUint64(1707305664), Ethash: new(EthashConfig), + BlobScheduleConfig: &BlobScheduleConfig{ + Cancun: DefaultCancunBlobConfig, + }, } // SepoliaChainConfig contains the chain parameters to run a node on the Sepolia test network. SepoliaChainConfig = &ChainConfig{ @@ -108,6 +115,9 @@ var ( ShanghaiTime: newUint64(1677557088), CancunTime: newUint64(1706655072), Ethash: new(EthashConfig), + BlobScheduleConfig: &BlobScheduleConfig{ + Cancun: DefaultCancunBlobConfig, + }, } // AllEthashProtocolChanges contains every protocol change (EIPs) introduced // and accepted by the Ethereum core developers into the Ethash consensus. @@ -158,6 +168,10 @@ var ( CancunTime: newUint64(0), TerminalTotalDifficulty: big.NewInt(0), PragueTime: newUint64(0), + BlobScheduleConfig: &BlobScheduleConfig{ + Cancun: DefaultCancunBlobConfig, + Prague: DefaultPragueBlobConfig, + }, } // AllCliqueProtocolChanges contains every protocol change (EIPs) introduced @@ -248,6 +262,10 @@ var ( TerminalTotalDifficulty: big.NewInt(0), Ethash: new(EthashConfig), Clique: nil, + BlobScheduleConfig: &BlobScheduleConfig{ + Cancun: DefaultCancunBlobConfig, + Prague: DefaultPragueBlobConfig, + }, } // NonActivatedConfig defines the chain configuration without activating @@ -282,6 +300,26 @@ var ( TestRules = TestChainConfig.Rules(new(big.Int), false, 0) ) +var ( + // DefaultCancunBlobConfig is the default blob configuration for the Cancun fork. + DefaultCancunBlobConfig = &BlobConfig{ + Target: 3, + Max: 6, + UpdateFraction: 3338477, + } + // DefaultPragueBlobConfig is the default blob configuration for the Prague fork. + DefaultPragueBlobConfig = &BlobConfig{ + Target: 6, + Max: 9, + UpdateFraction: 5007716, + } + // DefaultBlobSchedule is the latest configured blob schedule for test chains. + DefaultBlobSchedule = &BlobScheduleConfig{ + Cancun: DefaultCancunBlobConfig, + Prague: DefaultPragueBlobConfig, + } +) + // NetworkNames are user friendly names to use in the chain spec banner. var NetworkNames = map[string]string{ MainnetChainConfig.ChainID.String(): "mainnet", @@ -346,8 +384,9 @@ type ChainConfig struct { EnableVerkleAtGenesis bool `json:"enableVerkleAtGenesis,omitempty"` // Various consensus engines - Ethash *EthashConfig `json:"ethash,omitempty"` - Clique *CliqueConfig `json:"clique,omitempty"` + Ethash *EthashConfig `json:"ethash,omitempty"` + Clique *CliqueConfig `json:"clique,omitempty"` + BlobScheduleConfig *BlobScheduleConfig `json:"blobSchedule,omitempty"` } // EthashConfig is the consensus engine configs for proof-of-work based sealing. @@ -447,6 +486,20 @@ func (c *ChainConfig) Description() string { return banner } +// BlobConfig specifies the target and max blobs per block for the associated fork. +type BlobConfig struct { + Target int `json:"target"` + Max int `json:"max"` + UpdateFraction uint64 `json:"baseFeeUpdateFraction"` +} + +// BlobScheduleConfig determines target and max number of blobs allow per fork. +type BlobScheduleConfig struct { + Cancun *BlobConfig `json:"cancun,omitempty"` + Prague *BlobConfig `json:"prague,omitempty"` + Verkle *BlobConfig `json:"verkle,omitempty"` +} + // IsHomestead returns whether num is either equal to the homestead block or greater. func (c *ChainConfig) IsHomestead(num *big.Int) bool { return isBlockForked(c.HomesteadBlock, num) @@ -662,6 +715,45 @@ func (c *ChainConfig) CheckConfigForkOrder() error { lastFork = cur } } + + // Check that all forks with blobs explicitly define the blob schedule configuration. + bsc := c.BlobScheduleConfig + if bsc == nil { + bsc = new(BlobScheduleConfig) + } + for _, cur := range []struct { + name string + timestamp *uint64 + config *BlobConfig + }{ + {name: "cancun", timestamp: c.CancunTime, config: bsc.Cancun}, + {name: "prague", timestamp: c.PragueTime, config: bsc.Prague}, + } { + if cur.config != nil { + if err := cur.config.validate(); err != nil { + return fmt.Errorf("invalid blob configuration for fork %s: %v", cur.name, err) + } + } + if cur.timestamp != nil { + // If the fork is configured, a blob schedule must be defined for it. + if cur.config == nil { + return fmt.Errorf("unsupported fork configuration: missing blob configuration entry for %v in schedule", cur.name) + } + } + } + return nil +} + +func (bc *BlobConfig) validate() error { + if bc.Max < 0 { + return errors.New("max < 0") + } + if bc.Target < 0 { + return errors.New("target < 0") + } + if bc.UpdateFraction == 0 { + return errors.New("update fraction must be defined and non-zero") + } return nil } diff --git a/params/protocol_params.go b/params/protocol_params.go index c9179634a5..6b06dadaef 100644 --- a/params/protocol_params.go +++ b/params/protocol_params.go @@ -172,12 +172,8 @@ const ( BlobTxFieldElementsPerBlob = 4096 // Number of field elements stored in a single data blob BlobTxBlobGasPerBlob = 1 << 17 // Gas consumption of a single data blob (== blob byte size) BlobTxMinBlobGasprice = 1 // Minimum gas price for data blobs - BlobTxBlobGaspriceUpdateFraction = 3338477 // Controls the maximum rate of change for blob gas price BlobTxPointEvaluationPrecompileGas = 50000 // Gas price for the point evaluation precompile. - BlobTxTargetBlobGasPerBlock = 3 * BlobTxBlobGasPerBlob // Target consumable blob gas for data blobs per block (for 1559-like pricing) - MaxBlobGasPerBlock = 6 * BlobTxBlobGasPerBlob // Maximum consumable blob gas for data blobs per block - HistoryServeWindow = 8192 // Number of blocks to serve historical block hashes for, EIP-2935. ) diff --git a/tests/init.go b/tests/init.go index 8429f38e44..ee8d354224 100644 --- a/tests/init.go +++ b/tests/init.go @@ -334,6 +334,9 @@ var Forks = map[string]*params.ChainConfig{ TerminalTotalDifficulty: big.NewInt(0), ShanghaiTime: u64(0), CancunTime: u64(0), + BlobScheduleConfig: ¶ms.BlobScheduleConfig{ + Cancun: params.DefaultCancunBlobConfig, + }, }, "ShanghaiToCancunAtTime15k": { ChainID: big.NewInt(1), @@ -353,6 +356,9 @@ var Forks = map[string]*params.ChainConfig{ TerminalTotalDifficulty: big.NewInt(0), ShanghaiTime: u64(0), CancunTime: u64(15_000), + BlobScheduleConfig: ¶ms.BlobScheduleConfig{ + Cancun: params.DefaultCancunBlobConfig, + }, }, "Prague": { ChainID: big.NewInt(1), @@ -374,6 +380,10 @@ var Forks = map[string]*params.ChainConfig{ CancunTime: u64(0), PragueTime: u64(0), DepositContractAddress: params.MainnetChainConfig.DepositContractAddress, + BlobScheduleConfig: ¶ms.BlobScheduleConfig{ + Cancun: params.DefaultCancunBlobConfig, + Prague: params.DefaultPragueBlobConfig, + }, }, "CancunToPragueAtTime15k": { ChainID: big.NewInt(1), @@ -395,6 +405,10 @@ var Forks = map[string]*params.ChainConfig{ CancunTime: u64(0), PragueTime: u64(15_000), DepositContractAddress: params.MainnetChainConfig.DepositContractAddress, + BlobScheduleConfig: ¶ms.BlobScheduleConfig{ + Cancun: params.DefaultCancunBlobConfig, + Prague: params.DefaultPragueBlobConfig, + }, }, "Osaka": { ChainID: big.NewInt(1), diff --git a/tests/state_test.go b/tests/state_test.go index da4b8ef79d..0c9553c075 100644 --- a/tests/state_test.go +++ b/tests/state_test.go @@ -303,7 +303,7 @@ func runBenchmark(b *testing.B, t *StateTest) { // Prepare the EVM. txContext := core.NewEVMTxContext(msg) - context := core.NewEVMBlockContext(block.Header(), nil, &t.json.Env.Coinbase) + context := core.NewEVMBlockContext(block.Header(), &dummyChain{config: config}, &t.json.Env.Coinbase) context.GetHash = vmTestBlockHash context.BaseFee = baseFee evm := vm.NewEVM(context, state.StateDB, config, vmconfig) diff --git a/tests/state_test_util.go b/tests/state_test_util.go index e658b62ebf..24caf41ed9 100644 --- a/tests/state_test_util.go +++ b/tests/state_test_util.go @@ -28,6 +28,7 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/common/math" + "github.com/ethereum/go-ethereum/consensus" "github.com/ethereum/go-ethereum/consensus/misc/eip4844" "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core/rawdb" @@ -275,13 +276,14 @@ func (t *StateTest) RunNoVerify(subtest StateSubtest, vmconfig vm.Config, snapsh return st, common.Hash{}, 0, err } - { // Blob transactions may be present after the Cancun fork. - // In production, - // - the header is verified against the max in eip4844.go:VerifyEIP4844Header - // - the block body is verified against the header in block_validator.go:ValidateBody - // Here, we just do this shortcut smaller fix, since state tests do not - // utilize those codepaths - if len(msg.BlobHashes)*params.BlobTxBlobGasPerBlob > params.MaxBlobGasPerBlock { + // Blob transactions may be present after the Cancun fork. + // In production, + // - the header is verified against the max in eip4844.go:VerifyEIP4844Header + // - the block body is verified against the header in block_validator.go:ValidateBody + // Here, we just do this shortcut smaller fix, since state tests do not + // utilize those codepaths. + if config.IsCancun(new(big.Int), block.Time()) { + if len(msg.BlobHashes) > eip4844.MaxBlobsPerBlock(config, block.Time()) { return st, common.Hash{}, 0, errors.New("blob gas exceeds maximum") } } @@ -299,7 +301,7 @@ func (t *StateTest) RunNoVerify(subtest StateSubtest, vmconfig vm.Config, snapsh } // Prepare the EVM. - context := core.NewEVMBlockContext(block.Header(), nil, &t.json.Env.Coinbase) + context := core.NewEVMBlockContext(block.Header(), &dummyChain{config: config}, &t.json.Env.Coinbase) context.GetHash = vmTestBlockHash context.BaseFee = baseFee context.Random = nil @@ -312,8 +314,13 @@ func (t *StateTest) RunNoVerify(subtest StateSubtest, vmconfig vm.Config, snapsh context.Difficulty = big.NewInt(0) } if config.IsCancun(new(big.Int), block.Time()) && t.json.Env.ExcessBlobGas != nil { - context.BlobBaseFee = eip4844.CalcBlobFee(*t.json.Env.ExcessBlobGas) + header := &types.Header{ + Time: block.Time(), + ExcessBlobGas: t.json.Env.ExcessBlobGas, + } + context.BlobBaseFee = eip4844.CalcBlobFee(config, header) } + evm := vm.NewEVM(context, st.StateDB, config, vmconfig) if tracer := vmconfig.Tracer; tracer != nil && tracer.OnTxStart != nil { @@ -543,3 +550,12 @@ func (st *StateTestState) Close() { st.Snapshots = nil } } + +// dummyChain implements the core.ChainContext interface. +type dummyChain struct { + config *params.ChainConfig +} + +func (d *dummyChain) Engine() consensus.Engine { return nil } +func (d *dummyChain) GetHeader(h common.Hash, n uint64) *types.Header { return nil } +func (d *dummyChain) Config() *params.ChainConfig { return d.config }