github.com/ava-labs/subnet-evm@v0.6.4/accounts/external/backend.go (about) 1 // (c) 2019-2020, Ava Labs, Inc. 2 // 3 // This file is a derived work, based on the go-ethereum library whose original 4 // notices appear below. 5 // 6 // It is distributed under a license compatible with the licensing terms of the 7 // original code from which it is derived. 8 // 9 // Much love to the original authors for their work. 10 // ********** 11 // Copyright 2019 The go-ethereum Authors 12 // This file is part of the go-ethereum library. 13 // 14 // The go-ethereum library is free software: you can redistribute it and/or modify 15 // it under the terms of the GNU Lesser General Public License as published by 16 // the Free Software Foundation, either version 3 of the License, or 17 // (at your option) any later version. 18 // 19 // The go-ethereum library is distributed in the hope that it will be useful, 20 // but WITHOUT ANY WARRANTY; without even the implied warranty of 21 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 22 // GNU Lesser General Public License for more details. 23 // 24 // You should have received a copy of the GNU Lesser General Public License 25 // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>. 26 27 package external 28 29 import ( 30 "errors" 31 "fmt" 32 "math/big" 33 "sync" 34 35 "github.com/ava-labs/subnet-evm/accounts" 36 "github.com/ava-labs/subnet-evm/core/types" 37 "github.com/ava-labs/subnet-evm/interfaces" 38 "github.com/ava-labs/subnet-evm/rpc" 39 "github.com/ava-labs/subnet-evm/signer/core/apitypes" 40 "github.com/ethereum/go-ethereum/common" 41 "github.com/ethereum/go-ethereum/common/hexutil" 42 "github.com/ethereum/go-ethereum/event" 43 "github.com/ethereum/go-ethereum/log" 44 ) 45 46 type ExternalBackend struct { 47 signers []accounts.Wallet 48 } 49 50 func (eb *ExternalBackend) Wallets() []accounts.Wallet { 51 return eb.signers 52 } 53 54 func NewExternalBackend(endpoint string) (*ExternalBackend, error) { 55 signer, err := NewExternalSigner(endpoint) 56 if err != nil { 57 return nil, err 58 } 59 return &ExternalBackend{ 60 signers: []accounts.Wallet{signer}, 61 }, nil 62 } 63 64 func (eb *ExternalBackend) Subscribe(sink chan<- accounts.WalletEvent) event.Subscription { 65 return event.NewSubscription(func(quit <-chan struct{}) error { 66 <-quit 67 return nil 68 }) 69 } 70 71 // ExternalSigner provides an API to interact with an external signer (clef) 72 // It proxies request to the external signer while forwarding relevant 73 // request headers 74 type ExternalSigner struct { 75 client *rpc.Client 76 endpoint string 77 status string 78 cacheMu sync.RWMutex 79 cache []accounts.Account 80 } 81 82 func NewExternalSigner(endpoint string) (*ExternalSigner, error) { 83 client, err := rpc.Dial(endpoint) 84 if err != nil { 85 return nil, err 86 } 87 extsigner := &ExternalSigner{ 88 client: client, 89 endpoint: endpoint, 90 } 91 // Check if reachable 92 version, err := extsigner.pingVersion() 93 if err != nil { 94 return nil, err 95 } 96 extsigner.status = fmt.Sprintf("ok [version=%v]", version) 97 return extsigner, nil 98 } 99 100 func (api *ExternalSigner) URL() accounts.URL { 101 return accounts.URL{ 102 Scheme: "extapi", 103 Path: api.endpoint, 104 } 105 } 106 107 func (api *ExternalSigner) Status() (string, error) { 108 return api.status, nil 109 } 110 111 func (api *ExternalSigner) Open(passphrase string) error { 112 return errors.New("operation not supported on external signers") 113 } 114 115 func (api *ExternalSigner) Close() error { 116 return errors.New("operation not supported on external signers") 117 } 118 119 func (api *ExternalSigner) Accounts() []accounts.Account { 120 var accnts []accounts.Account 121 res, err := api.listAccounts() 122 if err != nil { 123 log.Error("account listing failed", "error", err) 124 return accnts 125 } 126 for _, addr := range res { 127 accnts = append(accnts, accounts.Account{ 128 URL: accounts.URL{ 129 Scheme: "extapi", 130 Path: api.endpoint, 131 }, 132 Address: addr, 133 }) 134 } 135 api.cacheMu.Lock() 136 api.cache = accnts 137 api.cacheMu.Unlock() 138 return accnts 139 } 140 141 func (api *ExternalSigner) Contains(account accounts.Account) bool { 142 api.cacheMu.RLock() 143 defer api.cacheMu.RUnlock() 144 if api.cache == nil { 145 // If we haven't already fetched the accounts, it's time to do so now 146 api.cacheMu.RUnlock() 147 api.Accounts() 148 api.cacheMu.RLock() 149 } 150 for _, a := range api.cache { 151 if a.Address == account.Address && (account.URL == (accounts.URL{}) || account.URL == api.URL()) { 152 return true 153 } 154 } 155 return false 156 } 157 158 func (api *ExternalSigner) Derive(path accounts.DerivationPath, pin bool) (accounts.Account, error) { 159 return accounts.Account{}, errors.New("operation not supported on external signers") 160 } 161 162 func (api *ExternalSigner) SelfDerive(bases []accounts.DerivationPath, chain interfaces.ChainStateReader) { 163 log.Error("operation SelfDerive not supported on external signers") 164 } 165 166 // SignData signs keccak256(data). The mimetype parameter describes the type of data being signed 167 func (api *ExternalSigner) SignData(account accounts.Account, mimeType string, data []byte) ([]byte, error) { 168 var res hexutil.Bytes 169 var signAddress = common.NewMixedcaseAddress(account.Address) 170 if err := api.client.Call(&res, "account_signData", 171 mimeType, 172 &signAddress, // Need to use the pointer here, because of how MarshalJSON is defined 173 hexutil.Encode(data)); err != nil { 174 return nil, err 175 } 176 // If V is on 27/28-form, convert to 0/1 for Clique 177 if mimeType == accounts.MimetypeClique && (res[64] == 27 || res[64] == 28) { 178 res[64] -= 27 // Transform V from 27/28 to 0/1 for Clique use 179 } 180 return res, nil 181 } 182 183 func (api *ExternalSigner) SignText(account accounts.Account, text []byte) ([]byte, error) { 184 var signature hexutil.Bytes 185 var signAddress = common.NewMixedcaseAddress(account.Address) 186 if err := api.client.Call(&signature, "account_signData", 187 accounts.MimetypeTextPlain, 188 &signAddress, // Need to use the pointer here, because of how MarshalJSON is defined 189 hexutil.Encode(text)); err != nil { 190 return nil, err 191 } 192 if signature[64] == 27 || signature[64] == 28 { 193 // If clef is used as a backend, it may already have transformed 194 // the signature to ethereum-type signature. 195 signature[64] -= 27 // Transform V from Ethereum-legacy to 0/1 196 } 197 return signature, nil 198 } 199 200 // signTransactionResult represents the signinig result returned by clef. 201 type signTransactionResult struct { 202 Raw hexutil.Bytes `json:"raw"` 203 Tx *types.Transaction `json:"tx"` 204 } 205 206 // SignTx sends the transaction to the external signer. 207 // If chainID is nil, or tx.ChainID is zero, the chain ID will be assigned 208 // by the external signer. For non-legacy transactions, the chain ID of the 209 // transaction overrides the chainID parameter. 210 func (api *ExternalSigner) SignTx(account accounts.Account, tx *types.Transaction, chainID *big.Int) (*types.Transaction, error) { 211 data := hexutil.Bytes(tx.Data()) 212 var to *common.MixedcaseAddress 213 if tx.To() != nil { 214 t := common.NewMixedcaseAddress(*tx.To()) 215 to = &t 216 } 217 args := &apitypes.SendTxArgs{ 218 Data: &data, 219 Nonce: hexutil.Uint64(tx.Nonce()), 220 Value: hexutil.Big(*tx.Value()), 221 Gas: hexutil.Uint64(tx.Gas()), 222 To: to, 223 From: common.NewMixedcaseAddress(account.Address), 224 } 225 switch tx.Type() { 226 case types.LegacyTxType, types.AccessListTxType: 227 args.GasPrice = (*hexutil.Big)(tx.GasPrice()) 228 case types.DynamicFeeTxType: 229 args.MaxFeePerGas = (*hexutil.Big)(tx.GasFeeCap()) 230 args.MaxPriorityFeePerGas = (*hexutil.Big)(tx.GasTipCap()) 231 default: 232 return nil, fmt.Errorf("unsupported tx type %d", tx.Type()) 233 } 234 // We should request the default chain id that we're operating with 235 // (the chain we're executing on) 236 if chainID != nil && chainID.Sign() != 0 { 237 args.ChainID = (*hexutil.Big)(chainID) 238 } 239 if tx.Type() != types.LegacyTxType { 240 // However, if the user asked for a particular chain id, then we should 241 // use that instead. 242 if tx.ChainId().Sign() != 0 { 243 args.ChainID = (*hexutil.Big)(tx.ChainId()) 244 } 245 accessList := tx.AccessList() 246 args.AccessList = &accessList 247 } 248 var res signTransactionResult 249 if err := api.client.Call(&res, "account_signTransaction", args); err != nil { 250 return nil, err 251 } 252 return res.Tx, nil 253 } 254 255 func (api *ExternalSigner) SignTextWithPassphrase(account accounts.Account, passphrase string, text []byte) ([]byte, error) { 256 return []byte{}, errors.New("password-operations not supported on external signers") 257 } 258 259 func (api *ExternalSigner) SignTxWithPassphrase(account accounts.Account, passphrase string, tx *types.Transaction, chainID *big.Int) (*types.Transaction, error) { 260 return nil, errors.New("password-operations not supported on external signers") 261 } 262 func (api *ExternalSigner) SignDataWithPassphrase(account accounts.Account, passphrase, mimeType string, data []byte) ([]byte, error) { 263 return nil, errors.New("password-operations not supported on external signers") 264 } 265 266 func (api *ExternalSigner) listAccounts() ([]common.Address, error) { 267 var res []common.Address 268 if err := api.client.Call(&res, "account_list"); err != nil { 269 return nil, err 270 } 271 return res, nil 272 } 273 274 func (api *ExternalSigner) pingVersion() (string, error) { 275 var v string 276 if err := api.client.Call(&v, "account_version"); err != nil { 277 return "", err 278 } 279 return v, nil 280 }