Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
144 changes: 75 additions & 69 deletions plugin/evm/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,38 +7,38 @@ import (
"context"
"fmt"

"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/log"

"github.com/ava-labs/avalanchego/api"
"github.com/ava-labs/avalanchego/ids"
"github.com/ava-labs/avalanchego/utils/crypto/secp256k1"
"github.com/ava-labs/avalanchego/utils/formatting"
"github.com/ava-labs/avalanchego/utils/formatting/address"
"github.com/ava-labs/avalanchego/utils/json"
"github.com/ava-labs/avalanchego/utils/rpc"

cjson "github.com/ava-labs/avalanchego/utils/json"
)

// Interface compliance
var _ Client = (*client)(nil)

// Client interface for interacting with EVM [chain]
type Client interface {
IssueTx(ctx context.Context, txBytes []byte) (ids.ID, error)
GetAtomicTxStatus(ctx context.Context, txID ids.ID) (Status, error)
GetAtomicTx(ctx context.Context, txID ids.ID) ([]byte, error)
GetAtomicUTXOs(ctx context.Context, addrs []string, sourceChain string, limit uint32, startAddress, startUTXOID string) ([][]byte, api.Index, error)
ListAddresses(ctx context.Context, userPass api.UserPass) ([]string, error)
ExportKey(ctx context.Context, userPass api.UserPass, addr string) (*secp256k1.PrivateKey, string, error)
ImportKey(ctx context.Context, userPass api.UserPass, privateKey *secp256k1.PrivateKey) (string, error)
Import(ctx context.Context, userPass api.UserPass, to string, sourceChain string) (ids.ID, error)
ExportAVAX(ctx context.Context, userPass api.UserPass, amount uint64, to string) (ids.ID, error)
Export(ctx context.Context, userPass api.UserPass, amount uint64, to string, assetID string) (ids.ID, error)
StartCPUProfiler(ctx context.Context) error
StopCPUProfiler(ctx context.Context) error
MemoryProfile(ctx context.Context) error
LockProfile(ctx context.Context) error
SetLogLevel(ctx context.Context, level log.Lvl) error
GetVMConfig(ctx context.Context) (*Config, error)
IssueTx(ctx context.Context, txBytes []byte, options ...rpc.Option) (ids.ID, error)
GetAtomicTxStatus(ctx context.Context, txID ids.ID, options ...rpc.Option) (Status, error)
GetAtomicTx(ctx context.Context, txID ids.ID, options ...rpc.Option) ([]byte, error)
GetAtomicUTXOs(ctx context.Context, addrs []ids.ShortID, sourceChain string, limit uint32, startAddress ids.ShortID, startUTXOID ids.ID, options ...rpc.Option) ([][]byte, ids.ShortID, ids.ID, error)
ExportKey(ctx context.Context, userPass api.UserPass, addr common.Address, options ...rpc.Option) (*secp256k1.PrivateKey, string, error)
ImportKey(ctx context.Context, userPass api.UserPass, privateKey *secp256k1.PrivateKey, options ...rpc.Option) (common.Address, error)
Import(ctx context.Context, userPass api.UserPass, to common.Address, sourceChain string, options ...rpc.Option) (ids.ID, error)
ExportAVAX(ctx context.Context, userPass api.UserPass, amount uint64, to ids.ShortID, targetChain string, options ...rpc.Option) (ids.ID, error)
Export(ctx context.Context, userPass api.UserPass, amount uint64, to ids.ShortID, targetChain string, assetID string, options ...rpc.Option) (ids.ID, error)
StartCPUProfiler(ctx context.Context, options ...rpc.Option) error
StopCPUProfiler(ctx context.Context, options ...rpc.Option) error
MemoryProfile(ctx context.Context, options ...rpc.Option) error
LockProfile(ctx context.Context, options ...rpc.Option) error
SetLogLevel(ctx context.Context, level log.Lvl, options ...rpc.Option) error
GetVMConfig(ctx context.Context, options ...rpc.Option) (*Config, error)
}

// Client implementation for interacting with EVM [chain]
Expand All @@ -61,7 +61,7 @@ func NewCChainClient(uri string) Client {
}

// IssueTx issues a transaction to a node and returns the TxID
func (c *client) IssueTx(ctx context.Context, txBytes []byte) (ids.ID, error) {
func (c *client) IssueTx(ctx context.Context, txBytes []byte, options ...rpc.Option) (ids.ID, error) {
res := &api.JSONTxID{}
txStr, err := formatting.Encode(formatting.Hex, txBytes)
if err != nil {
Expand All @@ -70,26 +70,26 @@ func (c *client) IssueTx(ctx context.Context, txBytes []byte) (ids.ID, error) {
err = c.requester.SendRequest(ctx, "avax.issueTx", &api.FormattedTx{
Tx: txStr,
Encoding: formatting.Hex,
}, res)
}, res, options...)
return res.TxID, err
}

// GetAtomicTxStatus returns the status of [txID]
func (c *client) GetAtomicTxStatus(ctx context.Context, txID ids.ID) (Status, error) {
func (c *client) GetAtomicTxStatus(ctx context.Context, txID ids.ID, options ...rpc.Option) (Status, error) {
res := &GetAtomicTxStatusReply{}
err := c.requester.SendRequest(ctx, "avax.getAtomicTxStatus", &api.JSONTxID{
TxID: txID,
}, res)
}, res, options...)
return res.Status, err
}

// GetAtomicTx returns the byte representation of [txID]
func (c *client) GetAtomicTx(ctx context.Context, txID ids.ID) ([]byte, error) {
func (c *client) GetAtomicTx(ctx context.Context, txID ids.ID, options ...rpc.Option) ([]byte, error) {
res := &api.FormattedTx{}
err := c.requester.SendRequest(ctx, "avax.getAtomicTx", &api.GetTxArgs{
TxID: txID,
Encoding: formatting.Hex,
}, res)
}, res, options...)
if err != nil {
return nil, err
}
Expand All @@ -99,70 +99,71 @@ func (c *client) GetAtomicTx(ctx context.Context, txID ids.ID) ([]byte, error) {

// GetAtomicUTXOs returns the byte representation of the atomic UTXOs controlled by [addresses]
// from [sourceChain]
func (c *client) GetAtomicUTXOs(ctx context.Context, addrs []string, sourceChain string, limit uint32, startAddress, startUTXOID string) ([][]byte, api.Index, error) {
func (c *client) GetAtomicUTXOs(ctx context.Context, addrs []ids.ShortID, sourceChain string, limit uint32, startAddress ids.ShortID, startUTXOID ids.ID, options ...rpc.Option) ([][]byte, ids.ShortID, ids.ID, error) {
res := &api.GetUTXOsReply{}
err := c.requester.SendRequest(ctx, "avax.getUTXOs", &api.GetUTXOsArgs{
Addresses: addrs,
Addresses: ids.ShortIDsToStrings(addrs),
SourceChain: sourceChain,
Limit: cjson.Uint32(limit),
Limit: json.Uint32(limit),
StartIndex: api.Index{
Address: startAddress,
UTXO: startUTXOID,
Address: startAddress.String(),
UTXO: startUTXOID.String(),
},
Encoding: formatting.Hex,
}, res)
}, res, options...)
if err != nil {
return nil, api.Index{}, err
return nil, ids.ShortID{}, ids.Empty, err
}

utxos := make([][]byte, len(res.UTXOs))
for i, utxo := range res.UTXOs {
b, err := formatting.Decode(formatting.Hex, utxo)
utxoBytes, err := formatting.Decode(res.Encoding, utxo)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(No action required) It may be worth separating non-functional (e.g. naming) from functional changes when refactoring to simplify review.

if err != nil {
return nil, api.Index{}, err
return nil, ids.ShortID{}, ids.Empty, err
}
utxos[i] = b
utxos[i] = utxoBytes
}
return utxos, res.EndIndex, nil
}

// ListAddresses returns all addresses on this chain controlled by [user]
func (c *client) ListAddresses(ctx context.Context, user api.UserPass) ([]string, error) {
res := &api.JSONAddresses{}
err := c.requester.SendRequest(ctx, "avax.listAddresses", &user, res)
return res.Addresses, err
endAddr, err := address.ParseToID(res.EndIndex.Address)
if err != nil {
return nil, ids.ShortID{}, ids.Empty, err
}
endUTXOID, err := ids.FromString(res.EndIndex.UTXO)
return utxos, endAddr, endUTXOID, err
}

// ExportKey returns the private key corresponding to [addr] controlled by [user]
// in both Avalanche standard format and hex format
func (c *client) ExportKey(ctx context.Context, user api.UserPass, addr string) (*secp256k1.PrivateKey, string, error) {
func (c *client) ExportKey(ctx context.Context, user api.UserPass, addr common.Address, options ...rpc.Option) (*secp256k1.PrivateKey, string, error) {
res := &ExportKeyReply{}
err := c.requester.SendRequest(ctx, "avax.exportKey", &ExportKeyArgs{
UserPass: user,
Address: addr,
}, res)
Address: addr.Hex(),
}, res, options...)
return res.PrivateKey, res.PrivateKeyHex, err
}

// ImportKey imports [privateKey] to [user]
func (c *client) ImportKey(ctx context.Context, user api.UserPass, privateKey *secp256k1.PrivateKey) (string, error) {
func (c *client) ImportKey(ctx context.Context, user api.UserPass, privateKey *secp256k1.PrivateKey, options ...rpc.Option) (common.Address, error) {
res := &api.JSONAddress{}
err := c.requester.SendRequest(ctx, "avax.importKey", &ImportKeyArgs{
UserPass: user,
PrivateKey: privateKey,
}, res)
return res.Address, err
}, res, options...)
if err != nil {
return common.Address{}, err
}
return ParseEthAddress(res.Address)
}

// Import sends an import transaction to import funds from [sourceChain] and
// returns the ID of the newly created transaction
func (c *client) Import(ctx context.Context, user api.UserPass, to, sourceChain string) (ids.ID, error) {
func (c *client) Import(ctx context.Context, user api.UserPass, to common.Address, sourceChain string, options ...rpc.Option) (ids.ID, error) {
res := &api.JSONTxID{}
err := c.requester.SendRequest(ctx, "avax.import", &ImportArgs{
UserPass: user,
To: to,
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(No action required) Why is it necessary to convert the address to hex for avax.exportKey but not here for avax.import?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could update the ExportKeyArgs.Address var to be common.Address rather than string and I think we'd be able to avoid calling .Hex().

SourceChain: sourceChain,
}, res)
}, res, options...)
return res.TxID, err
}

Expand All @@ -172,9 +173,11 @@ func (c *client) ExportAVAX(
ctx context.Context,
user api.UserPass,
amount uint64,
to string,
to ids.ShortID,
targetChain string,
options ...rpc.Option,
) (ids.ID, error) {
return c.Export(ctx, user, amount, to, "AVAX")
return c.Export(ctx, user, amount, to, targetChain, "AVAX", options...)
}

// Export sends an asset from this chain to the P/C-Chain.
Expand All @@ -184,47 +187,50 @@ func (c *client) Export(
ctx context.Context,
user api.UserPass,
amount uint64,
to string,
to ids.ShortID,
targetChain string,
assetID string,
options ...rpc.Option,
) (ids.ID, error) {
res := &api.JSONTxID{}
err := c.requester.SendRequest(ctx, "avax.export", &ExportArgs{
ExportAVAXArgs: ExportAVAXArgs{
UserPass: user,
Amount: cjson.Uint64(amount),
To: to,
UserPass: user,
Amount: json.Uint64(amount),
TargetChain: targetChain,
To: to.String(),
},
AssetID: assetID,
}, res)
}, res, options...)
return res.TxID, err
}

func (c *client) StartCPUProfiler(ctx context.Context) error {
return c.adminRequester.SendRequest(ctx, "admin.startCPUProfiler", struct{}{}, &api.EmptyReply{})
func (c *client) StartCPUProfiler(ctx context.Context, options ...rpc.Option) error {
return c.adminRequester.SendRequest(ctx, "admin.startCPUProfiler", struct{}{}, &api.EmptyReply{}, options...)
}

func (c *client) StopCPUProfiler(ctx context.Context) error {
return c.adminRequester.SendRequest(ctx, "admin.stopCPUProfiler", struct{}{}, &api.EmptyReply{})
func (c *client) StopCPUProfiler(ctx context.Context, options ...rpc.Option) error {
return c.adminRequester.SendRequest(ctx, "admin.stopCPUProfiler", struct{}{}, &api.EmptyReply{}, options...)
}

func (c *client) MemoryProfile(ctx context.Context) error {
return c.adminRequester.SendRequest(ctx, "admin.memoryProfile", struct{}{}, &api.EmptyReply{})
func (c *client) MemoryProfile(ctx context.Context, options ...rpc.Option) error {
return c.adminRequester.SendRequest(ctx, "admin.memoryProfile", struct{}{}, &api.EmptyReply{}, options...)
}

func (c *client) LockProfile(ctx context.Context) error {
return c.adminRequester.SendRequest(ctx, "admin.lockProfile", struct{}{}, &api.EmptyReply{})
func (c *client) LockProfile(ctx context.Context, options ...rpc.Option) error {
return c.adminRequester.SendRequest(ctx, "admin.lockProfile", struct{}{}, &api.EmptyReply{}, options...)
}

// SetLogLevel dynamically sets the log level for the C Chain
func (c *client) SetLogLevel(ctx context.Context, level log.Lvl) error {
func (c *client) SetLogLevel(ctx context.Context, level log.Lvl, options ...rpc.Option) error {
return c.adminRequester.SendRequest(ctx, "admin.setLogLevel", &SetLogLevelArgs{
Level: level.String(),
}, &api.EmptyReply{})
}, &api.EmptyReply{}, options...)
}

// GetVMConfig returns the current config of the VM
func (c *client) GetVMConfig(ctx context.Context) (*Config, error) {
func (c *client) GetVMConfig(ctx context.Context, options ...rpc.Option) (*Config, error) {
res := &ConfigReply{}
err := c.adminRequester.SendRequest(ctx, "admin.getVMConfig", struct{}{}, res)
err := c.adminRequester.SendRequest(ctx, "admin.getVMConfig", struct{}{}, res, options...)
return res.Config, err
}
11 changes: 11 additions & 0 deletions plugin/evm/formatting.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,17 @@ import (
"github.com/ethereum/go-ethereum/crypto"
)

// ParseServiceAddress get address ID from address string, being it either localized (using address manager,
// doing also components validations), or not localized.
// If both attempts fail, reports error from localized address parsing
func (vm *VM) ParseServiceAddress(addrStr string) (ids.ShortID, error) {
addr, err := ids.ShortFromString(addrStr)
if err == nil {
return addr, nil
}
return vm.ParseLocalAddress(addrStr)
}

// 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)
Expand Down
31 changes: 19 additions & 12 deletions plugin/evm/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -174,7 +174,7 @@ type ImportArgs struct {
SourceChain string `json:"sourceChain"`

// The address that will receive the imported funds
To string `json:"to"`
To common.Address `json:"to"`
}

// ImportAVAX is a deprecated name for Import.
Expand All @@ -192,11 +192,6 @@ func (service *AvaxAPI) Import(_ *http.Request, args *ImportArgs, response *api.
return fmt.Errorf("problem parsing chainID %q: %w", args.SourceChain, err)
}

to, err := ParseEthAddress(args.To)
if err != nil { // Parse address
return fmt.Errorf("couldn't parse argument 'to' to an address: %w", err)
}

// Get the user's info
db, err := service.vm.ctx.Keystore.GetDatabase(args.Username, args.Password)
if err != nil {
Expand Down Expand Up @@ -224,7 +219,7 @@ func (service *AvaxAPI) Import(_ *http.Request, args *ImportArgs, response *api.
baseFee = args.BaseFee.ToInt()
}

tx, err := service.vm.newImportTx(chainID, to, baseFee, privKeys)
tx, err := service.vm.newImportTx(chainID, args.To, baseFee, privKeys)
if err != nil {
return err
}
Expand All @@ -243,8 +238,12 @@ type ExportAVAXArgs struct {
// Amount of asset 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.
// Chain the funds are going to. Optional. Used if To address does not
// include the chainID.
TargetChain string `json:"targetChain"`

// ID of the address that will receive the AVAX. This address may include
// the chainID, which is used to determine what the destination chain is.
To string `json:"to"`
}

Expand Down Expand Up @@ -278,9 +277,17 @@ func (service *AvaxAPI) Export(_ *http.Request, args *ExportArgs, response *api.
return errors.New("argument 'amount' must be > 0")
}

// Get the chainID and parse the to address
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(No action required) Would it make sense to conditionally use the alternate parsing strategy based on the returned error? ParseAddress returns 4 different errors and I'm curious if all of them are appropriate prompts to follow the other strategy.

Or maybe the use of a full address could be deprecated and eventually removed? I'm not sure why its desirable to support the provision of the chain id in both ways vs picking one or the other.

chainID, to, err := service.vm.ParseAddress(args.To)
if err != nil {
return err
chainID, err = service.vm.ctx.BCLookup.Lookup(args.TargetChain)
if err != nil {
return err
}
to, err = ids.ShortFromString(args.To)
if err != nil {
return err
}
}

// Get this user's data
Expand Down Expand Up @@ -350,7 +357,7 @@ func (service *AvaxAPI) GetUTXOs(r *http.Request, args *api.GetUTXOsArgs, reply

addrSet := set.Set[ids.ShortID]{}
for _, addrStr := range args.Addresses {
addr, err := service.vm.ParseLocalAddress(addrStr)
addr, err := service.vm.ParseServiceAddress(addrStr)
if err != nil {
return fmt.Errorf("couldn't parse address %q: %w", addrStr, err)
}
Expand All @@ -360,7 +367,7 @@ func (service *AvaxAPI) GetUTXOs(r *http.Request, args *api.GetUTXOsArgs, reply
startAddr := ids.ShortEmpty
startUTXO := ids.Empty
if args.StartIndex.Address != "" || args.StartIndex.UTXO != "" {
startAddr, err = service.vm.ParseLocalAddress(args.StartIndex.Address)
startAddr, err = service.vm.ParseServiceAddress(args.StartIndex.Address)
if err != nil {
return fmt.Errorf("couldn't parse start index address %q: %w", args.StartIndex.Address, err)
}
Expand Down