core/txpool: add support for setcode tx

This commit is contained in:
lightclient 2024-11-05 16:47:56 +07:00
parent 59d2eec9fc
commit 487fcd8528
No known key found for this signature in database
GPG key ID: 657913021EF45A6A
5 changed files with 322 additions and 19 deletions

View file

@ -60,4 +60,9 @@ var (
// input transaction of non-blob type when a blob transaction from this sender // input transaction of non-blob type when a blob transaction from this sender
// remains pending (and vice-versa). // remains pending (and vice-versa).
ErrAlreadyReserved = errors.New("address already reserved") 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")
) )

View file

@ -209,12 +209,13 @@ type LegacyPool struct {
currentState *state.StateDB // Current state in the blockchain head currentState *state.StateDB // Current state in the blockchain head
pendingNonces *noncer // Pending state tracking virtual nonces pendingNonces *noncer // Pending state tracking virtual nonces
reserve txpool.AddressReserver // Address reserver to ensure exclusivity across subpools reserve txpool.AddressReserver // Address reserver to ensure exclusivity across subpools
pending map[common.Address]*list // All currently processable transactions pending map[common.Address]*list // All currently processable transactions
queue map[common.Address]*list // Queued but non-processable transactions queue map[common.Address]*list // Queued but non-processable transactions
beats map[common.Address]time.Time // Last heartbeat from each known account beats map[common.Address]time.Time // Last heartbeat from each known account
all *lookup // All transactions to allow lookups all *lookup // All transactions to allow lookups
priced *pricedList // All transactions sorted by price priced *pricedList // All transactions sorted by price
auths map[common.Address]*types.Transaction // All accounts with a pooled authorization
reqResetCh chan *txpoolResetRequest reqResetCh chan *txpoolResetRequest
reqPromoteCh chan *accountSet reqPromoteCh chan *accountSet
@ -246,6 +247,7 @@ func New(config Config, chain BlockChain) *LegacyPool {
pending: make(map[common.Address]*list), pending: make(map[common.Address]*list),
queue: make(map[common.Address]*list), queue: make(map[common.Address]*list),
beats: make(map[common.Address]time.Time), beats: make(map[common.Address]time.Time),
auths: make(map[common.Address]*types.Transaction),
all: newLookup(), all: newLookup(),
reqResetCh: make(chan *txpoolResetRequest), reqResetCh: make(chan *txpoolResetRequest),
reqPromoteCh: make(chan *accountSet), reqPromoteCh: make(chan *accountSet),
@ -540,7 +542,8 @@ func (pool *LegacyPool) validateTxBasics(tx *types.Transaction) error {
Accept: 0 | Accept: 0 |
1<<types.LegacyTxType | 1<<types.LegacyTxType |
1<<types.AccessListTxType | 1<<types.AccessListTxType |
1<<types.DynamicFeeTxType, 1<<types.DynamicFeeTxType |
1<<types.SetCodeTxType,
MaxSize: txMaxSize, MaxSize: txMaxSize,
MinTip: pool.gasTip.Load().ToBig(), MinTip: pool.gasTip.Load().ToBig(),
} }
@ -565,6 +568,14 @@ func (pool *LegacyPool) validateTx(tx *types.Transaction) error {
if list := pool.queue[addr]; list != nil { if list := pool.queue[addr]; list != nil {
have += list.Len() have += list.Len()
} }
// Limit the number of setcode tranasactions per account
if pool.currentState.GetCode(addr) != nil {
if have >= 1 {
return have, 0
} else {
return have, 1 - have
}
}
return have, math.MaxInt return have, math.MaxInt
}, },
ExistingExpenditure: func(addr common.Address) *big.Int { ExistingExpenditure: func(addr common.Address) *big.Int {
@ -581,6 +592,28 @@ func (pool *LegacyPool) validateTx(tx *types.Transaction) error {
} }
return nil return nil
}, },
KnownConflicts: func(sender common.Address, addrs []common.Address) []common.Address {
var conflicts []common.Address
if _, ok := pool.auths[sender]; ok {
conflicts = append(conflicts, sender)
}
for _, addr := range addrs {
var known bool
if list := pool.pending[addr]; list != nil {
known = true
}
if list := pool.queue[addr]; list != nil {
known = true
}
if _, ok := pool.auths[addr]; ok {
known = true
}
if known {
conflicts = append(conflicts, addr)
}
}
return conflicts
},
} }
if err := txpool.ValidateTransactionWithState(tx, pool.signer, opts); err != nil { if err := txpool.ValidateTransactionWithState(tx, pool.signer, opts); err != nil {
return err return err
@ -611,6 +644,7 @@ func (pool *LegacyPool) add(tx *types.Transaction) (replaced bool, err error) {
// If the address is not yet known, request exclusivity to track the account // If the address is not yet known, request exclusivity to track the account
// only by this subpool until all transactions are evicted // only by this subpool until all transactions are evicted
// TODO: need to track every authority from setcode txs
var ( var (
_, hasPending = pool.pending[from] _, hasPending = pool.pending[from]
_, hasQueued = pool.queue[from] _, hasQueued = pool.queue[from]
@ -704,11 +738,15 @@ func (pool *LegacyPool) add(tx *types.Transaction) (replaced bool, err error) {
if old != nil { if old != nil {
pool.all.Remove(old.Hash()) pool.all.Remove(old.Hash())
pool.priced.Removed(1) pool.priced.Removed(1)
pool.removeAuthorities(old)
pendingReplaceMeter.Mark(1) pendingReplaceMeter.Mark(1)
} }
pool.all.Add(tx) pool.all.Add(tx)
pool.priced.Put(tx) pool.priced.Put(tx)
pool.queueTxEvent(tx) pool.queueTxEvent(tx)
for _, addr := range tx.Authorities() {
pool.auths[addr] = tx
}
log.Trace("Pooled new executable transaction", "hash", hash, "from", from, "to", tx.To()) log.Trace("Pooled new executable transaction", "hash", hash, "from", from, "to", tx.To())
// Successful promotion, bump the heartbeat // Successful promotion, bump the heartbeat
@ -720,6 +758,9 @@ func (pool *LegacyPool) add(tx *types.Transaction) (replaced bool, err error) {
if err != nil { if err != nil {
return false, err return false, err
} }
for _, addr := range tx.Authorities() {
pool.auths[addr] = tx
}
log.Trace("Pooled new future transaction", "hash", hash, "from", from, "to", tx.To()) log.Trace("Pooled new future transaction", "hash", hash, "from", from, "to", tx.To())
return replaced, nil return replaced, nil
@ -767,6 +808,7 @@ func (pool *LegacyPool) enqueueTx(hash common.Hash, tx *types.Transaction, addAl
// Discard any previous transaction and mark this // Discard any previous transaction and mark this
if old != nil { if old != nil {
pool.all.Remove(old.Hash()) pool.all.Remove(old.Hash())
pool.removeAuthorities(old)
pool.priced.Removed(1) pool.priced.Removed(1)
queuedReplaceMeter.Mark(1) queuedReplaceMeter.Mark(1)
} else { } else {
@ -786,6 +828,10 @@ func (pool *LegacyPool) enqueueTx(hash common.Hash, tx *types.Transaction, addAl
if _, exist := pool.beats[from]; !exist { if _, exist := pool.beats[from]; !exist {
pool.beats[from] = time.Now() pool.beats[from] = time.Now()
} }
for _, auth := range tx.SetCodeAuthorizations() {
addr, _ := auth.Authority()
pool.auths[addr] = tx
}
return old != nil, nil return old != nil, nil
} }
@ -804,6 +850,7 @@ func (pool *LegacyPool) promoteTx(addr common.Address, hash common.Hash, tx *typ
if !inserted { if !inserted {
// An older transaction was better, discard this // An older transaction was better, discard this
pool.all.Remove(hash) pool.all.Remove(hash)
pool.removeAuthorities(tx)
pool.priced.Removed(1) pool.priced.Removed(1)
pendingDiscardMeter.Mark(1) pendingDiscardMeter.Mark(1)
return false return false
@ -811,6 +858,7 @@ func (pool *LegacyPool) promoteTx(addr common.Address, hash common.Hash, tx *typ
// Otherwise discard any previous transaction and mark this // Otherwise discard any previous transaction and mark this
if old != nil { if old != nil {
pool.all.Remove(old.Hash()) pool.all.Remove(old.Hash())
pool.removeAuthorities(old)
pool.priced.Removed(1) pool.priced.Removed(1)
pendingReplaceMeter.Mark(1) pendingReplaceMeter.Mark(1)
} else { } else {
@ -1002,6 +1050,9 @@ func (pool *LegacyPool) removeTx(hash common.Hash, outofbound bool, unreserve bo
if outofbound { if outofbound {
pool.priced.Removed(1) pool.priced.Removed(1)
} }
// Remove any authorities the pool was tracking.
pool.removeAuthorities(tx)
// Remove the transaction from the pending lists and reset the account nonce // Remove the transaction from the pending lists and reset the account nonce
if pending := pool.pending[addr]; pending != nil { if pending := pool.pending[addr]; pending != nil {
if removed, invalids := pending.Remove(tx); removed { if removed, invalids := pending.Remove(tx); removed {
@ -1035,6 +1086,12 @@ func (pool *LegacyPool) removeTx(hash common.Hash, outofbound bool, unreserve bo
return 0 return 0
} }
func (pool *LegacyPool) removeAuthorities(tx *types.Transaction) {
for _, addr := range tx.Authorities() {
delete(pool.auths, addr)
}
}
// requestReset requests a pool reset to the new head block. // requestReset requests a pool reset to the new head block.
// The returned channel is closed when the reset has occurred. // The returned channel is closed when the reset has occurred.
func (pool *LegacyPool) requestReset(oldHead *types.Header, newHead *types.Header) chan struct{} { func (pool *LegacyPool) requestReset(oldHead *types.Header, newHead *types.Header) chan struct{} {
@ -1334,15 +1391,15 @@ func (pool *LegacyPool) promoteExecutables(accounts []common.Address) []*types.T
// Drop all transactions that are deemed too old (low nonce) // Drop all transactions that are deemed too old (low nonce)
forwards := list.Forward(pool.currentState.GetNonce(addr)) forwards := list.Forward(pool.currentState.GetNonce(addr))
for _, tx := range forwards { for _, tx := range forwards {
hash := tx.Hash() pool.all.Remove(tx.Hash())
pool.all.Remove(hash) pool.removeAuthorities(tx)
} }
log.Trace("Removed old queued transactions", "count", len(forwards)) log.Trace("Removed old queued transactions", "count", len(forwards))
// Drop all transactions that are too costly (low balance or out of gas) // Drop all transactions that are too costly (low balance or out of gas)
drops, _ := list.Filter(pool.currentState.GetBalance(addr), gasLimit) drops, _ := list.Filter(pool.currentState.GetBalance(addr), gasLimit)
for _, tx := range drops { for _, tx := range drops {
hash := tx.Hash() pool.all.Remove(tx.Hash())
pool.all.Remove(hash) pool.removeAuthorities(tx)
} }
log.Trace("Removed unpayable queued transactions", "count", len(drops)) log.Trace("Removed unpayable queued transactions", "count", len(drops))
queuedNofundsMeter.Mark(int64(len(drops))) queuedNofundsMeter.Mark(int64(len(drops)))
@ -1363,6 +1420,7 @@ func (pool *LegacyPool) promoteExecutables(accounts []common.Address) []*types.T
for _, tx := range caps { for _, tx := range caps {
hash := tx.Hash() hash := tx.Hash()
pool.all.Remove(hash) pool.all.Remove(hash)
pool.removeAuthorities(tx)
log.Trace("Removed cap-exceeding queued transaction", "hash", hash) log.Trace("Removed cap-exceeding queued transaction", "hash", hash)
} }
queuedRateLimitMeter.Mark(int64(len(caps))) queuedRateLimitMeter.Mark(int64(len(caps)))
@ -1425,6 +1483,7 @@ func (pool *LegacyPool) truncatePending() {
// Drop the transaction from the global pools too // Drop the transaction from the global pools too
hash := tx.Hash() hash := tx.Hash()
pool.all.Remove(hash) pool.all.Remove(hash)
pool.removeAuthorities(tx)
// Update the account nonce to the dropped transaction // Update the account nonce to the dropped transaction
pool.pendingNonces.setIfLower(offenders[i], tx.Nonce()) pool.pendingNonces.setIfLower(offenders[i], tx.Nonce())
@ -1450,6 +1509,7 @@ func (pool *LegacyPool) truncatePending() {
// Drop the transaction from the global pools too // Drop the transaction from the global pools too
hash := tx.Hash() hash := tx.Hash()
pool.all.Remove(hash) pool.all.Remove(hash)
pool.removeAuthorities(tx)
// Update the account nonce to the dropped transaction // Update the account nonce to the dropped transaction
pool.pendingNonces.setIfLower(addr, tx.Nonce()) pool.pendingNonces.setIfLower(addr, tx.Nonce())
@ -1525,14 +1585,16 @@ func (pool *LegacyPool) demoteUnexecutables() {
for _, tx := range olds { for _, tx := range olds {
hash := tx.Hash() hash := tx.Hash()
pool.all.Remove(hash) pool.all.Remove(hash)
pool.removeAuthorities(tx)
log.Trace("Removed old pending transaction", "hash", hash) log.Trace("Removed old pending transaction", "hash", hash)
} }
// Drop all transactions that are too costly (low balance or out of gas), and queue any invalids back for later // Drop all transactions that are too costly (low balance or out of gas), and queue any invalids back for later
drops, invalids := list.Filter(pool.currentState.GetBalance(addr), gasLimit) drops, invalids := list.Filter(pool.currentState.GetBalance(addr), gasLimit)
for _, tx := range drops { for _, tx := range drops {
hash := tx.Hash() hash := tx.Hash()
log.Trace("Removed unpayable pending transaction", "hash", hash)
pool.all.Remove(hash) pool.all.Remove(hash)
pool.removeAuthorities(tx)
log.Trace("Removed unpayable pending transaction", "hash", hash)
} }
pendingNofundsMeter.Mark(int64(len(drops))) pendingNofundsMeter.Mark(int64(len(drops)))
@ -1761,4 +1823,5 @@ func (pool *LegacyPool) Clear() {
pool.pending = make(map[common.Address]*list) pool.pending = make(map[common.Address]*list)
pool.queue = make(map[common.Address]*list) pool.queue = make(map[common.Address]*list)
pool.pendingNonces = newNoncer(pool.currentState) pool.pendingNonces = newNoncer(pool.currentState)
pool.auths = make(map[common.Address]*types.Transaction)
} }

View file

@ -34,6 +34,7 @@ import (
"github.com/ethereum/go-ethereum/core/tracing" "github.com/ethereum/go-ethereum/core/tracing"
"github.com/ethereum/go-ethereum/core/txpool" "github.com/ethereum/go-ethereum/core/txpool"
"github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/core/vm"
"github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/event" "github.com/ethereum/go-ethereum/event"
"github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/params"
@ -79,8 +80,9 @@ func (bc *testBlockChain) Config() *params.ChainConfig {
func (bc *testBlockChain) CurrentBlock() *types.Header { func (bc *testBlockChain) CurrentBlock() *types.Header {
return &types.Header{ return &types.Header{
Number: new(big.Int), Number: new(big.Int),
GasLimit: bc.gasLimit.Load(), Difficulty: common.Big0,
GasLimit: bc.gasLimit.Load(),
} }
} }
@ -128,6 +130,39 @@ func dynamicFeeTx(nonce uint64, gaslimit uint64, gasFee *big.Int, tip *big.Int,
return tx 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.NewInt(1000), 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 { func makeAddressReserver() txpool.AddressReserver {
var ( var (
reserved = make(map[common.Address]struct{}) reserved = make(map[common.Address]struct{})
@ -2163,6 +2198,169 @@ 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(testTxPoolConfig.PriceLimit, blockchain.CurrentBlock(), makeAddressReserver())
defer pool.Close()
// Create the test accounts
keys := make([]*ecdsa.PrivateKey, 4)
addrs := make([]common.Address, len(keys))
for i := 0; i < len(keys); i++ {
keys[i], _ = crypto.GenerateKey()
addrs[i] = crypto.PubkeyToAddress(keys[i].PublicKey)
testAddBalance(pool, crypto.PubkeyToAddress(keys[i].PublicKey), big.NewInt(params.Ether))
}
// A few situations to test:
// 1. Accounts with delegation set can only have one in-flight transaction.
// 2. Setcode tx should be rejected if any authority has a known pooled tx.
// 3. New txs from senders with pooled delegations should not be accepted.
// 4. Ensure setcode tx can replace itself provided the fee bump is enough.
// 5. Make sure that if a setcode tx is replaced, the auths associated with
// the tx are removed.
// 5.1. This should also work when a self-sponsored setcode tx attempts
// to replace itself.
// make sure auth list recreated correctly after full reorg?
// (in different test?) verify that a setcode tx cannot invalidate a blob tx.
for _, tt := range []struct {
name string
pending int
queued int
run func() error
}{
{
name: "only-one-in-flight",
pending: 1,
run: func() error {
// Check that only one in-flight transaction is allowed for accounts
// with delegation set. Also verify the accepted transaction can be
// replaced by fee.
aa := common.Address{0xaa, 0xaa}
statedb.SetCode(addrs[0], 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, big.NewInt(1), keys[0])); err != nil {
return fmt.Errorf("failed to add remote transaction: %v", err)
}
if err := pool.addRemoteSync(pricedTransaction(1, 100000, big.NewInt(1), keys[0])); !errors.Is(err, txpool.ErrAccountLimitExceeded) {
return fmt.Errorf("error mismatch: want %v, have %v", txpool.ErrAccountLimitExceeded, err)
}
// Also check gapped transaction.
if err := pool.addRemoteSync(pricedTransaction(2, 100000, big.NewInt(1), keys[0])); !errors.Is(err, txpool.ErrAccountLimitExceeded) {
return fmt.Errorf("error mismatch: want %v, have %v", txpool.ErrAccountLimitExceeded, err)
}
// Replace by fee.
if err := pool.addRemoteSync(pricedTransaction(0, 100000, big.NewInt(10), keys[0])); err != nil {
return fmt.Errorf("failed to replace with remote transaction: %v", err)
}
return nil
},
},
{
name: "reject-setcode-tx-with-pending-authority-tx",
pending: 1,
run: func() error {
// Send two transactions where the first has no conflicting delegations and
// the second should be rejected due to a conflict with the tx sent in 1).
if err := pool.addRemoteSync(setCodeTx(0, keys[1], []unsignedAuth{{1, keys[2]}})); err != nil {
return fmt.Errorf("failed to add with remote setcode transaction: %v", err)
}
if err := pool.addRemoteSync(setCodeTx(1, keys[1], []unsignedAuth{{1, keys[2]}})); !errors.Is(err, txpool.ErrAuthorityReserved) {
return fmt.Errorf("error mismatch: want %v, have %v", txpool.ErrAuthorityReserved, err)
}
return nil
},
},
{
name: "reject-tx-from-pooled-delegation",
pending: 1,
run: func() error {
// Verify key[2] cannot originate another transaction when it has a pooled delegation.
if err := pool.addRemoteSync(setCodeTx(0, keys[0], []unsignedAuth{{0, keys[2]}})); err != nil {
return fmt.Errorf("failed to add with remote setcode transaction: %v", err)
}
if err := pool.addRemoteSync(pricedTransaction(0, 100000, big.NewInt(1), keys[2])); !errors.Is(err, txpool.ErrAuthorityReserved) {
return fmt.Errorf("error mismatch: want %v, have %v", txpool.ErrAuthorityReserved, err)
}
// Also check gapped transaction is rejected.
if err := pool.addRemoteSync(pricedTransaction(1, 100000, big.NewInt(1), keys[2])); !errors.Is(err, txpool.ErrAuthorityReserved) {
return fmt.Errorf("error mismatch: want %v, have %v", txpool.ErrAuthorityReserved, err)
}
return nil
},
},
{
name: "replace-by-fee-setcode-tx",
pending: 1,
run: func() error {
// 4. Fee bump the setcode tx send.
if err := pool.addRemoteSync(setCodeTx(0, keys[1], []unsignedAuth{{1, keys[2]}})); err != nil {
return fmt.Errorf("failed to add with remote setcode transaction: %v", err)
}
if err := pool.addRemoteSync(pricedSetCodeTx(0, 250000, uint256.NewInt(2000), uint256.NewInt(2), keys[1], []unsignedAuth{{0, keys[2]}})); err != nil {
t.Fatalf("failed to add with remote setcode transaction: %v", err)
}
return nil
},
},
{
name: "allow-tx-from-replaced-authority",
pending: 2,
run: func() error {
// Fee bump with a different auth list. Make sure that unlocks the authorities.
if err := pool.addRemoteSync(pricedSetCodeTx(0, 250000, uint256.NewInt(10), uint256.NewInt(3), keys[0], []unsignedAuth{{0, keys[1]}})); err != nil {
t.Fatalf("failed to add with remote setcode transaction: %v", err)
}
if err := pool.addRemoteSync(pricedSetCodeTx(0, 250000, uint256.NewInt(3000), uint256.NewInt(300), keys[0], []unsignedAuth{{0, keys[2]}})); err != nil {
t.Fatalf("failed to add with remote setcode transaction: %v", err)
}
fmt.Println(pool.auths)
// Now send a regular tx from keys[1].
if err := pool.addRemoteSync(pricedTransaction(0, 100000, big.NewInt(10), keys[1])); err != nil {
t.Fatalf("failed to replace with remote transaction: %v", err)
}
return nil
},
},
} {
if err := tt.run(); err != nil {
t.Fatalf("%s: %v", tt.name, err)
}
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()
}
/*
pool.Clear()
// if err := pool.addRemoteSync(setCodeTx(1, keys[3], []unsignedAuth{{1, keys[2]}})); !errors.Is(err, txpool.ErrAuthorityReserved) {
// t.Fatalf("expected to reject tx from in-flight authority: want %v, have %v", txpool.ErrAuthorityReserved, err)
// }
*/
}
// Benchmarks the speed of validating the contents of the pending queue of the // Benchmarks the speed of validating the contents of the pending queue of the
// transaction pool. // transaction pool.
func BenchmarkPendingDemotion100(b *testing.B) { benchmarkPendingDemotion(b, 100) } func BenchmarkPendingDemotion100(b *testing.B) { benchmarkPendingDemotion(b, 100) }

View file

@ -70,17 +70,21 @@ func ValidateTransaction(tx *types.Transaction, head *types.Header, signer types
return fmt.Errorf("%w: transaction size %v, limit %v", ErrOversizedData, tx.Size(), opts.MaxSize) return fmt.Errorf("%w: transaction size %v, limit %v", ErrOversizedData, tx.Size(), opts.MaxSize)
} }
// Ensure only transactions that have been enabled are accepted // Ensure only transactions that have been enabled are accepted
if !opts.Config.IsBerlin(head.Number) && tx.Type() != types.LegacyTxType { rules := opts.Config.Rules(head.Number, head.Difficulty.Sign() == 0, head.Time)
if !rules.IsBerlin && tx.Type() != types.LegacyTxType {
return fmt.Errorf("%w: type %d rejected, pool not yet in Berlin", core.ErrTxTypeNotSupported, tx.Type()) return fmt.Errorf("%w: type %d rejected, pool not yet in Berlin", core.ErrTxTypeNotSupported, tx.Type())
} }
if !opts.Config.IsLondon(head.Number) && tx.Type() == types.DynamicFeeTxType { if !rules.IsLondon && tx.Type() == types.DynamicFeeTxType {
return fmt.Errorf("%w: type %d rejected, pool not yet in London", core.ErrTxTypeNotSupported, tx.Type()) return fmt.Errorf("%w: type %d rejected, pool not yet in London", core.ErrTxTypeNotSupported, tx.Type())
} }
if !opts.Config.IsCancun(head.Number, head.Time) && tx.Type() == types.BlobTxType { if !rules.IsCancun && tx.Type() == types.BlobTxType {
return fmt.Errorf("%w: type %d rejected, pool not yet in Cancun", core.ErrTxTypeNotSupported, tx.Type()) return fmt.Errorf("%w: type %d rejected, pool not yet in Cancun", 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 // Check whether the init code size has been exceeded
if opts.Config.IsShanghai(head.Number, head.Time) && tx.To() == nil && len(tx.Data()) > params.MaxInitCodeSize { if rules.IsShanghai && tx.To() == nil && len(tx.Data()) > params.MaxInitCodeSize {
return fmt.Errorf("%w: code size %v, limit %v", core.ErrMaxInitCodeSizeExceeded, 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 // Transactions can't be negative. This may never happen using RLP decoded
@ -109,7 +113,7 @@ func ValidateTransaction(tx *types.Transaction, head *types.Header, signer types
} }
// Ensure the transaction has more gas than the bare minimum needed to cover // Ensure the transaction has more gas than the bare minimum needed to cover
// the transaction metadata // the transaction metadata
intrGas, err := core.IntrinsicGas(tx.Data(), tx.AccessList(), tx.SetCodeAuthorizations(), tx.To() == nil, true, opts.Config.IsIstanbul(head.Number), opts.Config.IsShanghai(head.Number, head.Time)) intrGas, err := core.IntrinsicGas(tx.Data(), tx.AccessList(), tx.SetCodeAuthorizations(), tx.To() == nil, true, rules.IsIstanbul, rules.IsShanghai)
if err != nil { if err != nil {
return err return err
} }
@ -154,6 +158,11 @@ func ValidateTransaction(tx *types.Transaction, head *types.Header, signer types
return err return err
} }
} }
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 return nil
} }
@ -209,6 +218,11 @@ type ValidationOptionsWithState struct {
// ExistingCost is a mandatory callback to retrieve an already pooled // ExistingCost is a mandatory callback to retrieve an already pooled
// transaction's cost with the given nonce to check for overdrafts. // transaction's cost with the given nonce to check for overdrafts.
ExistingCost func(addr common.Address, nonce uint64) *big.Int 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
} }
// ValidateTransactionWithState is a helper method to check whether a transaction // ValidateTransactionWithState is a helper method to check whether a transaction
@ -262,6 +276,14 @@ func ValidateTransactionWithState(tx *types.Transaction, signer types.Signer, op
if used, left := opts.UsedAndLeftSlots(from); left <= 0 { if used, left := opts.UsedAndLeftSlots(from); left <= 0 {
return fmt.Errorf("%w: pooled %d txs", ErrAccountLimitExceeded, used) 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.Authorities()); len(conflicts) > 0 {
return fmt.Errorf("%w: authorization conflicts with other known tx", ErrAuthorityReserved)
}
}
} }
return nil return nil
} }

View file

@ -483,6 +483,21 @@ func (tx *Transaction) SetCodeAuthorizations() []SetCodeAuthorization {
return setcodetx.AuthList return setcodetx.AuthList
} }
// Authorities returns a list of each authorization's corresponding authority.
func (tx *Transaction) Authorities() []common.Address {
setcodetx, ok := tx.inner.(*SetCodeTx)
if !ok {
return nil
}
auths := make([]common.Address, 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 // 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 // arbitrary times and by persistent transaction pools when loading old txs from
// disk. // disk.