diff --git a/README.md b/README.md index 3e12b76..2cf783e 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,13 @@ Hanchon's web3 toolkit - [docs](https://hanchond.com) +## Requirements: + +- `git` to clone the repositories that needs to be build locally +- `go` to build the Evmos client +- `wget` to download the Cosmos-Hub client +- `cargo/rust` to build Hermes + ## TODOs: - Playground queries should use the rest endpoint so the response is in JSON format. That way we can pipe it into `jq` and pipe the result into another query. diff --git a/cmd/playground/buildEvmos.go b/cmd/playground/buildEvmos.go index 5389c8b..f812c14 100644 --- a/cmd/playground/buildEvmos.go +++ b/cmd/playground/buildEvmos.go @@ -31,7 +31,7 @@ var buildEvmosCmd = &cobra.Command{ os.Exit(1) } fmt.Println("Moving built binary...") - if err := filesmanager.CopyFile(path+"/build/evmosd", filesmanager.GetEvmosdPath(version)); err != nil { + if err := filesmanager.MoveFile(path+"/build/evmosd", filesmanager.GetEvmosdPath(version)); err != nil { fmt.Println("could not move the built binary:", err.Error()) os.Exit(1) } diff --git a/cmd/playground/buildGaiad.go b/cmd/playground/buildGaiad.go new file mode 100644 index 0000000..683eead --- /dev/null +++ b/cmd/playground/buildGaiad.go @@ -0,0 +1,50 @@ +package playground + +import ( + "fmt" + "os" + + "github.com/hanchon/hanchond/playground/filesmanager" + "github.com/hanchon/hanchond/playground/gaia" + "github.com/spf13/cobra" +) + +// buildGaiadCmd represents the buildGaiad command +var buildGaiadCmd = &cobra.Command{ + Use: "build-gaiad", + Short: "Get the Gaiad binary from the github releases", + Long: `It downloads the already built gaiad binary from github, it accepts a version flag to specify any tag. It defaults to: v1.9.0.`, + Run: func(cmd *cobra.Command, _ []string) { + _ = filesmanager.SetHomeFolderFromCobraFlags(cmd) + version, err := cmd.Flags().GetString("version") + if err != nil { + fmt.Println("could not read the version:", err.Error()) + os.Exit(1) + } + + isDarwin, err := cmd.Flags().GetBool("is-darwin") + if err != nil { + fmt.Println("could not read the isDarwin:", err.Error()) + os.Exit(1) + } + + // Create build folder if needed + if err := filesmanager.CreateBuildsDir(); err != nil { + fmt.Println("could not create build folder:" + err.Error()) + os.Exit(1) + } + + fmt.Println("Downloading gaiad from github:", version) + if err = gaia.GetGaiadBinary(isDarwin, version); err != nil { + fmt.Println("could not get gaiad from github:" + err.Error()) + os.Exit(1) + } + fmt.Println("Gaiad is now available") + }, +} + +func init() { + PlaygroundCmd.AddCommand(buildGaiadCmd) + buildGaiadCmd.PersistentFlags().StringP("version", "v", "v18.1.0", "Gaiad version to download") + buildGaiadCmd.PersistentFlags().Bool("is-darwin", true, "Is the system MacOS arm?") +} diff --git a/cmd/playground/hermesAddChannel.go b/cmd/playground/hermesAddChannel.go index c44cc89..d664f9e 100644 --- a/cmd/playground/hermesAddChannel.go +++ b/cmd/playground/hermesAddChannel.go @@ -1,10 +1,13 @@ package playground import ( + "context" "fmt" "os" + "strconv" + "strings" - "github.com/hanchon/hanchond/playground/evmos" + "github.com/hanchon/hanchond/playground/database" "github.com/hanchon/hanchond/playground/hermes" "github.com/hanchon/hanchond/playground/sql" "github.com/spf13/cobra" @@ -12,62 +15,84 @@ import ( // hermesAddChannelCmd represents the hermesAddChannel command var hermesAddChannelCmd = &cobra.Command{ - Use: "hermes-add-channel id1 id2", + Use: "hermes-add-channel [chain_id] [chain_id]", Args: cobra.ExactArgs(2), Short: "It uses the hermes client to open an IBC channel between two chains", Long: `This command requires that Hermes was already built and at least one node for each chain running.`, Run: func(cmd *cobra.Command, args []string) { queries := sql.InitDBFromCmd(cmd) - node1 := args[0] - node2 := args[1] - fmt.Println("Getting first node data...") - firstNode := evmos.GetNodeFromDB(queries, node1) - fmt.Println("Getting second node data...") - secondNode := evmos.GetNodeFromDB(queries, node2) - // TODO: make sure that the nodes are running checking for the PID - if firstNode.Node.IsRunning != 1 { - fmt.Println("first node is not running") + chainOne := args[0] + chainOneID, err := strconv.Atoi(chainOne) + if err != nil { + fmt.Println("invalid chain id") os.Exit(1) - } - if secondNode.Node.IsRunning != 1 { - fmt.Println("second node is not running") + chainTwo := args[1] + chainTwoID, err := strconv.Atoi(chainTwo) + if err != nil { + fmt.Println("invalid chain id") os.Exit(1) + } + chains := make([]database.GetAllChainNodesRow, 2) + nodesChainOne, err := queries.GetAllChainNodes(context.Background(), int64(chainOneID)) + if err != nil { + fmt.Println("could not find nodes for chain:", chainOne) + os.Exit(1) + } + chains[0] = nodesChainOne[0] + nodesChainTwo, err := queries.GetAllChainNodes(context.Background(), int64(chainTwoID)) + if err != nil { + fmt.Println("could not find nodes for chain:", chainTwo) + os.Exit(1) } - fmt.Println("Both chains are running") + chains[1] = nodesChainTwo[0] h := hermes.NewHermes() fmt.Println("Relayer initialized") - if err := h.AddEvmosChain( - firstNode.Chain.ChainID, - firstNode.Ports.P26657, - firstNode.Ports.P9090, - firstNode.Node.ValidatorKeyName, - firstNode.Node.ValidatorKey, - ); err != nil { - fmt.Println("error adding first chain to the relayer:", err.Error()) - os.Exit(1) - } - fmt.Println("First chain added") + for _, v := range chains { + if v.IsRunning != 1 { + fmt.Println("the node is not running, chain id:", v.ChainID) + } - if err := h.AddEvmosChain( - secondNode.Chain.ChainID, - secondNode.Ports.P26657, - secondNode.Ports.P9090, - secondNode.Node.ValidatorKeyName, - secondNode.Node.ValidatorKey, - ); err != nil { - fmt.Println("error adding second chain to the relayer:", err.Error()) - os.Exit(1) - } + switch { + case strings.Contains(v.BinaryVersion, "gaia"): + fmt.Println("Adding gaia chain") + if err := h.AddCosmosChain( + v.ChainID_2, + v.P26657, + v.P9090, + v.ValidatorKeyName, + v.ValidatorKey, + v.Prefix, + v.Denom, + ); err != nil { + fmt.Println("error adding first chain to the relayer:", err.Error()) + os.Exit(1) + } + case strings.Contains(v.BinaryVersion, "evmos"): + fmt.Println("Adding evmos chain") + if err := h.AddEvmosChain( + v.ChainID_2, + v.P26657, + v.P9090, + v.ValidatorKeyName, + v.ValidatorKey, + ); err != nil { + fmt.Println("error adding first chain to the relayer:", err.Error()) + os.Exit(1) + } + default: + fmt.Println("incorrect binary name") + os.Exit(1) + } - fmt.Println("Second chain added") + } fmt.Println("Calling create channel") - err := h.CreateChannel(firstNode.Chain.ChainID, secondNode.Chain.ChainID) + err = h.CreateChannel(chains[0].ChainID_2, chains[1].ChainID_2) if err != nil { fmt.Println("error creating channel", err.Error()) os.Exit(1) diff --git a/cmd/playground/initGaia.go b/cmd/playground/initGaia.go new file mode 100644 index 0000000..f2d4439 --- /dev/null +++ b/cmd/playground/initGaia.go @@ -0,0 +1,150 @@ +package playground + +import ( + "fmt" + "os" + "path/filepath" + "strconv" + + "github.com/hanchon/hanchond/playground/database" + "github.com/hanchon/hanchond/playground/filesmanager" + "github.com/hanchon/hanchond/playground/gaia" + "github.com/hanchon/hanchond/playground/sql" + "github.com/spf13/cobra" +) + +// initGaiaCmd represents the initGaia command +var initGaiaCmd = &cobra.Command{ + Use: "init-gaia id", + Args: cobra.ExactArgs(1), + Short: "Init the genesis file for a new chain", + Long: `Set up the data and config folder for the new chain`, + Run: func(cmd *cobra.Command, args []string) { + queries := sql.InitDBFromCmd(cmd) + _ = queries + + chainid, err := strconv.ParseInt(args[0], 10, 64) + if err != nil { + fmt.Println("invalid chain id, it must be integer", err.Error()) + os.Exit(1) + } + + if filesmanager.IsNodeHomeFolderInitialized(chainid, 0) { + fmt.Println("the home folder for this node was already created") + os.Exit(1) + } + + chainID := "cosmoshub-99" + + nodes := make([]*gaia.Gaia, 2) + + var chainDB database.Chain + + for k := range nodes { + path := filesmanager.GetNodeHomeFolder(chainid, int64(k)) + g := gaia.NewGaia(fmt.Sprintf("moniker-%d-%d", chainid, k), path, chainID, "validator-key", "icsstake") + // Init the config files + if err := g.InitNode(); err != nil { + panic(err) + } + // Update general parameters in the genesis file + if err := g.UpdateGenesisFile(); err != nil { + panic(err) + } + if err := g.UpdateConfigFile(false); err != nil { + panic(err) + } + if err := g.UpdateAppFile(); err != nil { + panic(err) + } + if err := g.CreateGenTx(); err != nil { + panic(err) + } + // Assign random and unique ports + if err := g.AssignPorts(queries); err != nil { + panic(err) + } + // Update the Config Files + if err := g.UpdateConfigPorts(); err != nil { + panic(err) + } + + nodes[k] = g + if k == 0 { + chainDB, err = g.SaveChainToDB(queries) + if err != nil { + panic(err) + } + } + _, err := g.SaveNodeToDB(chainDB, queries) + if err != nil { + panic(err) + } + } + + // Join genesis transactions + for k, v := range nodes { + if k == 0 { + continue + } + files, err := filepath.Glob(v.HomeDir + "/config/gentx/*.json") + if err != nil { + panic("no files: " + err.Error()) + } + if len(files) == 0 { + panic("no files 2: " + err.Error()) + } + + if err := filesmanager.CopyFile( + files[0], + nodes[0].HomeDir+"/config/gentx", + ); err != nil { + panic(err) + } + addr, err := v.GetValidatorAddress() + if err != nil { + panic(err) + } + if err := nodes[0].AddGenesisAccount(addr); err != nil { + panic(err) + } + } + + if err := nodes[0].CollectGenTxs(); err != nil { + panic(err) + } + if err := nodes[0].ValidateGenesis(); err != nil { + panic(err) + } + + peers := []string{} + for k := range nodes { + peerInfo, err := nodes[k].GetPeerInfo() + if err != nil { + panic(err) + } + peers = append(peers, peerInfo) + + if k == 0 { + continue + } + if err := filesmanager.CopyFile( + nodes[0].HomeDir+"/config/genesis.json", + nodes[k].HomeDir+"/config/genesis.json", + ); err != nil { + panic(err) + } + } + + for k := range nodes { + if err := nodes[k].AddPersistenPeers(peers); err != nil { + panic(err) + } + } + }, +} + +func init() { + PlaygroundCmd.AddCommand(initGaiaCmd) + initGaiaCmd.Flags().StringP("version", "v", "local", "Version of the Evmos node that you want to use, defaults to local. Tag names are supported.") +} diff --git a/cmd/playground/initGenesis.go b/cmd/playground/initGenesis.go index f7ba8bf..bea4ce4 100644 --- a/cmd/playground/initGenesis.go +++ b/cmd/playground/initGenesis.go @@ -40,20 +40,20 @@ var initGenesisCmd = &cobra.Command{ } - if filesmanager.IsNodeHomeFolderInitialized(chainid) { + if filesmanager.IsNodeHomeFolderInitialized(chainid, 0) { fmt.Println("the home folder for this node was already created") os.Exit(1) } - path := filesmanager.GetNodeHomeFolder(chainid) + path := filesmanager.GetNodeHomeFolder(chainid, 0) chainID := fmt.Sprintf("evmos_9001-%d", chainid) - e := evmos.NewEvmos(version, path, chainID, fmt.Sprintf("mykey%d", chainid)) + e := evmos.NewEvmos("moniker", version, path, chainID, fmt.Sprintf("mykey%d", chainid), "aevmos") if err := e.InitGenesis(); err != nil { fmt.Println("could not init the genesis file", err.Error()) os.Exit(1) } - if err := e.SetPorts(); err != nil { + if err := e.Daemon.AssignPorts(queries); err != nil { fmt.Println("could not set the ports", err.Error()) os.Exit(1) } @@ -61,7 +61,8 @@ var initGenesisCmd = &cobra.Command{ row, err := queries.InsertChain(context.Background(), database.InsertChainParams{ Name: fmt.Sprintf("chain%d", chainid), ChainID: e.ChainID, - BinaryVersion: e.Version, + BinaryVersion: e.BinaryName, + Denom: e.BaseDenom, }) if err != nil { fmt.Println("could not insert chain. ", err.Error()) @@ -74,7 +75,7 @@ var initGenesisCmd = &cobra.Command{ Moniker: e.Moniker, ValidatorKey: e.ValMnemonic, ValidatorKeyName: e.ValKeyName, - BinaryVersion: e.Version, + BinaryVersion: e.BinaryName, ProcessID: 0, IsValidator: 1, IsArchive: 0, diff --git a/cmd/playground/initMultiChain.go b/cmd/playground/initMultiChain.go new file mode 100644 index 0000000..fdd578c --- /dev/null +++ b/cmd/playground/initMultiChain.go @@ -0,0 +1,109 @@ +package playground + +import ( + "context" + dbsql "database/sql" + "fmt" + "os" + "strconv" + "strings" + + "github.com/hanchon/hanchond/playground/cosmosdaemon" + "github.com/hanchon/hanchond/playground/evmos" + "github.com/hanchon/hanchond/playground/filesmanager" + "github.com/hanchon/hanchond/playground/gaia" + "github.com/hanchon/hanchond/playground/sql" + + "github.com/spf13/cobra" +) + +// initMultiChainCmd represents the initMultiChainCmd +var initMultiChainCmd = &cobra.Command{ + Use: "init-multi-chain [amount_of_validators]", + Args: cobra.ExactArgs(1), + Short: "Init the genesis and configurations files for a new chain", + Long: `Set up the validators nodes for the new chain.`, + Run: func(cmd *cobra.Command, args []string) { + client, err := cmd.Flags().GetString("client") + if err != nil { + fmt.Println("client flag was not set") + os.Exit(1) + } + version, err := cmd.Flags().GetString("version") + if err != nil { + fmt.Println("version flag was not set") + os.Exit(1) + } + + amountOfValidators, err := strconv.ParseInt(args[0], 10, 64) + if err != nil { + fmt.Println("invalid amount of validators") + os.Exit(1) + } + + queries := sql.InitDBFromCmd(cmd) + + latestChain, err := queries.GetLatestChain(context.Background()) + chainNumber := 1 + if err == nil { + chainNumber = int(latestChain.ID) + 1 + } else if err != dbsql.ErrNoRows { + fmt.Println("could not get the chains info from db") + os.Exit(1) + } + + nodes := make([]*cosmosdaemon.Daemon, amountOfValidators) + switch strings.ToLower(strings.TrimSpace(client)) { + case "evmos": + chainID := fmt.Sprintf("evmos_9000-%d", chainNumber) + for k := range nodes { + if filesmanager.IsNodeHomeFolderInitialized(int64(chainNumber), int64(k)) { + fmt.Printf("the home folder already exists: %d-%d\n", chainNumber, k) + os.Exit(1) + } + path := filesmanager.GetNodeHomeFolder(int64(chainNumber), int64(k)) + nodes[k] = evmos.NewEvmos( + fmt.Sprintf("moniker-%d-%d", chainNumber, k), + version, + path, + chainID, + fmt.Sprintf("validator-key-%d-%d", chainNumber, k), + "aevmos", + ).Daemon + } + case "gaia": + chainID := fmt.Sprintf("cosmoshub-%d", chainNumber) + for k := range nodes { + if filesmanager.IsNodeHomeFolderInitialized(int64(chainNumber), int64(k)) { + fmt.Printf("the home folder already exists: %d-%d\n", chainNumber, k) + os.Exit(1) + } + path := filesmanager.GetNodeHomeFolder(int64(chainNumber), int64(k)) + nodes[k] = gaia.NewGaia( + fmt.Sprintf("moniker-%d-%d", chainNumber, k), + path, + chainID, + fmt.Sprintf("validator-key-%d-%d", chainNumber, k), + "icsstake", + ).Daemon + } + default: + fmt.Println("invalid client") + os.Exit(1) + } + + chainID, err := cosmosdaemon.InitMultiNodeChain(nodes, queries) + if err != nil { + fmt.Printf("error: %s\n", err.Error()) + os.Exit(1) + } + + fmt.Println("New chain created with id:", chainID) + }, +} + +func init() { + PlaygroundCmd.AddCommand(initMultiChainCmd) + initMultiChainCmd.Flags().String("client", "evmos", "Client that you want to use. Options: evmos, gaia") + initMultiChainCmd.Flags().StringP("version", "v", "local", "Version of the Evmos node that you want to use, defaults to local. Tag names are supported. If selected node is gaia, the flag is ignored.") +} diff --git a/cmd/playground/startMultiChain.go b/cmd/playground/startMultiChain.go new file mode 100644 index 0000000..596067b --- /dev/null +++ b/cmd/playground/startMultiChain.go @@ -0,0 +1,75 @@ +package playground + +import ( + "context" + "fmt" + "os" + "strconv" + "strings" + + "github.com/hanchon/hanchond/playground/database" + "github.com/hanchon/hanchond/playground/evmos" + "github.com/hanchon/hanchond/playground/gaia" + "github.com/hanchon/hanchond/playground/sql" + "github.com/spf13/cobra" +) + +// startMultiChainCmd represents the startMultiChainCmd +var startMultiChainCmd = &cobra.Command{ + Use: "start-multi-chain [chain_id]", + Args: cobra.ExactArgs(1), + Short: "Start all the validators of the chain", + Long: `Start all the required processes to run the chain`, + Run: func(cmd *cobra.Command, args []string) { + queries := sql.InitDBFromCmd(cmd) + + // GetNextChainID + chainNumber, err := strconv.Atoi(strings.TrimSpace(args[0])) + if err != nil { + fmt.Println("invalid chain id:", err.Error()) + os.Exit(1) + } + nodes, err := queries.GetAllChainNodes(context.Background(), int64(chainNumber)) + if err != nil { + fmt.Println("could not find the chain:", err.Error()) + os.Exit(1) + } + + for _, v := range nodes { + version := strings.ToLower(strings.TrimSpace(v.BinaryVersion)) + var pID int + var err error + switch { + case strings.Contains(version, "gaia"): + d := gaia.NewGaia(v.Moniker, v.ConfigFolder, v.ChainID_2, v.ValidatorKeyName, v.Denom) + pID, err = d.Start() + case strings.Contains(version, "evmos"): + d := evmos.NewEvmos(v.Moniker, v.BinaryVersion, v.ConfigFolder, v.ChainID_2, v.ValidatorKeyName, v.Denom) + pID, err = d.Start() + default: + fmt.Println("incorrect binary name") + os.Exit(1) + } + + if err != nil { + fmt.Println("could not start the node:", err.Error()) + os.Exit(1) + } + + fmt.Println("node is running with id:", pID) + err = queries.SetProcessID(context.Background(), database.SetProcessIDParams{ + ProcessID: int64(pID), + IsRunning: 1, + ID: v.ID, + }) + if err != nil { + fmt.Println("could not save the process ID to the db:", err.Error()) + os.Exit(1) + } + } + }, +} + +func init() { + PlaygroundCmd.AddCommand(startMultiChainCmd) +} diff --git a/cmd/playground/startNode.go b/cmd/playground/startNode.go index 6ca5033..7836ff5 100644 --- a/cmd/playground/startNode.go +++ b/cmd/playground/startNode.go @@ -5,16 +5,18 @@ import ( "fmt" "os" "strconv" + "strings" "github.com/hanchon/hanchond/playground/database" "github.com/hanchon/hanchond/playground/evmos" + "github.com/hanchon/hanchond/playground/gaia" "github.com/hanchon/hanchond/playground/sql" "github.com/spf13/cobra" ) // startNodeCmd represents the startNode command var startNodeCmd = &cobra.Command{ - Use: "start-node id", + Use: "start-node [node_id]", Args: cobra.ExactArgs(1), Short: "Starts a node with the given ID", Long: `It will run the node in a subprocess, saving the pid in the database in case it needs to be stoped in the future`, @@ -40,13 +42,33 @@ var startNodeCmd = &cobra.Command{ os.Exit(1) } - e := evmos.NewEvmos(node.BinaryVersion, node.ConfigFolder, chain.ChainID, node.ValidatorKeyName) - pID, err := e.Start(node.Moniker) + var pID int + switch { + case strings.Contains(node.BinaryVersion, "evmos"): + d := evmos.NewEvmos( + node.Moniker, + node.BinaryVersion, + node.ConfigFolder, + chain.ChainID, + node.ValidatorKeyName, + chain.Denom, + ) + pID, err = d.Start() + case strings.Contains(node.BinaryVersion, "gaia"): + d := gaia.NewGaia( + node.Moniker, + node.ConfigFolder, + chain.ChainID, + node.ValidatorKeyName, + node.ValidatorKeyName, + ) + pID, err = d.Start() + } if err != nil { fmt.Println("could not start the node:", err.Error()) os.Exit(1) } - fmt.Println("Evmos is running with id:", pID) + fmt.Println("Node is running with pID:", pID) err = queries.SetProcessID(context.Background(), database.SetProcessIDParams{ ProcessID: int64(pID), diff --git a/docs/pages/hanchond/playground/buildGaiad.mdx b/docs/pages/hanchond/playground/buildGaiad.mdx new file mode 100644 index 0000000..523b29f --- /dev/null +++ b/docs/pages/hanchond/playground/buildGaiad.mdx @@ -0,0 +1,17 @@ +# Build Gaiad + +To run a cosmos-hub node the `gaiad` binary is required. + +:::info +Instead of building from the source code, the process gets the release from the GitHub release page. +::: + +## Download the binary + +The `build-gaiad` command supports the flag `--version` to build a specific tag. It defaults to version `v18.1.0`. It also supports the `--is-darwin` flag to download the binary for MacOS `arm` or Debian `amd64` (it defaults to `true`) + +```sh +hanchond playground build-gaiad +Downloading gaiad from github: v18.1.0 +Gaiad is now available +``` diff --git a/docs/pages/lib/txbuilder/mnemonic.mdx b/docs/pages/lib/txbuilder/mnemonic.mdx new file mode 100644 index 0000000..16705d0 --- /dev/null +++ b/docs/pages/lib/txbuilder/mnemonic.mdx @@ -0,0 +1,43 @@ +# Mnemonic + +A mnemonic can be created using entropy or the `crypto.Rand` package + +## Mnemonic using Entropy + +`NewMnemonicFromEntropy` returns the mnemonic generated by the given entropy. + +``` +mnemonic, err := txbuilder.NewMnemonicFromEntropy(entropy) +``` + +:::info +Entropy must be at least 43 base64 characters to be safe, any string with less length will throw an error +::: + +## Random Mnemonic + +`NewMnemonic` returns a new mnemonic using the `crypto.Rand` package. + +```go +mnemonic, err := txbuilder.NewMnemonic() +``` + +## Addresses + +Using the Mnemonic you can generate the correct address using the following functions: + +:::info +The prefix is only used to encode the final address with `bech32` +::: + +- `MnemonicToCosmosAddressWithPath(mnemonic, hdPath, prefix)` +- `MnemonicToCosmosAddress(mnemonic, prefix)` // It uses the default `hdPath` +- `MnemonicToEthereumAddressWithPath(mnemonic, hdPath, prefix)` +- `MnemonicToEthereumAddress(mnemonic, prefix)` // It uses the default `hdPath` +- `MnemonicToAddressWithPath(mnemonic, hdPath, prefix, algo)` // For internal use + +:::info +CosmosAddress = `coinType 118` and Bitcoin hashing algorithm. + +EthereumAddress = `cointType 60` and Ethereum hashing algorithm. +::: diff --git a/go.mod b/go.mod index e3faf6b..a79513b 100644 --- a/go.mod +++ b/go.mod @@ -4,6 +4,7 @@ go 1.22.2 require ( github.com/cosmos/cosmos-sdk v0.47.12 + github.com/cosmos/go-bip39 v1.0.0 github.com/cosmos/ibc-go/v7 v7.6.0 github.com/ethereum/go-ethereum v1.11.5 github.com/evmos/evmos/v18 v18.1.0 @@ -47,7 +48,6 @@ require ( github.com/confio/ics23/go v0.9.0 // indirect github.com/cosmos/btcutil v1.0.5 // indirect github.com/cosmos/cosmos-proto v1.0.0-beta.5 // indirect - github.com/cosmos/go-bip39 v1.0.0 // indirect github.com/cosmos/gogoproto v1.4.12 // indirect github.com/cosmos/iavl v0.21.0-alpha.1.0.20230904092046-df3db2d96583 // indirect github.com/cosmos/ics23/go v0.10.0 // indirect diff --git a/lib/requester/post.go b/lib/requester/post.go index 0ca7878..0764055 100644 --- a/lib/requester/post.go +++ b/lib/requester/post.go @@ -29,7 +29,7 @@ func (c *Client) SendPostRequestEasyJSON(endpoint string, body []byte, res easyj statusCode := resp.StatusCode() if statusCode != http.StatusOK { - return fmt.Errorf("status code is not ok: " + strconv.Itoa(statusCode)) + return fmt.Errorf("status code is not ok: %s", strconv.Itoa(statusCode)) } respBody := resp.Body() diff --git a/lib/requester/web3.go b/lib/requester/web3.go index e879d29..87f7d10 100644 --- a/lib/requester/web3.go +++ b/lib/requester/web3.go @@ -110,5 +110,5 @@ func (c *Client) BroadcastTx(tx *coretypes.Transaction) (string, error) { return resp.Result, nil } - return "", fmt.Errorf(resp.Error.Message) + return "", fmt.Errorf("%s", resp.Error.Message) } diff --git a/lib/txbuilder/wallet.go b/lib/txbuilder/wallet.go index b707b5a..c7ccc70 100644 --- a/lib/txbuilder/wallet.go +++ b/lib/txbuilder/wallet.go @@ -1,9 +1,15 @@ package txbuilder import ( + "crypto/sha256" "fmt" + "github.com/cosmos/cosmos-sdk/crypto/hd" + "github.com/cosmos/cosmos-sdk/crypto/keyring" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/go-bip39" "github.com/ethereum/go-ethereum/accounts" + evmoshd "github.com/evmos/evmos/v18/crypto/hd" hdwallet "github.com/miguelmota/go-ethereum-hdwallet" ) @@ -31,3 +37,49 @@ func WalletFromMnemonicWithAccountID(mnemonic string, accountID int) (*hdwallet. func WalletFromMnemonic(mnemonic string) (*hdwallet.Wallet, accounts.Account, error) { return WalletFromMnemonicWithPath(mnemonic, "m/44'/60'/0'/0/0") } + +func NewMnemonicFromEntropy(entropy string) (string, error) { + if len(entropy) < 43 { + return "", fmt.Errorf("256-bits is 43 characters in Base-64, and 100 in Base-6. You entered %v, and probably want more", len(entropy)) + } + hashedEntropy := sha256.Sum256([]byte(entropy)) + return bip39.NewMnemonic(hashedEntropy[:]) +} + +func NewMnemonic() (string, error) { + const mnemonicEntropySize = 256 + // read entropy seed straight from crypto.Rand + var err error + entropySeed, err := bip39.NewEntropy(mnemonicEntropySize) + if err != nil { + return "", err + } + return bip39.NewMnemonic(entropySeed) +} + +func MnemonicToAddressWithPath(mnemonic string, hdPath string, prefix string, algo keyring.SignatureAlgo) (string, error) { + derivedPriv, err := algo.Derive()(mnemonic, "", hdPath) + if err != nil { + return "", err + } + privKey := algo.Generate()(derivedPriv) + return sdk.Bech32ifyAddressBytes(prefix, privKey.PubKey().Address().Bytes()) +} + +func MnemonicToCosmosAddressWithPath(mnemonic string, hdPath string, prefix string) (string, error) { + return MnemonicToAddressWithPath(mnemonic, hdPath, prefix, hd.Secp256k1) +} + +func MnemonicToCosmosAddress(mnemonic string, prefix string) (string, error) { + hdPath := hd.CreateHDPath(118, 0, 0).String() + return MnemonicToCosmosAddressWithPath(mnemonic, hdPath, prefix) +} + +func MnemonicToEthereumAddressWithPath(mnemonic string, hdPath string, prefix string) (string, error) { + return MnemonicToAddressWithPath(mnemonic, hdPath, prefix, evmoshd.EthSecp256k1) +} + +func MnemonicToEthereumAddress(mnemonic string, prefix string) (string, error) { + hdPath := hd.CreateHDPath(60, 0, 0).String() + return MnemonicToEthereumAddressWithPath(mnemonic, hdPath, prefix) +} diff --git a/playground/cosmosdaemon/cli_helpers.go b/playground/cosmosdaemon/cli_helpers.go new file mode 100644 index 0000000..697fec7 --- /dev/null +++ b/playground/cosmosdaemon/cli_helpers.go @@ -0,0 +1,148 @@ +package cosmosdaemon + +import ( + "fmt" + "path/filepath" + + "github.com/hanchon/hanchond/playground/database" + "github.com/hanchon/hanchond/playground/filesmanager" +) + +func InitMultiNodeChain(nodes []*Daemon, queries *database.Queries) (int64, error) { + chainID, err := InitFilesAndDB(nodes, queries) + if err != nil { + return 0, err + } + if err := JoinGenesisTransactions(nodes, queries); err != nil { + return 0, err + } + if err := CollectGenTxns(nodes, queries); err != nil { + return 0, err + } + if err := UpdatePeers(nodes, queries); err != nil { + return 0, err + } + return chainID, nil +} + +func InitFilesAndDB(nodes []*Daemon, queries *database.Queries) (int64, error) { + var chainDB database.Chain + var err error + + for k := range nodes { + // Init the config files + if err := nodes[k].InitNode(); err != nil { + return 0, err + } + // Update general parameters in the genesis file + if err := nodes[k].UpdateGenesisFile(); err != nil { + return 0, err + } + if err := nodes[k].UpdateConfigFile(false); err != nil { + return 0, err + } + if err := nodes[k].UpdateAppFile(); err != nil { + return 0, err + } + if err := nodes[k].CreateGenTx(); err != nil { + return 0, err + } + // Assign random and unique ports + if err := nodes[k].AssignPorts(queries); err != nil { + return 0, err + } + // Update the Config Files + if err := nodes[k].UpdateConfigPorts(); err != nil { + return 0, err + } + + // Apply client specific configurations + if err := nodes[k].ExecuteCustomConfig(); err != nil { + return 0, err + } + + if k == 0 { + chainDB, err = nodes[k].SaveChainToDB(queries) + if err != nil { + return 0, err + } + } + nodeID, err := nodes[k].SaveNodeToDB(chainDB, queries) + if err != nil { + return 0, err + } + fmt.Printf("Node added with ID: %d\n", nodeID) + } + return chainDB.ID, nil +} + +func JoinGenesisTransactions(nodes []*Daemon, queries *database.Queries) error { + _ = queries + for k, v := range nodes { + // Node 0 will be the only the one that creates the genesis + if k == 0 { + continue + } + files, err := filepath.Glob(v.HomeDir + "/config/gentx/*.json") + if err != nil { + return err + } + if len(files) == 0 { + return err + } + + if err := filesmanager.CopyFile( + files[0], + nodes[0].HomeDir+"/config/gentx", + ); err != nil { + return err + } + addr, err := v.GetValidatorAddress() + if err != nil { + return err + } + if err := nodes[0].AddGenesisAccount(addr); err != nil { + return err + } + } + return nil +} + +func CollectGenTxns(nodes []*Daemon, queries *database.Queries) error { + _ = queries + if err := nodes[0].CollectGenTxs(); err != nil { + return err + } + if err := nodes[0].ValidateGenesis(); err != nil { + return err + } + return nil +} + +func UpdatePeers(nodes []*Daemon, queries *database.Queries) error { + _ = queries + peers := []string{} + + for k := range nodes { + peerInfo, err := nodes[k].GetPeerInfo() + if err != nil { + return err + } + peers = append(peers, peerInfo) + if k != 0 { + if err := filesmanager.CopyFile( + nodes[0].HomeDir+"/config/genesis.json", + nodes[k].HomeDir+"/config/genesis.json", + ); err != nil { + return err + } + } + } + + for k := range nodes { + if err := nodes[k].AddPersistenPeers(peers); err != nil { + return err + } + } + return nil +} diff --git a/playground/cosmosdaemon/commands.go b/playground/cosmosdaemon/commands.go new file mode 100644 index 0000000..5cafc98 --- /dev/null +++ b/playground/cosmosdaemon/commands.go @@ -0,0 +1,164 @@ +package cosmosdaemon + +import ( + "fmt" + "os/exec" + "strings" + "syscall" + "time" + + "github.com/hanchon/hanchond/playground/filesmanager" +) + +func (d *Daemon) AddGenesisAccount(validatorAddr string) error { + args := []string{ + "add-genesis-account", + validatorAddr, + d.ValidatorInitialSupply + d.BaseDenom, + "--keyring-backend", + d.KeyringBackend, + "--home", + d.HomeDir, + } + if d.SDKVersion == GaiaSDK { + args = append([]string{"genesis"}, args...) + } + + command := exec.Command( //nolint:gosec + d.BinaryPath, + args..., + ) + + out, err := command.CombinedOutput() + if err != nil { + err = fmt.Errorf("error %s: %s", err.Error(), string(out)) + } + return err +} + +func (d *Daemon) ValidatorGenTx() error { + args := []string{ + "gentx", + d.ValKeyName, + d.ValidatorInitialSupply[0:len(d.ValidatorInitialSupply)-4] + d.BaseDenom, + "--gas-prices", + d.BaseFee + d.BaseDenom, + "--chain-id", + d.ChainID, + "--keyring-backend", + d.KeyringBackend, + "--home", + d.HomeDir, + } + + if d.SDKVersion == GaiaSDK { + args = append([]string{"genesis"}, args...) + } + + command := exec.Command( //nolint:gosec + d.BinaryPath, + args..., + ) + out, err := command.CombinedOutput() + if err != nil { + err = fmt.Errorf("error %s: %s", err.Error(), string(out)) + } + return err +} + +func (d *Daemon) CollectGenTxs() error { + args := []string{ + "collect-gentxs", + "--home", + d.HomeDir, + } + + if d.SDKVersion == GaiaSDK { + args = append([]string{"genesis"}, args...) + } + command := exec.Command( //nolint:gosec + d.BinaryPath, + args..., + ) + out, err := command.CombinedOutput() + if err != nil { + err = fmt.Errorf("error %s: %s", err.Error(), string(out)) + } + return err +} + +func (d *Daemon) ValidateGenesis() error { + args := []string{ + "validate-genesis", + "--home", + d.HomeDir, + } + if d.SDKVersion == GaiaSDK { + args = append([]string{"genesis"}, args...) + } + command := exec.Command( //nolint:gosec + d.BinaryPath, + args..., + ) + out, err := command.CombinedOutput() + if err != nil { + err = fmt.Errorf("error %s: %s", err.Error(), string(out)) + } + return err +} + +// Returns bech32 encoded validator addresss +func (d *Daemon) GetValidatorAddress() (string, error) { + command := exec.Command( //nolint:gosec + d.BinaryPath, + "keys", + "show", + "-a", + d.ValKeyName, + "--keyring-backend", + d.KeyringBackend, + "--home", + d.HomeDir, + ) + o, err := command.CombinedOutput() + if err != nil { + err = fmt.Errorf("error %s: %s", err.Error(), string(o)) + return "", err + } + return strings.TrimSpace(string(o)), nil +} + +func (d *Daemon) Start(startCmd string) (int, error) { + fmt.Println(startCmd) + command := exec.Command("bash", "-c", startCmd) + // Deattach the program + command.SysProcAttr = &syscall.SysProcAttr{ + Setpgid: true, + } + err := command.Start() + if err != nil { + return 0, err + } + time.Sleep(2 * time.Second) + id, err := filesmanager.GetChildPID(command.Process.Pid) + if err != nil { + return 0, err + } + return id, nil +} + +func (d *Daemon) GetNodeID() (string, error) { + command := exec.Command( //nolint:gosec + d.BinaryPath, + "tendermint", + "show-node-id", + "--home", + d.HomeDir, + ) + o, err := command.CombinedOutput() + if err != nil { + err = fmt.Errorf("error %s: %s", err.Error(), string(o)) + return "", err + } + return strings.TrimSpace(string(o)), nil +} diff --git a/playground/cosmosdaemon/config_commands.go b/playground/cosmosdaemon/config_commands.go new file mode 100644 index 0000000..ba07bcb --- /dev/null +++ b/playground/cosmosdaemon/config_commands.go @@ -0,0 +1,55 @@ +package cosmosdaemon + +import ( + "fmt" + "os/exec" +) + +func (d *Daemon) ConfigKeyring() error { + command := exec.Command( //nolint:gosec + d.BinaryPath, + "config", + "keyring-backend", + d.KeyringBackend, + "--home", + d.HomeDir, + ) + out, err := command.CombinedOutput() + if err != nil { + err = fmt.Errorf("error %s: %s", err.Error(), string(out)) + } + return err +} + +func (d *Daemon) ConfigChainID() error { + command := exec.Command( //nolint:gosec + d.BinaryPath, + "config", + "chain-id", + d.ChainID, + "--home", + d.HomeDir, + ) + out, err := command.CombinedOutput() + if err != nil { + err = fmt.Errorf("error %s: %s", err.Error(), string(out)) + } + return err +} + +func (d *Daemon) NodeInit() error { + command := exec.Command( //nolint:gosec + d.BinaryPath, + "init", + d.Moniker, + "--chain-id", + d.ChainID, + "--home", + d.HomeDir, + ) + out, err := command.CombinedOutput() + if err != nil { + err = fmt.Errorf("error %s: %s", err.Error(), string(out)) + } + return err +} diff --git a/playground/cosmosdaemon/config_files.go b/playground/cosmosdaemon/config_files.go new file mode 100644 index 0000000..13dce9e --- /dev/null +++ b/playground/cosmosdaemon/config_files.go @@ -0,0 +1,279 @@ +package cosmosdaemon + +import ( + "fmt" + "regexp" + "strings" +) + +func (d *Daemon) UpdateConfigFile(withSlowBlocks bool) error { + // ConfigFile + configFile, err := d.openConfigFile() + if err != nil { + return err + } + + if withSlowBlocks { + configFile = d.enableSlowBlocks(configFile) + } + + configFile = d.allowDuplicateIP(configFile) + + return d.saveConfigFile(configFile) +} + +func (d *Daemon) UpdateAppFile() error { + // Pruning + appFile, err := d.OpenAppFile() + if err != nil { + return err + } + appFile = d.SetPruningInAppFile(true, appFile) + appFile = d.SetMinGasPricesInAppFile(appFile) + return d.SaveAppFile(appFile) +} + +func (d *Daemon) CreateGenTx() error { + validatorAddr, err := d.GetValidatorAddress() + if err != nil { + return err + } + if err := d.AddGenesisAccount(validatorAddr); err != nil { + return err + } + if err := d.ValidatorGenTx(); err != nil { + return err + } + return nil +} + +func (d *Daemon) InitGenesis() error { + if err := d.setBank(); err != nil { + return err + } + + if err := d.CollectGenTxs(); err != nil { + return err + } + + if err := d.ValidateGenesis(); err != nil { + return err + } + + // Make backups of the AppFile and ConfigFile + if err := d.backupConfigFiles(); err != nil { + return err + } + + return nil +} + +func (d *Daemon) allowDuplicateIP(configFile []byte) []byte { + configValues := string(configFile) + // Tendermint Values + configValues = strings.Replace( + configValues, + "allow_duplicate_ip = false", + "allow_duplicate_ip = true", + 1, + ) + return []byte(configValues) +} + +func (d *Daemon) enableSlowBlocks(configFile []byte) []byte { + configValues := string(configFile) + // Tendermint Values + configValues = strings.Replace( + configValues, + "timeout_propose = \"3s\"", + "timeout_propose = \"10s\"", + 1, + ) + configValues = strings.Replace( + configValues, + "timeout_propose_delta = \"500ms\"", + "timeout_propose_delta = \"1s\"", + 1, + ) + configValues = strings.Replace( + configValues, + "timeout_prevote = \"1s\"", + "timeout_prevote = \"5s\"", + 1, + ) + configValues = strings.Replace( + configValues, + "timeout_prevote_delta = \"500ms\"", + "timeout_prevote_delta = \"1s\"", + 1, + ) + configValues = strings.Replace( + configValues, + "timeout_precommit = \"1s\"", + "timeout_precommit = \"5s\"", + 1, + ) + configValues = strings.Replace( + configValues, + "timeout_precommit_delta = \"500ms\"", + "timeout_precommit_delta = \"1s\"", + 1, + ) + configValues = strings.Replace( + configValues, + "timeout_commit = \"3s\"", + "timeout_commit = \"5s\"", + 1, + ) + configValues = strings.Replace( + configValues, + "timeout_broadcast_tx_commit = \"10s\"", + "timeout_broadcast_tx_commit = \"15s\"", + 1, + ) + return []byte(configValues) +} + +func (d *Daemon) EnableWeb3API(config []byte) []byte { + configValues := string(config) + configValues = strings.Replace( + configValues, + `# Enable defines if the JSONRPC server should be enabled. +enable = false`, + "enable = true", + 1, + ) + return []byte(configValues) +} + +func (d *Daemon) SetMinGasPricesInAppFile(config []byte) []byte { + configValues := string(config) + configValues = strings.Replace( + configValues, + "minimum-gas-prices = \"\"", + "minimum-gas-prices = \"0.00001"+d.BaseDenom+"\"", + 1, + ) + return []byte(configValues) +} + +func (d *Daemon) SetPruningInAppFile(pruningEnabled bool, config []byte) []byte { + configValues := string(config) + if pruningEnabled { + configValues = strings.Replace( + configValues, + "pruning = \"default\"", + "pruning = \"custom\"", + 1, + ) + configValues = strings.Replace( + configValues, + "pruning-keep-recent = \"0\"", + "pruning-keep-recent = \"2\"", + 1, + ) + configValues = strings.Replace( + configValues, + "pruning-interval = \"0\"", + "pruning-interval = \"10\"", + 1, + ) + return []byte(configValues) + } + + // NoPrunning + configValues = strings.Replace( + configValues, + "pruning = \"default\"", + "pruning = \"nothing\"", + 1, + ) + configValues = strings.Replace( + configValues, + "pruning = \"custom\"", + "pruning = \"nothing\"", + 1, + ) + configValues = strings.Replace( + configValues, + "pruning = \"everything\"", + "pruning = \"nothing\"", + 1, + ) + return []byte(configValues) +} + +func (d *Daemon) GetPeerInfo() (string, error) { + nodeID, err := d.GetNodeID() + if err != nil { + return "", err + } + + return fmt.Sprintf("%s@127.0.0.1:%d", nodeID, d.Ports.P26656), nil +} + +func (d *Daemon) AddPersistenPeers(peers []string) error { + filtered := []string{} + for k := range peers { + // Exclude ourself from the list + if !strings.Contains(peers[k], fmt.Sprintf("%d", d.Ports.P26656)) { + filtered = append(filtered, peers[k]) + } + } + + configFile, err := d.openConfigFile() + if err != nil { + return err + } + regex := regexp.MustCompile(`persistent_peers\s*=\s*".*"`) + configFile = regex.ReplaceAll( + configFile, + []byte( + fmt.Sprintf( + "persistent_peers = \"%s\"", + strings.Join(filtered, ","), + ), + ), + ) + + if err := d.saveConfigFile(configFile); err != nil { + return err + } + + return nil +} + +func (d *Daemon) UpdateConfigPorts() error { + appFile, err := d.OpenAppFile() + if err != nil { + return err + } + app := string(appFile) + app = strings.Replace(app, "1317", fmt.Sprint(d.Ports.P1317), 1) + app = strings.Replace(app, "8080", fmt.Sprint(d.Ports.P8080), 1) + app = strings.Replace(app, "9090", fmt.Sprint(d.Ports.P9090), 1) + app = strings.Replace(app, "9091", fmt.Sprint(d.Ports.P9091), 1) + app = strings.Replace(app, "8545", fmt.Sprint(d.Ports.P8545), 1) + app = strings.Replace(app, "8546", fmt.Sprint(d.Ports.P8546), 1) + app = strings.Replace(app, "6065", fmt.Sprint(d.Ports.P6065), 1) + if err := d.SaveAppFile([]byte(app)); err != nil { + return err + } + + configFile, err := d.openConfigFile() + if err != nil { + return err + } + + config := string(configFile) + config = strings.Replace(config, "26656", fmt.Sprint(d.Ports.P26656), 1) + config = strings.Replace(config, "26657", fmt.Sprint(d.Ports.P26657), 1) + config = strings.Replace(config, "26658", fmt.Sprint(d.Ports.P26658), 1) + config = strings.Replace(config, "26660", fmt.Sprint(d.Ports.P26660), 1) + config = strings.Replace(config, "6060", fmt.Sprint(d.Ports.P6060), 1) + if err := d.saveConfigFile([]byte(config)); err != nil { + return err + } + + return nil +} diff --git a/playground/cosmosdaemon/daemon.go b/playground/cosmosdaemon/daemon.go new file mode 100644 index 0000000..bb6704c --- /dev/null +++ b/playground/cosmosdaemon/daemon.go @@ -0,0 +1,114 @@ +package cosmosdaemon + +import ( + "github.com/hanchon/hanchond/lib/converter" + "github.com/hanchon/hanchond/lib/txbuilder" +) + +type SignatureAlgo string + +const ( + EthAlgo SignatureAlgo = "eth_secp256k1" + CosmosAlgo SignatureAlgo = "secp256k1" +) + +type SDKVersion string + +const ( + // NOTE: there are some differences in the namespace while interacting with the CLI, like the genesis namespace + GaiaSDK SDKVersion = "gaiaSDK" + EvmosSDK SDKVersion = "evmosSDK" +) + +type Daemon struct { + ValKeyName string + ValMnemonic string + ValWallet string + KeyType SignatureAlgo + Prefix string + + KeyringBackend string + HomeDir string + BinaryName string + SDKVersion SDKVersion + + ChainID string + Moniker string + + BaseDenom string + GasLimit string + BaseFee string + + ValidatorInitialSupply string + + Ports *Ports + + BinaryPath string + + CustomConfig func() error +} + +func NewDameon( + moniker string, + binaryName string, + homeDir string, + chainID string, + keyName string, + algo SignatureAlgo, + denom string, + prefix string, + sdkVersion SDKVersion, +) *Daemon { + mnemonic, _ := txbuilder.NewMnemonic() + wallet := "" + switch algo { + case EthAlgo: + _, temp, _ := txbuilder.WalletFromMnemonic(mnemonic) + wallet, _ = converter.HexToBech32(temp.Address.Hex(), prefix) + case CosmosAlgo: + wallet, _ = txbuilder.MnemonicToCosmosAddress(mnemonic, prefix) + } + + return &Daemon{ + ValKeyName: keyName, + ValMnemonic: mnemonic, + ValWallet: wallet, + Prefix: prefix, + + KeyType: algo, + + KeyringBackend: "test", + HomeDir: homeDir, + BinaryName: binaryName, + SDKVersion: sdkVersion, + + ChainID: chainID, + Moniker: moniker, + + BaseDenom: denom, + + ValidatorInitialSupply: "100000000000000000000000000", + + // Maybe move this to just evmos + GasLimit: "10000000", + BaseFee: "1000000000", + + Ports: nil, + } +} + +func (d *Daemon) SetBinaryPath(path string) { + d.BinaryPath = path +} + +// This is used to change the config files that are specific to a client +func (d *Daemon) SetCustomConfig(configurator func() error) { + d.CustomConfig = configurator +} + +func (d *Daemon) ExecuteCustomConfig() error { + if d.CustomConfig == nil { + return nil + } + return d.CustomConfig() +} diff --git a/playground/cosmosdaemon/database.go b/playground/cosmosdaemon/database.go new file mode 100644 index 0000000..bdb4419 --- /dev/null +++ b/playground/cosmosdaemon/database.go @@ -0,0 +1,57 @@ +package cosmosdaemon + +import ( + "context" + "fmt" + + "github.com/hanchon/hanchond/playground/database" +) + +func (d *Daemon) SaveChainToDB(queries *database.Queries) (database.Chain, error) { + return queries.InsertChain(context.Background(), database.InsertChainParams{ + Name: fmt.Sprintf("chain-%s", d.ChainID), + ChainID: d.ChainID, + BinaryVersion: d.BinaryName, + Denom: d.BaseDenom, + Prefix: d.Prefix, + }) +} + +func (d *Daemon) SaveNodeToDB(chain database.Chain, queries *database.Queries) (int64, error) { + nodeID, err := queries.InsertNode(context.Background(), database.InsertNodeParams{ + ChainID: chain.ID, + ConfigFolder: d.HomeDir, + Moniker: d.Moniker, + ValidatorKey: d.ValMnemonic, + ValidatorKeyName: d.ValKeyName, + BinaryVersion: d.BinaryName, + ProcessID: 0, + IsValidator: 1, + IsArchive: 0, + IsRunning: 0, + }) + if err != nil { + return 0, err + } + + err = queries.InsertPorts(context.Background(), database.InsertPortsParams{ + NodeID: nodeID, + P1317: int64(d.Ports.P1317), + P8080: int64(d.Ports.P8080), + P9090: int64(d.Ports.P9090), + P9091: int64(d.Ports.P9091), + P8545: int64(d.Ports.P8545), + P8546: int64(d.Ports.P8546), + P6065: int64(d.Ports.P6065), + P26658: int64(d.Ports.P26658), + P26657: int64(d.Ports.P26657), + P6060: int64(d.Ports.P6060), + P26656: int64(d.Ports.P26656), + P26660: int64(d.Ports.P26660), + }) + if err != nil { + return 0, err + } + + return nodeID, nil +} diff --git a/playground/cosmosdaemon/genesis.go b/playground/cosmosdaemon/genesis.go new file mode 100644 index 0000000..7473a8b --- /dev/null +++ b/playground/cosmosdaemon/genesis.go @@ -0,0 +1,219 @@ +package cosmosdaemon + +func (d *Daemon) UpdateGenesisFile() error { + genesis, err := d.OpenGenesisFile() + if err != nil { + return err + } + // Update the genesis + d.setStaking(genesis) + d.setEvm(genesis) + d.setInflation(genesis) + d.setCrisis(genesis) + d.setMint(genesis) + d.setProvider(genesis) + d.setConsensusParams(genesis) + d.setFeeMarket(genesis) + d.setGovernance(genesis, true) + + return d.SaveGenesisFile(genesis) +} + +func (d *Daemon) setStaking(genesis map[string]interface{}) { + appState := genesis["app_state"].(map[string]interface{}) + if v, ok := appState["staking"]; ok { + if v, ok := v.(map[string]interface{}); ok { + if v, ok := v["params"]; ok { + if v, ok := v.(map[string]interface{}); ok { + // Base Denom + if _, ok := v["base_denom"]; ok { + appState["staking"].(map[string]interface{})["params"].(map[string]interface{})["bond_denom"] = d.BaseDenom + } + + // Bond denom + if _, ok := v["bond_denom"]; ok { + appState["staking"].(map[string]interface{})["params"].(map[string]interface{})["bond_denom"] = d.BaseDenom + } + } + } + } + } +} + +func (d *Daemon) setEvm(genesis map[string]interface{}) { + appState := genesis["app_state"].(map[string]interface{}) + if v, ok := appState["evm"]; ok { + if v, ok := v.(map[string]interface{}); ok { + if v, ok := v["params"]; ok { + if v, ok := v.(map[string]interface{}); ok { + if _, ok := v["evm_denom"]; ok { + appState["evm"].(map[string]interface{})["params"].(map[string]interface{})["evm_denom"] = d.BaseDenom + } + } + } + } + } +} + +func (d *Daemon) setInflation(genesis map[string]interface{}) { + appState := genesis["app_state"].(map[string]interface{}) + if v, ok := appState["inflation"]; ok { + if v, ok := v.(map[string]interface{}); ok { + if v, ok := v["params"]; ok { + if v, ok := v.(map[string]interface{}); ok { + if _, ok := v["mint_denom"]; ok { + appState["inflation"].(map[string]interface{})["params"].(map[string]interface{})["mint_denom"] = d.BaseDenom + } + } + } + } + } +} + +func (d *Daemon) setCrisis(genesis map[string]interface{}) { + appState := genesis["app_state"].(map[string]interface{}) + if v, ok := appState["crisis"]; ok { + if v, ok := v.(map[string]interface{}); ok { + if v, ok := v["constant_fee"]; ok { + if v, ok := v.(map[string]interface{}); ok { + if _, ok := v["denom"]; ok { + appState["crisis"].(map[string]interface{})["constant_fee"].(map[string]interface{})["denom"] = d.BaseDenom + } + } + } + } + } +} + +func (d *Daemon) setMint(genesis map[string]interface{}) { + appState := genesis["app_state"].(map[string]interface{}) + if v, ok := appState["mint"]; ok { + if v, ok := v.(map[string]interface{}); ok { + if v, ok := v["params"]; ok { + if v, ok := v.(map[string]interface{}); ok { + if _, ok := v["mint_denom"]; ok { + appState["mint"].(map[string]interface{})["params"].(map[string]interface{})["mint_denom"] = d.BaseDenom + } + } + } + } + } +} + +func (d *Daemon) setProvider(genesis map[string]interface{}) { + appState := genesis["app_state"].(map[string]interface{}) + if v, ok := appState["provider"]; ok { + if v, ok := v.(map[string]interface{}); ok { + if v, ok := v["params"]; ok { + if v, ok := v.(map[string]interface{}); ok { + if v, ok := v["consumer_reward_denom_registration_fee"]; ok { + if v, ok := v.(map[string]interface{}); ok { + if _, ok := v["denom"]; ok { + appState["provider"].(map[string]interface{})["params"].(map[string]interface{})["consumer_reward_denom_registration_fee"].(map[string]interface{})["denom"] = d.BaseDenom + } + } + } + } + } + } + } +} + +func (d *Daemon) setConsensusParams(genesis map[string]interface{}) { + consensusParams := genesis["consensus_params"].(map[string]interface{}) + if v, ok := consensusParams["block"]; ok { + if v, ok := v.(map[string]interface{}); ok { + if _, ok := v["max_gas"]; ok { + consensusParams["block"].(map[string]interface{})["max_gas"] = d.GasLimit + } + } + } +} + +func (d *Daemon) setFeeMarket(genesis map[string]interface{}) { + appState := genesis["app_state"].(map[string]interface{}) + if v, ok := appState["feemarket"]; ok { + if v, ok := v.(map[string]interface{}); ok { + if v, ok := v["params"]; ok { + if v, ok := v.(map[string]interface{}); ok { + // Evmos FeeMarket + if _, ok := v["base_fee"]; ok { + appState["feemarket"].(map[string]interface{})["params"].(map[string]interface{})["base_fee"] = d.BaseFee + } + // SDK FeeMarket + if _, ok := v["fee_denom"]; ok { + appState["feemarket"].(map[string]interface{})["params"].(map[string]interface{})["fee_denom"] = d.BaseDenom + } + } + } + } + } +} + +func (d *Daemon) setGovernance(genesis map[string]interface{}, fastProposals bool) { + appState := genesis["app_state"].(map[string]interface{}) + if v, ok := appState["gov"]; ok { + if v, ok := v.(map[string]interface{}); ok { + if v, ok := v["params"]; ok { + if v, ok := v.(map[string]interface{}); ok { + // Proposals + if fastProposals { + if _, ok := v["max_deposit_period"]; ok { + appState["gov"].(map[string]interface{})["params"].(map[string]interface{})["max_deposit_period"] = "10s" + } + + if _, ok := v["voting_period"]; ok { + appState["gov"].(map[string]interface{})["params"].(map[string]interface{})["voting_period"] = "15s" + } + + if _, ok := v["expedited_voting_period"]; ok { + appState["gov"].(map[string]interface{})["params"].(map[string]interface{})["expedited_voting_period"] = "14s" + } + } + + // Expedited_min_deposit + if v, ok := v["expedited_min_deposit"]; ok { + if v, ok := v.([]interface{}); ok { + if len(v) > 0 { + if v, ok := v[0].(map[string]interface{}); ok { + if _, ok := v["denom"]; ok { + appState["gov"].(map[string]interface{})["params"].(map[string]interface{})["expedited_min_deposit"].([]interface{})[0].(map[string]interface{})["denom"] = d.BaseDenom + } + } + } + } + } + + // Min Deposit + if v, ok := v["min_deposit"]; ok { + if v, ok := v.([]interface{}); ok { + if len(v) > 0 { + if v, ok := v[0].(map[string]interface{}); ok { + if _, ok := v["denom"]; ok { + appState["gov"].(map[string]interface{})["params"].(map[string]interface{})["min_deposit"].([]interface{})[0].(map[string]interface{})["denom"] = d.BaseDenom + } + } + } + } + } + + } + } + } + } +} + +func (d *Daemon) setBank() error { + genesis, err := d.OpenGenesisFile() + if err != nil { + return err + } + appState := genesis["app_state"].(map[string]interface{}) + appState["bank"].(map[string]interface{})["supply"].([]interface{})[0].(map[string]interface{})["amount"] = d.ValidatorInitialSupply + + if err := d.SaveGenesisFile(genesis); err != nil { + return err + } + + return nil +} diff --git a/playground/cosmosdaemon/init.go b/playground/cosmosdaemon/init.go new file mode 100644 index 0000000..c9ca9b1 --- /dev/null +++ b/playground/cosmosdaemon/init.go @@ -0,0 +1,20 @@ +package cosmosdaemon + +func (d *Daemon) InitNode() error { + if err := d.ConfigKeyring(); err != nil { + return err + } + + if err := d.ConfigChainID(); err != nil { + return err + } + + if err := d.NodeInit(); err != nil { + return err + } + + if err := d.AddValidatorKey(); err != nil { + return err + } + return nil +} diff --git a/playground/evmos/json_helper.go b/playground/cosmosdaemon/json_helper.go similarity index 96% rename from playground/evmos/json_helper.go rename to playground/cosmosdaemon/json_helper.go index 672bfc5..4f1821f 100644 --- a/playground/evmos/json_helper.go +++ b/playground/cosmosdaemon/json_helper.go @@ -1,4 +1,4 @@ -package evmos +package cosmosdaemon import ( "encoding/json" diff --git a/playground/cosmosdaemon/keys.go b/playground/cosmosdaemon/keys.go new file mode 100644 index 0000000..3993e6f --- /dev/null +++ b/playground/cosmosdaemon/keys.go @@ -0,0 +1,28 @@ +package cosmosdaemon + +import ( + "fmt" + "os/exec" + "strings" +) + +func (d *Daemon) AddValidatorKey() error { + return d.KeyAdd(d.ValKeyName, d.ValMnemonic) +} + +func (d *Daemon) KeyAdd(name string, mnemonic string) error { + cmd := fmt.Sprintf("echo \"%s\" | %s keys add %s --recover --keyring-backend %s --home %s --key-type %s", + mnemonic, + d.BinaryPath, + name, + d.KeyringBackend, + d.HomeDir, + d.KeyType, + ) + command := exec.Command("bash", "-c", cmd) + o, err := command.CombinedOutput() + if strings.Contains(string(o), "duplicated") { + return fmt.Errorf("duplicated address") + } + return err +} diff --git a/playground/cosmosdaemon/path.go b/playground/cosmosdaemon/path.go new file mode 100644 index 0000000..936930b --- /dev/null +++ b/playground/cosmosdaemon/path.go @@ -0,0 +1,62 @@ +package cosmosdaemon + +import ( + "os/exec" + + "github.com/hanchon/hanchond/playground/filesmanager" +) + +func (d *Daemon) getGenesisPath() string { + return d.HomeDir + "/config/genesis.json" +} + +func (d *Daemon) getConfigPath() string { + return d.HomeDir + "/config/config.toml" +} + +func (d *Daemon) getAppPath() string { + return d.HomeDir + "/config/app.toml" +} + +func (d *Daemon) OpenGenesisFile() (map[string]interface{}, error) { + return readJSONFile(d.getGenesisPath()) +} + +func (d *Daemon) SaveGenesisFile(genesis map[string]interface{}) error { + return saveJSONFile(genesis, d.getGenesisPath()) +} + +func (d *Daemon) openConfigFile() ([]byte, error) { + return filesmanager.ReadFile(d.getConfigPath()) +} + +func (d *Daemon) Path() string { + return d.getConfigPath() +} + +func (d *Daemon) saveConfigFile(configFile []byte) error { + return filesmanager.SaveFile(configFile, d.getConfigPath()) +} + +func (d *Daemon) OpenAppFile() ([]byte, error) { + return filesmanager.ReadFile(d.getAppPath()) +} + +func (d *Daemon) SaveAppFile(appFile []byte) error { + return filesmanager.SaveFile(appFile, d.getAppPath()) +} + +func (d *Daemon) backupConfigFiles() error { + cmd := exec.Command("cp", d.getAppPath(), d.getAppPath()+".bkup") //nolint:gosec + _, err := cmd.CombinedOutput() + if err != nil { + return err + } + cmd = exec.Command("cp", d.getConfigPath(), d.getConfigPath()+".bkup") //nolint:gosec + _, err = cmd.CombinedOutput() + if err != nil { + return err + } + + return nil +} diff --git a/playground/cosmosdaemon/ports.go b/playground/cosmosdaemon/ports.go new file mode 100644 index 0000000..78571fd --- /dev/null +++ b/playground/cosmosdaemon/ports.go @@ -0,0 +1,145 @@ +package cosmosdaemon + +import ( + "context" + "fmt" + "log" + "net" + "slices" + + "github.com/hanchon/hanchond/playground/database" +) + +type Ports struct { + // APP Ports + P1317 int + P8080 int + P9090 int + P9091 int + P8545 int + P8546 int + P6065 int + + // Config Ports + P26658 int + P26657 int + P6060 int + P26656 int + P26660 int +} + +func getAvailablePort() (int, error) { + listener, err := net.Listen("tcp", ":0") //nolint:gosec + if err != nil { + return 0, fmt.Errorf("could not find an available port: %w", err) + } + defer listener.Close() + addr := listener.Addr().(*net.TCPAddr) + return addr.Port, nil +} + +func (d *Daemon) AssignPorts(queries *database.Queries) error { + ports, err := newPorts(queries) + if err != nil { + return err + } + d.Ports = ports + return nil +} + +func newPorts(queries *database.Queries) (*Ports, error) { + dbPorts, err := queries.GetAllPorts(context.Background()) + if err != nil { + return nil, err + } + + ports := []int{} + +OUTER: + for len(ports) < 13 { + p, err := getAvailablePort() + if err != nil { + log.Panic("could not get available ports", err.Error()) + } + if p > 60_000 { + continue + } + port := int64(p) + for _, v := range dbPorts { + if v.P1317 == port { + continue OUTER + } + if v.P8080 == port { + continue OUTER + } + if v.P9090 == port { + continue OUTER + } + if v.P9091 == port { + continue OUTER + } + if v.P8545 == port { + continue OUTER + } + if v.P8546 == port { + continue OUTER + } + if v.P6065 == port { + continue OUTER + } + if v.P26658 == port { + continue OUTER + } + if v.P26657 == port { + continue OUTER + } + if v.P6060 == port { + continue OUTER + } + if v.P26656 == port { + continue OUTER + } + if v.P26660 == port { + continue OUTER + } + } + + if !slices.Contains(ports, p) { + ports = append(ports, p) + } else { + log.Println("it returned the same port twice") + } + } + + return &Ports{ + P1317: ports[0], + P8080: ports[1], + P9090: ports[2], + P9091: ports[3], + P8545: ports[4], + P8546: ports[5], + P6065: ports[6], + P26658: ports[7], + P26657: ports[8], + P6060: ports[9], + P26656: ports[10], + P26660: ports[11], + }, nil +} + +func (d *Daemon) RestorePortsFromDB(port database.Port) { + d.Ports = &Ports{ + P1317: int(port.P1317), + P8080: int(port.P8080), + P9090: int(port.P9090), + P9091: int(port.P9091), + P8545: int(port.P8545), + P8546: int(port.P8546), + P6065: int(port.P6065), + P26658: int(port.P26658), + P26657: int(port.P26657), + P6060: int(port.P6060), + P26656: int(port.P26656), + P26660: int(port.P26660), + } +} diff --git a/playground/database/models.go b/playground/database/models.go index 34e40ef..d2aab9c 100644 --- a/playground/database/models.go +++ b/playground/database/models.go @@ -9,6 +9,8 @@ type Chain struct { Name string ChainID string BinaryVersion string + Denom string + Prefix string } type Node struct { @@ -18,6 +20,8 @@ type Node struct { Moniker string ValidatorKey string ValidatorKeyName string + ValidatorWallet string + KeyType string BinaryVersion string ProcessID int64 IsValidator int64 diff --git a/playground/database/query.sql.go b/playground/database/query.sql.go index c2cf708..74528bc 100644 --- a/playground/database/query.sql.go +++ b/playground/database/query.sql.go @@ -9,8 +9,105 @@ import ( "context" ) +const getAllChainNodes = `-- name: GetAllChainNodes :many +SELECT n.id, n.chain_id, config_folder, moniker, validator_key, validator_key_name, validator_wallet, key_type, n.binary_version, process_id, is_validator, is_archive, is_running, p.id, node_id, p1317, p8080, p9090, p9091, p8545, p8546, p6065, p26658, p26657, p6060, p26656, p26660, c.id, name, c.chain_id, c.binary_version, denom, prefix FROM node n join ports p on p.node_id == n.id join chain c on n.chain_id == c.id where n.chain_id = ? +` + +type GetAllChainNodesRow struct { + ID int64 + ChainID int64 + ConfigFolder string + Moniker string + ValidatorKey string + ValidatorKeyName string + ValidatorWallet string + KeyType string + BinaryVersion string + ProcessID int64 + IsValidator int64 + IsArchive int64 + IsRunning int64 + ID_2 int64 + NodeID int64 + P1317 int64 + P8080 int64 + P9090 int64 + P9091 int64 + P8545 int64 + P8546 int64 + P6065 int64 + P26658 int64 + P26657 int64 + P6060 int64 + P26656 int64 + P26660 int64 + ID_3 int64 + Name string + ChainID_2 string + BinaryVersion_2 string + Denom string + Prefix string +} + +func (q *Queries) GetAllChainNodes(ctx context.Context, chainID int64) ([]GetAllChainNodesRow, error) { + rows, err := q.db.QueryContext(ctx, getAllChainNodes, chainID) + if err != nil { + return nil, err + } + defer rows.Close() + var items []GetAllChainNodesRow + for rows.Next() { + var i GetAllChainNodesRow + if err := rows.Scan( + &i.ID, + &i.ChainID, + &i.ConfigFolder, + &i.Moniker, + &i.ValidatorKey, + &i.ValidatorKeyName, + &i.ValidatorWallet, + &i.KeyType, + &i.BinaryVersion, + &i.ProcessID, + &i.IsValidator, + &i.IsArchive, + &i.IsRunning, + &i.ID_2, + &i.NodeID, + &i.P1317, + &i.P8080, + &i.P9090, + &i.P9091, + &i.P8545, + &i.P8546, + &i.P6065, + &i.P26658, + &i.P26657, + &i.P6060, + &i.P26656, + &i.P26660, + &i.ID_3, + &i.Name, + &i.ChainID_2, + &i.BinaryVersion_2, + &i.Denom, + &i.Prefix, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Close(); err != nil { + return nil, err + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + const getAllNodes = `-- name: GetAllNodes :many -SELECT id, chain_id, config_folder, moniker, validator_key, validator_key_name, binary_version, process_id, is_validator, is_archive, is_running FROM node +SELECT id, chain_id, config_folder, moniker, validator_key, validator_key_name, validator_wallet, key_type, binary_version, process_id, is_validator, is_archive, is_running FROM node ` func (q *Queries) GetAllNodes(ctx context.Context) ([]Node, error) { @@ -29,6 +126,8 @@ func (q *Queries) GetAllNodes(ctx context.Context) ([]Node, error) { &i.Moniker, &i.ValidatorKey, &i.ValidatorKeyName, + &i.ValidatorWallet, + &i.KeyType, &i.BinaryVersion, &i.ProcessID, &i.IsValidator, @@ -91,7 +190,7 @@ func (q *Queries) GetAllPorts(ctx context.Context) ([]Port, error) { } const getChain = `-- name: GetChain :one -SELECT id, name, chain_id, binary_version FROM chain where id =? LIMIT 1 +SELECT id, name, chain_id, binary_version, denom, prefix FROM chain where id =? LIMIT 1 ` func (q *Queries) GetChain(ctx context.Context, id int64) (Chain, error) { @@ -102,12 +201,32 @@ func (q *Queries) GetChain(ctx context.Context, id int64) (Chain, error) { &i.Name, &i.ChainID, &i.BinaryVersion, + &i.Denom, + &i.Prefix, + ) + return i, err +} + +const getLatestChain = `-- name: GetLatestChain :one +SELECT id, name, chain_id, binary_version, denom, prefix FROM chain ORDER BY id DESC LIMIT 1 +` + +func (q *Queries) GetLatestChain(ctx context.Context) (Chain, error) { + row := q.db.QueryRowContext(ctx, getLatestChain) + var i Chain + err := row.Scan( + &i.ID, + &i.Name, + &i.ChainID, + &i.BinaryVersion, + &i.Denom, + &i.Prefix, ) return i, err } const getNode = `-- name: GetNode :one -SELECT id, chain_id, config_folder, moniker, validator_key, validator_key_name, binary_version, process_id, is_validator, is_archive, is_running FROM node where id =? LIMIT 1 +SELECT id, chain_id, config_folder, moniker, validator_key, validator_key_name, validator_wallet, key_type, binary_version, process_id, is_validator, is_archive, is_running FROM node where id =? LIMIT 1 ` func (q *Queries) GetNode(ctx context.Context, id int64) (Node, error) { @@ -120,6 +239,8 @@ func (q *Queries) GetNode(ctx context.Context, id int64) (Node, error) { &i.Moniker, &i.ValidatorKey, &i.ValidatorKeyName, + &i.ValidatorWallet, + &i.KeyType, &i.BinaryVersion, &i.ProcessID, &i.IsValidator, @@ -181,27 +302,37 @@ func (q *Queries) InitRelayer(ctx context.Context) error { const insertChain = `-- name: InsertChain :one INSERT INTO chain( - name, chain_id, binary_version + name, chain_id, binary_version, denom, prefix ) VALUES ( - ?,?,? + ?,?,?,?,? ) -RETURNING id, name, chain_id, binary_version +RETURNING id, name, chain_id, binary_version, denom, prefix ` type InsertChainParams struct { Name string ChainID string BinaryVersion string + Denom string + Prefix string } func (q *Queries) InsertChain(ctx context.Context, arg InsertChainParams) (Chain, error) { - row := q.db.QueryRowContext(ctx, insertChain, arg.Name, arg.ChainID, arg.BinaryVersion) + row := q.db.QueryRowContext(ctx, insertChain, + arg.Name, + arg.ChainID, + arg.BinaryVersion, + arg.Denom, + arg.Prefix, + ) var i Chain err := row.Scan( &i.ID, &i.Name, &i.ChainID, &i.BinaryVersion, + &i.Denom, + &i.Prefix, ) return i, err } @@ -213,13 +344,15 @@ INSERT INTO node( moniker, validator_key, validator_key_name, + validator_wallet, + key_type, binary_version, process_id, is_validator, is_archive, is_running ) VALUES ( - ?,?,?,?,?,?,?,?,?,? + ?,?,?,?,?,?,?,?,?,?,?,? ) RETURNING ID ` @@ -230,6 +363,8 @@ type InsertNodeParams struct { Moniker string ValidatorKey string ValidatorKeyName string + ValidatorWallet string + KeyType string BinaryVersion string ProcessID int64 IsValidator int64 @@ -244,6 +379,8 @@ func (q *Queries) InsertNode(ctx context.Context, arg InsertNodeParams) (int64, arg.Moniker, arg.ValidatorKey, arg.ValidatorKeyName, + arg.ValidatorWallet, + arg.KeyType, arg.BinaryVersion, arg.ProcessID, arg.IsValidator, diff --git a/playground/evmos/command.go b/playground/evmos/command.go new file mode 100644 index 0000000..57b3725 --- /dev/null +++ b/playground/evmos/command.go @@ -0,0 +1,16 @@ +package evmos + +import ( + "fmt" +) + +func (e *Evmos) Start() (int, error) { + logFile := e.HomeDir + "/run.log" + cmd := fmt.Sprintf("%s start --chain-id %s --home %s --json-rpc.api eth,txpool,personal,net,debug,web3 --api.enable --grpc.enable >> %s 2>&1", + e.BinaryPath, + e.ChainID, + e.HomeDir, + logFile, + ) + return e.Daemon.Start(cmd) +} diff --git a/playground/evmos/bank.go b/playground/evmos/command_bank.go similarity index 78% rename from playground/evmos/bank.go rename to playground/evmos/command_bank.go index bc1cebe..c754903 100644 --- a/playground/evmos/bank.go +++ b/playground/evmos/command_bank.go @@ -3,13 +3,11 @@ package evmos import ( "fmt" "os/exec" - - "github.com/hanchon/hanchond/playground/filesmanager" ) func (e *Evmos) CheckBalance(wallet string) (string, error) { command := exec.Command( //nolint:gosec - filesmanager.GetEvmosdPath(e.Version), + e.BinaryPath, "q", "bank", "balances", diff --git a/playground/evmos/gov.go b/playground/evmos/command_gov.go similarity index 97% rename from playground/evmos/gov.go rename to playground/evmos/command_gov.go index ca400e8..501aa92 100644 --- a/playground/evmos/gov.go +++ b/playground/evmos/command_gov.go @@ -62,7 +62,7 @@ func (e *Evmos) CreateSTRv1Proposal(params STRv1) (string, error) { } command := exec.Command( //nolint:gosec - filesmanager.GetEvmosdPath(e.Version), + e.BinaryPath, "tx", "gov", "submit-proposal", @@ -90,7 +90,7 @@ func (e *Evmos) CreateSTRv1Proposal(params STRv1) (string, error) { func (e *Evmos) VoteOnProposal(proposalID string, option string) (string, error) { command := exec.Command( //nolint:gosec - filesmanager.GetEvmosdPath(e.Version), + e.BinaryPath, "tx", "gov", "vote", @@ -165,7 +165,7 @@ func (e *Evmos) VoteOnAllTheProposals(option string) ([]string, error) { func (e *Evmos) CreateUpgradeProposal(versionName string, upgradeHeight string) (string, error) { command := exec.Command( //nolint:gosec - filesmanager.GetEvmosdPath(e.Version), + e.BinaryPath, "tx", "gov", "submit-legacy-proposal", @@ -250,7 +250,7 @@ func (e *Evmos) CreateRateLimitProposal(params RateLimitParams) (string, error) } command := exec.Command( //nolint:gosec - filesmanager.GetEvmosdPath(e.Version), + e.BinaryPath, "tx", "gov", "submit-proposal", diff --git a/playground/evmos/ibc.go b/playground/evmos/command_ibc.go similarity index 85% rename from playground/evmos/ibc.go rename to playground/evmos/command_ibc.go index 32363a9..4b31e4b 100644 --- a/playground/evmos/ibc.go +++ b/playground/evmos/command_ibc.go @@ -3,13 +3,11 @@ package evmos import ( "fmt" "os/exec" - - "github.com/hanchon/hanchond/playground/filesmanager" ) func (e *Evmos) SendIBC(port, channel, receiver, amount string) (string, error) { command := exec.Command( //nolint:gosec - filesmanager.GetEvmosdPath(e.Version), + e.BinaryPath, "tx", "ibc-transfer", "transfer", diff --git a/playground/evmos/tx.go b/playground/evmos/command_tx.go similarity index 78% rename from playground/evmos/tx.go rename to playground/evmos/command_tx.go index 3046cf8..f999ea7 100644 --- a/playground/evmos/tx.go +++ b/playground/evmos/command_tx.go @@ -3,13 +3,11 @@ package evmos import ( "fmt" "os/exec" - - "github.com/hanchon/hanchond/playground/filesmanager" ) func (e *Evmos) GetTransaction(txhash string) (string, error) { command := exec.Command( //nolint:gosec - filesmanager.GetEvmosdPath(e.Version), + e.BinaryPath, "q", "tx", "--type=hash", diff --git a/playground/evmos/config.go b/playground/evmos/config.go deleted file mode 100644 index 0ae078e..0000000 --- a/playground/evmos/config.go +++ /dev/null @@ -1,63 +0,0 @@ -package evmos - -import ( - "os/exec" - "strings" - - "github.com/hanchon/hanchond/playground/filesmanager" -) - -func (e *Evmos) ConfigKeyring() error { - command := exec.Command( //nolint:gosec - filesmanager.GetEvmosdPath(e.Version), - "config", - "keyring-backend", - e.KeyringBackend, - "--home", - e.HomeDir, - ) - _, err := command.CombinedOutput() - return err -} - -func (e *Evmos) ConfigChainID() error { - command := exec.Command( //nolint:gosec - filesmanager.GetEvmosdPath(e.Version), - "config", - "chain-id", - e.ChainID, - "--home", - e.HomeDir, - ) - _, err := command.CombinedOutput() - return err -} - -func (e *Evmos) EvmosdInit() error { - command := exec.Command( //nolint:gosec - filesmanager.GetEvmosdPath(e.Version), - "init", - e.Moniker, - "--chain-id", - e.ChainID, - "--home", - e.HomeDir, - ) - _, err := command.CombinedOutput() - return err -} - -func (e *Evmos) EvmosdShowNodeID() (string, error) { - command := exec.Command( //nolint:gosec - filesmanager.GetEvmosdPath(e.Version), - "tendermint", - "show-node-id", - "--home", - e.HomeDir, - ) - o, err := command.CombinedOutput() - if err != nil { - return "", err - } - return strings.TrimSpace(string(o)), nil -} diff --git a/playground/evmos/config_files.go b/playground/evmos/config_files.go new file mode 100644 index 0000000..67a5689 --- /dev/null +++ b/playground/evmos/config_files.go @@ -0,0 +1,27 @@ +package evmos + +import ( + "strings" +) + +func (e *Evmos) UpdateAppFile() error { + // Pruning + appFile, err := e.Daemon.OpenAppFile() + if err != nil { + return err + } + appFile = e.enableWeb3API(appFile) + return e.Daemon.SaveAppFile(appFile) +} + +func (e *Evmos) enableWeb3API(config []byte) []byte { + configValues := string(config) + configValues = strings.Replace( + configValues, + `# Enable defines if the JSONRPC server should be enabled. +enable = false`, + "enable = true", + 1, + ) + return []byte(configValues) +} diff --git a/playground/evmos/db.go b/playground/evmos/db.go index 63a7817..b3bf1f7 100644 --- a/playground/evmos/db.go +++ b/playground/evmos/db.go @@ -42,7 +42,7 @@ func GetNodeFromDB(queries *database.Queries, nodeID string) *NodeFromDB { func NewEvmosFromDB(queries *database.Queries, nodeID string) *Evmos { data := GetNodeFromDB(queries, nodeID) - e := NewEvmos(data.Node.BinaryVersion, data.Node.ConfigFolder, data.Chain.ChainID, data.Node.ValidatorKeyName) + e := NewEvmos(data.Node.Moniker, data.Node.BinaryVersion, data.Node.ConfigFolder, data.Chain.ChainID, data.Node.ValidatorKeyName, data.Chain.Denom) e.RestorePortsFromDB(data.Ports) return e } diff --git a/playground/evmos/evmosd.go b/playground/evmos/evmosd.go index 6564be5..3a99749 100644 --- a/playground/evmos/evmosd.go +++ b/playground/evmos/evmosd.go @@ -2,165 +2,35 @@ package evmos import ( "fmt" - "os/exec" "strings" - "syscall" - "time" + "github.com/hanchon/hanchond/playground/cosmosdaemon" "github.com/hanchon/hanchond/playground/filesmanager" ) type Evmos struct { - ValKeyName string - ValMnemonic string - KeyringBackend string - HomeDir string - Version string - - ChainID string - Moniker string - - BaseDenom string - GasLimit string - BaseFee string - - ValidatorInitialSupply string - - Ports Ports -} - -func NewEvmos(version string, homeDir string, chainID string, keyName string) *Evmos { - // TODO: remove hardcoded values - return &Evmos{ - ValKeyName: keyName, - ValMnemonic: "gesture inject test cycle original hollow east ridge hen combine junk child bacon zero hope comfort vacuum milk pitch cage oppose unhappy lunar seat", - KeyringBackend: "test", - HomeDir: homeDir, - Version: version, - - ChainID: chainID, - Moniker: "moniker", - - BaseDenom: "aevmos", - GasLimit: "10000000", - BaseFee: "1000000000", - - ValidatorInitialSupply: "100000000000000000000000000", - - Ports: *NewPorts(), - } -} - -func (e *Evmos) KeyAdd(name string, mnemonic string) error { - cmd := fmt.Sprintf("echo \"%s\" | %s keys add %s --recover --keyring-backend %s --algo eth_secp256k1 --home %s", - mnemonic, - filesmanager.GetEvmosdPath(e.Version), - name, - e.KeyringBackend, - e.HomeDir, - ) - command := exec.Command("bash", "-c", cmd) - o, err := command.CombinedOutput() - if strings.Contains(string(o), "duplicated") { - return fmt.Errorf("duplicated address") - } - return err -} - -func (e *Evmos) AddValidatorKey() error { - return e.KeyAdd(e.ValKeyName, e.ValMnemonic) + *cosmosdaemon.Daemon } -func (e *Evmos) AddGenesisAccount() error { - validatorAddr, err := e.GetValidatorAddress() - if err != nil { - return err +func NewEvmos(moniker string, version string, homeDir string, chainID string, keyName string, denom string) *Evmos { + daemonName := version + if !strings.Contains(version, "evmosd") { + daemonName = fmt.Sprintf("evmosd%s", version) } - command := exec.Command( //nolint:gosec - filesmanager.GetEvmosdPath(e.Version), - "add-genesis-account", - validatorAddr, - e.ValidatorInitialSupply+"aevmos", - "--keyring-backend", - e.KeyringBackend, - "--home", - e.HomeDir, - ) - _, err = command.CombinedOutput() - return err -} - -func (e *Evmos) ValidatorGenTx() error { - command := exec.Command( //nolint:gosec - filesmanager.GetEvmosdPath(e.Version), - "gentx", - e.ValKeyName, - e.ValidatorInitialSupply[0:len(e.ValidatorInitialSupply)-4]+"aevmos", - "--gas-prices", - e.BaseFee+"aevmos", - "--chain-id", - e.ChainID, - "--keyring-backend", - e.KeyringBackend, - "--home", - e.HomeDir, - ) - _, err := command.CombinedOutput() - return err -} - -func (e *Evmos) CollectGenTxs() error { - command := exec.Command( //nolint:gosec - filesmanager.GetEvmosdPath(e.Version), - "collect-gentxs", - "--home", - e.HomeDir, - ) - _, err := command.CombinedOutput() - return err -} - -func (e *Evmos) ValidateGenesis() error { - command := exec.Command( //nolint:gosec - filesmanager.GetEvmosdPath(e.Version), - "validate-genesis", - "--home", - e.HomeDir, - ) - _, err := command.CombinedOutput() - return err -} - -func (e *Evmos) Start(name string) (int, error) { - // TODO: do I need the name here? - _ = name - logFile := e.HomeDir + "/run.log" - - cmd := fmt.Sprintf("%s start --chain-id %s --home %s --json-rpc.api eth,txpool,personal,net,debug,web3 --api.enable --grpc.enable >> %s 2>&1", - filesmanager.GetEvmosdPath(e.Version), - e.ChainID, - e.HomeDir, - logFile, - ) - command := exec.Command("bash", "-c", cmd) - - // Deattach the program - command.SysProcAttr = &syscall.SysProcAttr{ - Setpgid: true, - } - - err := command.Start() - if err != nil { - return 0, err + e := &Evmos{ + Daemon: cosmosdaemon.NewDameon( + moniker, + daemonName, + homeDir, + chainID, + keyName, + cosmosdaemon.EthAlgo, + denom, + "evmos", + cosmosdaemon.EvmosSDK, + ), } - - // Let evmosd start - time.Sleep(2 * time.Second) - - id, err := filesmanager.GetChildPID(command.Process.Pid) - if err != nil { - return 0, err - } - - return id, nil + e.SetBinaryPath(filesmanager.GetDaemondPath(daemonName)) + e.SetCustomConfig(e.UpdateAppFile) + return e } diff --git a/playground/evmos/genesis.go b/playground/evmos/genesis.go deleted file mode 100644 index b355b0a..0000000 --- a/playground/evmos/genesis.go +++ /dev/null @@ -1,303 +0,0 @@ -package evmos - -import ( - "fmt" - "strings" -) - -func (e *Evmos) initNode() error { - if err := e.ConfigKeyring(); err != nil { - return err - } - - if err := e.ConfigChainID(); err != nil { - return err - } - - if err := e.EvmosdInit(); err != nil { - return err - } - return nil -} - -func (e *Evmos) InitArchiveNode(origin *Evmos) error { - if err := e.initNode(); err != nil { - return err - } - // Copy genesis file - if err := e.copyGenesisFile(origin.getGenesisPath()); err != nil { - return err - } - - // Pruning - appFile, err := e.openAppFile() - if err != nil { - return err - } - appFile = e.SetPruningInAppFile(false, appFile) - // Enable API - appFile = e.EnableWeb3API(appFile) - if err := e.saveAppFile(appFile); err != nil { - return err - } - - // Update ports - if err := e.SetPorts(); err != nil { - return err - } - - // Add persistent peer - nodeID, err := origin.EvmosdShowNodeID() - if err != nil { - return err - } - - configFile, err := e.openConfigFile() - if err != nil { - return err - } - - configFile = []byte(strings.Replace( - string(configFile), - "persistent_peers = \"\"", - fmt.Sprintf("persistent_peers = \"%s@localhost:%d\"", nodeID, origin.Ports.P26656), - 1, - )) - - if err := e.saveConfigFile(configFile); err != nil { - return err - } - - return nil -} - -func (e *Evmos) InitGenesis() error { - if err := e.initNode(); err != nil { - return err - } - - if err := e.AddValidatorKey(); err != nil { - return err - } - - genesis, err := e.openGenesisFile() - if err != nil { - return err - } - // Update the genesis - e.UpdateGenesisDenom(genesis) - e.SetGenesisGasLimit(genesis) - e.SetGenesisBaseFee(genesis) - e.SetGenesisFastProposals(genesis) - - if err := e.saveGenesisFile(genesis); err != nil { - return err - } - - // ConfigFile - configFile, err := e.openConfigFile() - if err != nil { - return err - } - // TODO: renable this if we want slow blocks - // configFile = e.UpdateConfigFile(configFile) - if err := e.saveConfigFile(configFile); err != nil { - return err - } - - // Pruning - appFile, err := e.openAppFile() - if err != nil { - return err - } - - // NOTE: Running with pruning `nothing` to query old blocks data while debugging - appFile = e.SetPruningInAppFile(false, appFile) - // Enable API - appFile = e.EnableWeb3API(appFile) - if err := e.saveAppFile(appFile); err != nil { - return err - } - - if err := e.AddGenesisAccount(); err != nil { - return err - } - - if err := e.UpdateTotalSupply(); err != nil { - return err - } - - if err := e.ValidatorGenTx(); err != nil { - return err - } - - if err := e.CollectGenTxs(); err != nil { - return err - } - - if err := e.ValidateGenesis(); err != nil { - return err - } - - // Make backups of the AppFile and ConfigFile - if err := e.backupConfigFiles(); err != nil { - return err - } - - return nil -} - -func (e *Evmos) UpdateGenesisDenom(genesis map[string]interface{}) { - appState := genesis["app_state"].(map[string]interface{}) - // Staking - appState["staking"].(map[string]interface{})["params"].(map[string]interface{})["bond_denom"] = e.BaseDenom - // Gov - appState["gov"].(map[string]interface{})["params"].(map[string]interface{})["min_deposit"].([]interface{})[0].(map[string]interface{})["denom"] = e.BaseDenom - // Evm - appState["evm"].(map[string]interface{})["params"].(map[string]interface{})["evm_denom"] = e.BaseDenom - // Inflation - appState["inflation"].(map[string]interface{})["params"].(map[string]interface{})["mint_denom"] = e.BaseDenom -} - -func (e *Evmos) SetGenesisGasLimit(genesis map[string]interface{}) { - consensusParams := genesis["consensus_params"].(map[string]interface{}) - consensusParams["block"].(map[string]interface{})["max_gas"] = e.GasLimit -} - -func (e *Evmos) SetGenesisBaseFee(genesis map[string]interface{}) { - appState := genesis["app_state"].(map[string]interface{}) - appState["feemarket"].(map[string]interface{})["params"].(map[string]interface{})["base_fee"] = e.BaseFee -} - -func (e *Evmos) SetGenesisFastProposals(genesis map[string]interface{}) { - appState := genesis["app_state"].(map[string]interface{}) - appState["gov"].(map[string]interface{})["params"].(map[string]interface{})["max_deposit_period"] = "10s" - appState["gov"].(map[string]interface{})["params"].(map[string]interface{})["voting_period"] = "15s" -} - -func (e *Evmos) UpdateConfigFile(config []byte) []byte { - configValues := string(config) - // Tendermint Values - configValues = strings.Replace( - configValues, - "timeout_propose = \"3s\"", - "timeout_propose = \"10s\"", - 1, - ) - configValues = strings.Replace( - configValues, - "timeout_propose_delta = \"500ms\"", - "timeout_propose_delta = \"1s\"", - 1, - ) - configValues = strings.Replace( - configValues, - "timeout_prevote = \"1s\"", - "timeout_prevote = \"5s\"", - 1, - ) - configValues = strings.Replace( - configValues, - "timeout_prevote_delta = \"500ms\"", - "timeout_prevote_delta = \"1s\"", - 1, - ) - configValues = strings.Replace( - configValues, - "timeout_precommit = \"1s\"", - "timeout_precommit = \"5s\"", - 1, - ) - configValues = strings.Replace( - configValues, - "timeout_precommit_delta = \"500ms\"", - "timeout_precommit_delta = \"1s\"", - 1, - ) - configValues = strings.Replace( - configValues, - "timeout_commit = \"3s\"", - "timeout_commit = \"5s\"", - 1, - ) - configValues = strings.Replace( - configValues, - "timeout_broadcast_tx_commit = \"10s\"", - "timeout_broadcast_tx_commit = \"15s\"", - 1, - ) - return []byte(configValues) -} - -func (e *Evmos) EnableWeb3API(config []byte) []byte { - configValues := string(config) - configValues = strings.Replace( - configValues, - `# Enable defines if the JSONRPC server should be enabled. -enable = false`, - "enable = true", - 1, - ) - return []byte(configValues) -} - -func (e *Evmos) SetPruningInAppFile(pruningEnabled bool, config []byte) []byte { - configValues := string(config) - if pruningEnabled { - configValues = strings.Replace( - configValues, - "pruning = \"default\"", - "pruning = \"custom\"", - 1, - ) - configValues = strings.Replace( - configValues, - "pruning-keep-recent = \"0\"", - "pruning-keep-recent = \"2\"", - 1, - ) - configValues = strings.Replace( - configValues, - "pruning-interval = \"0\"", - "pruning-interval = \"10\"", - 1, - ) - return []byte(configValues) - } - - // NoPrunning - configValues = strings.Replace( - configValues, - "pruning = \"default\"", - "pruning = \"nothing\"", - 1, - ) - configValues = strings.Replace( - configValues, - "pruning = \"custom\"", - "pruning = \"nothing\"", - 1, - ) - configValues = strings.Replace( - configValues, - "pruning = \"everything\"", - "pruning = \"nothing\"", - 1, - ) - return []byte(configValues) -} - -func (e *Evmos) UpdateTotalSupply() error { - genesis, err := e.openGenesisFile() - if err != nil { - return err - } - appState := genesis["app_state"].(map[string]interface{}) - appState["bank"].(map[string]interface{})["supply"].([]interface{})[0].(map[string]interface{})["amount"] = e.ValidatorInitialSupply - - if err := e.saveGenesisFile(genesis); err != nil { - return err - } - - return nil -} diff --git a/playground/evmos/getters.go b/playground/evmos/getters.go deleted file mode 100644 index 1ee95cc..0000000 --- a/playground/evmos/getters.go +++ /dev/null @@ -1,28 +0,0 @@ -package evmos - -import ( - "os/exec" - "strings" - - "github.com/hanchon/hanchond/playground/filesmanager" -) - -// Returns bech32 encoded validator addresss -func (e *Evmos) GetValidatorAddress() (string, error) { - command := exec.Command( //nolint:gosec - filesmanager.GetEvmosdPath(e.Version), - "keys", - "show", - "-a", - e.ValKeyName, - "--keyring-backend", - e.KeyringBackend, - "--home", - e.HomeDir, - ) - o, err := command.CombinedOutput() - if err != nil { - return "", err - } - return strings.TrimSpace(string(o)), nil -} diff --git a/playground/evmos/path.go b/playground/evmos/path.go deleted file mode 100644 index 95c1135..0000000 --- a/playground/evmos/path.go +++ /dev/null @@ -1,67 +0,0 @@ -package evmos - -import ( - "os/exec" - - "github.com/hanchon/hanchond/playground/filesmanager" -) - -func (e *Evmos) getGenesisPath() string { - return e.HomeDir + "/config/genesis.json" -} - -func (e *Evmos) getConfigPath() string { - return e.HomeDir + "/config/config.toml" -} - -func (e *Evmos) getAppPath() string { - return e.HomeDir + "/config/app.toml" -} - -func (e *Evmos) openGenesisFile() (map[string]interface{}, error) { - return readJSONFile(e.getGenesisPath()) -} - -func (e *Evmos) saveGenesisFile(genesis map[string]interface{}) error { - return saveJSONFile(genesis, e.getGenesisPath()) -} - -func (e *Evmos) openConfigFile() ([]byte, error) { - return filesmanager.ReadFile(e.getConfigPath()) -} - -func (e *Evmos) saveConfigFile(configFile []byte) error { - return filesmanager.SaveFile(configFile, e.getConfigPath()) -} - -func (e *Evmos) openAppFile() ([]byte, error) { - return filesmanager.ReadFile(e.getAppPath()) -} - -func (e *Evmos) saveAppFile(appFile []byte) error { - return filesmanager.SaveFile(appFile, e.getAppPath()) -} - -func (e *Evmos) backupConfigFiles() error { - cmd := exec.Command("cp", e.getAppPath(), e.getAppPath()+".bkup") //nolint:gosec - _, err := cmd.CombinedOutput() - if err != nil { - return err - } - cmd = exec.Command("cp", e.getConfigPath(), e.getConfigPath()+".bkup") //nolint:gosec - _, err = cmd.CombinedOutput() - if err != nil { - return err - } - - return nil -} - -func (e *Evmos) copyGenesisFile(genesisPath string) error { - cmd := exec.Command("cp", genesisPath, e.getGenesisPath()) //nolint:gosec - _, err := cmd.CombinedOutput() - if err != nil { - return err - } - return nil -} diff --git a/playground/evmos/ports.go b/playground/evmos/ports.go deleted file mode 100644 index a80fb2d..0000000 --- a/playground/evmos/ports.go +++ /dev/null @@ -1,192 +0,0 @@ -package evmos - -import ( - "fmt" - "log" - "net" - "slices" - "strings" - - "github.com/hanchon/hanchond/playground/database" -) - -type Ports struct { - // APP Ports - P1317 int - P8080 int - P9090 int - P9091 int - P8545 int - P8546 int - P6065 int - - // Config Ports - P26658 int - P26657 int - P6060 int - P26656 int - P26660 int -} - -func NewPorts() *Ports { - ports := []int{} - for len(ports) < 13 { - p, err := getAvailablePort() - if err != nil { - log.Panic("could not get available ports", err.Error()) - } - // TODO: compare the ports with the rest of the config files - if !slices.Contains(ports, p) { - ports = append(ports, p) - } else { - log.Panic("it returned the same port twice") - } - } - - return &Ports{ - P1317: ports[0], - P8080: ports[1], - P9090: ports[2], - P9091: ports[3], - P8545: ports[4], - P8546: ports[5], - P6065: ports[6], - P26658: ports[7], - P26657: ports[8], - P6060: ports[9], - P26656: ports[10], - P26660: ports[11], - } -} - -func NewPorts2(dbPorts []database.Port) *Ports { - ports := []int{} - for len(ports) < 13 { - p, err := getAvailablePort() - if err != nil { - log.Panic("could not get available ports", err.Error()) - } - port := int64(p) - for _, v := range dbPorts { - if v.P1317 == port { - continue - } - if v.P8080 == port { - continue - } - if v.P9090 == port { - continue - } - if v.P9091 == port { - continue - } - if v.P8545 == port { - continue - } - if v.P8546 == port { - continue - } - if v.P6065 == port { - continue - } - if v.P26658 == port { - continue - } - if v.P26657 == port { - continue - } - if v.P6060 == port { - continue - } - if v.P26656 == port { - continue - } - if v.P26660 == port { - continue - } - - } - if !slices.Contains(ports, p) { - ports = append(ports, p) - } else { - log.Println("it returned the same port twice") - } - } - - return &Ports{ - P1317: ports[0], - P8080: ports[1], - P9090: ports[2], - P9091: ports[3], - P8545: ports[4], - P8546: ports[5], - P6065: ports[6], - P26658: ports[7], - P26657: ports[8], - P6060: ports[9], - P26656: ports[10], - P26660: ports[11], - } -} - -func (e *Evmos) RestorePortsFromDB(port database.Port) { - e.Ports = Ports{ - P1317: int(port.P1317), - P8080: int(port.P8080), - P9090: int(port.P9090), - P9091: int(port.P9091), - P8545: int(port.P8545), - P8546: int(port.P8546), - P6065: int(port.P6065), - P26658: int(port.P26658), - P26657: int(port.P26657), - P6060: int(port.P6060), - P26656: int(port.P26656), - P26660: int(port.P26660), - } -} - -func getAvailablePort() (int, error) { - listener, err := net.Listen("tcp", ":0") //nolint:gosec - if err != nil { - return 0, fmt.Errorf("could not find an available port: %w", err) - } - defer listener.Close() - addr := listener.Addr().(*net.TCPAddr) - return addr.Port, nil -} - -func (e *Evmos) SetPorts() error { - appFile, err := e.openAppFile() - if err != nil { - return err - } - app := string(appFile) - app = strings.Replace(app, "1317", fmt.Sprint(e.Ports.P1317), 1) - app = strings.Replace(app, "8080", fmt.Sprint(e.Ports.P8080), 1) - app = strings.Replace(app, "9090", fmt.Sprint(e.Ports.P9090), 1) - app = strings.Replace(app, "9091", fmt.Sprint(e.Ports.P9091), 1) - app = strings.Replace(app, "8545", fmt.Sprint(e.Ports.P8545), 1) - app = strings.Replace(app, "8546", fmt.Sprint(e.Ports.P8546), 1) - app = strings.Replace(app, "6065", fmt.Sprint(e.Ports.P6065), 1) - if err := e.saveAppFile([]byte(app)); err != nil { - return err - } - - configFile, err := e.openConfigFile() - if err != nil { - return err - } - - config := string(configFile) - config = strings.Replace(config, "26656", fmt.Sprint(e.Ports.P26656), 1) - config = strings.Replace(config, "26657", fmt.Sprint(e.Ports.P26657), 1) - config = strings.Replace(config, "26658", fmt.Sprint(e.Ports.P26658), 1) - config = strings.Replace(config, "26660", fmt.Sprint(e.Ports.P26660), 1) - config = strings.Replace(config, "6060", fmt.Sprint(e.Ports.P6060), 1) - if err := e.saveConfigFile([]byte(config)); err != nil { - return err - } - - return nil -} diff --git a/playground/filesmanager/build_evmos.go b/playground/filesmanager/build_evmos.go index 24a54ba..32fbf5a 100644 --- a/playground/filesmanager/build_evmos.go +++ b/playground/filesmanager/build_evmos.go @@ -1,6 +1,7 @@ package filesmanager import ( + "fmt" "os" "os/exec" ) @@ -28,13 +29,22 @@ func BuildEvmos(path string) error { func SaveEvmosBuiltVersion(version string) error { // Ensure the path exists _ = CreateBuildsDir() - return CopyFile(GetBranchFolder(version)+"/build/evmosd", GetEvmosdPath(version)) + return MoveFile(GetBranchFolder(version)+"/build/evmosd", GetEvmosdPath(version)) } -func CopyFile(origin string, destination string) error { +func MoveFile(origin string, destination string) error { return os.Rename(origin, destination) } +func CopyFile(origin string, destination string) error { + cmd := exec.Command("cp", origin, destination) + if out, err := cmd.CombinedOutput(); err != nil { + err = fmt.Errorf("error %s: %s", err.Error(), string(out)) + return err + } + return nil +} + // NOTE: This requires that the version was already cloned func BuildHermes(version string) error { // Change directory to the cloned repository diff --git a/playground/filesmanager/path.go b/playground/filesmanager/path.go index 2c74430..a4f0a35 100644 --- a/playground/filesmanager/path.go +++ b/playground/filesmanager/path.go @@ -40,25 +40,22 @@ func GetDataFolder() string { return fmt.Sprintf("%s/data", GetBaseDir()) } -func getNodeHomePath(chainID int64) string { - return fmt.Sprintf("%s/%d", GetDataFolder(), chainID) +func getNodeHomePath(chainID int64, nodeID int64) string { + return fmt.Sprintf("%s/%d-%d", GetDataFolder(), chainID, nodeID) } -func GetNodeHomeFolder(chainID int64) string { +func GetNodeHomeFolder(chainID, nodeID int64) string { if _, err := os.Stat(GetDataFolder()); os.IsNotExist(err) { if err := os.Mkdir(GetDataFolder(), os.ModePerm); err != nil { // We panic here because if we can not create the folder we should inmediately stop panic(err) } } - return getNodeHomePath(chainID) + return getNodeHomePath(chainID, nodeID) } -func IsNodeHomeFolderInitialized(chainID int64) bool { - if _, err := os.Stat(getNodeHomePath(chainID)); os.IsNotExist(err) { - return false - } - return true +func IsNodeHomeFolderInitialized(chainID int64, nodeID int64) bool { + return DoesFileExist(getNodeHomePath(chainID, nodeID)) } func GetBaseDir() string { @@ -66,7 +63,7 @@ func GetBaseDir() string { } func GetBuildsDir() string { - return baseDir + "/evmos_build" + return baseDir + "/builds" } func GetTempDir() string { @@ -81,6 +78,14 @@ func GetEvmosdPath(version string) string { return GetBuildsDir() + "/evmosd" + version } +func GetDaemondPath(binaryName string) string { + return GetBuildsDir() + "/" + binaryName +} + +func GetGaiadPath() string { + return GetBuildsDir() + "/gaiad" +} + func DoesEvmosdPathExist(version string) bool { return DoesFileExist(GetBuildsDir() + "/evmosd" + version) } diff --git a/playground/gaia/command.go b/playground/gaia/command.go new file mode 100644 index 0000000..14cfc5a --- /dev/null +++ b/playground/gaia/command.go @@ -0,0 +1,15 @@ +package gaia + +import ( + "fmt" +) + +func (g *Gaia) Start() (int, error) { + logFile := g.HomeDir + "/run.log" + cmd := fmt.Sprintf("%s start --home %s --api.enable --grpc.enable >> %s 2>&1", + g.BinaryPath, + g.HomeDir, + logFile, + ) + return g.Daemon.Start(cmd) +} diff --git a/playground/gaia/gaia.go b/playground/gaia/gaia.go new file mode 100644 index 0000000..0f3ff01 --- /dev/null +++ b/playground/gaia/gaia.go @@ -0,0 +1,19 @@ +package gaia + +import ( + "github.com/hanchon/hanchond/playground/cosmosdaemon" + "github.com/hanchon/hanchond/playground/filesmanager" +) + +type Gaia struct { + *cosmosdaemon.Daemon +} + +func NewGaia(moniker string, homeDir string, chainID string, keyName string, denom string) *Gaia { + g := &Gaia{ + Daemon: cosmosdaemon.NewDameon(moniker, "gaia", homeDir, chainID, keyName, cosmosdaemon.CosmosAlgo, denom, "cosmos", cosmosdaemon.GaiaSDK), + } + g.SetBinaryPath(filesmanager.GetGaiadPath()) + g.SetCustomConfig(g.UpdateGenesisFile) + return g +} diff --git a/playground/gaia/genesis.go b/playground/gaia/genesis.go new file mode 100644 index 0000000..e9a1783 --- /dev/null +++ b/playground/gaia/genesis.go @@ -0,0 +1,30 @@ +package gaia + +func (g *Gaia) UpdateGenesisFile() error { + // Gaia extra config + genesis, err := g.Daemon.OpenGenesisFile() + if err != nil { + return err + } + + g.setUnbondingTime(genesis) + // Maybe we need to update the `genesis_time` but I am not sure why + + return g.Daemon.SaveGenesisFile(genesis) +} + +func (g *Gaia) setUnbondingTime(genesis map[string]interface{}) { + appState := genesis["app_state"].(map[string]interface{}) + if v, ok := appState["staking"]; ok { + if v, ok := v.(map[string]interface{}); ok { + if v, ok := v["params"]; ok { + if v, ok := v.(map[string]interface{}); ok { + // Base Denom + if _, ok := v["unbonding_time"]; ok { + appState["staking"].(map[string]interface{})["params"].(map[string]interface{})["unbonding_time"] = "1814400s" + } + } + } + } + } +} diff --git a/playground/gaia/get_gaiad.go b/playground/gaia/get_gaiad.go new file mode 100644 index 0000000..109e112 --- /dev/null +++ b/playground/gaia/get_gaiad.go @@ -0,0 +1,21 @@ +package gaia + +import ( + "fmt" + "os/exec" + + "github.com/hanchon/hanchond/playground/filesmanager" +) + +func GetGaiadBinary(isDarwin bool, version string) error { + darwinURL := "https://github.com/cosmos/gaia/releases/download/" + version + "/gaiad-" + version + "-darwin-arm64" + url := " https://github.com/cosmos/gaia/releases/download/" + version + "/gaiad-" + version + "-linux-amd64" + if isDarwin { + url = darwinURL + } + path := filesmanager.GetGaiadPath() + cmdString := fmt.Sprintf("wget %s -O %s && chmod +x %s", url, path, path) + command := exec.Command("bash", "-c", cmdString) + _, err := command.CombinedOutput() + return err +} diff --git a/playground/hermes/binary.go b/playground/hermes/binary.go index 986cc79..c5ba5c7 100644 --- a/playground/hermes/binary.go +++ b/playground/hermes/binary.go @@ -13,12 +13,17 @@ func (h *Hermes) GetHermesBinary() string { return filesmanager.GetHermesBinary() } -func (h *Hermes) AddRelayerKey(chainID string, mnemonic string) error { +func (h *Hermes) AddRelayerKey(chainID string, mnemonic string, isEthWallet bool) error { + hdPath := " " + if isEthWallet { + hdPath = " --hd-path \"m/44'/60'/0'/0/0\" " + } cmd := fmt.Sprintf( - "echo \"%s\" | %s --config %s keys add --hd-path \"m/44'/60'/0'/0/0\" --mnemonic-file /dev/stdin --chain %s >> %s 2>&1", + "echo \"%s\" | %s --config %s keys add %s --mnemonic-file /dev/stdin --chain %s >> %s 2>&1", mnemonic, h.GetHermesBinary(), h.GetConfigFile(), + hdPath, chainID, filesmanager.GetHermesPath()+"/logs_keys"+chainID, ) @@ -37,7 +42,10 @@ func (h *Hermes) CreateChannel(firstChainID, secondChainID string) error { filesmanager.GetHermesPath()+"/logs_channel"+firstChainID+secondChainID, ) command := exec.Command("bash", "-c", cmd) - _, err := command.CombinedOutput() + out, err := command.CombinedOutput() + if err != nil { + err = fmt.Errorf("error %s: %s", err.Error(), string(out)) + } return err } diff --git a/playground/hermes/config.go b/playground/hermes/config.go index 32165a0..4b31d4c 100644 --- a/playground/hermes/config.go +++ b/playground/hermes/config.go @@ -13,6 +13,53 @@ func (h *Hermes) GetConfigFile() string { return filesmanager.GetHermesPath() + "/config.toml" } +func (h *Hermes) AddCosmosChain(chainID string, p26657 int64, p9090 int64, keyname string, mnemonic string, accountPrefix string, denom string) error { + values := fmt.Sprintf(` +[[chains]] +id = '%s' +rpc_addr = 'http://127.0.0.1:%d' +grpc_addr = 'http://127.0.0.1:%d' +event_source = { mode = 'pull', interval = '1s' } +rpc_timeout = '10s' +account_prefix = '%s' +key_store_folder = '%s' +key_name = '%s' +store_prefix = 'ibc' +default_gas = 5000000 +max_gas = 10000000 +gas_price = { price = 100, denom = '%s' } +gas_multiplier = 5 +max_msg_num = 20 +max_tx_size = 209715 +clock_drift = '20s' +max_block_time = '10s' +trust_threshold = { numerator = '1', denominator = '3' } +`, chainID, p26657, p9090, accountPrefix, filesmanager.GetHermesPath()+"/keyring"+chainID, keyname, denom) + + configFile, err := filesmanager.ReadFile(h.GetConfigFile()) + if err != nil { + return err + } + + configFileString := string(configFile) + // If the chain was already included in the config file, do nothing + if strings.Contains(configFileString, chainID) { + // Maybe update ports if needed + return nil + } + configFileString += values + err = filesmanager.SaveFile([]byte(configFileString), h.GetConfigFile()) + if err != nil { + panic(err) + } + + err = h.AddRelayerKey(chainID, mnemonic, false) + if err != nil { + panic(err) + } + return nil +} + func (h *Hermes) AddEvmosChain(chainID string, p26657 int64, p9090 int64, keyname string, mnemonic string) error { values := fmt.Sprintf(` [[chains]] @@ -52,7 +99,7 @@ address_type = { derivation = 'ethermint', proto_type = { pk_type = '/ethermint. panic(err) } - err = h.AddRelayerKey(chainID, mnemonic) + err = h.AddRelayerKey(chainID, mnemonic, true) if err != nil { panic(err) } diff --git a/playground/sql/query.sql b/playground/sql/query.sql index b50cb9b..daf0ec4 100644 --- a/playground/sql/query.sql +++ b/playground/sql/query.sql @@ -1,8 +1,8 @@ -- name: InsertChain :one INSERT INTO chain( - name, chain_id, binary_version + name, chain_id, binary_version, denom, prefix ) VALUES ( - ?,?,? + ?,?,?,?,? ) RETURNING *; @@ -13,13 +13,15 @@ INSERT INTO node( moniker, validator_key, validator_key_name, + validator_wallet, + key_type, binary_version, process_id, is_validator, is_archive, is_running ) VALUES ( - ?,?,?,?,?,?,?,?,?,? + ?,?,?,?,?,?,?,?,?,?,?,? ) RETURNING ID; @@ -69,9 +71,15 @@ SELECT * FROM ports; -- name: GetChain :one SELECT * FROM chain where id =? LIMIT 1; +-- name: GetLatestChain :one +SELECT * FROM chain ORDER BY id DESC LIMIT 1; + -- name: GetAllNodes :many SELECT * FROM node; +-- name: GetAllChainNodes :many +SELECT * FROM node n join ports p on p.node_id == n.id join chain c on n.chain_id == c.id where n.chain_id = ?; + -- name: InitRelayer :exec INSERT INTO relayer( process_id, is_running diff --git a/playground/sql/schema.sql b/playground/sql/schema.sql index 2c00250..9b550f1 100644 --- a/playground/sql/schema.sql +++ b/playground/sql/schema.sql @@ -2,7 +2,9 @@ CREATE TABLE IF NOT EXISTS chain( id INTEGER NOT NULL PRIMARY KEY, name TEXT NOT NULL, chain_id TEXT NOT NULL, - binary_version TEXT NOT NULL + binary_version TEXT NOT NULL, + denom TEXT NOT NULL, + prefix TEXT NOT NULL ); CREATE TABLE IF NOT EXISTS node( @@ -12,6 +14,8 @@ CREATE TABLE IF NOT EXISTS node( moniker TEXT NOT NULL, validator_key TEXT NOT NULL, validator_key_name TEXT NOT NULL, + validator_wallet TEXT NOT NULL, + key_type TEXT NOT NULL, binary_version TEXT NOT NULL, process_id INTEGER NOT NULL, diff --git a/vocs.config.ts b/vocs.config.ts index 608d14b..cfe89f4 100644 --- a/vocs.config.ts +++ b/vocs.config.ts @@ -44,12 +44,25 @@ export default defineConfig({ ], }, { - text: "Evmos", + text: "Binaries", items: [ { - text: "Build Evmos", + text: "Build Evmosd", link: "/hanchond/playground/buildEvmos", }, + { + text: "Build Gaiad", + link: "/hanchond/playground/buildGaiad", + }, + { + text: "Build Hermes", + link: "/hanchond/playground/buildHermes", + }, + ], + }, + { + text: "Evmos", + items: [ { text: "Init Genesis", link: "/hanchond/playground/initGenesis", @@ -75,10 +88,6 @@ export default defineConfig({ { text: "Hermes", items: [ - { - text: "Build Hermes", - link: "/hanchond/playground/buildHermes", - }, { text: "Add a New Channel", link: "/hanchond/playground/hermesAddChannel", @@ -238,6 +247,10 @@ export default defineConfig({ text: "Wallet", link: "/lib/txbuilder/wallet", }, + { + text: "Mnemonic", + link: "/lib/txbuilder/mnemonic", + }, { text: "Contract", link: "/lib/txbuilder/contract",