local testnet command (#378)

* evm: fix non-determinism

* fixes

* typo

* fix tests

* local testnet command

* fix testnet cmd (#383)

fix export-eth-key

generate eth type account in genesis.json file

* fixes

* update docker

* minor changes

* fix build-docker-local-ethermint

* fix dockerfile

* update Makefile

* update denoms

* update genesis file

* update makefile

* fix docker-compose.yml images

* fix localnet execution (#398)

* finish documentation

* changelog and comment rpc tests workflow

* update codecov

* update testnet docs

* fix docker-compose execution

* update docs

* fix errors and make testnet work (#403)

* fix errors and make testnet work

* Update Dockerfile

Co-authored-by: Federico Kunze <31522760+fedekunze@users.noreply.github.com>

* wip - fix db

* fixes emintd nodes and syncs nodes

* starts daemon and rpc server in bg

Co-authored-by: Federico Kunze <31522760+fedekunze@users.noreply.github.com>
Co-authored-by: Federico Kunze <federico.kunze94@gmail.com>

* update entrypoint and docs

* update logs

* try fix rpc

* build docker image

* Update Dockerfile

Co-authored-by: Holechain <nrgh@foxmail.com>
Co-authored-by: Alessio Treglia <quadrispro@ubuntu.com>
Co-authored-by: Daniel Choi <choidanielw@gmail.com>
This commit is contained in:
Federico Kunze 2020-07-31 23:42:04 +02:00 committed by GitHub
parent 0dc45bc24e
commit 375a7a074d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 934 additions and 196 deletions

2
.gitignore vendored
View File

@ -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

View File

@ -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

228
Makefile
View File

@ -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 ###
#######################
###############################################################################
# Start docs site at localhost:8080
docs-serve:
@ -312,3 +254,27 @@ docs-build:
@cd docs && \
npm install && \
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

View File

@ -20,7 +20,7 @@ parent:
<a href="https://goreportcard.com/report/github.com/ChainSafe/ethermint">
<img alt="Go report card" src="https://goreportcard.com/badge/github.com/ChainSafe/ethermint"/>
</a>
<a href="https://codecov.io/gh/cosmos/ethermint">
<a href="https://codecov.io/gh/ChainSafe/ethermint">
<img alt="Code Coverage" src="https://codecov.io/gh/ChainSafe/ethermint/branch/development/graph/badge.svg" />
</a>
</div>

View File

@ -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
}

View File

@ -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),

413
cmd/emintd/testnet.go Normal file
View File

@ -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
}

View File

@ -53,6 +53,8 @@ flags:
ignore:
- "docs"
- "*.md"
- "cmd"
- "x/faucet"
- "**/*.pb.go"
- "types/*.pb.go"
- "x/**/*.pb.go"

12
crypto/keys.go Normal file
View File

@ -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
}

78
docker-compose.yml Normal file
View File

@ -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

View File

@ -21,9 +21,21 @@ emintd -h
emintcli -h
```
<!-- ## Docker -->
## Docker
<!-- TODO: -->
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

View File

@ -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 <your_custom_moniker>
```
::: 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 = "<your_custom_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

View File

@ -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}
<!-- - [Install `jq`](https://stedolan.github.io/jq/download/) {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 = "<your_custom_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
```
<!-- TODO: link to genesis procedure -->
::: 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.

View File

@ -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.

4
networks/local/Makefile Normal file
View File

@ -0,0 +1,4 @@
all:
docker build --tag emintd/node ethermintnode
.PHONY: all

View File

@ -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"]

View File

@ -63,7 +63,7 @@ fi
# Compile ethermint
echo "compiling ethermint"
make build
make build-ethermint
# PID array declaration
arr=()

5
scripts/start.sh Normal file
View File

@ -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

View File

@ -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