core: rework tx auth cache

This commit is contained in:
lightclient 2025-05-23 12:03:21 -06:00
parent 95c732cfac
commit df0f945f6d
No known key found for this signature in database
GPG key ID: 657913021EF45A6A
6 changed files with 57 additions and 86 deletions

View file

@ -156,7 +156,7 @@ type Message struct {
BlobGasFeeCap *big.Int BlobGasFeeCap *big.Int
BlobHashes []common.Hash BlobHashes []common.Hash
SetCodeAuthorizations []types.SetCodeAuthorization SetCodeAuthorizations []types.SetCodeAuthorization
AuthorityCache *types.AuthorityCache Authorities []*common.Address // Recovered authority addresses
// When SkipNonceChecks is true, the message nonce is not checked against the // When SkipNonceChecks is true, the message nonce is not checked against the
// account nonce in state. // account nonce in state.
@ -186,7 +186,7 @@ func TransactionToMessage(tx *types.Transaction, s types.Signer, baseFee *big.In
Data: tx.Data(), Data: tx.Data(),
AccessList: tx.AccessList(), AccessList: tx.AccessList(),
SetCodeAuthorizations: tx.SetCodeAuthorizations(), SetCodeAuthorizations: tx.SetCodeAuthorizations(),
AuthorityCache: tx.AuthorityCache(), Authorities: tx.SetCodeAuthorities(),
SkipNonceChecks: false, SkipNonceChecks: false,
SkipTransactionChecks: false, SkipTransactionChecks: false,
BlobHashes: tx.BlobHashes(), BlobHashes: tx.BlobHashes(),
@ -507,12 +507,12 @@ func (st *stateTransition) execute() (*ExecutionResult, error) {
// Apply EIP-7702 authorizations. // Apply EIP-7702 authorizations.
if msg.SetCodeAuthorizations != nil { if msg.SetCodeAuthorizations != nil {
for i, auth := range msg.SetCodeAuthorizations { for i, auth := range msg.SetCodeAuthorizations {
// Note errors are ignored, we simply skip invalid authorizations here. addr := msg.Authorities[i]
authority, err := msg.AuthorityCache.Authority(i) if addr == nil {
if err != nil { // Skip invalid authorizations.
continue continue
} }
st.applyAuthorization(&auth, authority) st.applyAuthorization(&auth, *addr)
} }
} }
@ -580,18 +580,16 @@ func (st *stateTransition) execute() (*ExecutionResult, error) {
} }
// validateAuthorization validates an EIP-7702 authorization against the state. // validateAuthorization validates an EIP-7702 authorization against the state.
func (st *stateTransition) validateAuthorization(auth *types.SetCodeAuthorization, preDerivedAuthority common.Address) (authority common.Address, err error) { func (st *stateTransition) validateAuthorization(auth *types.SetCodeAuthorization, authority common.Address) error {
// Verify chain ID is null or equal to current chain ID. // Verify chain ID is null or equal to current chain ID.
if !auth.ChainID.IsZero() && auth.ChainID.CmpBig(st.evm.ChainConfig().ChainID) != 0 { if !auth.ChainID.IsZero() && auth.ChainID.CmpBig(st.evm.ChainConfig().ChainID) != 0 {
return authority, ErrAuthorizationWrongChainID return ErrAuthorizationWrongChainID
} }
// Limit nonce to 2^64-1 per EIP-2681. // Limit nonce to 2^64-1 per EIP-2681.
if auth.Nonce+1 < auth.Nonce { if auth.Nonce+1 < auth.Nonce {
return authority, ErrAuthorizationNonceOverflow return ErrAuthorizationNonceOverflow
} }
authority = preDerivedAuthority
// Check the authority account // Check the authority account
// 1) doesn't have code or has exisiting delegation // 1) doesn't have code or has exisiting delegation
// 2) matches the auth's nonce // 2) matches the auth's nonce
@ -600,17 +598,17 @@ func (st *stateTransition) validateAuthorization(auth *types.SetCodeAuthorizatio
st.state.AddAddressToAccessList(authority) st.state.AddAddressToAccessList(authority)
code := st.state.GetCode(authority) code := st.state.GetCode(authority)
if _, ok := types.ParseDelegation(code); len(code) != 0 && !ok { if _, ok := types.ParseDelegation(code); len(code) != 0 && !ok {
return authority, ErrAuthorizationDestinationHasCode return ErrAuthorizationDestinationHasCode
} }
if have := st.state.GetNonce(authority); have != auth.Nonce { if have := st.state.GetNonce(authority); have != auth.Nonce {
return authority, ErrAuthorizationNonceMismatch return ErrAuthorizationNonceMismatch
} }
return authority, nil return nil
} }
// applyAuthorization applies an EIP-7702 code delegation to the state. // applyAuthorization applies an EIP-7702 code delegation to the state.
func (st *stateTransition) applyAuthorization(auth *types.SetCodeAuthorization, preDerivedAuthority common.Address) error { func (st *stateTransition) applyAuthorization(auth *types.SetCodeAuthorization, authority common.Address) error {
authority, err := st.validateAuthorization(auth, preDerivedAuthority) err := st.validateAuthorization(auth, authority)
if err != nil { if err != nil {
return err return err
} }

View file

@ -619,7 +619,7 @@ func (pool *LegacyPool) validateAuth(tx *types.Transaction) error {
} }
// For symmetry, allow at most one in-flight tx for any authority with a // For symmetry, allow at most one in-flight tx for any authority with a
// pending transaction. // pending transaction.
if auths := tx.SetCodeAuthorities(); len(auths) > 0 { if auths := tx.UniqueSetCodeAuthorities(); len(auths) > 0 {
for _, auth := range auths { for _, auth := range auths {
var count int var count int
if pending := pool.pending[auth]; pending != nil { if pending := pool.pending[auth]; pending != nil {
@ -1748,7 +1748,7 @@ func (t *lookup) TxsBelowTip(threshold *big.Int) types.Transactions {
// addAuthorities tracks the supplied tx in relation to each authority it // addAuthorities tracks the supplied tx in relation to each authority it
// specifies. // specifies.
func (t *lookup) addAuthorities(tx *types.Transaction) { func (t *lookup) addAuthorities(tx *types.Transaction) {
for _, addr := range tx.SetCodeAuthorities() { for _, addr := range tx.UniqueSetCodeAuthorities() {
list, ok := t.auths[addr] list, ok := t.auths[addr]
if !ok { if !ok {
list = []common.Hash{} list = []common.Hash{}
@ -1766,7 +1766,7 @@ func (t *lookup) addAuthorities(tx *types.Transaction) {
// authorities. // authorities.
func (t *lookup) removeAuthorities(tx *types.Transaction) { func (t *lookup) removeAuthorities(tx *types.Transaction) {
hash := tx.Hash() hash := tx.Hash()
for _, addr := range tx.SetCodeAuthorities() { for _, addr := range tx.UniqueSetCodeAuthorities() {
list := t.auths[addr] list := t.auths[addr]
// Remove tx from tracker. // Remove tx from tracker.
if i := slices.Index(list, hash); i >= 0 { if i := slices.Index(list, hash); i >= 0 {

View file

@ -273,7 +273,7 @@ func validatePoolInternals(pool *LegacyPool) error {
} }
// Ensure all auths in pool are tracked // Ensure all auths in pool are tracked
for _, tx := range pool.all.txs { for _, tx := range pool.all.txs {
for _, addr := range tx.SetCodeAuthorities() { for _, addr := range tx.UniqueSetCodeAuthorities() {
list := pool.all.auths[addr] list := pool.all.auths[addr]
if i := slices.Index(list, tx.Hash()); i < 0 { if i := slices.Index(list, tx.Hash()); i < 0 {
return fmt.Errorf("authority not tracked: addr %s, tx %s", addr, tx.Hash()) return fmt.Errorf("authority not tracked: addr %s, tx %s", addr, tx.Hash())

View file

@ -58,9 +58,10 @@ type Transaction struct {
time time.Time // Time first seen locally (spam avoidance) time time.Time // Time first seen locally (spam avoidance)
// caches // caches
hash atomic.Pointer[common.Hash] hash atomic.Pointer[common.Hash]
size atomic.Uint64 size atomic.Uint64
from atomic.Pointer[sigCache] from atomic.Pointer[sigCache]
auths atomic.Pointer[authCache]
} }
// NewTx creates a new transaction. // NewTx creates a new transaction.
@ -512,32 +513,36 @@ func (tx *Transaction) SetCodeAuthorizations() []SetCodeAuthorization {
return setcodetx.AuthList return setcodetx.AuthList
} }
// SetCodeAuthorities returns a list of unique authorities from the // SetCodeAuthorities returns a list of authorities from the authorization list.
// authorization list. // Nil is used to represent authorizations that fail derivation.
func (tx *Transaction) SetCodeAuthorities() []common.Address { func (tx *Transaction) SetCodeAuthorities() []*common.Address {
setcodetx, ok := tx.inner.(*SetCodeTx) setcodetx, ok := tx.inner.(*SetCodeTx)
if !ok { if !ok {
return nil return nil
} }
if authorityCache := setcodetx.authorityCache.Load(); authorityCache != nil { if cache := tx.auths.Load(); cache != nil {
return authorityCache.authorities return *cache
} }
cache := ToAuthorityCache(setcodetx.AuthList) cache := setcodetx.deriveAuthorities()
setcodetx.authorityCache.Store(cache) tx.auths.Store(&cache)
return cache.authorities return cache
} }
func (tx *Transaction) AuthorityCache() *AuthorityCache { // UniqueSetCodeAuthorities returns a list of unique authorities from the
setcodetx, ok := tx.inner.(*SetCodeTx) // authorization list.
if !ok { func (tx *Transaction) UniqueSetCodeAuthorities() []common.Address {
return nil var (
auths = tx.SetCodeAuthorities()
marks = make(map[common.Address]bool)
uniques []common.Address
)
for _, auth := range auths {
if auth != nil && !marks[*auth] {
marks[*auth] = true
uniques = append(uniques, *auth)
}
} }
if authorityCache := setcodetx.authorityCache.Load(); authorityCache != nil { return uniques
return authorityCache
}
cache := ToAuthorityCache(setcodetx.AuthList)
setcodetx.authorityCache.Store(cache)
return cache
} }
// 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

View file

@ -67,56 +67,24 @@ type SetCodeTx struct {
S *uint256.Int S *uint256.Int
// caches // caches
authorityCache atomic.Pointer[AuthorityCache] auths atomic.Pointer[authCache]
} }
func ToAuthorityCache(authList []SetCodeAuthorization) *AuthorityCache { type authCache []*common.Address
var (
marks = make(map[common.Address]bool) // deriveAuthorities returns a list of recovered authorization signers. The
auths = make([]common.Address, 0, len(authList)) // length is always equal to the number of authorizations. Any authorities that
invalid []invalidAuth // empty since it's expected to be empty most of the time // fail to recover are set as nil in the list.
) func (tx *SetCodeTx) deriveAuthorities() authCache {
for i, auth := range authList { auths := make([]*common.Address, 0, len(tx.AuthList))
for _, auth := range tx.AuthList {
if addr, err := auth.Authority(); err == nil { if addr, err := auth.Authority(); err == nil {
if marks[addr] { auths = append(auths, &addr)
continue
}
marks[addr] = true
auths = append(auths, addr)
} else { } else {
invalid = append(invalid, invalidAuth{index: i, error: err}) auths = append(auths, nil)
} }
} }
return &AuthorityCache{authorities: auths, invalidAuths: invalid} return auths
}
type AuthorityCache struct {
authorities []common.Address
invalidAuths []invalidAuth
}
type invalidAuth struct {
index int
error error
}
func (ac *AuthorityCache) Authority(i int) (common.Address, error) {
indexToSub := 0
for _, invalid := range ac.invalidAuths {
if i == invalid.index {
return common.Address{}, invalid.error
}
if i > invalid.index {
indexToSub++
continue
}
break
}
targetIndex := i - indexToSub
if targetIndex < 0 || targetIndex >= len(ac.authorities) {
return common.Address{}, errors.New("index out of range")
}
return ac.authorities[i-indexToSub], nil
} }
//go:generate go run github.com/fjl/gencodec -type SetCodeAuthorization -field-override authorizationMarshaling -out gen_authorization.go //go:generate go run github.com/fjl/gencodec -type SetCodeAuthorization -field-override authorizationMarshaling -out gen_authorization.go

View file

@ -185,7 +185,7 @@ func (t *prestateTracer) OnTxStart(env *tracing.VMContext, tx *types.Transaction
t.lookupAccount(env.Coinbase) t.lookupAccount(env.Coinbase)
// Add accounts with authorizations to the prestate before they get applied. // Add accounts with authorizations to the prestate before they get applied.
for _, addr := range tx.SetCodeAuthorities() { for _, addr := range tx.UniqueSetCodeAuthorities() {
t.lookupAccount(addr) t.lookupAccount(addr)
} }
} }