Skip to content

Fusaka devnet 1 #31981

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 3 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
96 changes: 63 additions & 33 deletions consensus/misc/eip4844/eip4844.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ import (

"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/params/forks"
)

var (
Expand Down Expand Up @@ -71,48 +70,75 @@ func CalcExcessBlobGas(config *params.ChainConfig, parent *types.Header, headTim
parentExcessBlobGas = *parent.ExcessBlobGas
parentBlobGasUsed = *parent.BlobGasUsed
}
excessBlobGas := parentExcessBlobGas + parentBlobGasUsed
targetGas := uint64(targetBlobsPerBlock(config, headTimestamp)) * params.BlobTxBlobGasPerBlob
var (
excessBlobGas = parentExcessBlobGas + parentBlobGasUsed
target = targetBlobsPerBlock(config, headTimestamp)
targetGas = uint64(target) * params.BlobTxBlobGasPerBlob
)
if excessBlobGas < targetGas {
return 0
}
if !config.IsOsaka(config.LondonBlock, headTimestamp) {
return excessBlobGas - targetGas
}
// EIP-7918.
var (
reservePrice = new(big.Int).Mul(parent.BaseFee, big.NewInt(params.BlobBaseCost))
blobPrice = calcBlobPrice(config, parent)
)
if reservePrice.Cmp(blobPrice) > 0 {
max := MaxBlobsPerBlock(config, headTimestamp)
scaledExcess := parentBlobGasUsed * uint64(max-target) / uint64(max)
return parentExcessBlobGas + scaledExcess
}
return excessBlobGas - targetGas
}

// CalcBlobFee calculates the blobfee from the header's excess blob gas field.
func CalcBlobFee(config *params.ChainConfig, header *types.Header) *big.Int {
var frac uint64
switch config.LatestFork(header.Time) {
case forks.Osaka:
frac = config.BlobScheduleConfig.Osaka.UpdateFraction
case forks.Prague:
frac = config.BlobScheduleConfig.Prague.UpdateFraction
case forks.Cancun:
frac = config.BlobScheduleConfig.Cancun.UpdateFraction
default:
blobConfig := latestBlobConfig(config, header.Time)
if blobConfig == nil {
panic("calculating blob fee on unsupported fork")
}
return fakeExponential(minBlobGasPrice, new(big.Int).SetUint64(*header.ExcessBlobGas), new(big.Int).SetUint64(frac))
return fakeExponential(minBlobGasPrice, new(big.Int).SetUint64(*header.ExcessBlobGas), new(big.Int).SetUint64(blobConfig.UpdateFraction))
}

// 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 {
blobConfig := latestBlobConfig(cfg, time)
if blobConfig == nil {
return 0
}
return blobConfig.Max
}

func latestBlobConfig(cfg *params.ChainConfig, time uint64) *params.BlobConfig {
if cfg.BlobScheduleConfig == nil {
return nil
}
var (
london = cfg.LondonBlock
s = cfg.BlobScheduleConfig
)
switch {
case cfg.IsBPO5(london, time) && s.BPO5 != nil:
return s.BPO5
case cfg.IsBPO4(london, time) && s.BPO4 != nil:
return s.BPO4
case cfg.IsBPO3(london, time) && s.BPO3 != nil:
return s.BPO3
case cfg.IsBPO2(london, time) && s.BPO2 != nil:
return s.BPO2
case cfg.IsBPO1(london, time) && s.BPO1 != nil:
return s.BPO1
case cfg.IsOsaka(london, time) && s.Osaka != nil:
return s.Osaka.Max
return s.Osaka
case cfg.IsPrague(london, time) && s.Prague != nil:
return s.Prague.Max
return s.Prague
case cfg.IsCancun(london, time) && s.Cancun != nil:
return s.Cancun.Max
return s.Cancun
default:
return 0
return nil
}
}

Expand All @@ -129,6 +155,16 @@ func LatestMaxBlobsPerBlock(cfg *params.ChainConfig) int {
return 0
}
switch {
case s.BPO5 != nil:
return s.BPO5.Max
case s.BPO4 != nil:
return s.BPO4.Max
case s.BPO3 != nil:
return s.BPO3.Max
case s.BPO2 != nil:
return s.BPO2.Max
case s.BPO1 != nil:
return s.BPO1.Max
case s.Osaka != nil:
return s.Osaka.Max
case s.Prague != nil:
Expand All @@ -142,23 +178,11 @@ func LatestMaxBlobsPerBlock(cfg *params.ChainConfig) int {

// 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.IsOsaka(london, time) && s.Osaka != nil:
return s.Osaka.Target
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:
blobConfig := latestBlobConfig(cfg, time)
if blobConfig == nil {
return 0
}
return blobConfig.Target
}

// fakeExponential approximates factor * e ** (numerator / denominator) using
Expand All @@ -177,3 +201,9 @@ func fakeExponential(factor, numerator, denominator *big.Int) *big.Int {
}
return output.Div(output, denominator)
}

// calcBlobPrice calculates the blob price based for a block.
func calcBlobPrice(config *params.ChainConfig, header *types.Header) *big.Int {
blobBaseFee := CalcBlobFee(config, header)
return new(big.Int).Mul(blobBaseFee, big.NewInt(params.BlobTxBlobGasPerBlob))
}
53 changes: 53 additions & 0 deletions consensus/misc/eip4844/eip4844_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -127,3 +127,56 @@ func TestFakeExponential(t *testing.T) {
}
}
}

func TestCalcExcessBlobGasEIP7918(t *testing.T) {
// TODO: replace with a test config that has Osaka enabled.
c := *params.MainnetChainConfig
cfg := &c
cfg.OsakaTime = new(uint64)
pragueSchedule := *cfg.BlobScheduleConfig.Prague
cfg.BlobScheduleConfig.Osaka = &pragueSchedule

var (
targetBlobs = targetBlobsPerBlock(cfg, *cfg.CancunTime)
blobGasPerBlob = uint64(params.BlobTxBlobGasPerBlob)
blobGasTarget = uint64(targetBlobs) * blobGasPerBlob
)

makeHeader := func(
parentExcess uint64,
parentBaseFee uint64,
blobsUsed int,
) *types.Header {
blobGasUsed := uint64(blobsUsed) * blobGasPerBlob
return &types.Header{
BaseFee: big.NewInt(int64(parentBaseFee)),
ExcessBlobGas: &parentExcess,
BlobGasUsed: &blobGasUsed,
}
}

tests := []struct {
name string
header *types.Header
wantExcessGas uint64
}{
{
name: "BelowReservePrice",
header: makeHeader(0, 1_000_000_000, targetBlobs),
wantExcessGas: blobGasTarget * 3 / 9,
},
{
name: "AboveReservePrice",
header: makeHeader(0, 1, targetBlobs),
wantExcessGas: 0,
},
}

for _, tc := range tests {
got := CalcExcessBlobGas(cfg, tc.header, *cfg.CancunTime)
if got != tc.wantExcessGas {
t.Fatalf("%s: excess-blob-gas mismatch – have %d, want %d",
tc.name, got, tc.wantExcessGas)
}
}
}
4 changes: 4 additions & 0 deletions core/block_validator.go
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,10 @@ func (v *BlockValidator) ValidateBody(block *types.Block) error {
// Blob transactions may be present after the Cancun fork.
var blobs int
for i, tx := range block.Transactions() {
if v.config.IsOsaka(block.Number(), block.Time()) && tx.Gas() > params.MaxTxGas {
return fmt.Errorf("transaction exceeds maximum allowed gas limit (has %d gas)", tx.Gas())
}

// Count the number of blobs to validate against the header's blobGasUsed
blobs += len(tx.BlobHashes())

Expand Down
16 changes: 15 additions & 1 deletion core/txpool/legacypool/legacypool.go
Original file line number Diff line number Diff line change
Expand Up @@ -1255,6 +1255,20 @@ func (pool *LegacyPool) runReorg(done chan struct{}, reset *txpoolResetRequest,
}
pool.mu.Lock()
if reset != nil {
if reset.newHead != nil && reset.oldHead != nil {
if pool.chainconfig.IsOsaka(reset.newHead.Number, reset.newHead.Time) && !pool.chainconfig.IsOsaka(reset.oldHead.Number, reset.oldHead.Time) {
var removeHashes []common.Hash
pool.all.Range(func(hash common.Hash, tx *types.Transaction) bool {
if tx.Gas() > params.MaxTxGas {
removeHashes = append(removeHashes, hash)
}
return true
})
for _, hash := range removeHashes {
pool.all.Remove(hash)
}
}
}
// Reset from the old head to the new, rescheduling any reorged transactions
pool.reset(reset.oldHead, reset.newHead)

Expand All @@ -1279,7 +1293,7 @@ func (pool *LegacyPool) runReorg(done chan struct{}, reset *txpoolResetRequest,
// because of another transaction (e.g. higher gas price).
if reset != nil {
pool.demoteUnexecutables()
if reset.newHead != nil {
if reset.newHead != reset.oldHead {
if pool.chainconfig.IsLondon(new(big.Int).Add(reset.newHead.Number, big.NewInt(1))) {
pendingBaseFee := eip1559.CalcBaseFee(pool.chainconfig, reset.newHead)
pool.priced.SetBaseFee(pendingBaseFee)
Expand Down
15 changes: 15 additions & 0 deletions core/txpool/txpool.go
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,9 @@ type TxPool struct {
term chan struct{} // Termination channel to detect a closed pool

sync chan chan error // Testing / simulator channel to block until internal reset is done

headLock sync.RWMutex
head *types.Header // this reflects the state from the latest pool reset
}

// New creates a new transaction pool to gather, sort and filter inbound
Expand Down Expand Up @@ -103,6 +106,7 @@ func New(gasTip uint64, chain BlockChain, subpools []SubPool) (*TxPool, error) {
quit: make(chan chan error),
term: make(chan struct{}),
sync: make(chan chan error),
head: head,
}
reserver := NewReservationTracker()
for i, subpool := range subpools {
Expand Down Expand Up @@ -202,6 +206,9 @@ func (p *TxPool) loop(head *types.Header) {
}
select {
case resetDone <- newHead:
p.headLock.Lock()
p.head = newHead
p.headLock.Unlock()
case <-p.term:
}
}(oldHead, newHead)
Expand Down Expand Up @@ -256,6 +263,14 @@ func (p *TxPool) loop(head *types.Header) {
errc <- nil
}

// Head returns the header which corresponds to the most recent successful pool
// reset.
func (p *TxPool) Head() *types.Header {
p.headLock.RLock()
defer p.headLock.RUnlock()
return types.CopyHeader(p.head)
}

// SetGasTip updates the minimum gas tip required by the transaction pool for a
// new transaction, and drops all transactions below this threshold.
func (p *TxPool) SetGasTip(tip *big.Int) {
Expand Down
3 changes: 3 additions & 0 deletions core/txpool/validation.go
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,9 @@ func ValidateTransaction(tx *types.Transaction, head *types.Header, signer types
if rules.IsShanghai && tx.To() == nil && len(tx.Data()) > params.MaxInitCodeSize {
return fmt.Errorf("%w: code size %v, limit %v", core.ErrMaxInitCodeSizeExceeded, len(tx.Data()), params.MaxInitCodeSize)
}
if rules.IsOsaka && tx.Gas() > params.MaxTxGas {
return fmt.Errorf("transaction gas exceeded max allowed (%d): %d", params.MaxTxGas, tx.Gas())
}
// Transactions can't be negative. This may never happen using RLP decoded
// transactions but may occur for transactions created using the RPC.
if tx.Value().Sign() < 0 {
Expand Down
43 changes: 36 additions & 7 deletions miner/worker.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,8 @@ type environment struct {
sidecars []*types.BlobTxSidecar
blobs int

witness *stateless.Witness
witness *stateless.Witness
chainConfig *params.ChainConfig
}

const (
Expand Down Expand Up @@ -254,12 +255,13 @@ func (miner *Miner) makeEnv(parent *types.Header, header *types.Header, coinbase
}
// Note the passed coinbase may be different with header.Coinbase.
return &environment{
signer: types.MakeSigner(miner.chainConfig, header.Number, header.Time),
state: state,
coinbase: coinbase,
header: header,
witness: state.Witness(),
evm: vm.NewEVM(core.NewEVMBlockContext(header, miner.chain, &coinbase), state, miner.chainConfig, vm.Config{}),
signer: types.MakeSigner(miner.chainConfig, header.Number, header.Time),
state: state,
coinbase: coinbase,
header: header,
witness: state.Witness(),
evm: vm.NewEVM(core.NewEVMBlockContext(header, miner.chain, &coinbase), state, miner.chainConfig, vm.Config{}),
chainConfig: miner.chainConfig,
}, nil
}

Expand Down Expand Up @@ -443,6 +445,23 @@ func (miner *Miner) commitTransactions(env *environment, plainTxs, blobTxs *tran
return nil
}

// fiterTxsAboveGas removes any transactions from the set which exceed a gas threshold.
// If a transaction is removed, all higher-nonce transactions from the same account
// will also be filtered out.
func filterTxsAboveGas(txs map[common.Address][]*txpool.LazyTransaction, maxGas uint64) {
for sender, senderTxs := range txs {
for i, tx := range senderTxs {
if tx.Gas > maxGas {
if i == 0 {
delete(txs, sender)
} else {
txs[sender] = txs[sender][:i]
}
}
}
}
}

// fillTransactions retrieves the pending transactions from the txpool and fills them
// into the given sealing block. The transaction selection and ordering strategy can
// be customized with the plugin in the future.
Expand Down Expand Up @@ -472,6 +491,16 @@ func (miner *Miner) fillTransactions(interrupt *atomic.Int32, env *environment)
prioPlainTxs, normalPlainTxs := make(map[common.Address][]*txpool.LazyTransaction), pendingPlainTxs
prioBlobTxs, normalBlobTxs := make(map[common.Address][]*txpool.LazyTransaction), pendingBlobTxs

// if we have just entered the Osaka fork, it is possible due to the async
// nature of txpool resets that the state of the pool has not finished resetting
// to a post-fork block. If so, it may not have removed txs that exceed the
// eip-7825 transaction maximum gas limit.
poolHead := miner.txpool.Head()
if env.chainConfig.IsOsaka(env.header.Number, env.header.Time) && !env.chainConfig.IsOsaka(poolHead.Number, poolHead.Time) {
filterTxsAboveGas(prioPlainTxs, params.MaxTxGas)
filterTxsAboveGas(prioBlobTxs, params.MaxTxGas)
}

for _, account := range prio {
if txs := normalPlainTxs[account]; len(txs) > 0 {
delete(normalPlainTxs, account)
Expand Down
Loading