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
|
// 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")
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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) }
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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.
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue