diff --git a/cmd/nodecmd/wiz.go b/cmd/nodecmd/wiz.go index 95498cff6..98b84b258 100644 --- a/cmd/nodecmd/wiz.go +++ b/cmd/nodecmd/wiz.go @@ -44,26 +44,33 @@ const ( ) var ( - forceSubnetCreate bool - subnetGenesisFile string - useEvmSubnet bool - useCustomSubnet bool - evmVersion string - evmChainID uint64 - evmToken string - evmDefaults bool - useLatestEvmReleasedVersion bool - useLatestEvmPreReleasedVersion bool - customVMRepoURL string - customVMBranch string - customVMBuildScript string - nodeConf string - subnetConf string - chainConf string - validators []string - customGrafanaDashboardPath string - teleporterReady bool - runRelayer bool + forceSubnetCreate bool + subnetGenesisFile string + useEvmSubnet bool + useCustomSubnet bool + evmVersion string + evmChainID uint64 + evmToken string + evmDefaults bool + useLatestEvmReleasedVersion bool + useLatestEvmPreReleasedVersion bool + customVMRepoURL string + customVMBranch string + customVMBuildScript string + nodeConf string + subnetConf string + chainConf string + validators []string + customGrafanaDashboardPath string + teleporterReady bool + runRelayer bool + teleporterVersion string + teleporterMessengerContractAddressPath string + teleporterMessengerDeployerAddressPath string + teleporterMessengerDeployerTxPath string + teleporterRegistryBydecodePath string + deployTeleporterMessenger bool + deployTeleporterRegistry bool ) func newWizCmd() *cobra.Command { @@ -124,6 +131,13 @@ The node wiz command creates a devnet and deploys, sync and validate a subnet in cmd.Flags().StringVar(&volumeType, "aws-volume-type", "gp3", "AWS volume type") cmd.Flags().IntVar(&volumeSize, "aws-volume-size", constants.CloudServerStorageSize, "AWS volume size in GB") cmd.Flags().StringVar(&grafanaPkg, "grafana-pkg", "", "use grafana pkg instead of apt repo(by default), for example https://dl.grafana.com/oss/release/grafana_10.4.1_amd64.deb") + cmd.Flags().StringVar(&teleporterVersion, "teleporter-version", "latest", "teleporter version to deploy") + cmd.Flags().StringVar(&teleporterMessengerContractAddressPath, "teleporter-messenger-contract-address-path", "", "path to a teleporter messenger contract address file") + cmd.Flags().StringVar(&teleporterMessengerDeployerAddressPath, "teleporter-messenger-deployer-address-path", "", "path to a teleporter messenger deployer address file") + cmd.Flags().StringVar(&teleporterMessengerDeployerTxPath, "teleporter-messenger-deployer-tx-path", "", "path to a teleporter messenger deployer tx file") + cmd.Flags().StringVar(&teleporterRegistryBydecodePath, "teleporter-registry-bytecode-path", "", "path to a teleporter registry bytecode file") + cmd.Flags().BoolVar(&deployTeleporterMessenger, "deploy-teleporter-messenger", true, "deploy Teleporter Messenger") + cmd.Flags().BoolVar(&deployTeleporterRegistry, "deploy-teleporter-registry", true, "deploy Teleporter Registry") return cmd } @@ -350,8 +364,13 @@ func wiz(cmd *cobra.Command, args []string) error { Network: networkoptions.NetworkFlags{ ClusterName: clusterName, }, - DeployMessenger: true, - DeployRegistry: true, + DeployMessenger: deployTeleporterMessenger, + DeployRegistry: deployTeleporterRegistry, + Version: teleporterVersion, + MessengerContractAddressPath: teleporterMessengerContractAddressPath, + MessengerDeployerAddressPath: teleporterMessengerDeployerAddressPath, + MessengerDeployerTxPath: teleporterMessengerDeployerTxPath, + RegistryBydecodePath: teleporterRegistryBydecodePath, } if err := teleportercmd.CallDeploy([]string{}, flags); err != nil { return err diff --git a/cmd/subnetcmd/deploy.go b/cmd/subnetcmd/deploy.go index cd3f26aec..fa774eab1 100644 --- a/cmd/subnetcmd/deploy.go +++ b/cmd/subnetcmd/deploy.go @@ -53,8 +53,8 @@ var ( mainnetChainID uint32 skipCreatePrompt bool avagoBinaryPath string - skipLocalTeleporter bool subnetOnly bool + teleporterEsp subnet.TeleporterEsp errMutuallyExlusiveControlKeys = errors.New("--control-keys and --same-control-key are mutually exclusive") ErrMutuallyExlusiveKeyLedger = errors.New("key source flags --key, --ledger/--ledger-addrs are mutually exclusive") @@ -95,8 +95,14 @@ so you can take your locally tested Subnet and deploy it on Fuji or Mainnet.`, cmd.Flags().StringVarP(&subnetIDStr, "subnet-id", "u", "", "do not create a subnet, deploy the blockchain into the given subnet id") cmd.Flags().Uint32Var(&mainnetChainID, "mainnet-chain-id", 0, "use different ChainID for mainnet deployment") cmd.Flags().StringVar(&avagoBinaryPath, "avalanchego-path", "", "use this avalanchego binary path") - cmd.Flags().BoolVar(&skipLocalTeleporter, "skip-local-teleporter", false, "skip local teleporter deploy to a local network") cmd.Flags().BoolVar(&subnetOnly, "subnet-only", false, "only create a subnet") + cmd.Flags().BoolVar(&teleporterEsp.SkipDeploy, "skip-local-teleporter", false, "skip automatic teleporter deploy on local networks [to be deprecated]") + cmd.Flags().BoolVar(&teleporterEsp.SkipDeploy, "skip-teleporter-deploy", false, "skip automatic teleporter deploy") + cmd.Flags().StringVar(&teleporterEsp.Version, "teleporter-version", "latest", "teleporter version to deploy") + cmd.Flags().StringVar(&teleporterEsp.MessengerContractAddressPath, "teleporter-messenger-contract-address-path", "", "path to a teleporter messenger contract address file") + cmd.Flags().StringVar(&teleporterEsp.MessengerDeployerAddressPath, "teleporter-messenger-deployer-address-path", "", "path to a teleporter messenger deployer address file") + cmd.Flags().StringVar(&teleporterEsp.MessengerDeployerTxPath, "teleporter-messenger-deployer-tx-path", "", "path to a teleporter messenger deployer tx file") + cmd.Flags().StringVar(&teleporterEsp.RegistryBydecodePath, "teleporter-registry-bytecode-path", "", "path to a teleporter registry bytecode file") return cmd } @@ -265,6 +271,12 @@ func deploySubnet(cmd *cobra.Command, args []string) error { return err } + if teleporterEsp.MessengerContractAddressPath != "" || teleporterEsp.MessengerDeployerAddressPath != "" || teleporterEsp.MessengerDeployerTxPath != "" || teleporterEsp.RegistryBydecodePath != "" { + if teleporterEsp.MessengerContractAddressPath == "" || teleporterEsp.MessengerDeployerAddressPath == "" || teleporterEsp.MessengerDeployerTxPath == "" || teleporterEsp.RegistryBydecodePath == "" { + return fmt.Errorf("if setting any teleporter asset path, you must set all teleporter asset paths") + } + } + chain := chains[0] sidecar, err := app.LoadSidecar(chain) @@ -358,7 +370,7 @@ func deploySubnet(cmd *cobra.Command, args []string) error { } deployer := subnet.NewLocalDeployer(app, userProvidedAvagoVersion, avagoBinaryPath, vmBin) - deployInfo, err := deployer.DeployToLocalNetwork(chain, chainGenesis, genesisPath, skipLocalTeleporter, subnetIDStr) + deployInfo, err := deployer.DeployToLocalNetwork(chain, chainGenesis, genesisPath, teleporterEsp, subnetIDStr) if err != nil { if deployer.BackendStartedHere() { if innerErr := binutils.KillgRPCServerProcess(app); innerErr != nil { diff --git a/cmd/teleportercmd/deploy.go b/cmd/teleportercmd/deploy.go index 7b98ae414..2b24efdb9 100644 --- a/cmd/teleportercmd/deploy.go +++ b/cmd/teleportercmd/deploy.go @@ -10,27 +10,30 @@ import ( "github.com/ava-labs/avalanche-cli/pkg/cobrautils" "github.com/ava-labs/avalanche-cli/pkg/models" "github.com/ava-labs/avalanche-cli/pkg/networkoptions" - "github.com/ava-labs/avalanche-cli/pkg/prompts" "github.com/ava-labs/avalanche-cli/pkg/subnet" "github.com/ava-labs/avalanche-cli/pkg/teleporter" - "github.com/ava-labs/avalanche-cli/pkg/utils" "github.com/ava-labs/avalanchego/ids" "github.com/spf13/cobra" ) type DeployFlags struct { - Network networkoptions.NetworkFlags - SubnetName string - BlockchainID string - CChain bool - PrivateKey string - KeyName string - GenesisKey bool - DeployMessenger bool - DeployRegistry bool - TeleporterVersion string - RPCURL string + Network networkoptions.NetworkFlags + SubnetName string + BlockchainID string + CChain bool + PrivateKey string + KeyName string + GenesisKey bool + DeployMessenger bool + DeployRegistry bool + RPCURL string + Version string + MessengerContractAddressPath string + MessengerDeployerAddressPath string + MessengerDeployerTxPath string + RegistryBydecodePath string + PrivateKeyFlags PrivateKeyFlags } const ( @@ -60,13 +63,17 @@ func newDeployCmd() *cobra.Command { cmd.Flags().StringVar(&deployFlags.SubnetName, "subnet", "", "deploy teleporter into the given CLI subnet") cmd.Flags().StringVar(&deployFlags.BlockchainID, "blockchain-id", "", "deploy teleporter into the given blockchain ID/Alias") cmd.Flags().BoolVar(&deployFlags.CChain, "c-chain", false, "deploy teleporter into C-Chain") - cmd.Flags().StringVar(&deployFlags.PrivateKey, "private-key", "", "private key to use to fund teleporter deploy)") - cmd.Flags().StringVar(&deployFlags.KeyName, "key", "", "CLI stored key to use to fund teleporter deploy)") - cmd.Flags().BoolVar(&deployFlags.GenesisKey, "genesis-key", false, "use genesis aidrop key to fund teleporter deploy") + cmd.Flags().StringVar(&deployFlags.PrivateKeyFlags.PrivateKey, "private-key", "", "private key to use to fund teleporter deploy)") + cmd.Flags().StringVar(&deployFlags.PrivateKeyFlags.KeyName, "key", "", "CLI stored key to use to fund teleporter deploy)") + cmd.Flags().BoolVar(&deployFlags.PrivateKeyFlags.GenesisKey, "genesis-key", false, "use genesis aidrop key to fund teleporter deploy") cmd.Flags().BoolVar(&deployFlags.DeployMessenger, "deploy-messenger", true, "deploy Teleporter Messenger") cmd.Flags().BoolVar(&deployFlags.DeployRegistry, "deploy-registry", true, "deploy Teleporter Registry") - cmd.Flags().StringVar(&deployFlags.TeleporterVersion, "version", "latest", "version to deploy") cmd.Flags().StringVar(&deployFlags.RPCURL, "rpc-url", "", "use the given RPC URL to connect to the subnet") + cmd.Flags().StringVar(&deployFlags.Version, "version", "latest", "version to deploy") + cmd.Flags().StringVar(&deployFlags.MessengerContractAddressPath, "messenger-contract-address-path", "", "path to a messenger contract address file") + cmd.Flags().StringVar(&deployFlags.MessengerDeployerAddressPath, "messenger-deployer-address-path", "", "path to a messenger deployer address file") + cmd.Flags().StringVar(&deployFlags.MessengerDeployerTxPath, "messenger-deployer-tx-path", "", "path to a messenger deployer tx file") + cmd.Flags().StringVar(&deployFlags.RegistryBydecodePath, "registry-bytecode-path", "", "path to a registry bytecode file") return cmd } @@ -133,7 +140,7 @@ func CallDeploy(_ []string, flags DeployFlags) error { var ( blockchainID string teleporterSubnetDesc string - privateKey = flags.PrivateKey + privateKey string teleporterVersion string ) switch { @@ -169,74 +176,40 @@ func CallDeploy(_ []string, flags DeployFlags) error { teleporterSubnetDesc = cChainName blockchainID = cChainAlias } - var chainID ids.ID - if flags.CChain || !network.StandardPublicEndpoint() { - chainID, err = utils.GetChainID(network.Endpoint, blockchainID) - if err != nil { - return err - } - } else { - chainID, err = ids.FromString(blockchainID) - if err != nil { - return err - } - } - createChainTx, err := utils.GetBlockchainTx(network.Endpoint, chainID) - if err != nil { - return err - } - if !utils.ByteSliceIsSubnetEvmGenesis(createChainTx.GenesisData) { - return fmt.Errorf("teleporter can only be deployed to Subnet-EVM based vms") - } - if flags.KeyName != "" { - k, err := app.GetKey(flags.KeyName, network, false) - if err != nil { - return err - } - privateKey = k.PrivKeyHex() - } - _, genesisAddress, genesisPrivateKey, err := subnet.GetSubnetAirdropKeyInfo(app, network, flags.SubnetName, createChainTx.GenesisData) + genesisAddress, genesisPrivateKey, err := getEVMSubnetPrefundedKey( + network, + flags.SubnetName, + flags.CChain, + flags.BlockchainID, + ) if err != nil { return err } - if flags.GenesisKey { - privateKey = genesisPrivateKey - } if privateKey == "" { - cliKeyOpt := "Get private key from an existing stored key (created from avalanche key create or avalanche key import)" - customKeyOpt := "Custom" - genesisKeyOpt := fmt.Sprintf("Use the private key of the Genesis Aidrop address %s", genesisAddress) - keyOptions := []string{cliKeyOpt, customKeyOpt} - if genesisPrivateKey != "" { - keyOptions = []string{genesisKeyOpt, cliKeyOpt, customKeyOpt} - } - keyOption, err := app.Prompt.CaptureList("Which private key do you want to use to pay fees?", keyOptions) + privateKey, err = getPrivateKeyFromFlags( + deployFlags.PrivateKeyFlags, + genesisPrivateKey, + ) if err != nil { return err } - switch keyOption { - case cliKeyOpt: - keyName, err := prompts.CaptureKeyName(app.Prompt, "pay fees", app.GetKeyDir(), true) + if privateKey == "" { + privateKey, err = promptPrivateKey("deploy teleporter", genesisAddress, genesisPrivateKey) if err != nil { return err } - k, err := app.GetKey(keyName, network, false) - if err != nil { - return err - } - privateKey = k.PrivKeyHex() - case customKeyOpt: - privateKey, err = app.Prompt.CaptureString("Private Key") - if err != nil { - return err - } - case genesisKeyOpt: - privateKey = genesisPrivateKey } } - if flags.TeleporterVersion != "" && flags.TeleporterVersion != "latest" { - teleporterVersion = flags.TeleporterVersion - } else if teleporterVersion == "" { + switch { + case flags.MessengerContractAddressPath != "" || flags.MessengerDeployerAddressPath != "" || flags.MessengerDeployerTxPath != "" || flags.RegistryBydecodePath != "": + teleporterVersion = "" + if flags.MessengerContractAddressPath == "" || flags.MessengerDeployerAddressPath == "" || flags.MessengerDeployerTxPath == "" || flags.RegistryBydecodePath == "" { + return fmt.Errorf("if setting any teleporter asset path, you must set all teleporter asset paths") + } + case flags.Version != "" && flags.Version != "latest": + teleporterVersion = flags.Version + case teleporterVersion != "": + default: teleporterInfo, err := teleporter.GetInfo(app) if err != nil { return err @@ -249,9 +222,24 @@ func CallDeploy(_ []string, flags DeployFlags) error { rpcURL = flags.RPCURL } td := teleporter.Deployer{} + if flags.MessengerContractAddressPath != "" { + if err := td.SetAssetsFromPaths( + flags.MessengerContractAddressPath, + flags.MessengerDeployerAddressPath, + flags.MessengerDeployerTxPath, + flags.RegistryBydecodePath, + ); err != nil { + return err + } + } else { + if err := td.DownloadAssets( + app.GetTeleporterBinDir(), + teleporterVersion, + ); err != nil { + return err + } + } alreadyDeployed, teleporterMessengerAddress, teleporterRegistryAddress, err := td.Deploy( - app.GetTeleporterBinDir(), - teleporterVersion, teleporterSubnetDesc, rpcURL, privateKey, @@ -288,8 +276,6 @@ func CallDeploy(_ []string, flags DeployFlags) error { return err } alreadyDeployed, teleporterMessengerAddress, teleporterRegistryAddress, err := td.Deploy( - app.GetTeleporterBinDir(), - teleporterVersion, cChainName, network.BlockchainEndpoint(cChainAlias), ewoq.PrivKeyHex(), diff --git a/cmd/teleportercmd/helpers.go b/cmd/teleportercmd/helpers.go index 5da74f259..ce8ce47db 100644 --- a/cmd/teleportercmd/helpers.go +++ b/cmd/teleportercmd/helpers.go @@ -1,11 +1,144 @@ -// Copyright (C) 2022, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package teleportercmd import ( + "fmt" "strings" + + "github.com/ava-labs/avalanche-cli/cmd/subnetcmd" + "github.com/ava-labs/avalanche-cli/pkg/models" + "github.com/ava-labs/avalanche-cli/pkg/prompts" + "github.com/ava-labs/avalanche-cli/pkg/subnet" + "github.com/ava-labs/avalanche-cli/pkg/utils" + "github.com/ava-labs/avalanchego/ids" ) +type PrivateKeyFlags struct { + PrivateKey string + KeyName string + GenesisKey bool +} + +func getPrivateKeyFromFlags( + flags PrivateKeyFlags, + genesisPrivateKey string, +) (string, error) { + privateKey := flags.PrivateKey + if flags.KeyName != "" { + k, err := app.GetKey(flags.KeyName, models.NewLocalNetwork(), false) + if err != nil { + return "", err + } + privateKey = k.PrivKeyHex() + } + if flags.GenesisKey { + privateKey = genesisPrivateKey + } + return privateKey, nil +} + +func getEVMSubnetPrefundedKey( + network models.Network, + subnetName string, + isCChain bool, + blockchainID string, +) (string, string, error) { + if blockchainID == "" { + if isCChain { + blockchainID = "C" + } else { + sc, err := app.LoadSidecar(subnetName) + if err != nil { + return "", "", fmt.Errorf("failed to load sidecar: %w", err) + } + if b, _, err := subnetcmd.HasSubnetEVMGenesis(subnetName); err != nil { + return "", "", err + } else if !b { + return "", "", fmt.Errorf("getPrefundedKey only works on EVM based vms") + } + if sc.Networks[network.Name()].BlockchainID == ids.Empty { + return "", "", fmt.Errorf("subnet has not been deployed to %s", network.Name()) + } + blockchainID = sc.Networks[network.Name()].BlockchainID.String() + } + } + var ( + err error + chainID ids.ID + ) + if isCChain || !network.StandardPublicEndpoint() { + chainID, err = utils.GetChainID(network.Endpoint, blockchainID) + if err != nil { + return "", "", err + } + } else { + chainID, err = ids.FromString(blockchainID) + if err != nil { + return "", "", err + } + } + createChainTx, err := utils.GetBlockchainTx(network.Endpoint, chainID) + if err != nil { + return "", "", err + } + if !utils.ByteSliceIsSubnetEvmGenesis(createChainTx.GenesisData) { + return "", "", fmt.Errorf("getPrefundedKey only works on EVM based vms") + } + _, genesisAddress, genesisPrivateKey, err := subnet.GetSubnetAirdropKeyInfo( + app, + network, + subnetName, + createChainTx.GenesisData, + ) + if err != nil { + return "", "", err + } + return genesisAddress, genesisPrivateKey, nil +} + +func promptPrivateKey( + goal string, + genesisAddress string, + genesisPrivateKey string, +) (string, error) { + privateKey := "" + cliKeyOpt := "Get private key from an existing stored key (created from avalanche key create or avalanche key import)" + customKeyOpt := "Custom" + genesisKeyOpt := fmt.Sprintf("Use the private key of the Genesis Aidrop address %s", genesisAddress) + keyOptions := []string{cliKeyOpt, customKeyOpt} + if genesisPrivateKey != "" { + keyOptions = []string{genesisKeyOpt, cliKeyOpt, customKeyOpt} + } + keyOption, err := app.Prompt.CaptureList( + fmt.Sprintf("Which private key do you want to use to %s?", goal), + keyOptions, + ) + if err != nil { + return "", err + } + switch keyOption { + case cliKeyOpt: + keyName, err := prompts.CaptureKeyName(app.Prompt, goal, app.GetKeyDir(), true) + if err != nil { + return "", err + } + k, err := app.GetKey(keyName, models.NewLocalNetwork(), false) + if err != nil { + return "", err + } + privateKey = k.PrivKeyHex() + case customKeyOpt: + privateKey, err = app.Prompt.CaptureString("Private Key") + if err != nil { + return "", err + } + case genesisKeyOpt: + privateKey = genesisPrivateKey + } + return privateKey, nil +} + func isCChain(subnetName string) bool { return strings.ToLower(subnetName) == "c-chain" || strings.ToLower(subnetName) == "cchain" } diff --git a/cmd/teleportercmd/msg.go b/cmd/teleportercmd/msg.go index 22b6e303d..308082f6f 100644 --- a/cmd/teleportercmd/msg.go +++ b/cmd/teleportercmd/msg.go @@ -1,36 +1,40 @@ -// Copyright (C) 2022, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package teleportercmd import ( - "context" - "encoding/hex" "fmt" - "math/big" "time" + cmdflags "github.com/ava-labs/avalanche-cli/cmd/flags" "github.com/ava-labs/avalanche-cli/cmd/teleportercmd/bridgecmd" "github.com/ava-labs/avalanche-cli/pkg/cobrautils" + "github.com/ava-labs/avalanche-cli/pkg/contract" "github.com/ava-labs/avalanche-cli/pkg/evm" "github.com/ava-labs/avalanche-cli/pkg/networkoptions" + "github.com/ava-labs/avalanche-cli/pkg/prompts" + "github.com/ava-labs/avalanche-cli/pkg/teleporter" "github.com/ava-labs/avalanche-cli/pkg/ux" "github.com/ava-labs/avalanchego/ids" - "github.com/ava-labs/subnet-evm/core/types" - teleportermessenger "github.com/ava-labs/teleporter/abi-bindings/go/Teleporter/TeleporterMessenger" "github.com/ethereum/go-ethereum/common" "github.com/spf13/cobra" ) +type MsgFlags struct { + Network networkoptions.NetworkFlags + DestinationAddress string + HexEncodedMessage bool + PrivateKeyFlags PrivateKeyFlags +} + var ( msgSupportedNetworkOptions = []networkoptions.NetworkOption{ networkoptions.Local, - networkoptions.Cluster, - networkoptions.Fuji, - networkoptions.Mainnet, networkoptions.Devnet, + networkoptions.Fuji, } - globalNetworkFlags networkoptions.NetworkFlags + msgFlags MsgFlags ) // avalanche teleporter msg @@ -42,7 +46,12 @@ func newMsgCmd() *cobra.Command { RunE: msg, Args: cobrautils.ExactArgs(3), } - networkoptions.AddNetworkFlagsToCmd(cmd, &globalNetworkFlags, true, msgSupportedNetworkOptions) + networkoptions.AddNetworkFlagsToCmd(cmd, &msgFlags.Network, true, msgSupportedNetworkOptions) + cmd.Flags().BoolVar(&msgFlags.HexEncodedMessage, "hex-encoded", false, "given message is hex encoded") + cmd.Flags().StringVar(&msgFlags.DestinationAddress, "destination-address", "", "deliver the message to the given contract destination address") + cmd.Flags().StringVar(&msgFlags.PrivateKeyFlags.PrivateKey, "private-key", "", "private key to use as message originator and to pay source blockchain fees") + cmd.Flags().StringVar(&msgFlags.PrivateKeyFlags.KeyName, "key", "", "CLI stored key to use to use as message originator and to pay source blockchain fees") + cmd.Flags().BoolVar(&msgFlags.PrivateKeyFlags.GenesisKey, "genesis-key", false, "use genesis aidrop key to use as message originator and to pay source blockchain fees") return cmd } @@ -51,6 +60,14 @@ func msg(_ *cobra.Command, args []string) error { destSubnetName := args[1] message := args[2] + if !cmdflags.EnsureMutuallyExclusive([]bool{ + msgFlags.PrivateKeyFlags.PrivateKey != "", + msgFlags.PrivateKeyFlags.KeyName != "", + msgFlags.PrivateKeyFlags.GenesisKey, + }) { + return fmt.Errorf("--private-key, --key and --genesis-key are mutually exclusive flags") + } + subnetNameToGetNetworkFrom := "" if !isCChain(sourceSubnetName) { subnetNameToGetNetworkFrom = sourceSubnetName @@ -61,7 +78,7 @@ func msg(_ *cobra.Command, args []string) error { network, err := networkoptions.GetNetworkFromCmdLineFlags( app, "", - globalNetworkFlags, + msgFlags.Network, true, false, msgSupportedNetworkOptions, @@ -71,87 +88,78 @@ func msg(_ *cobra.Command, args []string) error { return err } - _, _, sourceChainID, sourceMessengerAddress, _, sourceKey, err := bridgecmd.GetSubnetParams( + genesisAddress, genesisPrivateKey, err := getEVMSubnetPrefundedKey( network, sourceSubnetName, isCChain(sourceSubnetName), + "", ) if err != nil { return err } - _, _, destChainID, destMessengerAddress, _, _, err := bridgecmd.GetSubnetParams( - network, - destSubnetName, - isCChain(destSubnetName), + privateKey, err := getPrivateKeyFromFlags( + msgFlags.PrivateKeyFlags, + genesisPrivateKey, ) if err != nil { return err } - - if sourceMessengerAddress != destMessengerAddress { - return fmt.Errorf("different teleporter messenger addresses among subnets: %s vs %s", sourceMessengerAddress, destMessengerAddress) + if privateKey == "" { + privateKey, err = promptPrivateKey("send the message", genesisAddress, genesisPrivateKey) + if err != nil { + return err + } } - ctx, cancel := context.WithTimeout(context.Background(), time.Second*10) - defer cancel() - - // get clients + messengers - sourceClient, err := evm.GetClient(network.BlockchainEndpoint(sourceChainID.String())) - if err != nil { - return err - } - sourceMessenger, err := teleportermessenger.NewTeleporterMessenger(common.HexToAddress(sourceMessengerAddress), sourceClient) - if err != nil { - return err - } - destWebSocketClient, err := evm.GetClient(network.BlockchainWSEndpoint(destChainID.String())) + _, _, sourceBlockchainID, sourceMessengerAddress, _, _, err := bridgecmd.GetSubnetParams( + network, + sourceSubnetName, + isCChain(sourceSubnetName), + ) if err != nil { return err } - destMessenger, err := teleportermessenger.NewTeleporterMessenger(common.HexToAddress(destMessengerAddress), destWebSocketClient) + _, _, destBlockchainID, destMessengerAddress, _, _, err := bridgecmd.GetSubnetParams( + network, + destSubnetName, + isCChain(destSubnetName), + ) if err != nil { return err } - // subscribe to get new heads from destination - destHeadsCh := make(chan *types.Header, 10) - destHeadsSubscription, err := destWebSocketClient.SubscribeNewHead(ctx, destHeadsCh) - if err != nil { - return err + if sourceMessengerAddress != destMessengerAddress { + return fmt.Errorf("different teleporter messenger addresses among subnets: %s vs %s", sourceMessengerAddress, destMessengerAddress) } - defer destHeadsSubscription.Unsubscribe() - // send tx to the teleporter contract at the source - sourceAddress := common.HexToAddress(sourceKey.C()) - msgInput := teleportermessenger.TeleporterMessageInput{ - DestinationBlockchainID: destChainID, - DestinationAddress: sourceAddress, - FeeInfo: teleportermessenger.TeleporterFeeInfo{ - FeeTokenAddress: sourceAddress, - Amount: big.NewInt(0), - }, - RequiredGasLimit: big.NewInt(1), - AllowedRelayerAddresses: []common.Address{}, - Message: []byte(message), - } - ux.Logger.PrintToUser("Delivering message %q from source subnet %q (%s)", message, sourceSubnetName, sourceChainID) - txOpts, err := evm.GetTxOptsWithSigner(sourceClient, sourceKey.PrivKeyHex()) - if err != nil { - return err + encodedMessage := []byte(message) + if msgFlags.HexEncodedMessage { + encodedMessage = common.FromHex(message) } - txOpts.Context = ctx - tx, err := sourceMessenger.SendCrossChainMessage(txOpts, msgInput) - if err != nil { - return err + destAddr := common.Address{} + if msgFlags.DestinationAddress != "" { + if err := prompts.ValidateAddress(msgFlags.DestinationAddress); err != nil { + return fmt.Errorf("failure validating address %s: %w", msgFlags.DestinationAddress, err) + } + destAddr = common.HexToAddress(msgFlags.DestinationAddress) } - sourceReceipt, b, err := evm.WaitForTransaction(sourceClient, tx) + // send tx to the teleporter contract at the source + ux.Logger.PrintToUser("Delivering message %q from source subnet %q (%s)", message, sourceSubnetName, sourceBlockchainID) + tx, receipt, err := teleporter.SendCrossChainMessage( + network.BlockchainEndpoint(sourceBlockchainID.String()), + common.HexToAddress(sourceMessengerAddress), + privateKey, + destBlockchainID, + destAddr, + encodedMessage, + ) if err != nil { return err } - if !b { + if err == contract.ErrFailedReceiptStatus { txHash := tx.Hash().String() ux.Logger.PrintToUser("error: source receipt status for tx %s is not ReceiptStatusSuccessful", txHash) - trace, err := evm.GetTrace(network.BlockchainEndpoint(sourceChainID.String()), txHash) + trace, err := evm.GetTrace(network.BlockchainEndpoint(sourceBlockchainID.String()), txHash) if err != nil { ux.Logger.PrintToUser("error obtaining tx trace: %s", err) ux.Logger.PrintToUser("") @@ -162,73 +170,40 @@ func msg(_ *cobra.Command, args []string) error { } return fmt.Errorf("source receipt status for tx %s is not ReceiptStatusSuccessful", txHash) } - sourceEvent, err := evm.GetEventFromLogs(sourceReceipt.Logs, sourceMessenger.ParseSendCrossChainMessage) + + event, err := evm.GetEventFromLogs(receipt.Logs, teleporter.ParseSendCrossChainMessage) if err != nil { return err } - if destChainID != ids.ID(sourceEvent.DestinationBlockchainID[:]) { - return fmt.Errorf("invalid destination blockchain id at source event, expected %s, got %s", destChainID, ids.ID(sourceEvent.DestinationBlockchainID[:])) + if destBlockchainID != ids.ID(event.DestinationBlockchainID[:]) { + return fmt.Errorf("invalid destination blockchain id at source event, expected %s, got %s", destBlockchainID, ids.ID(event.DestinationBlockchainID[:])) } - if message != string(sourceEvent.Message.Message) { - return fmt.Errorf("invalid message content at source event, expected %s, got %s", message, string(sourceEvent.Message.Message)) + if message != string(event.Message.Message) { + return fmt.Errorf("invalid message content at source event, expected %s, got %s", message, string(event.Message.Message)) } // receive and process head from destination - ux.Logger.PrintToUser("Waiting for message to be received on destination subnet %q (%s)", destSubnetName, destChainID) - var head *types.Header - select { - case head = <-destHeadsCh: - case <-ctx.Done(): - return ctx.Err() - } - if sourceChainID == destChainID { - // we have another block - select { - case head = <-destHeadsCh: - case <-ctx.Done(): - return ctx.Err() + ux.Logger.PrintToUser("Waiting for message to be delivered to destination subnet %q (%s)", destSubnetName, destBlockchainID) + + arrivalCheckInterval := 100 * time.Millisecond + arrivalCheckTimeout := 10 * time.Second + t0 := time.Now() + for { + if b, err := teleporter.MessageReceived( + network.BlockchainEndpoint(destBlockchainID.String()), + common.HexToAddress(destMessengerAddress), + event.MessageID, + ); err != nil { + return err + } else if b { + break } - } - blockNumber := head.Number - block, err := destWebSocketClient.BlockByNumber(ctx, blockNumber) - if err != nil { - return err - } - if len(block.Transactions()) != 1 { - return fmt.Errorf("expected to have only one transaction on new block at destination") - } - destReceipt, err := destWebSocketClient.TransactionReceipt(ctx, block.Transactions()[0].Hash()) - if err != nil { - return err - } - if destReceipt.Status != types.ReceiptStatusSuccessful { - txHash := block.Transactions()[0].Hash().String() - ux.Logger.PrintToUser("error: dest receipt status for tx %s is not ReceiptStatusSuccessful", txHash) - trace, err := evm.GetTrace(network.BlockchainEndpoint(destChainID.String()), txHash) - if err != nil { - ux.Logger.PrintToUser("error obtaining tx trace: %s", err) - ux.Logger.PrintToUser("") - } else { - ux.Logger.PrintToUser("") - ux.Logger.PrintToUser("trace: %#v", trace) - ux.Logger.PrintToUser("") + elapsed := time.Since(t0) + if elapsed > arrivalCheckTimeout { + return fmt.Errorf("timeout waiting for message to be teleported") } - return fmt.Errorf("dest receipt status for tx %s is not ReceiptStatusSuccessful", txHash) - } - destEvent, err := evm.GetEventFromLogs(destReceipt.Logs, destMessenger.ParseReceiveCrossChainMessage) - if err != nil { - return err - } - - if sourceChainID != ids.ID(destEvent.SourceBlockchainID[:]) { - return fmt.Errorf("invalid source blockchain id at dest event, expected %s, got %s", sourceChainID, ids.ID(destEvent.SourceBlockchainID[:])) - } - if message != string(destEvent.Message.Message) { - return fmt.Errorf("invalid message content at source event, expected %s, got %s", message, string(destEvent.Message.Message)) - } - if sourceEvent.MessageID != destEvent.MessageID { - return fmt.Errorf("unexpected difference between message ID at source and dest events: %s vs %s", hex.EncodeToString(sourceEvent.MessageID[:]), hex.EncodeToString(destEvent.MessageID[:])) + time.Sleep(arrivalCheckInterval) } ux.Logger.PrintToUser("Message successfully Teleported!") diff --git a/cmd/teleportercmd/startRelayer.go b/cmd/teleportercmd/startRelayer.go index 4846c7915..98252fac6 100644 --- a/cmd/teleportercmd/startRelayer.go +++ b/cmd/teleportercmd/startRelayer.go @@ -17,7 +17,10 @@ import ( "github.com/spf13/cobra" ) -var startRelayerNetworkOptions = []networkoptions.NetworkOption{networkoptions.Local, networkoptions.Cluster} +var ( + startRelayerNetworkOptions = []networkoptions.NetworkOption{networkoptions.Local, networkoptions.Cluster} + globalNetworkFlags networkoptions.NetworkFlags +) // avalanche teleporter relayer start func newStartRelayerCmd() *cobra.Command { diff --git a/pkg/bridge/deploy.go b/pkg/bridge/deploy.go index 2b5ab1572..47dca6e3a 100644 --- a/pkg/bridge/deploy.go +++ b/pkg/bridge/deploy.go @@ -34,7 +34,7 @@ func RegisterERC20Remote( feeInfo := TeleporterFeeInfo{ Amount: big.NewInt(0), } - return contract.TxToMethod( + _, _, err := contract.TxToMethod( rpcURL, privateKey, remoteAddress, @@ -42,6 +42,7 @@ func RegisterERC20Remote( "registerWithHome((address, uint256))", feeInfo, ) + return err } func DeployERC20Remote( diff --git a/pkg/bridge/operate.go b/pkg/bridge/operate.go index 5a8341ebf..e109a247d 100644 --- a/pkg/bridge/operate.go +++ b/pkg/bridge/operate.go @@ -4,6 +4,7 @@ package bridge import ( _ "embed" + "fmt" "math/big" "github.com/ava-labs/avalanche-cli/pkg/contract" @@ -49,7 +50,10 @@ func ERC20TokenHomeGetTokenAddress( if err != nil { return common.Address{}, err } - tokenAddress := out[0].(common.Address) + tokenAddress, b := out[0].(common.Address) + if !b { + return common.Address{}, fmt.Errorf("error at token call, expected common.Address, got %T", out[0]) + } return tokenAddress, nil } @@ -65,7 +69,10 @@ func NativeTokenHomeGetTokenAddress( if err != nil { return common.Address{}, err } - tokenAddress := out[0].(common.Address) + tokenAddress, b := out[0].(common.Address) + if !b { + return common.Address{}, fmt.Errorf("error at wrappedToken call, expected common.Address, got %T", out[0]) + } return tokenAddress, nil } @@ -81,8 +88,11 @@ func ERC20TokenRemoteGetTokenHomeAddress( if err != nil { return common.Address{}, err } - tokenHomeAddress := out[0].(common.Address) - return tokenHomeAddress, nil + tokenHubAddress, b := out[0].(common.Address) + if !b { + return common.Address{}, fmt.Errorf("error at tokenHubAddress call, expected common.Address, got %T", out[0]) + } + return tokenHubAddress, nil } func ERC20TokenHomeSend( @@ -108,7 +118,7 @@ func ERC20TokenHomeSend( if err != nil { return err } - if err := contract.TxToMethod( + if _, _, err := contract.TxToMethod( rpcURL, privateKey, tokenAddress, @@ -129,7 +139,7 @@ func ERC20TokenHomeSend( RequiredGasLimit: big.NewInt(250000), MultiHopFallback: common.Address{}, } - return contract.TxToMethod( + _, _, err = contract.TxToMethod( rpcURL, privateKey, homeAddress, @@ -138,6 +148,7 @@ func ERC20TokenHomeSend( params, amount, ) + return err } func NativeTokenHomeSend( @@ -173,7 +184,7 @@ func NativeTokenHomeSend( RequiredGasLimit: big.NewInt(250000), MultiHopFallback: common.Address{}, } - return contract.TxToMethod( + _, _, err = contract.TxToMethod( rpcURL, privateKey, homeAddress, @@ -181,6 +192,7 @@ func NativeTokenHomeSend( "send((bytes32, address, address, address, uint256, uint256, uint256, address))", params, ) + return err } func ERC20TokenRemoteSend( @@ -192,7 +204,7 @@ func ERC20TokenRemoteSend( amountRecipient common.Address, amount *big.Int, ) error { - if err := contract.TxToMethod( + if _, _, err := contract.TxToMethod( rpcURL, privateKey, remoteAddress, @@ -223,7 +235,7 @@ func ERC20TokenRemoteSend( RequiredGasLimit: big.NewInt(250000), MultiHopFallback: common.Address{}, } - return contract.TxToMethod( + _, _, err := contract.TxToMethod( rpcURL, privateKey, remoteAddress, @@ -232,4 +244,5 @@ func ERC20TokenRemoteSend( params, amount, ) + return err } diff --git a/pkg/contract/contract.go b/pkg/contract/contract.go index c54644497..77f07b4a5 100644 --- a/pkg/contract/contract.go +++ b/pkg/contract/contract.go @@ -11,10 +11,14 @@ import ( "strings" "github.com/ava-labs/avalanche-cli/pkg/evm" + "github.com/ava-labs/avalanche-cli/pkg/utils" "github.com/ava-labs/subnet-evm/accounts/abi/bind" + "github.com/ava-labs/subnet-evm/core/types" "github.com/ethereum/go-ethereum/common" ) +var ErrFailedReceiptStatus = fmt.Errorf("failed receipt status") + func removeSurroundingParenthesis(s string) (string, error) { s = strings.TrimSpace(s) if len(s) > 0 { @@ -40,16 +44,21 @@ func removeSurroundingBrackets(s string) (string, error) { func getWords(s string) []string { words := []string{} word := "" - insideParenthesis := false + parenthesisCount := 0 insideBrackets := false for _, rune := range s { c := string(rune) - if insideParenthesis { + if parenthesisCount > 0 { word += c + if c == "(" { + parenthesisCount++ + } if c == ")" { - words = append(words, word) - word = "" - insideParenthesis = false + parenthesisCount-- + if parenthesisCount == 0 { + words = append(words, word) + word = "" + } } continue } @@ -72,7 +81,7 @@ func getWords(s string) []string { continue } if c == "(" { - insideParenthesis = true + parenthesisCount++ } if c == "[" { insideBrackets = true @@ -87,10 +96,48 @@ func getWords(s string) []string { func getMap( types []string, - params ...interface{}, + params interface{}, ) ([]map[string]interface{}, error) { r := []map[string]interface{}{} for i, t := range types { + var ( + param interface{} + name string + structName string + ) + rt := reflect.ValueOf(params) + if rt.Kind() == reflect.Ptr { + rt = rt.Elem() + } + if rt.Kind() == reflect.Slice { + if rt.Len() != len(types) { + if rt.Len() == 1 { + return getMap(types, rt.Index(0).Interface()) + } else { + return nil, fmt.Errorf( + "inconsistency in slice len between method esp %q and given params %#v: expected %d got %d", + types, + params, + len(types), + rt.Len(), + ) + } + } + param = rt.Index(i).Interface() + } else if rt.Kind() == reflect.Struct { + if rt.NumField() < len(types) { + return nil, fmt.Errorf( + "inconsistency in struct len between method esp %q and given params %#v: expected %d got %d", + types, + params, + len(types), + rt.NumField(), + ) + } + name = rt.Type().Field(i).Name + structName = rt.Type().Field(i).Type.Name() + param = rt.Field(i).Interface() + } m := map[string]interface{}{} switch { case string(t[0]) == "(": @@ -100,16 +147,18 @@ func getMap( if err != nil { return nil, err } - m["components"], err = getMap(getWords(t), params[i]) + m["components"], err = getMap(getWords(t), param) if err != nil { return nil, err } - m["internaltype"] = "tuple" + if structName != "" { + m["internalType"] = "struct " + structName + } else { + m["internalType"] = "tuple" + } m["type"] = "tuple" - m["name"] = "" + m["name"] = name case string(t[0]) == "[": - // TODO: add more types - // slice struct type var err error t, err = removeSurroundingBrackets(t) if err != nil { @@ -120,30 +169,30 @@ func getMap( if err != nil { return nil, err } - m["components"], err = getMap(getWords(t), params[i]) + rt := reflect.ValueOf(param) + if rt.Kind() != reflect.Slice { + return nil, fmt.Errorf("expected param for field %d of esp %q to be an slice", i, types) + } + param = reflect.Zero(rt.Type().Elem()).Interface() + structName = rt.Type().Elem().Name() + m["components"], err = getMap(getWords(t), param) if err != nil { return nil, err } - m["internaltype"] = "tuple[]" + if structName != "" { + m["internalType"] = "struct " + structName + "[]" + } else { + m["internalType"] = "tuple[]" + } m["type"] = "tuple[]" - m["name"] = "" + m["name"] = name } else { - m["internaltype"] = fmt.Sprintf("%s[]", t) + m["internalType"] = fmt.Sprintf("%s[]", t) m["type"] = fmt.Sprintf("%s[]", t) - m["name"] = "" + m["name"] = name } default: - name := "" - if len(params) == 1 { - rt := reflect.ValueOf(params[0]) - if rt.Kind() == reflect.Slice && rt.Len() > 0 { - rt = rt.Index(0) - } - if rt.Kind() == reflect.Struct && rt.NumField() == len(types) { - name = rt.Type().Field(i).Name - } - } - m["internaltype"] = t + m["internalType"] = t m["type"] = t m["name"] = name } @@ -152,71 +201,86 @@ func getMap( return r, nil } -func ParseMethodEsp( - methodEsp string, +func ParseEsp( + esp string, + indexedFields []int, constructor bool, + event bool, paid bool, view bool, params ...interface{}, ) (string, string, error) { - index := strings.Index(methodEsp, "(") + index := strings.Index(esp, "(") if index == -1 { - return methodEsp, "", nil + return esp, "", nil } - methodName := methodEsp[:index] - methodTypes := methodEsp[index:] - methodInputs := "" - methodOutputs := "" - index = strings.Index(methodTypes, "->") + name := esp[:index] + types := esp[index:] + inputs := "" + outputs := "" + index = strings.Index(types, "->") if index == -1 { - methodInputs = methodTypes + inputs = types } else { - methodInputs = methodTypes[:index] - methodOutputs = methodTypes[index+2:] + inputs = types[:index] + outputs = types[index+2:] } var err error - methodInputs, err = removeSurroundingParenthesis(methodInputs) + inputs, err = removeSurroundingParenthesis(inputs) if err != nil { return "", "", err } - methodOutputs, err = removeSurroundingParenthesis(methodOutputs) + outputs, err = removeSurroundingParenthesis(outputs) if err != nil { return "", "", err } - inputTypes := getWords(methodInputs) - outputTypes := getWords(methodOutputs) - inputs, err := getMap(inputTypes, params...) + inputTypes := getWords(inputs) + outputTypes := getWords(outputs) + inputsMaps, err := getMap(inputTypes, params) if err != nil { return "", "", err } - outputs, err := getMap(outputTypes) + outputsMaps, err := getMap(outputTypes, nil) if err != nil { return "", "", err } + if event { + for i := range inputsMaps { + if utils.Belongs(indexedFields, i) { + inputsMaps[i]["indexed"] = true + } + } + } abiMap := []map[string]interface{}{ { - "inputs": inputs, - "stateMutability": "nonpayable", - "type": "function", + "inputs": inputsMaps, }, } - if !constructor { - abiMap[0]["outputs"] = outputs - abiMap[0]["name"] = methodName - } else { - abiMap[0]["type"] = "constructor" - } - if paid { + switch { + case paid: abiMap[0]["stateMutability"] = "payable" - } - if view { + case view: abiMap[0]["stateMutability"] = "view" + default: + abiMap[0]["stateMutability"] = "nonpayable" + } + switch { + case constructor: + abiMap[0]["type"] = "constructor" + case event: + abiMap[0]["type"] = "event" + abiMap[0]["name"] = name + delete(abiMap[0], "stateMutability") + default: + abiMap[0]["type"] = "function" + abiMap[0]["outputs"] = outputsMaps + abiMap[0]["name"] = name } abiBytes, err := json.MarshalIndent(abiMap, "", " ") if err != nil { return "", "", err } - return methodName, string(abiBytes), nil + return name, string(abiBytes), nil } func TxToMethod( @@ -226,39 +290,40 @@ func TxToMethod( payment *big.Int, methodEsp string, params ...interface{}, -) error { - methodName, methodABI, err := ParseMethodEsp(methodEsp, false, payment != nil, false, params...) +) (*types.Transaction, *types.Receipt, error) { + methodName, methodABI, err := ParseEsp(methodEsp, nil, false, false, payment != nil, false, params...) if err != nil { - return err + return nil, nil, err } metadata := &bind.MetaData{ ABI: methodABI, } abi, err := metadata.GetAbi() if err != nil { - return err + return nil, nil, err } client, err := evm.GetClient(rpcURL) if err != nil { - return err + return nil, nil, err } defer client.Close() contract := bind.NewBoundContract(contractAddress, *abi, client, client, client) txOpts, err := evm.GetTxOptsWithSigner(client, privateKey) if err != nil { - return err + return nil, nil, err } txOpts.Value = payment tx, err := contract.Transact(txOpts, methodName, params...) if err != nil { - return err + return nil, nil, err } - if _, success, err := evm.WaitForTransaction(client, tx); err != nil { - return err + receipt, success, err := evm.WaitForTransaction(client, tx) + if err != nil { + return tx, nil, err } else if !success { - return fmt.Errorf("failed receipt status deploying contract") + return tx, receipt, ErrFailedReceiptStatus } - return nil + return tx, receipt, nil } func CallToMethod( @@ -267,7 +332,7 @@ func CallToMethod( methodEsp string, params ...interface{}, ) ([]interface{}, error) { - methodName, methodABI, err := ParseMethodEsp(methodEsp, false, false, true, params...) + methodName, methodABI, err := ParseEsp(methodEsp, nil, false, false, false, true, params...) if err != nil { return nil, err } @@ -299,7 +364,7 @@ func DeployContract( methodEsp string, params ...interface{}, ) (common.Address, error) { - _, methodABI, err := ParseMethodEsp(methodEsp, true, false, false, params...) + _, methodABI, err := ParseEsp(methodEsp, nil, true, false, false, false, params...) if err != nil { return common.Address{}, err } @@ -328,7 +393,28 @@ func DeployContract( if _, success, err := evm.WaitForTransaction(client, tx); err != nil { return common.Address{}, err } else if !success { - return common.Address{}, fmt.Errorf("failed receipt status deploying contract") + return common.Address{}, ErrFailedReceiptStatus } return address, nil } + +func UnpackLog( + eventEsp string, + indexedFields []int, + log types.Log, + event interface{}, +) error { + eventName, eventABI, err := ParseEsp(eventEsp, indexedFields, false, true, false, false, event) + if err != nil { + return err + } + metadata := &bind.MetaData{ + ABI: eventABI, + } + abi, err := metadata.GetAbi() + if err != nil { + return err + } + contract := bind.NewBoundContract(common.Address{}, *abi, nil, nil, nil) + return contract.UnpackLog(event, eventName, log) +} diff --git a/pkg/evm/evm.go b/pkg/evm/evm.go index 72d115b8e..92ade8440 100644 --- a/pkg/evm/evm.go +++ b/pkg/evm/evm.go @@ -338,13 +338,18 @@ func WaitForTransaction( // Returns the first log in 'logs' that is successfully parsed by 'parser' func GetEventFromLogs[T any](logs []*types.Log, parser func(log types.Log) (T, error)) (T, error) { - for _, log := range logs { + cumErrMsg := "" + for i, log := range logs { event, err := parser(*log) if err == nil { return event, nil } + if cumErrMsg != "" { + cumErrMsg += "; " + } + cumErrMsg += fmt.Sprintf("log %d -> %s", i, err.Error()) } - return *new(T), fmt.Errorf("failed to find %T event in receipt logs", *new(T)) + return *new(T), fmt.Errorf("failed to find %T event in receipt logs: [%s]", *new(T), cumErrMsg) } func GetRPCClient(rpcURL string) (*rpc.Client, error) { diff --git a/pkg/subnet/local.go b/pkg/subnet/local.go index eb9c9afb9..2fa47be6a 100644 --- a/pkg/subnet/local.go +++ b/pkg/subnet/local.go @@ -90,6 +90,15 @@ type getGRPCClientFunc func(...binutils.GRPCClientOpOption) (client.Client, erro type setDefaultSnapshotFunc func(string, bool, string, bool) (bool, error) +type TeleporterEsp struct { + SkipDeploy bool + Version string + MessengerContractAddressPath string + MessengerDeployerAddressPath string + MessengerDeployerTxPath string + RegistryBydecodePath string +} + type DeployInfo struct { SubnetID ids.ID BlockchainID ids.ID @@ -100,11 +109,11 @@ type DeployInfo struct { // DeployToLocalNetwork does the heavy lifting: // * it checks the gRPC is running, if not, it starts it // * kicks off the actual deployment -func (d *LocalDeployer) DeployToLocalNetwork(chain string, chainGenesis []byte, genesisPath string, skipTeleporter bool, subnetIDStr string) (*DeployInfo, error) { +func (d *LocalDeployer) DeployToLocalNetwork(chain string, chainGenesis []byte, genesisPath string, teleporterEsp TeleporterEsp, subnetIDStr string) (*DeployInfo, error) { if err := d.StartServer(); err != nil { return nil, err } - return d.doDeploy(chain, chainGenesis, genesisPath, skipTeleporter, subnetIDStr) + return d.doDeploy(chain, chainGenesis, genesisPath, teleporterEsp, subnetIDStr) } func getAssetID(wallet primary.Wallet, tokenName string, tokenSymbol string, maxSupply uint64) (ids.ID, error) { @@ -370,7 +379,7 @@ func (d *LocalDeployer) BackendStartedHere() bool { // - deploy a new blockchain for the given VM ID, genesis, and available subnet ID // - waits completion of operation // - show status -func (d *LocalDeployer) doDeploy(chain string, chainGenesis []byte, genesisPath string, skipTeleporter bool, subnetIDStr string) (*DeployInfo, error) { +func (d *LocalDeployer) doDeploy(chain string, chainGenesis []byte, genesisPath string, teleporterEsp TeleporterEsp, subnetIDStr string) (*DeployInfo, error) { needsRestart, avalancheGoBinPath, err := d.SetupLocalEnv() if err != nil { return nil, err @@ -564,7 +573,7 @@ func (d *LocalDeployer) doDeploy(chain string, chainGenesis []byte, genesisPath teleporterMessengerAddress string teleporterRegistryAddress string ) - if sc.TeleporterReady && !skipTeleporter { + if sc.TeleporterReady && !teleporterEsp.SkipDeploy { network := models.NewLocalNetwork() // get relayer address relayerAddress, relayerPrivateKey, err := teleporter.GetRelayerKeyInfo(d.app.GetKeyPath(constants.AWMRelayerKeyName)) @@ -578,9 +587,40 @@ func (d *LocalDeployer) doDeploy(chain string, chainGenesis []byte, genesisPath } // deploy C-Chain ux.Logger.PrintToUser("") + td := teleporter.Deployer{} + if teleporterEsp.MessengerContractAddressPath != "" { + if err := td.SetAssetsFromPaths( + teleporterEsp.MessengerContractAddressPath, + teleporterEsp.MessengerDeployerAddressPath, + teleporterEsp.MessengerDeployerTxPath, + teleporterEsp.RegistryBydecodePath, + ); err != nil { + return nil, err + } + } else { + teleporterVersion := "" + switch { + case teleporterEsp.Version != "" && teleporterEsp.Version != "latest": + teleporterVersion = teleporterEsp.Version + case sc.TeleporterVersion != "": + teleporterVersion = sc.TeleporterVersion + default: + teleporterInfo, err := teleporter.GetInfo(d.app) + if err != nil { + return nil, err + } + teleporterVersion = teleporterInfo.Version + } + if err := td.DownloadAssets( + d.app.GetTeleporterBinDir(), + teleporterVersion, + ); err != nil { + return nil, err + } + } alreadyDeployed, cchainTeleporterMessengerAddress, cchainTeleporterRegistryAddress, err := teleporter.DeployAndFundRelayer( d.app, - sc.TeleporterVersion, + &td, network, "c-chain", "C", @@ -630,7 +670,7 @@ func (d *LocalDeployer) doDeploy(chain string, chainGenesis []byte, genesisPath } _, teleporterMessengerAddress, teleporterRegistryAddress, err = teleporter.DeployAndFundRelayer( d.app, - sc.TeleporterVersion, + &td, network, chain, blockchainID, diff --git a/pkg/subnet/local_test.go b/pkg/subnet/local_test.go index cc2d4e5f4..0aaf9eb70 100644 --- a/pkg/subnet/local_test.go +++ b/pkg/subnet/local_test.go @@ -140,7 +140,10 @@ func TestDeployToLocal(t *testing.T) { err = os.WriteFile(testSidecar.Name(), []byte(sidecar), constants.DefaultPerms755) require.NoError(err) // test actual deploy - deployInfo, err := testDeployer.DeployToLocalNetwork(testChainName, []byte(genesis), testGenesis.Name(), true, "") + teleporterEsp := TeleporterEsp{ + SkipDeploy: true, + } + deployInfo, err := testDeployer.DeployToLocalNetwork(testChainName, []byte(genesis), testGenesis.Name(), teleporterEsp, "") require.NoError(err) require.Equal(testSubnetID2, deployInfo.SubnetID.String()) require.Equal(testBlockChainID2, deployInfo.BlockchainID.String()) diff --git a/pkg/teleporter/operate.go b/pkg/teleporter/operate.go new file mode 100644 index 000000000..e037cfbfc --- /dev/null +++ b/pkg/teleporter/operate.go @@ -0,0 +1,137 @@ +// Copyright (C) 2022, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. +package teleporter + +import ( + _ "embed" + "fmt" + "math/big" + + "github.com/ava-labs/avalanche-cli/pkg/contract" + "github.com/ava-labs/avalanchego/ids" + "github.com/ava-labs/subnet-evm/core/types" + "github.com/ethereum/go-ethereum/common" +) + +func GetNextMessageID( + rpcURL string, + messengerAddress common.Address, + destinationBlockchainID ids.ID, +) (ids.ID, error) { + out, err := contract.CallToMethod( + rpcURL, + messengerAddress, + "getNextMessageID(bytes32)->(bytes32)", + destinationBlockchainID, + ) + if err != nil { + return ids.Empty, err + } + received, b := out[0].([32]byte) + if !b { + return ids.Empty, fmt.Errorf("error at getNextMessageID call, expected ids.ID, got %T", out[0]) + } + return received, nil +} + +func MessageReceived( + rpcURL string, + messengerAddress common.Address, + messageID ids.ID, +) (bool, error) { + out, err := contract.CallToMethod( + rpcURL, + messengerAddress, + "messageReceived(bytes32)->(bool)", + messageID, + ) + if err != nil { + return false, err + } + received, b := out[0].(bool) + if !b { + return false, fmt.Errorf("error at messageReceived call, expected bool, got %T", out[0]) + } + return received, nil +} + +func SendCrossChainMessage( + rpcURL string, + messengerAddress common.Address, + privateKey string, + destinationBlockchainID ids.ID, + destinationAddress common.Address, + message []byte, +) (*types.Transaction, *types.Receipt, error) { + type FeeInfo struct { + FeeTokenAddress common.Address + Amount *big.Int + } + type Params struct { + DestinationBlockchainID [32]byte + DestinationAddress common.Address + FeeInfo FeeInfo + RequiredGasLimit *big.Int + AllowedRelayerAddresses []common.Address + Message []byte + } + params := Params{ + DestinationBlockchainID: destinationBlockchainID, + DestinationAddress: destinationAddress, + FeeInfo: FeeInfo{ + FeeTokenAddress: common.Address{}, + Amount: big.NewInt(0), + }, + RequiredGasLimit: big.NewInt(1), + AllowedRelayerAddresses: []common.Address{}, + Message: message, + } + return contract.TxToMethod( + rpcURL, + privateKey, + messengerAddress, + nil, + "sendCrossChainMessage((bytes32, address, (address, uint256), uint256, [address], bytes))->(bytes32)", + params, + ) +} + +// events + +type TeleporterMessageReceipt struct { + ReceivedMessageNonce *big.Int + RelayerRewardAddress common.Address +} +type TeleporterFeeInfo struct { + FeeTokenAddress common.Address + Amount *big.Int +} +type TeleporterMessage struct { + MessageNonce *big.Int + OriginSenderAddress common.Address + DestinationBlockchainID [32]byte + DestinationAddress common.Address + RequiredGasLimit *big.Int + AllowedRelayerAddresses []common.Address + Receipts []TeleporterMessageReceipt + Message []byte +} +type TeleporterMessengerSendCrossChainMessage struct { + MessageID [32]byte + DestinationBlockchainID [32]byte + Message TeleporterMessage + FeeInfo TeleporterFeeInfo +} + +func ParseSendCrossChainMessage(log types.Log) (*TeleporterMessengerSendCrossChainMessage, error) { + event := new(TeleporterMessengerSendCrossChainMessage) + if err := contract.UnpackLog( + "SendCrossChainMessage(bytes32,bytes32,(uint256,address,bytes32,address,uint256,[address],[(uint256,address)],bytes),(address,uint256))", + []int{0, 1}, + log, + event, + ); err != nil { + return nil, err + } + return event, nil +} diff --git a/pkg/teleporter/teleporter.go b/pkg/teleporter/teleporter.go index 957ec9ee6..72b30cbd8 100644 --- a/pkg/teleporter/teleporter.go +++ b/pkg/teleporter/teleporter.go @@ -78,6 +78,70 @@ func (t *Deployer) GetAssets( return t.messengerContractAddress, t.messengerDeployerAddress, t.messengerDeployerTx, t.registryBydecode, nil } +func (t *Deployer) CheckAssets() error { + if t.messengerContractAddress == "" || t.messengerDeployerAddress == "" || t.messengerDeployerTx == "" || t.registryBydecode == "" { + return fmt.Errorf("teleporter assets has not been initialized") + } + return nil +} + +func (t *Deployer) SetAssetsFromPaths( + messengerContractAddressPath string, + messengerDeployerAddressPath string, + messengerDeployerTxPath string, + registryBydecodePath string, +) error { + if messengerContractAddressPath != "" { + if bs, err := os.ReadFile(messengerContractAddressPath); err != nil { + return err + } else { + t.messengerContractAddress = string(bs) + } + } + if messengerDeployerAddressPath != "" { + if bs, err := os.ReadFile(messengerDeployerAddressPath); err != nil { + return err + } else { + t.messengerDeployerAddress = string(bs) + } + } + if messengerDeployerTxPath != "" { + if bs, err := os.ReadFile(messengerDeployerTxPath); err != nil { + return err + } else { + t.messengerDeployerTx = string(bs) + } + } + if registryBydecodePath != "" { + if bs, err := os.ReadFile(registryBydecodePath); err != nil { + return err + } else { + t.registryBydecode = string(bs) + } + } + return nil +} + +func (t *Deployer) SetAssets( + messengerContractAddress string, + messengerDeployerAddress string, + messengerDeployerTx string, + registryBydecode string, +) { + if messengerContractAddress != "" { + t.messengerContractAddress = messengerContractAddress + } + if messengerDeployerAddress != "" { + t.messengerDeployerAddress = messengerDeployerAddress + } + if messengerDeployerTx != "" { + t.messengerDeployerTx = messengerDeployerTx + } + if registryBydecode != "" { + t.registryBydecode = registryBydecode + } +} + func (t *Deployer) DownloadAssets( teleporterInstallDir string, version string, @@ -173,8 +237,6 @@ func (t *Deployer) DownloadAssets( } func (t *Deployer) Deploy( - teleporterInstallDir string, - version string, subnetName string, rpcURL string, privateKey string, @@ -189,8 +251,6 @@ func (t *Deployer) Deploy( ) if deployMessenger { alreadyDeployed, messengerAddress, err = t.DeployMessenger( - teleporterInstallDir, - version, subnetName, rpcURL, privateKey, @@ -198,20 +258,18 @@ func (t *Deployer) Deploy( } if err == nil && deployRegistry { if !deployMessenger || !alreadyDeployed { - registryAddress, err = t.DeployRegistry(teleporterInstallDir, version, subnetName, rpcURL, privateKey) + registryAddress, err = t.DeployRegistry(subnetName, rpcURL, privateKey) } } return alreadyDeployed, messengerAddress, registryAddress, err } func (t *Deployer) DeployMessenger( - teleporterInstallDir string, - version string, subnetName string, rpcURL string, privateKey string, ) (bool, string, error) { - if err := t.DownloadAssets(teleporterInstallDir, version); err != nil { + if err := t.CheckAssets(); err != nil { return false, "", err } // check if contract is already deployed @@ -257,13 +315,11 @@ func (t *Deployer) DeployMessenger( } func (t *Deployer) DeployRegistry( - teleporterInstallDir string, - version string, subnetName string, rpcURL string, privateKey string, ) (string, error) { - if err := t.DownloadAssets(teleporterInstallDir, version); err != nil { + if err := t.CheckAssets(); err != nil { return "", err } messengerContractAddress := common.HexToAddress(t.messengerContractAddress) @@ -333,7 +389,7 @@ func SetProposerVM( func DeployAndFundRelayer( app *application.Avalanche, - teleporterVersion string, + td *Deployer, network models.Network, subnetName string, blockchainID string, @@ -344,10 +400,7 @@ func DeployAndFundRelayer( return false, "", "", err } endpoint := network.BlockchainEndpoint(blockchainID) - td := Deployer{} alreadyDeployed, messengerAddress, registryAddress, err := td.Deploy( - app.GetTeleporterBinDir(), - teleporterVersion, subnetName, endpoint, privKeyStr,