From d46b49cc7a0c624cb29eb40016d956d10e100ec9 Mon Sep 17 00:00:00 2001
From: Aaron Buchwald <aaron.buchwald56@gmail.com>
Date: Tue, 3 Nov 2020 20:11:32 -0500
Subject: Add bech32 address parsing

---
 plugin/evm/vm.go | 41 ++++++++++++++++++++++++++++++++++++++---
 1 file changed, 38 insertions(+), 3 deletions(-)

(limited to 'plugin')

diff --git a/plugin/evm/vm.go b/plugin/evm/vm.go
index a8c4fc0..fd177a0 100644
--- a/plugin/evm/vm.go
+++ b/plugin/evm/vm.go
@@ -155,7 +155,8 @@ func init() {
 type VM struct {
 	ctx *snow.Context
 
-	CLIConfig CommandLineConfig
+	CLIConfig       CommandLineConfig
+	encodingManager formatting.EncodingManager
 
 	chainID          *big.Int
 	networkID        uint64
@@ -236,6 +237,12 @@ func (vm *VM) Initialize(
 		return vm.CLIConfig.ParsingError
 	}
 
+	encodingManager, err := formatting.NewEncodingManager(formatting.CB58Encoding)
+	if err != nil {
+		return fmt.Errorf("problem creating encoding manager: %w", err)
+	}
+	vm.encodingManager = encodingManager
+
 	if len(fxs) > 0 {
 		return errUnsupportedFXs
 	}
@@ -243,8 +250,7 @@ func (vm *VM) Initialize(
 	vm.ctx = ctx
 	vm.chaindb = Database{db}
 	g := new(core.Genesis)
-	err := json.Unmarshal(b, g)
-	if err != nil {
+	if err := json.Unmarshal(b, g); err != nil {
 		return err
 	}
 
@@ -947,6 +953,35 @@ func (vm *VM) GetAcceptedNonce(address common.Address) (uint64, error) {
 	return state.GetNonce(address), nil
 }
 
+// ParseLocalAddress takes in an address for this chain and produces the ID
+func (vm *VM) ParseLocalAddress(addrStr string) (ids.ShortID, error) {
+	chainID, addr, err := vm.ParseAddress(addrStr)
+	if err != nil {
+		return ids.ShortID{}, err
+	}
+	if !chainID.Equals(vm.ctx.ChainID) {
+		return ids.ShortID{}, fmt.Errorf("expected chainID to be %q but was %q",
+			vm.ctx.ChainID, chainID)
+	}
+	return addr, nil
+}
+
+// FormatLocalAddress takes in a raw address and produces the formatted address
+func (vm *VM) FormatLocalAddress(addr ids.ShortID) (string, error) {
+	return vm.FormatAddress(vm.ctx.ChainID, addr)
+}
+
+// FormatAddress takes in a chainID and a raw address and produces the formatted
+// address
+func (vm *VM) FormatAddress(chainID ids.ID, addr ids.ShortID) (string, error) {
+	chainIDAlias, err := vm.ctx.BCLookup.PrimaryAlias(chainID)
+	if err != nil {
+		return "", err
+	}
+	hrp := constants.GetHRP(vm.ctx.NetworkID)
+	return formatting.FormatAddress(chainIDAlias, hrp, addr.Bytes())
+}
+
 // ParseEthAddress parses [addrStr] and returns an Ethereum address
 func ParseEthAddress(addrStr string) (common.Address, error) {
 	if !common.IsHexAddress(addrStr) {
-- 
cgit v1.2.3-70-g09d2


From 3a887bbc7b7d88070dbcdb1104119bce282ed249 Mon Sep 17 00:00:00 2001
From: Aaron Buchwald <aaron.buchwald56@gmail.com>
Date: Tue, 3 Nov 2020 20:11:42 -0500
Subject: Add getUTXOs and issueTx

---
 plugin/evm/service.go | 164 ++++++++++++++++++++++++++++++++++++++++++++++++++
 plugin/evm/tx.go      |   2 +-
 2 files changed, 165 insertions(+), 1 deletion(-)

(limited to 'plugin')

diff --git a/plugin/evm/service.go b/plugin/evm/service.go
index 7b8368e..f9b32b7 100644
--- a/plugin/evm/service.go
+++ b/plugin/evm/service.go
@@ -31,6 +31,14 @@ const (
 const (
 	GenesisTestAddr = "0x751a0b96e1042bee789452ecb20253fba40dbe85"
 	GenesisTestKey  = "0xabd71b35d559563fea757f0f5edbde286fb8c043105b15abb7cd57189306d7d1"
+
+	// Max number of addresses that can be passed in as argument to GetUTXOs
+	maxGetUTXOsAddrs = 1024
+)
+
+var (
+	errNoAddresses   = errors.New("no addresses provided")
+	errNoSourceChain = errors.New("no source chain provided")
 )
 
 // SnowmanAPI introduces snowman specific functionality to the evm
@@ -295,3 +303,159 @@ func (service *AvaxAPI) Export(_ *http.Request, args *ExportArgs, response *api.
 	response.TxID = tx.ID()
 	return service.vm.issueTx(tx)
 }
+
+// Index is an address and an associated UTXO.
+// Marks a starting or stopping point when fetching UTXOs. Used for pagination.
+type Index struct {
+	Address string `json:"address"` // The address as a string
+	UTXO    string `json:"utxo"`    // The UTXO ID as a string
+}
+
+// GetUTXOsArgs are arguments for passing into GetUTXOs.
+// Gets the UTXOs that reference at least one address in [Addresses].
+// Returns at most [limit] addresses.
+// If specified, [SourceChain] is the chain where the atomic UTXOs were exported from. If not specified,
+// then GetUTXOs returns an error since the C Chain only has atomic UTXOs.
+// If [limit] == 0 or > [maxUTXOsToFetch], fetches up to [maxUTXOsToFetch].
+// [StartIndex] defines where to start fetching UTXOs (for pagination.)
+// UTXOs fetched are from addresses equal to or greater than [StartIndex.Address]
+// For address [StartIndex.Address], only UTXOs with IDs greater than [StartIndex.UTXO] will be returned.
+// If [StartIndex] is omitted, gets all UTXOs.
+// If GetUTXOs is called multiple times, with our without [StartIndex], it is not guaranteed
+// that returned UTXOs are unique. That is, the same UTXO may appear in the response of multiple calls.
+type GetUTXOsArgs struct {
+	Addresses   []string    `json:"addresses"`
+	SourceChain string      `json:"sourceChain"`
+	Limit       json.Uint32 `json:"limit"`
+	StartIndex  Index       `json:"startIndex"`
+	Encoding    string      `json:"encoding"`
+}
+
+// GetUTXOsReply defines the GetUTXOs replies returned from the API
+type GetUTXOsReply struct {
+	// Number of UTXOs returned
+	NumFetched json.Uint64 `json:"numFetched"`
+	// The UTXOs
+	UTXOs []string `json:"utxos"`
+	// The last UTXO that was returned, and the address it corresponds to.
+	// Used for pagination. To get the rest of the UTXOs, call GetUTXOs
+	// again and set [StartIndex] to this value.
+	EndIndex Index `json:"endIndex"`
+	// Encoding specifies the encoding format the UTXOs are returned in
+	Encoding string `json:"encoding"`
+}
+
+// GetUTXOs gets all utxos for passed in addresses
+func (service *AvaxAPI) GetUTXOs(r *http.Request, args *GetUTXOsArgs, reply *GetUTXOsReply) error {
+	service.vm.ctx.Log.Info("EVM: GetUTXOs called for with %s", args.Addresses)
+
+	if len(args.Addresses) == 0 {
+		return errNoAddresses
+	}
+	if len(args.Addresses) > maxGetUTXOsAddrs {
+		return fmt.Errorf("number of addresses given, %d, exceeds maximum, %d", len(args.Addresses), maxGetUTXOsAddrs)
+	}
+
+	encoding, err := service.vm.encodingManager.GetEncoding(args.Encoding)
+	if err != nil {
+		return fmt.Errorf("problem getting encoding formatter for '%s': %w", args.Encoding, err)
+	}
+
+	sourceChain := ids.ID{}
+	if args.SourceChain == "" {
+		return errNoSourceChain
+	}
+
+	chainID, err := service.vm.ctx.BCLookup.Lookup(args.SourceChain)
+	if err != nil {
+		return fmt.Errorf("problem parsing source chainID %q: %w", args.SourceChain, err)
+	}
+	sourceChain = chainID
+
+	addrSet := ids.ShortSet{}
+	for _, addrStr := range args.Addresses {
+		addr, err := service.vm.ParseLocalAddress(addrStr)
+		if err != nil {
+			return fmt.Errorf("couldn't parse address %q: %w", addrStr, err)
+		}
+		addrSet.Add(addr)
+	}
+
+	startAddr := ids.ShortEmpty
+	startUTXO := ids.Empty
+	if args.StartIndex.Address != "" || args.StartIndex.UTXO != "" {
+		startAddr, err = service.vm.ParseLocalAddress(args.StartIndex.Address)
+		if err != nil {
+			return fmt.Errorf("couldn't parse start index address %q: %w", args.StartIndex.Address, err)
+		}
+		startUTXO, err = ids.FromString(args.StartIndex.UTXO)
+		if err != nil {
+			return fmt.Errorf("couldn't parse start index utxo: %w", err)
+		}
+	}
+
+	utxos, endAddr, endUTXOID, err := service.vm.GetAtomicUTXOs(
+		sourceChain,
+		addrSet,
+		startAddr,
+		startUTXO,
+		int(args.Limit),
+	)
+	if err != nil {
+		return fmt.Errorf("problem retrieving UTXOs: %w", err)
+	}
+
+	reply.UTXOs = make([]string, len(utxos))
+	for i, utxo := range utxos {
+		b, err := service.vm.codec.Marshal(utxo)
+		if err != nil {
+			return fmt.Errorf("problem marshalling UTXO: %w", err)
+		}
+		reply.UTXOs[i] = encoding.ConvertBytes(b)
+	}
+
+	endAddress, err := service.vm.FormatLocalAddress(endAddr)
+	if err != nil {
+		return fmt.Errorf("problem formatting address: %w", err)
+	}
+
+	reply.EndIndex.Address = endAddress
+	reply.EndIndex.UTXO = endUTXOID.String()
+	reply.NumFetched = json.Uint64(len(utxos))
+	reply.Encoding = encoding.Encoding()
+	return nil
+}
+
+// IssueTx ...
+func (service *AvaxAPI) IssueTx(r *http.Request, args *api.FormattedTx, response *api.JSONTxID) error {
+	log.Info("EVM: IssueTx called")
+
+	encoding, err := service.vm.encodingManager.GetEncoding(args.Encoding)
+	if err != nil {
+		return fmt.Errorf("problem getting encoding formatter for '%s': %w", args.Encoding, err)
+	}
+	txBytes, err := encoding.ConvertString(args.Tx)
+	if err != nil {
+		return fmt.Errorf("problem decoding transaction: %w", err)
+	}
+
+	tx := &Tx{}
+	if err := service.vm.codec.Unmarshal(txBytes, tx); err != nil {
+		return fmt.Errorf("problem parsing transaction: %w", err)
+	}
+	if err := tx.Sign(service.vm.codec, nil); err != nil {
+		return fmt.Errorf("problem initializing transaction: %w", err)
+	}
+
+	utx, ok := tx.UnsignedTx.(UnsignedAtomicTx)
+	if !ok {
+		return errors.New("cannot issue non-atomic transaction through IssueTx API")
+	}
+
+	if err := utx.SemanticVerify(service.vm, tx); err != nil {
+		return err
+	}
+
+	response.TxID = tx.ID()
+	return service.vm.issueTx(tx)
+}
diff --git a/plugin/evm/tx.go b/plugin/evm/tx.go
index 7c2ebf1..2d722a5 100644
--- a/plugin/evm/tx.go
+++ b/plugin/evm/tx.go
@@ -117,7 +117,7 @@ func (tx *Tx) Sign(c codec.Codec, signers [][]*crypto.PrivateKeySECP256K1R) erro
 
 	signedBytes, err := c.Marshal(tx)
 	if err != nil {
-		return fmt.Errorf("couldn't marshal ProposalTx: %w", err)
+		return fmt.Errorf("couldn't marshal Tx: %w", err)
 	}
 	tx.Initialize(unsignedBytes, signedBytes)
 	return nil
-- 
cgit v1.2.3-70-g09d2