From 4588569b636c419d954e4901573e47ab1bbbdfc6 Mon Sep 17 00:00:00 2001 From: HAOYUatHZ <37070449+HAOYUatHZ@users.noreply.github.com> Date: Thu, 2 Mar 2023 16:38:43 +0800 Subject: [PATCH] feat: `VerifyFee` for `sendTx` and `txpool.add` (#236) * init * rework * handle `LesApiBackend` * fix tests * add L1fee even for 0 gasPrice * update comments --- core/tx_pool.go | 9 ++++++ core/tx_pool_test.go | 62 ++++++++++++++++++++++++--------------- eth/api_backend.go | 1 + les/api_backend.go | 1 + light/txpool.go | 8 +++++ params/config.go | 2 ++ rollup/fees/rollup_fee.go | 60 +++++++++++++++++++++++++++++++++++++ 7 files changed, 119 insertions(+), 24 deletions(-) diff --git a/core/tx_pool.go b/core/tx_pool.go index 506526561d..cbd0d4e523 100644 --- a/core/tx_pool.go +++ b/core/tx_pool.go @@ -34,6 +34,7 @@ import ( "github.com/scroll-tech/go-ethereum/log" "github.com/scroll-tech/go-ethereum/metrics" "github.com/scroll-tech/go-ethereum/params" + "github.com/scroll-tech/go-ethereum/rollup/fees" ) const ( @@ -681,6 +682,14 @@ func (pool *TxPool) add(tx *types.Transaction, local bool) (replaced bool, err e // the sender is marked as local previously, treat it as the local transaction. isLocal := local || pool.locals.containsTx(tx) + if pool.chainconfig.UsingScroll { + if err := fees.VerifyFee(pool.signer, tx, pool.currentState); err != nil { + log.Trace("Discarding insufficient l1fee transaction", "hash", hash, "err", err) + invalidTxMeter.Mark(1) + return false, err + } + } + // If the transaction fails basic validation, discard it if err := pool.validateTx(tx, isLocal); err != nil { log.Trace("Discarding invalid transaction", "hash", hash, "err", err) diff --git a/core/tx_pool_test.go b/core/tx_pool_test.go index 5b8978ed58..3a701cce25 100644 --- a/core/tx_pool_test.go +++ b/core/tx_pool_test.go @@ -43,18 +43,32 @@ var ( // sideeffects used during testing. testTxPoolConfig TxPoolConfig + // noL1feeConfig is a chain config without L1fee enabled. + noL1feeConfig *params.ChainConfig + // eip1559Config is a chain config with EIP-1559 enabled at block 0. eip1559Config *params.ChainConfig + + // eip1559NoL1feeConfig is a chain config with EIP-1559 enabled at block 0 but not enabling L1fee. + eip1559NoL1feeConfig *params.ChainConfig ) func init() { testTxPoolConfig = DefaultTxPoolConfig testTxPoolConfig.Journal = "" - cpy := *params.TestChainConfig - eip1559Config = &cpy + cpy0 := *params.TestNoL1feeChainConfig + noL1feeConfig = &cpy0 + + cpy1 := *params.TestChainConfig + eip1559Config = &cpy1 eip1559Config.BerlinBlock = common.Big0 eip1559Config.LondonBlock = common.Big0 + + cpy2 := *params.TestNoL1feeChainConfig + eip1559NoL1feeConfig = &cpy2 + eip1559NoL1feeConfig.BerlinBlock = common.Big0 + eip1559NoL1feeConfig.LondonBlock = common.Big0 } type testBlockChain struct { @@ -276,7 +290,7 @@ func testSetNonce(pool *TxPool, addr common.Address, nonce uint64) { func TestInvalidTransactions(t *testing.T) { t.Parallel() - pool, key := setupTxPool() + pool, key := setupTxPoolWithConfig(noL1feeConfig) defer pool.Stop() tx := transaction(0, 100, key) @@ -313,7 +327,7 @@ func TestInvalidTransactions(t *testing.T) { func TestTransactionQueue(t *testing.T) { t.Parallel() - pool, key := setupTxPool() + pool, key := setupTxPoolWithConfig(noL1feeConfig) defer pool.Stop() tx := transaction(0, 100, key) @@ -344,7 +358,7 @@ func TestTransactionQueue(t *testing.T) { func TestTransactionQueue2(t *testing.T) { t.Parallel() - pool, key := setupTxPool() + pool, key := setupTxPoolWithConfig(noL1feeConfig) defer pool.Stop() tx1 := transaction(0, 100, key) @@ -370,7 +384,7 @@ func TestTransactionQueue2(t *testing.T) { func TestTransactionNegativeValue(t *testing.T) { t.Parallel() - pool, key := setupTxPool() + pool, key := setupTxPoolWithConfig(noL1feeConfig) defer pool.Stop() tx, _ := types.SignTx(types.NewTransaction(0, common.Address{}, big.NewInt(-1), 100, big.NewInt(1), nil), types.HomesteadSigner{}, key) @@ -384,7 +398,7 @@ func TestTransactionNegativeValue(t *testing.T) { func TestTransactionTipAboveFeeCap(t *testing.T) { t.Parallel() - pool, key := setupTxPoolWithConfig(eip1559Config) + pool, key := setupTxPoolWithConfig(eip1559NoL1feeConfig) defer pool.Stop() tx := dynamicFeeTx(0, 100, big.NewInt(1), big.NewInt(2), key) @@ -397,7 +411,7 @@ func TestTransactionTipAboveFeeCap(t *testing.T) { func TestTransactionVeryHighValues(t *testing.T) { t.Parallel() - pool, key := setupTxPoolWithConfig(eip1559Config) + pool, key := setupTxPoolWithConfig(eip1559NoL1feeConfig) defer pool.Stop() veryBigNumber := big.NewInt(1) @@ -417,7 +431,7 @@ func TestTransactionVeryHighValues(t *testing.T) { func TestTransactionChainFork(t *testing.T) { t.Parallel() - pool, key := setupTxPool() + pool, key := setupTxPoolWithConfig(noL1feeConfig) defer pool.Stop() addr := crypto.PubkeyToAddress(key.PublicKey) @@ -446,7 +460,7 @@ func TestTransactionChainFork(t *testing.T) { func TestTransactionDoubleNonce(t *testing.T) { t.Parallel() - pool, key := setupTxPool() + pool, key := setupTxPoolWithConfig(noL1feeConfig) defer pool.Stop() addr := crypto.PubkeyToAddress(key.PublicKey) @@ -497,7 +511,7 @@ func TestTransactionDoubleNonce(t *testing.T) { func TestTransactionMissingNonce(t *testing.T) { t.Parallel() - pool, key := setupTxPool() + pool, key := setupTxPoolWithConfig(noL1feeConfig) defer pool.Stop() addr := crypto.PubkeyToAddress(key.PublicKey) @@ -521,7 +535,7 @@ func TestTransactionNonceRecovery(t *testing.T) { t.Parallel() const n = 10 - pool, key := setupTxPool() + pool, key := setupTxPoolWithConfig(noL1feeConfig) defer pool.Stop() addr := crypto.PubkeyToAddress(key.PublicKey) @@ -547,7 +561,7 @@ func TestTransactionDropping(t *testing.T) { t.Parallel() // Create a test account and fund it - pool, key := setupTxPool() + pool, key := setupTxPoolWithConfig(noL1feeConfig) defer pool.Stop() account := crypto.PubkeyToAddress(key.PublicKey) @@ -765,7 +779,7 @@ func TestTransactionGapFilling(t *testing.T) { t.Parallel() // Create a test account and fund it - pool, key := setupTxPool() + pool, key := setupTxPoolWithConfig(noL1feeConfig) defer pool.Stop() account := crypto.PubkeyToAddress(key.PublicKey) @@ -819,7 +833,7 @@ func TestTransactionQueueAccountLimiting(t *testing.T) { t.Parallel() // Create a test account and fund it - pool, key := setupTxPool() + pool, key := setupTxPoolWithConfig(noL1feeConfig) defer pool.Stop() account := crypto.PubkeyToAddress(key.PublicKey) @@ -1100,7 +1114,7 @@ func TestTransactionPendingLimiting(t *testing.T) { t.Parallel() // Create a test account and fund it - pool, key := setupTxPool() + pool, key := setupTxPoolWithConfig(noL1feeConfig) defer pool.Stop() account := crypto.PubkeyToAddress(key.PublicKey) @@ -1189,7 +1203,7 @@ func TestTransactionAllowedTxSize(t *testing.T) { t.Parallel() // Create a test account and fund it - pool, key := setupTxPool() + pool, key := setupTxPoolWithConfig(noL1feeConfig) defer pool.Stop() account := crypto.PubkeyToAddress(key.PublicKey) @@ -1449,7 +1463,7 @@ func TestTransactionPoolRepricingDynamicFee(t *testing.T) { t.Parallel() // Create the pool to test the pricing enforcement with - pool, _ := setupTxPoolWithConfig(eip1559Config) + pool, _ := setupTxPoolWithConfig(eip1559NoL1feeConfig) defer pool.Stop() // Keep track of transaction events to ensure all executables get announced @@ -1820,7 +1834,7 @@ func TestTransactionPoolStableUnderpricing(t *testing.T) { func TestTransactionPoolUnderpricingDynamicFee(t *testing.T) { t.Parallel() - pool, _ := setupTxPoolWithConfig(eip1559Config) + pool, _ := setupTxPoolWithConfig(eip1559NoL1feeConfig) defer pool.Stop() pool.config.GlobalSlots = 2 @@ -1927,7 +1941,7 @@ func TestTransactionPoolUnderpricingDynamicFee(t *testing.T) { func TestDualHeapEviction(t *testing.T) { t.Parallel() - pool, _ := setupTxPoolWithConfig(eip1559Config) + pool, _ := setupTxPoolWithConfig(eip1559NoL1feeConfig) defer pool.Stop() pool.config.GlobalSlots = 10 @@ -2130,7 +2144,7 @@ func TestTransactionReplacementDynamicFee(t *testing.T) { t.Parallel() // Create the pool to test the pricing enforcement with - pool, key := setupTxPoolWithConfig(eip1559Config) + pool, key := setupTxPoolWithConfig(eip1559NoL1feeConfig) defer pool.Stop() testAddBalance(pool, crypto.PubkeyToAddress(key.PublicKey), big.NewInt(1000000000)) @@ -2429,7 +2443,7 @@ func BenchmarkPendingDemotion10000(b *testing.B) { benchmarkPendingDemotion(b, 1 func benchmarkPendingDemotion(b *testing.B, size int) { // Add a batch of transactions to a pool one by one - pool, key := setupTxPool() + pool, key := setupTxPoolWithConfig(noL1feeConfig) defer pool.Stop() account := crypto.PubkeyToAddress(key.PublicKey) @@ -2454,7 +2468,7 @@ func BenchmarkFuturePromotion10000(b *testing.B) { benchmarkFuturePromotion(b, 1 func benchmarkFuturePromotion(b *testing.B, size int) { // Add a batch of transactions to a pool one by one - pool, key := setupTxPool() + pool, key := setupTxPoolWithConfig(noL1feeConfig) defer pool.Stop() account := crypto.PubkeyToAddress(key.PublicKey) @@ -2482,7 +2496,7 @@ func BenchmarkPoolBatchLocalInsert10000(b *testing.B) { benchmarkPoolBatchInsert func benchmarkPoolBatchInsert(b *testing.B, size int, local bool) { // Generate a batch of transactions to enqueue into the pool - pool, key := setupTxPool() + pool, key := setupTxPoolWithConfig(noL1feeConfig) defer pool.Stop() account := crypto.PubkeyToAddress(key.PublicKey) diff --git a/eth/api_backend.go b/eth/api_backend.go index cfabc499a1..9be299ad39 100644 --- a/eth/api_backend.go +++ b/eth/api_backend.go @@ -240,6 +240,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.AddLocal return b.eth.txPool.AddLocal(signedTx) } diff --git a/les/api_backend.go b/les/api_backend.go index 886117feb8..b2bf572d85 100644 --- a/les/api_backend.go +++ b/les/api_backend.go @@ -196,6 +196,7 @@ func (b *LesApiBackend) GetEVM(ctx context.Context, msg core.Message, state *sta } func (b *LesApiBackend) SendTx(ctx context.Context, signedTx *types.Transaction) error { + // will `VerifyFee` & `validateTx` in txPool.Add return b.eth.txPool.Add(ctx, signedTx) } diff --git a/light/txpool.go b/light/txpool.go index aa2ca7a79b..edfe105f89 100644 --- a/light/txpool.go +++ b/light/txpool.go @@ -32,6 +32,7 @@ import ( "github.com/scroll-tech/go-ethereum/event" "github.com/scroll-tech/go-ethereum/log" "github.com/scroll-tech/go-ethereum/params" + "github.com/scroll-tech/go-ethereum/rollup/fees" ) const ( @@ -400,6 +401,13 @@ func (pool *TxPool) add(ctx context.Context, tx *types.Transaction) error { if pool.pending[hash] != nil { return fmt.Errorf("Known transaction (%x)", hash[:4]) } + + if pool.config.UsingScroll { + if err := fees.VerifyFee(pool.signer, tx, pool.currentState(ctx)); err != nil { + return err + } + } + err := pool.validateTx(ctx, tx) if err != nil { return err diff --git a/params/config.go b/params/config.go index d99f305381..0852e966c4 100644 --- a/params/config.go +++ b/params/config.go @@ -269,6 +269,8 @@ var ( TestChainConfig = &ChainConfig{big.NewInt(1), big.NewInt(0), nil, false, big.NewInt(0), common.Hash{}, big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), nil, new(EthashConfig), nil, false, &common.Address{123}, true, true, nil, true} TestRules = TestChainConfig.Rules(new(big.Int)) + + TestNoL1feeChainConfig = &ChainConfig{big.NewInt(1), big.NewInt(0), nil, false, big.NewInt(0), common.Hash{}, big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), nil, new(EthashConfig), nil, false, &common.Address{123}, true, true, nil, false} ) // TrustedCheckpoint represents a set of post-processed trie roots (CHT and diff --git a/rollup/fees/rollup_fee.go b/rollup/fees/rollup_fee.go index 2d634be309..b0e4dd044d 100644 --- a/rollup/fees/rollup_fee.go +++ b/rollup/fees/rollup_fee.go @@ -3,6 +3,7 @@ package fees import ( "bytes" "errors" + "fmt" "math/big" "github.com/scroll-tech/go-ethereum/common" @@ -35,6 +36,7 @@ type Message interface { // required to compute the L1 fee type StateDB interface { GetState(common.Address, common.Hash) common.Hash + GetBalance(addr common.Address) *big.Int } // CalculateL1MsgFee computes the L1 portion of the fee given @@ -138,3 +140,61 @@ func mulAndScale(x *big.Int, y *big.Int, precision *big.Int) *big.Int { z := new(big.Int).Mul(x, y) return new(big.Int).Quo(z, precision) } + +// copyTransaction copies the transaction, removing the signature +func copyTransaction(tx *types.Transaction) *types.Transaction { + if tx.To() == nil { + return types.NewContractCreation( + tx.Nonce(), + tx.Value(), + tx.Gas(), + tx.GasPrice(), + tx.Data(), + ) + } + return types.NewTransaction( + tx.Nonce(), + *tx.To(), + tx.Value(), + tx.Gas(), + tx.GasPrice(), + tx.Data(), + ) +} + +func CalculateTotalFee(tx *types.Transaction, state StateDB) (*big.Int, error) { + unsigned := copyTransaction(tx) + raw, err := rlpEncode(unsigned) + if err != nil { + return nil, err + } + + l1BaseFee, overhead, scalar := readGPOStorageSlots(rcfg.L1GasPriceOracleAddress, state) + l1Fee := CalculateL1Fee(raw, overhead, l1BaseFee, scalar) + + l2GasLimit := new(big.Int).SetUint64(tx.Gas()) + l2Fee := new(big.Int).Mul(tx.GasPrice(), l2GasLimit) + fee := new(big.Int).Add(l1Fee, l2Fee) + return fee, nil +} + +func VerifyFee(signer types.Signer, tx *types.Transaction, state StateDB) error { + fee, err := CalculateTotalFee(tx, state) + if err != nil { + return fmt.Errorf("invalid transaction: %w", err) + } + + cost := tx.Value() + cost = cost.Add(cost, fee) + from, err := types.Sender(signer, tx) + if err != nil { + return errors.New("invalid transaction: invalid sender") + } + if state.GetBalance(from).Cmp(cost) < 0 { + return errors.New("invalid transaction: insufficient funds for gas * price + value") + } + + // TODO: check GasPrice is in an expected range + + return nil +}