mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-08-17 17:33:47 +00:00
core/txpool: add support for setcode tx
This commit is contained in:
parent
59d2eec9fc
commit
487fcd8528
5 changed files with 322 additions and 19 deletions
|
|
@ -60,4 +60,9 @@ var (
|
|||
// input transaction of non-blob type when a blob transaction from this sender
|
||||
// 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")
|
||||
)
|
||||
|
|
|
|||
|
|
@ -215,6 +215,7 @@ type LegacyPool struct {
|
|||
beats map[common.Address]time.Time // Last heartbeat from each known account
|
||||
all *lookup // All transactions to allow lookups
|
||||
priced *pricedList // All transactions sorted by price
|
||||
auths map[common.Address]*types.Transaction // All accounts with a pooled authorization
|
||||
|
||||
reqResetCh chan *txpoolResetRequest
|
||||
reqPromoteCh chan *accountSet
|
||||
|
|
@ -246,6 +247,7 @@ func New(config Config, chain BlockChain) *LegacyPool {
|
|||
pending: make(map[common.Address]*list),
|
||||
queue: make(map[common.Address]*list),
|
||||
beats: make(map[common.Address]time.Time),
|
||||
auths: make(map[common.Address]*types.Transaction),
|
||||
all: newLookup(),
|
||||
reqResetCh: make(chan *txpoolResetRequest),
|
||||
reqPromoteCh: make(chan *accountSet),
|
||||
|
|
@ -540,7 +542,8 @@ func (pool *LegacyPool) validateTxBasics(tx *types.Transaction) error {
|
|||
Accept: 0 |
|
||||
1<<types.LegacyTxType |
|
||||
1<<types.AccessListTxType |
|
||||
1<<types.DynamicFeeTxType,
|
||||
1<<types.DynamicFeeTxType |
|
||||
1<<types.SetCodeTxType,
|
||||
MaxSize: txMaxSize,
|
||||
MinTip: pool.gasTip.Load().ToBig(),
|
||||
}
|
||||
|
|
@ -565,6 +568,14 @@ func (pool *LegacyPool) validateTx(tx *types.Transaction) error {
|
|||
if list := pool.queue[addr]; list != nil {
|
||||
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
|
||||
},
|
||||
ExistingExpenditure: func(addr common.Address) *big.Int {
|
||||
|
|
@ -581,6 +592,28 @@ func (pool *LegacyPool) validateTx(tx *types.Transaction) error {
|
|||
}
|
||||
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 {
|
||||
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
|
||||
// only by this subpool until all transactions are evicted
|
||||
// TODO: need to track every authority from setcode txs
|
||||
var (
|
||||
_, hasPending = pool.pending[from]
|
||||
_, hasQueued = pool.queue[from]
|
||||
|
|
@ -704,11 +738,15 @@ func (pool *LegacyPool) add(tx *types.Transaction) (replaced bool, err error) {
|
|||
if old != nil {
|
||||
pool.all.Remove(old.Hash())
|
||||
pool.priced.Removed(1)
|
||||
pool.removeAuthorities(old)
|
||||
pendingReplaceMeter.Mark(1)
|
||||
}
|
||||
pool.all.Add(tx)
|
||||
pool.priced.Put(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())
|
||||
|
||||
// Successful promotion, bump the heartbeat
|
||||
|
|
@ -720,6 +758,9 @@ func (pool *LegacyPool) add(tx *types.Transaction) (replaced bool, err error) {
|
|||
if err != nil {
|
||||
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())
|
||||
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
|
||||
if old != nil {
|
||||
pool.all.Remove(old.Hash())
|
||||
pool.removeAuthorities(old)
|
||||
pool.priced.Removed(1)
|
||||
queuedReplaceMeter.Mark(1)
|
||||
} else {
|
||||
|
|
@ -786,6 +828,10 @@ func (pool *LegacyPool) enqueueTx(hash common.Hash, tx *types.Transaction, addAl
|
|||
if _, exist := pool.beats[from]; !exist {
|
||||
pool.beats[from] = time.Now()
|
||||
}
|
||||
for _, auth := range tx.SetCodeAuthorizations() {
|
||||
addr, _ := auth.Authority()
|
||||
pool.auths[addr] = tx
|
||||
}
|
||||
return old != nil, nil
|
||||
}
|
||||
|
||||
|
|
@ -804,6 +850,7 @@ func (pool *LegacyPool) promoteTx(addr common.Address, hash common.Hash, tx *typ
|
|||
if !inserted {
|
||||
// An older transaction was better, discard this
|
||||
pool.all.Remove(hash)
|
||||
pool.removeAuthorities(tx)
|
||||
pool.priced.Removed(1)
|
||||
pendingDiscardMeter.Mark(1)
|
||||
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
|
||||
if old != nil {
|
||||
pool.all.Remove(old.Hash())
|
||||
pool.removeAuthorities(old)
|
||||
pool.priced.Removed(1)
|
||||
pendingReplaceMeter.Mark(1)
|
||||
} else {
|
||||
|
|
@ -1002,6 +1050,9 @@ func (pool *LegacyPool) removeTx(hash common.Hash, outofbound bool, unreserve bo
|
|||
if outofbound {
|
||||
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
|
||||
if pending := pool.pending[addr]; pending != nil {
|
||||
if removed, invalids := pending.Remove(tx); removed {
|
||||
|
|
@ -1035,6 +1086,12 @@ func (pool *LegacyPool) removeTx(hash common.Hash, outofbound bool, unreserve bo
|
|||
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.
|
||||
// The returned channel is closed when the reset has occurred.
|
||||
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)
|
||||
forwards := list.Forward(pool.currentState.GetNonce(addr))
|
||||
for _, tx := range forwards {
|
||||
hash := tx.Hash()
|
||||
pool.all.Remove(hash)
|
||||
pool.all.Remove(tx.Hash())
|
||||
pool.removeAuthorities(tx)
|
||||
}
|
||||
log.Trace("Removed old queued transactions", "count", len(forwards))
|
||||
// Drop all transactions that are too costly (low balance or out of gas)
|
||||
drops, _ := list.Filter(pool.currentState.GetBalance(addr), gasLimit)
|
||||
for _, tx := range drops {
|
||||
hash := tx.Hash()
|
||||
pool.all.Remove(hash)
|
||||
pool.all.Remove(tx.Hash())
|
||||
pool.removeAuthorities(tx)
|
||||
}
|
||||
log.Trace("Removed unpayable queued transactions", "count", len(drops))
|
||||
queuedNofundsMeter.Mark(int64(len(drops)))
|
||||
|
|
@ -1363,6 +1420,7 @@ func (pool *LegacyPool) promoteExecutables(accounts []common.Address) []*types.T
|
|||
for _, tx := range caps {
|
||||
hash := tx.Hash()
|
||||
pool.all.Remove(hash)
|
||||
pool.removeAuthorities(tx)
|
||||
log.Trace("Removed cap-exceeding queued transaction", "hash", hash)
|
||||
}
|
||||
queuedRateLimitMeter.Mark(int64(len(caps)))
|
||||
|
|
@ -1425,6 +1483,7 @@ func (pool *LegacyPool) truncatePending() {
|
|||
// Drop the transaction from the global pools too
|
||||
hash := tx.Hash()
|
||||
pool.all.Remove(hash)
|
||||
pool.removeAuthorities(tx)
|
||||
|
||||
// Update the account nonce to the dropped transaction
|
||||
pool.pendingNonces.setIfLower(offenders[i], tx.Nonce())
|
||||
|
|
@ -1450,6 +1509,7 @@ func (pool *LegacyPool) truncatePending() {
|
|||
// Drop the transaction from the global pools too
|
||||
hash := tx.Hash()
|
||||
pool.all.Remove(hash)
|
||||
pool.removeAuthorities(tx)
|
||||
|
||||
// Update the account nonce to the dropped transaction
|
||||
pool.pendingNonces.setIfLower(addr, tx.Nonce())
|
||||
|
|
@ -1525,14 +1585,16 @@ func (pool *LegacyPool) demoteUnexecutables() {
|
|||
for _, tx := range olds {
|
||||
hash := tx.Hash()
|
||||
pool.all.Remove(hash)
|
||||
pool.removeAuthorities(tx)
|
||||
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
|
||||
drops, invalids := list.Filter(pool.currentState.GetBalance(addr), gasLimit)
|
||||
for _, tx := range drops {
|
||||
hash := tx.Hash()
|
||||
log.Trace("Removed unpayable pending transaction", "hash", hash)
|
||||
pool.all.Remove(hash)
|
||||
pool.removeAuthorities(tx)
|
||||
log.Trace("Removed unpayable pending transaction", "hash", hash)
|
||||
}
|
||||
pendingNofundsMeter.Mark(int64(len(drops)))
|
||||
|
||||
|
|
@ -1761,4 +1823,5 @@ func (pool *LegacyPool) Clear() {
|
|||
pool.pending = make(map[common.Address]*list)
|
||||
pool.queue = make(map[common.Address]*list)
|
||||
pool.pendingNonces = newNoncer(pool.currentState)
|
||||
pool.auths = make(map[common.Address]*types.Transaction)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -34,6 +34,7 @@ import (
|
|||
"github.com/ethereum/go-ethereum/core/tracing"
|
||||
"github.com/ethereum/go-ethereum/core/txpool"
|
||||
"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/event"
|
||||
"github.com/ethereum/go-ethereum/params"
|
||||
|
|
@ -80,6 +81,7 @@ func (bc *testBlockChain) Config() *params.ChainConfig {
|
|||
func (bc *testBlockChain) CurrentBlock() *types.Header {
|
||||
return &types.Header{
|
||||
Number: new(big.Int),
|
||||
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
|
||||
}
|
||||
|
||||
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 {
|
||||
var (
|
||||
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
|
||||
// transaction pool.
|
||||
func BenchmarkPendingDemotion100(b *testing.B) { benchmarkPendingDemotion(b, 100) }
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
// 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())
|
||||
}
|
||||
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())
|
||||
}
|
||||
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())
|
||||
}
|
||||
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.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)
|
||||
}
|
||||
// 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
|
||||
// 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 {
|
||||
return err
|
||||
}
|
||||
|
|
@ -154,6 +158,11 @@ func ValidateTransaction(tx *types.Transaction, head *types.Header, signer types
|
|||
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
|
||||
}
|
||||
|
||||
|
|
@ -209,6 +218,11 @@ type ValidationOptionsWithState struct {
|
|||
// ExistingCost is a mandatory callback to retrieve an already pooled
|
||||
// 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
|
||||
}
|
||||
|
||||
// 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 {
|
||||
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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -483,6 +483,21 @@ func (tx *Transaction) SetCodeAuthorizations() []SetCodeAuthorization {
|
|||
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
|
||||
// arbitrary times and by persistent transaction pools when loading old txs from
|
||||
// disk.
|
||||
|
|
|
|||
Loading…
Reference in a new issue