Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
96 changes: 65 additions & 31 deletions core/state_transition.go
Original file line number Diff line number Diff line change
Expand Up @@ -143,19 +143,22 @@ 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
SetCodeAuthorizations []types.SetCodeAuthorization
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

// AuthList provides an abstraction over authorization handling.
// It handles both signed authorizations and unsigned authorizations
AuthList []types.SetCodeAuth

// When SkipNonceChecks is true, the message nonce is not checked against the
// account nonce in state.
Expand All @@ -172,8 +175,30 @@ type Message struct {
SkipTransactionChecks bool
}

// getAuthorizationList extracts SetCodeAuthorization list from auth interfaces.
// This is used for intrinsic gas calculation and validation.
func (msg *Message) getAuthorizationList() []types.SetCodeAuthorization {
if msg.AuthList == nil {
return nil
}
authList := make([]types.SetCodeAuthorization, len(msg.AuthList))
for i, auth := range msg.AuthList {
authList[i] = auth.AsSetCodeAuthorization()
}
return authList
}

// TransactionToMessage converts a transaction into a Message.
func TransactionToMessage(tx *types.Transaction, s types.Signer, baseFee *big.Int) (*Message, error) {
// Create authorization interfaces from transaction authorizations
var authList []types.SetCodeAuth
if auths := tx.SetCodeAuthorizations(); auths != nil {
authList = make([]types.SetCodeAuth, len(auths))
for i, auth := range auths {
authList[i] = types.NewSignedAuthorization(auth)
}
}

msg := &Message{
Nonce: tx.Nonce(),
GasLimit: tx.Gas(),
Expand All @@ -184,7 +209,7 @@ func TransactionToMessage(tx *types.Transaction, s types.Signer, baseFee *big.In
Value: tx.Value(),
Data: tx.Data(),
AccessList: tx.AccessList(),
SetCodeAuthorizations: tx.SetCodeAuthorizations(),
AuthList: authList,
SkipNonceChecks: false,
SkipTransactionChecks: false,
BlobHashes: tx.BlobHashes(),
Expand Down Expand Up @@ -398,11 +423,11 @@ func (st *stateTransition) preCheck() error {
}
}
// Check that EIP-7702 authorization list signatures are well formed.
if msg.SetCodeAuthorizations != nil {
if msg.AuthList != nil {
if msg.To == nil {
return fmt.Errorf("%w (sender %v)", ErrSetCodeTxCreate, msg.From)
}
if len(msg.SetCodeAuthorizations) == 0 {
if len(msg.AuthList) == 0 {
return fmt.Errorf("%w (sender %v)", ErrEmptyAuthList, msg.From)
}
}
Expand Down Expand Up @@ -443,7 +468,7 @@ func (st *stateTransition) execute() (*ExecutionResult, error) {
)

// Check clauses 4-5, subtract intrinsic gas if everything is correct
gas, err := IntrinsicGas(msg.Data, msg.AccessList, msg.SetCodeAuthorizations, contractCreation, rules.IsHomestead, rules.IsIstanbul, rules.IsShanghai)
gas, err := IntrinsicGas(msg.Data, msg.AccessList, msg.getAuthorizationList(), contractCreation, rules.IsHomestead, rules.IsIstanbul, rules.IsShanghai)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -503,10 +528,10 @@ func (st *stateTransition) execute() (*ExecutionResult, error) {
st.state.SetNonce(msg.From, st.state.GetNonce(msg.From)+1, tracing.NonceChangeEoACall)

// Apply EIP-7702 authorizations.
if msg.SetCodeAuthorizations != nil {
for _, auth := range msg.SetCodeAuthorizations {
if msg.AuthList != nil {
for _, auth := range msg.AuthList {
// Note errors are ignored, we simply skip invalid authorizations here.
st.applyAuthorization(&auth)
st.applyAuthorization(auth)
}
}

Expand Down Expand Up @@ -574,17 +599,21 @@ 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.SetCodeAuth) (authority common.Address, err error) {
skipValidation := st.msg.SkipTransactionChecks

// Verify chain ID is null or equal to current chain ID.
if !auth.ChainID.IsZero() && auth.ChainID.CmpBig(st.evm.ChainConfig().ChainID) != 0 {
chainID := auth.GetChainID()
if !skipValidation && !chainID.IsZero() && chainID.CmpBig(st.evm.ChainConfig().ChainID) != 0 {
return authority, ErrAuthorizationWrongChainID
}
// Limit nonce to 2^64-1 per EIP-2681.
if auth.Nonce+1 < auth.Nonce {
nonce := auth.GetNonce()
if !skipValidation && nonce+1 < nonce {
return authority, ErrAuthorizationNonceOverflow
}
// Validate signature values and recover authority.
authority, err = auth.Authority()
// Get authority from auth
authority, err = auth.GetAuthority()
if err != nil {
return authority, fmt.Errorf("%w: %v", ErrAuthorizationInvalidSignature, err)
}
Expand All @@ -598,14 +627,17 @@ func (st *stateTransition) validateAuthorization(auth *types.SetCodeAuthorizatio
if _, ok := types.ParseDelegation(code); len(code) != 0 && !ok {
return authority, ErrAuthorizationDestinationHasCode
}
if have := st.state.GetNonce(authority); have != auth.Nonce {
return authority, ErrAuthorizationNonceMismatch
// Skip state checks during gas estimation
if !skipValidation {
if have := st.state.GetNonce(authority); have != nonce {
return authority, ErrAuthorizationNonceMismatch
}
}
return authority, nil
}

// applyAuthorization applies an EIP-7702 code delegation to the state.
func (st *stateTransition) applyAuthorization(auth *types.SetCodeAuthorization) error {
func (st *stateTransition) applyAuthorization(auth types.SetCodeAuth) error {
authority, err := st.validateAuthorization(auth)
if err != nil {
return err
Expand All @@ -618,15 +650,17 @@ func (st *stateTransition) applyAuthorization(auth *types.SetCodeAuthorization)
}

// Update nonce and account code.
st.state.SetNonce(authority, auth.Nonce+1, tracing.NonceChangeAuthorization)
if auth.Address == (common.Address{}) {
nonce := auth.GetNonce()
address := auth.GetAddress()
st.state.SetNonce(authority, nonce+1, tracing.NonceChangeAuthorization)
if address == (common.Address{}) {
// Delegation to zero address means clear.
st.state.SetCode(authority, nil, tracing.CodeChangeAuthorizationClear)
return nil
}

// Otherwise install delegation to auth.Address.
st.state.SetCode(authority, types.AddressToDelegation(auth.Address), tracing.CodeChangeAuthorization)
st.state.SetCode(authority, types.AddressToDelegation(address), tracing.CodeChangeAuthorization)

return nil
}
Expand Down
90 changes: 90 additions & 0 deletions core/types/tx_setcode.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,19 @@ type SetCodeTx struct {

//go:generate go run github.com/fjl/gencodec -type SetCodeAuthorization -field-override authorizationMarshaling -out gen_authorization.go

// SetCodeAuth is an interface for getting authorization details.
// It abstracts over signed and unsigned authorizations, enabling
// gas estimation without valid signatures.
type SetCodeAuth interface {
GetChainID() uint256.Int
GetAddress() common.Address
GetNonce() uint64
GetAuthority() (common.Address, error)

// AsSetCodeAuthorization returns the underlying SetCodeAuthorization for encoding.
AsSetCodeAuthorization() SetCodeAuthorization
}

// SetCodeAuthorization is an authorization from an account to deploy code at its address.
type SetCodeAuthorization struct {
ChainID uint256.Int `json:"chainId" gencodec:"required"`
Expand Down Expand Up @@ -241,3 +254,80 @@ func (tx *SetCodeTx) sigHash(chainID *big.Int) common.Hash {
tx.AuthList,
})
}

// SignedAuthorization wraps a SetCodeAuthorization and recovers authority from signature.
type SignedAuthorization struct {
auth SetCodeAuthorization
}

// NewSignedAuthorization creates a new SignedAuthorization.
func NewSignedAuthorization(auth SetCodeAuthorization) *SignedAuthorization {
return &SignedAuthorization{auth: auth}
}

func (s *SignedAuthorization) GetChainID() uint256.Int {
return s.auth.ChainID
}

func (s *SignedAuthorization) GetAddress() common.Address {
return s.auth.Address
}

func (s *SignedAuthorization) GetNonce() uint64 {
return s.auth.Nonce
}

func (s *SignedAuthorization) GetAuthority() (common.Address, error) {
return s.auth.Authority()
}

func (s *SignedAuthorization) AsSetCodeAuthorization() SetCodeAuthorization {
return s.auth
}

// UnsignedAuthorization represents an authorization with an authority address,
// used for gas estimation when a valid signature is not available.
type UnsignedAuthorization struct {
chainID uint256.Int
address common.Address
nonce uint64
authority common.Address
}

// NewUnsignedAuthorization creates a new UnsignedAuthorization.
func NewUnsignedAuthorization(chainID uint256.Int, address common.Address, nonce uint64, authority common.Address) *UnsignedAuthorization {
return &UnsignedAuthorization{
chainID: chainID,
address: address,
nonce: nonce,
authority: authority,
}
}

func (u *UnsignedAuthorization) GetChainID() uint256.Int {
return u.chainID
}

func (u *UnsignedAuthorization) GetAddress() common.Address {
return u.address
}

func (u *UnsignedAuthorization) GetNonce() uint64 {
return u.nonce
}

func (u *UnsignedAuthorization) GetAuthority() (common.Address, error) {
return u.authority, nil
}

func (u *UnsignedAuthorization) AsSetCodeAuthorization() SetCodeAuthorization {
// Return a zero-valued authorization with basic fields filled in
return SetCodeAuthorization{
ChainID: u.chainID,
Address: u.address,
Nonce: u.nonce,
V: 0,
R: uint256.Int{},
S: uint256.Int{},
}
}
88 changes: 88 additions & 0 deletions internal/ethapi/api_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4054,3 +4054,91 @@ func TestSendRawTransactionSync_Timeout(t *testing.T) {
t.Fatalf("expected ErrorData=%s, got %v", want, got)
}
}

// TestEstimateGasWithAuthorityHints tests that gas estimation works with the
// optional authorityHints field in transaction args
func TestEstimateGasWithAuthorityHints(t *testing.T) {
t.Parallel()
accounts := newAccounts(3)
targetContract := accounts[2].addr
genesis := &core.Genesis{
Config: params.AllDevChainProtocolChanges,
Alloc: types.GenesisAlloc{
accounts[0].addr: {Balance: big.NewInt(params.Ether)},
accounts[1].addr: {Balance: big.NewInt(params.Ether)},
targetContract: {Code: []byte{0x60, 0x01, 0x60, 0x00, 0x55}}, // PUSH1 1 PUSH1 0 SSTORE
},
}
api := NewBlockChainAPI(newTestBackend(t, 1, genesis, beacon.New(ethash.NewFaker()), func(i int, b *core.BlockGen) {
b.SetPoS()
}))
tests := []struct {
name string
setupArgs func() TransactionArgs
expectErr bool
}{
{
name: "Gas estimation without signature)",
setupArgs: func() TransactionArgs {
auth := types.SetCodeAuthorization{
ChainID: *uint256.NewInt(1337),
Address: targetContract,
Nonce: 0,
V: 0,
R: *uint256.NewInt(0),
S: *uint256.NewInt(0),
}
return TransactionArgs{
From: &accounts[0].addr,
To: &accounts[1].addr,
Value: (*hexutil.Big)(big.NewInt(1000)),
AuthorizationList: []types.SetCodeAuthorization{auth},
AuthorityHints: []common.Address{accounts[0].addr}, // Provide authority hint
}
},
expectErr: false,
},
{
name: "Gas estimation with valid signature",
setupArgs: func() TransactionArgs {
auth, err := types.SignSetCode(accounts[0].key, types.SetCodeAuthorization{
ChainID: *uint256.NewInt(1337),
Address: targetContract,
Nonce: 0,
})
if err != nil {
t.Fatalf("Failed to sign authorization: %v", err)
}
return TransactionArgs{
From: &accounts[0].addr,
To: &accounts[1].addr,
Value: (*hexutil.Big)(big.NewInt(1000)),
AuthorizationList: []types.SetCodeAuthorization{auth},
}
},
expectErr: false,
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
args := tt.setupArgs()
// Perform gas estimation
blockNr := rpc.BlockNumberOrHashWithNumber(rpc.LatestBlockNumber)
estimate, err := api.EstimateGas(context.Background(), args, &blockNr, nil, nil)

if tt.expectErr {
if err == nil {
t.Errorf("Expected error but got none")
}
} else {
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
if estimate == 0 {
t.Errorf("Expected non-zero gas estimate")
}
}
})
}
}
Loading
Loading