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