From 9f503c997bdb67a40ac2817c6cf0eb780a86f3c1 Mon Sep 17 00:00:00 2001 From: Determinant Date: Thu, 20 Aug 2020 00:14:34 -0400 Subject: add C-to-X RPC --- plugin/evm/service.go | 60 ++++++++++++++++++++++++++++++++++++++++++++++++--- plugin/evm/vm.go | 43 +++++++++++++++++++++++++++++------- 2 files changed, 92 insertions(+), 11 deletions(-) diff --git a/plugin/evm/service.go b/plugin/evm/service.go index 3165ca7..29ef35d 100644 --- a/plugin/evm/service.go +++ b/plugin/evm/service.go @@ -6,6 +6,7 @@ package evm import ( "context" "crypto/rand" + "errors" "fmt" "math/big" "net/http" @@ -18,6 +19,7 @@ import ( "github.com/ava-labs/gecko/utils/constants" "github.com/ava-labs/gecko/utils/crypto" "github.com/ava-labs/gecko/utils/formatting" + "github.com/ava-labs/gecko/utils/json" "github.com/ava-labs/go-ethereum/common" "github.com/ava-labs/go-ethereum/common/hexutil" ethcrypto "github.com/ava-labs/go-ethereum/crypto" @@ -149,7 +151,7 @@ func (service *AvaAPI) ExportKey(r *http.Request, args *ExportKeyArgs, reply *Ex return fmt.Errorf("problem retrieving user '%s': %w", args.Username, err) } user := user{db: db} - if address, err := service.vm.ParseLocalAddress(args.Address); err != nil { + if address, err := service.vm.ParseEthAddress(args.Address); err != nil { return fmt.Errorf("couldn't parse %s to address: %s", args.Address, err) } else if sk, err := user.getKey(address); err != nil { return fmt.Errorf("problem retrieving private key: %w", err) @@ -201,7 +203,7 @@ func (service *AvaAPI) ImportKey(r *http.Request, args *ImportKeyArgs, reply *ap } // TODO: return eth address here - reply.Address, err = service.vm.FormatAddress(GetEthAddress(sk)) + reply.Address, err = service.vm.FormatEthAddress(GetEthAddress(sk)) if err != nil { return fmt.Errorf("problem formatting address: %w", err) } @@ -236,7 +238,7 @@ func (service *AvaAPI) ImportAVAX(_ *http.Request, args *ImportAVAXArgs, respons } user := user{db: db} - to, err := service.vm.ParseLocalAddress(args.To) + to, err := service.vm.ParseEthAddress(args.To) if err != nil { // Parse address return fmt.Errorf("couldn't parse argument 'to' to an address: %w", err) } @@ -254,3 +256,55 @@ func (service *AvaAPI) ImportAVAX(_ *http.Request, args *ImportAVAXArgs, respons response.TxID = tx.ID() return service.vm.issueTx(tx) } + +// ExportAVAXArgs are the arguments to ExportAVAX +type ExportAVAXArgs struct { + api.UserPass + + // Amount of AVAX to send + Amount json.Uint64 `json:"amount"` + + // ID of the address that will receive the AVAX. This address includes the + // chainID, which is used to determine what the destination chain is. + To string `json:"to"` +} + +// ExportAVAX exports AVAX from the P-Chain to the X-Chain +// It must be imported on the X-Chain to complete the transfer +func (service *AvaAPI) ExportAVAX(_ *http.Request, args *ExportAVAXArgs, response *api.JsonTxID) error { + service.vm.ctx.Log.Info("Platform: ExportAVAX called") + + if args.Amount == 0 { + return errors.New("argument 'amount' must be > 0") + } + + chainID, to, err := service.vm.ParseAddress(args.To) + if err != nil { + return err + } + + // Get this user's data + db, err := service.vm.ctx.Keystore.GetDatabase(args.Username, args.Password) + if err != nil { + return fmt.Errorf("problem retrieving user '%s': %w", args.Username, err) + } + user := user{db: db} + privKeys, err := user.getKeys() + if err != nil { + return fmt.Errorf("couldn't get addresses controlled by the user: %w", err) + } + + // Create the transaction + tx, err := service.vm.newExportTx( + uint64(args.Amount), // Amount + chainID, // ID of the chain to send the funds to + to, // Address + privKeys, // Private keys + ) + if err != nil { + return fmt.Errorf("couldn't create tx: %w", err) + } + + response.TxID = tx.ID() + return service.vm.issueTx(tx) +} diff --git a/plugin/evm/vm.go b/plugin/evm/vm.go index 6155728..f62bc7b 100644 --- a/plugin/evm/vm.go +++ b/plugin/evm/vm.go @@ -25,7 +25,7 @@ import ( ethcrypto "github.com/ava-labs/go-ethereum/crypto" "github.com/ava-labs/go-ethereum/rlp" "github.com/ava-labs/go-ethereum/rpc" - avarpc "github.com/gorilla/rpc/v2" + geckorpc "github.com/gorilla/rpc/v2" "github.com/ava-labs/gecko/api/admin" "github.com/ava-labs/gecko/cache" @@ -35,8 +35,10 @@ import ( "github.com/ava-labs/gecko/snow/choices" "github.com/ava-labs/gecko/snow/consensus/snowman" "github.com/ava-labs/gecko/utils/codec" + "github.com/ava-labs/gecko/utils/constants" "github.com/ava-labs/gecko/utils/crypto" - avajson "github.com/ava-labs/gecko/utils/json" + "github.com/ava-labs/gecko/utils/formatting" + geckojson "github.com/ava-labs/gecko/utils/json" "github.com/ava-labs/gecko/utils/timer" "github.com/ava-labs/gecko/utils/wrappers" "github.com/ava-labs/gecko/vms/components/avax" @@ -454,9 +456,9 @@ func (vm *VM) LastAccepted() ids.ID { // By default the LockOption is WriteLock // [lockOption] should have either 0 or 1 elements. Elements beside the first are ignored. func newHandler(name string, service interface{}, lockOption ...commonEng.LockOption) *commonEng.HTTPHandler { - server := avarpc.NewServer() - server.RegisterCodec(avajson.NewCodec(), "application/json") - server.RegisterCodec(avajson.NewCodec(), "application/json;charset=UTF-8") + server := geckorpc.NewServer() + server.RegisterCodec(geckojson.NewCodec(), "application/json") + server.RegisterCodec(geckojson.NewCodec(), "application/json;charset=UTF-8") server.RegisterService(service, name) var lock commonEng.LockOption = commonEng.WriteLock @@ -643,18 +645,43 @@ func (vm *VM) getLastAccepted() *Block { return vm.lastAccepted } -// ParseLocalAddress takes in an address for this chain and produces the ID -func (vm *VM) ParseLocalAddress(addrStr string) (common.Address, error) { +func (vm *VM) ParseEthAddress(addrStr string) (common.Address, error) { if !common.IsHexAddress(addrStr) { return common.Address{}, errInvalidAddr } return common.HexToAddress(addrStr), nil } -func (vm *VM) FormatAddress(addr common.Address) (string, error) { +func (vm *VM) FormatEthAddress(addr common.Address) (string, error) { return addr.Hex(), nil } +// ParseAddress takes in an address and produces the ID of the chain it's for +// the ID of the address +func (vm *VM) ParseAddress(addrStr string) (ids.ID, ids.ShortID, error) { + chainIDAlias, hrp, addrBytes, err := formatting.ParseAddress(addrStr) + if err != nil { + return ids.ID{}, ids.ShortID{}, err + } + + chainID, err := vm.ctx.BCLookup.Lookup(chainIDAlias) + if err != nil { + return ids.ID{}, ids.ShortID{}, err + } + + expectedHRP := constants.GetHRP(vm.ctx.NetworkID) + if hrp != expectedHRP { + return ids.ID{}, ids.ShortID{}, fmt.Errorf("expected hrp %q but got %q", + expectedHRP, hrp) + } + + addr, err := ids.ToShortID(addrBytes) + if err != nil { + return ids.ID{}, ids.ShortID{}, err + } + return chainID, addr, nil +} + func (vm *VM) issueTx(tx *Tx) error { select { case vm.pendingAtomicTxs <- tx: -- cgit v1.2.3