mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-06-19 13:21:37 +00:00
SignTx populated BlobHashes and the sidecar fields (Blobs/Commitments/ Proofs) for a blob transaction but never set args.BlobFeeCap. As a result the external (clef) signer received maxFeePerBlobGas:null and signed a transaction inconsistent with the one passed in, silently dropping the blob fee cap. Set args.BlobFeeCap from tx.BlobGasFeeCap() so the signing request faithfully reflects the input transaction. This mirrors the existing handling of the other blob-tx fields.
282 lines
8.8 KiB
Go
282 lines
8.8 KiB
Go
// Copyright 2019 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 <http://www.gnu.org/licenses/>.
|
|
|
|
package external
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"math/big"
|
|
"sync"
|
|
|
|
"github.com/ethereum/go-ethereum"
|
|
"github.com/ethereum/go-ethereum/accounts"
|
|
"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/event"
|
|
"github.com/ethereum/go-ethereum/log"
|
|
"github.com/ethereum/go-ethereum/rpc"
|
|
"github.com/ethereum/go-ethereum/signer/core/apitypes"
|
|
)
|
|
|
|
type ExternalBackend struct {
|
|
signers []accounts.Wallet
|
|
}
|
|
|
|
func (eb *ExternalBackend) Wallets() []accounts.Wallet {
|
|
return eb.signers
|
|
}
|
|
|
|
func NewExternalBackend(endpoint string) (*ExternalBackend, error) {
|
|
signer, err := NewExternalSigner(endpoint)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return &ExternalBackend{
|
|
signers: []accounts.Wallet{signer},
|
|
}, nil
|
|
}
|
|
|
|
func (eb *ExternalBackend) Subscribe(sink chan<- accounts.WalletEvent) event.Subscription {
|
|
return event.NewSubscription(func(quit <-chan struct{}) error {
|
|
<-quit
|
|
return nil
|
|
})
|
|
}
|
|
|
|
// ExternalSigner provides an API to interact with an external signer (clef)
|
|
// It proxies request to the external signer while forwarding relevant
|
|
// request headers
|
|
type ExternalSigner struct {
|
|
client *rpc.Client
|
|
endpoint string
|
|
status string
|
|
cacheMu sync.RWMutex
|
|
cache []accounts.Account
|
|
}
|
|
|
|
func NewExternalSigner(endpoint string) (*ExternalSigner, error) {
|
|
client, err := rpc.Dial(endpoint)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
extsigner := &ExternalSigner{
|
|
client: client,
|
|
endpoint: endpoint,
|
|
}
|
|
// Check if reachable
|
|
version, err := extsigner.pingVersion()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
extsigner.status = fmt.Sprintf("ok [version=%v]", version)
|
|
return extsigner, nil
|
|
}
|
|
|
|
func (api *ExternalSigner) URL() accounts.URL {
|
|
return accounts.URL{
|
|
Scheme: "extapi",
|
|
Path: api.endpoint,
|
|
}
|
|
}
|
|
|
|
func (api *ExternalSigner) Status() (string, error) {
|
|
return api.status, nil
|
|
}
|
|
|
|
func (api *ExternalSigner) Open(passphrase string) error {
|
|
return errors.New("operation not supported on external signers")
|
|
}
|
|
|
|
func (api *ExternalSigner) Close() error {
|
|
return errors.New("operation not supported on external signers")
|
|
}
|
|
|
|
func (api *ExternalSigner) Accounts() []accounts.Account {
|
|
var accnts []accounts.Account
|
|
res, err := api.listAccounts()
|
|
if err != nil {
|
|
log.Error("account listing failed", "error", err)
|
|
return accnts
|
|
}
|
|
for _, addr := range res {
|
|
accnts = append(accnts, accounts.Account{
|
|
URL: accounts.URL{
|
|
Scheme: "extapi",
|
|
Path: api.endpoint,
|
|
},
|
|
Address: addr,
|
|
})
|
|
}
|
|
api.cacheMu.Lock()
|
|
api.cache = accnts
|
|
api.cacheMu.Unlock()
|
|
return accnts
|
|
}
|
|
|
|
func (api *ExternalSigner) Contains(account accounts.Account) bool {
|
|
api.cacheMu.RLock()
|
|
defer api.cacheMu.RUnlock()
|
|
if api.cache == nil {
|
|
// If we haven't already fetched the accounts, it's time to do so now
|
|
api.cacheMu.RUnlock()
|
|
api.Accounts()
|
|
api.cacheMu.RLock()
|
|
}
|
|
for _, a := range api.cache {
|
|
if a.Address == account.Address && (account.URL == (accounts.URL{}) || account.URL == api.URL()) {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func (api *ExternalSigner) Derive(path accounts.DerivationPath, pin bool) (accounts.Account, error) {
|
|
return accounts.Account{}, errors.New("operation not supported on external signers")
|
|
}
|
|
|
|
func (api *ExternalSigner) SelfDerive(bases []accounts.DerivationPath, chain ethereum.ChainStateReader) {
|
|
log.Error("operation SelfDerive not supported on external signers")
|
|
}
|
|
|
|
// SignData signs keccak256(data). The mimetype parameter describes the type of data being signed
|
|
func (api *ExternalSigner) SignData(account accounts.Account, mimeType string, data []byte) ([]byte, error) {
|
|
var res hexutil.Bytes
|
|
var signAddress = common.NewMixedcaseAddress(account.Address)
|
|
if err := api.client.Call(&res, "account_signData",
|
|
mimeType,
|
|
&signAddress, // Need to use the pointer here, because of how MarshalJSON is defined
|
|
hexutil.Encode(data)); err != nil {
|
|
return nil, err
|
|
}
|
|
// If V is on 27/28-form, convert to 0/1 for Clique
|
|
if mimeType == accounts.MimetypeClique && (res[64] == 27 || res[64] == 28) {
|
|
res[64] -= 27 // Transform V from 27/28 to 0/1 for Clique use
|
|
}
|
|
return res, nil
|
|
}
|
|
|
|
func (api *ExternalSigner) SignText(account accounts.Account, text []byte) ([]byte, error) {
|
|
var signature hexutil.Bytes
|
|
var signAddress = common.NewMixedcaseAddress(account.Address)
|
|
if err := api.client.Call(&signature, "account_signData",
|
|
accounts.MimetypeTextPlain,
|
|
&signAddress, // Need to use the pointer here, because of how MarshalJSON is defined
|
|
hexutil.Encode(text)); err != nil {
|
|
return nil, err
|
|
}
|
|
if signature[64] == 27 || signature[64] == 28 {
|
|
// If clef is used as a backend, it may already have transformed
|
|
// the signature to ethereum-type signature.
|
|
signature[64] -= 27 // Transform V from Ethereum-legacy to 0/1
|
|
}
|
|
return signature, nil
|
|
}
|
|
|
|
// signTransactionResult represents the signinig result returned by clef.
|
|
type signTransactionResult struct {
|
|
Raw hexutil.Bytes `json:"raw"`
|
|
Tx *types.Transaction `json:"tx"`
|
|
}
|
|
|
|
// SignTx sends the transaction to the external signer.
|
|
// If chainID is nil, or tx.ChainID is zero, the chain ID will be assigned
|
|
// 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) {
|
|
data := hexutil.Bytes(tx.Data())
|
|
var to *common.MixedcaseAddress
|
|
if tx.To() != nil {
|
|
t := common.NewMixedcaseAddress(*tx.To())
|
|
to = &t
|
|
}
|
|
args := &apitypes.SendTxArgs{
|
|
Input: &data,
|
|
Nonce: hexutil.Uint64(tx.Nonce()),
|
|
Value: hexutil.Big(*tx.Value()),
|
|
Gas: hexutil.Uint64(tx.Gas()),
|
|
To: to,
|
|
From: common.NewMixedcaseAddress(account.Address),
|
|
}
|
|
switch tx.Type() {
|
|
case types.LegacyTxType, types.AccessListTxType:
|
|
args.GasPrice = (*hexutil.Big)(tx.GasPrice())
|
|
case types.DynamicFeeTxType, types.BlobTxType, types.SetCodeTxType:
|
|
args.MaxFeePerGas = (*hexutil.Big)(tx.GasFeeCap())
|
|
args.MaxPriorityFeePerGas = (*hexutil.Big)(tx.GasTipCap())
|
|
default:
|
|
return nil, fmt.Errorf("unsupported tx type %d", tx.Type())
|
|
}
|
|
// We should request the default chain id that we're operating with
|
|
// (the chain we're executing on)
|
|
if chainID != nil && chainID.Sign() != 0 {
|
|
args.ChainID = (*hexutil.Big)(chainID)
|
|
}
|
|
if tx.Type() != types.LegacyTxType {
|
|
// However, if the user asked for a particular chain id, then we should
|
|
// use that instead.
|
|
if tx.ChainId().Sign() != 0 {
|
|
args.ChainID = (*hexutil.Big)(tx.ChainId())
|
|
}
|
|
accessList := tx.AccessList()
|
|
args.AccessList = &accessList
|
|
}
|
|
if tx.Type() == types.BlobTxType {
|
|
args.BlobHashes = tx.BlobHashes()
|
|
args.BlobFeeCap = (*hexutil.Big)(tx.BlobGasFeeCap())
|
|
sidecar := tx.BlobTxSidecar()
|
|
if sidecar == nil {
|
|
return nil, errors.New("blobs must be present for signing")
|
|
}
|
|
args.Blobs = sidecar.Blobs
|
|
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
|
|
}
|
|
|
|
func (api *ExternalSigner) SignTextWithPassphrase(account accounts.Account, passphrase string, text []byte) ([]byte, error) {
|
|
return []byte{}, errors.New("password-operations not supported on external signers")
|
|
}
|
|
|
|
func (api *ExternalSigner) SignTxWithPassphrase(account accounts.Account, passphrase string, tx *types.Transaction, chainID *big.Int) (*types.Transaction, error) {
|
|
return nil, errors.New("password-operations not supported on external signers")
|
|
}
|
|
func (api *ExternalSigner) SignDataWithPassphrase(account accounts.Account, passphrase, mimeType string, data []byte) ([]byte, error) {
|
|
return nil, errors.New("password-operations not supported on external signers")
|
|
}
|
|
|
|
func (api *ExternalSigner) listAccounts() ([]common.Address, error) {
|
|
var res []common.Address
|
|
if err := api.client.Call(&res, "account_list"); err != nil {
|
|
return nil, err
|
|
}
|
|
return res, nil
|
|
}
|
|
|
|
func (api *ExternalSigner) pingVersion() (string, error) {
|
|
var v string
|
|
if err := api.client.Call(&v, "account_version"); err != nil {
|
|
return "", err
|
|
}
|
|
return v, nil
|
|
}
|