feat: VerifyFee for sendTx and txpool.add (#236)

* init

* rework

* handle `LesApiBackend`

* fix tests

* add L1fee even for 0 gasPrice

* update comments
This commit is contained in:
HAOYUatHZ 2023-03-02 16:38:43 +08:00 committed by GitHub
parent 481ca91e34
commit 4588569b63
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 119 additions and 24 deletions

View file

@ -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)

View file

@ -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)

View file

@ -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)
}

View file

@ -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)
}

View file

@ -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

View file

@ -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

View file

@ -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
}