feat: l1datafee (#611)

* update params/config.go

* update core/state_transition.go

update core/state_transition.go

* update core/state_prefetcher.go

* update core/state_processor.go

* update core/types/gen_receipt_json.go

* update core/types/receipt.go

* update internal/ethapi/api.go

* update Message type and interface

* update accounts/abi/bind/backends/simulated.go

* update eth/tracers/api.go

* eth/tracers/logger/logger.go WIP

* update eth/state_accessor.go

update eth/state_accessor.go

* update les/state_accessor.go

* update cmd/evm/internal/t8ntool/execution.go

* update core/rawdb/accessors_chain.go

* update eth/api_backend.go

* update les/api_backend.go

* update `ValidateTransactionWithState`

* update light/txpool.go

* refactor `ErrInsufficientFundsWithL1DataFee`

* clean up `rollup/fees/rollup_fee.go`

* update tests/state_test_util.go

* fix tests
This commit is contained in:
HAOYUatHZ 2024-01-09 16:58:02 +08:00 committed by GitHub
parent adfec9a0a3
commit 07d5fddde8
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
27 changed files with 362 additions and 108 deletions

View file

@ -42,6 +42,7 @@ import (
"github.com/ethereum/go-ethereum/event" "github.com/ethereum/go-ethereum/event"
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/rollup/fees"
"github.com/ethereum/go-ethereum/rpc" "github.com/ethereum/go-ethereum/rpc"
) )
@ -704,8 +705,13 @@ func (b *SimulatedBackend) callContract(ctx context.Context, call ethereum.CallM
evmContext := core.NewEVMBlockContext(header, b.blockchain, nil) evmContext := core.NewEVMBlockContext(header, b.blockchain, nil)
vmEnv := vm.NewEVM(evmContext, txContext, stateDB, b.config, vm.Config{NoBaseFee: true}) vmEnv := vm.NewEVM(evmContext, txContext, stateDB, b.config, vm.Config{NoBaseFee: true})
gasPool := new(core.GasPool).AddGas(math.MaxUint64) gasPool := new(core.GasPool).AddGas(math.MaxUint64)
signer := types.MakeSigner(b.blockchain.Config(), header.Number, header.Time)
l1DataFee, err := fees.EstimateL1DataFeeForMessage(msg, header.BaseFee, b.blockchain.Config().ChainID, signer, stateDB)
if err != nil {
return nil, err
}
return core.ApplyMessage(vmEnv, msg, gasPool) return core.ApplyMessage(vmEnv, msg, gasPool, l1DataFee)
} }
// SendTransaction updates the pending block to include the given transaction. // SendTransaction updates the pending block to include the given transaction.

View file

@ -35,6 +35,7 @@ import (
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/rlp" "github.com/ethereum/go-ethereum/rlp"
"github.com/ethereum/go-ethereum/rollup/fees"
"github.com/ethereum/go-ethereum/trie" "github.com/ethereum/go-ethereum/trie"
"golang.org/x/crypto/sha3" "golang.org/x/crypto/sha3"
) )
@ -220,8 +221,16 @@ func (pre *Prestate) Apply(vmConfig vm.Config, chainConfig *params.ChainConfig,
) )
evm := vm.NewEVM(vmContext, txContext, statedb, chainConfig, vmConfig) evm := vm.NewEVM(vmContext, txContext, statedb, chainConfig, vmConfig)
l1DataFee, err := fees.CalculateL1DataFee(tx, statedb)
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()})
gaspool.SetGas(prevGas)
continue
}
// (ret []byte, usedGas uint64, failed bool, err error) // (ret []byte, usedGas uint64, failed bool, err error)
msgResult, err := core.ApplyMessage(evm, msg, gaspool) msgResult, err := core.ApplyMessage(evm, msg, gaspool, l1DataFee)
if err != nil { if err != nil {
statedb.RevertToSnapshot(snapshot) statedb.RevertToSnapshot(snapshot)
log.Info("rejected tx", "index", i, "hash", tx.Hash(), "from", msg.From, "error", err) log.Info("rejected tx", "index", i, "hash", tx.Hash(), "from", msg.From, "error", err)

View file

@ -74,6 +74,10 @@ var (
// is higher than the balance of the user's account. // is higher than the balance of the user's account.
ErrInsufficientFunds = errors.New("insufficient funds for gas * price + value") ErrInsufficientFunds = errors.New("insufficient funds for gas * price + value")
// ErrInsufficientFundsWithL1DataFee is returned if (the total cost of executing a transaction + L1DataFee)
// is higher than the balance of the user's account.
ErrInsufficientFundsWithL1DataFee = errors.New("insufficient funds for l1fee + gas * price + value")
// ErrGasUintOverflow is returned when calculating gas usage. // ErrGasUintOverflow is returned when calculating gas usage.
ErrGasUintOverflow = errors.New("gas uint64 overflow") ErrGasUintOverflow = errors.New("gas uint64 overflow")

View file

@ -688,6 +688,7 @@ type storedReceiptRLP struct {
PostStateOrStatus []byte PostStateOrStatus []byte
CumulativeGasUsed uint64 CumulativeGasUsed uint64
Logs []*types.Log Logs []*types.Log
L1Fee *big.Int
} }
// ReceiptLogs is a barebone version of ReceiptForStorage which only keeps // ReceiptLogs is a barebone version of ReceiptForStorage which only keeps

View file

@ -17,6 +17,7 @@
package core package core
import ( import (
"math/big"
"sync/atomic" "sync/atomic"
"github.com/ethereum/go-ethereum/consensus" "github.com/ethereum/go-ethereum/consensus"
@ -24,6 +25,7 @@ import (
"github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/core/vm"
"github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/rollup/fees"
) )
// statePrefetcher is a basic Prefetcher, which blindly executes a block on top // statePrefetcher is a basic Prefetcher, which blindly executes a block on top
@ -68,7 +70,13 @@ func (p *statePrefetcher) Prefetch(block *types.Block, statedb *state.StateDB, c
return // Also invalid block, bail out return // Also invalid block, bail out
} }
statedb.SetTxContext(tx.Hash(), i) statedb.SetTxContext(tx.Hash(), i)
if err := precacheTransaction(msg, p.config, gaspool, statedb, header, evm); err != nil {
l1DataFee, err := fees.CalculateL1DataFee(tx, statedb)
if err != nil {
return
}
if err := precacheTransaction(msg, p.config, gaspool, statedb, header, evm, l1DataFee); err != nil {
return // Ugh, something went horribly wrong, bail out return // Ugh, something went horribly wrong, bail out
} }
// If we're pre-byzantium, pre-load trie nodes for the intermediate root // If we're pre-byzantium, pre-load trie nodes for the intermediate root
@ -85,10 +93,10 @@ func (p *statePrefetcher) Prefetch(block *types.Block, statedb *state.StateDB, c
// precacheTransaction attempts to apply a transaction to the given state database // precacheTransaction attempts to apply a transaction to the given state database
// and uses the input parameters for its environment. The goal is not to execute // and uses the input parameters for its environment. The goal is not to execute
// the transaction successfully, rather to warm up touched data slots. // the transaction successfully, rather to warm up touched data slots.
func precacheTransaction(msg *Message, config *params.ChainConfig, gaspool *GasPool, statedb *state.StateDB, header *types.Header, evm *vm.EVM) error { func precacheTransaction(msg *Message, config *params.ChainConfig, gaspool *GasPool, statedb *state.StateDB, header *types.Header, evm *vm.EVM, l1DataFee *big.Int) error {
// Update the evm with the new transaction context. // Update the evm with the new transaction context.
evm.Reset(NewEVMTxContext(msg), statedb) evm.Reset(NewEVMTxContext(msg), statedb)
// Add addresses to access list if applicable // Add addresses to access list if applicable
_, err := ApplyMessage(evm, msg, gaspool) _, err := ApplyMessage(evm, msg, gaspool, l1DataFee)
return err return err
} }

View file

@ -29,6 +29,7 @@ import (
"github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/core/vm"
"github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/rollup/fees"
) )
// StateProcessor is a basic Processor, which takes care of transitioning // StateProcessor is a basic Processor, which takes care of transitioning
@ -109,8 +110,13 @@ func applyTransaction(msg *Message, config *params.ChainConfig, gp *GasPool, sta
txContext := NewEVMTxContext(msg) txContext := NewEVMTxContext(msg)
evm.Reset(txContext, statedb) evm.Reset(txContext, statedb)
l1DataFee, err := fees.CalculateL1DataFee(tx, statedb)
if err != nil {
return nil, err
}
// Apply the transaction to the current state (included in the env). // Apply the transaction to the current state (included in the env).
result, err := ApplyMessage(evm, msg, gp) result, err := ApplyMessage(evm, msg, gp, l1DataFee)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -151,6 +157,7 @@ func applyTransaction(msg *Message, config *params.ChainConfig, gp *GasPool, sta
receipt.BlockHash = blockHash receipt.BlockHash = blockHash
receipt.BlockNumber = blockNumber receipt.BlockNumber = blockNumber
receipt.TransactionIndex = uint(statedb.TxIndex()) receipt.TransactionIndex = uint(statedb.TxIndex())
receipt.L1Fee = result.L1DataFee
return receipt, err return receipt, err
} }

View file

@ -26,6 +26,7 @@ import (
cmath "github.com/ethereum/go-ethereum/common/math" cmath "github.com/ethereum/go-ethereum/common/math"
"github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/core/vm"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/params"
) )
@ -149,6 +150,18 @@ type Message struct {
IsL1MessageTx bool IsL1MessageTx bool
} }
func (m *Message) GetFrom() common.Address { return m.From }
func (m *Message) GetTo() *common.Address { return m.To }
func (m *Message) GetGasPrice() *big.Int { return m.GasPrice }
func (m *Message) GetGasLimit() uint64 { return m.GasLimit }
func (m *Message) GetGasFeeCap() *big.Int { return m.GasFeeCap }
func (m *Message) GetGasTipCap() *big.Int { return m.GasTipCap }
func (m *Message) GetValue() *big.Int { return m.Value }
func (m *Message) GetNonce() uint64 { return m.Nonce }
func (m *Message) GetData() []byte { return m.Data }
func (m *Message) GetAccessList() types.AccessList { return m.AccessList }
func (m *Message) GetIsL1MessageTx() bool { return m.IsL1MessageTx }
// TransactionToMessage converts a transaction into a Message. // TransactionToMessage converts a transaction into a Message.
func TransactionToMessage(tx *types.Transaction, s types.Signer, baseFee *big.Int) (*Message, error) { func TransactionToMessage(tx *types.Transaction, s types.Signer, baseFee *big.Int) (*Message, error) {
msg := &Message{ msg := &Message{
@ -182,8 +195,8 @@ func TransactionToMessage(tx *types.Transaction, s types.Signer, baseFee *big.In
// the gas used (which includes gas refunds) and an error if it failed. An error always // the gas used (which includes gas refunds) and an error if it failed. An error always
// indicates a core error meaning that the message would always fail for that particular // indicates a core error meaning that the message would always fail for that particular
// state and would never be accepted within a block. // state and would never be accepted within a block.
func ApplyMessage(evm *vm.EVM, msg *Message, gp *GasPool) (*ExecutionResult, error) { func ApplyMessage(evm *vm.EVM, msg *Message, gp *GasPool, l1DataFee *big.Int) (*ExecutionResult, error) {
return NewStateTransition(evm, msg, gp).TransitionDb() return NewStateTransition(evm, msg, gp, l1DataFee).TransitionDb()
} }
// StateTransition represents a state transition. // StateTransition represents a state transition.
@ -215,15 +228,18 @@ type StateTransition struct {
initialGas uint64 initialGas uint64
state vm.StateDB state vm.StateDB
evm *vm.EVM evm *vm.EVM
l1DataFee *big.Int
} }
// NewStateTransition initialises and returns a new state transition object. // NewStateTransition initialises and returns a new state transition object.
func NewStateTransition(evm *vm.EVM, msg *Message, gp *GasPool) *StateTransition { func NewStateTransition(evm *vm.EVM, msg *Message, gp *GasPool, l1DataFee *big.Int) *StateTransition {
return &StateTransition{ return &StateTransition{
gp: gp, gp: gp,
evm: evm, evm: evm,
msg: msg, msg: msg,
state: evm.StateDB, state: evm.StateDB,
l1DataFee: l1DataFee,
} }
} }
@ -238,11 +254,28 @@ func (st *StateTransition) to() common.Address {
func (st *StateTransition) buyGas() error { func (st *StateTransition) buyGas() error {
mgval := new(big.Int).SetUint64(st.msg.GasLimit) mgval := new(big.Int).SetUint64(st.msg.GasLimit)
mgval = mgval.Mul(mgval, st.msg.GasPrice) mgval = mgval.Mul(mgval, st.msg.GasPrice)
if st.evm.ChainConfig().Scroll.FeeVaultEnabled() {
// should be fine to add st.l1DataFee even without `L1MessageTx` check, since L1MessageTx will come with 0 l1DataFee,
// but double check to make sure
if !st.msg.IsL1MessageTx {
log.Debug("Adding L1DataFee", "l1DataFee", st.l1DataFee)
mgval = mgval.Add(mgval, st.l1DataFee)
}
}
balanceCheck := new(big.Int).Set(mgval) balanceCheck := new(big.Int).Set(mgval)
if st.msg.GasFeeCap != nil { if st.msg.GasFeeCap != nil {
balanceCheck.SetUint64(st.msg.GasLimit) balanceCheck.SetUint64(st.msg.GasLimit)
balanceCheck = balanceCheck.Mul(balanceCheck, st.msg.GasFeeCap) balanceCheck = balanceCheck.Mul(balanceCheck, st.msg.GasFeeCap)
balanceCheck.Add(balanceCheck, st.msg.Value) balanceCheck.Add(balanceCheck, st.msg.Value)
if st.evm.ChainConfig().Scroll.FeeVaultEnabled() {
// should be fine to add st.l1DataFee even without `L1MessageTx` check, since L1MessageTx will come with 0 l1DataFee,
// but double check to make sure
if !st.msg.IsL1MessageTx {
balanceCheck.Add(balanceCheck, st.l1DataFee)
}
}
} }
if st.evm.ChainConfig().IsCancun(st.evm.Context.BlockNumber, st.evm.Context.Time) { if st.evm.ChainConfig().IsCancun(st.evm.Context.BlockNumber, st.evm.Context.Time) {
if blobGas := st.blobGasUsed(); blobGas > 0 { if blobGas := st.blobGasUsed(); blobGas > 0 {
@ -455,12 +488,17 @@ func (st *StateTransition) TransitionDb() (*ExecutionResult, error) {
// are 0. This avoids a negative effectiveTip being applied to // are 0. This avoids a negative effectiveTip being applied to
// the coinbase when simulating calls. // the coinbase when simulating calls.
} else { } else {
// The L2 Fee is the same as the fee that is charged in the normal geth
// codepath. Add the L1DataFee to the L2 fee for the total fee that is sent
// to the sequencer.
fee := new(big.Int).SetUint64(st.gasUsed()) fee := new(big.Int).SetUint64(st.gasUsed())
fee.Mul(fee, effectiveTip) fee.Mul(fee, effectiveTip)
st.state.AddBalance(st.evm.Context.Coinbase, fee) fee.Add(fee, st.l1DataFee)
st.state.AddBalance(st.evm.Context.Coinbase, fee) // TODO: change to `st.evm.FeeRecipient()`
} }
return &ExecutionResult{ return &ExecutionResult{
L1DataFee: st.l1DataFee,
UsedGas: st.gasUsed(), UsedGas: st.gasUsed(),
Err: vmerr, Err: vmerr,
ReturnData: ret, ReturnData: ret,

View file

@ -28,6 +28,7 @@ import (
"github.com/ethereum/go-ethereum/crypto/kzg4844" "github.com/ethereum/go-ethereum/crypto/kzg4844"
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/rollup/fees"
) )
// ValidationOptions define certain differences between transaction validation // ValidationOptions define certain differences between transaction validation
@ -219,9 +220,23 @@ func ValidateTransactionWithState(tx *types.Transaction, signer types.Signer, op
balance = opts.State.GetBalance(from) balance = opts.State.GetBalance(from)
cost = tx.Cost() cost = tx.Cost()
) )
// 1. Check balance >= transaction cost (V + GP * GL) to maintain compatibility with the logic without considering L1 data fee.
if balance.Cmp(cost) < 0 { if balance.Cmp(cost) < 0 {
return fmt.Errorf("%w: balance %v, tx cost %v, overshot %v", core.ErrInsufficientFunds, balance, cost, new(big.Int).Sub(cost, balance)) return fmt.Errorf("%w: balance %v, tx cost %v, overshot %v", core.ErrInsufficientFunds, balance, cost, new(big.Int).Sub(cost, balance))
} }
// 2. Perform an additional check for L1 data fees.
// Always perform the check, because it's not easy to check FeeVault here
// Get L1 data fee in current state
l1DataFee, err := fees.CalculateL1DataFee(tx, opts.State)
if err != nil {
return fmt.Errorf("failed to calculate L1 data fee, err: %w", err)
}
// Transactor should have enough funds to cover the costs
// cost == L1 data fee + V + GP * GL
cost = new(big.Int).Add(tx.Cost(), l1DataFee)
if balance.Cmp(cost) < 0 {
return fmt.Errorf("invalid transaction: %w", core.ErrInsufficientFundsWithL1DataFee)
}
// Ensure the transactor has enough funds to cover for replacements or nonce // Ensure the transactor has enough funds to cover for replacements or nonce
// expansions without overdrafts // expansions without overdrafts
spent := opts.ExistingExpenditure(from) spent := opts.ExistingExpenditure(from)

View file

@ -31,6 +31,7 @@ func (r Receipt) MarshalJSON() ([]byte, error) {
BlockHash common.Hash `json:"blockHash,omitempty"` BlockHash common.Hash `json:"blockHash,omitempty"`
BlockNumber *hexutil.Big `json:"blockNumber,omitempty"` BlockNumber *hexutil.Big `json:"blockNumber,omitempty"`
TransactionIndex hexutil.Uint `json:"transactionIndex"` TransactionIndex hexutil.Uint `json:"transactionIndex"`
L1Fee *hexutil.Big `json:"l1Fee,omitempty"`
} }
var enc Receipt var enc Receipt
enc.Type = hexutil.Uint64(r.Type) enc.Type = hexutil.Uint64(r.Type)
@ -48,6 +49,7 @@ func (r Receipt) MarshalJSON() ([]byte, error) {
enc.BlockHash = r.BlockHash enc.BlockHash = r.BlockHash
enc.BlockNumber = (*hexutil.Big)(r.BlockNumber) enc.BlockNumber = (*hexutil.Big)(r.BlockNumber)
enc.TransactionIndex = hexutil.Uint(r.TransactionIndex) enc.TransactionIndex = hexutil.Uint(r.TransactionIndex)
enc.L1Fee = (*hexutil.Big)(r.L1Fee)
return json.Marshal(&enc) return json.Marshal(&enc)
} }
@ -69,6 +71,7 @@ func (r *Receipt) UnmarshalJSON(input []byte) error {
BlockHash *common.Hash `json:"blockHash,omitempty"` BlockHash *common.Hash `json:"blockHash,omitempty"`
BlockNumber *hexutil.Big `json:"blockNumber,omitempty"` BlockNumber *hexutil.Big `json:"blockNumber,omitempty"`
TransactionIndex *hexutil.Uint `json:"transactionIndex"` TransactionIndex *hexutil.Uint `json:"transactionIndex"`
L1Fee *hexutil.Big `json:"l1Fee,omitempty"`
} }
var dec Receipt var dec Receipt
if err := json.Unmarshal(input, &dec); err != nil { if err := json.Unmarshal(input, &dec); err != nil {
@ -124,5 +127,8 @@ func (r *Receipt) UnmarshalJSON(input []byte) error {
if dec.TransactionIndex != nil { if dec.TransactionIndex != nil {
r.TransactionIndex = uint(*dec.TransactionIndex) r.TransactionIndex = uint(*dec.TransactionIndex)
} }
if dec.L1Fee != nil {
r.L1Fee = (*big.Int)(dec.L1Fee)
}
return nil return nil
} }

View file

@ -71,6 +71,9 @@ type Receipt struct {
BlockHash common.Hash `json:"blockHash,omitempty"` BlockHash common.Hash `json:"blockHash,omitempty"`
BlockNumber *big.Int `json:"blockNumber,omitempty"` BlockNumber *big.Int `json:"blockNumber,omitempty"`
TransactionIndex uint `json:"transactionIndex"` TransactionIndex uint `json:"transactionIndex"`
// Scroll rollup
L1Fee *big.Int `json:"l1Fee,omitempty"`
} }
type receiptMarshaling struct { type receiptMarshaling struct {
@ -84,6 +87,7 @@ type receiptMarshaling struct {
BlobGasPrice *hexutil.Big BlobGasPrice *hexutil.Big
BlockNumber *hexutil.Big BlockNumber *hexutil.Big
TransactionIndex hexutil.Uint TransactionIndex hexutil.Uint
L1Fee *hexutil.Big
} }
// receiptRLP is the consensus encoding of a receipt. // receiptRLP is the consensus encoding of a receipt.
@ -99,6 +103,7 @@ type storedReceiptRLP struct {
PostStateOrStatus []byte PostStateOrStatus []byte
CumulativeGasUsed uint64 CumulativeGasUsed uint64
Logs []*Log Logs []*Log
L1Fee *big.Int
} }
// NewReceipt creates a barebone transaction receipt, copying the init fields. // NewReceipt creates a barebone transaction receipt, copying the init fields.
@ -264,6 +269,10 @@ type ReceiptForStorage Receipt
// EncodeRLP implements rlp.Encoder, and flattens all content fields of a receipt // EncodeRLP implements rlp.Encoder, and flattens all content fields of a receipt
// into an RLP stream. // into an RLP stream.
func (r *ReceiptForStorage) EncodeRLP(_w io.Writer) error { func (r *ReceiptForStorage) EncodeRLP(_w io.Writer) error {
if r.L1Fee == nil {
r.L1Fee = big.NewInt(0)
}
w := rlp.NewEncoderBuffer(_w) w := rlp.NewEncoderBuffer(_w)
outerList := w.List() outerList := w.List()
w.WriteBytes((*Receipt)(r).statusEncoding()) w.WriteBytes((*Receipt)(r).statusEncoding())
@ -275,6 +284,7 @@ func (r *ReceiptForStorage) EncodeRLP(_w io.Writer) error {
} }
} }
w.ListEnd(logList) w.ListEnd(logList)
w.WriteBigInt(r.L1Fee)
w.ListEnd(outerList) w.ListEnd(outerList)
return w.Flush() return w.Flush()
} }
@ -292,6 +302,7 @@ func (r *ReceiptForStorage) DecodeRLP(s *rlp.Stream) error {
r.CumulativeGasUsed = stored.CumulativeGasUsed r.CumulativeGasUsed = stored.CumulativeGasUsed
r.Logs = stored.Logs r.Logs = stored.Logs
r.Bloom = CreateBloom(Receipts{(*Receipt)(r)}) r.Bloom = CreateBloom(Receipts{(*Receipt)(r)})
r.L1Fee = stored.L1Fee
return nil return nil
} }

View file

@ -304,6 +304,7 @@ func (b *EthAPIBackend) SubscribeLogsEvent(ch chan<- []*types.Log) event.Subscri
} }
func (b *EthAPIBackend) SendTx(ctx context.Context, signedTx *types.Transaction) error { func (b *EthAPIBackend) SendTx(ctx context.Context, signedTx *types.Transaction) error {
// will `VerifyFee` & `validateTx` in txPool.Add
return b.eth.txPool.Add([]*types.Transaction{signedTx}, true, false)[0] return b.eth.txPool.Add([]*types.Transaction{signedTx}, true, false)[0]
} }

View file

@ -30,6 +30,7 @@ import (
"github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/core/vm"
"github.com/ethereum/go-ethereum/eth/tracers" "github.com/ethereum/go-ethereum/eth/tracers"
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/rollup/fees"
"github.com/ethereum/go-ethereum/trie" "github.com/ethereum/go-ethereum/trie"
) )
@ -248,7 +249,11 @@ func (eth *Ethereum) stateAtTransaction(ctx context.Context, block *types.Block,
// Not yet the searched for transaction, execute on top of the current state // Not yet the searched for transaction, execute on top of the current state
vmenv := vm.NewEVM(context, txContext, statedb, eth.blockchain.Config(), vm.Config{}) vmenv := vm.NewEVM(context, txContext, statedb, eth.blockchain.Config(), vm.Config{})
statedb.SetTxContext(tx.Hash(), idx) statedb.SetTxContext(tx.Hash(), idx)
if _, err := core.ApplyMessage(vmenv, msg, new(core.GasPool).AddGas(tx.Gas())); err != nil { l1DataFee, err := fees.CalculateL1DataFee(tx, statedb)
if err != nil {
return nil, vm.BlockContext{}, nil, nil, err
}
if _, err := core.ApplyMessage(vmenv, msg, new(core.GasPool).AddGas(tx.Gas()), l1DataFee); err != nil {
return nil, vm.BlockContext{}, nil, nil, fmt.Errorf("transaction %#x failed: %v", tx.Hash(), err) return nil, vm.BlockContext{}, nil, nil, fmt.Errorf("transaction %#x failed: %v", tx.Hash(), err)
} }
// Ensure any modifications are committed to the state // Ensure any modifications are committed to the state

View file

@ -22,6 +22,7 @@ import (
"encoding/json" "encoding/json"
"errors" "errors"
"fmt" "fmt"
"math/big"
"os" "os"
"runtime" "runtime"
"sync" "sync"
@ -41,6 +42,7 @@ import (
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/rlp" "github.com/ethereum/go-ethereum/rlp"
"github.com/ethereum/go-ethereum/rollup/fees"
"github.com/ethereum/go-ethereum/rpc" "github.com/ethereum/go-ethereum/rpc"
) )
@ -276,7 +278,14 @@ func (api *API) traceChain(start, end *types.Block, config *TraceConfig, closed
TxIndex: i, TxIndex: i,
TxHash: tx.Hash(), TxHash: tx.Hash(),
} }
res, err := api.traceTx(ctx, msg, txctx, blockCtx, task.statedb, config) l1DataFee, err := fees.CalculateL1DataFee(tx, task.statedb)
if err != nil {
// though it's not a "tracing error", we still need to put it here
task.results[i] = &txTraceResult{TxHash: tx.Hash(), Error: err.Error()}
log.Warn("CalculateL1DataFee failed", "hash", tx.Hash(), "block", task.block.NumberU64(), "err", err)
break
}
res, err := api.traceTx(ctx, msg, txctx, blockCtx, task.statedb, config, l1DataFee)
if err != nil { if err != nil {
task.results[i] = &txTraceResult{TxHash: tx.Hash(), Error: err.Error()} task.results[i] = &txTraceResult{TxHash: tx.Hash(), Error: err.Error()}
log.Warn("Tracing failed", "hash", tx.Hash(), "block", task.block.NumberU64(), "err", err) log.Warn("Tracing failed", "hash", tx.Hash(), "block", task.block.NumberU64(), "err", err)
@ -535,7 +544,12 @@ func (api *API) IntermediateRoots(ctx context.Context, hash common.Hash, config
vmenv = vm.NewEVM(vmctx, txContext, statedb, chainConfig, vm.Config{}) vmenv = vm.NewEVM(vmctx, txContext, statedb, chainConfig, vm.Config{})
) )
statedb.SetTxContext(tx.Hash(), i) statedb.SetTxContext(tx.Hash(), i)
if _, err := core.ApplyMessage(vmenv, msg, new(core.GasPool).AddGas(msg.GasLimit)); err != nil { l1DataFee, err := fees.CalculateL1DataFee(tx, statedb)
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
}
if _, err := core.ApplyMessage(vmenv, msg, new(core.GasPool).AddGas(msg.GasLimit), l1DataFee); err != nil {
log.Warn("Tracing intermediate roots did not complete", "txindex", i, "txhash", tx.Hash(), "err", err) log.Warn("Tracing intermediate roots did not complete", "txindex", i, "txhash", tx.Hash(), "err", err)
// We intentionally don't return the error here: if we do, then the RPC server will not // We intentionally don't return the error here: if we do, then the RPC server will not
// return the roots. Most likely, the caller already knows that a certain transaction fails to // return the roots. Most likely, the caller already knows that a certain transaction fails to
@ -611,7 +625,11 @@ func (api *API) traceBlock(ctx context.Context, block *types.Block, config *Trac
TxIndex: i, TxIndex: i,
TxHash: tx.Hash(), TxHash: tx.Hash(),
} }
res, err := api.traceTx(ctx, msg, txctx, blockCtx, statedb, config) l1DataFee, err := fees.CalculateL1DataFee(tx, statedb)
if err != nil {
return nil, err
}
res, err := api.traceTx(ctx, msg, txctx, blockCtx, statedb, config, l1DataFee)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -654,7 +672,13 @@ func (api *API) traceBlockParallel(ctx context.Context, block *types.Block, stat
TxIndex: task.index, TxIndex: task.index,
TxHash: txs[task.index].Hash(), TxHash: txs[task.index].Hash(),
} }
res, err := api.traceTx(ctx, msg, txctx, blockCtx, task.statedb, config) l1DataFee, err := fees.CalculateL1DataFee(txs[task.index], task.statedb)
if err != nil {
// though it's not a "tracing error", we still need to put it here
results[task.index] = &txTraceResult{TxHash: txs[task.index].Hash(), Error: err.Error()}
continue
}
res, err := api.traceTx(ctx, msg, txctx, blockCtx, task.statedb, config, l1DataFee)
if err != nil { if err != nil {
results[task.index] = &txTraceResult{TxHash: txs[task.index].Hash(), Error: err.Error()} results[task.index] = &txTraceResult{TxHash: txs[task.index].Hash(), Error: err.Error()}
continue continue
@ -681,7 +705,12 @@ txloop:
msg, _ := core.TransactionToMessage(tx, signer, block.BaseFee()) msg, _ := core.TransactionToMessage(tx, signer, block.BaseFee())
statedb.SetTxContext(tx.Hash(), i) statedb.SetTxContext(tx.Hash(), i)
vmenv := vm.NewEVM(blockCtx, core.NewEVMTxContext(msg), statedb, api.backend.ChainConfig(), vm.Config{}) vmenv := vm.NewEVM(blockCtx, core.NewEVMTxContext(msg), statedb, api.backend.ChainConfig(), vm.Config{})
if _, err := core.ApplyMessage(vmenv, msg, new(core.GasPool).AddGas(msg.GasLimit)); err != nil { l1DataFee, err := fees.CalculateL1DataFee(tx, statedb)
if err != nil {
failed = err
break txloop
}
if _, err := core.ApplyMessage(vmenv, msg, new(core.GasPool).AddGas(msg.GasLimit), l1DataFee); err != nil {
failed = err failed = err
break txloop break txloop
} }
@ -788,7 +817,10 @@ func (api *API) standardTraceBlockToFile(ctx context.Context, block *types.Block
// Execute the transaction and flush any traces to disk // Execute the transaction and flush any traces to disk
vmenv := vm.NewEVM(vmctx, txContext, statedb, chainConfig, vmConf) vmenv := vm.NewEVM(vmctx, txContext, statedb, chainConfig, vmConf)
statedb.SetTxContext(tx.Hash(), i) statedb.SetTxContext(tx.Hash(), i)
_, err = core.ApplyMessage(vmenv, msg, new(core.GasPool).AddGas(msg.GasLimit)) l1DataFee, err := fees.CalculateL1DataFee(tx, statedb)
if err == nil {
_, err = core.ApplyMessage(vmenv, msg, new(core.GasPool).AddGas(msg.GasLimit), l1DataFee)
}
if writer != nil { if writer != nil {
writer.Flush() writer.Flush()
} }
@ -857,7 +889,11 @@ func (api *API) TraceTransaction(ctx context.Context, hash common.Hash, config *
TxIndex: int(index), TxIndex: int(index),
TxHash: hash, TxHash: hash,
} }
return api.traceTx(ctx, msg, txctx, vmctx, statedb, config) l1DataFee, err := fees.CalculateL1DataFee(tx, statedb)
if err != nil {
return nil, err
}
return api.traceTx(ctx, msg, txctx, vmctx, statedb, config, l1DataFee)
} }
// TraceCall lets you trace a given eth_call. It collects the structured logs // TraceCall lets you trace a given eth_call. It collects the structured logs
@ -916,13 +952,18 @@ func (api *API) TraceCall(ctx context.Context, args ethapi.TransactionArgs, bloc
if config != nil { if config != nil {
traceConfig = &config.TraceConfig traceConfig = &config.TraceConfig
} }
return api.traceTx(ctx, msg, new(Context), vmctx, statedb, traceConfig) signer := types.MakeSigner(api.backend.ChainConfig(), block.Number(), block.Time())
l1DataFee, err := fees.EstimateL1DataFeeForMessage(msg, block.BaseFee(), api.backend.ChainConfig().ChainID, signer, statedb)
if err != nil {
return nil, err
}
return api.traceTx(ctx, msg, new(Context), vmctx, statedb, traceConfig, l1DataFee)
} }
// traceTx configures a new tracer according to the provided configuration, and // traceTx configures a new tracer according to the provided configuration, and
// executes the given message in the provided environment. The return value will // executes the given message in the provided environment. The return value will
// be tracer dependent. // be tracer dependent.
func (api *API) traceTx(ctx context.Context, message *core.Message, txctx *Context, vmctx vm.BlockContext, statedb *state.StateDB, config *TraceConfig) (interface{}, error) { func (api *API) traceTx(ctx context.Context, message *core.Message, txctx *Context, vmctx vm.BlockContext, statedb *state.StateDB, config *TraceConfig, l1DataFee *big.Int) (interface{}, error) {
var ( var (
tracer Tracer tracer Tracer
err error err error
@ -942,6 +983,11 @@ func (api *API) traceTx(ctx context.Context, message *core.Message, txctx *Conte
} }
vmenv := vm.NewEVM(vmctx, txContext, statedb, api.backend.ChainConfig(), vm.Config{Tracer: tracer, NoBaseFee: true}) vmenv := vm.NewEVM(vmctx, txContext, statedb, api.backend.ChainConfig(), vm.Config{Tracer: tracer, NoBaseFee: true})
// If gasPrice is 0, make sure that the account has sufficient balance to cover `l1DataFee`.
if message.GasPrice.Cmp(big.NewInt(0)) == 0 {
statedb.AddBalance(message.From, l1DataFee)
}
// Define a meaningful timeout of a single transaction trace // Define a meaningful timeout of a single transaction trace
if config.Timeout != nil { if config.Timeout != nil {
if timeout, err = time.ParseDuration(*config.Timeout); err != nil { if timeout, err = time.ParseDuration(*config.Timeout); err != nil {
@ -961,7 +1007,7 @@ func (api *API) traceTx(ctx context.Context, message *core.Message, txctx *Conte
// Call Prepare to clear out the statedb access list // Call Prepare to clear out the statedb access list
statedb.SetTxContext(txctx.TxHash, txctx.TxIndex) statedb.SetTxContext(txctx.TxHash, txctx.TxIndex)
if _, err = core.ApplyMessage(vmenv, message, new(core.GasPool).AddGas(message.GasLimit)); err != nil { if _, err = core.ApplyMessage(vmenv, message, new(core.GasPool).AddGas(message.GasLimit), l1DataFee); err != nil {
return nil, fmt.Errorf("tracing failed: %w", err) return nil, fmt.Errorf("tracing failed: %w", err)
} }
return tracer.GetResult() return tracer.GetResult()

View file

@ -42,6 +42,7 @@ import (
"github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/ethdb"
"github.com/ethereum/go-ethereum/internal/ethapi" "github.com/ethereum/go-ethereum/internal/ethapi"
"github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/rollup/fees"
"github.com/ethereum/go-ethereum/rpc" "github.com/ethereum/go-ethereum/rpc"
"golang.org/x/exp/slices" "golang.org/x/exp/slices"
) )
@ -177,7 +178,11 @@ func (b *testBackend) StateAtTransaction(ctx context.Context, block *types.Block
return msg, context, statedb, release, nil return msg, context, statedb, release, nil
} }
vmenv := vm.NewEVM(context, txContext, statedb, b.chainConfig, vm.Config{}) vmenv := vm.NewEVM(context, txContext, statedb, b.chainConfig, vm.Config{})
if _, err := core.ApplyMessage(vmenv, msg, new(core.GasPool).AddGas(tx.Gas())); err != nil { l1DataFee, err := fees.CalculateL1DataFee(tx, statedb)
if err != nil {
return nil, vm.BlockContext{}, nil, nil, fmt.Errorf("transaction %#x CalculateL1DataFee failed: %v", tx.Hash(), err)
}
if _, err := core.ApplyMessage(vmenv, msg, new(core.GasPool).AddGas(tx.Gas()), l1DataFee); err != nil {
return nil, vm.BlockContext{}, nil, nil, fmt.Errorf("transaction %#x failed: %v", tx.Hash(), err) return nil, vm.BlockContext{}, nil, nil, fmt.Errorf("transaction %#x failed: %v", tx.Hash(), err)
} }
statedb.Finalise(vmenv.ChainConfig().IsEIP158(block.Number())) statedb.Finalise(vmenv.ChainConfig().IsEIP158(block.Number()))

View file

@ -34,6 +34,7 @@ import (
"github.com/ethereum/go-ethereum/eth/tracers" "github.com/ethereum/go-ethereum/eth/tracers"
"github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/rlp" "github.com/ethereum/go-ethereum/rlp"
"github.com/ethereum/go-ethereum/rollup/fees"
"github.com/ethereum/go-ethereum/tests" "github.com/ethereum/go-ethereum/tests"
) )
@ -150,7 +151,11 @@ func testCallTracer(tracerName string, dirPath string, t *testing.T) {
if err != nil { if err != nil {
t.Fatalf("failed to prepare transaction for tracing: %v", err) t.Fatalf("failed to prepare transaction for tracing: %v", err)
} }
vmRet, err := core.ApplyMessage(evm, msg, new(core.GasPool).AddGas(tx.Gas())) l1DataFee, err := fees.CalculateL1DataFee(tx, statedb)
if err != nil {
t.Fatalf("failed to calculate l1DataFee: %v", err)
}
vmRet, err := core.ApplyMessage(evm, msg, new(core.GasPool).AddGas(tx.Gas()), l1DataFee)
if err != nil { if err != nil {
t.Fatalf("failed to execute transaction: %v", err) t.Fatalf("failed to execute transaction: %v", err)
} }
@ -251,7 +256,11 @@ func benchTracer(tracerName string, test *callTracerTest, b *testing.B) {
} }
evm := vm.NewEVM(context, txContext, statedb, test.Genesis.Config, vm.Config{Tracer: tracer}) evm := vm.NewEVM(context, txContext, statedb, test.Genesis.Config, vm.Config{Tracer: tracer})
snap := statedb.Snapshot() snap := statedb.Snapshot()
st := core.NewStateTransition(evm, msg, new(core.GasPool).AddGas(tx.Gas())) l1DataFee, err := fees.CalculateL1DataFee(tx, statedb)
if err != nil {
b.Fatalf("failed to calculate l1DataFee: %v", err)
}
st := core.NewStateTransition(evm, msg, new(core.GasPool).AddGas(tx.Gas()), l1DataFee)
if _, err = st.TransitionDb(); err != nil { if _, err = st.TransitionDb(); err != nil {
b.Fatalf("failed to execute transaction: %v", err) b.Fatalf("failed to execute transaction: %v", err)
} }
@ -388,7 +397,12 @@ func TestInternals(t *testing.T) {
GasTipCap: big.NewInt(0), GasTipCap: big.NewInt(0),
SkipAccountChecks: false, SkipAccountChecks: false,
} }
st := core.NewStateTransition(evm, msg, new(core.GasPool).AddGas(msg.GasLimit)) signer := types.MakeSigner(params.MainnetChainConfig, context.BlockNumber, context.Time)
l1DataFee, err := fees.EstimateL1DataFeeForMessage(msg, nil, params.MainnetChainConfig.ChainID, signer, statedb)
if err != nil {
t.Fatalf("test %v: failed to estimate L1DataFee: %v", tc.name, err)
}
st := core.NewStateTransition(evm, msg, new(core.GasPool).AddGas(msg.GasLimit), l1DataFee)
if _, err := st.TransitionDb(); err != nil { if _, err := st.TransitionDb(); err != nil {
t.Fatalf("test %v: failed to execute transaction: %v", tc.name, err) t.Fatalf("test %v: failed to execute transaction: %v", tc.name, err)
} }

View file

@ -17,6 +17,7 @@ import (
"github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/core/vm"
"github.com/ethereum/go-ethereum/rlp" "github.com/ethereum/go-ethereum/rlp"
"github.com/ethereum/go-ethereum/rollup/fees"
"github.com/ethereum/go-ethereum/tests" "github.com/ethereum/go-ethereum/tests"
// Force-load the native, to trigger registration // Force-load the native, to trigger registration
@ -114,7 +115,11 @@ func flatCallTracerTestRunner(tracerName string, filename string, dirPath string
if err != nil { if err != nil {
return fmt.Errorf("failed to prepare transaction for tracing: %v", err) return fmt.Errorf("failed to prepare transaction for tracing: %v", err)
} }
st := core.NewStateTransition(evm, msg, new(core.GasPool).AddGas(tx.Gas())) l1DataFee, err := fees.CalculateL1DataFee(tx, statedb)
if err != nil {
return fmt.Errorf("failed to calculate L1DataFee: %v", err)
}
st := core.NewStateTransition(evm, msg, new(core.GasPool).AddGas(tx.Gas()), l1DataFee)
if _, err = st.TransitionDb(); err != nil { if _, err = st.TransitionDb(); err != nil {
return fmt.Errorf("failed to execute transaction: %v", err) return fmt.Errorf("failed to execute transaction: %v", err)

View file

@ -30,6 +30,7 @@ import (
"github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/core/vm"
"github.com/ethereum/go-ethereum/eth/tracers" "github.com/ethereum/go-ethereum/eth/tracers"
"github.com/ethereum/go-ethereum/rollup/fees"
"github.com/ethereum/go-ethereum/tests" "github.com/ethereum/go-ethereum/tests"
) )
@ -121,7 +122,11 @@ func testPrestateDiffTracer(tracerName string, dirPath string, t *testing.T) {
if err != nil { if err != nil {
t.Fatalf("failed to prepare transaction for tracing: %v", err) t.Fatalf("failed to prepare transaction for tracing: %v", err)
} }
st := core.NewStateTransition(evm, msg, new(core.GasPool).AddGas(tx.Gas())) l1DataFee, err := fees.CalculateL1DataFee(tx, statedb)
if err != nil {
t.Fatalf("failed to calculate L1DataFee: %v", err)
}
st := core.NewStateTransition(evm, msg, new(core.GasPool).AddGas(tx.Gas()), l1DataFee)
if _, err = st.TransitionDb(); err != nil { if _, err = st.TransitionDb(); err != nil {
t.Fatalf("failed to execute transaction: %v", err) t.Fatalf("failed to execute transaction: %v", err)
} }

View file

@ -252,6 +252,7 @@ func (l *StructLogger) GetResult() (json.RawMessage, error) {
Failed: failed, Failed: failed,
ReturnValue: returnVal, ReturnValue: returnVal,
StructLogs: formatLogs(l.StructLogs()), StructLogs: formatLogs(l.StructLogs()),
// L1DataFee: (*hexutil.Big)(result.L1DataFee),
}) })
} }

View file

@ -28,6 +28,7 @@ import (
"github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/eth/tracers/logger" "github.com/ethereum/go-ethereum/eth/tracers/logger"
"github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/rollup/fees"
"github.com/ethereum/go-ethereum/tests" "github.com/ethereum/go-ethereum/tests"
) )
@ -99,7 +100,11 @@ func BenchmarkTransactionTrace(b *testing.B) {
for i := 0; i < b.N; i++ { for i := 0; i < b.N; i++ {
snap := statedb.Snapshot() snap := statedb.Snapshot()
st := core.NewStateTransition(evm, msg, new(core.GasPool).AddGas(tx.Gas())) l1DataFee, err := fees.CalculateL1DataFee(tx, statedb)
if err != nil {
b.Fatal(err)
}
st := core.NewStateTransition(evm, msg, new(core.GasPool).AddGas(tx.Gas()), l1DataFee)
_, err = st.TransitionDb() _, err = st.TransitionDb()
if err != nil { if err != nil {
b.Fatal(err) b.Fatal(err)

View file

@ -45,6 +45,7 @@ import (
"github.com/ethereum/go-ethereum/p2p" "github.com/ethereum/go-ethereum/p2p"
"github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/rlp" "github.com/ethereum/go-ethereum/rlp"
"github.com/ethereum/go-ethereum/rollup/fees"
"github.com/ethereum/go-ethereum/rpc" "github.com/ethereum/go-ethereum/rpc"
"github.com/ethereum/go-ethereum/trie" "github.com/ethereum/go-ethereum/trie"
"github.com/tyler-smith/go-bip39" "github.com/tyler-smith/go-bip39"
@ -1099,7 +1100,7 @@ func doCall(ctx context.Context, b Backend, args TransactionArgs, state *state.S
// Execute the message. // Execute the message.
gp := new(core.GasPool).AddGas(math.MaxUint64) gp := new(core.GasPool).AddGas(math.MaxUint64)
result, err := core.ApplyMessage(evm, msg, gp) result, err := core.ApplyMessage(evm, msg, gp, common.Big0)
if err := vmError(); err != nil { if err := vmError(); err != nil {
return nil, err return nil, err
} }
@ -1177,6 +1178,48 @@ func (s *BlockChainAPI) Call(ctx context.Context, args TransactionArgs, blockNrO
return result.Return(), result.Err return result.Return(), result.Err
} }
func EstimateL1MsgFee(ctx context.Context, b Backend, args TransactionArgs, blockNrOrHash rpc.BlockNumberOrHash, overrides *StateOverride, timeout time.Duration, globalGasCap uint64, config *params.ChainConfig) (*big.Int, error) {
if !config.Scroll.FeeVaultEnabled() {
return big.NewInt(0), nil
}
state, header, err := b.StateAndHeaderByNumberOrHash(ctx, blockNrOrHash)
if state == nil || err != nil {
return nil, err
}
if err := overrides.Apply(state); err != nil {
return nil, err
}
// Setup context so it may be cancelled the call has completed
// or, in case of unmetered gas, setup a context with a timeout.
var cancel context.CancelFunc
if timeout > 0 {
ctx, cancel = context.WithTimeout(ctx, timeout)
} else {
ctx, cancel = context.WithCancel(ctx)
}
// Make sure the context is cancelled when the call has completed
// this makes sure resources are cleaned up.
defer cancel()
// Get a new instance of the EVM.
msg, err := args.ToMessage(globalGasCap, header.BaseFee)
if err != nil {
return nil, err
}
blockCtx := core.NewEVMBlockContext(header, NewChainContext(ctx, b), nil)
evm, _ := b.GetEVM(ctx, msg, state, header, &vm.Config{NoBaseFee: true}, &blockCtx)
// Wait for the context to be done and cancel the evm. Even if the
// EVM has finished, cancelling may be done (repeatedly)
go func() {
<-ctx.Done()
evm.Cancel()
}()
signer := types.MakeSigner(config, header.Number, header.Time)
return fees.EstimateL1DataFeeForMessage(msg, header.BaseFee, config.ChainID, signer, evm.StateDB)
}
// executeEstimate is a helper that executes the transaction under a given gas limit and returns // executeEstimate is a helper that executes the transaction under a given gas limit and returns
// true if the transaction fails for a reason that might be related to not enough gas. A non-nil // true if the transaction fails for a reason that might be related to not enough gas. A non-nil
// error means execution failed due to reasons unrelated to the gas limit. // error means execution failed due to reasons unrelated to the gas limit.
@ -1244,12 +1287,25 @@ func DoEstimateGas(ctx context.Context, b Backend, args TransactionArgs, blockNr
if feeCap.BitLen() != 0 { if feeCap.BitLen() != 0 {
balance := state.GetBalance(*args.From) // from can't be nil balance := state.GetBalance(*args.From) // from can't be nil
available := new(big.Int).Set(balance) available := new(big.Int).Set(balance)
// account for tx value
if args.Value != nil { if args.Value != nil {
if args.Value.ToInt().Cmp(available) >= 0 { if args.Value.ToInt().Cmp(available) >= 0 {
return 0, core.ErrInsufficientFundsForTransfer return 0, core.ErrInsufficientFundsForTransfer
} }
available.Sub(available, args.Value.ToInt()) available.Sub(available, args.Value.ToInt())
} }
// account for l1 fee
l1DataFee, err := EstimateL1MsgFee(ctx, b, args, blockNrOrHash, nil, 0, gasCap, b.ChainConfig())
if err != nil {
return 0, err
}
if l1DataFee.Cmp(available) >= 0 {
return 0, errors.New("insufficient funds for l1 fee")
}
available.Sub(available, l1DataFee)
allowance := new(big.Int).Div(available, feeCap) allowance := new(big.Int).Div(available, feeCap)
// If the allowance is larger than maximum uint64, skip checking // If the allowance is larger than maximum uint64, skip checking
@ -1655,7 +1711,12 @@ func AccessList(ctx context.Context, b Backend, blockNrOrHash rpc.BlockNumberOrH
tracer := logger.NewAccessListTracer(accessList, args.from(), to, precompiles) tracer := logger.NewAccessListTracer(accessList, args.from(), to, precompiles)
config := vm.Config{Tracer: tracer, NoBaseFee: true} config := vm.Config{Tracer: tracer, NoBaseFee: true}
vmenv, _ := b.GetEVM(ctx, msg, statedb, header, &config, nil) vmenv, _ := b.GetEVM(ctx, msg, statedb, header, &config, nil)
res, err := core.ApplyMessage(vmenv, msg, new(core.GasPool).AddGas(msg.GasLimit)) signer := types.MakeSigner(b.ChainConfig(), header.Number, header.Time)
l1DataFee, err := fees.EstimateL1DataFeeForMessage(msg, header.BaseFee, b.ChainConfig().ChainID, signer, statedb)
if err != nil {
return nil, 0, nil, fmt.Errorf("failed to apply transaction: %v err: %v", args.toTransaction().Hash(), err)
}
res, err := core.ApplyMessage(vmenv, msg, new(core.GasPool).AddGas(msg.GasLimit), l1DataFee)
if err != nil { if err != nil {
return nil, 0, nil, fmt.Errorf("failed to apply transaction: %v err: %v", args.toTransaction().Hash(), err) return nil, 0, nil, fmt.Errorf("failed to apply transaction: %v err: %v", args.toTransaction().Hash(), err)
} }
@ -1834,6 +1895,7 @@ func marshalReceipt(receipt *types.Receipt, blockHash common.Hash, blockNumber u
"logsBloom": receipt.Bloom, "logsBloom": receipt.Bloom,
"type": hexutil.Uint(tx.Type()), "type": hexutil.Uint(tx.Type()),
"effectiveGasPrice": (*hexutil.Big)(receipt.EffectiveGasPrice), "effectiveGasPrice": (*hexutil.Big)(receipt.EffectiveGasPrice),
"l1Fee": (*hexutil.Big)(receipt.L1Fee),
} }
// Assign receipt status or post state. // Assign receipt status or post state.

View file

@ -196,6 +196,7 @@ func (b *LesApiBackend) GetEVM(ctx context.Context, msg *core.Message, state *st
} }
func (b *LesApiBackend) SendTx(ctx context.Context, signedTx *types.Transaction) error { func (b *LesApiBackend) SendTx(ctx context.Context, signedTx *types.Transaction) error {
// will `VerifyFee` & `validateTx` in txPool.Add
return b.eth.txPool.Add(ctx, signedTx) return b.eth.txPool.Add(ctx, signedTx)
} }

View file

@ -27,6 +27,7 @@ import (
"github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/core/vm"
"github.com/ethereum/go-ethereum/eth/tracers" "github.com/ethereum/go-ethereum/eth/tracers"
"github.com/ethereum/go-ethereum/light" "github.com/ethereum/go-ethereum/light"
"github.com/ethereum/go-ethereum/rollup/fees"
) )
// noopReleaser is returned in case there is no operation expected // noopReleaser is returned in case there is no operation expected
@ -69,7 +70,11 @@ func (leth *LightEthereum) stateAtTransaction(ctx context.Context, block *types.
} }
// Not yet the searched for transaction, execute on top of the current state // Not yet the searched for transaction, execute on top of the current state
vmenv := vm.NewEVM(context, txContext, statedb, leth.blockchain.Config(), vm.Config{}) vmenv := vm.NewEVM(context, txContext, statedb, leth.blockchain.Config(), vm.Config{})
if _, err := core.ApplyMessage(vmenv, msg, new(core.GasPool).AddGas(tx.Gas())); err != nil { l1DataFee, err := fees.CalculateL1DataFee(tx, statedb)
if err != nil {
return nil, vm.BlockContext{}, nil, nil, fmt.Errorf("transaction %#x failed: %v", tx.Hash(), err)
}
if _, err := core.ApplyMessage(vmenv, msg, new(core.GasPool).AddGas(tx.Gas()), l1DataFee); err != nil {
return nil, vm.BlockContext{}, nil, nil, fmt.Errorf("transaction %#x failed: %v", tx.Hash(), err) return nil, vm.BlockContext{}, nil, nil, fmt.Errorf("transaction %#x failed: %v", tx.Hash(), err)
} }
// Ensure any modifications are committed to the state // Ensure any modifications are committed to the state

View file

@ -36,6 +36,7 @@ import (
"github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/ethdb"
"github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/rlp" "github.com/ethereum/go-ethereum/rlp"
"github.com/ethereum/go-ethereum/rollup/fees"
"github.com/ethereum/go-ethereum/trie" "github.com/ethereum/go-ethereum/trie"
"github.com/ethereum/go-ethereum/trie/trienode" "github.com/ethereum/go-ethereum/trie/trienode"
) )
@ -217,7 +218,9 @@ func odrContractCall(ctx context.Context, db ethdb.Database, bc *core.BlockChain
context := core.NewEVMBlockContext(header, chain, nil) context := core.NewEVMBlockContext(header, chain, nil)
vmenv := vm.NewEVM(context, txContext, st, config, vm.Config{NoBaseFee: true}) vmenv := vm.NewEVM(context, txContext, st, config, vm.Config{NoBaseFee: true})
gp := new(core.GasPool).AddGas(math.MaxUint64) gp := new(core.GasPool).AddGas(math.MaxUint64)
result, _ := core.ApplyMessage(vmenv, msg, gp) signer := types.MakeSigner(config, header.Number, header.Time)
l1DataFee, _ := fees.EstimateL1DataFeeForMessage(msg, header.BaseFee, config.ChainID, signer, st)
result, _ := core.ApplyMessage(vmenv, msg, gp, l1DataFee)
res = append(res, result.Return()...) res = append(res, result.Return()...)
if st.Error() != nil { if st.Error() != nil {
return res, st.Error() return res, st.Error()

View file

@ -33,6 +33,7 @@ import (
"github.com/ethereum/go-ethereum/event" "github.com/ethereum/go-ethereum/event"
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/rollup/fees"
) )
const ( const (
@ -379,11 +380,25 @@ func (pool *TxPool) validateTx(ctx context.Context, tx *types.Transaction) error
return txpool.ErrNegativeValue return txpool.ErrNegativeValue
} }
// 1. Check balance >= transaction cost (V + GP * GL) to maintain compatibility with the logic without considering L1 data fee.
// Transactor should have enough funds to cover the costs // Transactor should have enough funds to cover the costs
// cost == V + GP * GL // cost == V + GP * GL
if b := currentState.GetBalance(from); b.Cmp(tx.Cost()) < 0 { if b := currentState.GetBalance(from); b.Cmp(tx.Cost()) < 0 {
return core.ErrInsufficientFunds return core.ErrInsufficientFunds
} }
// 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)
if err != nil {
return fmt.Errorf("failed to calculate L1 data fee, err: %w", err)
}
// Transactor should have enough funds to cover the costs
// cost == L1 data fee + V + GP * GL
if b := currentState.GetBalance(from); b.Cmp(new(big.Int).Add(tx.Cost(), l1DataFee)) < 0 {
return fmt.Errorf("invalid transaction: %w", core.ErrInsufficientFundsWithL1DataFee)
}
}
// Should supply enough intrinsic gas // Should supply enough intrinsic gas
gas, err := core.IntrinsicGas(tx.Data(), tx.AccessList(), tx.To() == nil, true, pool.istanbul, pool.shanghai) gas, err := core.IntrinsicGas(tx.Data(), tx.AccessList(), tx.To() == nil, true, pool.istanbul, pool.shanghai)

View file

@ -345,6 +345,9 @@ type ScrollConfig struct {
// Maximum tx payload size of blocks that we produce [optional] // Maximum tx payload size of blocks that we produce [optional]
MaxTxPayloadBytesPerBlock *int `json:"maxTxPayloadBytesPerBlock,omitempty"` MaxTxPayloadBytesPerBlock *int `json:"maxTxPayloadBytesPerBlock,omitempty"`
// Transaction fee vault address [optional]
FeeVaultAddress *common.Address `json:"feeVaultAddress,omitempty"`
// L1 config // L1 config
L1Config *L1Config `json:"l1Config,omitempty"` L1Config *L1Config `json:"l1Config,omitempty"`
} }
@ -366,6 +369,10 @@ func (c *L1Config) String() string {
c.L1ChainId, c.L1MessageQueueAddress.Hex(), c.NumL1MessagesPerBlock, c.ScrollChainAddress.Hex()) c.L1ChainId, c.L1MessageQueueAddress.Hex(), c.NumL1MessagesPerBlock, c.ScrollChainAddress.Hex())
} }
func (s ScrollConfig) FeeVaultEnabled() bool {
return s.FeeVaultAddress != nil
}
func (s ScrollConfig) ShouldIncludeL1Messages() bool { func (s ScrollConfig) ShouldIncludeL1Messages() bool {
return s.L1Config != nil && s.L1Config.NumL1MessagesPerBlock > 0 return s.L1Config != nil && s.L1Config.NumL1MessagesPerBlock > 0
} }

View file

@ -2,8 +2,6 @@ package fees
import ( import (
"bytes" "bytes"
"errors"
"fmt"
"math/big" "math/big"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
@ -25,17 +23,17 @@ var (
// It should be a subset of the methods found on // It should be a subset of the methods found on
// types.Message // types.Message
type Message interface { type Message interface {
From() common.Address GetFrom() common.Address
To() *common.Address GetTo() *common.Address
GasPrice() *big.Int GetGasPrice() *big.Int
Gas() uint64 GetGasLimit() uint64
GasFeeCap() *big.Int GetGasFeeCap() *big.Int
GasTipCap() *big.Int GetGasTipCap() *big.Int
Value() *big.Int GetValue() *big.Int
Nonce() uint64 GetNonce() uint64
Data() []byte GetData() []byte
AccessList() types.AccessList GetAccessList() types.AccessList
IsL1MessageTx() bool GetIsL1MessageTx() bool
} }
// StateDB represents the StateDB interface // StateDB represents the StateDB interface
@ -46,7 +44,7 @@ type StateDB interface {
} }
func EstimateL1DataFeeForMessage(msg Message, baseFee, chainID *big.Int, signer types.Signer, state StateDB) (*big.Int, error) { func EstimateL1DataFeeForMessage(msg Message, baseFee, chainID *big.Int, signer types.Signer, state StateDB) (*big.Int, error) {
if msg.IsL1MessageTx() { if msg.GetIsL1MessageTx() {
return big.NewInt(0), nil return big.NewInt(0), nil
} }
@ -70,7 +68,7 @@ func EstimateL1DataFeeForMessage(msg Message, baseFee, chainID *big.Int, signer
// asUnsignedTx turns a Message into a types.Transaction // asUnsignedTx turns a Message into a types.Transaction
func asUnsignedTx(msg Message, baseFee, chainID *big.Int) *types.Transaction { func asUnsignedTx(msg Message, baseFee, chainID *big.Int) *types.Transaction {
if baseFee == nil { if baseFee == nil {
if msg.AccessList() == nil { if msg.GetAccessList() == nil {
return asUnsignedLegacyTx(msg) return asUnsignedLegacyTx(msg)
} }
@ -82,38 +80,38 @@ func asUnsignedTx(msg Message, baseFee, chainID *big.Int) *types.Transaction {
func asUnsignedLegacyTx(msg Message) *types.Transaction { func asUnsignedLegacyTx(msg Message) *types.Transaction {
return types.NewTx(&types.LegacyTx{ return types.NewTx(&types.LegacyTx{
Nonce: msg.Nonce(), Nonce: msg.GetNonce(),
To: msg.To(), To: msg.GetTo(),
Value: msg.Value(), Value: msg.GetValue(),
Gas: msg.Gas(), Gas: msg.GetGasLimit(),
GasPrice: msg.GasPrice(), GasPrice: msg.GetGasPrice(),
Data: msg.Data(), Data: msg.GetData(),
}) })
} }
func asUnsignedAccessListTx(msg Message, chainID *big.Int) *types.Transaction { func asUnsignedAccessListTx(msg Message, chainID *big.Int) *types.Transaction {
return types.NewTx(&types.AccessListTx{ return types.NewTx(&types.AccessListTx{
Nonce: msg.Nonce(), Nonce: msg.GetNonce(),
To: msg.To(), To: msg.GetTo(),
Value: msg.Value(), Value: msg.GetValue(),
Gas: msg.Gas(), Gas: msg.GetGasLimit(),
GasPrice: msg.GasPrice(), GasPrice: msg.GetGasPrice(),
Data: msg.Data(), Data: msg.GetData(),
AccessList: msg.AccessList(), AccessList: msg.GetAccessList(),
ChainID: chainID, ChainID: chainID,
}) })
} }
func asUnsignedDynamicTx(msg Message, chainID *big.Int) *types.Transaction { func asUnsignedDynamicTx(msg Message, chainID *big.Int) *types.Transaction {
return types.NewTx(&types.DynamicFeeTx{ return types.NewTx(&types.DynamicFeeTx{
Nonce: msg.Nonce(), Nonce: msg.GetNonce(),
To: msg.To(), To: msg.GetTo(),
Value: msg.Value(), Value: msg.GetValue(),
Gas: msg.Gas(), Gas: msg.GetGasLimit(),
GasFeeCap: msg.GasFeeCap(), GasFeeCap: msg.GetGasFeeCap(),
GasTipCap: msg.GasTipCap(), GasTipCap: msg.GetGasTipCap(),
Data: msg.Data(), Data: msg.GetData(),
AccessList: msg.AccessList(), AccessList: msg.GetAccessList(),
ChainID: chainID, ChainID: chainID,
}) })
} }
@ -189,37 +187,3 @@ func CalculateL1DataFee(tx *types.Transaction, state StateDB) (*big.Int, error)
l1DataFee := calculateEncodedL1DataFee(raw, overhead, l1BaseFee, scalar) l1DataFee := calculateEncodedL1DataFee(raw, overhead, l1BaseFee, scalar)
return l1DataFee, nil return l1DataFee, nil
} }
func calculateL2Fee(tx *types.Transaction) *big.Int {
l2GasLimit := new(big.Int).SetUint64(tx.Gas())
return new(big.Int).Mul(tx.GasPrice(), l2GasLimit)
}
func VerifyFee(signer types.Signer, tx *types.Transaction, state StateDB) error {
from, err := types.Sender(signer, tx)
if err != nil {
return errors.New("invalid transaction: invalid sender")
}
balance := state.GetBalance(from)
l2Fee := calculateL2Fee(tx)
l1DataFee, err := CalculateL1DataFee(tx, state)
if err != nil {
return fmt.Errorf("invalid transaction: %w", err)
}
cost := tx.Value()
cost = cost.Add(cost, l2Fee)
if balance.Cmp(cost) < 0 {
return errors.New("invalid transaction: insufficient funds for gas * price + value")
}
cost = cost.Add(cost, l1DataFee)
if balance.Cmp(cost) < 0 {
return errors.New("invalid transaction: insufficient funds for l1fee + gas * price + value")
}
// TODO: check GasPrice is in an expected range
return nil
}

View file

@ -38,6 +38,7 @@ import (
"github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/ethdb"
"github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/rlp" "github.com/ethereum/go-ethereum/rlp"
"github.com/ethereum/go-ethereum/rollup/fees"
"github.com/ethereum/go-ethereum/trie" "github.com/ethereum/go-ethereum/trie"
"github.com/ethereum/go-ethereum/trie/triedb/hashdb" "github.com/ethereum/go-ethereum/trie/triedb/hashdb"
"github.com/ethereum/go-ethereum/trie/triedb/pathdb" "github.com/ethereum/go-ethereum/trie/triedb/pathdb"
@ -254,9 +255,9 @@ func (t *StateTest) RunNoVerify(subtest StateSubtest, vmconfig vm.Config, snapsh
return nil, nil, nil, common.Hash{}, err return nil, nil, nil, common.Hash{}, err
} }
var ttx types.Transaction
// Try to recover tx with current signer // Try to recover tx with current signer
if len(post.TxBytes) != 0 { if len(post.TxBytes) != 0 {
var ttx types.Transaction
err := ttx.UnmarshalBinary(post.TxBytes) err := ttx.UnmarshalBinary(post.TxBytes)
if err != nil { if err != nil {
triedb.Close() triedb.Close()
@ -289,7 +290,11 @@ func (t *StateTest) RunNoVerify(subtest StateSubtest, vmconfig vm.Config, snapsh
snapshot := statedb.Snapshot() snapshot := statedb.Snapshot()
gaspool := new(core.GasPool) gaspool := new(core.GasPool)
gaspool.AddGas(block.GasLimit()) gaspool.AddGas(block.GasLimit())
_, err = core.ApplyMessage(evm, msg, gaspool) l1DataFee, err := fees.CalculateL1DataFee(&ttx, statedb)
if err != nil {
return nil, nil, nil, common.Hash{}, err
}
_, err = core.ApplyMessage(evm, msg, gaspool, l1DataFee)
if err != nil { if err != nil {
statedb.RevertToSnapshot(snapshot) statedb.RevertToSnapshot(snapshot)
} }