From 47622451dce02d08aa4d42262f3d082486a1c5eb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BA=8C=E7=8B=97=E5=90=8C=E5=AD=A6?= Date: Mon, 6 Jul 2026 11:26:09 +0800 Subject: [PATCH] Preserve set-code auth list in external signer --- accounts/external/backend.go | 26 +++++++--- accounts/external/backend_test.go | 81 ++++++++++++++++++++++++++++++ signer/core/apitypes/types.go | 24 +++++++++ signer/core/apitypes/types_test.go | 73 +++++++++++++++++++++++++++ 4 files changed, 197 insertions(+), 7 deletions(-) create mode 100644 accounts/external/backend_test.go diff --git a/accounts/external/backend.go b/accounts/external/backend.go index d73fb06ad0..18c58b2c88 100644 --- a/accounts/external/backend.go +++ b/accounts/external/backend.go @@ -198,6 +198,19 @@ type signTransactionResult struct { // by the external signer. For non-legacy transactions, the chain ID of the // transaction overrides the chainID parameter. func (api *ExternalSigner) SignTx(account accounts.Account, tx *types.Transaction, chainID *big.Int) (*types.Transaction, error) { + args, err := signTxArgs(account, tx, chainID) + if err != nil { + return nil, err + } + + var res signTransactionResult + if err := api.client.Call(&res, "account_signTransaction", args); err != nil { + return nil, err + } + return res.Tx, nil +} + +func signTxArgs(account accounts.Account, tx *types.Transaction, chainID *big.Int) (*apitypes.SendTxArgs, error) { data := hexutil.Bytes(tx.Data()) var to *common.MixedcaseAddress if tx.To() != nil { @@ -215,9 +228,13 @@ func (api *ExternalSigner) SignTx(account accounts.Account, tx *types.Transactio switch tx.Type() { case types.LegacyTxType, types.AccessListTxType: args.GasPrice = (*hexutil.Big)(tx.GasPrice()) - case types.DynamicFeeTxType, types.BlobTxType, types.SetCodeTxType: + case types.DynamicFeeTxType, types.BlobTxType: args.MaxFeePerGas = (*hexutil.Big)(tx.GasFeeCap()) args.MaxPriorityFeePerGas = (*hexutil.Big)(tx.GasTipCap()) + case types.SetCodeTxType: + args.MaxFeePerGas = (*hexutil.Big)(tx.GasFeeCap()) + args.MaxPriorityFeePerGas = (*hexutil.Big)(tx.GasTipCap()) + args.AuthorizationList = tx.SetCodeAuthorizations() default: return nil, fmt.Errorf("unsupported tx type %d", tx.Type()) } @@ -246,12 +263,7 @@ func (api *ExternalSigner) SignTx(account accounts.Account, tx *types.Transactio args.Commitments = sidecar.Commitments args.Proofs = sidecar.Proofs } - - var res signTransactionResult - if err := api.client.Call(&res, "account_signTransaction", args); err != nil { - return nil, err - } - return res.Tx, nil + return args, nil } func (api *ExternalSigner) SignTextWithPassphrase(account accounts.Account, passphrase string, text []byte) ([]byte, error) { diff --git a/accounts/external/backend_test.go b/accounts/external/backend_test.go new file mode 100644 index 0000000000..b479a9643d --- /dev/null +++ b/accounts/external/backend_test.go @@ -0,0 +1,81 @@ +// Copyright 2026 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY +// or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License +// for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package external + +import ( + "bytes" + "encoding/json" + "math/big" + "reflect" + "testing" + + "github.com/ethereum/go-ethereum/accounts" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/holiman/uint256" +) + +func TestSignTxArgsSetCodeAuthorizationList(t *testing.T) { + account := accounts.Account{Address: common.HexToAddress("0x1000000000000000000000000000000000000001")} + to := common.HexToAddress("0x2000000000000000000000000000000000000002") + auth := types.SetCodeAuthorization{ + ChainID: *uint256.NewInt(5), + Address: common.HexToAddress("0x3000000000000000000000000000000000000003"), + Nonce: 9, + V: 1, + R: *uint256.NewInt(10), + S: *uint256.NewInt(11), + } + tx := types.NewTx(&types.SetCodeTx{ + ChainID: uint256.NewInt(5), + Nonce: 4, + GasTipCap: uint256.NewInt(3), + GasFeeCap: uint256.NewInt(30), + Gas: 21000, + To: to, + Value: uint256.NewInt(7), + Data: []byte{0x01, 0x02}, + AccessList: types.AccessList{{ + Address: common.HexToAddress("0x4000000000000000000000000000000000000004"), + StorageKeys: []common.Hash{common.HexToHash("0x01")}, + }}, + AuthList: []types.SetCodeAuthorization{auth}, + }) + args, err := signTxArgs(account, tx, big.NewInt(1)) + if err != nil { + t.Fatal(err) + } + if args.ChainID == nil || (*big.Int)(args.ChainID).Cmp(big.NewInt(5)) != 0 { + t.Fatalf("have chain id %v, want 5", args.ChainID) + } + if args.MaxFeePerGas == nil || (*big.Int)(args.MaxFeePerGas).Cmp(big.NewInt(30)) != 0 { + t.Fatalf("have max fee %v, want 30", args.MaxFeePerGas) + } + if args.MaxPriorityFeePerGas == nil || (*big.Int)(args.MaxPriorityFeePerGas).Cmp(big.NewInt(3)) != 0 { + t.Fatalf("have max priority fee %v, want 3", args.MaxPriorityFeePerGas) + } + if !reflect.DeepEqual(args.AuthorizationList, []types.SetCodeAuthorization{auth}) { + t.Fatalf("have auth list %#v, want %#v", args.AuthorizationList, []types.SetCodeAuthorization{auth}) + } + blob, err := json.Marshal(args) + if err != nil { + t.Fatal(err) + } + if !bytes.Contains(blob, []byte(`"authorizationList"`)) { + t.Fatalf("marshaled args missing authorizationList: %s", blob) + } +} diff --git a/signer/core/apitypes/types.go b/signer/core/apitypes/types.go index ee12bb263e..8a7701ef6c 100644 --- a/signer/core/apitypes/types.go +++ b/signer/core/apitypes/types.go @@ -112,6 +112,9 @@ type SendTxArgs struct { Blobs []kzg4844.Blob `json:"blobs,omitempty"` Commitments []kzg4844.Commitment `json:"commitments,omitempty"` Proofs []kzg4844.Proof `json:"proofs,omitempty"` + + // For SetCodeTxType + AuthorizationList []types.SetCodeAuthorization `json:"authorizationList,omitempty"` } func (args SendTxArgs) String() string { @@ -146,6 +149,27 @@ func (args *SendTxArgs) ToTransaction() (*types.Transaction, error) { } var data types.TxData switch { + case args.AuthorizationList != nil: + al := types.AccessList{} + if args.AccessList != nil { + al = *args.AccessList + } + if to == nil { + return nil, errors.New("transaction recipient must be set for set-code transactions") + } + data = &types.SetCodeTx{ + To: *to, + ChainID: uint256.MustFromBig((*big.Int)(args.ChainID)), + Nonce: uint64(args.Nonce), + Gas: uint64(args.Gas), + GasFeeCap: uint256.MustFromBig((*big.Int)(args.MaxFeePerGas)), + GasTipCap: uint256.MustFromBig((*big.Int)(args.MaxPriorityFeePerGas)), + Value: uint256.MustFromBig((*big.Int)(&args.Value)), + Data: args.data(), + AccessList: al, + AuthList: args.AuthorizationList, + } + case args.BlobHashes != nil: al := types.AccessList{} if args.AccessList != nil { diff --git a/signer/core/apitypes/types_test.go b/signer/core/apitypes/types_test.go index 3f0ffaa175..624e92afdf 100644 --- a/signer/core/apitypes/types_test.go +++ b/signer/core/apitypes/types_test.go @@ -17,12 +17,16 @@ package apitypes import ( + "bytes" "crypto/rand" "crypto/sha256" "encoding/json" + "math/big" + "reflect" "testing" "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/crypto/kzg4844" "github.com/holiman/uint256" @@ -99,6 +103,75 @@ func TestTxArgs(t *testing.T) { } } +func TestSetCodeTxArgs(t *testing.T) { + to := common.HexToAddress("0x1000000000000000000000000000000000000001") + from := common.NewMixedcaseAddress(common.HexToAddress("0x9000000000000000000000000000000000000009")) + mixedTo := common.NewMixedcaseAddress(to) + input := hexutil.Bytes{0x01, 0x02} + chainID := hexutil.Big(*big.NewInt(5)) + maxFee := hexutil.Big(*big.NewInt(30)) + maxTip := hexutil.Big(*big.NewInt(3)) + value := hexutil.Big(*big.NewInt(7)) + accessList := types.AccessList{{ + Address: common.HexToAddress("0x2000000000000000000000000000000000000002"), + StorageKeys: []common.Hash{common.HexToHash("0x01")}, + }} + auth := types.SetCodeAuthorization{ + ChainID: *uint256.NewInt(5), + Address: common.HexToAddress("0x3000000000000000000000000000000000000003"), + Nonce: 9, + V: 1, + R: *uint256.NewInt(10), + S: *uint256.NewInt(11), + } + args := SendTxArgs{ + From: from, + To: &mixedTo, + Gas: 21000, + MaxFeePerGas: &maxFee, + MaxPriorityFeePerGas: &maxTip, + Value: value, + Nonce: 4, + Input: &input, + AccessList: &accessList, + ChainID: &chainID, + AuthorizationList: []types.SetCodeAuthorization{auth}, + } + blob, err := json.Marshal(args) + if err != nil { + t.Fatal(err) + } + if !bytes.Contains(blob, []byte(`"authorizationList"`)) { + t.Fatalf("marshaled args missing authorizationList: %s", blob) + } + var decoded SendTxArgs + if err := json.Unmarshal(blob, &decoded); err != nil { + t.Fatal(err) + } + tx, err := decoded.ToTransaction() + if err != nil { + t.Fatal(err) + } + if have, want := tx.Type(), uint8(types.SetCodeTxType); have != want { + t.Fatalf("have tx type %d, want %d", have, want) + } + if have := tx.To(); have == nil || *have != to { + t.Fatalf("have to %v, want %v", have, to) + } + if have, want := tx.ChainId(), big.NewInt(5); have.Cmp(want) != 0 { + t.Fatalf("have chain id %v, want %v", have, want) + } + if !bytes.Equal(tx.Data(), input) { + t.Fatalf("have input %x, want %x", tx.Data(), input) + } + if !reflect.DeepEqual(tx.AccessList(), accessList) { + t.Fatalf("have access list %#v, want %#v", tx.AccessList(), accessList) + } + if !reflect.DeepEqual(tx.SetCodeAuthorizations(), []types.SetCodeAuthorization{auth}) { + t.Fatalf("have auth list %#v, want %#v", tx.SetCodeAuthorizations(), []types.SetCodeAuthorization{auth}) + } +} + func TestBlobTxs(t *testing.T) { blob := kzg4844.Blob{0x1} commitment, err := kzg4844.BlobToCommitment(&blob)