Skip to content

Commit

Permalink
tx_pool, state_transition: accept sponsored tx with expired time = 0 (#…
Browse files Browse the repository at this point in the history
…379)

* tx_pool, state_transition: accept sponsored tx with expired time = 0

The sponsored transaction with expired time = 0 means there is no expired time
for that transaction, so we skip the expired time check for that one.

* api: add payer field to sponsored transaction in RPC response

This commit adds payer field to sponsored transaction in RPC response of APIs
related to transactions to easy lookup without manually recovering from the
returned payer's signature.

* txpool: update the signer of txlist when it differs from txpool's signer

txpool's signer can change when we reach higher block that triggers the new
signer. This commit checks the txpool's and txlist's signer when adding the new
transaction to txlist, update the txlist's signer with txpool's one when they
differ.

* state_transition: nil check gasFeeCap before using in buyGas

In normal case, gasFeeCap is never nil. In transactions other than dynamic fee
transaction, gasFeeCap is set to gasPrice. However, in some test cases,
gasFeeCap can be nil. In these cases, nil check the gasFeeCap and use gasPrice
instead.
  • Loading branch information
minh-bq authored Nov 20, 2023
1 parent 4878e9b commit d485e8e
Show file tree
Hide file tree
Showing 6 changed files with 115 additions and 6 deletions.
18 changes: 17 additions & 1 deletion core/blockchain_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3574,7 +3574,23 @@ func TestSponsoredTxTransition(t *testing.T) {
t.Fatalf("Expect error %s, get %s", ErrInsufficientSenderFunds, err)
}

// 5. Successfully add tx
// 5. Successfully add tx, sponsored tx with expired time = 0 is accepted
innerTx.ExpiredTime = 0
innerTx.PayerR, innerTx.PayerS, innerTx.PayerV, err = types.PayerSign(
payerKey,
mikoSigner,
crypto.PubkeyToAddress(senderKey.PublicKey),
&innerTx,
)
if err != nil {
t.Fatalf("Payer fails to sign transaction, err %s", err)
}

sponsoredTx, err = types.SignNewTx(senderKey, mikoSigner, &innerTx)
if err != nil {
t.Fatalf("Fail to sign transaction, err %s", err)
}

blocks, _ = GenerateChain(&chainConfig, blocks[0], engine, db, 1, func(i int, bg *BlockGen) {
tx, err := types.SignTx(types.NewTransaction(1, senderAddr, innerTx.Value, params.TxGas, bg.header.BaseFee, nil), mikoSigner, adminKey)
if err != nil {
Expand Down
10 changes: 8 additions & 2 deletions core/state_transition.go
Original file line number Diff line number Diff line change
Expand Up @@ -204,7 +204,12 @@ func (st *StateTransition) buyGas() error {
// transaction, st.gasPrice is the already calculated gas
// price based on block base fee, gas fee cap and gas tip cap
effectiveGasFee := new(big.Int).Mul(gas, st.gasPrice)
maxGasFee := new(big.Int).Mul(gas, st.gasFeeCap)
var maxGasFee *big.Int
if st.gasFeeCap != nil {
maxGasFee = new(big.Int).Mul(gas, st.gasFeeCap)
} else {
maxGasFee = new(big.Int).Mul(gas, st.gasPrice)
}

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
Expand Down Expand Up @@ -283,7 +288,8 @@ func (st *StateTransition) preCheck() error {

// Check expired time, gas fee cap and tip cap in sponsored transaction
if st.msg.Payer() != st.msg.From() {
if st.msg.ExpiredTime() <= st.evm.Context.Time {
expiredTime := st.msg.ExpiredTime()
if expiredTime != 0 && expiredTime <= st.evm.Context.Time {
return fmt.Errorf("%w: expiredTime: %d, blockTime: %d", ErrExpiredSponsoredTx,
st.msg.ExpiredTime(), st.evm.Context.Time)
}
Expand Down
18 changes: 16 additions & 2 deletions core/tx_list.go
Original file line number Diff line number Diff line change
Expand Up @@ -271,6 +271,16 @@ func newTxList(strict bool, signer types.Signer) *txList {
}
}

// Signer returns the signer of txlist
func (l *txList) Signer() types.Signer {
return l.signer
}

// The txpool's signer has changed, we need to update the signer of txlist
func (l *txList) UpdateSigner(signer types.Signer) {
l.signer = signer
}

// Overlaps returns whether the transaction specified has the same nonce as one
// already contained within the list.
func (l *txList) Overlaps(tx *types.Transaction) bool {
Expand Down Expand Up @@ -374,11 +384,15 @@ func (l *txList) Filter(
}

if tx.Type() == types.SponsoredTxType {
payer, _ := types.Payer(l.signer, tx)
payer, err := types.Payer(l.signer, tx)
if err != nil {
return false
}
gasFee := new(big.Int).Mul(tx.GasPrice(), new(big.Int).SetUint64(tx.Gas()))
expiredTime := tx.ExpiredTime()
return gasFee.Cmp(payerCostLimit[payer]) > 0 ||
tx.Value().Cmp(costLimit) > 0 ||
tx.ExpiredTime() <= currentTime
(expiredTime != 0 && expiredTime <= currentTime)
} else {
return tx.Cost().Cmp(costLimit) > 0
}
Expand Down
11 changes: 10 additions & 1 deletion core/tx_pool.go
Original file line number Diff line number Diff line change
Expand Up @@ -661,7 +661,8 @@ func (pool *TxPool) validateTx(tx *types.Transaction, local bool) error {
}

// Ensure sponsored transaction is not expired
if tx.ExpiredTime() <= pool.currentTime {
expiredTime := tx.ExpiredTime()
if expiredTime != 0 && expiredTime <= pool.currentTime {
return ErrExpiredSponsoredTx
}

Expand Down Expand Up @@ -794,6 +795,10 @@ func (pool *TxPool) add(tx *types.Transaction, local bool) (replaced bool, err e
// Try to replace an existing transaction in the pending pool
from, _ := types.Sender(pool.signer, tx) // already validated
if list := pool.pending[from]; list != nil && list.Overlaps(tx) {
if !pool.signer.Equal(pool.pending[from].Signer()) {
pool.pending[from].UpdateSigner(pool.signer)
}

// Nonce already pending, check if required price bump is met
inserted, old := list.Add(tx, pool.config.PriceBump)
if !inserted {
Expand Down Expand Up @@ -844,6 +849,8 @@ func (pool *TxPool) enqueueTx(hash common.Hash, tx *types.Transaction, local boo
from, _ := types.Sender(pool.signer, tx) // already validated
if pool.queue[from] == nil {
pool.queue[from] = newTxList(false, pool.signer)
} else if !pool.signer.Equal(pool.queue[from].Signer()) {
pool.queue[from].UpdateSigner(pool.signer)
}
inserted, old := pool.queue[from].Add(tx, pool.config.PriceBump)
if !inserted {
Expand Down Expand Up @@ -896,6 +903,8 @@ func (pool *TxPool) promoteTx(addr common.Address, hash common.Hash, tx *types.T
// Try to insert the transaction into the pending queue
if pool.pending[addr] == nil {
pool.pending[addr] = newTxList(true, pool.signer)
} else if !pool.signer.Equal(pool.pending[addr].Signer()) {
pool.pending[addr].UpdateSigner(pool.signer)
}
list := pool.pending[addr]

Expand Down
61 changes: 61 additions & 0 deletions core/tx_pool_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2864,6 +2864,29 @@ func TestExpiredTimeAndGasCheckSponsoredTx(t *testing.T) {
if err != nil {
t.Fatalf("Expect successfully add tx, get %s", err)
}

// 7. Sponsored tx with expired time == 0 is accepted
innerTx.ExpiredTime = 0
innerTx.Nonce = 2
innerTx.PayerR, innerTx.PayerS, innerTx.PayerV, err = types.PayerSign(
payerKey,
mikoSigner,
crypto.PubkeyToAddress(senderKey.PublicKey),
&innerTx,
)
if err != nil {
t.Fatalf("Payer fails to sign transaction, err %s", err)
}

tx, err = types.SignNewTx(senderKey, mikoSigner, &innerTx)
if err != nil {
t.Fatalf("Fail to sign transaction, err %s", err)
}

err = txpool.addRemoteSync(tx)
if err != nil {
t.Fatalf("Expect successfully add tx, get %s", err)
}
}

// TestSponsoredTxInTxPoolQueue tests that sponsored tx is removed from
Expand Down Expand Up @@ -3087,4 +3110,42 @@ func TestSponsoredTxInTxPoolQueue(t *testing.T) {
if queued != 0 {
t.Fatalf("Queued txpool, expect %d get %d", 0, queued)
}

// 5. Sponsored tx with expired time == 0 is not removed
<-txpool.requestReset(nil, types.CopyHeader(&types.Header{Time: 200, GasLimit: blockchain.gasLimit}))

sponsoredTx1.ExpiredTime = 0
sponsoredTx1.PayerR, sponsoredTx1.PayerS, sponsoredTx1.PayerV, err = types.PayerSign(
payerKey,
mikoSigner,
crypto.PubkeyToAddress(senderKey.PublicKey),
&sponsoredTx1,
)
if err != nil {
t.Fatalf("Payer fails to sign transaction, err %s", err)
}

tx1, err = types.SignNewTx(senderKey, mikoSigner, &sponsoredTx1)
if err != nil {
t.Fatalf("Fail to sign transaction, err %s", err)
}

errs = txpool.AddRemotesSync([]*types.Transaction{tx1})
for _, err := range errs {
if err != nil {
t.Fatalf("Fail to add tx to pool, err %s", err)
}
}

pending, _ = txpool.Stats()
if pending != 1 {
t.Fatalf("Pending txpool, expect %d get %d", 1, pending)
}

<-txpool.requestReset(nil, types.CopyHeader(&types.Header{Time: 200, GasLimit: blockchain.gasLimit}))

pending, _ = txpool.Stats()
if pending != 1 {
t.Fatalf("Pending txpool, expect %d get %d", 1, pending)
}
}
3 changes: 3 additions & 0 deletions internal/ethapi/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -1333,6 +1333,7 @@ type RPCTransaction struct {
V *hexutil.Big `json:"v"`
R *hexutil.Big `json:"r"`
S *hexutil.Big `json:"s"`
Payer *common.Address `json:"payer,omitempty"`
ExpiredTime *hexutil.Uint64 `json:"expiredTime,omitempty"`
PayerV *hexutil.Big `json:"payerV,omitempty"`
PayerR *hexutil.Big `json:"payerR,omitempty"`
Expand Down Expand Up @@ -1384,6 +1385,7 @@ func newRPCTransaction(tx *types.Transaction, blockHash common.Hash, blockNumber
result.GasPrice = (*hexutil.Big)(tx.GasFeeCap())
}
case types.SponsoredTxType:
payer, _ := types.Payer(signer, tx)
result.ChainID = (*hexutil.Big)(tx.ChainId())
result.GasFeeCap = (*hexutil.Big)(tx.GasFeeCap())
result.GasTipCap = (*hexutil.Big)(tx.GasTipCap())
Expand All @@ -1401,6 +1403,7 @@ func newRPCTransaction(tx *types.Transaction, blockHash common.Hash, blockNumber
result.PayerR = (*hexutil.Big)(r)
result.PayerS = (*hexutil.Big)(s)
result.PayerV = (*hexutil.Big)(v)
result.Payer = &payer
}
return result
}
Expand Down

0 comments on commit d485e8e

Please sign in to comment.