Skip to content

Commit

Permalink
Fix transaction replacements (#283)
Browse files Browse the repository at this point in the history
  • Loading branch information
gastonponti authored Dec 3, 2024
1 parent fb687af commit 8736c4f
Show file tree
Hide file tree
Showing 4 changed files with 55 additions and 4 deletions.
2 changes: 1 addition & 1 deletion common/exchange/rates.go
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ func getRate(exchangeRates common.ExchangeRates, feeCurrency *common.Address) (*
// returns -1 0 or 1 depending if val1 < val2, val1 == val2, or val1 > val2 respectively.
func CompareValue(exchangeRates common.ExchangeRates, val1 *big.Int, feeCurrency1 *common.Address, val2 *big.Int, feeCurrency2 *common.Address) (int, error) {
// Short circuit if the fee currency is the same.
if feeCurrency1 == feeCurrency2 {
if common.AreSameAddress(feeCurrency1, feeCurrency2) {
return val1.Cmp(val2), nil
}

Expand Down
8 changes: 6 additions & 2 deletions core/txpool/legacypool/legacypool.go
Original file line number Diff line number Diff line change
Expand Up @@ -689,7 +689,11 @@ func (pool *LegacyPool) validateTx(tx *types.Transaction, local bool) error {
},
ExistingExpenditure: func(addr common.Address) (*big.Int, *big.Int) {
if list := pool.pending[addr]; list != nil {
return list.TotalCostFor(tx.FeeCurrency()).ToBig(), list.TotalCostFor(nil).ToBig()
if tx.FeeCurrency() != nil {
return list.TotalCostFor(tx.FeeCurrency()).ToBig(), list.TotalCostFor(nil).ToBig()
} else {
return common.Big0, list.TotalCostFor(nil).ToBig()
}
}
return new(big.Int), new(big.Int)
},
Expand All @@ -703,7 +707,7 @@ func (pool *LegacyPool) validateTx(tx *types.Transaction, local bool) error {
nativeCost = nativeCost.Add(nativeCost, l1Cost)
}
}
if tx.FeeCurrency() != feeCurrency {
if !common.AreSameAddress(tx.FeeCurrency(), feeCurrency) {
// We are only interested in costs in the same currency
feeCurrencyCost = new(big.Int)
}
Expand Down
44 changes: 44 additions & 0 deletions core/txpool/legacypool/legacypool_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2235,6 +2235,50 @@ func TestReplacement(t *testing.T) {
}
}

// Tests that the pool accepts replacement transactions if the account only owns Celo.
func TestCeloReplacement(t *testing.T) {
t.Parallel()

// Create the pool to test the pricing enforcement with
statedb, _ := state.New(types.EmptyRootHash, state.NewDatabase(rawdb.NewMemoryDatabase()), nil)
blockchain := newTestBlockChain(params.TestChainConfig, 1000000, statedb, new(event.Feed))

pool := New(testTxPoolConfig, blockchain)
pool.Init(testTxPoolConfig.PriceLimit, blockchain.CurrentBlock(), makeAddressReserver())
defer pool.Close()

// Keep track of transaction events to ensure all executables get announced
events := make(chan core.NewTxsEvent, 32)
sub := pool.txFeed.Subscribe(events)
defer sub.Unsubscribe()

// Create a test account to add transactions with
key, _ := crypto.GenerateKey()
balance := big.NewInt(6000000)
testAddBalance(pool, crypto.PubkeyToAddress(key.PublicKey), balance)

// Add pending transactions, ensuring the minimum price bump is enforced for replacement (for ultra low prices too)
price := int64(100)
priceBumped := (price * (100 + int64(testTxPoolConfig.PriceBump))) / 100

if err := pool.addRemoteSync(pricedTransaction(0, 50000, big.NewInt(price), key)); err != nil {
t.Fatalf("failed to add original cheap pending transaction: %v", err)
}
if err := pool.addRemoteSync(pricedTransaction(0, 50000, big.NewInt(priceBumped-1), key)); err != txpool.ErrReplaceUnderpriced {
t.Fatalf("original cheap queued transaction replacement error mismatch: have %v, want %v", err, txpool.ErrReplaceUnderpriced)
}
if err := pool.addRemoteSync(pricedTransaction(0, 50000, big.NewInt(priceBumped), key)); err != nil {
t.Fatalf("failed to replace original transaction: %v", err)
}
if err := pool.addRemoteSync(pricedTransaction(0, 50000, big.NewInt(price*2), key)); !errors.Is(err, core.ErrInsufficientFunds) {
txCost := big.NewInt(price*2*50000 + 100) // 100 is the amount default of the pricedTransaction function
t.Fatalf("second queue replacement error mismatch: have %v, want %v", err, fmt.Errorf("%w: balance %v, tx cost %v, overshot %v", core.ErrInsufficientFunds, balance, txCost, new(big.Int).Sub(txCost, balance)))
}
if err := validateEvents(events, 2); err != nil {
t.Fatalf("replacement event firing failed: %v", err)
}
}

// Tests that the pool rejects replacement dynamic fee transactions that don't
// meet the minimum price bump required.
func TestReplacementDynamicFee(t *testing.T) {
Expand Down
5 changes: 4 additions & 1 deletion core/txpool/validation.go
Original file line number Diff line number Diff line change
Expand Up @@ -275,10 +275,13 @@ func ValidateTransactionWithState(tx *types.Transaction, signer types.Signer, op
}
// Ensure the transactor has enough funds to cover the transaction costs
var (
feeCurrencyBalance = opts.ExistingBalance(from, tx.FeeCurrency())
feeCurrencyBalance = common.Big0
nativeBalance = opts.ExistingBalance(from, &common.ZeroAddress)
feeCurrencyCost, nativeCost = tx.Cost()
)
if tx.FeeCurrency() != nil {
feeCurrencyBalance = opts.ExistingBalance(from, tx.FeeCurrency())
}
if feeCurrencyBalance == nil {
return fmt.Errorf("feeCurrencyBalance is nil for FeeCurrency %x", tx.FeeCurrency())
}
Expand Down

0 comments on commit 8736c4f

Please sign in to comment.