mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-08-19 10:22:23 +00:00
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:
parent
adfec9a0a3
commit
07d5fddde8
27 changed files with 362 additions and 108 deletions
|
|
@ -42,6 +42,7 @@ import (
|
|||
"github.com/ethereum/go-ethereum/event"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
"github.com/ethereum/go-ethereum/params"
|
||||
"github.com/ethereum/go-ethereum/rollup/fees"
|
||||
"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)
|
||||
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(), 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.
|
||||
|
|
|
|||
|
|
@ -35,6 +35,7 @@ import (
|
|||
"github.com/ethereum/go-ethereum/log"
|
||||
"github.com/ethereum/go-ethereum/params"
|
||||
"github.com/ethereum/go-ethereum/rlp"
|
||||
"github.com/ethereum/go-ethereum/rollup/fees"
|
||||
"github.com/ethereum/go-ethereum/trie"
|
||||
"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)
|
||||
|
||||
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)
|
||||
msgResult, err := core.ApplyMessage(evm, msg, gaspool)
|
||||
msgResult, err := core.ApplyMessage(evm, msg, gaspool, l1DataFee)
|
||||
if err != nil {
|
||||
statedb.RevertToSnapshot(snapshot)
|
||||
log.Info("rejected tx", "index", i, "hash", tx.Hash(), "from", msg.From, "error", err)
|
||||
|
|
|
|||
|
|
@ -74,6 +74,10 @@ var (
|
|||
// is higher than the balance of the user's account.
|
||||
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 = errors.New("gas uint64 overflow")
|
||||
|
||||
|
|
|
|||
|
|
@ -688,6 +688,7 @@ type storedReceiptRLP struct {
|
|||
PostStateOrStatus []byte
|
||||
CumulativeGasUsed uint64
|
||||
Logs []*types.Log
|
||||
L1Fee *big.Int
|
||||
}
|
||||
|
||||
// ReceiptLogs is a barebone version of ReceiptForStorage which only keeps
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@
|
|||
package core
|
||||
|
||||
import (
|
||||
"math/big"
|
||||
"sync/atomic"
|
||||
|
||||
"github.com/ethereum/go-ethereum/consensus"
|
||||
|
|
@ -24,6 +25,7 @@ import (
|
|||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/core/vm"
|
||||
"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
|
||||
|
|
@ -68,7 +70,13 @@ func (p *statePrefetcher) Prefetch(block *types.Block, statedb *state.StateDB, c
|
|||
return // Also invalid block, bail out
|
||||
}
|
||||
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
|
||||
}
|
||||
// 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
|
||||
// and uses the input parameters for its environment. The goal is not to execute
|
||||
// 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.
|
||||
evm.Reset(NewEVMTxContext(msg), statedb)
|
||||
// Add addresses to access list if applicable
|
||||
_, err := ApplyMessage(evm, msg, gaspool)
|
||||
_, err := ApplyMessage(evm, msg, gaspool, l1DataFee)
|
||||
return err
|
||||
}
|
||||
|
|
|
|||
|
|
@ -29,6 +29,7 @@ import (
|
|||
"github.com/ethereum/go-ethereum/core/vm"
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
"github.com/ethereum/go-ethereum/params"
|
||||
"github.com/ethereum/go-ethereum/rollup/fees"
|
||||
)
|
||||
|
||||
// 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)
|
||||
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).
|
||||
result, err := ApplyMessage(evm, msg, gp)
|
||||
result, err := ApplyMessage(evm, msg, gp, l1DataFee)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -151,6 +157,7 @@ func applyTransaction(msg *Message, config *params.ChainConfig, gp *GasPool, sta
|
|||
receipt.BlockHash = blockHash
|
||||
receipt.BlockNumber = blockNumber
|
||||
receipt.TransactionIndex = uint(statedb.TxIndex())
|
||||
receipt.L1Fee = result.L1DataFee
|
||||
return receipt, err
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ import (
|
|||
cmath "github.com/ethereum/go-ethereum/common/math"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/core/vm"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
"github.com/ethereum/go-ethereum/params"
|
||||
)
|
||||
|
||||
|
|
@ -149,6 +150,18 @@ type Message struct {
|
|||
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.
|
||||
func TransactionToMessage(tx *types.Transaction, s types.Signer, baseFee *big.Int) (*Message, error) {
|
||||
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
|
||||
// indicates a core error meaning that the message would always fail for that particular
|
||||
// state and would never be accepted within a block.
|
||||
func ApplyMessage(evm *vm.EVM, msg *Message, gp *GasPool) (*ExecutionResult, error) {
|
||||
return NewStateTransition(evm, msg, gp).TransitionDb()
|
||||
func ApplyMessage(evm *vm.EVM, msg *Message, gp *GasPool, l1DataFee *big.Int) (*ExecutionResult, error) {
|
||||
return NewStateTransition(evm, msg, gp, l1DataFee).TransitionDb()
|
||||
}
|
||||
|
||||
// StateTransition represents a state transition.
|
||||
|
|
@ -215,15 +228,18 @@ type StateTransition struct {
|
|||
initialGas uint64
|
||||
state vm.StateDB
|
||||
evm *vm.EVM
|
||||
|
||||
l1DataFee *big.Int
|
||||
}
|
||||
|
||||
// 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{
|
||||
gp: gp,
|
||||
evm: evm,
|
||||
msg: msg,
|
||||
state: evm.StateDB,
|
||||
gp: gp,
|
||||
evm: evm,
|
||||
msg: msg,
|
||||
state: evm.StateDB,
|
||||
l1DataFee: l1DataFee,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -238,11 +254,28 @@ func (st *StateTransition) to() common.Address {
|
|||
func (st *StateTransition) buyGas() error {
|
||||
mgval := new(big.Int).SetUint64(st.msg.GasLimit)
|
||||
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)
|
||||
if st.msg.GasFeeCap != nil {
|
||||
balanceCheck.SetUint64(st.msg.GasLimit)
|
||||
balanceCheck = balanceCheck.Mul(balanceCheck, st.msg.GasFeeCap)
|
||||
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 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
|
||||
// the coinbase when simulating calls.
|
||||
} 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.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{
|
||||
L1DataFee: st.l1DataFee,
|
||||
UsedGas: st.gasUsed(),
|
||||
Err: vmerr,
|
||||
ReturnData: ret,
|
||||
|
|
|
|||
|
|
@ -28,6 +28,7 @@ import (
|
|||
"github.com/ethereum/go-ethereum/crypto/kzg4844"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
"github.com/ethereum/go-ethereum/params"
|
||||
"github.com/ethereum/go-ethereum/rollup/fees"
|
||||
)
|
||||
|
||||
// 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)
|
||||
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 {
|
||||
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
|
||||
// expansions without overdrafts
|
||||
spent := opts.ExistingExpenditure(from)
|
||||
|
|
|
|||
|
|
@ -31,6 +31,7 @@ func (r Receipt) MarshalJSON() ([]byte, error) {
|
|||
BlockHash common.Hash `json:"blockHash,omitempty"`
|
||||
BlockNumber *hexutil.Big `json:"blockNumber,omitempty"`
|
||||
TransactionIndex hexutil.Uint `json:"transactionIndex"`
|
||||
L1Fee *hexutil.Big `json:"l1Fee,omitempty"`
|
||||
}
|
||||
var enc Receipt
|
||||
enc.Type = hexutil.Uint64(r.Type)
|
||||
|
|
@ -48,6 +49,7 @@ func (r Receipt) MarshalJSON() ([]byte, error) {
|
|||
enc.BlockHash = r.BlockHash
|
||||
enc.BlockNumber = (*hexutil.Big)(r.BlockNumber)
|
||||
enc.TransactionIndex = hexutil.Uint(r.TransactionIndex)
|
||||
enc.L1Fee = (*hexutil.Big)(r.L1Fee)
|
||||
return json.Marshal(&enc)
|
||||
}
|
||||
|
||||
|
|
@ -69,6 +71,7 @@ func (r *Receipt) UnmarshalJSON(input []byte) error {
|
|||
BlockHash *common.Hash `json:"blockHash,omitempty"`
|
||||
BlockNumber *hexutil.Big `json:"blockNumber,omitempty"`
|
||||
TransactionIndex *hexutil.Uint `json:"transactionIndex"`
|
||||
L1Fee *hexutil.Big `json:"l1Fee,omitempty"`
|
||||
}
|
||||
var dec Receipt
|
||||
if err := json.Unmarshal(input, &dec); err != nil {
|
||||
|
|
@ -124,5 +127,8 @@ func (r *Receipt) UnmarshalJSON(input []byte) error {
|
|||
if dec.TransactionIndex != nil {
|
||||
r.TransactionIndex = uint(*dec.TransactionIndex)
|
||||
}
|
||||
if dec.L1Fee != nil {
|
||||
r.L1Fee = (*big.Int)(dec.L1Fee)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -71,6 +71,9 @@ type Receipt struct {
|
|||
BlockHash common.Hash `json:"blockHash,omitempty"`
|
||||
BlockNumber *big.Int `json:"blockNumber,omitempty"`
|
||||
TransactionIndex uint `json:"transactionIndex"`
|
||||
|
||||
// Scroll rollup
|
||||
L1Fee *big.Int `json:"l1Fee,omitempty"`
|
||||
}
|
||||
|
||||
type receiptMarshaling struct {
|
||||
|
|
@ -84,6 +87,7 @@ type receiptMarshaling struct {
|
|||
BlobGasPrice *hexutil.Big
|
||||
BlockNumber *hexutil.Big
|
||||
TransactionIndex hexutil.Uint
|
||||
L1Fee *hexutil.Big
|
||||
}
|
||||
|
||||
// receiptRLP is the consensus encoding of a receipt.
|
||||
|
|
@ -99,6 +103,7 @@ type storedReceiptRLP struct {
|
|||
PostStateOrStatus []byte
|
||||
CumulativeGasUsed uint64
|
||||
Logs []*Log
|
||||
L1Fee *big.Int
|
||||
}
|
||||
|
||||
// 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
|
||||
// into an RLP stream.
|
||||
func (r *ReceiptForStorage) EncodeRLP(_w io.Writer) error {
|
||||
if r.L1Fee == nil {
|
||||
r.L1Fee = big.NewInt(0)
|
||||
}
|
||||
|
||||
w := rlp.NewEncoderBuffer(_w)
|
||||
outerList := w.List()
|
||||
w.WriteBytes((*Receipt)(r).statusEncoding())
|
||||
|
|
@ -275,6 +284,7 @@ func (r *ReceiptForStorage) EncodeRLP(_w io.Writer) error {
|
|||
}
|
||||
}
|
||||
w.ListEnd(logList)
|
||||
w.WriteBigInt(r.L1Fee)
|
||||
w.ListEnd(outerList)
|
||||
return w.Flush()
|
||||
}
|
||||
|
|
@ -292,6 +302,7 @@ func (r *ReceiptForStorage) DecodeRLP(s *rlp.Stream) error {
|
|||
r.CumulativeGasUsed = stored.CumulativeGasUsed
|
||||
r.Logs = stored.Logs
|
||||
r.Bloom = CreateBloom(Receipts{(*Receipt)(r)})
|
||||
r.L1Fee = stored.L1Fee
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
// will `VerifyFee` & `validateTx` in txPool.Add
|
||||
return b.eth.txPool.Add([]*types.Transaction{signedTx}, true, false)[0]
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -30,6 +30,7 @@ import (
|
|||
"github.com/ethereum/go-ethereum/core/vm"
|
||||
"github.com/ethereum/go-ethereum/eth/tracers"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
"github.com/ethereum/go-ethereum/rollup/fees"
|
||||
"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
|
||||
vmenv := vm.NewEVM(context, txContext, statedb, eth.blockchain.Config(), vm.Config{})
|
||||
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)
|
||||
}
|
||||
// Ensure any modifications are committed to the state
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ import (
|
|||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math/big"
|
||||
"os"
|
||||
"runtime"
|
||||
"sync"
|
||||
|
|
@ -41,6 +42,7 @@ import (
|
|||
"github.com/ethereum/go-ethereum/log"
|
||||
"github.com/ethereum/go-ethereum/params"
|
||||
"github.com/ethereum/go-ethereum/rlp"
|
||||
"github.com/ethereum/go-ethereum/rollup/fees"
|
||||
"github.com/ethereum/go-ethereum/rpc"
|
||||
)
|
||||
|
||||
|
|
@ -276,7 +278,14 @@ func (api *API) traceChain(start, end *types.Block, config *TraceConfig, closed
|
|||
TxIndex: i,
|
||||
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 {
|
||||
task.results[i] = &txTraceResult{TxHash: tx.Hash(), Error: err.Error()}
|
||||
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{})
|
||||
)
|
||||
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)
|
||||
// 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
|
||||
|
|
@ -611,7 +625,11 @@ func (api *API) traceBlock(ctx context.Context, block *types.Block, config *Trac
|
|||
TxIndex: i,
|
||||
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 {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -654,7 +672,13 @@ func (api *API) traceBlockParallel(ctx context.Context, block *types.Block, stat
|
|||
TxIndex: task.index,
|
||||
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 {
|
||||
results[task.index] = &txTraceResult{TxHash: txs[task.index].Hash(), Error: err.Error()}
|
||||
continue
|
||||
|
|
@ -681,7 +705,12 @@ txloop:
|
|||
msg, _ := core.TransactionToMessage(tx, signer, block.BaseFee())
|
||||
statedb.SetTxContext(tx.Hash(), i)
|
||||
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
|
||||
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
|
||||
vmenv := vm.NewEVM(vmctx, txContext, statedb, chainConfig, vmConf)
|
||||
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 {
|
||||
writer.Flush()
|
||||
}
|
||||
|
|
@ -857,7 +889,11 @@ func (api *API) TraceTransaction(ctx context.Context, hash common.Hash, config *
|
|||
TxIndex: int(index),
|
||||
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
|
||||
|
|
@ -916,13 +952,18 @@ func (api *API) TraceCall(ctx context.Context, args ethapi.TransactionArgs, bloc
|
|||
if config != nil {
|
||||
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
|
||||
// executes the given message in the provided environment. The return value will
|
||||
// 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 (
|
||||
tracer Tracer
|
||||
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})
|
||||
|
||||
// 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
|
||||
if config.Timeout != 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
|
||||
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 tracer.GetResult()
|
||||
|
|
|
|||
|
|
@ -42,6 +42,7 @@ import (
|
|||
"github.com/ethereum/go-ethereum/ethdb"
|
||||
"github.com/ethereum/go-ethereum/internal/ethapi"
|
||||
"github.com/ethereum/go-ethereum/params"
|
||||
"github.com/ethereum/go-ethereum/rollup/fees"
|
||||
"github.com/ethereum/go-ethereum/rpc"
|
||||
"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
|
||||
}
|
||||
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)
|
||||
}
|
||||
statedb.Finalise(vmenv.ChainConfig().IsEIP158(block.Number()))
|
||||
|
|
|
|||
|
|
@ -34,6 +34,7 @@ import (
|
|||
"github.com/ethereum/go-ethereum/eth/tracers"
|
||||
"github.com/ethereum/go-ethereum/params"
|
||||
"github.com/ethereum/go-ethereum/rlp"
|
||||
"github.com/ethereum/go-ethereum/rollup/fees"
|
||||
"github.com/ethereum/go-ethereum/tests"
|
||||
)
|
||||
|
||||
|
|
@ -150,7 +151,11 @@ func testCallTracer(tracerName string, dirPath string, t *testing.T) {
|
|||
if err != nil {
|
||||
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 {
|
||||
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})
|
||||
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 {
|
||||
b.Fatalf("failed to execute transaction: %v", err)
|
||||
}
|
||||
|
|
@ -388,7 +397,12 @@ func TestInternals(t *testing.T) {
|
|||
GasTipCap: big.NewInt(0),
|
||||
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 {
|
||||
t.Fatalf("test %v: failed to execute transaction: %v", tc.name, err)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ import (
|
|||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/core/vm"
|
||||
"github.com/ethereum/go-ethereum/rlp"
|
||||
"github.com/ethereum/go-ethereum/rollup/fees"
|
||||
"github.com/ethereum/go-ethereum/tests"
|
||||
|
||||
// Force-load the native, to trigger registration
|
||||
|
|
@ -114,7 +115,11 @@ func flatCallTracerTestRunner(tracerName string, filename string, dirPath string
|
|||
if err != nil {
|
||||
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 {
|
||||
return fmt.Errorf("failed to execute transaction: %v", err)
|
||||
|
|
|
|||
|
|
@ -30,6 +30,7 @@ import (
|
|||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/core/vm"
|
||||
"github.com/ethereum/go-ethereum/eth/tracers"
|
||||
"github.com/ethereum/go-ethereum/rollup/fees"
|
||||
"github.com/ethereum/go-ethereum/tests"
|
||||
)
|
||||
|
||||
|
|
@ -121,7 +122,11 @@ func testPrestateDiffTracer(tracerName string, dirPath string, t *testing.T) {
|
|||
if err != nil {
|
||||
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 {
|
||||
t.Fatalf("failed to execute transaction: %v", err)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -252,6 +252,7 @@ func (l *StructLogger) GetResult() (json.RawMessage, error) {
|
|||
Failed: failed,
|
||||
ReturnValue: returnVal,
|
||||
StructLogs: formatLogs(l.StructLogs()),
|
||||
// L1DataFee: (*hexutil.Big)(result.L1DataFee),
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -28,6 +28,7 @@ import (
|
|||
"github.com/ethereum/go-ethereum/crypto"
|
||||
"github.com/ethereum/go-ethereum/eth/tracers/logger"
|
||||
"github.com/ethereum/go-ethereum/params"
|
||||
"github.com/ethereum/go-ethereum/rollup/fees"
|
||||
"github.com/ethereum/go-ethereum/tests"
|
||||
)
|
||||
|
||||
|
|
@ -99,7 +100,11 @@ func BenchmarkTransactionTrace(b *testing.B) {
|
|||
|
||||
for i := 0; i < b.N; i++ {
|
||||
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()
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
|
|
|
|||
|
|
@ -45,6 +45,7 @@ import (
|
|||
"github.com/ethereum/go-ethereum/p2p"
|
||||
"github.com/ethereum/go-ethereum/params"
|
||||
"github.com/ethereum/go-ethereum/rlp"
|
||||
"github.com/ethereum/go-ethereum/rollup/fees"
|
||||
"github.com/ethereum/go-ethereum/rpc"
|
||||
"github.com/ethereum/go-ethereum/trie"
|
||||
"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.
|
||||
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 {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -1177,6 +1178,48 @@ func (s *BlockChainAPI) Call(ctx context.Context, args TransactionArgs, blockNrO
|
|||
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
|
||||
// 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.
|
||||
|
|
@ -1244,12 +1287,25 @@ func DoEstimateGas(ctx context.Context, b Backend, args TransactionArgs, blockNr
|
|||
if feeCap.BitLen() != 0 {
|
||||
balance := state.GetBalance(*args.From) // from can't be nil
|
||||
available := new(big.Int).Set(balance)
|
||||
|
||||
// account for tx value
|
||||
if args.Value != nil {
|
||||
if args.Value.ToInt().Cmp(available) >= 0 {
|
||||
return 0, core.ErrInsufficientFundsForTransfer
|
||||
}
|
||||
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)
|
||||
|
||||
// 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)
|
||||
config := vm.Config{Tracer: tracer, NoBaseFee: true}
|
||||
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 {
|
||||
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,
|
||||
"type": hexutil.Uint(tx.Type()),
|
||||
"effectiveGasPrice": (*hexutil.Big)(receipt.EffectiveGasPrice),
|
||||
"l1Fee": (*hexutil.Big)(receipt.L1Fee),
|
||||
}
|
||||
|
||||
// Assign receipt status or post state.
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
// will `VerifyFee` & `validateTx` in txPool.Add
|
||||
return b.eth.txPool.Add(ctx, signedTx)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@ import (
|
|||
"github.com/ethereum/go-ethereum/core/vm"
|
||||
"github.com/ethereum/go-ethereum/eth/tracers"
|
||||
"github.com/ethereum/go-ethereum/light"
|
||||
"github.com/ethereum/go-ethereum/rollup/fees"
|
||||
)
|
||||
|
||||
// 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
|
||||
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)
|
||||
}
|
||||
// Ensure any modifications are committed to the state
|
||||
|
|
|
|||
|
|
@ -36,6 +36,7 @@ import (
|
|||
"github.com/ethereum/go-ethereum/ethdb"
|
||||
"github.com/ethereum/go-ethereum/params"
|
||||
"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/trienode"
|
||||
)
|
||||
|
|
@ -217,7 +218,9 @@ func odrContractCall(ctx context.Context, db ethdb.Database, bc *core.BlockChain
|
|||
context := core.NewEVMBlockContext(header, chain, nil)
|
||||
vmenv := vm.NewEVM(context, txContext, st, config, vm.Config{NoBaseFee: true})
|
||||
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()...)
|
||||
if st.Error() != nil {
|
||||
return res, st.Error()
|
||||
|
|
|
|||
|
|
@ -33,6 +33,7 @@ import (
|
|||
"github.com/ethereum/go-ethereum/event"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
"github.com/ethereum/go-ethereum/params"
|
||||
"github.com/ethereum/go-ethereum/rollup/fees"
|
||||
)
|
||||
|
||||
const (
|
||||
|
|
@ -379,11 +380,25 @@ func (pool *TxPool) validateTx(ctx context.Context, tx *types.Transaction) error
|
|||
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
|
||||
// cost == V + GP * GL
|
||||
if b := currentState.GetBalance(from); b.Cmp(tx.Cost()) < 0 {
|
||||
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
|
||||
gas, err := core.IntrinsicGas(tx.Data(), tx.AccessList(), tx.To() == nil, true, pool.istanbul, pool.shanghai)
|
||||
|
|
|
|||
|
|
@ -345,6 +345,9 @@ type ScrollConfig struct {
|
|||
// Maximum tx payload size of blocks that we produce [optional]
|
||||
MaxTxPayloadBytesPerBlock *int `json:"maxTxPayloadBytesPerBlock,omitempty"`
|
||||
|
||||
// Transaction fee vault address [optional]
|
||||
FeeVaultAddress *common.Address `json:"feeVaultAddress,omitempty"`
|
||||
|
||||
// L1 config
|
||||
L1Config *L1Config `json:"l1Config,omitempty"`
|
||||
}
|
||||
|
|
@ -366,6 +369,10 @@ func (c *L1Config) String() string {
|
|||
c.L1ChainId, c.L1MessageQueueAddress.Hex(), c.NumL1MessagesPerBlock, c.ScrollChainAddress.Hex())
|
||||
}
|
||||
|
||||
func (s ScrollConfig) FeeVaultEnabled() bool {
|
||||
return s.FeeVaultAddress != nil
|
||||
}
|
||||
|
||||
func (s ScrollConfig) ShouldIncludeL1Messages() bool {
|
||||
return s.L1Config != nil && s.L1Config.NumL1MessagesPerBlock > 0
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,8 +2,6 @@ package fees
|
|||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math/big"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
|
|
@ -25,17 +23,17 @@ var (
|
|||
// It should be a subset of the methods found on
|
||||
// types.Message
|
||||
type Message interface {
|
||||
From() common.Address
|
||||
To() *common.Address
|
||||
GasPrice() *big.Int
|
||||
Gas() uint64
|
||||
GasFeeCap() *big.Int
|
||||
GasTipCap() *big.Int
|
||||
Value() *big.Int
|
||||
Nonce() uint64
|
||||
Data() []byte
|
||||
AccessList() types.AccessList
|
||||
IsL1MessageTx() bool
|
||||
GetFrom() common.Address
|
||||
GetTo() *common.Address
|
||||
GetGasPrice() *big.Int
|
||||
GetGasLimit() uint64
|
||||
GetGasFeeCap() *big.Int
|
||||
GetGasTipCap() *big.Int
|
||||
GetValue() *big.Int
|
||||
GetNonce() uint64
|
||||
GetData() []byte
|
||||
GetAccessList() types.AccessList
|
||||
GetIsL1MessageTx() bool
|
||||
}
|
||||
|
||||
// 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) {
|
||||
if msg.IsL1MessageTx() {
|
||||
if msg.GetIsL1MessageTx() {
|
||||
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
|
||||
func asUnsignedTx(msg Message, baseFee, chainID *big.Int) *types.Transaction {
|
||||
if baseFee == nil {
|
||||
if msg.AccessList() == nil {
|
||||
if msg.GetAccessList() == nil {
|
||||
return asUnsignedLegacyTx(msg)
|
||||
}
|
||||
|
||||
|
|
@ -82,38 +80,38 @@ func asUnsignedTx(msg Message, baseFee, chainID *big.Int) *types.Transaction {
|
|||
|
||||
func asUnsignedLegacyTx(msg Message) *types.Transaction {
|
||||
return types.NewTx(&types.LegacyTx{
|
||||
Nonce: msg.Nonce(),
|
||||
To: msg.To(),
|
||||
Value: msg.Value(),
|
||||
Gas: msg.Gas(),
|
||||
GasPrice: msg.GasPrice(),
|
||||
Data: msg.Data(),
|
||||
Nonce: msg.GetNonce(),
|
||||
To: msg.GetTo(),
|
||||
Value: msg.GetValue(),
|
||||
Gas: msg.GetGasLimit(),
|
||||
GasPrice: msg.GetGasPrice(),
|
||||
Data: msg.GetData(),
|
||||
})
|
||||
}
|
||||
|
||||
func asUnsignedAccessListTx(msg Message, chainID *big.Int) *types.Transaction {
|
||||
return types.NewTx(&types.AccessListTx{
|
||||
Nonce: msg.Nonce(),
|
||||
To: msg.To(),
|
||||
Value: msg.Value(),
|
||||
Gas: msg.Gas(),
|
||||
GasPrice: msg.GasPrice(),
|
||||
Data: msg.Data(),
|
||||
AccessList: msg.AccessList(),
|
||||
Nonce: msg.GetNonce(),
|
||||
To: msg.GetTo(),
|
||||
Value: msg.GetValue(),
|
||||
Gas: msg.GetGasLimit(),
|
||||
GasPrice: msg.GetGasPrice(),
|
||||
Data: msg.GetData(),
|
||||
AccessList: msg.GetAccessList(),
|
||||
ChainID: chainID,
|
||||
})
|
||||
}
|
||||
|
||||
func asUnsignedDynamicTx(msg Message, chainID *big.Int) *types.Transaction {
|
||||
return types.NewTx(&types.DynamicFeeTx{
|
||||
Nonce: msg.Nonce(),
|
||||
To: msg.To(),
|
||||
Value: msg.Value(),
|
||||
Gas: msg.Gas(),
|
||||
GasFeeCap: msg.GasFeeCap(),
|
||||
GasTipCap: msg.GasTipCap(),
|
||||
Data: msg.Data(),
|
||||
AccessList: msg.AccessList(),
|
||||
Nonce: msg.GetNonce(),
|
||||
To: msg.GetTo(),
|
||||
Value: msg.GetValue(),
|
||||
Gas: msg.GetGasLimit(),
|
||||
GasFeeCap: msg.GetGasFeeCap(),
|
||||
GasTipCap: msg.GetGasTipCap(),
|
||||
Data: msg.GetData(),
|
||||
AccessList: msg.GetAccessList(),
|
||||
ChainID: chainID,
|
||||
})
|
||||
}
|
||||
|
|
@ -189,37 +187,3 @@ func CalculateL1DataFee(tx *types.Transaction, state StateDB) (*big.Int, error)
|
|||
l1DataFee := calculateEncodedL1DataFee(raw, overhead, l1BaseFee, scalar)
|
||||
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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -38,6 +38,7 @@ import (
|
|||
"github.com/ethereum/go-ethereum/ethdb"
|
||||
"github.com/ethereum/go-ethereum/params"
|
||||
"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/triedb/hashdb"
|
||||
"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
|
||||
}
|
||||
|
||||
var ttx types.Transaction
|
||||
// Try to recover tx with current signer
|
||||
if len(post.TxBytes) != 0 {
|
||||
var ttx types.Transaction
|
||||
err := ttx.UnmarshalBinary(post.TxBytes)
|
||||
if err != nil {
|
||||
triedb.Close()
|
||||
|
|
@ -289,7 +290,11 @@ func (t *StateTest) RunNoVerify(subtest StateSubtest, vmconfig vm.Config, snapsh
|
|||
snapshot := statedb.Snapshot()
|
||||
gaspool := new(core.GasPool)
|
||||
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 {
|
||||
statedb.RevertToSnapshot(snapshot)
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue