mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-06-19 21:31:37 +00:00
This commit is contained in:
parent
df83610a30
commit
217b069ec0
6 changed files with 401 additions and 14 deletions
|
|
@ -65,6 +65,11 @@ var (
|
|||
// remains pending (and vice-versa).
|
||||
ErrAlreadyReserved = errors.New("address already reserved")
|
||||
|
||||
// ErrAuthorityReserved is returned if a transaction has an authorization
|
||||
// signed by an address which already has in-flight transactions known to the
|
||||
// pool.
|
||||
ErrAuthorityReserved = errors.New("authority already reserved")
|
||||
|
||||
ErrZeroGasPrice = errors.New("zero gas price")
|
||||
|
||||
ErrUnderMinGasPrice = errors.New("under min gas price")
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ import (
|
|||
"fmt"
|
||||
"math"
|
||||
"math/big"
|
||||
"slices"
|
||||
"sort"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
|
|
@ -214,6 +215,20 @@ func (config *Config) sanitize() Config {
|
|||
// The pool separates processable transactions (which can be applied to the
|
||||
// current state) and future transactions. Transactions move between those
|
||||
// two states over time as they are received and processed.
|
||||
//
|
||||
// In addition to tracking transactions, the pool also tracks a set of pending SetCode
|
||||
// authorizations (EIP7702). This helps minimize number of transactions that can be
|
||||
// trivially churned in the pool. As a standard rule, any account with a deployed
|
||||
// delegation or an in-flight authorization to deploy a delegation will only be allowed a
|
||||
// single transaction slot instead of the standard number. This is due to the possibility
|
||||
// of the account being sweeped by an unrelated account.
|
||||
//
|
||||
// Because SetCode transactions can have many authorizations included, we avoid explicitly
|
||||
// checking their validity to save the state lookup. So long as the encompassing
|
||||
// transaction is valid, the authorization will be accepted and tracked by the pool. In
|
||||
// case the pool is tracking a pending / queued transaction from a specific account, it
|
||||
// will reject new transactions with delegations from that account with standard in-flight
|
||||
// transactions.
|
||||
type LegacyPool struct {
|
||||
config Config
|
||||
chainconfig *params.ChainConfig
|
||||
|
|
@ -297,7 +312,7 @@ func New(config Config, chain BlockChain) *LegacyPool {
|
|||
// pool, specifically, whether it is a Legacy, AccessList or Dynamic transaction.
|
||||
func (pool *LegacyPool) Filter(tx *types.Transaction) bool {
|
||||
switch tx.Type() {
|
||||
case types.LegacyTxType, types.AccessListTxType, types.DynamicFeeTxType:
|
||||
case types.LegacyTxType, types.AccessListTxType, types.DynamicFeeTxType, types.SetCodeTxType:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
|
|
@ -622,7 +637,8 @@ func (pool *LegacyPool) validateTxBasics(tx *types.Transaction, local bool) erro
|
|||
Accept: 0 |
|
||||
1<<types.LegacyTxType |
|
||||
1<<types.AccessListTxType |
|
||||
1<<types.DynamicFeeTxType,
|
||||
1<<types.DynamicFeeTxType |
|
||||
1<<types.SetCodeTxType,
|
||||
MaxSize: txMaxSize,
|
||||
MinTip: pool.gasTip.Load(),
|
||||
NotSigner: func(from common.Address) bool {
|
||||
|
|
@ -653,6 +669,11 @@ func (pool *LegacyPool) validateTx(tx *types.Transaction, local bool) error {
|
|||
if list := pool.queue[addr]; list != nil {
|
||||
have += list.Len()
|
||||
}
|
||||
if pool.currentState.GetCodeHash(addr) != types.EmptyCodeHash || len(pool.all.auths[addr]) != 0 {
|
||||
// Allow at most one in-flight tx for delegated accounts or those with
|
||||
// a pending authorization.
|
||||
return have, max(0, 1-have)
|
||||
}
|
||||
return have, math.MaxInt
|
||||
},
|
||||
ExistingExpenditure: func(addr common.Address) *big.Int {
|
||||
|
|
@ -669,6 +690,18 @@ func (pool *LegacyPool) validateTx(tx *types.Transaction, local bool) error {
|
|||
}
|
||||
return nil
|
||||
},
|
||||
KnownConflicts: func(from common.Address, auths []common.Address) []common.Address {
|
||||
var conflicts []common.Address
|
||||
// Authorities cannot conflict with any pending or queued transactions.
|
||||
for _, addr := range auths {
|
||||
if list := pool.pending[addr]; list != nil {
|
||||
conflicts = append(conflicts, addr)
|
||||
} else if list := pool.queue[addr]; list != nil {
|
||||
conflicts = append(conflicts, addr)
|
||||
}
|
||||
}
|
||||
return conflicts
|
||||
},
|
||||
|
||||
Trc21FeeCapacity: pool.trc21FeeCapacity,
|
||||
|
||||
|
|
@ -1549,8 +1582,7 @@ func (pool *LegacyPool) promoteExecutables(accounts []common.Address) []*types.T
|
|||
// Drop all transactions that are deemed too old (low nonce)
|
||||
forwards := list.Forward(pool.currentState.GetNonce(addr))
|
||||
for _, tx := range forwards {
|
||||
hash := tx.Hash()
|
||||
pool.all.Remove(hash)
|
||||
pool.all.Remove(tx.Hash())
|
||||
}
|
||||
log.Trace("Removed old queued transactions", "count", len(forwards))
|
||||
// Drop all transactions that are too costly (low balance or out of gas)
|
||||
|
|
@ -1560,8 +1592,7 @@ func (pool *LegacyPool) promoteExecutables(accounts []common.Address) []*types.T
|
|||
}
|
||||
drops, _ := list.Filter(pool.currentState.GetBalance(addr), gasLimit, pool.trc21FeeCapacity, number)
|
||||
for _, tx := range drops {
|
||||
hash := tx.Hash()
|
||||
pool.all.Remove(hash)
|
||||
pool.all.Remove(tx.Hash())
|
||||
}
|
||||
log.Trace("Removed unpayable queued transactions", "count", len(drops))
|
||||
queuedNofundsMeter.Mark(int64(len(drops)))
|
||||
|
|
@ -1766,8 +1797,8 @@ func (pool *LegacyPool) demoteUnexecutables() {
|
|||
drops, invalids := list.Filter(pool.currentState.GetBalance(addr), gasLimit, pool.trc21FeeCapacity, number)
|
||||
for _, tx := range drops {
|
||||
hash := tx.Hash()
|
||||
log.Trace("Removed unpayable pending transaction", "hash", hash)
|
||||
pool.all.Remove(hash)
|
||||
log.Trace("Removed unpayable pending transaction", "hash", hash)
|
||||
}
|
||||
pool.priced.Removed(len(olds) + len(drops))
|
||||
pendingNofundsMeter.Mark(int64(len(drops)))
|
||||
|
|
@ -1918,6 +1949,8 @@ type lookup struct {
|
|||
lock sync.RWMutex
|
||||
locals map[common.Hash]*types.Transaction
|
||||
remotes map[common.Hash]*types.Transaction
|
||||
|
||||
auths map[common.Address][]common.Hash // All accounts with a pooled authorization
|
||||
}
|
||||
|
||||
// newLookup returns a new lookup structure.
|
||||
|
|
@ -1925,6 +1958,7 @@ func newLookup() *lookup {
|
|||
return &lookup{
|
||||
locals: make(map[common.Hash]*types.Transaction),
|
||||
remotes: make(map[common.Hash]*types.Transaction),
|
||||
auths: make(map[common.Address][]common.Hash),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -2023,6 +2057,7 @@ func (t *lookup) Add(tx *types.Transaction, local bool) {
|
|||
} else {
|
||||
t.remotes[tx.Hash()] = tx
|
||||
}
|
||||
t.addAuthorities(tx)
|
||||
}
|
||||
|
||||
// Remove removes a transaction from the lookup.
|
||||
|
|
@ -2030,6 +2065,7 @@ func (t *lookup) Remove(hash common.Hash) {
|
|||
t.lock.Lock()
|
||||
defer t.lock.Unlock()
|
||||
|
||||
t.removeAuthorities(hash)
|
||||
tx, ok := t.locals[hash]
|
||||
if !ok {
|
||||
tx, ok = t.remotes[hash]
|
||||
|
|
@ -2074,7 +2110,79 @@ func (t *lookup) RemotesBelowTip(threshold *big.Int) types.Transactions {
|
|||
return found
|
||||
}
|
||||
|
||||
// addAuthorities tracks the supplied tx in relation to each authority it
|
||||
// specifies.
|
||||
func (t *lookup) addAuthorities(tx *types.Transaction) {
|
||||
for _, addr := range tx.SetCodeAuthorities() {
|
||||
list, ok := t.auths[addr]
|
||||
if !ok {
|
||||
list = []common.Hash{}
|
||||
}
|
||||
if slices.Contains(list, tx.Hash()) {
|
||||
// Don't add duplicates.
|
||||
continue
|
||||
}
|
||||
list = append(list, tx.Hash())
|
||||
t.auths[addr] = list
|
||||
}
|
||||
}
|
||||
|
||||
// removeAuthorities stops tracking the supplied tx in relation to its
|
||||
// authorities.
|
||||
func (t *lookup) removeAuthorities(hash common.Hash) {
|
||||
for addr := range t.auths {
|
||||
list := t.auths[addr]
|
||||
// Remove tx from tracker.
|
||||
if i := slices.Index(list, hash); i >= 0 {
|
||||
list = append(list[:i], list[i+1:]...)
|
||||
} else {
|
||||
log.Error("Authority with untracked tx", "addr", addr, "hash", hash)
|
||||
}
|
||||
if len(list) == 0 {
|
||||
// If list is newly empty, delete it entirely.
|
||||
delete(t.auths, addr)
|
||||
continue
|
||||
}
|
||||
t.auths[addr] = list
|
||||
}
|
||||
}
|
||||
|
||||
// numSlots calculates the number of slots needed for a single transaction.
|
||||
func numSlots(tx *types.Transaction) int {
|
||||
return int((tx.Size() + txSlotSize - 1) / txSlotSize)
|
||||
}
|
||||
|
||||
// Clear implements txpool.SubPool, removing all tracked txs from the pool
|
||||
// and rotating the journal.
|
||||
func (pool *LegacyPool) Clear() {
|
||||
pool.mu.Lock()
|
||||
defer pool.mu.Unlock()
|
||||
|
||||
// unreserve each tracked account. Ideally, we could just clear the
|
||||
// reservation map in the parent txpool context. However, if we clear in
|
||||
// parent context, to avoid exposing the subpool lock, we have to lock the
|
||||
// reservations and then lock each subpool.
|
||||
//
|
||||
// This creates the potential for a deadlock situation:
|
||||
//
|
||||
// * TxPool.Clear locks the reservations
|
||||
// * a new transaction is received which locks the subpool mutex
|
||||
// * TxPool.Clear attempts to lock subpool mutex
|
||||
//
|
||||
// The transaction addition may attempt to reserve the sender addr which
|
||||
// can't happen until Clear releases the reservation lock. Clear cannot
|
||||
// acquire the subpool lock until the transaction addition is completed.
|
||||
for _, tx := range pool.all.locals {
|
||||
senderAddr, _ := types.Sender(pool.signer, tx)
|
||||
pool.reserve(senderAddr, false)
|
||||
}
|
||||
for _, tx := range pool.all.remotes {
|
||||
senderAddr, _ := types.Sender(pool.signer, tx)
|
||||
pool.reserve(senderAddr, false)
|
||||
}
|
||||
pool.all = newLookup()
|
||||
pool.priced = newPricedList(pool.all)
|
||||
pool.pending = make(map[common.Address]*list)
|
||||
pool.queue = make(map[common.Address]*list)
|
||||
pool.pendingNonces = newNoncer(pool.currentState)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -38,10 +38,12 @@ import (
|
|||
"github.com/XinFinOrg/XDPoSChain/core/tracing"
|
||||
"github.com/XinFinOrg/XDPoSChain/core/txpool"
|
||||
"github.com/XinFinOrg/XDPoSChain/core/types"
|
||||
"github.com/XinFinOrg/XDPoSChain/core/vm"
|
||||
"github.com/XinFinOrg/XDPoSChain/crypto"
|
||||
"github.com/XinFinOrg/XDPoSChain/event"
|
||||
"github.com/XinFinOrg/XDPoSChain/params"
|
||||
"github.com/XinFinOrg/XDPoSChain/trie"
|
||||
"github.com/holiman/uint256"
|
||||
)
|
||||
|
||||
var (
|
||||
|
|
@ -94,9 +96,10 @@ func (bc *testBlockChain) Config() *params.ChainConfig {
|
|||
|
||||
func (bc *testBlockChain) CurrentBlock() *types.Header {
|
||||
return &types.Header{
|
||||
Root: types.EmptyRootHash,
|
||||
Number: new(big.Int),
|
||||
GasLimit: bc.gasLimit.Load(),
|
||||
Root: types.EmptyRootHash,
|
||||
Number: new(big.Int),
|
||||
Difficulty: common.Big0,
|
||||
GasLimit: bc.gasLimit.Load(),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -144,6 +147,39 @@ func dynamicFeeTx(nonce uint64, gaslimit uint64, gasFee *big.Int, tip *big.Int,
|
|||
return tx
|
||||
}
|
||||
|
||||
type unsignedAuth struct {
|
||||
nonce uint64
|
||||
key *ecdsa.PrivateKey
|
||||
}
|
||||
|
||||
func setCodeTx(nonce uint64, key *ecdsa.PrivateKey, unsigned []unsignedAuth) *types.Transaction {
|
||||
return pricedSetCodeTx(nonce, 250000, uint256.MustFromBig(common.MinGasPrice), uint256.NewInt(1), key, unsigned)
|
||||
}
|
||||
|
||||
func pricedSetCodeTx(nonce uint64, gaslimit uint64, gasFee, tip *uint256.Int, key *ecdsa.PrivateKey, unsigned []unsignedAuth) *types.Transaction {
|
||||
var authList []types.SetCodeAuthorization
|
||||
for _, u := range unsigned {
|
||||
auth, _ := types.SignSetCode(u.key, types.SetCodeAuthorization{
|
||||
ChainID: *uint256.MustFromBig(params.TestChainConfig.ChainID),
|
||||
Address: common.Address{0x42},
|
||||
Nonce: u.nonce,
|
||||
})
|
||||
authList = append(authList, auth)
|
||||
}
|
||||
return types.MustSignNewTx(key, types.LatestSignerForChainID(params.TestChainConfig.ChainID), &types.SetCodeTx{
|
||||
ChainID: uint256.MustFromBig(params.TestChainConfig.ChainID),
|
||||
Nonce: nonce,
|
||||
GasTipCap: tip,
|
||||
GasFeeCap: gasFee,
|
||||
Gas: gaslimit,
|
||||
To: common.Address{},
|
||||
Value: uint256.NewInt(100),
|
||||
Data: nil,
|
||||
AccessList: nil,
|
||||
AuthList: authList,
|
||||
})
|
||||
}
|
||||
|
||||
func makeAddressReserver() txpool.AddressReserver {
|
||||
var (
|
||||
reserved = make(map[common.Address]struct{})
|
||||
|
|
@ -2588,6 +2624,207 @@ func TestSlotCount(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
// TestSetCodeTransactions tests a few scenarios regarding the EIP-7702
|
||||
// SetCodeTx.
|
||||
func TestSetCodeTransactions(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
// Create the pool to test the status retrievals with
|
||||
statedb, _ := state.New(types.EmptyRootHash, state.NewDatabaseForTesting())
|
||||
blockchain := newTestBlockChain(params.MergedTestChainConfig, 1000000, statedb, new(event.Feed))
|
||||
|
||||
pool := New(testTxPoolConfig, blockchain)
|
||||
pool.Init(new(big.Int).SetUint64(testTxPoolConfig.PriceLimit), blockchain.CurrentBlock(), makeAddressReserver())
|
||||
defer pool.Close()
|
||||
|
||||
// Create the test accounts
|
||||
var (
|
||||
keyA, _ = crypto.GenerateKey()
|
||||
keyB, _ = crypto.GenerateKey()
|
||||
keyC, _ = crypto.GenerateKey()
|
||||
addrA = crypto.PubkeyToAddress(keyA.PublicKey)
|
||||
addrB = crypto.PubkeyToAddress(keyB.PublicKey)
|
||||
addrC = crypto.PubkeyToAddress(keyC.PublicKey)
|
||||
)
|
||||
testAddBalance(pool, addrA, big.NewInt(params.Ether))
|
||||
testAddBalance(pool, addrB, big.NewInt(params.Ether))
|
||||
testAddBalance(pool, addrC, big.NewInt(params.Ether))
|
||||
|
||||
minGasPrice := new(big.Int).Set(common.MinGasPrice)
|
||||
minGasFee := uint256.MustFromBig(minGasPrice)
|
||||
doubleGasFee := new(uint256.Int).Mul(new(uint256.Int).Set(minGasFee), uint256.NewInt(2))
|
||||
tripleGasFee := new(uint256.Int).Mul(new(uint256.Int).Set(minGasFee), uint256.NewInt(3))
|
||||
legacyReplacePrice := new(big.Int).Mul(new(big.Int).Set(minGasPrice), big.NewInt(10))
|
||||
|
||||
for _, tt := range []struct {
|
||||
name string
|
||||
pending int
|
||||
queued int
|
||||
run func(string)
|
||||
}{
|
||||
{
|
||||
// Check that only one in-flight transaction is allowed for accounts
|
||||
// with delegation set. Also verify the accepted transaction can be
|
||||
// replaced by fee.
|
||||
name: "only-one-in-flight",
|
||||
pending: 1,
|
||||
run: func(name string) {
|
||||
aa := common.Address{0xaa, 0xaa}
|
||||
statedb.SetCode(addrA, append(types.DelegationPrefix, aa.Bytes()...))
|
||||
statedb.SetCode(aa, []byte{byte(vm.ADDRESS), byte(vm.PUSH0), byte(vm.SSTORE)})
|
||||
// Send transactions. First is accepted, second is rejected.
|
||||
if err := pool.addRemoteSync(pricedTransaction(0, 100000, minGasPrice, keyA)); err != nil {
|
||||
t.Fatalf("%s: failed to add remote transaction: %v", name, err)
|
||||
}
|
||||
if err := pool.addRemoteSync(pricedTransaction(1, 100000, new(big.Int).Set(minGasPrice), keyA)); !errors.Is(err, txpool.ErrAccountLimitExceeded) {
|
||||
t.Fatalf("%s: error mismatch: want %v, have %v", name, txpool.ErrAccountLimitExceeded, err)
|
||||
}
|
||||
// Also check gapped transaction.
|
||||
if err := pool.addRemoteSync(pricedTransaction(2, 100000, new(big.Int).Set(minGasPrice), keyA)); !errors.Is(err, txpool.ErrAccountLimitExceeded) {
|
||||
t.Fatalf("%s: error mismatch: want %v, have %v", name, txpool.ErrAccountLimitExceeded, err)
|
||||
}
|
||||
// Replace by fee.
|
||||
if err := pool.addRemoteSync(pricedTransaction(0, 100000, legacyReplacePrice, keyA)); err != nil {
|
||||
t.Fatalf("%s: failed to replace with remote transaction: %v", name, err)
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "allow-setcode-tx-with-pending-authority-tx",
|
||||
pending: 2,
|
||||
run: func(name string) {
|
||||
// Send two transactions where the first has no conflicting delegations and
|
||||
// the second should be allowed despite conflicting with the authorities in 1).
|
||||
if err := pool.addRemoteSync(setCodeTx(0, keyA, []unsignedAuth{{1, keyC}})); err != nil {
|
||||
t.Fatalf("%s: failed to add with remote setcode transaction: %v", name, err)
|
||||
}
|
||||
if err := pool.addRemoteSync(setCodeTx(0, keyB, []unsignedAuth{{1, keyC}})); err != nil {
|
||||
t.Fatalf("%s: failed to add conflicting delegation: %v", name, err)
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "allow-one-tx-from-pooled-delegation",
|
||||
pending: 2,
|
||||
run: func(name string) {
|
||||
// Verify C cannot originate another transaction when it has a pooled delegation.
|
||||
if err := pool.addRemoteSync(setCodeTx(0, keyA, []unsignedAuth{{0, keyC}})); err != nil {
|
||||
t.Fatalf("%s: failed to add with remote setcode transaction: %v", name, err)
|
||||
}
|
||||
if err := pool.addRemoteSync(pricedTransaction(0, 100000, new(big.Int).Set(minGasPrice), keyC)); err != nil {
|
||||
t.Fatalf("%s: failed to add with pending delegatio: %v", name, err)
|
||||
}
|
||||
// Also check gapped transaction is rejected.
|
||||
if err := pool.addRemoteSync(pricedTransaction(1, 100000, new(big.Int).Set(minGasPrice), keyC)); !errors.Is(err, txpool.ErrAccountLimitExceeded) {
|
||||
t.Fatalf("%s: error mismatch: want %v, have %v", name, txpool.ErrAccountLimitExceeded, err)
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "replace-by-fee-setcode-tx",
|
||||
pending: 1,
|
||||
run: func(name string) {
|
||||
// 4. Fee bump the setcode tx send.
|
||||
if err := pool.addRemoteSync(setCodeTx(0, keyB, []unsignedAuth{{1, keyC}})); err != nil {
|
||||
t.Fatalf("%s: failed to add with remote setcode transaction: %v", name, err)
|
||||
}
|
||||
if err := pool.addRemoteSync(pricedSetCodeTx(0, 250000, doubleGasFee, uint256.NewInt(2), keyB, []unsignedAuth{{0, keyC}})); err != nil {
|
||||
t.Fatalf("%s: failed to add with remote setcode transaction: %v", name, err)
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "allow-tx-from-replaced-authority",
|
||||
pending: 2,
|
||||
run: func(name string) {
|
||||
// Fee bump with a different auth list. Make sure that unlocks the authorities.
|
||||
if err := pool.addRemoteSync(pricedSetCodeTx(0, 250000, minGasFee, uint256.NewInt(3), keyA, []unsignedAuth{{0, keyB}})); err != nil {
|
||||
t.Fatalf("%s: failed to add with remote setcode transaction: %v", name, err)
|
||||
}
|
||||
if err := pool.addRemoteSync(pricedSetCodeTx(0, 250000, tripleGasFee, uint256.NewInt(300), keyA, []unsignedAuth{{0, keyC}})); err != nil {
|
||||
t.Fatalf("%s: failed to add with remote setcode transaction: %v", name, err)
|
||||
}
|
||||
// Now send a regular tx from B.
|
||||
if err := pool.addRemoteSync(pricedTransaction(0, 100000, new(big.Int).Set(minGasPrice), keyB)); err != nil {
|
||||
t.Fatalf("%s: failed to replace with remote transaction: %v", name, err)
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "allow-tx-from-replaced-self-sponsor-authority",
|
||||
pending: 2,
|
||||
run: func(name string) {
|
||||
//
|
||||
if err := pool.addRemoteSync(pricedSetCodeTx(0, 250000, minGasFee, uint256.NewInt(3), keyA, []unsignedAuth{{0, keyA}})); err != nil {
|
||||
t.Fatalf("%s: failed to add with remote setcode transaction: %v", name, err)
|
||||
}
|
||||
if err := pool.addRemoteSync(pricedSetCodeTx(0, 250000, tripleGasFee, uint256.NewInt(30), keyA, []unsignedAuth{{0, keyB}})); err != nil {
|
||||
t.Fatalf("%s: failed to add with remote setcode transaction: %v", name, err)
|
||||
}
|
||||
// Now send a regular tx from keyA.
|
||||
if err := pool.addRemoteSync(pricedTransaction(0, 100000, new(big.Int).Set(legacyReplacePrice), keyA)); err != nil {
|
||||
t.Fatalf("%s: failed to replace with remote transaction: %v", name, err)
|
||||
}
|
||||
// Make sure we can still send from keyB.
|
||||
if err := pool.addRemoteSync(pricedTransaction(0, 100000, new(big.Int).Set(minGasPrice), keyB)); err != nil {
|
||||
t.Fatalf("%s: failed to replace with remote transaction: %v", name, err)
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "track-multiple-conflicting-delegations",
|
||||
pending: 3,
|
||||
run: func(name string) {
|
||||
// Send two setcode txs both with C as an authority.
|
||||
if err := pool.addRemoteSync(pricedSetCodeTx(0, 250000, minGasFee, uint256.NewInt(3), keyA, []unsignedAuth{{0, keyC}})); err != nil {
|
||||
t.Fatalf("%s: failed to add with remote setcode transaction: %v", name, err)
|
||||
}
|
||||
if err := pool.addRemoteSync(pricedSetCodeTx(0, 250000, tripleGasFee, uint256.NewInt(30), keyB, []unsignedAuth{{0, keyC}})); err != nil {
|
||||
t.Fatalf("%s: failed to add with remote setcode transaction: %v", name, err)
|
||||
}
|
||||
// Replace the tx from A with a non-setcode tx.
|
||||
if err := pool.addRemoteSync(pricedTransaction(0, 100000, new(big.Int).Set(legacyReplacePrice), keyA)); err != nil {
|
||||
t.Fatalf("%s: failed to replace with remote transaction: %v", name, err)
|
||||
}
|
||||
// Make sure we can only pool one tx from keyC since it is still a
|
||||
// pending authority.
|
||||
if err := pool.addRemoteSync(pricedTransaction(0, 100000, new(big.Int).Set(minGasPrice), keyC)); err != nil {
|
||||
t.Fatalf("%s: failed to added single pooled for account with pending delegation: %v", name, err)
|
||||
}
|
||||
if err, want := pool.addRemoteSync(pricedTransaction(1, 100000, new(big.Int).Set(minGasPrice), keyC)), txpool.ErrAccountLimitExceeded; !errors.Is(err, want) {
|
||||
t.Fatalf("%s: error mismatch: want %v, have %v", name, want, err)
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "reject-delegation-from-pending-account",
|
||||
pending: 1,
|
||||
run: func(name string) {
|
||||
// Attempt to submit a delegation from an account with a pending tx.
|
||||
if err := pool.addRemoteSync(pricedTransaction(0, 100000, new(big.Int).Set(minGasPrice), keyC)); err != nil {
|
||||
t.Fatalf("%s: failed to add with remote setcode transaction: %v", name, err)
|
||||
}
|
||||
if err, want := pool.addRemoteSync(setCodeTx(0, keyA, []unsignedAuth{{1, keyC}})), txpool.ErrAuthorityReserved; !errors.Is(err, want) {
|
||||
t.Fatalf("%s: error mismatch: want %v, have %v", name, want, err)
|
||||
}
|
||||
},
|
||||
},
|
||||
} {
|
||||
tt.run(tt.name)
|
||||
pending, queued := pool.Stats()
|
||||
if pending != tt.pending {
|
||||
t.Fatalf("%s: pending transactions mismatched: have %d, want %d", tt.name, pending, tt.pending)
|
||||
}
|
||||
if queued != tt.queued {
|
||||
t.Fatalf("%s: queued transactions mismatched: have %d, want %d", tt.name, queued, tt.queued)
|
||||
}
|
||||
if err := validatePoolInternals(pool); err != nil {
|
||||
t.Fatalf("%s: pool internal state corrupted: %v", tt.name, err)
|
||||
}
|
||||
pool.Clear()
|
||||
}
|
||||
}
|
||||
|
||||
// Benchmarks the speed of validating the contents of the pending queue of the
|
||||
// transaction pool.
|
||||
func BenchmarkPendingDemotion100(b *testing.B) { benchmarkPendingDemotion(b, 100) }
|
||||
|
|
|
|||
|
|
@ -57,11 +57,15 @@ func ValidateTransaction(tx *types.Transaction, head *types.Header, signer types
|
|||
return fmt.Errorf("%w: transaction size %v, limit %v", ErrOversizedData, tx.Size(), opts.MaxSize)
|
||||
}
|
||||
// Ensure only transactions that have been enabled are accepted
|
||||
if !opts.Config.IsEIP1559(head.Number) && tx.Type() != types.LegacyTxType {
|
||||
rules := opts.Config.Rules(head.Number)
|
||||
if !rules.IsEIP1559 && tx.Type() != types.LegacyTxType {
|
||||
return fmt.Errorf("%w: type %d rejected, pool not yet in EIP1559", core.ErrTxTypeNotSupported, tx.Type())
|
||||
}
|
||||
if !rules.IsPrague && tx.Type() == types.SetCodeTxType {
|
||||
return fmt.Errorf("%w: type %d rejected, pool not yet in Prague", core.ErrTxTypeNotSupported, tx.Type())
|
||||
}
|
||||
// Check whether the init code size has been exceeded
|
||||
if opts.Config.IsEIP1559(head.Number) && tx.To() == nil && len(tx.Data()) > params.MaxInitCodeSize {
|
||||
if rules.IsEIP1559 && tx.To() == nil && len(tx.Data()) > params.MaxInitCodeSize {
|
||||
return fmt.Errorf("%w: code size %v, limit %v", core.ErrMaxInitCodeSizeExceeded, len(tx.Data()), params.MaxInitCodeSize)
|
||||
}
|
||||
// Transactions can't be negative. This may never happen using RLP decoded
|
||||
|
|
@ -111,13 +115,18 @@ func ValidateTransaction(tx *types.Transaction, head *types.Header, signer types
|
|||
}
|
||||
// Ensure the transaction has more gas than the bare minimum needed to
|
||||
// cover the transaction metadata
|
||||
intrGas, err := core.IntrinsicGas(tx.Data(), tx.AccessList(), tx.SetCodeAuthorizations(), tx.To() == nil, true, opts.Config.IsEIP1559(head.Number))
|
||||
intrGas, err := core.IntrinsicGas(tx.Data(), tx.AccessList(), tx.SetCodeAuthorizations(), tx.To() == nil, true, rules.IsEIP1559)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if tx.Gas() < intrGas {
|
||||
return fmt.Errorf("%w: needed %v, allowed %v", core.ErrIntrinsicGas, intrGas, tx.Gas())
|
||||
}
|
||||
if tx.Type() == types.SetCodeTxType {
|
||||
if len(tx.SetCodeAuthorizations()) == 0 {
|
||||
return fmt.Errorf("set code tx must have at least one authorization tuple")
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
@ -145,6 +154,11 @@ type ValidationOptionsWithState struct {
|
|||
// transaction's cost with the given nonce to check for overdrafts.
|
||||
ExistingCost func(addr common.Address, nonce uint64) *big.Int
|
||||
|
||||
// KnownConflicts is an optional callback which iterates over the list of
|
||||
// addresses and returns all addresses known to the pool with in-flight
|
||||
// transactions.
|
||||
KnownConflicts func(sender common.Address, authorizers []common.Address) []common.Address
|
||||
|
||||
Trc21FeeCapacity map[common.Address]*big.Int
|
||||
|
||||
PendingNonce func(addr common.Address) uint64
|
||||
|
|
@ -221,6 +235,14 @@ func ValidateTransactionWithState(tx *types.Transaction, signer types.Signer, op
|
|||
if used, left := opts.UsedAndLeftSlots(from); left <= 0 {
|
||||
return fmt.Errorf("%w: pooled %d txs", ErrAccountLimitExceeded, used)
|
||||
}
|
||||
|
||||
// Verify no authorizations will invalidate existing transactions known to
|
||||
// the pool.
|
||||
if opts.KnownConflicts != nil {
|
||||
if conflicts := opts.KnownConflicts(from, tx.SetCodeAuthorities()); len(conflicts) > 0 {
|
||||
return fmt.Errorf("%w: authorization conflicts with other known tx", ErrAuthorityReserved)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure sender and receiver are not in black list
|
||||
|
|
|
|||
|
|
@ -400,6 +400,21 @@ func (tx *Transaction) SetCodeAuthorizations() []SetCodeAuthorization {
|
|||
return setcodetx.AuthList
|
||||
}
|
||||
|
||||
// SetCodeAuthorities returns a list of each authorization's corresponding authority.
|
||||
func (tx *Transaction) SetCodeAuthorities() []common.Address {
|
||||
setcodetx, ok := tx.inner.(*SetCodeTx)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
auths := make([]common.Address, 0, len(setcodetx.AuthList))
|
||||
for _, auth := range setcodetx.AuthList {
|
||||
if addr, err := auth.Authority(); err == nil {
|
||||
auths = append(auths, addr)
|
||||
}
|
||||
}
|
||||
return auths
|
||||
}
|
||||
|
||||
// SetTime sets the decoding time of a transaction. This is used by tests to set
|
||||
// arbitrary times and by persistent transaction pools when loading old txs from
|
||||
// disk.
|
||||
|
|
|
|||
|
|
@ -88,7 +88,7 @@ type authorizationMarshaling struct {
|
|||
}
|
||||
|
||||
// SignSetCode creates a signed SetCode authorization.
|
||||
func SignSetCode(auth SetCodeAuthorization, prv *ecdsa.PrivateKey) (SetCodeAuthorization, error) {
|
||||
func SignSetCode(prv *ecdsa.PrivateKey, auth SetCodeAuthorization) (SetCodeAuthorization, error) {
|
||||
sighash := auth.sigHash()
|
||||
sig, err := crypto.Sign(sighash[:], prv)
|
||||
if err != nil {
|
||||
|
|
|
|||
Loading…
Reference in a new issue