github.com/aidoskuneen/adk-node@v0.0.0-20220315131952-2e32567cb7f4/accounts/external/backend.go (about)

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