Merge branch 'develop' into fix-requests-hash-in-sdk

This commit is contained in:
colin 2025-06-26 18:10:20 +08:00 committed by GitHub
commit 47bc86cd96
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
37 changed files with 461 additions and 118 deletions

View file

@ -640,7 +640,7 @@ func (b *SimulatedBackend) callContract(ctx context.Context, call ethereum.CallM
vmEnv := vm.NewEVM(evmContext, txContext, stateDB, b.config, vm.Config{NoBaseFee: true})
gasPool := new(core.GasPool).AddGas(math.MaxUint64)
signer := types.MakeSigner(b.blockchain.Config(), head.Number, head.Time)
l1DataFee, err := fees.EstimateL1DataFeeForMessage(msg, head.BaseFee, b.blockchain.Config(), signer, stateDB, head.Number)
l1DataFee, err := fees.EstimateL1DataFeeForMessage(msg, head.BaseFee, b.blockchain.Config(), signer, stateDB, head.Number, head.Time)
if err != nil {
return nil, err
}

View file

@ -152,6 +152,10 @@ func (pre *Prestate) Apply(vmConfig vm.Config, chainConfig *params.ChainConfig,
if chainConfig.CurieBlock != nil && chainConfig.CurieBlock.Cmp(new(big.Int).SetUint64(pre.Env.Number)) == 0 {
misc.ApplyCurieHardFork(statedb)
}
// Apply Feynman hard fork
if chainConfig.IsFeynmanTransitionBlock(pre.Env.Timestamp, pre.Env.ParentTimestamp) {
misc.ApplyFeynmanHardFork(statedb)
}
// Apply EIP-2935
if pre.Env.BlockHashes != nil && chainConfig.IsFeynman(pre.Env.Timestamp) {
var (
@ -180,7 +184,7 @@ func (pre *Prestate) Apply(vmConfig vm.Config, chainConfig *params.ChainConfig,
snapshot := statedb.Snapshot()
evm := vm.NewEVM(vmContext, txContext, statedb, chainConfig, vmConfig)
l1DataFee, err := fees.CalculateL1DataFee(tx, statedb, chainConfig, new(big.Int).SetUint64(pre.Env.Number))
l1DataFee, err := fees.CalculateL1DataFee(tx, statedb, chainConfig, new(big.Int).SetUint64(pre.Env.Number), pre.Env.Timestamp)
if err != nil {
log.Info("rejected tx due to fees.CalculateL1DataFee", "index", i, "hash", tx.Hash(), "from", msg.From(), "error", err)
rejectedTxs = append(rejectedTxs, &rejectedTx{i, err.Error()})

View file

@ -176,6 +176,8 @@ var (
utils.CircuitCapacityCheckWorkersFlag,
utils.RollupVerifyEnabledFlag,
utils.ShadowforkPeersFlag,
utils.TxGossipBroadcastDisabledFlag,
utils.TxGossipReceivingDisabledFlag,
utils.DASyncEnabledFlag,
utils.DABlockNativeAPIEndpointFlag,
utils.DABlobScanAPIEndpointFlag,

View file

@ -248,6 +248,8 @@ var AppHelpFlagGroups = []flags.FlagGroup{
utils.DARecoveryProduceBlocksFlag,
utils.CircuitCapacityCheckEnabledFlag,
utils.CircuitCapacityCheckWorkersFlag,
utils.TxGossipBroadcastDisabledFlag,
utils.TxGossipReceivingDisabledFlag,
},
},
{

View file

@ -893,6 +893,16 @@ var (
Usage: "peer ids of shadow fork peers",
}
// Tx gossip settings
TxGossipBroadcastDisabledFlag = cli.BoolFlag{
Name: "txgossip.disablebroadcast",
Usage: "Disable gossip broadcast transactions to other peers",
}
TxGossipReceivingDisabledFlag = cli.BoolFlag{
Name: "txgossip.disablereceiving",
Usage: "Disable gossip receiving transactions from other peers",
}
// DA syncing settings
DASyncEnabledFlag = cli.BoolFlag{
Name: "da.sync",
@ -1790,6 +1800,14 @@ func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *ethconfig.Config) {
cfg.ShadowForkPeerIDs = ctx.GlobalStringSlice(ShadowforkPeersFlag.Name)
log.Info("Shadow fork peers", "ids", cfg.ShadowForkPeerIDs)
}
if ctx.GlobalIsSet(TxGossipBroadcastDisabledFlag.Name) {
cfg.TxGossipBroadcastDisabled = ctx.GlobalBool(TxGossipBroadcastDisabledFlag.Name)
log.Info("Transaction gossip broadcast disabled", "disabled", cfg.TxGossipBroadcastDisabled)
}
if ctx.GlobalIsSet(TxGossipReceivingDisabledFlag.Name) {
cfg.TxGossipReceivingDisabled = ctx.GlobalBool(TxGossipReceivingDisabledFlag.Name)
log.Info("Transaction gossip receiving disabled", "disabled", cfg.TxGossipReceivingDisabled)
}
// Cap the cache allowance and tune the garbage collector
mem, err := gopsutil.VirtualMemory()

22
consensus/misc/feynman.go Normal file
View file

@ -0,0 +1,22 @@
package misc
import (
"github.com/scroll-tech/go-ethereum/common"
"github.com/scroll-tech/go-ethereum/core/state"
"github.com/scroll-tech/go-ethereum/log"
"github.com/scroll-tech/go-ethereum/rollup/rcfg"
)
// ApplyFeynmanHardFork modifies the state database according to the Feynman hard-fork rules,
// updating the bytecode and storage of the L1GasPriceOracle contract.
func ApplyFeynmanHardFork(statedb *state.StateDB) {
log.Info("Applying Feynman hard fork")
// update contract byte code
statedb.SetCode(rcfg.L1GasPriceOracleAddress, rcfg.FeynmanL1GasPriceOracleBytecode)
// initialize new storage slots
statedb.SetState(rcfg.L1GasPriceOracleAddress, rcfg.IsFeynmanSlot, common.BytesToHash([]byte{1}))
statedb.SetState(rcfg.L1GasPriceOracleAddress, rcfg.PenaltyThresholdSlot, common.BigToHash(rcfg.InitialPenaltyThreshold))
statedb.SetState(rcfg.L1GasPriceOracleAddress, rcfg.PenaltyFactorSlot, common.BigToHash(rcfg.InitialPenaltyFactor))
}

View file

@ -249,6 +249,9 @@ func GenerateChain(config *params.ChainConfig, parent *types.Block, engine conse
if config.CurieBlock != nil && config.CurieBlock.Cmp(b.header.Number) == 0 {
misc.ApplyCurieHardFork(statedb)
}
if config.IsFeynmanTransitionBlock(b.Time(), parent.Time()) {
misc.ApplyFeynmanHardFork(statedb)
}
// Execute any user modifications to the block
if gen != nil {
gen(i, b)

View file

@ -71,7 +71,7 @@ func (p *statePrefetcher) Prefetch(block *types.Block, statedb *state.StateDB, c
}
statedb.SetTxContext(tx.Hash(), i)
l1DataFee, err := fees.CalculateL1DataFee(tx, statedb, p.config, block.Number())
l1DataFee, err := fees.CalculateL1DataFee(tx, statedb, p.config, block.Number(), block.Time())
if err != nil {
return
}

View file

@ -80,6 +80,7 @@ func (p *StateProcessor) Process(block *types.Block, statedb *state.StateDB, cfg
header = block.Header()
blockHash = block.Hash()
blockNumber = block.Number()
blockTime = block.Time()
allLogs []*types.Log
gp = new(GasPool).AddGas(block.GasLimit())
)
@ -91,6 +92,11 @@ func (p *StateProcessor) Process(block *types.Block, statedb *state.StateDB, cfg
if p.config.CurieBlock != nil && p.config.CurieBlock.Cmp(block.Number()) == 0 {
misc.ApplyCurieHardFork(statedb)
}
// Apply Feynman hard fork
parent := p.bc.GetHeaderByHash(block.ParentHash())
if p.config.IsFeynmanTransitionBlock(block.Time(), parent.Time) {
misc.ApplyFeynmanHardFork(statedb)
}
blockContext := NewEVMBlockContext(header, p.bc, p.config, nil)
vmenv := vm.NewEVM(blockContext, vm.TxContext{}, statedb, p.config, cfg)
processorBlockTransactionGauge.Update(int64(block.Transactions().Len()))
@ -105,7 +111,7 @@ func (p *StateProcessor) Process(block *types.Block, statedb *state.StateDB, cfg
return nil, nil, 0, fmt.Errorf("could not apply tx %d [%v]: %w", i, tx.Hash().Hex(), err)
}
statedb.SetTxContext(tx.Hash(), i)
receipt, err := applyTransaction(msg, p.config, p.bc, nil, gp, statedb, blockNumber, blockHash, tx, usedGas, vmenv)
receipt, err := applyTransaction(msg, p.config, p.bc, nil, gp, statedb, blockNumber, blockTime, blockHash, tx, usedGas, vmenv)
if err != nil {
return nil, nil, 0, fmt.Errorf("could not apply tx %d [%v]: %w", i, tx.Hash().Hex(), err)
}
@ -120,7 +126,7 @@ func (p *StateProcessor) Process(block *types.Block, statedb *state.StateDB, cfg
return receipts, allLogs, *usedGas, nil
}
func applyTransaction(msg types.Message, config *params.ChainConfig, bc ChainContext, author *common.Address, gp *GasPool, statedb *state.StateDB, blockNumber *big.Int, blockHash common.Hash, tx *types.Transaction, usedGas *uint64, evm *vm.EVM) (*types.Receipt, error) {
func applyTransaction(msg types.Message, config *params.ChainConfig, bc ChainContext, author *common.Address, gp *GasPool, statedb *state.StateDB, blockNumber *big.Int, blockTime uint64, blockHash common.Hash, tx *types.Transaction, usedGas *uint64, evm *vm.EVM) (*types.Receipt, error) {
defer func(t0 time.Time) {
applyTransactionTimer.Update(time.Since(t0))
}(time.Now())
@ -129,7 +135,7 @@ func applyTransaction(msg types.Message, config *params.ChainConfig, bc ChainCon
txContext := NewEVMTxContext(msg)
evm.Reset(txContext, statedb)
l1DataFee, err := fees.CalculateL1DataFee(tx, statedb, config, blockNumber)
l1DataFee, err := fees.CalculateL1DataFee(tx, statedb, config, blockNumber, blockTime)
if err != nil {
return nil, err
}
@ -201,7 +207,7 @@ func ApplyTransaction(config *params.ChainConfig, bc ChainContext, author *commo
// Create a new context to be used in the EVM environment
blockContext := NewEVMBlockContext(header, bc, config, author)
vmenv := vm.NewEVM(blockContext, vm.TxContext{}, statedb, config, cfg)
return applyTransaction(msg, config, bc, author, gp, statedb, header.Number, header.Hash(), tx, usedGas, vmenv)
return applyTransaction(msg, config, bc, author, gp, statedb, header.Number, header.Time, header.Hash(), tx, usedGas, vmenv)
}
// ProcessParentBlockHash stores the parent block hash in the history storage contract

View file

@ -282,7 +282,7 @@ func (l *txList) Overlaps(tx *types.Transaction) bool {
//
// If the new transaction is accepted into the list, the lists' cost and gas
// thresholds are also potentially updated.
func (l *txList) Add(tx *types.Transaction, state *state.StateDB, priceBump uint64, chainconfig *params.ChainConfig, blockNumber *big.Int) (bool, *types.Transaction) {
func (l *txList) Add(tx *types.Transaction, state *state.StateDB, priceBump uint64, chainconfig *params.ChainConfig, blockNumber *big.Int, blockTime uint64) (bool, *types.Transaction) {
// If there's an older better transaction, abort
old := l.txs.Get(tx.Nonce())
if old != nil {
@ -310,7 +310,7 @@ func (l *txList) Add(tx *types.Transaction, state *state.StateDB, priceBump uint
l1DataFee := big.NewInt(0)
if state != nil && chainconfig != nil {
var err error
l1DataFee, err = fees.CalculateL1DataFee(tx, state, chainconfig, blockNumber)
l1DataFee, err = fees.CalculateL1DataFee(tx, state, chainconfig, blockNumber, blockTime)
if err != nil {
log.Error("Failed to calculate L1 data fee", "err", err, "tx", tx)
return false, nil

View file

@ -38,7 +38,7 @@ func TestStrictTxListAdd(t *testing.T) {
// Insert the transactions in a random order
list := newTxList(true)
for _, v := range rand.Perm(len(txs)) {
list.Add(txs[v], nil, DefaultTxPoolConfig.PriceBump, nil, nil)
list.Add(txs[v], nil, DefaultTxPoolConfig.PriceBump, nil, nil, 0)
}
// Verify internal state
if len(list.txs.items) != len(txs) {
@ -65,7 +65,7 @@ func BenchmarkTxListAdd(b *testing.B) {
for i := 0; i < b.N; i++ {
list := newTxList(true)
for _, v := range rand.Perm(len(txs)) {
list.Add(txs[v], nil, DefaultTxPoolConfig.PriceBump, nil, nil)
list.Add(txs[v], nil, DefaultTxPoolConfig.PriceBump, nil, nil, 0)
list.Filter(priceLimit, DefaultTxPoolConfig.PriceBump)
}
}

View file

@ -301,6 +301,7 @@ type TxPool struct {
currentState *state.StateDB // Current state in the blockchain head
currentHead *big.Int // Current blockchain head
currentTime uint64 // Current blockchain head time
pendingNonces *txNoncer // Pending state tracking virtual nonces
currentMaxGas uint64 // Current gas limit for transaction caps
@ -838,7 +839,7 @@ func (pool *TxPool) validateTx(tx *types.Transaction, local bool) error {
// 2. If FeeVault is enabled, perform an additional check for L1 data fees.
if pool.chainconfig.Scroll.FeeVaultEnabled() {
// Get L1 data fee in current state
l1DataFee, err := fees.CalculateL1DataFee(tx, pool.currentState, pool.chainconfig, pool.currentHead)
l1DataFee, err := fees.CalculateL1DataFee(tx, pool.currentState, pool.chainconfig, pool.currentHead, pool.currentTime)
if err != nil {
return fmt.Errorf("failed to calculate L1 data fee, err: %w", err)
}
@ -947,7 +948,7 @@ func (pool *TxPool) add(tx *types.Transaction, local bool) (replaced bool, err e
from, _ := types.Sender(pool.signer, tx) // already validated
if list := pool.pending[from]; list != nil && list.Overlaps(tx) {
// Nonce already pending, check if required price bump is met
inserted, old := list.Add(tx, pool.currentState, pool.config.PriceBump, pool.chainconfig, pool.currentHead)
inserted, old := list.Add(tx, pool.currentState, pool.config.PriceBump, pool.chainconfig, pool.currentHead, pool.currentTime)
if !inserted {
log.Debug("Discarding underpriced pending transaction", "hash", hash.Hex(), "gasTipCap", tx.GasTipCap(), "gasFeeCap", tx.GasFeeCap())
pendingDiscardMeter.Mark(1)
@ -1001,7 +1002,7 @@ func (pool *TxPool) enqueueTx(hash common.Hash, tx *types.Transaction, local boo
pool.queue[from] = newTxList(false)
}
inserted, old := pool.queue[from].Add(tx, pool.currentState, pool.config.PriceBump, pool.chainconfig, pool.currentHead)
inserted, old := pool.queue[from].Add(tx, pool.currentState, pool.config.PriceBump, pool.chainconfig, pool.currentHead, pool.currentTime)
if !inserted {
// An older transaction was better, discard this
log.Debug("Discarding underpriced queued transaction", "hash", hash.Hex(), "gasTipCap", tx.GasTipCap(), "gasFeeCap", tx.GasFeeCap())
@ -1068,7 +1069,7 @@ func (pool *TxPool) promoteTx(addr common.Address, hash common.Hash, tx *types.T
return false
}
inserted, old := list.Add(tx, pool.currentState, pool.config.PriceBump, pool.chainconfig, pool.currentHead)
inserted, old := list.Add(tx, pool.currentState, pool.config.PriceBump, pool.chainconfig, pool.currentHead, pool.currentTime)
if !inserted {
// An older transaction was better, discard this
log.Debug("Discarding underpriced pending transaction", "hash", hash.Hex(), "gasTipCap", tx.GasTipCap(), "gasFeeCap", tx.GasFeeCap())
@ -1603,6 +1604,7 @@ func (pool *TxPool) reset(oldHead, newHead *types.Header) {
// Update current head
pool.currentHead = next
pool.currentTime = newHead.Time
}
// promoteExecutables moves transactions that have become processable from the
@ -1690,7 +1692,7 @@ func (pool *TxPool) executableTxFilter(costLimit *big.Int) func(tx *types.Transa
if pool.chainconfig.Scroll.FeeVaultEnabled() {
// recheck L1 data fee, as the oracle price may have changed
l1DataFee, err := fees.CalculateL1DataFee(tx, pool.currentState, pool.chainconfig, pool.currentHead)
l1DataFee, err := fees.CalculateL1DataFee(tx, pool.currentState, pool.chainconfig, pool.currentHead, pool.currentTime)
if err != nil {
log.Error("Failed to calculate L1 data fee", "err", err, "tx", tx)
return false

View file

@ -362,6 +362,11 @@ func generateWitness(blockchain *core.BlockChain, block *types.Block) (*stateles
// Collect storage locations that prover needs but sequencer might not touch necessarily
statedb.GetState(rcfg.L2MessageQueueAddress, rcfg.WithdrawTrieRootSlot)
// Note: scroll-revm detects the Feynman transition block using this storage slot,
// since it does not have access to the parent block timestamp. We need to make
// sure that this is always present in the execution witness.
statedb.GetState(rcfg.L1GasPriceOracleAddress, rcfg.IsFeynmanSlot)
receipts, _, usedGas, err := blockchain.Processor().Process(block, statedb, *blockchain.GetVMConfig())
if err != nil {
return nil, fmt.Errorf("failed to process block %d: %w", block.Number(), err)

View file

@ -273,16 +273,18 @@ func New(stack *node.Node, config *ethconfig.Config, l1Client l1.Client) (*Ether
checkpoint = params.TrustedCheckpoints[genesisHash]
}
if eth.handler, err = newHandler(&handlerConfig{
Database: chainDb,
Chain: eth.blockchain,
TxPool: eth.txPool,
Network: config.NetworkId,
Sync: config.SyncMode,
BloomCache: uint64(cacheLimit),
EventMux: eth.eventMux,
Checkpoint: checkpoint,
Whitelist: config.Whitelist,
ShadowForkPeerIDs: config.ShadowForkPeerIDs,
Database: chainDb,
Chain: eth.blockchain,
TxPool: eth.txPool,
Network: config.NetworkId,
Sync: config.SyncMode,
BloomCache: uint64(cacheLimit),
EventMux: eth.eventMux,
Checkpoint: checkpoint,
Whitelist: config.Whitelist,
ShadowForkPeerIDs: config.ShadowForkPeerIDs,
DisableTxBroadcast: config.TxGossipBroadcastDisabled,
DisableTxReceiving: config.TxGossipReceivingDisabled,
}); err != nil {
return nil, err
}

View file

@ -230,6 +230,9 @@ type Config struct {
// DA syncer options
DA da_syncer.Config
TxGossipBroadcastDisabled bool
TxGossipReceivingDisabled bool
}
// CreateConsensusEngine creates a consensus engine for the given chain configuration.

View file

@ -93,6 +93,9 @@ type handlerConfig struct {
Checkpoint *params.TrustedCheckpoint // Hard coded checkpoint for sync challenges
Whitelist map[uint64]common.Hash // Hard coded whitelist for sync challenged
ShadowForkPeerIDs []string // List of peer ids that take part in the shadow-fork
DisableTxBroadcast bool
DisableTxReceiving bool
}
type handler struct {
@ -131,7 +134,9 @@ type handler struct {
wg sync.WaitGroup
peerWG sync.WaitGroup
shadowForkPeerIDs []string
shadowForkPeerIDs []string
disableTxBroadcast bool
disableTxReceiving bool
}
// newHandler returns a handler for all Ethereum chain management protocol.
@ -141,16 +146,18 @@ func newHandler(config *handlerConfig) (*handler, error) {
config.EventMux = new(event.TypeMux) // Nicety initialization for tests
}
h := &handler{
networkID: config.Network,
forkFilter: forkid.NewFilter(config.Chain),
eventMux: config.EventMux,
database: config.Database,
txpool: config.TxPool,
chain: config.Chain,
peers: newPeerSet(),
whitelist: config.Whitelist,
quitSync: make(chan struct{}),
shadowForkPeerIDs: config.ShadowForkPeerIDs,
networkID: config.Network,
forkFilter: forkid.NewFilter(config.Chain),
eventMux: config.EventMux,
database: config.Database,
txpool: config.TxPool,
chain: config.Chain,
peers: newPeerSet(),
whitelist: config.Whitelist,
quitSync: make(chan struct{}),
shadowForkPeerIDs: config.ShadowForkPeerIDs,
disableTxBroadcast: config.DisableTxBroadcast,
disableTxReceiving: config.DisableTxReceiving,
}
if config.Sync == downloader.FullSync {
// The database seems empty as the current block is the genesis. Yet the fast
@ -415,10 +422,12 @@ func (h *handler) Start(maxPeers int) {
h.maxPeers = maxPeers
// broadcast transactions
h.wg.Add(1)
h.txsCh = make(chan core.NewTxsEvent, txChanSize)
h.txsSub = h.txpool.SubscribeNewTxsEvent(h.txsCh)
go h.txBroadcastLoop()
if !h.disableTxBroadcast {
h.wg.Add(1)
h.txsCh = make(chan core.NewTxsEvent, txChanSize)
h.txsSub = h.txpool.SubscribeNewTxsEvent(h.txsCh)
go h.txBroadcastLoop()
}
// broadcast mined blocks
h.wg.Add(1)

View file

@ -56,6 +56,9 @@ func (h *ethHandler) PeerInfo(id enode.ID) interface{} {
// AcceptTxs retrieves whether transaction processing is enabled on the node
// or if inbound transactions should simply be dropped.
func (h *ethHandler) AcceptTxs() bool {
if h.disableTxReceiving {
return false
}
return atomic.LoadUint32(&h.acceptTxs) == 1
}

View file

@ -198,7 +198,7 @@ func (eth *Ethereum) stateAtTransaction(block *types.Block, txIndex int, reexec
// Not yet the searched for transaction, execute on top of the current state
vmenv := vm.NewEVM(context, txContext, statedb, eth.blockchain.Config(), vm.Config{})
statedb.SetTxContext(tx.Hash(), idx)
l1DataFee, err := fees.CalculateL1DataFee(tx, statedb, eth.blockchain.Config(), block.Number())
l1DataFee, err := fees.CalculateL1DataFee(tx, statedb, eth.blockchain.Config(), block.Number(), block.Time())
if err != nil {
return nil, vm.BlockContext{}, nil, err
}

View file

@ -292,7 +292,7 @@ func (api *API) traceChain(ctx context.Context, start, end *types.Block, config
TxHash: tx.Hash(),
}
l1DataFee, err := fees.CalculateL1DataFee(tx, task.statedb, api.backend.ChainConfig(), task.block.Number())
l1DataFee, err := fees.CalculateL1DataFee(tx, task.statedb, api.backend.ChainConfig(), task.block.Number(), task.block.Time())
if err != nil {
// though it's not a "tracing error", we still need to put it here
task.results[i] = &txTraceResult{Error: err.Error()}
@ -557,7 +557,7 @@ func (api *API) IntermediateRoots(ctx context.Context, hash common.Hash, config
)
statedb.SetTxContext(tx.Hash(), i)
l1DataFee, err := fees.CalculateL1DataFee(tx, statedb, api.backend.ChainConfig(), block.Number())
l1DataFee, err := fees.CalculateL1DataFee(tx, statedb, api.backend.ChainConfig(), block.Number(), block.Time())
if err != nil {
log.Warn("Tracing intermediate roots did not complete due to fees.CalculateL1DataFee", "txindex", i, "txhash", tx.Hash(), "err", err)
return nil, err
@ -645,7 +645,7 @@ func (api *API) traceBlock(ctx context.Context, block *types.Block, config *Trac
TxHash: txs[task.index].Hash(),
}
l1DataFee, err := fees.CalculateL1DataFee(txs[task.index], task.statedb, api.backend.ChainConfig(), block.Number())
l1DataFee, err := fees.CalculateL1DataFee(txs[task.index], task.statedb, api.backend.ChainConfig(), block.Number(), block.Time())
if err != nil {
// though it's not a "tracing error", we still need to put it here
results[task.index] = &txTraceResult{
@ -679,7 +679,7 @@ func (api *API) traceBlock(ctx context.Context, block *types.Block, config *Trac
msg, _ := tx.AsMessage(signer, block.BaseFee())
statedb.SetTxContext(tx.Hash(), i)
vmenv := vm.NewEVM(blockCtx, core.NewEVMTxContext(msg), statedb, api.backend.ChainConfig(), vm.Config{})
l1DataFee, err := fees.CalculateL1DataFee(tx, statedb, api.backend.ChainConfig(), block.Number())
l1DataFee, err := fees.CalculateL1DataFee(tx, statedb, api.backend.ChainConfig(), block.Number(), block.Time())
if err != nil {
failed = err
break
@ -804,7 +804,7 @@ func (api *API) standardTraceBlockToFile(ctx context.Context, block *types.Block
// Execute the transaction and flush any traces to disk
vmenv := vm.NewEVM(vmctx, txContext, statedb, chainConfig, vmConf)
statedb.SetTxContext(tx.Hash(), i)
l1DataFee, err := fees.CalculateL1DataFee(tx, statedb, api.backend.ChainConfig(), block.Number())
l1DataFee, err := fees.CalculateL1DataFee(tx, statedb, api.backend.ChainConfig(), block.Number(), block.Time())
if err == nil {
_, err = core.ApplyMessage(vmenv, msg, new(core.GasPool).AddGas(msg.Gas()), l1DataFee)
}
@ -869,7 +869,7 @@ func (api *API) TraceTransaction(ctx context.Context, hash common.Hash, config *
TxIndex: int(index),
TxHash: hash,
}
l1DataFee, err := fees.CalculateL1DataFee(tx, statedb, api.backend.ChainConfig(), block.Number())
l1DataFee, err := fees.CalculateL1DataFee(tx, statedb, api.backend.ChainConfig(), block.Number(), block.Time())
if err != nil {
return nil, err
}
@ -929,7 +929,7 @@ func (api *API) TraceCall(ctx context.Context, args ethapi.TransactionArgs, bloc
}
signer := types.MakeSigner(api.backend.ChainConfig(), block.Number(), block.Time())
l1DataFee, err := fees.EstimateL1DataFeeForMessage(msg, block.BaseFee(), api.backend.ChainConfig(), signer, statedb, block.Number())
l1DataFee, err := fees.EstimateL1DataFeeForMessage(msg, block.BaseFee(), api.backend.ChainConfig(), signer, statedb, block.Number(), block.Time())
if err != nil {
return nil, err
}

View file

@ -173,7 +173,7 @@ func (b *testBackend) StateAtTransaction(ctx context.Context, block *types.Block
return msg, context, statedb, nil
}
vmenv := vm.NewEVM(context, txContext, statedb, b.chainConfig, vm.Config{})
l1DataFee, err := fees.CalculateL1DataFee(tx, statedb, b.chainConfig, block.Number())
l1DataFee, err := fees.CalculateL1DataFee(tx, statedb, b.chainConfig, block.Number(), block.Time())
if err != nil {
return nil, vm.BlockContext{}, nil, err
}

View file

@ -193,7 +193,7 @@ func testCallTracer(tracerName string, dirPath string, t *testing.T) {
if err != nil {
t.Fatalf("failed to prepare transaction for tracing: %v", err)
}
l1DataFee, err := fees.CalculateL1DataFee(tx, statedb, test.Genesis.Config, context.BlockNumber)
l1DataFee, err := fees.CalculateL1DataFee(tx, statedb, test.Genesis.Config, context.BlockNumber, context.Time.Uint64())
if err != nil {
t.Fatalf("failed to calculate l1DataFee: %v", err)
}
@ -308,7 +308,7 @@ func benchTracer(tracerName string, test *callTracerTest, b *testing.B) {
}
evm := vm.NewEVM(context, txContext, statedb, test.Genesis.Config, vm.Config{Debug: true, Tracer: tracer})
snap := statedb.Snapshot()
l1DataFee, err := fees.CalculateL1DataFee(tx, statedb, test.Genesis.Config, context.BlockNumber)
l1DataFee, err := fees.CalculateL1DataFee(tx, statedb, test.Genesis.Config, context.BlockNumber, context.Time.Uint64())
if err != nil {
b.Fatalf("failed to calculate l1DataFee: %v", err)
}
@ -381,7 +381,7 @@ func TestZeroValueToNotExitCall(t *testing.T) {
if err != nil {
t.Fatalf("failed to prepare transaction for tracing: %v", err)
}
l1DataFee, err := fees.CalculateL1DataFee(tx, statedb, params.MainnetChainConfig, context.BlockNumber)
l1DataFee, err := fees.CalculateL1DataFee(tx, statedb, params.MainnetChainConfig, context.BlockNumber, context.Time.Uint64())
if err != nil {
t.Fatalf("failed to calculate l1DataFee: %v", err)
}

View file

@ -112,7 +112,7 @@ func BenchmarkTransactionTrace(b *testing.B) {
for i := 0; i < b.N; i++ {
snap := statedb.Snapshot()
l1DataFee, err := fees.CalculateL1DataFee(tx, statedb, params.AllEthashProtocolChanges, context.BlockNumber)
l1DataFee, err := fees.CalculateL1DataFee(tx, statedb, params.AllEthashProtocolChanges, context.BlockNumber, context.Time.Uint64())
if err != nil {
b.Fatal(err)
}

2
go.mod
View file

@ -51,7 +51,7 @@ require (
github.com/prometheus/tsdb v0.7.1
github.com/rjeczalik/notify v0.9.1
github.com/rs/cors v1.7.0
github.com/scroll-tech/da-codec v0.1.3-0.20250313120912-344f2d5e33e1
github.com/scroll-tech/da-codec v0.1.3-0.20250626091118-58b899494da6
github.com/scroll-tech/zktrie v0.8.4
github.com/shirou/gopsutil v3.21.11+incompatible
github.com/sourcegraph/conc v0.3.0

4
go.sum
View file

@ -396,8 +396,8 @@ github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncj
github.com/rs/cors v1.7.0 h1:+88SsELBHx5r+hZ8TCkggzSstaWNbDvThkVK8H6f9ik=
github.com/rs/cors v1.7.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU=
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/scroll-tech/da-codec v0.1.3-0.20250313120912-344f2d5e33e1 h1:Dhd58LE1D+dnoxpgLVeQBMF9uweL/fhQfZHWtWSiOlE=
github.com/scroll-tech/da-codec v0.1.3-0.20250313120912-344f2d5e33e1/go.mod h1:yhTS9OVC0xQGhg7DN5iV5KZJvnSIlFWAxDdp+6jxQtY=
github.com/scroll-tech/da-codec v0.1.3-0.20250626091118-58b899494da6 h1:vb2XLvQwCf+F/ifP6P/lfeiQrHY6+Yb/E3R4KHXLqSE=
github.com/scroll-tech/da-codec v0.1.3-0.20250626091118-58b899494da6/go.mod h1:Z6kN5u2khPhiqHyk172kGB7o38bH/nj7Ilrb/46wZGg=
github.com/scroll-tech/zktrie v0.8.4 h1:UagmnZ4Z3ITCk+aUq9NQZJNAwnWl4gSxsLb2Nl7IgRE=
github.com/scroll-tech/zktrie v0.8.4/go.mod h1:XvNo7vAk8yxNyTjBDj5WIiFzYW4bx/gJ78+NK6Zn6Uk=
github.com/segmentio/kafka-go v0.1.0/go.mod h1:X6itGqS9L4jDletMsxZ7Dz+JFWxM6JHfPOCvTvk+EJo=

View file

@ -999,7 +999,7 @@ func EstimateL1MsgFee(ctx context.Context, b Backend, args TransactionArgs, bloc
}()
signer := types.MakeSigner(config, header.Number, header.Time)
return fees.EstimateL1DataFeeForMessage(msg, header.BaseFee, config, signer, evm.StateDB, header.Number)
return fees.EstimateL1DataFeeForMessage(msg, header.BaseFee, config, signer, evm.StateDB, header.Number, header.Time)
}
func DoCall(ctx context.Context, b Backend, args TransactionArgs, blockNrOrHash rpc.BlockNumberOrHash, overrides *StateOverride, timeout time.Duration, globalGasCap uint64) (*core.ExecutionResult, error) {
@ -1595,7 +1595,7 @@ func AccessList(ctx context.Context, b Backend, blockNrOrHash rpc.BlockNumberOrH
return nil, 0, nil, err
}
signer := types.MakeSigner(b.ChainConfig(), header.Number, header.Time)
l1DataFee, err := fees.EstimateL1DataFeeForMessage(msg, header.BaseFee, b.ChainConfig(), signer, statedb, header.Number)
l1DataFee, err := fees.EstimateL1DataFeeForMessage(msg, header.BaseFee, b.ChainConfig(), signer, statedb, header.Number, header.Time)
if err != nil {
return nil, 0, nil, fmt.Errorf("failed to apply transaction: %v err: %v", args.toTransaction().Hash(), err)
}

View file

@ -145,7 +145,7 @@ func odrContractCall(ctx context.Context, db ethdb.Database, config *params.Chai
//vmenv := core.NewEnv(statedb, config, bc, msg, header, vm.Config{})
gp := new(core.GasPool).AddGas(math.MaxUint64)
signer := types.MakeSigner(config, header.Number, header.Time)
l1DataFee, _ := fees.EstimateL1DataFeeForMessage(msg, header.BaseFee, config, signer, statedb, header.Number)
l1DataFee, _ := fees.EstimateL1DataFeeForMessage(msg, header.BaseFee, config, signer, statedb, header.Number, header.Time)
result, _ := core.ApplyMessage(vmenv, msg, gp, l1DataFee)
res = append(res, result.Return()...)
}
@ -159,7 +159,7 @@ func odrContractCall(ctx context.Context, db ethdb.Database, config *params.Chai
vmenv := vm.NewEVM(context, txContext, state, config, vm.Config{NoBaseFee: true})
gp := new(core.GasPool).AddGas(math.MaxUint64)
signer := types.MakeSigner(config, header.Number, header.Time)
l1DataFee, _ := fees.EstimateL1DataFeeForMessage(msg, header.BaseFee, config, signer, state, header.Number)
l1DataFee, _ := fees.EstimateL1DataFeeForMessage(msg, header.BaseFee, config, signer, state, header.Number, header.Time)
result, _ := core.ApplyMessage(vmenv, msg, gp, l1DataFee)
if state.Error() == nil {
res = append(res, result.Return()...)

View file

@ -65,7 +65,7 @@ func (leth *LightEthereum) stateAtTransaction(ctx context.Context, block *types.
}
// Not yet the searched for transaction, execute on top of the current state
vmenv := vm.NewEVM(context, txContext, statedb, leth.blockchain.Config(), vm.Config{})
l1DataFee, err := fees.CalculateL1DataFee(tx, statedb, leth.blockchain.Config(), block.Number())
l1DataFee, err := fees.CalculateL1DataFee(tx, statedb, leth.blockchain.Config(), block.Number(), block.Time())
if err != nil {
return nil, vm.BlockContext{}, nil, fmt.Errorf("transaction %#x failed: %v", tx.Hash(), err)
}

View file

@ -201,7 +201,7 @@ func odrContractCall(ctx context.Context, db ethdb.Database, bc *core.BlockChain
vmenv := vm.NewEVM(context, txContext, st, config, vm.Config{NoBaseFee: true})
gp := new(core.GasPool).AddGas(math.MaxUint64)
signer := types.MakeSigner(config, header.Number, header.Time)
l1DataFee, _ := fees.EstimateL1DataFeeForMessage(msg, header.BaseFee, config, signer, st, header.Number)
l1DataFee, _ := fees.EstimateL1DataFeeForMessage(msg, header.BaseFee, config, signer, st, header.Number, header.Time)
result, _ := core.ApplyMessage(vmenv, msg, gp, l1DataFee)
res = append(res, result.Return()...)
if st.Error() != nil {

View file

@ -74,6 +74,7 @@ type TxPool struct {
shanghai bool // Fork indicator whether we are in the shanghai stage.
currentHead *big.Int // Current blockchain head
currentTime uint64 // Current blockchain head time
}
// TxRelayBackend provides an interface to the mechanism that forwards transacions
@ -326,6 +327,7 @@ func (pool *TxPool) setNewHead(head *types.Header) {
pool.shanghai = pool.config.IsShanghai(next)
pool.currentHead = next
pool.currentTime = head.Time
}
// Stop stops the light transaction pool
@ -408,7 +410,7 @@ func (pool *TxPool) validateTx(ctx context.Context, tx *types.Transaction) error
// 2. If FeeVault is enabled, perform an additional check for L1 data fees.
if pool.config.Scroll.FeeVaultEnabled() {
// Get L1 data fee in current state
l1DataFee, err := fees.CalculateL1DataFee(tx, currentState, pool.config, pool.currentHead)
l1DataFee, err := fees.CalculateL1DataFee(tx, currentState, pool.config, pool.currentHead, pool.currentTime)
if err != nil {
return fmt.Errorf("failed to calculate L1 data fee, err: %w", err)
}

View file

@ -464,8 +464,7 @@ func (w *worker) collectPendingL1Messages(startIndex uint64) []types.L1MessageTx
}
// newWork
func (w *worker) newWork(now time.Time, parentHash common.Hash, reorging bool, reorgReason error) error {
parent := w.chain.GetBlockByHash(parentHash)
func (w *worker) newWork(now time.Time, parent *types.Block, reorging bool, reorgReason error) error {
header := &types.Header{
ParentHash: parent.Hash(),
Number: new(big.Int).Add(parent.Number(), common.Big1),
@ -586,13 +585,14 @@ func (w *worker) newWork(now time.Time, parentHash common.Hash, reorging bool, r
}
// tryCommitNewWork
func (w *worker) tryCommitNewWork(now time.Time, parent common.Hash, reorging bool, reorgReason error) (common.Hash, error) {
func (w *worker) tryCommitNewWork(now time.Time, parentHash common.Hash, reorging bool, reorgReason error) (common.Hash, error) {
parent := w.chain.GetBlockByHash(parentHash)
err := w.newWork(now, parent, reorging, reorgReason)
if err != nil {
return common.Hash{}, fmt.Errorf("failed creating new work: %w", err)
}
shouldCommit, err := w.handleForks()
shouldCommit, err := w.handleForks(parent)
if err != nil {
return common.Hash{}, fmt.Errorf("failed handling forks: %w", err)
}
@ -626,17 +626,16 @@ func (w *worker) tryCommitNewWork(now time.Time, parent common.Hash, reorging bo
}
// handleForks
func (w *worker) handleForks() (bool, error) {
func (w *worker) handleForks(parent *types.Block) (bool, error) {
// Apply Curie predeployed contract update
if w.chainConfig.CurieBlock != nil && w.chainConfig.CurieBlock.Cmp(w.current.header.Number) == 0 {
misc.ApplyCurieHardFork(w.current.state)
return true, nil
}
// Stop/start miner at Euclid fork boundary on zktrie/mpt nodes
if w.chainConfig.IsEuclid(w.current.header.Time) {
parent := w.chain.GetBlockByHash(w.current.header.ParentHash)
return parent != nil && !w.chainConfig.IsEuclid(parent.Time()), nil
// Apply Feynman hard fork
if w.chainConfig.IsFeynmanTransitionBlock(w.current.header.Time, parent.Time()) {
misc.ApplyFeynmanHardFork(w.current.state)
}
// Apply EIP-2935
@ -646,6 +645,12 @@ func (w *worker) handleForks() (bool, error) {
core.ProcessParentBlockHash(w.current.header.ParentHash, vmenv, w.current.state)
}
// Stop/start miner at Euclid fork boundary on zktrie/mpt nodes
if w.chainConfig.IsEuclid(w.current.header.Time) {
parent := w.chain.GetBlockByHash(w.current.header.ParentHash)
return parent != nil && !w.chainConfig.IsEuclid(parent.Time()), nil
}
return false, nil
}

View file

@ -1002,6 +1002,11 @@ func (c *ChainConfig) IsFeynman(now uint64) bool {
return isForkedTime(now, c.FeynmanTime)
}
// IsFeynmanTransitionBlock returns whether the given block timestamp corresponds to the first Feynman block.
func (c *ChainConfig) IsFeynmanTransitionBlock(blockTimestamp uint64, parentTimestamp uint64) bool {
return isForkedTime(blockTimestamp, c.FeynmanTime) && !isForkedTime(parentTimestamp, c.FeynmanTime)
}
// IsTerminalPoWBlock returns whether the given block is the last block of PoW stage.
func (c *ChainConfig) IsTerminalPoWBlock(parentTotalDiff *big.Int, totalDiff *big.Int) bool {
if c.TerminalTotalDifficulty == nil {

View file

@ -24,7 +24,7 @@ import (
const (
VersionMajor = 5 // Major version component of the current release
VersionMinor = 8 // Minor version component of the current release
VersionPatch = 56 // Patch version component of the current release
VersionPatch = 59 // Patch version component of the current release
VersionMeta = "mainnet" // Version metadata to append to the version string
)

View file

@ -2,14 +2,17 @@ package fees
import (
"bytes"
"fmt"
"math"
"math/big"
"github.com/holiman/uint256"
"github.com/scroll-tech/da-codec/encoding"
"github.com/scroll-tech/go-ethereum/common"
"github.com/scroll-tech/go-ethereum/core/types"
"github.com/scroll-tech/go-ethereum/crypto"
"github.com/scroll-tech/go-ethereum/log"
"github.com/scroll-tech/go-ethereum/params"
"github.com/scroll-tech/go-ethereum/rollup/rcfg"
)
@ -54,15 +57,17 @@ type StateDB interface {
}
type gpoState struct {
l1BaseFee *big.Int
overhead *big.Int
scalar *big.Int
l1BlobBaseFee *big.Int
commitScalar *big.Int
blobScalar *big.Int
l1BaseFee *big.Int
overhead *big.Int
scalar *big.Int
l1BlobBaseFee *big.Int
commitScalar *big.Int
blobScalar *big.Int
penaltyThreshold *big.Int
penaltyFactor *big.Int
}
func EstimateL1DataFeeForMessage(msg Message, baseFee *big.Int, config *params.ChainConfig, signer types.Signer, state StateDB, blockNumber *big.Int) (*big.Int, error) {
func EstimateL1DataFeeForMessage(msg Message, baseFee *big.Int, config *params.ChainConfig, signer types.Signer, state StateDB, blockNumber *big.Int, blockTime uint64) (*big.Int, error) {
if msg.IsL1MessageTx() {
return big.NewInt(0), nil
}
@ -74,22 +79,7 @@ func EstimateL1DataFeeForMessage(msg Message, baseFee *big.Int, config *params.C
return nil, err
}
raw, err := tx.MarshalBinary()
if err != nil {
return nil, err
}
gpoState := readGPOStorageSlots(rcfg.L1GasPriceOracleAddress, state)
var l1DataFee *big.Int
if !config.IsCurie(blockNumber) {
l1DataFee = calculateEncodedL1DataFee(raw, gpoState.overhead, gpoState.l1BaseFee, gpoState.scalar)
} else {
l1DataFee = calculateEncodedL1DataFeeCurie(raw, gpoState.l1BaseFee, gpoState.l1BlobBaseFee, gpoState.commitScalar, gpoState.blobScalar)
}
return l1DataFee, nil
return CalculateL1DataFee(tx, state, config, blockNumber, blockTime)
}
// asUnsignedTx turns a Message into a types.Transaction
@ -173,9 +163,58 @@ func readGPOStorageSlots(addr common.Address, state StateDB) gpoState {
gpoState.l1BlobBaseFee = state.GetState(addr, rcfg.L1BlobBaseFeeSlot).Big()
gpoState.commitScalar = state.GetState(addr, rcfg.CommitScalarSlot).Big()
gpoState.blobScalar = state.GetState(addr, rcfg.BlobScalarSlot).Big()
gpoState.penaltyThreshold = state.GetState(addr, rcfg.PenaltyThresholdSlot).Big()
gpoState.penaltyFactor = state.GetState(addr, rcfg.PenaltyFactorSlot).Big()
return gpoState
}
// estimateTxCompressionRatio estimates the compression ratio for transaction data using da-codec
// compression_ratio(tx) = size(tx) * PRECISION / size(zstd(tx))
func estimateTxCompressionRatio(data []byte, blockNumber uint64, blockTime uint64, config *params.ChainConfig) (*big.Int, error) {
if len(data) == 0 {
return nil, fmt.Errorf("raw data is empty")
}
// Compress data using da-codec
compressed, err := encoding.CompressScrollBatchBytes(data, blockNumber, blockTime, config)
if err != nil {
log.Error("Batch compression failed, using 1.0 compression ratio", "error", err, "data size", len(data), "data", common.Bytes2Hex(data))
return nil, fmt.Errorf("batch compression failed: %w", err)
}
if len(compressed) == 0 {
log.Error("Compressed data is empty, using 1.0 compression ratio", "data size", len(data), "data", common.Bytes2Hex(data))
return nil, fmt.Errorf("compressed data is empty")
}
// compression_ratio = size(tx) * PRECISION / size(zstd(tx))
originalSize := new(big.Int).SetUint64(uint64(len(data)))
compressedSize := new(big.Int).SetUint64(uint64(len(compressed)))
// Make sure compression ratio >= 1 by checking if compressed data is bigger or equal to original data
// This behavior is consistent with DA Batch compression in codecv7 and later versions
if len(compressed) >= len(data) {
log.Debug("Compressed data is bigger or equal to the original data, using 1.0 compression ratio", "original size", len(data), "compressed size", len(compressed))
return rcfg.Precision, nil
}
ratio := new(big.Int).Mul(originalSize, rcfg.Precision)
ratio.Div(ratio, compressedSize)
return ratio, nil
}
// calculatePenalty computes the penalty multiplier based on compression ratio
// penalty(tx) = compression_ratio(tx) >= penalty_threshold ? 1 * PRECISION : penalty_factor
func calculatePenalty(compressionRatio, penaltyThreshold, penaltyFactor *big.Int) *big.Int {
if compressionRatio.Cmp(penaltyThreshold) >= 0 {
// No penalty
return rcfg.Precision
}
// Apply penalty
return penaltyFactor
}
// calculateEncodedL1DataFee computes the L1 fee for an RLP-encoded tx
func calculateEncodedL1DataFee(data []byte, overhead, l1BaseFee *big.Int, scalar *big.Int) *big.Int {
l1GasUsed := calculateL1GasUsed(data, overhead)
@ -200,6 +239,51 @@ func calculateEncodedL1DataFeeCurie(data []byte, l1BaseFee *big.Int, l1BlobBaseF
return l1DataFee
}
// calculateEncodedL1DataFeeFeynman computes the L1 fee for an RLP-encoded tx, post Feynman
//
// Post Feynman formula:
// rollup_fee(tx) = (execScalar * l1BaseFee + blobScalar * l1BlobBaseFee) * size(tx) * penalty(tx) / PRECISION / PRECISION
//
// Where:
// penalty(tx) = compression_ratio(tx) >= penalty_threshold ? 1 * PRECISION : penalty_factor
//
// compression_ratio(tx) = size(tx) * PRECISION / size(zstd(tx))
// exec_scalar = compression_scalar * (commit_scalar + verification_scalar)
// blob_scalar = compression_scalar * blob_scalar
func calculateEncodedL1DataFeeFeynman(
data []byte,
l1BaseFee *big.Int,
l1BlobBaseFee *big.Int,
execScalar *big.Int,
blobScalar *big.Int,
penaltyThreshold *big.Int,
penaltyFactor *big.Int,
compressionRatio *big.Int,
) *big.Int {
// Calculate penalty multiplier
penalty := calculatePenalty(compressionRatio, penaltyThreshold, penaltyFactor)
// Transaction size (RLP-encoded)
txSize := big.NewInt(int64(len(data)))
// Compute gas components
execGas := new(big.Int).Mul(execScalar, l1BaseFee)
blobGas := new(big.Int).Mul(blobScalar, l1BlobBaseFee)
// fee per byte = execGas + blobGas
feePerByte := new(big.Int).Add(execGas, blobGas)
// l1DataFee = feePerByte * txSize * penalty
l1DataFee := new(big.Int).Mul(feePerByte, txSize)
l1DataFee.Mul(l1DataFee, penalty)
// Divide by rcfg.Precision (once for scalars, once for penalty)
l1DataFee.Div(l1DataFee, rcfg.Precision) // account for scalars
l1DataFee.Div(l1DataFee, rcfg.Precision) // accounts for penalty
return l1DataFee
}
// calculateL1GasUsed computes the L1 gas used based on the calldata and
// constant sized overhead. The overhead can be decreased as the cost of the
// batch submission goes down via contract optimizations. This will not overflow
@ -233,7 +317,7 @@ func mulAndScale(x *big.Int, y *big.Int, precision *big.Int) *big.Int {
return new(big.Int).Quo(z, precision)
}
func CalculateL1DataFee(tx *types.Transaction, state StateDB, config *params.ChainConfig, blockNumber *big.Int) (*big.Int, error) {
func CalculateL1DataFee(tx *types.Transaction, state StateDB, config *params.ChainConfig, blockNumber *big.Int, blockTime uint64) (*big.Int, error) {
if tx.IsL1MessageTx() {
return big.NewInt(0), nil
}
@ -249,8 +333,26 @@ func CalculateL1DataFee(tx *types.Transaction, state StateDB, config *params.Cha
if !config.IsCurie(blockNumber) {
l1DataFee = calculateEncodedL1DataFee(raw, gpoState.overhead, gpoState.l1BaseFee, gpoState.scalar)
} else {
} else if !config.IsFeynman(blockTime) {
l1DataFee = calculateEncodedL1DataFeeCurie(raw, gpoState.l1BaseFee, gpoState.l1BlobBaseFee, gpoState.commitScalar, gpoState.blobScalar)
} else {
// Calculate compression ratio for Feynman
compressionRatio, err := estimateTxCompressionRatio(raw, blockNumber.Uint64(), blockTime, config)
if err != nil {
return nil, fmt.Errorf("failed to estimate compression ratio: tx hash=%s: %w", tx.Hash().Hex(), err)
}
// The contract slot for commitScalar is changed to execScalar in Feynman
l1DataFee = calculateEncodedL1DataFeeFeynman(
raw,
gpoState.l1BaseFee,
gpoState.l1BlobBaseFee,
gpoState.commitScalar, // now represents execScalar
gpoState.blobScalar,
gpoState.penaltyThreshold,
gpoState.penaltyFactor,
compressionRatio,
)
}
// ensure l1DataFee fits into uint64 for circuit compatibility

View file

@ -5,6 +5,8 @@ import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/scroll-tech/go-ethereum/params"
)
func TestL1DataFeeBeforeCurie(t *testing.T) {
@ -31,3 +33,109 @@ func TestL1DataFeeAfterCurie(t *testing.T) {
actual := calculateEncodedL1DataFeeCurie(data, l1BaseFee, l1BlobBaseFee, commitScalar, blobScalar)
assert.Equal(t, expected, actual)
}
func TestL1DataFeeFeynman(t *testing.T) {
l1BaseFee := new(big.Int).SetInt64(1_000_000_000)
l1BlobBaseFee := new(big.Int).SetInt64(1_000_000_000)
execScalar := new(big.Int).SetInt64(10)
blobScalar := new(big.Int).SetInt64(20)
penaltyThreshold := new(big.Int).SetInt64(6_000_000_000) // 6 * PRECISION
penaltyFactor := new(big.Int).SetInt64(2_000_000_000) // 2 * PRECISION (200% penalty)
// Test case 1: No penalty (compression ratio >= threshold)
t.Run("no penalty case", func(t *testing.T) {
data := make([]byte, 100) // txSize = 100
compressionRatio := new(big.Int).SetInt64(6_000_000_000) // exactly at threshold
// Since compression ratio >= penaltyThreshold, penalty = 1 * PRECISION
// feePerByte = execScalar * l1BaseFee + blobScalar * l1BlobBaseFee = 10 * 1_000_000_000 + 20 * 1_000_000_000 = 30_000_000_000
// l1DataFee = feePerByte * txSize * penalty / PRECISION / PRECISION
// = 30_000_000_000 * 100 * 1_000_000_000 / 1_000_000_000 / 1_000_000_000 = 3000
expected := new(big.Int).SetInt64(3000)
actual := calculateEncodedL1DataFeeFeynman(
data,
l1BaseFee,
l1BlobBaseFee,
execScalar,
blobScalar,
penaltyThreshold,
penaltyFactor,
compressionRatio,
)
assert.Equal(t, expected, actual)
})
// Test case 2: With penalty (compression ratio < threshold)
t.Run("with penalty case", func(t *testing.T) {
data := make([]byte, 100) // txSize = 100
compressionRatio := new(big.Int).SetInt64(5_000_000_000) // below threshold
// Since compression ratio < penaltyThreshold, penalty = penaltyFactor
// feePerByte = execScalar * l1BaseFee + blobScalar * l1BlobBaseFee = 30_000_000_000
// l1DataFee = feePerByte * txSize * penaltyFactor / PRECISION / PRECISION
// = 30_000_000_000 * 100 * 2_000_000_000 / 1_000_000_000 / 1_000_000_000 = 6000
expected := new(big.Int).SetInt64(6000)
actual := calculateEncodedL1DataFeeFeynman(
data,
l1BaseFee,
l1BlobBaseFee,
execScalar,
blobScalar,
penaltyThreshold,
penaltyFactor,
compressionRatio,
)
assert.Equal(t, expected, actual)
})
}
func TestEstimateTxCompressionRatio(t *testing.T) {
// Mock config that would select a specific codec version
// Note: You'll need to adjust this based on your actual params.ChainConfig structure
t.Run("empty data", func(t *testing.T) {
data := []byte{}
// Should return 1.0 ratio (PRECISION)
ratio, err := estimateTxCompressionRatio(data, 1000000, 1700000000, params.TestChainConfig)
assert.Error(t, err, "raw data is empty")
assert.Nil(t, ratio)
// The exact value depends on rcfg.Precision, but should be the "1.0" equivalent
})
t.Run("non-empty data", func(t *testing.T) {
// Create some compressible data
data := make([]byte, 1000)
for i := range data {
data[i] = byte(i % 10) // Create patterns for better compression
}
ratio, err := estimateTxCompressionRatio(data, 1000000, 1700000000, params.TestChainConfig)
assert.NoError(t, err)
assert.NotNil(t, ratio)
// Should return a ratio > 1.0 (since compressed size < original size)
})
}
func TestCalculatePenalty(t *testing.T) {
precision := new(big.Int).SetInt64(1_000_000_000) // PRECISION
penaltyThreshold := new(big.Int).SetInt64(6_000_000_000) // 6 * PRECISION
penaltyFactor := new(big.Int).SetInt64(2_000_000_000) // 2 * PRECISION
t.Run("no penalty when ratio >= threshold", func(t *testing.T) {
compressionRatio := new(big.Int).SetInt64(6_000_000_000) // exactly at threshold
penalty := calculatePenalty(compressionRatio, penaltyThreshold, penaltyFactor)
assert.Equal(t, precision, penalty)
})
t.Run("penalty when ratio < threshold", func(t *testing.T) {
compressionRatio := new(big.Int).SetInt64(5_000_000_000) // below threshold
penalty := calculatePenalty(compressionRatio, penaltyThreshold, penaltyFactor)
assert.Equal(t, penaltyFactor, penalty)
})
}

File diff suppressed because one or more lines are too long

View file

@ -239,7 +239,7 @@ func (env *TraceEnv) GetBlockTrace(block *types.Block) (*types.BlockTrace, error
msg, _ := tx.AsMessage(env.signer, block.BaseFee())
env.state.SetTxContext(tx.Hash(), i)
vmenv := vm.NewEVM(env.blockCtx, core.NewEVMTxContext(msg), env.state, env.chainConfig, vm.Config{})
l1DataFee, err := fees.CalculateL1DataFee(tx, env.state, env.chainConfig, block.Number())
l1DataFee, err := fees.CalculateL1DataFee(tx, env.state, env.chainConfig, block.Number(), block.Time())
if err != nil {
failed = err
break
@ -342,7 +342,7 @@ func (env *TraceEnv) getTxResult(state *state.StateDB, index int, block *types.B
state.SetTxContext(txctx.TxHash, txctx.TxIndex)
// Computes the new state by applying the given message.
l1DataFee, err := fees.CalculateL1DataFee(tx, state, env.chainConfig, block.Number())
l1DataFee, err := fees.CalculateL1DataFee(tx, state, env.chainConfig, block.Number(), block.Time())
if err != nil {
return err
}

View file

@ -251,7 +251,7 @@ func (t *StateTest) RunNoVerify(subtest StateSubtest, vmconfig vm.Config, snapsh
gaspool := new(core.GasPool)
gaspool.AddGas(block.GasLimit())
l1DataFee, err := fees.CalculateL1DataFee(&ttx, statedb, config, block.Number())
l1DataFee, err := fees.CalculateL1DataFee(&ttx, statedb, config, block.Number(), block.Time())
if err != nil {
return nil, nil, common.Hash{}, err
}