Added testnet command and localnet targets

Finished testnet command and introduced localnet targets in Makefile, together with gaiadnode Docker image

Fixed function parameter list - now starts with ctx

Separated GenTxConfig into a server/config package so both the server package and the mock package can use it

Adding server/config to app package

gaiadnode Docker image

Separated GenTxConfig into a server/config package so both the server package and the mock package can use it

Adding server/config to app package

Fixes requested by Rigel

Removed commented code

Global flag fixes
This commit is contained in:
Greg Szabo 2018-06-05 15:17:04 -04:00
parent 1b20adcd22
commit 4c5e536b31
15 changed files with 632 additions and 181 deletions

2
.gitignore vendored
View File

@ -3,6 +3,7 @@
*.swp
*.swo
.vscode
.idea
# Build
vendor
@ -15,6 +16,7 @@ docs/_build
examples/basecoin/app/data
baseapp/data/*
client/lcd/keys/*
mytestnet
# Testing
coverage.txt

View File

@ -1,25 +1,50 @@
# Changelog
## Pending
BREAKING CHANGES
* msg.GetSignBytes() now returns bech32-encoded addresses in all cases
* [lcd] REST end-points now include gas
* [cli] rearranged commands under subcommands
* [stake] remove Tick and add EndBlocker
FEATURES
IMPROVEMENTS
* export command now writes current validator set for Tendermint
* [tests] Application module tests now use a mock application
* [gaiacli] Fix error message when account isn't found when running gaiacli account
* [lcd] refactored to eliminate use of global variables, and interdependent tests
* bank module uses go-wire codec instead of 'encoding/json'
* auth module uses go-wire codec instead of 'encoding/json'
* revised use of endblock and beginblock
* added testnet command
* added localnet commands to Makefile (docker-based local testnet)
FIXES
* [lcd] Switch to bech32 for addresses on all human readable inputs and outputs
* [lcd] fixed tx indexing/querying
* [cli] Added `--gas` flag to specify transaction gas limit
* [cli] fixed cli-bash tests
* [ci] added cli-bash tests
* [basecoin] updated basecoin for stake and slashing
## 0.18.1
BREAKING CHANGES
* [x/auth] move stuff specific to auth anteHandler to the auth module rather than the types folder. This includes:
* StdTx (and its related stuff i.e. StdSignDoc, etc)
* StdFee
* StdSignature
* Account interface
* Related to this organization, I also:
* [x/auth] got rid of AccountMapper interface (in favor of the struct already in auth module)
* [x/auth] removed the FeeHandler function from the AnteHandler, Replaced with FeeKeeper
* [x/auth] Removed GetSignatures() from Tx interface (as different Tx styles might use something different than StdSignature)
* [store] Removed SubspaceIterator and ReverseSubspaceIterator from KVStore interface and replaced them with helper functions in /types
* Switch to bech32cosmos on all human readable inputs and outputs
BUG FIXES
* auto-sequencing transactions correctly
* query sequence via account store
* fixed duplicate pub_key in stake.Validator
## 0.18.0
_2018-06-05_
_TBD_
BREAKING CHANGES
@ -38,20 +63,6 @@ BREAKING CHANGES
* Introduction of Unbonding fields, lowlevel logic throughout (not fully implemented with queue)
* Introduction of PoolShares type within validators,
replaces three rational fields (BondedShares, UnbondingShares, UnbondedShares
* [x/auth] move stuff specific to auth anteHandler to the auth module rather than the types folder. This includes:
* StdTx (and its related stuff i.e. StdSignDoc, etc)
* StdFee
* StdSignature
* Account interface
* Related to this organization, I also:
* [x/auth] got rid of AccountMapper interface (in favor of the struct already in auth module)
* [x/auth] removed the FeeHandler function from the AnteHandler, Replaced with FeeKeeper
* [x/auth] Removed GetSignatures() from Tx interface (as different Tx styles might use something different than StdSignature)
* [store] Removed SubspaceIterator and ReverseSubspaceIterator from KVStore interface and replaced them with helper functions in /types
* [cli] rearranged commands under subcommands
* [stake] remove Tick and add EndBlocker
* Switch to bech32cosmos on all human readable inputs and outputs
FEATURES
@ -68,41 +79,12 @@ FEATURES
* [stake] Added REST API
* [Makefile] Added terraform/ansible playbooks to easily create remote testnets on Digital Ocean
BUG FIXES
* Auto-sequencing now works correctly
* [stake] staking delegator shares exchange rate now relative to equivalent-bonded-tokens the validator has instead of bonded tokens
^ this is important for unbonded validators in the power store!
* [cli] fixed cli-bash tests
* [ci] added cli-bash tests
* [basecoin] updated basecoin for stake and slashing
* [docs] fixed references to old cli commands
* [docs] Downgraded Swagger to v2 for downstream compatibility
* auto-sequencing transactions correctly
* query sequence via account store
* fixed duplicate pub_key in stake.Validator
* Auto-sequencing now works correctly
## 0.17.5
*June 5, 2018*
Update to Tendermint v0.19.9 (Fix evidence reactor, mempool deadlock, WAL panic,
memory leak)
## 0.17.4
*May 31, 2018*
Update to Tendermint v0.19.7 (WAL fixes and more)
## 0.17.3
*May 29, 2018*
Update to Tendermint v0.19.6 (fix fast-sync halt)
## 0.17.2
@ -142,7 +124,6 @@ BUG FIXES
* Auto-sequencing now works correctly
## 0.16.0 (May 14th, 2018)
BREAKING CHANGES
@ -177,14 +158,12 @@ BUG FIXES
* Gaia now uses stake, ported from github.com/cosmos/gaia
## 0.15.1 (April 29, 2018)
IMPROVEMENTS:
* Update Tendermint to v0.19.1 (includes many rpc fixes)
## 0.15.0 (April 29, 2018)
NOTE: v0.15.0 is a large breaking change that updates the encoding scheme to use

View File

@ -130,12 +130,27 @@ devdoc_update:
########################################
### Remote validator nodes using terraform and ansible
### Local validator nodes using docker and docker-compose
# Build linux binary
build-linux:
GOOS=linux GOARCH=amd64 $(MAKE) build
build-docker-gaiadnode:
$(MAKE) -C networks/local
# Run a 4-node testnet locally
localnet-start: localnet-stop
@if ! [ -f build/node0/gaiad/config/genesis.json ]; then docker run --rm -v $(CURDIR)/build:/gaiad:Z tendermint/gaiadnode testnet --v 4 --o . --starting-ip-address 192.168.10.2 ; fi
docker-compose up
# Stop testnet
localnet-stop:
docker-compose down
########################################
### Remote validator nodes using terraform and ansible
TESTNET_NAME?=remotenet
SERVERS?=4
BINARY=$(CURDIR)/build/gaiad
@ -157,4 +172,4 @@ remotenet-status:
# To avoid unintended conflicts with file names, always add to .PHONY
# unless there is a reason not to.
# https://www.gnu.org/software/make/manual/html_node/Phony-Targets.html
.PHONY: build build_examples install install_examples dist check_tools get_tools get_vendor_deps draw_deps test test_cli test_unit test_cover test_lint benchmark devdoc_init devdoc devdoc_save devdoc_update remotenet-start remotenet-stop remotenet-status
.PHONY: build build_examples install install_examples dist check_tools get_tools get_vendor_deps draw_deps test test_cli test_unit test_cover test_lint benchmark devdoc_init devdoc devdoc_save devdoc_update build-linux build-docker-gaiadnode localnet-start localnet-stop remotenet-start remotenet-stop remotenet-status

View File

@ -3,13 +3,12 @@ package app
import (
"encoding/json"
"errors"
"github.com/spf13/pflag"
"github.com/spf13/viper"
crypto "github.com/tendermint/go-crypto"
tmtypes "github.com/tendermint/tendermint/types"
"github.com/cosmos/cosmos-sdk/server"
gc "github.com/cosmos/cosmos-sdk/server/config"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/wire"
"github.com/cosmos/cosmos-sdk/x/auth"
@ -50,25 +49,15 @@ func (ga *GenesisAccount) ToAccount() (acc *auth.BaseAccount) {
}
}
var (
flagName = "name"
flagClientHome = "home-client"
flagOWK = "owk"
// bonded tokens given to genesis validators/accounts
freeFermionVal = int64(100)
freeFermionsAcc = int64(50)
)
// get app init parameters for server init command
func GaiaAppInit() server.AppInit {
fsAppGenState := pflag.NewFlagSet("", pflag.ContinueOnError)
fsAppGenTx := pflag.NewFlagSet("", pflag.ContinueOnError)
fsAppGenTx.String(flagName, "", "validator moniker, required")
fsAppGenTx.String(flagClientHome, DefaultCLIHome,
fsAppGenTx.String(server.FlagName, "", "validator moniker, required")
fsAppGenTx.String(server.FlagClientHome, DefaultCLIHome,
"home directory for the client, used for key generation")
fsAppGenTx.Bool(flagOWK, false, "overwrite the accounts created")
fsAppGenTx.Bool(server.FlagOWK, false, "overwrite the accounts created")
return server.AppInit{
FlagsAppGenState: fsAppGenState,
@ -86,18 +75,15 @@ type GaiaGenTx struct {
}
// Generate a gaia genesis transaction with flags
func GaiaAppGenTx(cdc *wire.Codec, pk crypto.PubKey) (
func GaiaAppGenTx(cdc *wire.Codec, pk crypto.PubKey, genTxConfig gc.GenTxConfig) (
appGenTx, cliPrint json.RawMessage, validator tmtypes.GenesisValidator, err error) {
clientRoot := viper.GetString(flagClientHome)
overwrite := viper.GetBool(flagOWK)
name := viper.GetString(flagName)
if name == "" {
if genTxConfig.Name == "" {
return nil, nil, tmtypes.GenesisValidator{}, errors.New("Must specify --name (validator moniker)")
}
var addr sdk.Address
var secret string
addr, secret, err = server.GenerateSaveCoinKey(clientRoot, name, "1234567890", overwrite)
addr, secret, err = server.GenerateSaveCoinKey(genTxConfig.CliRoot, genTxConfig.Name, "1234567890", genTxConfig.Overwrite)
if err != nil {
return
}
@ -107,8 +93,10 @@ func GaiaAppGenTx(cdc *wire.Codec, pk crypto.PubKey) (
if err != nil {
return
}
cliPrint = json.RawMessage(bz)
return GaiaAppGenTxNF(cdc, pk, addr, name, overwrite)
appGenTx, _, validator, err = GaiaAppGenTxNF(cdc, pk, addr, genTxConfig.Name, genTxConfig.Overwrite)
return
}
// Generate a gaia genesis transaction without flags
@ -129,7 +117,7 @@ func GaiaAppGenTxNF(cdc *wire.Codec, pk crypto.PubKey, addr sdk.Address, name st
validator = tmtypes.GenesisValidator{
PubKey: pk,
Power: freeFermionVal,
Power: server.FreeFermionVal,
}
return
}
@ -160,21 +148,21 @@ func GaiaAppGenState(cdc *wire.Codec, appGenTxs []json.RawMessage) (genesisState
accAuth := auth.NewBaseAccountWithAddress(genTx.Address)
accAuth.Coins = sdk.Coins{
{genTx.Name + "Token", 1000},
{"steak", freeFermionsAcc},
{"steak", server.FreeFermionsAcc},
}
acc := NewGenesisAccount(&accAuth)
genaccs[i] = acc
stakeData.Pool.LooseUnbondedTokens += freeFermionsAcc // increase the supply
stakeData.Pool.LooseUnbondedTokens += server.FreeFermionsAcc // increase the supply
// add the validator
if len(genTx.Name) > 0 {
desc := stake.NewDescription(genTx.Name, "", "", "")
validator := stake.NewValidator(genTx.Address, genTx.PubKey, desc)
validator.PoolShares = stake.NewBondedShares(sdk.NewRat(freeFermionVal))
validator.PoolShares = stake.NewBondedShares(sdk.NewRat(server.FreeFermionVal))
stakeData.Validators = append(stakeData.Validators, validator)
// pool logic
stakeData.Pool.BondedTokens += freeFermionVal
stakeData.Pool.BondedTokens += server.FreeFermionVal
stakeData.Pool.BondedShares = sdk.NewRat(stakeData.Pool.BondedTokens)
}
}

68
docker-compose.yml Normal file
View File

@ -0,0 +1,68 @@
version: '3'
services:
node0:
container_name: node0
image: "tendermint/gaiadnode"
ports:
- "46656-46657:46656-46657"
environment:
- ID=0
- LOG=$${LOG:-gaiad.log}
volumes:
- ./build:/gaiad:Z
networks:
localnet:
ipv4_address: 192.168.10.2
node1:
container_name: node1
image: "tendermint/gaiadnode"
ports:
- "46659-46660:46656-46657"
environment:
- ID=1
- LOG=$${LOG:-gaiad.log}
volumes:
- ./build:/gaiad:Z
networks:
localnet:
ipv4_address: 192.168.10.3
node2:
container_name: node2
image: "tendermint/gaiadnode"
environment:
- ID=2
- LOG=$${LOG:-gaiad.log}
ports:
- "46661-46662:46656-46657"
volumes:
- ./build:/gaiad:Z
networks:
localnet:
ipv4_address: 192.168.10.4
node3:
container_name: node3
image: "tendermint/gaiadnode"
environment:
- ID=3
- LOG=$${LOG:-gaiad.log}
ports:
- "46663-46664:46656-46657"
volumes:
- ./build:/gaiad:Z
networks:
localnet:
ipv4_address: 192.168.10.5
networks:
localnet:
driver: bridge
ipam:
driver: default
config:
-
subnet: 192.168.10.0/16

7
networks/local/Makefile Normal file
View File

@ -0,0 +1,7 @@
# Makefile for the "gaiadnode" docker image.
all:
docker build --tag tendermint/gaiadnode gaiadnode
.PHONY: all

79
networks/local/README.md Normal file
View File

@ -0,0 +1,79 @@
# Local Cluster with Docker Compose
## Requirements
- [Install gaia](/docs/index.rst)
- [Install docker](https://docs.docker.com/engine/installation/)
- [Install docker-compose](https://docs.docker.com/compose/install/)
## Build
Build the `gaiad` binary and the `tendermint/gaiadnode` docker image.
Note the binary will be mounted into the container so it can be updated without
rebuilding the image.
```
cd $GOPATH/src/github.com/cosmos/cosmos-sdk
# Build the linux binary in ./build
make build-linux
# Build tendermint/gaiadnode image
make build-docker-gaiadnode
```
## Run a testnet
To start a 4 node testnet run:
```
make localnet-start
```
The nodes bind their RPC servers to ports 46657, 46660, 46662, and 46664 on the host.
This file creates a 4-node network using the gaiadnode image.
The nodes of the network expose their P2P and RPC endpoints to the host machine on ports 46656-46657, 46659-46660, 46661-46662, and 46663-46664 respectively.
To update the binary, just rebuild it and restart the nodes:
```
make build-linux
make localnet-stop
make localnet-start
```
## Configuration
The `make localnet-start` creates files for a 4-node testnet in `./build` by calling the `gaiad testnet` command.
The `./build` directory is mounted to the `/gaiad` mount point to attach the binary and config files to the container.
For instance, to create a single node testnet:
```
cd $GOPATH/src/github.com/cosmos/cosmos-sdk
# Clear the build folder
rm -rf ./build
# Build binary
make build-linux
# Create configuration
docker run -v `pwd`/build:/gaiad tendermint/gaiadnode testnet --o . --v 1
#Run the node
docker run -v `pwd`/build:/gaiad tendermint/gaiadnode
```
## Logging
Log is saved under the attached volume, in the `gaiad.log` file and written on the screen.
## 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.

View File

@ -0,0 +1,16 @@
FROM alpine:3.7
MAINTAINER Greg Szabo <greg@tendermint.com>
RUN apk update && \
apk upgrade && \
apk --no-cache add curl jq file
VOLUME [ /gaiad ]
WORKDIR /gaiad
EXPOSE 46656 46657
ENTRYPOINT ["/usr/bin/wrapper.sh"]
CMD ["start"]
STOPSIGNAL SIGTERM
COPY wrapper.sh /usr/bin/wrapper.sh

View File

@ -0,0 +1,35 @@
#!/usr/bin/env sh
##
## Input parameters
##
BINARY=/gaiad/${BINARY:-gaiad}
ID=${ID:-0}
LOG=${LOG:-gaiad.log}
##
## Assert linux binary
##
if ! [ -f "${BINARY}" ]; then
echo "The binary $(basename "${BINARY}") cannot be found. Please add the binary to the shared folder. Please use the BINARY environment variable if the name of the binary is not 'gaiad' E.g.: -e BINARY=gaiad_my_test_version"
exit 1
fi
BINARY_CHECK="$(file "$BINARY" | grep 'ELF 64-bit LSB executable, x86-64')"
if [ -z "${BINARY_CHECK}" ]; then
echo "Binary needs to be OS linux, ARCH amd64"
exit 1
fi
##
## Run binary with all parameters
##
export GAIADHOME="/gaiad/node${ID}/gaiad"
if [ -d "`dirname ${GAIADHOME}/${LOG}`" ]; then
"$BINARY" --home "$GAIADHOME" "$@" | tee "${GAIADHOME}/${LOG}"
else
"$BINARY" --home "$GAIADHOME" "$@"
fi
chmod 777 -R /gaiad

14
server/config/config.go Normal file
View File

@ -0,0 +1,14 @@
package config
//_____________________________________________________________________
// Configuration structure for command functions that share configuration.
// For example: init, init gen-tx and testnet commands need similar input and run the same code
// Storage for init gen-tx command input parameters
type GenTxConfig struct {
Name string
CliRoot string
Overwrite bool
IP string
}

View File

@ -19,13 +19,14 @@ import (
"github.com/tendermint/go-crypto/keys/words"
cfg "github.com/tendermint/tendermint/config"
"github.com/tendermint/tendermint/p2p"
tmtypes "github.com/tendermint/tendermint/types"
pvm "github.com/tendermint/tendermint/privval"
tmtypes "github.com/tendermint/tendermint/types"
tmcli "github.com/tendermint/tmlibs/cli"
cmn "github.com/tendermint/tmlibs/common"
dbm "github.com/tendermint/tmlibs/db"
clkeys "github.com/cosmos/cosmos-sdk/client/keys"
gc "github.com/cosmos/cosmos-sdk/server/config"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/wire"
)
@ -39,10 +40,36 @@ type GenesisTx struct {
}
var (
flagOverwrite = "overwrite"
flagGenTxs = "gen-txs"
flagIP = "ip"
flagChainID = "chain-id"
//--name parameter name, init gen-tx command
FlagName = "name"
//--home-client parameter name, init gen-tx command
FlagClientHome = "home-client"
//--owk parameter name, init gen-tx command
FlagOWK = "owk"
// bonded tokens given to genesis validators/accounts
FreeFermionVal = int64(100)
// ...
FreeFermionsAcc = int64(50)
)
// Storage for init command input parameters
type InitConfig struct {
ChainID string
GenTxs bool
GenTxsDir string
Overwrite bool
}
var (
//--overwrite parameter name, init command
FlagOverwrite = "overwrite"
//--gen-txs parameter name, init command
FlagGenTxs = "gen-txs"
//--ip parameter name, init command
FlagIP = "ip"
//--chain-id parameter name, init command
FlagChainID = "chain-id"
)
// get cmd to initialize all files for tendermint and application
@ -54,50 +81,27 @@ func GenTxCmd(ctx *Context, cdc *wire.Codec, appInit AppInit) *cobra.Command {
RunE: func(_ *cobra.Command, args []string) error {
config := ctx.Config
nodeKey, err := p2p.LoadOrGenNodeKey(config.NodeKeyFile())
if err != nil {
return err
}
nodeID := string(nodeKey.ID())
pubKey := readOrCreatePrivValidator(config)
config.SetRoot(viper.GetString(tmcli.HomeFlag))
appGenTx, cliPrint, validator, err := appInit.AppGenTx(cdc, pubKey)
if err != nil {
return err
}
ip := viper.GetString(flagIP)
ip := viper.GetString(FlagIP)
if len(ip) == 0 {
ip, err = externalIP()
eip, err := externalIP()
if err != nil {
return err
}
ip = eip
}
tx := GenesisTx{
NodeID: nodeID,
IP: ip,
Validator: validator,
AppGenTx: appGenTx,
genTxConfig := gc.GenTxConfig{
viper.GetString(FlagName),
viper.GetString(FlagClientHome),
viper.GetBool(FlagOWK),
ip,
}
bz, err := wire.MarshalJSONIndent(cdc, tx)
cliPrint, genTxFile, err := gentxWithConfig(ctx, cdc, appInit, config, genTxConfig)
if err != nil {
return err
}
genTxFile := json.RawMessage(bz)
name := fmt.Sprintf("gentx-%v.json", nodeID)
writePath := filepath.Join(viper.GetString(tmcli.HomeFlag), "config", "gentx")
file := filepath.Join(writePath, name)
err = cmn.EnsureDir(writePath, 0700)
if err != nil {
return err
}
err = cmn.WriteFile(file, bz, 0644)
if err != nil {
return err
}
// print out some key information
toPrint := struct {
AppMessage json.RawMessage `json:"app_message"`
GenTxFile json.RawMessage `json:"gen_tx_file"`
@ -113,11 +117,51 @@ func GenTxCmd(ctx *Context, cdc *wire.Codec, appInit AppInit) *cobra.Command {
return nil
},
}
cmd.Flags().String(flagIP, "", "external facing IP to use if left blank IP will be retrieved from this machine")
cmd.Flags().String(FlagIP, "", "external facing IP to use if left blank IP will be retrieved from this machine")
cmd.Flags().AddFlagSet(appInit.FlagsAppGenTx)
return cmd
}
func gentxWithConfig(ctx *Context, cdc *wire.Codec, appInit AppInit, config *cfg.Config, genTxConfig gc.GenTxConfig) (
cliPrint json.RawMessage, genTxFile json.RawMessage, err error) {
nodeKey, err := p2p.LoadOrGenNodeKey(config.NodeKeyFile())
if err != nil {
return
}
nodeID := string(nodeKey.ID())
pubKey := readOrCreatePrivValidator(config)
appGenTx, cliPrint, validator, err := appInit.AppGenTx(cdc, pubKey, genTxConfig)
if err != nil {
return
}
tx := GenesisTx{
NodeID: nodeID,
IP: genTxConfig.IP,
Validator: validator,
AppGenTx: appGenTx,
}
bz, err := wire.MarshalJSONIndent(cdc, tx)
if err != nil {
return
}
genTxFile = json.RawMessage(bz)
name := fmt.Sprintf("gentx-%v.json", nodeID)
writePath := filepath.Join(config.RootDir, "config", "gentx")
file := filepath.Join(writePath, name)
err = cmn.EnsureDir(writePath, 0700)
if err != nil {
return
}
err = cmn.WriteFile(file, bz, 0644)
if err != nil {
return
}
return
}
// get cmd to initialize all files for tendermint and application
func InitCmd(ctx *Context, cdc *wire.Codec, appInit AppInit) *cobra.Command {
cmd := &cobra.Command{
@ -127,58 +171,18 @@ func InitCmd(ctx *Context, cdc *wire.Codec, appInit AppInit) *cobra.Command {
RunE: func(_ *cobra.Command, _ []string) error {
config := ctx.Config
nodeKey, err := p2p.LoadOrGenNodeKey(config.NodeKeyFile())
config.SetRoot(viper.GetString(tmcli.HomeFlag))
initConfig := InitConfig{
viper.GetString(FlagChainID),
viper.GetBool(FlagGenTxs),
filepath.Join(config.RootDir, "config", "gentx"),
viper.GetBool(FlagOverwrite),
}
chainID, nodeID, appMessage, err := initWithConfig(ctx, cdc, appInit, config, initConfig)
if err != nil {
return err
}
nodeID := string(nodeKey.ID())
pubKey := readOrCreatePrivValidator(config)
chainID := viper.GetString(flagChainID)
if chainID == "" {
chainID = cmn.Fmt("test-chain-%v", cmn.RandStr(6))
}
genFile := config.GenesisFile()
if !viper.GetBool(flagOverwrite) && cmn.FileExists(genFile) {
return fmt.Errorf("genesis.json file already exists: %v", genFile)
}
// process genesis transactions, or otherwise create one for defaults
var appMessage json.RawMessage
var appGenTxs []json.RawMessage
var validators []tmtypes.GenesisValidator
var persistentPeers string
if viper.GetBool(flagGenTxs) {
genTxsDir := filepath.Join(viper.GetString(tmcli.HomeFlag), "config", "gentx")
validators, appGenTxs, persistentPeers, err = processGenTxs(genTxsDir, cdc, appInit)
if err != nil {
return err
}
config.P2P.PersistentPeers = persistentPeers
configFilePath := filepath.Join(viper.GetString(tmcli.HomeFlag), "config", "config.toml")
cfg.WriteConfigFile(configFilePath, config)
} else {
appGenTx, am, validator, err := appInit.AppGenTx(cdc, pubKey)
appMessage = am
if err != nil {
return err
}
validators = []tmtypes.GenesisValidator{validator}
appGenTxs = []json.RawMessage{appGenTx}
}
appState, err := appInit.AppGenState(cdc, appGenTxs)
if err != nil {
return err
}
err = writeGenesisFile(cdc, genFile, chainID, validators, appState)
if err != nil {
return err
}
// print out some key information
toPrint := struct {
ChainID string `json:"chain_id"`
@ -194,19 +198,80 @@ func InitCmd(ctx *Context, cdc *wire.Codec, appInit AppInit) *cobra.Command {
return err
}
fmt.Println(string(out))
return nil
},
}
cmd.Flags().BoolP(flagOverwrite, "o", false, "overwrite the genesis.json file")
cmd.Flags().String(flagChainID, "", "genesis file chain-id, if left blank will be randomly created")
cmd.Flags().Bool(flagGenTxs, false, "apply genesis transactions from [--home]/config/gentx/")
cmd.Flags().BoolP(FlagOverwrite, "o", false, "overwrite the genesis.json file")
cmd.Flags().String(FlagChainID, "", "genesis file chain-id, if left blank will be randomly created")
cmd.Flags().Bool(FlagGenTxs, false, "apply genesis transactions from [--home]/config/gentx/")
cmd.Flags().AddFlagSet(appInit.FlagsAppGenState)
cmd.Flags().AddFlagSet(appInit.FlagsAppGenTx) // need to add this flagset for when no GenTx's provided
cmd.AddCommand(GenTxCmd(ctx, cdc, appInit))
return cmd
}
func initWithConfig(ctx *Context, cdc *wire.Codec, appInit AppInit, config *cfg.Config, initConfig InitConfig) (
chainID string, nodeID string, appMessage json.RawMessage, err error) {
nodeKey, err := p2p.LoadOrGenNodeKey(config.NodeKeyFile())
if err != nil {
return
}
nodeID = string(nodeKey.ID())
pubKey := readOrCreatePrivValidator(config)
if initConfig.ChainID == "" {
initConfig.ChainID = fmt.Sprintf("test-chain-%v", cmn.RandStr(6))
}
chainID = initConfig.ChainID
genFile := config.GenesisFile()
if !initConfig.Overwrite && cmn.FileExists(genFile) {
err = fmt.Errorf("genesis.json file already exists: %v", genFile)
return
}
// process genesis transactions, or otherwise create one for defaults
var appGenTxs []json.RawMessage
var validators []tmtypes.GenesisValidator
var persistentPeers string
if initConfig.GenTxs {
validators, appGenTxs, persistentPeers, err = processGenTxs(initConfig.GenTxsDir, cdc, appInit)
if err != nil {
return
}
config.P2P.PersistentPeers = persistentPeers
configFilePath := filepath.Join(config.RootDir, "config", "config.toml")
cfg.WriteConfigFile(configFilePath, config)
} else {
genTxConfig := gc.GenTxConfig{
viper.GetString(FlagName),
viper.GetString(FlagClientHome),
viper.GetBool(FlagOWK),
"127.0.0.1",
}
appGenTx, am, validator, err := appInit.AppGenTx(cdc, pubKey, genTxConfig)
appMessage = am
if err != nil {
return "", "", nil, err
}
validators = []tmtypes.GenesisValidator{validator}
appGenTxs = []json.RawMessage{appGenTx}
}
appState, err := appInit.AppGenState(cdc, appGenTxs)
if err != nil {
return
}
err = writeGenesisFile(cdc, genFile, initConfig.ChainID, validators, appState)
if err != nil {
return
}
return
}
// append a genesis-piece
func processGenTxs(genTxsDir string, cdc *wire.Codec, appInit AppInit) (
validators []tmtypes.GenesisValidator, appGenTxs []json.RawMessage, persistentPeers string, err error) {
@ -315,7 +380,7 @@ type AppInit struct {
FlagsAppGenTx *pflag.FlagSet
// create the application genesis tx
AppGenTx func(cdc *wire.Codec, pk crypto.PubKey) (
AppGenTx func(cdc *wire.Codec, pk crypto.PubKey, genTxConfig gc.GenTxConfig) (
appGenTx, cliPrint json.RawMessage, validator tmtypes.GenesisValidator, err error)
// AppGenState creates the core parameters initialization. It takes in a
@ -337,7 +402,7 @@ type SimpleGenTx struct {
}
// Generate a genesis transaction
func SimpleAppGenTx(cdc *wire.Codec, pk crypto.PubKey) (
func SimpleAppGenTx(cdc *wire.Codec, pk crypto.PubKey, genTxConfig gc.GenTxConfig) (
appGenTx, cliPrint json.RawMessage, validator tmtypes.GenesisValidator, err error) {
var addr sdk.Address

View File

@ -34,6 +34,10 @@ func TestGenTxCmd(t *testing.T) {
// TODO
}
func TestTestnetFilesCmd(t *testing.T) {
// TODO
}
func TestSimpleAppGenTx(t *testing.T) {
// TODO
}

View File

@ -12,6 +12,7 @@ import (
"github.com/tendermint/tmlibs/log"
bam "github.com/cosmos/cosmos-sdk/baseapp"
gc "github.com/cosmos/cosmos-sdk/server/config"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/wire"
)
@ -124,7 +125,7 @@ func AppGenState(_ *wire.Codec, _ []json.RawMessage) (appState json.RawMessage,
}
// Return a validator, not much else
func AppGenTx(_ *wire.Codec, pk crypto.PubKey) (
func AppGenTx(_ *wire.Codec, pk crypto.PubKey, genTxConfig gc.GenTxConfig) (
appGenTx, cliPrint json.RawMessage, validator tmtypes.GenesisValidator, err error) {
validator = tmtypes.GenesisValidator{

177
server/testnet.go Normal file
View File

@ -0,0 +1,177 @@
package server
import (
"fmt"
"net"
"path/filepath"
"github.com/spf13/cobra"
gc "github.com/cosmos/cosmos-sdk/server/config"
"github.com/cosmos/cosmos-sdk/wire"
"github.com/spf13/viper"
cfg "github.com/tendermint/tendermint/config"
cmn "github.com/tendermint/tmlibs/common"
"os"
)
var (
nodeDirPrefix = "node-dir-prefix"
nValidators = "v"
outputDir = "o"
startingIPAddress = "starting-ip-address"
)
const nodeDirPerm = 0755
// get cmd to initialize all files for tendermint testnet and application
func TestnetFilesCmd(ctx *Context, cdc *wire.Codec, appInit AppInit) *cobra.Command {
cmd := &cobra.Command{
Use: "testnet",
Short: "Initialize files for a Gaiad 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:
gaiad testnet --v 4 --output-dir ./output --starting-ip-address 192.168.10.2
`,
RunE: func(_ *cobra.Command, _ []string) error {
config := ctx.Config
err := testnetWithConfig(config, ctx, cdc, appInit)
return err
},
}
cmd.Flags().Int(nValidators, 4,
"Number of validators to initialize the testnet with")
cmd.Flags().String(outputDir, "./mytestnet",
"Directory to store initialization data for the testnet")
cmd.Flags().String(nodeDirPrefix, "node",
"Prefix the directory name for each node with (node results in node0, node1, ...)")
cmd.Flags().String(startingIPAddress, "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, ...)")
return cmd
}
func testnetWithConfig(config *cfg.Config, ctx *Context, cdc *wire.Codec, appInit AppInit) error {
outDir := viper.GetString(outputDir)
// Generate private key, node ID, initial transaction
for i := 0; i < viper.GetInt(nValidators); i++ {
nodeDirName := fmt.Sprintf("%s%d", viper.GetString(nodeDirPrefix), i)
nodeDir := filepath.Join(outDir, nodeDirName, "gaiad")
clientDir := filepath.Join(outDir, nodeDirName, "gaiacli")
gentxsDir := filepath.Join(outDir, "gentxs")
config.SetRoot(nodeDir)
err := os.MkdirAll(filepath.Join(nodeDir, "config"), nodeDirPerm)
if err != nil {
_ = os.RemoveAll(outDir)
return err
}
err = os.MkdirAll(clientDir, nodeDirPerm)
if err != nil {
_ = os.RemoveAll(outDir)
return err
}
config.Moniker = nodeDirName
ip := viper.GetString(startingIPAddress)
if len(ip) == 0 {
ip, err = externalIP()
if err != nil {
return err
}
} else {
ip, err = calculateIP(ip, i)
if err != nil {
return err
}
}
genTxConfig := gc.GenTxConfig{
nodeDirName,
clientDir,
true,
ip,
}
// Run `init gen-tx` and generate initial transactions
cliPrint, genTxFile, err := gentxWithConfig(ctx, cdc, appInit, config, genTxConfig)
if err != nil {
return err
}
// Save private key seed words
name := fmt.Sprintf("%v.json", "key_seed")
writePath := filepath.Join(clientDir)
file := filepath.Join(writePath, name)
err = cmn.EnsureDir(writePath, 0700)
if err != nil {
return err
}
err = cmn.WriteFile(file, cliPrint, 0600)
if err != nil {
return err
}
// Gather gentxs folder
name = fmt.Sprintf("%v.json", nodeDirName)
writePath = filepath.Join(gentxsDir)
file = filepath.Join(writePath, name)
err = cmn.EnsureDir(writePath, 0700)
if err != nil {
return err
}
err = cmn.WriteFile(file, genTxFile, 0644)
if err != nil {
return err
}
}
// Generate genesis.json and config.toml
chainID := "chain-" + cmn.RandStr(6)
for i := 0; i < viper.GetInt(nValidators); i++ {
nodeDirName := fmt.Sprintf("%s%d", viper.GetString(nodeDirPrefix), i)
nodeDir := filepath.Join(outDir, nodeDirName, "gaiad")
gentxsDir := filepath.Join(outDir, "gentxs")
initConfig := InitConfig{
chainID,
true,
gentxsDir,
true,
}
config.Moniker = nodeDirName
config.SetRoot(nodeDir)
// Run `init` and generate genesis.json and config.toml
_, _, _, err := initWithConfig(ctx, cdc, appInit, config, initConfig)
if err != nil {
return err
}
}
fmt.Printf("Successfully initialized %v node directories\n", viper.GetInt(nValidators))
return nil
}
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
}

View File

@ -85,6 +85,7 @@ func AddCommands(
rootCmd.AddCommand(
InitCmd(ctx, cdc, appInit),
TestnetFilesCmd(ctx, cdc, appInit),
StartCmd(ctx, appCreator),
UnsafeResetAllCmd(ctx),
client.LineBreak,