diff --git a/core/blockchain_test.go b/core/blockchain_test.go index 8c5e912c58..04a6cac889 100644 --- a/core/blockchain_test.go +++ b/core/blockchain_test.go @@ -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 { diff --git a/core/state_transition.go b/core/state_transition.go index 90b6484104..4e9a5d9aa4 100644 --- a/core/state_transition.go +++ b/core/state_transition.go @@ -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 @@ -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) } diff --git a/core/tx_list.go b/core/tx_list.go index c157aeb74c..924ced4c09 100644 --- a/core/tx_list.go +++ b/core/tx_list.go @@ -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 { @@ -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 } diff --git a/core/tx_pool.go b/core/tx_pool.go index 239902c627..698cef4d5e 100644 --- a/core/tx_pool.go +++ b/core/tx_pool.go @@ -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 } @@ -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 { @@ -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 { @@ -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] diff --git a/core/tx_pool_test.go b/core/tx_pool_test.go index dacbdf324d..a696ec034c 100644 --- a/core/tx_pool_test.go +++ b/core/tx_pool_test.go @@ -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 @@ -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) + } } diff --git a/internal/ethapi/api.go b/internal/ethapi/api.go index 04db451ceb..4679b19303 100644 --- a/internal/ethapi/api.go +++ b/internal/ethapi/api.go @@ -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"` @@ -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()) @@ -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 }