diff --git a/.gitignore b/.gitignore
index ebf6f371..b5400224 100644
--- a/.gitignore
+++ b/.gitignore
@@ -19,6 +19,7 @@
.glide/
vendor
build
+bin
tools/bin/*
docs/_build
docs/tutorial
@@ -28,6 +29,7 @@ dist
tools-stamp
proto-tools-stamp
golangci-lint
+keyring_test_cosmos
# Testing
coverage.txt
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 4eb12d62..11f2da66 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -65,6 +65,7 @@ Ref: https://keepachangelog.com/en/1.0.0/
### Features
+* (build) [\#378](https://github.com/ChainSafe/ethermint/pull/378) Create multi-node, local, automated testnet setup with `make localnet-start`.
* (rpc) [\#330](https://github.com/ChainSafe/ethermint/issues/330) Implement `PublicFilterAPI`'s `EventSystem` which subscribes to Tendermint events upon `Filter` creation.
* (rpc) [\#231](https://github.com/ChainSafe/ethermint/issues/231) Implement `NewBlockFilter` in rpc/filters.go which instantiates a polling block filter
* Polls for new blocks via `BlockNumber` rpc call; if block number changes, it requests the new block via `GetBlockByNumber` rpc call and adds it to its internal list of blocks
diff --git a/Makefile b/Makefile
index b8a81ef6..5c33e787 100644
--- a/Makefile
+++ b/Makefile
@@ -21,27 +21,32 @@ ETHERMINT_DAEMON_BINARY = emintd
ETHERMINT_CLI_BINARY = emintcli
GO_MOD=GO111MODULE=on
BINDIR ?= $(GOPATH)/bin
+BUILDDIR ?= $(CURDIR)/build
SIMAPP = github.com/cosmos/ethermint/app
RUNSIM = $(BINDIR)/runsim
all: tools verify install
-#######################
-### Build / Install ###
-#######################
+###############################################################################
+### Build ###
+###############################################################################
-build:
-ifeq ($(OS),Windows_NT)
- ${GO_MOD} go build $(BUILD_FLAGS) -o build/$(ETHERMINT_DAEMON_BINARY).exe ./cmd/emintd
- ${GO_MOD} go build $(BUILD_FLAGS) -o build/$(ETHERMINT_CLI_BINARY).exe ./cmd/emintcli
-else
- ${GO_MOD} go build $(BUILD_FLAGS) -o build/$(ETHERMINT_DAEMON_BINARY) ./cmd/emintd/
- ${GO_MOD} go build $(BUILD_FLAGS) -o build/$(ETHERMINT_CLI_BINARY) ./cmd/emintcli/
-endif
+build: go.sum
+ go build -mod=readonly ./...
+
+build-ethermint: go.sum
+ mkdir -p $(BUILDDIR)
+ go build -mod=readonly $(BUILD_FLAGS) -o $(BUILDDIR) ./cmd/$(ETHERMINT_DAEMON_BINARY)
+ go build -mod=readonly $(BUILD_FLAGS) -o $(BUILDDIR) ./cmd/$(ETHERMINT_CLI_BINARY)
+
+build-ethermint-linux: go.sum
+ GOOS=linux GOARCH=amd64 CGO_ENABLED=1 $(MAKE) build-ethermint
+
+.PHONY: build build-ethermint build-ethermint-linux
install:
- ${GO_MOD} go install $(BUILD_FLAGS) ./cmd/emintd
- ${GO_MOD} go install $(BUILD_FLAGS) ./cmd/emintcli
+ ${GO_MOD} go install $(BUILD_FLAGS) ./cmd/$(ETHERMINT_DAEMON_BINARY)
+ ${GO_MOD} go install $(BUILD_FLAGS) ./cmd/$(ETHERMINT_CLI_BINARY)
clean:
@rm -rf ./build ./vendor
@@ -55,31 +60,26 @@ verify:
@echo "--> Verifying dependencies have not been modified"
${GO_MOD} go mod verify
+docker:
+ docker build -t ${DOCKER_IMAGE}:${DOCKER_TAG} .
+ docker tag ${DOCKER_IMAGE}:${DOCKER_TAG} ${DOCKER_IMAGE}:latest
+ docker tag ${DOCKER_IMAGE}:${DOCKER_TAG} ${DOCKER_IMAGE}:${COMMIT_HASH}
+ # update old container
+ docker rm ethermint
+ # create a new container from the latest image
+ docker create --name ethermint -t -i cosmos/ethermint:latest ethermint
+ # move the binaries to the ./build directory
+ mkdir -p ./build/
+ docker cp ethermint:/usr/bin/emintd ./build/ ; \
+ docker cp ethermint:/usr/bin/emintcli ./build/
-############################
-### Tools / Dependencies ###
-############################
-
-##########################################################
-### TODO: Move tool depedencies to a separate makefile ###
-##########################################################
-
-GOLINT = github.com/tendermint/lint/golint
-GOCILINT = github.com/golangci/golangci-lint/cmd/golangci-lint
-UNCONVERT = github.com/mdempsky/unconvert
-INEFFASSIGN = github.com/gordonklaus/ineffassign
-MISSPELL = github.com/client9/misspell/cmd/misspell
-ERRCHECK = github.com/kisielk/errcheck
-UNPARAM = mvdan.cc/unparam
-
-GOLINT_CHECK := $(shell command -v golint 2> /dev/null)
-GOCILINT_CHECK := $(shell command -v golangci-lint 2> /dev/null)
-UNCONVERT_CHECK := $(shell command -v unconvert 2> /dev/null)
-INEFFASSIGN_CHECK := $(shell command -v ineffassign 2> /dev/null)
-MISSPELL_CHECK := $(shell command -v misspell 2> /dev/null)
-ERRCHECK_CHECK := $(shell command -v errcheck 2> /dev/null)
-UNPARAM_CHECK := $(shell command -v unparam 2> /dev/null)
+docker-localnet:
+ # build the image
+ docker build -f ./networks/local/ethermintnode/Dockerfile . -t emintd/node
+###############################################################################
+### Tools & Dependencies ###
+###############################################################################
# Install the runsim binary with a temporary workaround of entering an outside
# directory as the "go get" command ignores the -mod option and will polute the
@@ -91,53 +91,10 @@ $(RUNSIM):
@(cd /tmp && go get github.com/cosmos/tools/cmd/runsim@v1.0.0)
tools: $(RUNSIM)
-ifdef GOLINT_CHECK
- @echo "Golint is already installed. Run 'make update-tools' to update."
-else
- @echo "--> Installing golint"
- ${GO_MOD} go get -v $(GOLINT)
-endif
-ifdef GOCILINT_CHECK
- @echo "golangci-lint is already installed. Run 'make update-tools' to update."
-else
- @echo "--> Installing golangci-lint"
- ${GO_MOD} go get -v $(GOCILINT)
-endif
-ifdef UNCONVERT_CHECK
- @echo "Unconvert is already installed. Run 'make update-tools' to update."
-else
- @echo "--> Installing unconvert"
- ${GO_MOD} go get -v $(UNCONVERT)
-endif
-ifdef INEFFASSIGN_CHECK
- @echo "Ineffassign is already installed. Run 'make update-tools' to update."
-else
- @echo "--> Installing ineffassign"
- ${GO_MOD} go get -v $(INEFFASSIGN)
-endif
-ifdef MISSPELL_CHECK
- @echo "misspell is already installed. Run 'make update-tools' to update."
-else
- @echo "--> Installing misspell"
- ${GO_MOD} go get -v $(MISSPELL)
-endif
-ifdef ERRCHECK_CHECK
- @echo "errcheck is already installed. Run 'make update-tools' to update."
-else
- @echo "--> Installing errcheck"
- ${GO_MOD} go get -v $(ERRCHECK)
-endif
-ifdef UNPARAM_CHECK
- @echo "unparam is already installed. Run 'make update-tools' to update."
-else
- @echo "--> Installing unparam"
- ${GO_MOD} go get -v $(UNPARAM)
-endif
-
-#######################
-### Testing / Misc. ###
-#######################
+###############################################################################
+### Tests & Simulation ###
+###############################################################################
test: test-unit
@@ -155,15 +112,40 @@ test-import:
test-rpc:
./scripts/integration-test-all.sh -q 1 -z 1 -s 2
-godocs:
- @echo "--> Wait a few seconds and visit http://localhost:6060/pkg/github.com/cosmos/ethermint"
- godoc -http=:6060
+test-sim-nondeterminism:
+ @echo "Running non-determinism test..."
+ @go test -mod=readonly $(SIMAPP) -run TestAppStateDeterminism -Enabled=true \
+ -NumBlocks=100 -BlockSize=200 -Commit=true -Period=0 -v -timeout 24h
-docker:
- docker build -t ${DOCKER_IMAGE}:${DOCKER_TAG} .
- docker tag ${DOCKER_IMAGE}:${DOCKER_TAG} ${DOCKER_IMAGE}:latest
- docker tag ${DOCKER_IMAGE}:${DOCKER_TAG} ${DOCKER_IMAGE}:${COMMIT_HASH}
+test-sim-custom-genesis-fast:
+ @echo "Running custom genesis simulation..."
+ @echo "By default, ${HOME}/.$(ETHERMINT_DAEMON_BINARY)/config/genesis.json will be used."
+ @go test -mod=readonly $(SIMAPP) -run TestFullAppSimulation -Genesis=${HOME}/.$(ETHERMINT_DAEMON_BINARY)/config/genesis.json \
+ -Enabled=true -NumBlocks=100 -BlockSize=200 -Commit=true -Seed=99 -Period=5 -v -timeout 24h
+test-sim-import-export: runsim
+ @echo "Running Ethermint import/export simulation. This may take several minutes..."
+ @$(BINDIR)/runsim -Jobs=4 -SimAppPkg=$(SIMAPP) 25 5 TestAppImportExport
+
+test-sim-after-import: runsim
+ @echo "Running Ethermint simulation-after-import. This may take several minutes..."
+ @$(BINDIR)/runsim -Jobs=4 -SimAppPkg=$(SIMAPP) 25 5 TestAppSimulationAfterImport
+
+test-sim-custom-genesis-multi-seed: runsim
+ @echo "Running multi-seed custom genesis simulation..."
+ @echo "By default, ${HOME}/.$(ETHERMINT_DAEMON_BINARY)/config/genesis.json will be used."
+ @$(BINDIR)/runsim -Jobs=4 -Genesis=${HOME}/.$(ETHERMINT_DAEMON_BINARY)/config/genesis.json 400 5 TestFullAppSimulation
+
+test-sim-multi-seed-long: runsim
+ @echo "Running multi-seed application simulation. This may take awhile!"
+ @$(BINDIR)/runsim -Jobs=4 -SimAppPkg=$(SIMAPP) 500 50 TestFullAppSimulation
+
+test-sim-multi-seed-short: runsim
+ @echo "Running multi-seed application simulation. This may take awhile!"
+ @$(BINDIR)/runsim -Jobs=4 -SimAppPkg=$(SIMAPP) 50 10 TestFullAppSimulation
+
+.PHONY: runsim test-sim-nondeterminism test-sim-custom-genesis-fast test-sim-fast sim-import-export \
+ test-sim-simulation-after-import test-sim-custom-genesis-multi-seed test-sim-multi-seed
.PHONY: build install update-tools tools godocs clean format lint \
test-cli test-race test-unit test test-import
@@ -256,50 +238,10 @@ proto-update-deps:
.PHONY: proto-all proto-gen proto-lint proto-check-breaking proto-update-deps
-#######################
-### Simulations ###
-#######################
-test-sim-nondeterminism:
- @echo "Running non-determinism test..."
- @go test -mod=readonly $(SIMAPP) -run TestAppStateDeterminism -Enabled=true \
- -NumBlocks=100 -BlockSize=200 -Commit=true -Period=0 -v -timeout 24h
-
-test-sim-custom-genesis-fast:
- @echo "Running custom genesis simulation..."
- @echo "By default, ${HOME}/.emintd/config/genesis.json will be used."
- @go test -mod=readonly $(SIMAPP) -run TestFullAppSimulation -Genesis=${HOME}/.emintd/config/genesis.json \
- -Enabled=true -NumBlocks=100 -BlockSize=200 -Commit=true -Seed=99 -Period=5 -v -timeout 24h
-
-test-sim-import-export: runsim
- @echo "Running Ethermint import/export simulation. This may take several minutes..."
- @$(BINDIR)/runsim -Jobs=4 -SimAppPkg=$(SIMAPP) 25 5 TestAppImportExport
-
-test-sim-after-import: runsim
- @echo "Running Ethermint simulation-after-import. This may take several minutes..."
- @$(BINDIR)/runsim -Jobs=4 -SimAppPkg=$(SIMAPP) 25 5 TestAppSimulationAfterImport
-
-test-sim-custom-genesis-multi-seed: runsim
- @echo "Running multi-seed custom genesis simulation..."
- @echo "By default, ${HOME}/.emintd/config/genesis.json will be used."
- @$(BINDIR)/runsim -Jobs=4 -Genesis=${HOME}/.emintd/config/genesis.json 400 5 TestFullAppSimulation
-
-test-sim-multi-seed-long: runsim
- @echo "Running multi-seed application simulation. This may take awhile!"
- @$(BINDIR)/runsim -Jobs=4 -SimAppPkg=$(SIMAPP) 500 50 TestFullAppSimulation
-
-test-sim-multi-seed-short: runsim
- @echo "Running multi-seed application simulation. This may take awhile!"
- @$(BINDIR)/runsim -Jobs=4 -SimAppPkg=$(SIMAPP) 50 10 TestFullAppSimulation
-
-.PHONY: runsim test-sim-nondeterminism test-sim-custom-genesis-fast test-sim-fast sim-import-export \
- test-sim-simulation-after-import test-sim-custom-genesis-multi-seed test-sim-multi-seed \
-
-
-
-#######################
-### Documentation ###
-#######################
+###############################################################################
+### Documentation ###
+###############################################################################
# Start docs site at localhost:8080
docs-serve:
@@ -311,4 +253,28 @@ docs-serve:
docs-build:
@cd docs && \
npm install && \
- npm run build
\ No newline at end of file
+ npm run build
+
+godocs:
+ @echo "--> Wait a few seconds and visit http://localhost:6060/pkg/github.com/cosmos/ethermint"
+ godoc -http=:6060
+
+###############################################################################
+### Localnet ###
+###############################################################################
+
+build-docker-local-ethermint:
+ @$(MAKE) -C networks/local
+
+# Run a 4-node testnet locally
+localnet-start: localnet-stop
+ mkdir -p ./build/
+ @$(MAKE) docker-localnet
+
+ if ! [ -f build/node0/$(ETHERMINT_DAEMON_BINARY)/config/genesis.json ]; then docker run --rm -v $(CURDIR)/build:/ethermint:Z emintd/node "emintd testnet --v 4 -o /ethermint --starting-ip-address 192.168.10.2 --keyring-backend=test"; fi
+ docker-compose up -d
+
+localnet-stop:
+ docker-compose down
+
+.PHONY: build-docker-local-ethermint localnet-start localnet-stop
diff --git a/README.md b/README.md
index 051b3515..c2a8da72 100644
--- a/README.md
+++ b/README.md
@@ -20,8 +20,8 @@ parent:
-
-
+
+
diff --git a/cmd/emintcli/keys.go b/cmd/emintcli/keys.go
index eb429965..fd14a34a 100644
--- a/cmd/emintcli/keys.go
+++ b/cmd/emintcli/keys.go
@@ -4,14 +4,12 @@ import (
"bufio"
"io"
- "github.com/tendermint/tendermint/crypto"
-
"github.com/cosmos/cosmos-sdk/client/flags"
clientkeys "github.com/cosmos/cosmos-sdk/client/keys"
"github.com/cosmos/cosmos-sdk/crypto/keyring"
sdk "github.com/cosmos/cosmos-sdk/types"
- emintCrypto "github.com/cosmos/ethermint/crypto"
+ "github.com/cosmos/ethermint/crypto"
"github.com/spf13/cobra"
"github.com/spf13/viper"
@@ -54,7 +52,7 @@ func keyCommands() *cobra.Command {
func getKeybase(transient bool, buf io.Reader) (keyring.Keybase, error) {
if transient {
- return keyring.NewInMemory(keyring.WithKeygenFunc(ethermintKeygenFunc)), nil
+ return keyring.NewInMemory(keyring.WithKeygenFunc(crypto.EthermintKeygenFunc)), nil
}
return keyring.NewKeyring(
@@ -62,7 +60,7 @@ func getKeybase(transient bool, buf io.Reader) (keyring.Keybase, error) {
viper.GetString(flags.FlagKeyringBackend),
viper.GetString(flags.FlagHome),
buf,
- keyring.WithKeygenFunc(ethermintKeygenFunc))
+ keyring.WithKeygenFunc(crypto.EthermintKeygenFunc))
}
func runAddCmd(cmd *cobra.Command, args []string) error {
@@ -74,7 +72,3 @@ func runAddCmd(cmd *cobra.Command, args []string) error {
return clientkeys.RunAddCmd(cmd, args, kb, inBuf)
}
-
-func ethermintKeygenFunc(bz []byte, algo keyring.SigningAlgo) (crypto.PrivKey, error) {
- return emintCrypto.PrivKeySecp256k1(bz), nil
-}
diff --git a/cmd/emintd/main.go b/cmd/emintd/main.go
index d0da3224..da19b2d5 100644
--- a/cmd/emintd/main.go
+++ b/cmd/emintd/main.go
@@ -75,7 +75,7 @@ func main() {
app.DefaultNodeHome, app.DefaultCLIHome,
),
genutilcli.ValidateGenesisCmd(ctx, cdc, app.ModuleBasics),
-
+ testnetCmd(ctx, cdc, app.ModuleBasics, bank.GenesisBalancesIterator{}),
// AddGenesisAccountCmd allows users to add accounts to the genesis file
AddGenesisAccountCmd(ctx, cdc, appCodec, app.DefaultNodeHome, app.DefaultCLIHome),
flags.NewCompletionCmd(rootCmd, true),
diff --git a/cmd/emintd/testnet.go b/cmd/emintd/testnet.go
new file mode 100644
index 00000000..8190ebb1
--- /dev/null
+++ b/cmd/emintd/testnet.go
@@ -0,0 +1,413 @@
+package main
+
+// DONTCOVER
+
+import (
+ "bufio"
+ "encoding/json"
+ "fmt"
+ "net"
+ "os"
+ "path/filepath"
+
+ ethcrypto "github.com/ethereum/go-ethereum/crypto"
+ "github.com/spf13/cobra"
+ tmconfig "github.com/tendermint/tendermint/config"
+ tmcrypto "github.com/tendermint/tendermint/crypto"
+ tmos "github.com/tendermint/tendermint/libs/os"
+ tmrand "github.com/tendermint/tendermint/libs/rand"
+ tmtypes "github.com/tendermint/tendermint/types"
+ tmtime "github.com/tendermint/tendermint/types/time"
+
+ "github.com/cosmos/cosmos-sdk/client/flags"
+ clientkeys "github.com/cosmos/cosmos-sdk/client/keys"
+ "github.com/cosmos/cosmos-sdk/codec"
+ "github.com/cosmos/cosmos-sdk/crypto/keyring"
+ "github.com/cosmos/cosmos-sdk/server"
+ srvconfig "github.com/cosmos/cosmos-sdk/server/config"
+ sdk "github.com/cosmos/cosmos-sdk/types"
+ "github.com/cosmos/cosmos-sdk/types/module"
+ authexported "github.com/cosmos/cosmos-sdk/x/auth/exported"
+ authtypes "github.com/cosmos/cosmos-sdk/x/auth/types"
+ banktypes "github.com/cosmos/cosmos-sdk/x/bank/types"
+ crisistypes "github.com/cosmos/cosmos-sdk/x/crisis/types"
+ "github.com/cosmos/cosmos-sdk/x/genutil"
+ genutiltypes "github.com/cosmos/cosmos-sdk/x/genutil/types"
+ govtypes "github.com/cosmos/cosmos-sdk/x/gov/types"
+ minttypes "github.com/cosmos/cosmos-sdk/x/mint/types"
+ stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types"
+
+ "github.com/cosmos/ethermint/crypto"
+ "github.com/cosmos/ethermint/types"
+)
+
+var (
+ flagNodeDirPrefix = "node-dir-prefix"
+ flagNumValidators = "v"
+ flagOutputDir = "output-dir"
+ flagNodeDaemonHome = "node-daemon-home"
+ flagNodeCLIHome = "node-cli-home"
+ flagStartingIPAddress = "starting-ip-address"
+)
+
+const nodeDirPerm = 0755
+
+// get cmd to initialize all files for tendermint testnet and application
+func testnetCmd(ctx *server.Context, cdc *codec.Codec,
+ mbm module.BasicManager, genBalIterator banktypes.GenesisBalancesIterator,
+) *cobra.Command {
+
+ cmd := &cobra.Command{
+ Use: "testnet",
+ Short: "Initialize files for a Ethermint testnet",
+ Long: `testnet will create "v" number of directories and populate each with
+necessary files (private validator, genesis, config, etc.).
+
+Note, strict routability for addresses is turned off in the config file.`,
+
+ Example: "emintd testnet --v 4 --keyring-backend test --output-dir ./output --starting-ip-address 192.168.10.2",
+ RunE: func(cmd *cobra.Command, _ []string) error {
+ config := ctx.Config
+
+ outputDir, _ := cmd.Flags().GetString(flagOutputDir)
+ keyringBackend, _ := cmd.Flags().GetString(flags.FlagKeyringBackend)
+ chainID, _ := cmd.Flags().GetString(flags.FlagChainID)
+ minGasPrices, _ := cmd.Flags().GetString(server.FlagMinGasPrices)
+ nodeDirPrefix, _ := cmd.Flags().GetString(flagNodeDirPrefix)
+ nodeDaemonHome, _ := cmd.Flags().GetString(flagNodeDaemonHome)
+ nodeCLIHome, _ := cmd.Flags().GetString(flagNodeCLIHome)
+ startingIPAddress, _ := cmd.Flags().GetString(flagStartingIPAddress)
+ numValidators, _ := cmd.Flags().GetInt(flagNumValidators)
+
+ return InitTestnet(
+ cmd, config, cdc, mbm, genBalIterator, outputDir, chainID, minGasPrices,
+ nodeDirPrefix, nodeDaemonHome, nodeCLIHome, startingIPAddress, keyringBackend, numValidators,
+ )
+ },
+ }
+
+ cmd.Flags().Int(flagNumValidators, 4, "Number of validators to initialize the testnet with")
+ cmd.Flags().StringP(flagOutputDir, "o", "./build", "Directory to store initialization data for the testnet")
+ cmd.Flags().String(flagNodeDirPrefix, "node", "Prefix the directory name for each node with (node results in node0, node1, ...)")
+ cmd.Flags().String(flagNodeDaemonHome, "emintd", "Home directory of the node's daemon configuration")
+ cmd.Flags().String(flagNodeCLIHome, "emintcli", "Home directory of the node's cli configuration")
+ cmd.Flags().String(flagStartingIPAddress, "192.168.0.1", "Starting IP address (192.168.0.1 results in persistent peers list ID0@192.168.0.1:46656, ID1@192.168.0.2:46656, ...)")
+ cmd.Flags().String(flags.FlagChainID, "", "genesis file chain-id, if left blank will be randomly created")
+ cmd.Flags().String(server.FlagMinGasPrices, fmt.Sprintf("0.000006%s", types.DenomDefault), "Minimum gas prices to accept for transactions; All fees in a tx must meet this minimum (e.g. 0.01photon,0.001stake)")
+ cmd.Flags().String(flags.FlagKeyringBackend, flags.DefaultKeyringBackend, "Select keyring's backend (os|file|test)")
+
+ return cmd
+}
+
+// InitTestnet initializes the testnet configuration
+func InitTestnet(
+ cmd *cobra.Command, config *tmconfig.Config, cdc *codec.Codec,
+ mbm module.BasicManager, genBalIterator banktypes.GenesisBalancesIterator,
+ outputDir, chainID, minGasPrices, nodeDirPrefix, nodeDaemonHome,
+ nodeCLIHome, startingIPAddress, keyringBackend string, numValidators int,
+) error {
+
+ if chainID == "" {
+ chainID = fmt.Sprintf("%d", tmrand.Int63())
+ }
+
+ nodeIDs := make([]string, numValidators)
+ valPubKeys := make([]tmcrypto.PubKey, numValidators)
+
+ simappConfig := srvconfig.DefaultConfig()
+ simappConfig.MinGasPrices = minGasPrices
+
+ var (
+ genAccounts []authexported.GenesisAccount
+ genBalances []banktypes.Balance
+ genFiles []string
+ )
+
+ inBuf := bufio.NewReader(cmd.InOrStdin())
+ // generate private keys, node IDs, and initial transactions
+ for i := 0; i < numValidators; i++ {
+ nodeDirName := fmt.Sprintf("%s%d", nodeDirPrefix, i)
+ nodeDir := filepath.Join(outputDir, nodeDirName, nodeDaemonHome)
+ clientDir := filepath.Join(outputDir, nodeDirName, nodeCLIHome)
+ gentxsDir := filepath.Join(outputDir, "gentxs")
+
+ config.SetRoot(nodeDir)
+ config.RPC.ListenAddress = "tcp://0.0.0.0:26657"
+
+ if err := os.MkdirAll(filepath.Join(nodeDir, "config"), nodeDirPerm); err != nil {
+ _ = os.RemoveAll(outputDir)
+ return err
+ }
+
+ if err := os.MkdirAll(clientDir, nodeDirPerm); err != nil {
+ _ = os.RemoveAll(outputDir)
+ return err
+ }
+
+ config.Moniker = nodeDirName
+
+ ip, err := getIP(i, startingIPAddress)
+ if err != nil {
+ _ = os.RemoveAll(outputDir)
+ return err
+ }
+
+ nodeIDs[i], valPubKeys[i], err = genutil.InitializeNodeValidatorFiles(config)
+ if err != nil {
+ _ = os.RemoveAll(outputDir)
+ return err
+ }
+
+ memo := fmt.Sprintf("%s@%s:26656", nodeIDs[i], ip)
+ genFiles = append(genFiles, config.GenesisFile())
+
+ kb, err := keyring.NewKeyring(
+ sdk.KeyringServiceName(),
+ keyringBackend,
+ clientDir,
+ inBuf,
+ keyring.WithKeygenFunc(crypto.EthermintKeygenFunc),
+ )
+ if err != nil {
+ return err
+ }
+
+ cmd.Printf(
+ "Password for account '%s' :\n", nodeDirName,
+ )
+
+ keyPass := clientkeys.DefaultKeyPass
+ addr, secret, err := server.GenerateSaveCoinKey(kb, nodeDirName, keyPass, true)
+ if err != nil {
+ _ = os.RemoveAll(outputDir)
+ return err
+ }
+
+ info := map[string]string{"secret": secret}
+
+ cliPrint, err := json.Marshal(info)
+ if err != nil {
+ return err
+ }
+
+ // save private key seed words
+ if err := writeFile(fmt.Sprintf("%v.json", "key_seed"), clientDir, cliPrint); err != nil {
+ return err
+ }
+
+ accTokens := sdk.TokensFromConsensusPower(1000)
+ accStakingTokens := sdk.TokensFromConsensusPower(5000)
+ coins := sdk.NewCoins(
+ sdk.NewCoin(sdk.DefaultBondDenom, accTokens),
+ sdk.NewCoin(types.DenomDefault, accStakingTokens),
+ )
+
+ genBalances = append(genBalances, banktypes.Balance{Address: addr, Coins: coins})
+ genAccounts = append(genAccounts, types.EthAccount{
+ BaseAccount: authtypes.NewBaseAccount(addr, nil, 0, 0),
+ CodeHash: ethcrypto.Keccak256(nil),
+ })
+
+ valTokens := sdk.TokensFromConsensusPower(100)
+ msg := stakingtypes.NewMsgCreateValidator(
+ sdk.ValAddress(addr),
+ valPubKeys[i],
+ sdk.NewCoin(types.DenomDefault, valTokens),
+ stakingtypes.NewDescription(nodeDirName, "", "", "", ""),
+ stakingtypes.NewCommissionRates(sdk.OneDec(), sdk.OneDec(), sdk.OneDec()),
+ sdk.OneInt(),
+ )
+
+ tx := authtypes.NewStdTx([]sdk.Msg{msg}, authtypes.StdFee{}, []authtypes.StdSignature{}, memo) //nolint:staticcheck // SA1019: authtypes.StdFee is deprecated
+ txBldr := authtypes.NewTxBuilderFromCLI(inBuf).WithChainID(chainID).WithMemo(memo).WithKeybase(kb)
+
+ signedTx, err := txBldr.SignStdTx(nodeDirName, clientkeys.DefaultKeyPass, tx, false)
+ if err != nil {
+ _ = os.RemoveAll(outputDir)
+ return err
+ }
+
+ txBytes, err := cdc.MarshalJSON(signedTx)
+ if err != nil {
+ _ = os.RemoveAll(outputDir)
+ return err
+ }
+
+ // gather gentxs folder
+ if err := writeFile(fmt.Sprintf("%v.json", nodeDirName), gentxsDir, txBytes); err != nil {
+ _ = os.RemoveAll(outputDir)
+ return err
+ }
+
+ srvconfig.WriteConfigFile(filepath.Join(nodeDir, "config/app.toml"), simappConfig)
+ }
+
+ if err := initGenFiles(cdc, mbm, chainID, genAccounts, genBalances, genFiles, numValidators); err != nil {
+ return err
+ }
+
+ err := collectGenFiles(
+ cdc, config, chainID, nodeIDs, valPubKeys, numValidators,
+ outputDir, nodeDirPrefix, nodeDaemonHome, genBalIterator,
+ )
+ if err != nil {
+ return err
+ }
+
+ cmd.PrintErrf("Successfully initialized %d node directories\n", numValidators)
+ return nil
+}
+
+func initGenFiles(
+ cdc codec.JSONMarshaler, mbm module.BasicManager, chainID string,
+ genAccounts []authexported.GenesisAccount, genBalances []banktypes.Balance,
+ genFiles []string, numValidators int,
+) error {
+
+ appGenState := mbm.DefaultGenesis(cdc)
+
+ // set the accounts in the genesis state
+ var authGenState authtypes.GenesisState
+ cdc.MustUnmarshalJSON(appGenState[authtypes.ModuleName], &authGenState)
+
+ authGenState.Accounts = genAccounts
+ appGenState[authtypes.ModuleName] = cdc.MustMarshalJSON(authGenState)
+
+ // set the balances in the genesis state
+ var bankGenState banktypes.GenesisState
+ cdc.MustUnmarshalJSON(appGenState[banktypes.ModuleName], &bankGenState)
+
+ bankGenState.Balances = genBalances
+ appGenState[banktypes.ModuleName] = cdc.MustMarshalJSON(bankGenState)
+
+ var stakingGenState stakingtypes.GenesisState
+ cdc.MustUnmarshalJSON(appGenState[stakingtypes.ModuleName], &stakingGenState)
+
+ stakingGenState.Params.BondDenom = types.DenomDefault
+ appGenState[stakingtypes.ModuleName] = cdc.MustMarshalJSON(stakingGenState)
+
+ var govGenState govtypes.GenesisState
+ cdc.MustUnmarshalJSON(appGenState[govtypes.ModuleName], &govGenState)
+
+ govGenState.DepositParams.MinDeposit[0].Denom = types.DenomDefault
+ appGenState[govtypes.ModuleName] = cdc.MustMarshalJSON(govGenState)
+
+ var mintGenState minttypes.GenesisState
+ cdc.MustUnmarshalJSON(appGenState[minttypes.ModuleName], &mintGenState)
+
+ mintGenState.Params.MintDenom = types.DenomDefault
+ appGenState[minttypes.ModuleName] = cdc.MustMarshalJSON(mintGenState)
+
+ var crisisGenState crisistypes.GenesisState
+ cdc.MustUnmarshalJSON(appGenState[crisistypes.ModuleName], &crisisGenState)
+
+ crisisGenState.ConstantFee.Denom = types.DenomDefault
+ appGenState[crisistypes.ModuleName] = cdc.MustMarshalJSON(crisisGenState)
+
+ appGenStateJSON, err := codec.MarshalJSONIndent(cdc, appGenState)
+ if err != nil {
+ return err
+ }
+
+ genDoc := tmtypes.GenesisDoc{
+ ChainID: chainID,
+ AppState: appGenStateJSON,
+ Validators: nil,
+ }
+
+ // generate empty genesis files for each validator and save
+ for i := 0; i < numValidators; i++ {
+ if err := genDoc.SaveAs(genFiles[i]); err != nil {
+ return err
+ }
+ }
+ return nil
+}
+
+func collectGenFiles(
+ cdc *codec.Codec, config *tmconfig.Config, chainID string,
+ nodeIDs []string, valPubKeys []tmcrypto.PubKey,
+ numValidators int, outputDir, nodeDirPrefix, nodeDaemonHome string,
+ genBalIterator banktypes.GenesisBalancesIterator,
+) error {
+
+ var appState json.RawMessage
+ genTime := tmtime.Now()
+
+ for i := 0; i < numValidators; i++ {
+ nodeDirName := fmt.Sprintf("%s%d", nodeDirPrefix, i)
+ nodeDir := filepath.Join(outputDir, nodeDirName, nodeDaemonHome)
+ gentxsDir := filepath.Join(outputDir, "gentxs")
+ config.Moniker = nodeDirName
+
+ config.SetRoot(nodeDir)
+
+ nodeID, valPubKey := nodeIDs[i], valPubKeys[i]
+ initCfg := genutiltypes.NewInitConfig(chainID, gentxsDir, nodeID, nodeID, valPubKey)
+
+ genDoc, err := tmtypes.GenesisDocFromFile(config.GenesisFile())
+ if err != nil {
+ return err
+ }
+
+ nodeAppState, err := genutil.GenAppStateFromConfig(cdc, config, initCfg, *genDoc, genBalIterator)
+ if err != nil {
+ return err
+ }
+
+ if appState == nil {
+ // set the canonical application state (they should not differ)
+ appState = nodeAppState
+ }
+
+ genFile := config.GenesisFile()
+
+ // overwrite each validator's genesis file to have a canonical genesis time
+ if err := genutil.ExportGenesisFileWithTime(genFile, chainID, nil, appState, genTime); err != nil {
+ return err
+ }
+ }
+
+ return nil
+}
+
+func getIP(i int, startingIPAddr string) (ip string, err error) {
+ if len(startingIPAddr) == 0 {
+ ip, err = server.ExternalIP()
+ if err != nil {
+ return "", err
+ }
+ return ip, nil
+ }
+ return calculateIP(startingIPAddr, i)
+}
+
+func calculateIP(ip string, i int) (string, error) {
+ ipv4 := net.ParseIP(ip).To4()
+ if ipv4 == nil {
+ return "", fmt.Errorf("%v: non ipv4 address", ip)
+ }
+
+ for j := 0; j < i; j++ {
+ ipv4[3]++
+ }
+
+ return ipv4.String(), nil
+}
+
+func writeFile(name string, dir string, contents []byte) error {
+ writePath := filepath.Join(dir)
+ file := filepath.Join(writePath, name)
+
+ err := tmos.EnsureDir(writePath, 0755)
+ if err != nil {
+ return err
+ }
+
+ err = tmos.WriteFile(file, contents, 0644)
+ if err != nil {
+ return err
+ }
+
+ return nil
+}
diff --git a/.codecov.yml b/codecov.yml
similarity index 97%
rename from .codecov.yml
rename to codecov.yml
index 2517bd53..74317c50 100644
--- a/.codecov.yml
+++ b/codecov.yml
@@ -53,6 +53,8 @@ flags:
ignore:
- "docs"
- "*.md"
+ - "cmd"
+ - "x/faucet"
- "**/*.pb.go"
- "types/*.pb.go"
- "x/**/*.pb.go"
diff --git a/crypto/keys.go b/crypto/keys.go
new file mode 100644
index 00000000..c3cdb793
--- /dev/null
+++ b/crypto/keys.go
@@ -0,0 +1,12 @@
+package crypto
+
+import (
+ "github.com/cosmos/cosmos-sdk/crypto/keyring"
+ tmcrypto "github.com/tendermint/tendermint/crypto"
+)
+
+// EthermintKeygenFunc is the key generation function to generate secp256k1 ToECDSA
+// from ethereum.
+func EthermintKeygenFunc(bz []byte, algo keyring.SigningAlgo) (tmcrypto.PrivKey, error) {
+ return PrivKeySecp256k1(bz), nil
+}
diff --git a/docker-compose.yml b/docker-compose.yml
new file mode 100644
index 00000000..861a13bf
--- /dev/null
+++ b/docker-compose.yml
@@ -0,0 +1,78 @@
+version: "3"
+
+services:
+ emintdnode0:
+ container_name: emintdnode0
+ image: "emintd/node"
+ ports:
+ - "26656-26657:26656-26657"
+ - "1317:1317"
+ - "8545:8545"
+ environment:
+ - ID=0
+ - LOG=${LOG:-emintd.log}
+ volumes:
+ - ./build:/ethermint:Z
+ networks:
+ localnet:
+ ipv4_address: 192.168.10.2
+ entrypoint: "bash start.sh"
+
+ emintdnode1:
+ container_name: emintdnode1
+ image: "emintd/node"
+ ports:
+ - "26659-26660:26656-26657"
+ - "1318:1317"
+ - "8546:8545"
+ environment:
+ - ID=1
+ - LOG=${LOG:-emintd.log}
+ volumes:
+ - ./build:/ethermint:Z
+ networks:
+ localnet:
+ ipv4_address: 192.168.10.3
+ entrypoint: "bash start.sh"
+
+ emintdnode2:
+ container_name: emintdnode2
+ image: "emintd/node"
+ environment:
+ - ID=2
+ - LOG=${LOG:-emintd.log}
+ ports:
+ - "26661-26662:26656-26657"
+ - "1319:1317"
+ - "8547:8545"
+ volumes:
+ - ./build:/ethermint:Z
+ networks:
+ localnet:
+ ipv4_address: 192.168.10.4
+ entrypoint: "bash start.sh"
+
+ emintdnode3:
+ container_name: emintdnode3
+ image: "emintd/node"
+ environment:
+ - ID=3
+ - LOG=${LOG:-emintd.log}
+ ports:
+ - "26663-26664:26656-26657"
+ - "1320:1317"
+ - "8548:8545"
+ volumes:
+ - ./build:/ethermint:Z
+ networks:
+ localnet:
+ ipv4_address: 192.168.10.5
+ entrypoint: "bash start.sh"
+
+networks:
+ localnet:
+ driver: bridge
+ ipam:
+ driver: default
+ config:
+ - subnet: 192.168.10.0/16
diff --git a/docs/quickstart/installation.md b/docs/quickstart/installation.md
index 2bf55efd..01d16ed0 100644
--- a/docs/quickstart/installation.md
+++ b/docs/quickstart/installation.md
@@ -21,9 +21,21 @@ emintd -h
emintcli -h
```
-
+## Docker
-
+You can build Ethermint using Docker by running:
+
+```bash
+make docker
+```
+
+This will install the binaries on the `./build` directory. Now, check that the binaries have been
+successfuly installed:
+
+```bash
+emintd -h
+emintcli -h
+```
## Releases
diff --git a/docs/quickstart/run_node.md b/docs/quickstart/run_node.md
index 43cad4e6..5ef321a1 100644
--- a/docs/quickstart/run_node.md
+++ b/docs/quickstart/run_node.md
@@ -10,7 +10,7 @@ Run a local node and start the REST and JSON-RPC clients {synopsis}
- [Installation](./installation.md) {prereq}
-## Script deployment
+## Automated deployment
Run the local node with faucet enabled:
@@ -29,43 +29,10 @@ In another terminal window or tab, run the Ethereum JSON-RPC server as well as t
emintcli rest-server --laddr "tcp://localhost:8545" --unlock-key mykey --chain-id 8
```
-## Manual setup
+## Manual deployment
-These instructions are for setting up a brand new full node from scratch.
-
-First, initialize the node and create the necessary config files:
-
-```bash
-emintd init
-```
-
-::: warning
-Monikers can contain only ASCII characters. Using Unicode characters will render your node unreachable.
-:::
-
-You can edit this `moniker` later, in the `$(HOME)/.emintd/config/config.toml` file:
-
-```toml
-# A custom human readable name for this node
-moniker = ""
-```
-
-You can edit the `$HOME/.emintd/config/app.toml` file in order to enable the anti spam mechanism and reject incoming transactions with less than the minimum gas prices:
-
-```toml
-# This is a TOML config file.
-# For more information, see https://github.com/toml-lang/toml
-
-##### main base config options #####
-
-# The minimum gas prices a validator is willing to accept for processing a
-# transaction. A transaction's fees must meet the minimum of any denomination
-# specified in this config (e.g. 10uatom).
-
-minimum-gas-prices = ""
-```
-
-Your full node is now initiallized.
+The instructions for setting up a brand new full node from scratch are the the same as running a
+[single node local testnet](./testnet.md#single-node-local-manual-testnet).
## Start node
diff --git a/docs/quickstart/testnet.md b/docs/quickstart/testnet.md
index e625b5bc..24b38c45 100644
--- a/docs/quickstart/testnet.md
+++ b/docs/quickstart/testnet.md
@@ -4,19 +4,270 @@ order: 3
# Testnet
-Learn how to deploy a local testnet or connect to an existing one {synopsis}
+Learn how to deploy a local testnet or connect to an existing public one {synopsis}
## Pre-requisite Readings
-- [Run Node](./run_node.md) {prereq}
+- [Install Ethermint](./installation.md) {prereq}
+- [Install Docker](https://docs.docker.com/engine/installation/) {prereq}
+- [Install docker-compose](https://docs.docker.com/compose/install/) {prereq}
+
-## Genesis and Seeds
+## Single-node, Local, Manual Testnet
-### Copy the Genesis File
+This guide helps you create a single validator node that runs a network locally for testing and other development related uses.
+
+### Initialize node
+
+```bash
+$MONIKER=testing
+$KEY=mykey
+$CHAINID=8
+
+emintd init $MONIKER --chain-id=$CHAINID
+```
+
+::: warning
+Monikers can contain only ASCII characters. Using Unicode characters will render your node unreachable.
+:::
+
+You can edit this `moniker` later, in the `$(HOME)/.emintd/config/config.toml` file:
+
+```toml
+# A custom human readable name for this node
+moniker = ""
+```
+
+You can edit the `$HOME/.emintd/config/app.toml` file in order to enable the anti spam mechanism and reject incoming transactions with less than the minimum gas prices:
+
+```toml
+# This is a TOML config file.
+# For more information, see https://github.com/toml-lang/toml
+
+##### main base config options #####
+
+# The minimum gas prices a validator is willing to accept for processing a
+# transaction. A transaction's fees must meet the minimum of any denomination
+# specified in this config (e.g. 10photon).
+
+minimum-gas-prices = ""
+```
+
+### Genesis Procedure
+
+```bash
+# Create a key to hold your account
+emintcli keys add $KEY
+
+# Add that key into the genesis.app_state.accounts array in the genesis file
+# NOTE: this command lets you set the number of coins. Make sure this account has some coins
+# with the genesis.app_state.staking.params.bond_denom denom, the default is staking
+emintd add-genesis-account $(emintcli keys show validator -a) 1000000000stake,10000000000photon
+
+# Generate the transaction that creates your validator
+emintd gentx --name $KEY
+
+# Add the generated bonding transaction to the genesis file
+emintd collect-gentxs
+
+# Finally, check the correctness of the genesis.json file
+emintd validate-genesis
+```
+
+### Run Testnet
+
+Now its safe to start the daemon:
+
+```bash
+emintd start
+```
+
+You can then stop the node using Ctrl+C.
+
+## Multi-node, Local, Automated Testnet
+
+### Build Testnet & Start Testnet
+
+To build start a 4 node testnet run:
+
+```bash
+make localnet-start
+```
+
+This command creates a 4-node network using the `emintdnode` Docker image.
+The ports for each node are found in this table:
+
+| Node ID | P2P Port | REST/RPC Port |
+|--------------|----------|---------------|
+| `emintnode0` | `26656` | `26657` |
+| `emintnode1` | `26659` | `26660` |
+| `emintnode2` | `26661` | `26662` |
+| `emintnode3` | `26663` | `26664` |
+
+To update the binary, just rebuild it and restart the nodes
+
+```bash
+make localnet-start
+```
+
+The command above command will run containers in the background using Docker compose. You will see the network being created:
+
+```bash
+...
+Creating network "chainsafe-ethermint_localnet" with driver "bridge"
+Creating emintdnode0 ... done
+Creating emintdnode2 ... done
+Creating emintdnode1 ... done
+Creating emintdnode3 ... done
+```
+
+
+### Stop Testnet
+
+Once you are done, execute:
+
+```bash
+make localnet-stop
+```
+
+### Configuration
+
+The `make localnet-start` creates files for a 4-node testnet in `./build` by
+calling the `emintd testnet` command. This outputs a handful of files in the
+`./build` directory:
+
+```bash
+tree -L 3 build/
+
+build/
+├── emintcli
+├── emintd
+├── gentxs
+│ ├── node0.json
+│ ├── node1.json
+│ ├── node2.json
+│ └── node3.json
+├── node0
+│ ├── emintcli
+│ │ ├── key_seed.json
+│ │ └── keyring-test-cosmos
+│ └── emintd
+│ ├── config
+│ ├── data
+│ └── emintd.log
+├── node1
+│ ├── emintcli
+│ │ ├── key_seed.json
+│ │ └── keyring-test-cosmos
+│ └── emintd
+│ ├── config
+│ ├── data
+│ └── emintd.log
+├── node2
+│ ├── emintcli
+│ │ ├── key_seed.json
+│ │ └── keyring-test-cosmos
+│ └── emintd
+│ ├── config
+│ ├── data
+│ └── emintd.log
+└── node3
+ ├── emintcli
+ │ ├── key_seed.json
+ │ └── keyring-test-cosmos
+ └── emintd
+ ├── config
+ ├── data
+ └── emintd.log
+```
+
+Each `./build/nodeN` directory is mounted to the `/emintd` directory in each container.
+
+### Logging
+
+In order to see the logs of a particular node you can use the following command:
+
+```bash
+# node 0: daemon logs
+docker exec emintdnode0 tail emintd.log
+
+# node 0: REST & RPC logs
+docker exec emintdnode0 tail emintcli.log
+```
+
+The logs for the daemon will look like:
+
+```bash
+I[2020-07-29|17:33:52.452] starting ABCI with Tendermint module=main
+E[2020-07-29|17:33:53.394] Can't add peer's address to addrbook module=p2p err="Cannot add non-routable address 272a247b837653cf068d39efd4c407ffbd9a0e6f@192.168.10.5:26656"
+E[2020-07-29|17:33:53.394] Can't add peer's address to addrbook module=p2p err="Cannot add non-routable address 3e05d3637b7ebf4fc0948bbef01b54d670aa810a@192.168.10.4:26656"
+E[2020-07-29|17:33:53.394] Can't add peer's address to addrbook module=p2p err="Cannot add non-routable address 689f8606ede0b26ad5b79ae244c14cc67ab4efe7@192.168.10.3:26656"
+I[2020-07-29|17:33:58.828] Executed block module=state height=88 validTxs=0 invalidTxs=0
+I[2020-07-29|17:33:58.830] Committed state module=state height=88 txs=0 appHash=90CC5FA53CF8B5EC49653A14DA20888AD81C92FCF646F04D501453FD89FCC791
+I[2020-07-29|17:34:04.032] Executed block module=state height=89 validTxs=0 invalidTxs=0
+I[2020-07-29|17:34:04.034] Committed state module=state height=89 txs=0 appHash=0B54C4DB1A0DACB1EEDCD662B221C048C826D309FD2A2F31FF26BAE8D2D7D8D7
+I[2020-07-29|17:34:09.381] Executed block module=state height=90 validTxs=0 invalidTxs=0
+I[2020-07-29|17:34:09.383] Committed state module=state height=90 txs=0 appHash=75FD1EE834F0669D5E717C812F36B21D5F20B3CCBB45E8B8D415CB9C4513DE51
+I[2020-07-29|17:34:14.700] Executed block module=state height=91 validTxs=0 invalidTxs=0
+```
-
::: tip
-If you want to start a network from scratch, you will need to start the genesis procedure.
+You can disregard the `Can't add peer's address to addrbook` warning. As long as the blocks are
+being produced and the app hashes are the same for each node, there should not be any issues.
+:::
+
+Whereas the logs for the REST & RPC server would look like:
+
+```bash
+I[2020-07-30|09:39:17.488] Starting application REST service (chain-id: "7305661614933169792")... module=rest-server
+I[2020-07-30|09:39:17.488] Starting RPC HTTP server on 127.0.0.1:8545 module=rest-server
+...
+```
+
+#### Follow Logs
+
+You can also watch logs as they are produced via Docker with the `--follow` (`-f`) flag, for
+example:
+
+```bash
+docker logs -f emintdnode0
+```
+
+### Keys & Accounts
+
+To interact with `emintcli` and start querying state or creating txs, you use the
+`emintcli` directory of any given node as your `home`, for example:
+
+```bash
+emintcli keys list --home ./build/node0/emintcli
+```
+
+Now that accounts exists, you may create new accounts and send those accounts
+funds!
+
+::: tip
+**Note**: Each node's seed is located at `./build/nodeN/emintcli/key_seed.json` and can be restored to the CLI using the `emintcli keys add --restore` command
+:::
+
+### Special Binaries
+
+If you have multiple binaries with different names, you can specify which one to run with the BINARY environment variable. The path of the binary is relative to the attached volume. For example:
+
+```bash
+# Run with custom binary
+BINARY=ethermint make localnet-start
+```
+
+## Multi-node, Public, Manual Testnet
+
+If you are looking to connect to a persistent public testnet. You will need to manually configure your node.
+
+### Genesis and Seeds
+
+#### Copy the Genesis File
+
+::: tip
+If you want to start a network from scratch, you will need to start the [genesis procedure](#genesis-procedure) by creating a `genesis.json` and submit + collect the genesis transactions from the [validators](./validator-setup.md).
:::
If you want to connect to an existing testnet, fetch the testnet's `genesis.json` file and copy it into the `emintd`'s config directory (i.e `$HOME/.emintd/config/genesis.json`).
@@ -27,13 +278,13 @@ Then verify the correctness of the genesis configuration file:
emintd validate-genesis
```
-### Add Seed Nodes
+#### Add Seed Nodes
Your node needs to know how to find peers. You'll need to add healthy seed nodes to `$HOME/.emintd/config/config.toml`. If those seeds aren't working, you can find more seeds and persistent peers on an existing explorer.
For more information on seeds and peers, you can the Tendermint [P2P documentation](https://docs.tendermint.com/master/spec/p2p/peer.html).
-### Start testnet
+#### Start testnet
The final step is to [start the nodes](./run_node.md#start-node). Once enough voting power (+2/3) from the genesis validators is up-and-running, the testnet will start producing blocks.
diff --git a/docs/quickstart/validator-setup.md b/docs/quickstart/validator-setup.md
index acc96887..7bb6c019 100644
--- a/docs/quickstart/validator-setup.md
+++ b/docs/quickstart/validator-setup.md
@@ -56,7 +56,7 @@ When specifying commission parameters, the `commission-max-change-rate` is used
You can confirm that you are in the validator set by using a third party explorer.
-## Genesis transactions
+## Genesis Transactions
A genesis transaction (aka `gentx`) is a JSON file carrying a self-delegation from a validator. All genesis transactions are collected by a genesis coordinator and validated against an initial `genesis.json` file.
@@ -106,7 +106,7 @@ encoded `address` in the `~/.emintd/config/priv_validator.json` file.
To be in the validator set, you need to have more total voting power than the 100th validator.
:::
-## Halting Your Validator
+## Halt Your Validator Node
When attempting to perform routine maintenance or planning for an upcoming coordinated
upgrade, it can be useful to have your validator systematically and gracefully halt the chain and shutdown the node.
diff --git a/networks/local/Makefile b/networks/local/Makefile
new file mode 100644
index 00000000..f5c867a8
--- /dev/null
+++ b/networks/local/Makefile
@@ -0,0 +1,4 @@
+all:
+ docker build --tag emintd/node ethermintnode
+
+.PHONY: all
diff --git a/networks/local/ethermintnode/Dockerfile b/networks/local/ethermintnode/Dockerfile
new file mode 100644
index 00000000..451fcc81
--- /dev/null
+++ b/networks/local/ethermintnode/Dockerfile
@@ -0,0 +1,32 @@
+FROM golang:stretch as build-env
+
+# Install minimum necessary dependencies
+ENV PACKAGES curl make git libc-dev bash gcc
+RUN apt-get update && apt-get upgrade -y && \
+ apt-get install -y $PACKAGES
+
+# Set working directory for the build
+WORKDIR /go/src/github.com/ChainSafe/ethermint
+
+# Add source files
+COPY . .
+
+# build Ethermint
+RUN make build-ethermint-linux
+
+# Final image
+FROM golang:1.14 as final
+
+WORKDIR /
+
+RUN apt-get update
+
+# Copy over binaries from the build-env
+COPY --from=build-env /go/src/github.com/ChainSafe/ethermint/build/emintd /usr/bin/emintd
+COPY --from=build-env /go/src/github.com/ChainSafe/ethermint/build/emintcli /usr/bin/emintcli
+COPY --from=build-env /go/src/github.com/ChainSafe/ethermint/scripts/start.sh /
+
+EXPOSE 26656 26657 1317 8545
+
+# Run emintd by default, omit entrypoint to ease using container with emintcli
+ENTRYPOINT ["/bin/bash", "-c"]
\ No newline at end of file
diff --git a/scripts/integration-test-all.sh b/scripts/integration-test-all.sh
index 4560a49e..bfcf7a4a 100755
--- a/scripts/integration-test-all.sh
+++ b/scripts/integration-test-all.sh
@@ -63,7 +63,7 @@ fi
# Compile ethermint
echo "compiling ethermint"
-make build
+make build-ethermint
# PID array declaration
arr=()
diff --git a/scripts/start.sh b/scripts/start.sh
new file mode 100644
index 00000000..4eca5c74
--- /dev/null
+++ b/scripts/start.sh
@@ -0,0 +1,5 @@
+#!/bin/sh
+emintd --home /ethermint/node$ID/emintd/ start > emintd.log &
+sleep 5
+emintcli rest-server --laddr "tcp://localhost:8545" --chain-id 7305661614933169792 --trace > emintcli.log &
+tail -f /dev/null
\ No newline at end of file
diff --git a/x/evm/types/state_object.go b/x/evm/types/state_object.go
index bd1875fb..99238452 100644
--- a/x/evm/types/state_object.go
+++ b/x/evm/types/state_object.go
@@ -54,7 +54,6 @@ type StateObject interface {
// Finally, call CommitTrie to write the modified storage trie into a database.
type stateObject struct {
code types.Code // contract bytecode, which gets set when code is loaded
-
// State objects are used by the consensus core and VM which are
// unable to deal with database-level errors. Any error that occurs
// during a database read is memoized here and will eventually be returned