gitlab.com/flarenetwork/coreth@v0.1.1/plugin/evm/service.go (about)

     1  // (c) 2019-2020, Ava Labs, Inc. All rights reserved.
     2  // See the file LICENSE for licensing terms.
     3  
     4  package evm
     5  
     6  import (
     7  	"context"
     8  	"errors"
     9  	"fmt"
    10  	"math/big"
    11  	"net/http"
    12  	"strings"
    13  
    14  	"github.com/ava-labs/avalanchego/api"
    15  	"github.com/ava-labs/avalanchego/ids"
    16  	"github.com/ava-labs/avalanchego/utils/constants"
    17  	"github.com/ava-labs/avalanchego/utils/crypto"
    18  	"github.com/ava-labs/avalanchego/utils/formatting"
    19  	"github.com/ava-labs/avalanchego/utils/json"
    20  	"github.com/ethereum/go-ethereum/common"
    21  	"github.com/ethereum/go-ethereum/common/hexutil"
    22  	ethcrypto "github.com/ethereum/go-ethereum/crypto"
    23  	"github.com/ethereum/go-ethereum/log"
    24  	"gitlab.com/flarenetwork/coreth/params"
    25  )
    26  
    27  // test constants
    28  const (
    29  	GenesisTestAddr = "0x751a0b96e1042bee789452ecb20253fba40dbe85"
    30  	GenesisTestKey  = "0xabd71b35d559563fea757f0f5edbde286fb8c043105b15abb7cd57189306d7d1"
    31  
    32  	// Max number of addresses that can be passed in as argument to GetUTXOs
    33  	maxGetUTXOsAddrs = 1024
    34  )
    35  
    36  var (
    37  	errNoAddresses   = errors.New("no addresses provided")
    38  	errNoSourceChain = errors.New("no source chain provided")
    39  	errNilTxID       = errors.New("nil transaction ID")
    40  
    41  	initialBaseFee = big.NewInt(params.ApricotPhase3InitialBaseFee)
    42  )
    43  
    44  // SnowmanAPI introduces snowman specific functionality to the evm
    45  type SnowmanAPI struct{ vm *VM }
    46  
    47  // AvaxAPI offers Avalanche network related API methods
    48  type AvaxAPI struct{ vm *VM }
    49  
    50  // NetAPI offers network related API methods
    51  type NetAPI struct{ vm *VM }
    52  
    53  // Listening returns an indication if the node is listening for network connections.
    54  func (s *NetAPI) Listening() bool { return true } // always listening
    55  
    56  // PeerCount returns the number of connected peers
    57  func (s *NetAPI) PeerCount() hexutil.Uint { return hexutil.Uint(0) }
    58  
    59  // Version returns the current ethereum protocol version.
    60  func (s *NetAPI) Version() string { return fmt.Sprintf("%d", s.vm.networkID) }
    61  
    62  // Web3API offers helper API methods
    63  type Web3API struct{}
    64  
    65  // ClientVersion returns the version of the vm running
    66  func (s *Web3API) ClientVersion() string { return Version }
    67  
    68  // Sha3 returns the bytes returned by hashing [input] with Keccak256
    69  func (s *Web3API) Sha3(input hexutil.Bytes) hexutil.Bytes { return ethcrypto.Keccak256(input) }
    70  
    71  // GetAcceptedFrontReply defines the reply that will be sent from the
    72  // GetAcceptedFront API call
    73  type GetAcceptedFrontReply struct {
    74  	Hash   common.Hash `json:"hash"`
    75  	Number *big.Int    `json:"number"`
    76  }
    77  
    78  // GetAcceptedFront returns the last accepted block's hash and height
    79  func (api *SnowmanAPI) GetAcceptedFront(ctx context.Context) (*GetAcceptedFrontReply, error) {
    80  	blk := api.vm.chain.LastAcceptedBlock()
    81  	return &GetAcceptedFrontReply{
    82  		Hash:   blk.Hash(),
    83  		Number: blk.Number(),
    84  	}, nil
    85  }
    86  
    87  // IssueBlock to the chain
    88  func (api *SnowmanAPI) IssueBlock(ctx context.Context) error {
    89  	log.Info("Issuing a new block")
    90  
    91  	api.vm.signalTxsReady()
    92  	return nil
    93  }
    94  
    95  // parseAssetID parses an assetID string into an ID
    96  func (service *AvaxAPI) parseAssetID(assetID string) (ids.ID, error) {
    97  	if assetID == "" {
    98  		return ids.ID{}, fmt.Errorf("assetID is required")
    99  	} else if assetID == "AVAX" {
   100  		return service.vm.ctx.AVAXAssetID, nil
   101  	} else {
   102  		return ids.FromString(assetID)
   103  	}
   104  }
   105  
   106  type VersionReply struct {
   107  	Version string `json:"version"`
   108  }
   109  
   110  // ClientVersion returns the version of the VM running
   111  func (service *AvaxAPI) Version(r *http.Request, args *struct{}, reply *VersionReply) error {
   112  	reply.Version = Version
   113  	return nil
   114  }
   115  
   116  // ExportKeyArgs are arguments for ExportKey
   117  type ExportKeyArgs struct {
   118  	api.UserPass
   119  	Address string `json:"address"`
   120  }
   121  
   122  // ExportKeyReply is the response for ExportKey
   123  type ExportKeyReply struct {
   124  	// The decrypted PrivateKey for the Address provided in the arguments
   125  	PrivateKey    string `json:"privateKey"`
   126  	PrivateKeyHex string `json:"privateKeyHex"`
   127  }
   128  
   129  // ExportKey returns a private key from the provided user
   130  func (service *AvaxAPI) ExportKey(r *http.Request, args *ExportKeyArgs, reply *ExportKeyReply) error {
   131  	log.Info("EVM: ExportKey called")
   132  
   133  	address, err := ParseEthAddress(args.Address)
   134  	if err != nil {
   135  		return fmt.Errorf("couldn't parse %s to address: %s", args.Address, err)
   136  	}
   137  
   138  	db, err := service.vm.ctx.Keystore.GetDatabase(args.Username, args.Password)
   139  	if err != nil {
   140  		return fmt.Errorf("problem retrieving user '%s': %w", args.Username, err)
   141  	}
   142  	defer db.Close()
   143  
   144  	user := user{
   145  		secpFactory: &service.vm.secpFactory,
   146  		db:          db,
   147  	}
   148  	sk, err := user.getKey(address)
   149  	if err != nil {
   150  		return fmt.Errorf("problem retrieving private key: %w", err)
   151  	}
   152  	encodedKey, err := formatting.EncodeWithChecksum(formatting.CB58, sk.Bytes())
   153  	if err != nil {
   154  		return fmt.Errorf("problem encoding bytes as cb58: %w", err)
   155  	}
   156  	reply.PrivateKey = constants.SecretKeyPrefix + encodedKey
   157  	reply.PrivateKeyHex = hexutil.Encode(sk.Bytes())
   158  	return nil
   159  }
   160  
   161  // ImportKeyArgs are arguments for ImportKey
   162  type ImportKeyArgs struct {
   163  	api.UserPass
   164  	PrivateKey string `json:"privateKey"`
   165  }
   166  
   167  // ImportKey adds a private key to the provided user
   168  func (service *AvaxAPI) ImportKey(r *http.Request, args *ImportKeyArgs, reply *api.JSONAddress) error {
   169  	log.Info("EVM: ImportKey called", "username", args.Username)
   170  
   171  	if !strings.HasPrefix(args.PrivateKey, constants.SecretKeyPrefix) {
   172  		return fmt.Errorf("private key missing %s prefix", constants.SecretKeyPrefix)
   173  	}
   174  
   175  	trimmedPrivateKey := strings.TrimPrefix(args.PrivateKey, constants.SecretKeyPrefix)
   176  	pkBytes, err := formatting.Decode(formatting.CB58, trimmedPrivateKey)
   177  	if err != nil {
   178  		return fmt.Errorf("problem parsing private key: %w", err)
   179  	}
   180  
   181  	skIntf, err := service.vm.secpFactory.ToPrivateKey(pkBytes)
   182  	if err != nil {
   183  		return fmt.Errorf("problem parsing private key: %w", err)
   184  	}
   185  	sk, ok := skIntf.(*crypto.PrivateKeySECP256K1R)
   186  	if !ok {
   187  		return fmt.Errorf("expected *crypto.PrivateKeySECP256K1R but got %T", skIntf)
   188  	}
   189  
   190  	// TODO: return eth address here
   191  	reply.Address = FormatEthAddress(GetEthAddress(sk))
   192  
   193  	db, err := service.vm.ctx.Keystore.GetDatabase(args.Username, args.Password)
   194  	if err != nil {
   195  		return fmt.Errorf("problem retrieving data: %w", err)
   196  	}
   197  	defer db.Close()
   198  
   199  	user := user{
   200  		secpFactory: &service.vm.secpFactory,
   201  		db:          db,
   202  	}
   203  	if err := user.putAddress(sk); err != nil {
   204  		return fmt.Errorf("problem saving key %w", err)
   205  	}
   206  	return nil
   207  }
   208  
   209  // ImportArgs are arguments for passing into Import requests
   210  type ImportArgs struct {
   211  	api.UserPass
   212  
   213  	// Fee that should be used when creating the tx
   214  	BaseFee *hexutil.Big `json:"baseFee"`
   215  
   216  	// Chain the funds are coming from
   217  	SourceChain string `json:"sourceChain"`
   218  
   219  	// The address that will receive the imported funds
   220  	To string `json:"to"`
   221  }
   222  
   223  // ImportAVAX is a deprecated name for Import.
   224  func (service *AvaxAPI) ImportAVAX(_ *http.Request, args *ImportArgs, response *api.JSONTxID) error {
   225  	return service.Import(nil, args, response)
   226  }
   227  
   228  // Import issues a transaction to import AVAX from the X-chain. The AVAX
   229  // must have already been exported from the X-Chain.
   230  func (service *AvaxAPI) Import(_ *http.Request, args *ImportArgs, response *api.JSONTxID) error {
   231  	log.Info("EVM: ImportAVAX called")
   232  
   233  	chainID, err := service.vm.ctx.BCLookup.Lookup(args.SourceChain)
   234  	if err != nil {
   235  		return fmt.Errorf("problem parsing chainID %q: %w", args.SourceChain, err)
   236  	}
   237  
   238  	to, err := ParseEthAddress(args.To)
   239  	if err != nil { // Parse address
   240  		return fmt.Errorf("couldn't parse argument 'to' to an address: %w", err)
   241  	}
   242  
   243  	// Get the user's info
   244  	db, err := service.vm.ctx.Keystore.GetDatabase(args.Username, args.Password)
   245  	if err != nil {
   246  		return fmt.Errorf("couldn't get user '%s': %w", args.Username, err)
   247  	}
   248  	defer db.Close()
   249  
   250  	user := user{
   251  		secpFactory: &service.vm.secpFactory,
   252  		db:          db,
   253  	}
   254  	privKeys, err := user.getKeys()
   255  	if err != nil { // Get keys
   256  		return fmt.Errorf("couldn't get keys controlled by the user: %w", err)
   257  	}
   258  
   259  	var baseFee *big.Int
   260  	if args.BaseFee == nil {
   261  		// Get the base fee to use
   262  		baseFee, err = service.vm.estimateBaseFee(context.Background())
   263  		if err != nil {
   264  			return err
   265  		}
   266  	} else {
   267  		baseFee = args.BaseFee.ToInt()
   268  	}
   269  
   270  	tx, err := service.vm.newImportTx(chainID, to, baseFee, privKeys)
   271  	if err != nil {
   272  		return err
   273  	}
   274  
   275  	response.TxID = tx.ID()
   276  	return service.vm.issueTx(tx)
   277  }
   278  
   279  // ExportAVAXArgs are the arguments to ExportAVAX
   280  type ExportAVAXArgs struct {
   281  	api.UserPass
   282  
   283  	// Fee that should be used when creating the tx
   284  	BaseFee *hexutil.Big `json:"baseFee"`
   285  
   286  	// Amount of asset to send
   287  	Amount json.Uint64 `json:"amount"`
   288  
   289  	// ID of the address that will receive the AVAX. This address includes the
   290  	// chainID, which is used to determine what the destination chain is.
   291  	To string `json:"to"`
   292  }
   293  
   294  // ExportAVAX exports AVAX from the C-Chain to the X-Chain
   295  // It must be imported on the X-Chain to complete the transfer
   296  func (service *AvaxAPI) ExportAVAX(_ *http.Request, args *ExportAVAXArgs, response *api.JSONTxID) error {
   297  	return service.Export(nil, &ExportArgs{
   298  		ExportAVAXArgs: *args,
   299  		AssetID:        service.vm.ctx.AVAXAssetID.String(),
   300  	}, response)
   301  }
   302  
   303  // ExportArgs are the arguments to Export
   304  type ExportArgs struct {
   305  	ExportAVAXArgs
   306  	// AssetID of the tokens
   307  	AssetID string `json:"assetID"`
   308  }
   309  
   310  // Export exports an asset from the C-Chain to the X-Chain
   311  // It must be imported on the X-Chain to complete the transfer
   312  func (service *AvaxAPI) Export(_ *http.Request, args *ExportArgs, response *api.JSONTxID) error {
   313  	log.Info("EVM: Export called")
   314  
   315  	assetID, err := service.parseAssetID(args.AssetID)
   316  	if err != nil {
   317  		return err
   318  	}
   319  
   320  	if args.Amount == 0 {
   321  		return errors.New("argument 'amount' must be > 0")
   322  	}
   323  
   324  	chainID, to, err := service.vm.ParseAddress(args.To)
   325  	if err != nil {
   326  		return err
   327  	}
   328  
   329  	// Get this user's data
   330  	db, err := service.vm.ctx.Keystore.GetDatabase(args.Username, args.Password)
   331  	if err != nil {
   332  		return fmt.Errorf("problem retrieving user '%s': %w", args.Username, err)
   333  	}
   334  	defer db.Close()
   335  
   336  	user := user{
   337  		secpFactory: &service.vm.secpFactory,
   338  		db:          db,
   339  	}
   340  	privKeys, err := user.getKeys()
   341  	if err != nil {
   342  		return fmt.Errorf("couldn't get addresses controlled by the user: %w", err)
   343  	}
   344  
   345  	var baseFee *big.Int
   346  	if args.BaseFee == nil {
   347  		// Get the base fee to use
   348  		baseFee, err = service.vm.estimateBaseFee(context.Background())
   349  		if err != nil {
   350  			return err
   351  		}
   352  	} else {
   353  		baseFee = args.BaseFee.ToInt()
   354  	}
   355  
   356  	// Create the transaction
   357  	tx, err := service.vm.newExportTx(
   358  		assetID,             // AssetID
   359  		uint64(args.Amount), // Amount
   360  		chainID,             // ID of the chain to send the funds to
   361  		to,                  // Address
   362  		baseFee,
   363  		privKeys, // Private keys
   364  	)
   365  	if err != nil {
   366  		return fmt.Errorf("couldn't create tx: %w", err)
   367  	}
   368  
   369  	response.TxID = tx.ID()
   370  	return service.vm.issueTx(tx)
   371  }
   372  
   373  // GetUTXOs gets all utxos for passed in addresses
   374  func (service *AvaxAPI) GetUTXOs(r *http.Request, args *api.GetUTXOsArgs, reply *api.GetUTXOsReply) error {
   375  	service.vm.ctx.Log.Info("EVM: GetUTXOs called for with %s", args.Addresses)
   376  
   377  	if len(args.Addresses) == 0 {
   378  		return errNoAddresses
   379  	}
   380  	if len(args.Addresses) > maxGetUTXOsAddrs {
   381  		return fmt.Errorf("number of addresses given, %d, exceeds maximum, %d", len(args.Addresses), maxGetUTXOsAddrs)
   382  	}
   383  
   384  	if args.SourceChain == "" {
   385  		return errNoSourceChain
   386  	}
   387  
   388  	chainID, err := service.vm.ctx.BCLookup.Lookup(args.SourceChain)
   389  	if err != nil {
   390  		return fmt.Errorf("problem parsing source chainID %q: %w", args.SourceChain, err)
   391  	}
   392  	sourceChain := chainID
   393  
   394  	addrSet := ids.ShortSet{}
   395  	for _, addrStr := range args.Addresses {
   396  		addr, err := service.vm.ParseLocalAddress(addrStr)
   397  		if err != nil {
   398  			return fmt.Errorf("couldn't parse address %q: %w", addrStr, err)
   399  		}
   400  		addrSet.Add(addr)
   401  	}
   402  
   403  	startAddr := ids.ShortEmpty
   404  	startUTXO := ids.Empty
   405  	if args.StartIndex.Address != "" || args.StartIndex.UTXO != "" {
   406  		startAddr, err = service.vm.ParseLocalAddress(args.StartIndex.Address)
   407  		if err != nil {
   408  			return fmt.Errorf("couldn't parse start index address %q: %w", args.StartIndex.Address, err)
   409  		}
   410  		startUTXO, err = ids.FromString(args.StartIndex.UTXO)
   411  		if err != nil {
   412  			return fmt.Errorf("couldn't parse start index utxo: %w", err)
   413  		}
   414  	}
   415  
   416  	utxos, endAddr, endUTXOID, err := service.vm.GetAtomicUTXOs(
   417  		sourceChain,
   418  		addrSet,
   419  		startAddr,
   420  		startUTXO,
   421  		int(args.Limit),
   422  	)
   423  	if err != nil {
   424  		return fmt.Errorf("problem retrieving UTXOs: %w", err)
   425  	}
   426  
   427  	reply.UTXOs = make([]string, len(utxos))
   428  	for i, utxo := range utxos {
   429  		b, err := service.vm.codec.Marshal(codecVersion, utxo)
   430  		if err != nil {
   431  			return fmt.Errorf("problem marshalling UTXO: %w", err)
   432  		}
   433  		str, err := formatting.EncodeWithChecksum(args.Encoding, b)
   434  		if err != nil {
   435  			return fmt.Errorf("problem encoding utxo: %w", err)
   436  		}
   437  		reply.UTXOs[i] = str
   438  	}
   439  
   440  	endAddress, err := service.vm.FormatLocalAddress(endAddr)
   441  	if err != nil {
   442  		return fmt.Errorf("problem formatting address: %w", err)
   443  	}
   444  
   445  	reply.EndIndex.Address = endAddress
   446  	reply.EndIndex.UTXO = endUTXOID.String()
   447  	reply.NumFetched = json.Uint64(len(utxos))
   448  	reply.Encoding = args.Encoding
   449  	return nil
   450  }
   451  
   452  // IssueTx ...
   453  func (service *AvaxAPI) IssueTx(r *http.Request, args *api.FormattedTx, response *api.JSONTxID) error {
   454  	log.Info("EVM: IssueTx called")
   455  
   456  	txBytes, err := formatting.Decode(args.Encoding, args.Tx)
   457  	if err != nil {
   458  		return fmt.Errorf("problem decoding transaction: %w", err)
   459  	}
   460  
   461  	tx := &Tx{}
   462  	if _, err := service.vm.codec.Unmarshal(txBytes, tx); err != nil {
   463  		return fmt.Errorf("problem parsing transaction: %w", err)
   464  	}
   465  	if err := tx.Sign(service.vm.codec, nil); err != nil {
   466  		return fmt.Errorf("problem initializing transaction: %w", err)
   467  	}
   468  
   469  	response.TxID = tx.ID()
   470  	return service.vm.issueTx(tx)
   471  }
   472  
   473  // GetAtomicTxStatusReply defines the GetAtomicTxStatus replies returned from the API
   474  type GetAtomicTxStatusReply struct {
   475  	Status      Status       `json:"status"`
   476  	BlockHeight *json.Uint64 `json:"blockHeight,omitempty"`
   477  }
   478  
   479  // GetAtomicTxStatus returns the status of the specified transaction
   480  func (service *AvaxAPI) GetAtomicTxStatus(r *http.Request, args *api.JSONTxID, reply *GetAtomicTxStatusReply) error {
   481  	log.Info("EVM: GetAtomicTxStatus called", "txID", args.TxID)
   482  
   483  	if args.TxID == ids.Empty {
   484  		return errNilTxID
   485  	}
   486  
   487  	_, status, height, _ := service.vm.getAtomicTx(args.TxID)
   488  
   489  	reply.Status = status
   490  	if status == Accepted {
   491  		jsonHeight := json.Uint64(height)
   492  		reply.BlockHeight = &jsonHeight
   493  	}
   494  	return nil
   495  }
   496  
   497  type FormattedTx struct {
   498  	api.FormattedTx
   499  	BlockHeight *json.Uint64 `json:"blockHeight,omitempty"`
   500  }
   501  
   502  // GetAtomicTx returns the specified transaction
   503  func (service *AvaxAPI) GetAtomicTx(r *http.Request, args *api.GetTxArgs, reply *FormattedTx) error {
   504  	log.Info("EVM: GetAtomicTx called", "txID", args.TxID)
   505  
   506  	if args.TxID == ids.Empty {
   507  		return errNilTxID
   508  	}
   509  
   510  	tx, status, height, err := service.vm.getAtomicTx(args.TxID)
   511  	if err != nil {
   512  		return err
   513  	}
   514  
   515  	if status == Unknown {
   516  		return fmt.Errorf("could not find tx %s", args.TxID)
   517  	}
   518  
   519  	txBytes, err := formatting.EncodeWithChecksum(args.Encoding, tx.Bytes())
   520  	if err != nil {
   521  		return err
   522  	}
   523  	reply.Tx = txBytes
   524  	reply.Encoding = args.Encoding
   525  	if status == Accepted {
   526  		jsonHeight := json.Uint64(height)
   527  		reply.BlockHeight = &jsonHeight
   528  	}
   529  	return nil
   530  }