Introduce Wasmgovd (#189)

* Introduce wasmgovd; disable wasm proposals with wasmd

* Update changelog

* Setup wasmgov with permission Nobody

* Review feedback
This commit is contained in:
Alexander Peters
2020-07-16 16:47:44 +02:00
committed by GitHub
parent 84bcb393a1
commit 774f6d7876
18 changed files with 1010 additions and 37 deletions

View File

@@ -37,6 +37,7 @@ Ref: https://keepachangelog.com/en/1.0.0/
## [Unreleased] ## [Unreleased]
### Features ### Features
* (wasmd) [\#187](https://github.com/CosmWasm/wasmd/issues/187) Introduce wasmgovd binary
* (wasmd) [\#178](https://github.com/CosmWasm/wasmd/issues/178) Add cli support for wasm gov proposals * (wasmd) [\#178](https://github.com/CosmWasm/wasmd/issues/178) Add cli support for wasm gov proposals
* (wasmd) [\#163](https://github.com/CosmWasm/wasmd/issues/163) Control who can instantiate code * (wasmd) [\#163](https://github.com/CosmWasm/wasmd/issues/163) Control who can instantiate code
* (wasmd) [\#164](https://github.com/CosmWasm/wasmd/issues/164) Control who can upload code * (wasmd) [\#164](https://github.com/CosmWasm/wasmd/issues/164) Control who can upload code

View File

@@ -39,6 +39,7 @@ RUN BUILD_TAGS=muslc make build
FROM alpine:3.12 FROM alpine:3.12
COPY --from=go-builder /code/build/wasmd /usr/bin/wasmd COPY --from=go-builder /code/build/wasmd /usr/bin/wasmd
COPY --from=go-builder /code/build/wasmgovd /usr/bin/wasmgovd
COPY --from=go-builder /code/build/wasmcli /usr/bin/wasmcli COPY --from=go-builder /code/build/wasmcli /usr/bin/wasmcli
COPY docker/* /opt/ COPY docker/* /opt/

View File

@@ -70,9 +70,11 @@ all: install lint test
build: go.sum build: go.sum
ifeq ($(OS),Windows_NT) ifeq ($(OS),Windows_NT)
go build -mod=readonly $(BUILD_FLAGS) -o build/wasmd.exe ./cmd/wasmd go build -mod=readonly $(BUILD_FLAGS) -o build/wasmd.exe ./cmd/wasmd
go build -mod=readonly $(BUILD_FLAGS) -o build/wasmgovd.exe ./cmd/wasmgovd
go build -mod=readonly $(BUILD_FLAGS) -o build/wasmcli.exe ./cmd/wasmcli go build -mod=readonly $(BUILD_FLAGS) -o build/wasmcli.exe ./cmd/wasmcli
else else
go build -mod=readonly $(BUILD_FLAGS) -o build/wasmd ./cmd/wasmd go build -mod=readonly $(BUILD_FLAGS) -o build/wasmd ./cmd/wasmd
go build -mod=readonly $(BUILD_FLAGS) -o build/wasmgovd ./cmd/wasmgovd
go build -mod=readonly $(BUILD_FLAGS) -o build/wasmcli ./cmd/wasmcli go build -mod=readonly $(BUILD_FLAGS) -o build/wasmcli ./cmd/wasmcli
endif endif

View File

@@ -41,7 +41,7 @@ func main() {
cobra.EnableCommandSorting = false cobra.EnableCommandSorting = false
rootCmd := &cobra.Command{ rootCmd := &cobra.Command{
Use: "wasmd", Use: "wasmd",
Short: "Wasm Daemon (server)", Short: "Wasm Daemon (server) with wasm gov proposals disabled\",",
PersistentPreRunE: server.PersistentPreRunEFn(ctx), PersistentPreRunE: server.PersistentPreRunEFn(ctx),
} }
@@ -86,7 +86,7 @@ func newApp(logger log.Logger, db dbm.DB, traceStore io.Writer) abci.Application
} }
return app.NewWasmApp(logger, db, traceStore, true, invCheckPeriod, return app.NewWasmApp(logger, db, traceStore, true, invCheckPeriod,
wasm.EnableAllProposals, skipUpgradeHeights, wasm.DisableAllProposals, skipUpgradeHeights,
baseapp.SetPruning(store.NewPruningOptionsFromString(viper.GetString("pruning"))), baseapp.SetPruning(store.NewPruningOptionsFromString(viper.GetString("pruning"))),
baseapp.SetMinGasPrices(viper.GetString(server.FlagMinGasPrices)), baseapp.SetMinGasPrices(viper.GetString(server.FlagMinGasPrices)),
baseapp.SetHaltHeight(viper.GetUint64(server.FlagHaltHeight)), baseapp.SetHaltHeight(viper.GetUint64(server.FlagHaltHeight)),
@@ -99,7 +99,7 @@ func exportAppStateAndTMValidators(
) (json.RawMessage, []tmtypes.GenesisValidator, error) { ) (json.RawMessage, []tmtypes.GenesisValidator, error) {
if height != -1 { if height != -1 {
gapp := app.NewWasmApp(logger, db, traceStore, false, uint(1), wasm.EnableAllProposals, nil) gapp := app.NewWasmApp(logger, db, traceStore, false, uint(1), wasm.DisableAllProposals, nil)
err := gapp.LoadHeight(height) err := gapp.LoadHeight(height)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
@@ -107,6 +107,6 @@ func exportAppStateAndTMValidators(
return gapp.ExportAppStateAndValidators(forZeroHeight, jailWhiteList) return gapp.ExportAppStateAndValidators(forZeroHeight, jailWhiteList)
} }
gapp := app.NewWasmApp(logger, db, traceStore, true, uint(1), wasm.EnableAllProposals, nil) gapp := app.NewWasmApp(logger, db, traceStore, true, uint(1), wasm.DisableAllProposals, nil)
return gapp.ExportAppStateAndValidators(forZeroHeight, jailWhiteList) return gapp.ExportAppStateAndValidators(forZeroHeight, jailWhiteList)
} }

View File

@@ -92,7 +92,7 @@ func replayTxs(rootDir string) error {
fmt.Fprintln(os.Stderr, "Creating application") fmt.Fprintln(os.Stderr, "Creating application")
gapp := app.NewWasmApp( gapp := app.NewWasmApp(
// TODO: do we want to set skipUpgradeHieghts here? // TODO: do we want to set skipUpgradeHieghts here?
ctx.Logger, appDB, traceStoreWriter, true, uint(1), wasm.EnableAllProposals, nil, ctx.Logger, appDB, traceStoreWriter, true, uint(1), wasm.DisableAllProposals, nil,
baseapp.SetPruning(store.PruneEverything)) baseapp.SetPruning(store.PruneEverything))
// Genesis // Genesis

153
cmd/wasmgovd/genaccounts.go Normal file
View File

@@ -0,0 +1,153 @@
package main
import (
"bufio"
"errors"
"fmt"
"github.com/spf13/cobra"
"github.com/spf13/viper"
"github.com/tendermint/tendermint/libs/cli"
"github.com/cosmos/cosmos-sdk/client/flags"
"github.com/cosmos/cosmos-sdk/codec"
"github.com/cosmos/cosmos-sdk/crypto/keys"
"github.com/cosmos/cosmos-sdk/server"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/x/auth"
authexported "github.com/cosmos/cosmos-sdk/x/auth/exported"
authvesting "github.com/cosmos/cosmos-sdk/x/auth/vesting"
"github.com/cosmos/cosmos-sdk/x/genutil"
)
const (
flagClientHome = "home-client"
flagVestingStart = "vesting-start-time"
flagVestingEnd = "vesting-end-time"
flagVestingAmt = "vesting-amount"
)
// AddGenesisAccountCmd returns add-genesis-account cobra Command.
func AddGenesisAccountCmd(
ctx *server.Context, cdc *codec.Codec, defaultNodeHome, defaultClientHome string,
) *cobra.Command {
cmd := &cobra.Command{
Use: "add-genesis-account [address_or_key_name] [coin][,[coin]]",
Short: "Add a genesis account to genesis.json",
Long: `Add a genesis account to genesis.json. The provided account must specify
the account address or key name and a list of initial coins. If a key name is given,
the address will be looked up in the local Keybase. The list of initial tokens must
contain valid denominations. Accounts may optionally be supplied with vesting parameters.
`,
Args: cobra.ExactArgs(2),
RunE: func(cmd *cobra.Command, args []string) error {
config := ctx.Config
config.SetRoot(viper.GetString(cli.HomeFlag))
addr, err := sdk.AccAddressFromBech32(args[0])
inBuf := bufio.NewReader(cmd.InOrStdin())
if err != nil {
// attempt to lookup address from Keybase if no address was provided
kb, err := keys.NewKeyring(
sdk.KeyringServiceName(),
viper.GetString(flags.FlagKeyringBackend),
viper.GetString(flagClientHome),
inBuf,
)
if err != nil {
return err
}
info, err := kb.Get(args[0])
if err != nil {
return fmt.Errorf("failed to get address from Keybase: %w", err)
}
addr = info.GetAddress()
}
coins, err := sdk.ParseCoins(args[1])
if err != nil {
return fmt.Errorf("failed to parse coins: %w", err)
}
vestingStart := viper.GetInt64(flagVestingStart)
vestingEnd := viper.GetInt64(flagVestingEnd)
vestingAmt, err := sdk.ParseCoins(viper.GetString(flagVestingAmt))
if err != nil {
return fmt.Errorf("failed to parse vesting amount: %w", err)
}
// create concrete account type based on input parameters
var genAccount authexported.GenesisAccount
baseAccount := auth.NewBaseAccount(addr, coins.Sort(), nil, 0, 0)
if !vestingAmt.IsZero() {
baseVestingAccount, err := authvesting.NewBaseVestingAccount(baseAccount, vestingAmt.Sort(), vestingEnd)
if err != nil {
return fmt.Errorf("failed to create base vesting account: %w", err)
}
switch {
case vestingStart != 0 && vestingEnd != 0:
genAccount = authvesting.NewContinuousVestingAccountRaw(baseVestingAccount, vestingStart)
case vestingEnd != 0:
genAccount = authvesting.NewDelayedVestingAccountRaw(baseVestingAccount)
default:
return errors.New("invalid vesting parameters; must supply start and end time or end time")
}
} else {
genAccount = baseAccount
}
if err := genAccount.Validate(); err != nil {
return fmt.Errorf("failed to validate new genesis account: %w", err)
}
genFile := config.GenesisFile()
appState, genDoc, err := genutil.GenesisStateFromGenFile(cdc, genFile)
if err != nil {
return fmt.Errorf("failed to unmarshal genesis state: %w", err)
}
authGenState := auth.GetGenesisStateFromAppState(cdc, appState)
if authGenState.Accounts.Contains(addr) {
return fmt.Errorf("cannot add account at existing address %s", addr)
}
// Add the new account to the set of genesis accounts and sanitize the
// accounts afterwards.
authGenState.Accounts = append(authGenState.Accounts, genAccount)
authGenState.Accounts = auth.SanitizeGenesisAccounts(authGenState.Accounts)
authGenStateBz, err := cdc.MarshalJSON(authGenState)
if err != nil {
return fmt.Errorf("failed to marshal auth genesis state: %w", err)
}
appState[auth.ModuleName] = authGenStateBz
appStateJSON, err := cdc.MarshalJSON(appState)
if err != nil {
return fmt.Errorf("failed to marshal application genesis state: %w", err)
}
genDoc.AppState = appStateJSON
return genutil.ExportGenesisFile(genDoc, genFile)
},
}
cmd.Flags().String(cli.HomeFlag, defaultNodeHome, "node's home directory")
cmd.Flags().String(flags.FlagKeyringBackend, flags.DefaultKeyringBackend, "Select keyring's backend (os|file|test)")
cmd.Flags().String(flagClientHome, defaultClientHome, "client's home directory")
cmd.Flags().String(flagVestingAmt, "", "amount of coins for vesting accounts")
cmd.Flags().Uint64(flagVestingStart, 0, "schedule start time (unix epoch) for vesting accounts")
cmd.Flags().Uint64(flagVestingEnd, 0, "schedule end time (unix epoch) for vesting accounts")
return cmd
}

114
cmd/wasmgovd/main.go Normal file
View File

@@ -0,0 +1,114 @@
package main
import (
"encoding/json"
"io"
"os"
"github.com/CosmWasm/wasmd/app"
"github.com/CosmWasm/wasmd/x/wasm"
"github.com/cosmos/cosmos-sdk/baseapp"
"github.com/cosmos/cosmos-sdk/client/debug"
"github.com/cosmos/cosmos-sdk/client/flags"
"github.com/cosmos/cosmos-sdk/server"
"github.com/cosmos/cosmos-sdk/store"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/x/auth"
genutilcli "github.com/cosmos/cosmos-sdk/x/genutil/client/cli"
"github.com/cosmos/cosmos-sdk/x/staking"
"github.com/spf13/cobra"
"github.com/spf13/viper"
abci "github.com/tendermint/tendermint/abci/types"
"github.com/tendermint/tendermint/libs/cli"
"github.com/tendermint/tendermint/libs/log"
tmtypes "github.com/tendermint/tendermint/types"
dbm "github.com/tendermint/tm-db"
)
const flagInvCheckPeriod = "inv-check-period"
var invCheckPeriod uint
func main() {
cdc := app.MakeCodec()
config := sdk.GetConfig()
config.SetBech32PrefixForAccount(sdk.Bech32PrefixAccAddr, sdk.Bech32PrefixAccPub)
config.SetBech32PrefixForValidator(sdk.Bech32PrefixValAddr, sdk.Bech32PrefixValPub)
config.SetBech32PrefixForConsensusNode(sdk.Bech32PrefixConsAddr, sdk.Bech32PrefixConsPub)
config.Seal()
homeDir := os.ExpandEnv("$HOME/.wasmgovd")
ctx := server.NewDefaultContext()
cobra.EnableCommandSorting = false
rootCmd := &cobra.Command{
Use: "wasmgovd",
Short: "Wasm Daemon (server) with wasm gov proposals enabled\",",
PersistentPreRunE: server.PersistentPreRunEFn(ctx),
}
rootCmd.AddCommand(genutilcli.InitCmd(ctx, cdc, app.ModuleBasics, homeDir))
rootCmd.AddCommand(genutilcli.CollectGenTxsCmd(ctx, cdc, auth.GenesisAccountIterator{}, homeDir))
rootCmd.AddCommand(genutilcli.MigrateGenesisCmd(ctx, cdc))
rootCmd.AddCommand(
genutilcli.GenTxCmd(
ctx, cdc, app.ModuleBasics, staking.AppModuleBasic{},
auth.GenesisAccountIterator{}, homeDir, app.DefaultCLIHome,
),
)
rootCmd.AddCommand(genutilcli.ValidateGenesisCmd(ctx, cdc, app.ModuleBasics))
rootCmd.AddCommand(AddGenesisAccountCmd(ctx, cdc, homeDir, app.DefaultCLIHome))
rootCmd.AddCommand(flags.NewCompletionCmd(rootCmd, true))
// rootCmd.AddCommand(testnetCmd(ctx, cdc, app.ModuleBasics, auth.GenesisAccountIterator{}))
rootCmd.AddCommand(replayCmd())
rootCmd.AddCommand(debug.Cmd(cdc))
server.AddCommands(ctx, cdc, rootCmd, newApp, exportAppStateAndTMValidators)
// prepare and add flags
executor := cli.PrepareBaseCmd(rootCmd, "WM", homeDir)
rootCmd.PersistentFlags().UintVar(&invCheckPeriod, flagInvCheckPeriod,
0, "Assert registered invariants every N blocks")
err := executor.Execute()
if err != nil {
panic(err)
}
}
func newApp(logger log.Logger, db dbm.DB, traceStore io.Writer) abci.Application {
var cache sdk.MultiStorePersistentCache
if viper.GetBool(server.FlagInterBlockCache) {
cache = store.NewCommitKVStoreCacheManager()
}
skipUpgradeHeights := make(map[int64]bool)
for _, h := range viper.GetIntSlice(server.FlagUnsafeSkipUpgrades) {
skipUpgradeHeights[int64(h)] = true
}
return app.NewWasmApp(logger, db, traceStore, true, invCheckPeriod,
wasm.EnableAllProposals, skipUpgradeHeights,
baseapp.SetPruning(store.NewPruningOptionsFromString(viper.GetString("pruning"))),
baseapp.SetMinGasPrices(viper.GetString(server.FlagMinGasPrices)),
baseapp.SetHaltHeight(viper.GetUint64(server.FlagHaltHeight)),
baseapp.SetHaltTime(viper.GetUint64(server.FlagHaltTime)),
baseapp.SetInterBlockCache(cache))
}
func exportAppStateAndTMValidators(
logger log.Logger, db dbm.DB, traceStore io.Writer, height int64, forZeroHeight bool, jailWhiteList []string,
) (json.RawMessage, []tmtypes.GenesisValidator, error) {
if height != -1 {
gapp := app.NewWasmApp(logger, db, traceStore, false, uint(1), wasm.EnableAllProposals, nil)
err := gapp.LoadHeight(height)
if err != nil {
return nil, nil, err
}
return gapp.ExportAppStateAndValidators(forZeroHeight, jailWhiteList)
}
gapp := app.NewWasmApp(logger, db, traceStore, true, uint(1), wasm.EnableAllProposals, nil)
return gapp.ExportAppStateAndValidators(forZeroHeight, jailWhiteList)
}

191
cmd/wasmgovd/replay.go Normal file
View File

@@ -0,0 +1,191 @@
package main
import (
"fmt"
"io"
"os"
"path/filepath"
"time"
"github.com/CosmWasm/wasmd/app"
"github.com/CosmWasm/wasmd/x/wasm"
"github.com/cosmos/cosmos-sdk/baseapp"
"github.com/cosmos/cosmos-sdk/server"
"github.com/cosmos/cosmos-sdk/store"
sdk "github.com/cosmos/cosmos-sdk/types"
cpm "github.com/otiai10/copy"
"github.com/spf13/cobra"
abci "github.com/tendermint/tendermint/abci/types"
tmos "github.com/tendermint/tendermint/libs/os"
"github.com/tendermint/tendermint/proxy"
tmsm "github.com/tendermint/tendermint/state"
tmstore "github.com/tendermint/tendermint/store"
tm "github.com/tendermint/tendermint/types"
)
func replayCmd() *cobra.Command {
return &cobra.Command{
Use: "replay <root-dir>",
Short: "Replay gaia transactions",
RunE: func(_ *cobra.Command, args []string) error {
return replayTxs(args[0])
},
Args: cobra.ExactArgs(1),
}
}
func replayTxs(rootDir string) error {
if false {
// Copy the rootDir to a new directory, to preserve the old one.
fmt.Fprintln(os.Stderr, "Copying rootdir over")
oldRootDir := rootDir
rootDir = oldRootDir + "_replay"
if tmos.FileExists(rootDir) {
tmos.Exit(fmt.Sprintf("temporary copy dir %v already exists", rootDir))
}
if err := cpm.Copy(oldRootDir, rootDir); err != nil {
return err
}
}
configDir := filepath.Join(rootDir, "config")
dataDir := filepath.Join(rootDir, "data")
ctx := server.NewDefaultContext()
// App DB
// appDB := dbm.NewMemDB()
fmt.Fprintln(os.Stderr, "Opening app database")
appDB, err := sdk.NewLevelDB("application", dataDir)
if err != nil {
return err
}
// TM DB
// tmDB := dbm.NewMemDB()
fmt.Fprintln(os.Stderr, "Opening tendermint state database")
tmDB, err := sdk.NewLevelDB("state", dataDir)
if err != nil {
return err
}
// Blockchain DB
fmt.Fprintln(os.Stderr, "Opening blockstore database")
bcDB, err := sdk.NewLevelDB("blockstore", dataDir)
if err != nil {
return err
}
// TraceStore
var traceStoreWriter io.Writer
var traceStoreDir = filepath.Join(dataDir, "trace.log")
traceStoreWriter, err = os.OpenFile(
traceStoreDir,
os.O_WRONLY|os.O_APPEND|os.O_CREATE,
0666,
)
if err != nil {
return err
}
// Application
fmt.Fprintln(os.Stderr, "Creating application")
gapp := app.NewWasmApp(
// TODO: do we want to set skipUpgradeHieghts here?
ctx.Logger, appDB, traceStoreWriter, true, uint(1), wasm.EnableAllProposals, nil,
baseapp.SetPruning(store.PruneEverything))
// Genesis
var genDocPath = filepath.Join(configDir, "genesis.json")
genDoc, err := tm.GenesisDocFromFile(genDocPath)
if err != nil {
return err
}
genState, err := tmsm.MakeGenesisState(genDoc)
if err != nil {
return err
}
// tmsm.SaveState(tmDB, genState)
cc := proxy.NewLocalClientCreator(gapp)
proxyApp := proxy.NewAppConns(cc)
err = proxyApp.Start()
if err != nil {
return err
}
defer func() {
err = proxyApp.Stop()
if err != nil {
return
}
}()
state := tmsm.LoadState(tmDB)
if state.LastBlockHeight == 0 {
// Send InitChain msg
fmt.Fprintln(os.Stderr, "Sending InitChain msg")
validators := tm.TM2PB.ValidatorUpdates(genState.Validators)
csParams := tm.TM2PB.ConsensusParams(genDoc.ConsensusParams)
req := abci.RequestInitChain{
Time: genDoc.GenesisTime,
ChainId: genDoc.ChainID,
ConsensusParams: csParams,
Validators: validators,
AppStateBytes: genDoc.AppState,
}
res, err := proxyApp.Consensus().InitChainSync(req)
if err != nil {
return err
}
newValidatorz, err := tm.PB2TM.ValidatorUpdates(res.Validators)
if err != nil {
return err
}
newValidators := tm.NewValidatorSet(newValidatorz)
// Take the genesis state.
state = genState
state.Validators = newValidators
state.NextValidators = newValidators
}
// Create executor
fmt.Fprintln(os.Stderr, "Creating block executor")
blockExec := tmsm.NewBlockExecutor(tmDB, ctx.Logger, proxyApp.Consensus(), nil, tmsm.MockEvidencePool{})
// Create block store
fmt.Fprintln(os.Stderr, "Creating block store")
blockStore := tmstore.NewBlockStore(bcDB)
tz := []time.Duration{0, 0, 0}
for i := int(state.LastBlockHeight) + 1; ; i++ {
fmt.Fprintln(os.Stderr, "Running block ", i)
t1 := time.Now()
// Apply block
fmt.Printf("loading and applying block %d\n", i)
blockmeta := blockStore.LoadBlockMeta(int64(i))
if blockmeta == nil {
fmt.Printf("Couldn't find block meta %d... done?\n", i)
return nil
}
block := blockStore.LoadBlock(int64(i))
if block == nil {
return fmt.Errorf("couldn't find block %d", i)
}
t2 := time.Now()
state, err = blockExec.ApplyBlock(state, blockmeta.BlockID, block)
if err != nil {
return err
}
t3 := time.Now()
tz[0] += t2.Sub(t1)
tz[1] += t3.Sub(t2)
fmt.Fprintf(os.Stderr, "new app hash: %X\n", state.AppHash)
fmt.Fprintln(os.Stderr, tz)
}
}

374
cmd/wasmgovd/testnet.go Normal file
View File

@@ -0,0 +1,374 @@
package main
// DONTCOVER
import (
"bufio"
"encoding/json"
"fmt"
"net"
"os"
"path/filepath"
"github.com/spf13/cobra"
"github.com/spf13/viper"
tmconfig "github.com/tendermint/tendermint/config"
"github.com/tendermint/tendermint/crypto"
tmos "github.com/tendermint/tendermint/libs/os"
tmrand "github.com/tendermint/tendermint/libs/rand"
"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/keys"
"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"
"github.com/cosmos/cosmos-sdk/x/auth"
authexported "github.com/cosmos/cosmos-sdk/x/auth/exported"
"github.com/cosmos/cosmos-sdk/x/genutil"
genutiltypes "github.com/cosmos/cosmos-sdk/x/genutil/types"
"github.com/cosmos/cosmos-sdk/x/staking"
)
var (
flagNodeDirPrefix = "node-dir-prefix"
flagNumValidators = "v"
flagOutputDir = "output-dir"
flagNodeDaemonHome = "node-daemon-home"
flagNodeCLIHome = "node-cli-home"
flagStartingIPAddress = "starting-ip-address"
)
// get cmd to initialize all files for tendermint testnet and application
func testnetCmd(ctx *server.Context, cdc *codec.Codec,
mbm module.BasicManager, genAccIterator genutiltypes.GenesisAccountsIterator,
) *cobra.Command {
cmd := &cobra.Command{
Use: "testnet",
Short: "Initialize files for a Wasmd 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:
wasmd testnet --v 4 --output-dir ./output --starting-ip-address 192.168.10.2
`,
RunE: func(cmd *cobra.Command, _ []string) error {
config := ctx.Config
outputDir := viper.GetString(flagOutputDir)
chainID := viper.GetString(flags.FlagChainID)
minGasPrices := viper.GetString(server.FlagMinGasPrices)
nodeDirPrefix := viper.GetString(flagNodeDirPrefix)
nodeDaemonHome := viper.GetString(flagNodeDaemonHome)
nodeCLIHome := viper.GetString(flagNodeCLIHome)
startingIPAddress := viper.GetString(flagStartingIPAddress)
numValidators := viper.GetInt(flagNumValidators)
return InitTestnet(cmd, config, cdc, mbm, genAccIterator, outputDir, chainID,
minGasPrices, nodeDirPrefix, nodeDaemonHome, nodeCLIHome, startingIPAddress, numValidators)
},
}
cmd.Flags().Int(flagNumValidators, 4,
"Number of validators to initialize the testnet with")
cmd.Flags().StringP(flagOutputDir, "o", "./mytestnet",
"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, "wasmd",
"Home directory of the node's daemon configuration")
cmd.Flags().String(flagNodeCLIHome, "wasmcli",
"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", sdk.DefaultBondDenom),
"Minimum gas prices to accept for transactions; All fees in a tx must meet this minimum (e.g. 0.01photino,0.001stake)")
cmd.Flags().String(flags.FlagKeyringBackend, flags.DefaultKeyringBackend, "Select keyring's backend (os|file|test)")
return cmd
}
const nodeDirPerm = 0755
// Initialize the testnet
func InitTestnet(cmd *cobra.Command, config *tmconfig.Config, cdc *codec.Codec,
mbm module.BasicManager, genAccIterator genutiltypes.GenesisAccountsIterator,
outputDir, chainID, minGasPrices, nodeDirPrefix, nodeDaemonHome,
nodeCLIHome, startingIPAddress string, numValidators int) error {
if chainID == "" {
chainID = "chain-" + tmrand.Str(6)
}
monikers := make([]string, numValidators)
nodeIDs := make([]string, numValidators)
valPubKeys := make([]crypto.PubKey, numValidators)
wasmConfig := srvconfig.DefaultConfig()
wasmConfig.MinGasPrices = minGasPrices
//nolint:prealloc
var (
genAccounts []authexported.GenesisAccount
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
}
monikers = append(monikers, nodeDirName)
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 := keys.NewKeyring(
sdk.KeyringServiceName(),
viper.GetString(flags.FlagKeyringBackend),
viper.GetString(flagClientHome),
inBuf,
)
if err != nil {
return err
}
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(500)
coins := sdk.Coins{
sdk.NewCoin(fmt.Sprintf("%stoken", nodeDirName), accTokens),
sdk.NewCoin(sdk.DefaultBondDenom, accStakingTokens),
}
genAccounts = append(genAccounts, auth.NewBaseAccount(addr, coins.Sort(), nil, 0, 0))
valTokens := sdk.TokensFromConsensusPower(100)
msg := staking.NewMsgCreateValidator(
sdk.ValAddress(addr),
valPubKeys[i],
sdk.NewCoin(sdk.DefaultBondDenom, valTokens),
staking.NewDescription(nodeDirName, "", "", "", ""),
staking.NewCommissionRates(sdk.ZeroDec(), sdk.ZeroDec(), sdk.ZeroDec()),
sdk.OneInt(),
)
tx := auth.NewStdTx([]sdk.Msg{msg}, auth.StdFee{}, []auth.StdSignature{}, memo)
txBldr := auth.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
}
// TODO: Rename config file to server.toml as it's not particular to Gaia
// (REF: https://github.com/cosmos/cosmos-sdk/issues/4125).
wasmConfigFilePath := filepath.Join(nodeDir, "config/wasmd.toml")
srvconfig.WriteConfigFile(wasmConfigFilePath, wasmConfig)
}
if err := initGenFiles(cdc, mbm, chainID, genAccounts, genFiles, numValidators); err != nil {
return err
}
err := collectGenFiles(
cdc, config, chainID, monikers, nodeIDs, valPubKeys, numValidators,
outputDir, nodeDirPrefix, nodeDaemonHome, genAccIterator,
)
if err != nil {
return err
}
cmd.PrintErrf("Successfully initialized %d node directories\n", numValidators)
return nil
}
func initGenFiles(
cdc *codec.Codec, mbm module.BasicManager, chainID string,
genAccounts []authexported.GenesisAccount, genFiles []string, numValidators int,
) error {
appGenState := mbm.DefaultGenesis()
// set the accounts in the genesis state
authDataBz := appGenState[auth.ModuleName]
var authGenState auth.GenesisState
cdc.MustUnmarshalJSON(authDataBz, &authGenState)
authGenState.Accounts = genAccounts
appGenState[auth.ModuleName] = cdc.MustMarshalJSON(authGenState)
appGenStateJSON, err := codec.MarshalJSONIndent(cdc, appGenState)
if err != nil {
return err
}
genDoc := types.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,
monikers, nodeIDs []string, valPubKeys []crypto.PubKey,
numValidators int, outputDir, nodeDirPrefix, nodeDaemonHome string,
genAccIterator genutiltypes.GenesisAccountsIterator) 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")
moniker := monikers[i]
config.Moniker = nodeDirName
config.SetRoot(nodeDir)
nodeID, valPubKey := nodeIDs[i], valPubKeys[i]
initCfg := genutil.NewInitConfig(chainID, gentxsDir, moniker, nodeID, valPubKey)
genDoc, err := types.GenesisDocFromFile(config.GenesisFile())
if err != nil {
return err
}
nodeAppState, err := genutil.GenAppStateFromConfig(cdc, config, initCfg, *genDoc, genAccIterator)
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, 0700)
if err != nil {
return err
}
err = tmos.WriteFile(file, contents, 0600)
if err != nil {
return err
}
return nil
}

10
docker/run_wasmgovd.sh Executable file
View File

@@ -0,0 +1,10 @@
#!/bin/sh
if test -n "$1"; then
# need -R not -r to copy hidden files
cp -R "$1/.wasmd" /root
cp -R "$1/.wasmcli" /root
fi
mkdir -p /root/log
wasmgovd start --rpc.laddr tcp://0.0.0.0:26657 --trace

24
docker/setup_wasmgovd.sh Executable file
View File

@@ -0,0 +1,24 @@
#!/bin/sh
#set -o errexit -o nounset -o pipefail
PASSWORD=${PASSWORD:-1234567890}
STAKE=${STAKE_TOKEN:-ustake}
FEE=${FEE_TOKEN:-ucosm}
wasmgovd init --chain-id=testing testing
sed -i 's/permission": "Everybody"/permission": "Nobody"/' "$HOME"/.wasmgovd/config/genesis.json
# staking/governance token is hardcoded in config, change this
sed -i "s/\"stake\"/\"$STAKE\"/" "$HOME"/.wasmgovd/config/genesis.json
if ! wasmcli keys show validator; then
(echo "$PASSWORD"; echo "$PASSWORD") | wasmcli keys add validator
fi
# hardcode the validator account for this instance
echo "$PASSWORD" | wasmgovd add-genesis-account validator "1000000000$STAKE,1000000000$FEE"
# (optionally) add a few more genesis accounts
for addr in "$@"; do
echo $addr
wasmgovd add-genesis-account "$addr" "1000000000$STAKE,1000000000$FEE"
done
# submit a genesis validator tx
(echo "$PASSWORD"; echo "$PASSWORD"; echo "$PASSWORD") | wasmgovd gentx --name validator --amount "250000000$STAKE"
wasmgovd collect-gentxs

File diff suppressed because one or more lines are too long

View File

@@ -306,7 +306,7 @@ func TestInstantiate(t *testing.T) {
require.Equal(t, "cosmos18vd8fpwxzck93qlwghaj6arh4p7c5n89uzcee5", addr.String()) require.Equal(t, "cosmos18vd8fpwxzck93qlwghaj6arh4p7c5n89uzcee5", addr.String())
gasAfter := ctx.GasMeter().GasConsumed() gasAfter := ctx.GasMeter().GasConsumed()
require.Equal(t, uint64(0x11542), gasAfter-gasBefore) require.Equal(t, uint64(0x1155d), gasAfter-gasBefore)
// ensure it is stored properly // ensure it is stored properly
info := keeper.GetContractInfo(ctx, addr) info := keeper.GetContractInfo(ctx, addr)
@@ -527,7 +527,7 @@ func TestExecute(t *testing.T) {
// make sure gas is properly deducted from ctx // make sure gas is properly deducted from ctx
gasAfter := ctx.GasMeter().GasConsumed() gasAfter := ctx.GasMeter().GasConsumed()
require.Equal(t, uint64(0x11bfb), gasAfter-gasBefore) require.Equal(t, uint64(0x11c16), gasAfter-gasBefore)
// ensure bob now exists and got both payments released // ensure bob now exists and got both payments released
bobAcct = accKeeper.GetAccount(ctx, bob) bobAcct = accKeeper.GetAccount(ctx, bob)

View File

@@ -36,7 +36,14 @@ func FuzzStateModel(m *types.Model, c fuzz.Continue) {
} }
func FuzzAccessType(m *types.AccessType, c fuzz.Continue) { func FuzzAccessType(m *types.AccessType, c fuzz.Continue) {
*m = types.AllAccessTypes[c.Int()%len(types.AllAccessTypes)] pos := c.Int() % len(types.AllAccessTypes)
for k, _ := range types.AllAccessTypes {
if pos == 0 {
*m = k
return
}
pos--
}
} }
func FuzzAccessConfig(m *types.AccessConfig, c fuzz.Continue) { func FuzzAccessConfig(m *types.AccessConfig, c fuzz.Continue) {
FuzzAccessType(&m.Type, c) FuzzAccessType(&m.Type, c)

View File

@@ -21,9 +21,9 @@ func (s Sequence) ValidateBasic() error {
// GenesisState is the struct representation of the export genesis // GenesisState is the struct representation of the export genesis
type GenesisState struct { type GenesisState struct {
Params Params `json:"params"` Params Params `json:"params"`
Codes []Code `json:"codes"` Codes []Code `json:"codes,omitempty"`
Contracts []Contract `json:"contracts"` Contracts []Contract `json:"contracts,omitempty"`
Sequences []Sequence `json:"sequences"` Sequences []Sequence `json:"sequences,omitempty"`
} }
func (s GenesisState) ValidateBasic() error { func (s GenesisState) ValidateBasic() error {

View File

@@ -18,16 +18,20 @@ const (
var ParamStoreKeyUploadAccess = []byte("uploadAccess") var ParamStoreKeyUploadAccess = []byte("uploadAccess")
var ParamStoreKeyInstantiateAccess = []byte("instantiateAccess") var ParamStoreKeyInstantiateAccess = []byte("instantiateAccess")
type AccessType uint8 type AccessType string
const ( const (
Undefined AccessType = 0 Undefined AccessType = "Undefined"
Nobody AccessType = 1 Nobody AccessType = "Nobody"
OnlyAddress AccessType = 2 OnlyAddress AccessType = "OnlyAddress"
Everybody AccessType = 3 Everybody AccessType = "Everybody"
) )
var AllAccessTypes = []AccessType{Nobody, OnlyAddress, Everybody} var AllAccessTypes = map[AccessType]struct{}{
Nobody: {},
OnlyAddress: {},
Everybody: {},
}
func (a AccessType) With(addr sdk.AccAddress) AccessConfig { func (a AccessType) With(addr sdk.AccAddress) AccessConfig {
switch a { switch a {
@@ -44,9 +48,26 @@ func (a AccessType) With(addr sdk.AccAddress) AccessConfig {
panic("unsupported access type") panic("unsupported access type")
} }
func (a *AccessType) UnmarshalText(text []byte) error {
s := AccessType(text)
if _, ok := AllAccessTypes[s]; ok {
*a = s
return nil
}
*a = Undefined
return nil
}
func (a AccessType) MarshalText() ([]byte, error) {
if _, ok := AllAccessTypes[a]; ok {
return []byte(a), nil
}
return []byte(Undefined), nil
}
type AccessConfig struct { type AccessConfig struct {
Type AccessType `json:"type" yaml:"type"` Type AccessType `json:"permission" yaml:"permission"`
Address sdk.AccAddress `json:"address" yaml:"address"` Address sdk.AccAddress `json:"address,omitempty" yaml:"address"`
} }
func (a AccessConfig) Equals(o AccessConfig) bool { func (a AccessConfig) Equals(o AccessConfig) bool {
@@ -61,7 +82,7 @@ var (
// Params defines the set of wasm parameters. // Params defines the set of wasm parameters.
type Params struct { type Params struct {
UploadAccess AccessConfig `json:"upload_access" yaml:"upload_access"` UploadAccess AccessConfig `json:"code_upload_access" yaml:"code_upload_access"`
DefaultInstantiatePermission AccessType `json:"instantiate_default_permission" yaml:"instantiate_default_permission"` DefaultInstantiatePermission AccessType `json:"instantiate_default_permission" yaml:"instantiate_default_permission"`
} }
@@ -118,12 +139,10 @@ func validateAccessType(i interface{}) error {
if v == Undefined { if v == Undefined {
return sdkerrors.Wrap(ErrEmpty, "type") return sdkerrors.Wrap(ErrEmpty, "type")
} }
for i := range AllAccessTypes { if _, ok := AllAccessTypes[v]; !ok {
if AllAccessTypes[i] == v { return sdkerrors.Wrapf(ErrInvalid, "unknown type: %q", v)
return nil
}
} }
return sdkerrors.Wrapf(ErrInvalid, "unknown type: %d", v) return nil
} }
func (v AccessConfig) ValidateBasic() error { func (v AccessConfig) ValidateBasic() error {
@@ -138,7 +157,7 @@ func (v AccessConfig) ValidateBasic() error {
case OnlyAddress: case OnlyAddress:
return sdk.VerifyAddressFormat(v.Address) return sdk.VerifyAddressFormat(v.Address)
} }
return sdkerrors.Wrapf(ErrInvalid, "unknown type: %d", v.Type) return sdkerrors.Wrapf(ErrInvalid, "unknown type: %q", v.Type)
} }
func (v AccessConfig) Allowed(actor sdk.AccAddress) bool { func (v AccessConfig) Allowed(actor sdk.AccAddress) bool {

View File

@@ -1,9 +1,11 @@
package types package types
import ( import (
"encoding/json"
"testing" "testing"
sdk "github.com/cosmos/cosmos-sdk/types" sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )
@@ -41,14 +43,14 @@ func TestValidateParams(t *testing.T) {
"reject empty type in instantiate permission": { "reject empty type in instantiate permission": {
src: Params{ src: Params{
UploadAccess: AllowNobody, UploadAccess: AllowNobody,
DefaultInstantiatePermission: 0, DefaultInstantiatePermission: "",
}, },
expErr: true, expErr: true,
}, },
"reject unknown type in instantiate": { "reject unknown type in instantiate": {
src: Params{ src: Params{
UploadAccess: AllowNobody, UploadAccess: AllowNobody,
DefaultInstantiatePermission: 4, DefaultInstantiatePermission: "Undefined",
}, },
expErr: true, expErr: true,
}, },
@@ -84,5 +86,44 @@ func TestValidateParams(t *testing.T) {
} }
}) })
} }
}
func TestAccessTypeMarshalJson(t *testing.T) {
specs := map[string]struct {
src AccessType
exp string
}{
"Undefined": {src: Undefined, exp: `"Undefined"`},
"Nobody": {src: Nobody, exp: `"Nobody"`},
"OnlyAddress": {src: OnlyAddress, exp: `"OnlyAddress"`},
"Everybody": {src: Everybody, exp: `"Everybody"`},
"unknown": {src: "", exp: `"Undefined"`},
}
for msg, spec := range specs {
t.Run(msg, func(t *testing.T) {
got, err := json.Marshal(spec.src)
require.NoError(t, err)
assert.Equal(t, []byte(spec.exp), got)
})
}
}
func TestAccessTypeUnMarshalJson(t *testing.T) {
specs := map[string]struct {
src string
exp AccessType
}{
"Undefined": {src: `"Undefined"`, exp: Undefined},
"Nobody": {src: `"Nobody"`, exp: Nobody},
"OnlyAddress": {src: `"OnlyAddress"`, exp: OnlyAddress},
"Everybody": {src: `"Everybody"`, exp: Everybody},
"unknown": {src: `""`, exp: Undefined},
}
for msg, spec := range specs {
t.Run(msg, func(t *testing.T) {
var got AccessType
err := json.Unmarshal([]byte(spec.src), &got)
require.NoError(t, err)
assert.Equal(t, spec.exp, got)
})
}
} }