diff --git a/core/state_transition.go b/core/state_transition.go index 0f9ee9eea5..00e02e3a0c 100644 --- a/core/state_transition.go +++ b/core/state_transition.go @@ -156,6 +156,7 @@ type Message struct { BlobGasFeeCap *big.Int BlobHashes []common.Hash SetCodeAuthorizations []types.SetCodeAuthorization + AuthorityCache *types.AuthorityCache // When SkipNonceChecks is true, the message nonce is not checked against the // account nonce in state. @@ -179,6 +180,7 @@ func TransactionToMessage(tx *types.Transaction, s types.Signer, baseFee *big.In Data: tx.Data(), AccessList: tx.AccessList(), SetCodeAuthorizations: tx.SetCodeAuthorizations(), + AuthorityCache: tx.AuthorityCache(), SkipNonceChecks: false, SkipFromEOACheck: false, BlobHashes: tx.BlobHashes(), @@ -490,9 +492,13 @@ func (st *stateTransition) execute() (*ExecutionResult, error) { // Apply EIP-7702 authorizations. if msg.SetCodeAuthorizations != nil { - for _, auth := range msg.SetCodeAuthorizations { + for i, auth := range msg.SetCodeAuthorizations { // Note errors are ignored, we simply skip invalid authorizations here. - st.applyAuthorization(&auth) + authority, err := msg.AuthorityCache.Authority(i) + if err != nil { + continue + } + st.applyAuthorization(&auth, authority) } } @@ -557,7 +563,7 @@ 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, preDerivedAuthority common.Address) (authority common.Address, err 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 @@ -566,11 +572,9 @@ func (st *stateTransition) validateAuthorization(auth *types.SetCodeAuthorizatio 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) - } + + authority = preDerivedAuthority + // Check the authority account // 1) doesn't have code or has exisiting delegation // 2) matches the auth's nonce @@ -588,8 +592,8 @@ func (st *stateTransition) validateAuthorization(auth *types.SetCodeAuthorizatio } // 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, preDerivedAuthority common.Address) error { + authority, err := st.validateAuthorization(auth, preDerivedAuthority) if err != nil { return err } diff --git a/core/types/transaction.go b/core/types/transaction.go index 7df13e04bb..2c4f8e7892 100644 --- a/core/types/transaction.go +++ b/core/types/transaction.go @@ -489,13 +489,25 @@ func (tx *Transaction) SetCodeAuthorities() []common.Address { if !ok { return nil } - auths := make([]common.Address, 0, len(setcodetx.AuthList)) - for _, auth := range setcodetx.AuthList { - if addr, err := auth.Authority(); err == nil { - auths = append(auths, addr) - } + if authorityCache := setcodetx.authorityCache.Load(); authorityCache != nil { + return authorityCache.authorities } - return auths + cache := ToAuthorityCache(setcodetx.AuthList) + setcodetx.authorityCache.Store(cache) + return cache.authorities +} + +func (tx *Transaction) AuthorityCache() *AuthorityCache { + setcodetx, ok := tx.inner.(*SetCodeTx) + if !ok { + return nil + } + if authorityCache := setcodetx.authorityCache.Load(); authorityCache != nil { + 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 diff --git a/core/types/tx_setcode.go b/core/types/tx_setcode.go index 894bac10a3..654501a195 100644 --- a/core/types/tx_setcode.go +++ b/core/types/tx_setcode.go @@ -21,6 +21,7 @@ import ( "crypto/ecdsa" "errors" "math/big" + "sync/atomic" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" @@ -64,6 +65,51 @@ type SetCodeTx struct { V *uint256.Int R *uint256.Int S *uint256.Int + + // caches + authorityCache atomic.Pointer[AuthorityCache] +} + +func ToAuthorityCache(authList []SetCodeAuthorization) *AuthorityCache { + auths := make([]common.Address, 0, len(authList)) + var invalid []invalidAuth // empty since it's expected to be empty most of the time + for i, auth := range authList { + if addr, err := auth.Authority(); err == nil { + auths = append(auths, addr) + } else { + invalid = append(invalid, invalidAuth{index: i, error: err}) + } + } + return &AuthorityCache{authorities: auths, invalidAuths: invalid} +} + +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 diff --git a/eth/tracers/native/prestate.go b/eth/tracers/native/prestate.go index e04b77f61f..df0f983a48 100644 --- a/eth/tracers/native/prestate.go +++ b/eth/tracers/native/prestate.go @@ -161,11 +161,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.SetCodeAuthorities() { t.lookupAccount(addr) } } diff --git a/internal/ethapi/transaction_args.go b/internal/ethapi/transaction_args.go index 7a7d63c535..3a59deec6a 100644 --- a/internal/ethapi/transaction_args.go +++ b/internal/ethapi/transaction_args.go @@ -461,6 +461,7 @@ func (args *TransactionArgs) ToMessage(baseFee *big.Int, skipNonceCheck, skipEoA BlobGasFeeCap: (*big.Int)(args.BlobFeeCap), BlobHashes: args.BlobHashes, SetCodeAuthorizations: args.AuthorizationList, + AuthorityCache: types.ToAuthorityCache(args.AuthorizationList), SkipNonceChecks: skipNonceCheck, SkipFromEOACheck: skipEoACheck, } diff --git a/tests/state_test_util.go b/tests/state_test_util.go index a22e470ad8..14493b5f16 100644 --- a/tests/state_test_util.go +++ b/tests/state_test_util.go @@ -477,6 +477,7 @@ func (tx *stTransaction) toMessage(ps stPostState, baseFee *big.Int) (*core.Mess BlobHashes: tx.BlobVersionedHashes, BlobGasFeeCap: tx.BlobGasFeeCap, SetCodeAuthorizations: authList, + AuthorityCache: types.ToAuthorityCache(authList), } return msg, nil }