Skip to content

Commit

Permalink
core/state, txpool: handle new sponsored transaction, adjust gas check
Browse files Browse the repository at this point in the history
This commit adjust the gas check to correctly check gas fee from payer of
transaction and value from sender in txpool and in preCheck before applying
transaction.
  • Loading branch information
minh-bq committed Oct 27, 2023
1 parent ac30513 commit d64f58c
Show file tree
Hide file tree
Showing 6 changed files with 194 additions and 39 deletions.
11 changes: 11 additions & 0 deletions core/error.go
Original file line number Diff line number Diff line change
Expand Up @@ -96,4 +96,15 @@ var (

// ErrSenderNoEOA is returned if the sender of a transaction is a contract.
ErrSenderNoEOA = errors.New("sender not an eoa")

// ErrExpiredSponsoredTx is returned if the sponsored transaction is expired.
ErrExpiredSponsoredTx = errors.New("sponsored transaction is expired")

// ErrInsufficientPayerFunds is returned if the gas fee cost of executing a transaction
// is higher than the balance of the payer's account.
ErrInsufficientPayerFunds = errors.New("insufficient payer funds for gas * price")

// ErrInsufficientSenderFunds is returned if the value in transaction
// is higher than the balance of the user's account.
ErrInsufficientSenderFunds = errors.New("insufficient sender funds for value")
)
5 changes: 4 additions & 1 deletion core/state_processor.go
Original file line number Diff line number Diff line change
Expand Up @@ -144,9 +144,12 @@ func applyTransaction(
from := msg.From()

// Check if sender and recipient are blacklisted
payer := msg.Payer()
if config.Consortium != nil && config.IsOdysseus(blockNumber) {
contractAddr := config.BlacklistContractAddress
if state.IsAddressBlacklisted(statedb, contractAddr, &from) || state.IsAddressBlacklisted(statedb, contractAddr, msg.To()) {
if state.IsAddressBlacklisted(statedb, contractAddr, &from) ||
state.IsAddressBlacklisted(statedb, contractAddr, msg.To()) ||
state.IsAddressBlacklisted(statedb, contractAddr, &payer) {
return nil, nil, ErrAddressBlacklisted
}
}
Expand Down
60 changes: 46 additions & 14 deletions core/state_transition.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,11 @@ package core

import (
"fmt"
"github.com/ethereum/go-ethereum/consensus"
"math"
"math/big"

"github.com/ethereum/go-ethereum/consensus"

"github.com/ethereum/go-ethereum/common"
cmath "github.com/ethereum/go-ethereum/common/math"
"github.com/ethereum/go-ethereum/core/types"
Expand Down Expand Up @@ -78,6 +79,12 @@ type Message interface {
IsFake() bool
Data() []byte
AccessList() types.AccessList

// In legacy transaction, this is the same as From.
// In sponsored transaction, this is the payer's
// address recovered from the payer's signature.
Payer() common.Address
ExpiredTime() uint64
}

// ExecutionResult includes all output after executing given evm
Expand Down Expand Up @@ -191,24 +198,39 @@ func (st *StateTransition) to() common.Address {
}

func (st *StateTransition) buyGas() error {
mgval := new(big.Int).SetUint64(st.msg.Gas())
mgval = mgval.Mul(mgval, st.gasPrice)
balanceCheck := mgval
if st.gasFeeCap != nil {
balanceCheck = new(big.Int).SetUint64(st.msg.Gas())
balanceCheck = balanceCheck.Mul(balanceCheck, st.gasFeeCap)
balanceCheck.Add(balanceCheck, st.value)
}
if have, want := st.state.GetBalance(st.msg.From()), balanceCheck; have.Cmp(want) < 0 {
return fmt.Errorf("%w: address %v have %v want %v", ErrInsufficientFunds, st.msg.From().Hex(), have, want)
gasFee := new(big.Int).SetUint64(st.msg.Gas())
// To be precise, the gas fee must be gas * gasPrice in
// transaction types other than dynamic gas fee transaction.
// However, the st.gasFeeCap = tx.inner.gasFeeCap = tx.GasPrice
// in these transactions, so this gas fee calculation is still
// correct.
gasFee = gasFee.Mul(gasFee, st.gasFeeCap)
if st.msg.Payer() != st.msg.From() {
// This is sponsored transaction, check gas fee with payer's balance and msg.value with sender's balance
if have, want := st.state.GetBalance(st.msg.Payer()), gasFee; have.Cmp(want) < 0 {
return fmt.Errorf("%w: address %v have %v want %v", ErrInsufficientPayerFunds, st.msg.Payer().Hex(), have, want)
}

if have, want := st.state.GetBalance(st.msg.From()), st.value; have.Cmp(want) < 0 {
return fmt.Errorf("%w: address %v have %v want %v", ErrInsufficientSenderFunds, st.msg.From().Hex(), have, want)
}
} else {
gasFeeAndValue := new(big.Int).Add(gasFee, st.value)
if have, want := st.state.GetBalance(st.msg.From()), gasFeeAndValue; have.Cmp(want) < 0 {
return fmt.Errorf("%w: address %v have %v want %v", ErrInsufficientFunds, st.msg.From().Hex(), have, want)
}
}

if err := st.gp.SubGas(st.msg.Gas()); err != nil {
return err
}
st.gas += st.msg.Gas()

st.initialGas = st.msg.Gas()
st.state.SubBalance(st.msg.From(), mgval)

// Subtract the gas fee from balance of the fee payer,
// the msg.value is transfered to the recipient in later step.
st.state.SubBalance(st.msg.Payer(), gasFee)
return nil
}

Expand Down Expand Up @@ -257,6 +279,15 @@ func (st *StateTransition) preCheck() error {
}
}
}

// Check expired time in sponsored transaction
if st.msg.Payer() != st.msg.From() {
if st.msg.ExpiredTime() <= st.evm.Context.Time {
return fmt.Errorf("%w: expiredTime: %d, blockTime: %d", ErrExpiredSponsoredTx,
st.msg.ExpiredTime(), st.evm.Context.Time)
}
}

return st.buyGas()
}

Expand All @@ -278,7 +309,8 @@ func (st *StateTransition) TransitionDb() (*ExecutionResult, error) {
// applying the message. The rules include these clauses
//
// 1. the nonce of the message caller is correct
// 2. caller has enough balance to cover transaction fee(gaslimit * gasprice)
// 2. payer has enough balance to cover transaction fee(gaslimit * gasprice)
// payer signature is not expired
// 3. the amount of gas required is available in the block
// 4. the purchased gas is enough to cover intrinsic usage
// 5. there is no overflow when calculating intrinsic gas
Expand Down Expand Up @@ -376,7 +408,7 @@ func (st *StateTransition) refundGas(refundQuotient uint64) {

// Return ETH for remaining gas, exchanged at the original rate.
remaining := new(big.Int).Mul(new(big.Int).SetUint64(st.gas), st.gasPrice)
st.state.AddBalance(st.msg.From(), remaining)
st.state.AddBalance(st.msg.Payer(), remaining)

// Also return remaining gas to the block gas counter so it is
// available for the next transaction.
Expand Down
80 changes: 69 additions & 11 deletions core/tx_list.go
Original file line number Diff line number Diff line change
Expand Up @@ -253,17 +253,21 @@ type txList struct {
strict bool // Whether nonces are strictly continuous or not
txs *txSortedMap // Heap indexed sorted hash map of the transactions

costcap *big.Int // Price of the highest costing transaction (reset only if exceeds balance)
gascap uint64 // Gas limit of the highest spending transaction (reset only if exceeds block limit)
costcap *big.Int // Price of the highest costing transaction (reset only if exceeds balance)
gascap uint64 // Gas limit of the highest spending transaction (reset only if exceeds block limit)
signer types.Signer // The signer of the transaction pool
payers map[common.Address]int // The reference count of payers
}

// newTxList create a new transaction list for maintaining nonce-indexable fast,
// gapped, sortable transaction lists.
func newTxList(strict bool) *txList {
func newTxList(strict bool, signer types.Signer) *txList {
return &txList{
strict: strict,
txs: newTxSortedMap(),
costcap: new(big.Int),
signer: signer,
payers: make(map[common.Address]int),
}
}

Expand Down Expand Up @@ -310,14 +314,30 @@ func (l *txList) Add(tx *types.Transaction, priceBump uint64) (bool, *types.Tran
if gas := tx.Gas(); l.gascap < gas {
l.gascap = gas
}
payer, _ := types.Payer(l.signer, tx)
l.payers[payer]++
return true, old
}

// removePayer decrease the reference count of payers in the list
// and remove the payer if the reference count reaches 0
func (l *txList) removePayer(removed types.Transactions) {
for _, removedTx := range removed {
payer, _ := types.Payer(l.signer, removedTx)
l.payers[payer]--
if l.payers[payer] == 0 {
delete(l.payers, payer)
}
}
}

// Forward removes all transactions from the list with a nonce lower than the
// provided threshold. Every removed transaction is returned for any post-removal
// maintenance.
func (l *txList) Forward(threshold uint64) types.Transactions {
return l.txs.Forward(threshold)
removed := l.txs.Forward(threshold)
l.removePayer(removed)
return removed
}

// Filter removes all transactions from the list with a cost or gas limit higher
Expand All @@ -329,17 +349,35 @@ func (l *txList) Forward(threshold uint64) types.Transactions {
// a point in calculating all the costs or if the balance covers all. If the threshold
// is lower than the costgas cap, the caps will be reset to a new high after removing
// the newly invalidated transactions.
func (l *txList) Filter(costLimit *big.Int, gasLimit uint64) (types.Transactions, types.Transactions) {
func (l *txList) Filter(
costLimit *big.Int,
gasLimit uint64,
payerCostLimit map[common.Address]*big.Int,
currentTime uint64,
) (types.Transactions, types.Transactions) {
// If all transactions are below the threshold, short circuit
if l.costcap.Cmp(costLimit) <= 0 && l.gascap <= gasLimit {
if l.costcap.Cmp(costLimit) <= 0 && l.gascap <= gasLimit && len(payerCostLimit) == 0 {
return nil, nil
}
l.costcap = new(big.Int).Set(costLimit) // Lower the caps to the thresholds
l.gascap = gasLimit

// Filter out all the transactions above the account's funds
removed := l.txs.Filter(func(tx *types.Transaction) bool {
return tx.Gas() > gasLimit || tx.Cost().Cmp(costLimit) > 0
// Filter out all the transactions above block gas limit, above
// the payer's or sender's fund
removed := l.txs.filter(func(tx *types.Transaction) bool {
if tx.Gas() > gasLimit {
return true
}

if tx.Type() == types.SponsoredTxType {
payer, _ := types.Payer(l.signer, tx)
gasFee := new(big.Int).Mul(tx.GasPrice(), new(big.Int).SetUint64(tx.Gas()))
return gasFee.Cmp(payerCostLimit[payer]) > 0 ||
tx.Value().Cmp(costLimit) > 0 ||
tx.ExpiredTime() <= currentTime
} else {
return tx.Cost().Cmp(costLimit) > 0
}
})

if len(removed) == 0 {
Expand All @@ -357,28 +395,38 @@ func (l *txList) Filter(costLimit *big.Int, gasLimit uint64) (types.Transactions
invalids = l.txs.filter(func(tx *types.Transaction) bool { return tx.Nonce() > lowest })
}
l.txs.reheap()
l.removePayer(removed)
l.removePayer(invalids)
return removed, invalids
}

// Cap places a hard limit on the number of items, returning all transactions
// exceeding that limit.
func (l *txList) Cap(threshold int) types.Transactions {
return l.txs.Cap(threshold)
removed := l.txs.Cap(threshold)
l.removePayer(removed)
return removed
}

// Remove deletes a transaction from the maintained list, returning whether the
// transaction was found, and also returning any transaction invalidated due to
// the deletion (strict mode only).
func (l *txList) Remove(tx *types.Transaction) (bool, types.Transactions) {
// Remove the transaction from the set
var removed types.Transactions
nonce := tx.Nonce()
if removed := l.txs.Remove(nonce); !removed {
return false, nil
}
removed = append(removed, tx)
// In strict mode, filter out non-executable transactions
if l.strict {
return true, l.txs.Filter(func(tx *types.Transaction) bool { return tx.Nonce() > nonce })
filteredTx := l.txs.Filter(func(tx *types.Transaction) bool { return tx.Nonce() > nonce })
removed = append(removed, filteredTx...)
l.removePayer(removed)
return true, filteredTx
}
l.removePayer(removed)
return true, nil
}

Expand Down Expand Up @@ -416,6 +464,16 @@ func (l *txList) LastElement() *types.Transaction {
return l.txs.LastElement()
}

func (l *txList) Payers() []common.Address {
var payers []common.Address

for payer := range l.payers {
payers = append(payers, payer)
}

return payers
}

// priceHeap is a heap.Interface implementation over transactions for retrieving
// price-sorted transactions to discard when the pool fills up. If baseFee is set
// then the heap is sorted based on the effective tip based on the given base fee.
Expand Down
7 changes: 4 additions & 3 deletions core/tx_list_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import (
"math/rand"
"testing"

"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto"
)
Expand All @@ -36,7 +37,7 @@ func TestStrictTxListAdd(t *testing.T) {
txs[i] = transaction(uint64(i), 0, key)
}
// Insert the transactions in a random order
list := newTxList(true)
list := newTxList(true, types.NewEIP155Signer(common.Big1))
for _, v := range rand.Perm(len(txs)) {
list.Add(txs[v], DefaultTxPoolConfig.PriceBump)
}
Expand All @@ -63,10 +64,10 @@ func BenchmarkTxListAdd(b *testing.B) {
priceLimit := big.NewInt(int64(DefaultTxPoolConfig.PriceLimit))
b.ResetTimer()
for i := 0; i < b.N; i++ {
list := newTxList(true)
list := newTxList(true, types.NewEIP155Signer(common.Big1))
for _, v := range rand.Perm(len(txs)) {
list.Add(txs[v], DefaultTxPoolConfig.PriceBump)
list.Filter(priceLimit, DefaultTxPoolConfig.PriceBump)
list.Filter(priceLimit, DefaultTxPoolConfig.PriceBump, make(map[common.Address]*big.Int), 0)
}
}
}
Loading

0 comments on commit d64f58c

Please sign in to comment.