diff --git a/core/state_transition.go b/core/state_transition.go index b5b8b22155..6b94c10d5a 100644 --- a/core/state_transition.go +++ b/core/state_transition.go @@ -207,20 +207,35 @@ func toWordSize(size uint64) uint64 { // A Message contains the data derived from a single transaction that is relevant to state // processing. type Message struct { - To *common.Address - From common.Address - Nonce uint64 - Value *big.Int - GasLimit uint64 - GasPrice *big.Int - GasFeeCap *big.Int - GasTipCap *big.Int - Data []byte - AccessList types.AccessList - BlobGasFeeCap *big.Int - BlobHashes []common.Hash + To *common.Address + From common.Address + Nonce uint64 + Value *big.Int + GasLimit uint64 + GasPrice *big.Int + GasFeeCap *big.Int + GasTipCap *big.Int + Data []byte + AccessList types.AccessList + BlobGasFeeCap *big.Int + BlobHashes []common.Hash + + // When SetCodeAuthorizations is not nil, it represents the full set of + // auths included in the actual transaction. + // + // To avoid recovering the authority addresses during the state transition, + // they are precomputed or retrieved from the Transaction cache and placed + // into Authorities below. The authority for any given index will match the + // corresponding authorization in SetCodeAuthorizations. Nil is used when the + // authorization is invalid to maintain indexes between the two lists. SetCodeAuthorizations []types.SetCodeAuthorization + // Authorities is the list of recovered authority addresses for the + // authorization list. Nil is used to represent an invalid authorization. The + // indexes between SetCodeAuthorizations and Authorities match so that the + // each authority matches the corresponding authorization. + Authorities []*common.Address // Recovered authority addresses + // When SkipNonceChecks is true, the message nonce is not checked against the // account nonce in state. // @@ -249,6 +264,7 @@ func TransactionToMessage(tx *types.Transaction, s types.Signer, baseFee *big.In Data: tx.Data(), AccessList: tx.AccessList(), SetCodeAuthorizations: tx.SetCodeAuthorizations(), + Authorities: tx.SetCodeAuthorities(), SkipNonceChecks: false, SkipTransactionChecks: false, BlobHashes: tx.BlobHashes(), @@ -575,9 +591,17 @@ func (st *stateTransition) execute() (*ExecutionResult, error) { // Apply EIP-7702 authorizations. if msg.SetCodeAuthorizations != nil { - for _, auth := range msg.SetCodeAuthorizations { - // Note errors are ignored, we simply skip invalid authorizations here. - st.applyAuthorization(&auth) + if len(msg.SetCodeAuthorizations) != len(msg.Authorities) { + // This is an invariant of Message that cannot be invalidated. + panic("Length of authorizations does match authorities") + } + for i, auth := range msg.SetCodeAuthorizations { + addr := msg.Authorities[i] + if addr == nil { + // Skip invalid authorizations. + continue + } + st.applyAuthorization(&auth, *addr) } } @@ -661,20 +685,16 @@ func (st *stateTransition) execute() (*ExecutionResult, error) { } // validateAuthorization validates an EIP-7702 authorization against the state. -func (st *stateTransition) validateAuthorization(auth *types.SetCodeAuthorization) (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. 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. if auth.Nonce+1 < auth.Nonce { - return authority, ErrAuthorizationNonceOverflow - } - // Validate signature values and recover authority. - authority, err = auth.Authority() - if err != nil { - return authority, fmt.Errorf("%w: %v", ErrAuthorizationInvalidSignature, err) + return ErrAuthorizationNonceOverflow } + // Check the authority account // 1) doesn't have code or has exisiting delegation // 2) matches the auth's nonce @@ -683,17 +703,17 @@ func (st *stateTransition) validateAuthorization(auth *types.SetCodeAuthorizatio st.state.AddAddressToAccessList(authority) code := st.state.GetCode(authority) if _, ok := types.ParseDelegation(code); len(code) != 0 && !ok { - return authority, ErrAuthorizationDestinationHasCode + return ErrAuthorizationDestinationHasCode } 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. -func (st *stateTransition) applyAuthorization(auth *types.SetCodeAuthorization) error { - authority, err := st.validateAuthorization(auth) +func (st *stateTransition) applyAuthorization(auth *types.SetCodeAuthorization, authority common.Address) error { + err := st.validateAuthorization(auth, authority) if err != nil { return err } diff --git a/core/txpool/legacypool/legacypool.go b/core/txpool/legacypool/legacypool.go index 3d66803fd7..a73d8d5747 100644 --- a/core/txpool/legacypool/legacypool.go +++ b/core/txpool/legacypool/legacypool.go @@ -632,7 +632,7 @@ func (pool *LegacyPool) validateAuth(tx *types.Transaction) error { } // For symmetry, allow at most one in-flight tx for any authority with a // pending transaction. - if auths := tx.SetCodeAuthorities(); len(auths) > 0 { + if auths := tx.UniqueSetCodeAuthorities(); len(auths) > 0 { for _, auth := range auths { var count int if pending := pool.pending[auth]; pending != nil { @@ -1759,7 +1759,7 @@ func (t *lookup) TxsBelowTip(threshold *big.Int) types.Transactions { // addAuthorities tracks the supplied tx in relation to each authority it // specifies. func (t *lookup) addAuthorities(tx *types.Transaction) { - for _, addr := range tx.SetCodeAuthorities() { + for _, addr := range tx.UniqueSetCodeAuthorities() { list, ok := t.auths[addr] if !ok { list = []common.Hash{} @@ -1777,7 +1777,7 @@ func (t *lookup) addAuthorities(tx *types.Transaction) { // authorities. func (t *lookup) removeAuthorities(tx *types.Transaction) { hash := tx.Hash() - for _, addr := range tx.SetCodeAuthorities() { + for _, addr := range tx.UniqueSetCodeAuthorities() { list := t.auths[addr] // Remove tx from tracker. if i := slices.Index(list, hash); i >= 0 { diff --git a/core/txpool/legacypool/legacypool_test.go b/core/txpool/legacypool/legacypool_test.go index f8592ba001..ddd8057c78 100644 --- a/core/txpool/legacypool/legacypool_test.go +++ b/core/txpool/legacypool/legacypool_test.go @@ -277,7 +277,7 @@ func validatePoolInternals(pool *LegacyPool) error { } // Ensure all auths in pool are tracked for _, tx := range pool.all.txs { - for _, addr := range tx.SetCodeAuthorities() { + for _, addr := range tx.UniqueSetCodeAuthorities() { list := pool.all.auths[addr] if i := slices.Index(list, tx.Hash()); i < 0 { return fmt.Errorf("authority not tracked: addr %s, tx %s", addr, tx.Hash()) diff --git a/core/types/transaction.go b/core/types/transaction.go index e9bf08daef..8bd60a5cba 100644 --- a/core/types/transaction.go +++ b/core/types/transaction.go @@ -58,9 +58,10 @@ type Transaction struct { time time.Time // Time first seen locally (spam avoidance) // caches - hash atomic.Pointer[common.Hash] - size atomic.Uint64 - from atomic.Pointer[sigCache] + hash atomic.Pointer[common.Hash] + size atomic.Uint64 + from atomic.Pointer[sigCache] + auths atomic.Pointer[authCache] } // NewTx creates a new transaction. @@ -539,27 +540,36 @@ func (tx *Transaction) SetCodeAuthorizations() []SetCodeAuthorization { return setcodetx.AuthList } -// SetCodeAuthorities returns a list of unique authorities from the -// authorization list. -func (tx *Transaction) SetCodeAuthorities() []common.Address { +// SetCodeAuthorities returns a list of authorities from the authorization list. +// Nil is used to represent authorizations that fail derivation. +func (tx *Transaction) SetCodeAuthorities() []*common.Address { setcodetx, ok := tx.inner.(*SetCodeTx) if !ok { return nil } + if cache := tx.auths.Load(); cache != nil { + return *cache + } + cache := (authCache)(DeriveAuthorities(setcodetx.AuthList)) + tx.auths.Store(&cache) + return cache +} + +// UniqueSetCodeAuthorities returns a list of unique authorities from the +// authorization list. +func (tx *Transaction) UniqueSetCodeAuthorities() []common.Address { var ( - marks = make(map[common.Address]bool) - auths = make([]common.Address, 0, len(setcodetx.AuthList)) + auths = tx.SetCodeAuthorities() + marks = make(map[common.Address]bool) + uniques []common.Address ) - for _, auth := range setcodetx.AuthList { - if addr, err := auth.Authority(); err == nil { - if marks[addr] { - continue - } - marks[addr] = true - auths = append(auths, addr) + for _, auth := range auths { + if auth != nil && !marks[*auth] { + marks[*auth] = true + uniques = append(uniques, *auth) } } - return auths + return uniques } // SetTime sets the decoding time of a transaction. This is used by tests to set diff --git a/core/types/tx_setcode.go b/core/types/tx_setcode.go index 9487c9cc81..d3f4e984e6 100644 --- a/core/types/tx_setcode.go +++ b/core/types/tx_setcode.go @@ -66,6 +66,25 @@ type SetCodeTx struct { S *uint256.Int } +// authCache is an internal helper type used in the Transaction object to cache +// authorizations. +type authCache []*common.Address + +// DeriveAuthorities returns a list of recovered authorization signers. The +// length is always equal to the number of authorizations. Any authorities that +// fail to recover are set as nil in the list. +func DeriveAuthorities(authList []SetCodeAuthorization) []*common.Address { + auths := make([]*common.Address, 0, len(authList)) + for _, auth := range authList { + if addr, err := auth.Authority(); err == nil { + auths = append(auths, &addr) + } else { + auths = append(auths, nil) + } + } + return auths +} + //go:generate go run github.com/fjl/gencodec -type SetCodeAuthorization -field-override authorizationMarshaling -out gen_authorization.go // SetCodeAuthorization is an authorization from an account to deploy code at its address. diff --git a/eth/tracers/native/prestate.go b/eth/tracers/native/prestate.go index 36cb16e44b..6ee1adf10d 100644 --- a/eth/tracers/native/prestate.go +++ b/eth/tracers/native/prestate.go @@ -196,11 +196,7 @@ func (t *prestateTracer) OnTxStart(env *tracing.VMContext, tx *types.Transaction t.lookupAccount(env.Coinbase) // Add accounts with authorizations to the prestate before they get applied. - for _, auth := range tx.SetCodeAuthorizations() { - addr, err := auth.Authority() - if err != nil { - continue - } + for _, addr := range tx.UniqueSetCodeAuthorities() { t.lookupAccount(addr) } } diff --git a/internal/ethapi/transaction_args.go b/internal/ethapi/transaction_args.go index 4fb30e6289..8e6ec69489 100644 --- a/internal/ethapi/transaction_args.go +++ b/internal/ethapi/transaction_args.go @@ -491,6 +491,7 @@ func (args *TransactionArgs) ToMessage(baseFee *big.Int, skipNonceCheck bool) *c BlobGasFeeCap: (*big.Int)(args.BlobFeeCap), BlobHashes: args.BlobHashes, SetCodeAuthorizations: args.AuthorizationList, + Authorities: types.DeriveAuthorities(args.AuthorizationList), SkipNonceChecks: skipNonceCheck, SkipTransactionChecks: true, } diff --git a/tests/state_test_util.go b/tests/state_test_util.go index f7cf1c0697..a3e93cd863 100644 --- a/tests/state_test_util.go +++ b/tests/state_test_util.go @@ -489,6 +489,7 @@ func (tx *stTransaction) toMessage(ps stPostState, baseFee *big.Int) (*core.Mess BlobHashes: tx.BlobVersionedHashes, BlobGasFeeCap: tx.BlobGasFeeCap, SetCodeAuthorizations: authList, + Authorities: types.DeriveAuthorities(authList), } return msg, nil }