mirror of https://github.com/certusone/wasmd.git
505 lines
14 KiB
Go
505 lines
14 KiB
Go
package lcdtest
|
|
|
|
import (
|
|
"bytes"
|
|
"fmt"
|
|
"io/ioutil"
|
|
"net"
|
|
"os"
|
|
"path/filepath"
|
|
"sort"
|
|
"strings"
|
|
|
|
"github.com/pkg/errors"
|
|
"github.com/spf13/viper"
|
|
|
|
"github.com/cosmos/cosmos-sdk/baseapp"
|
|
"github.com/cosmos/cosmos-sdk/client"
|
|
"github.com/cosmos/cosmos-sdk/client/lcd"
|
|
"github.com/cosmos/cosmos-sdk/codec"
|
|
crkeys "github.com/cosmos/cosmos-sdk/crypto/keys"
|
|
"github.com/cosmos/cosmos-sdk/server"
|
|
"github.com/cosmos/cosmos-sdk/store"
|
|
"github.com/cosmos/cosmos-sdk/tests"
|
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
|
"github.com/cosmos/cosmos-sdk/x/auth"
|
|
authrest "github.com/cosmos/cosmos-sdk/x/auth/client/rest"
|
|
authexported "github.com/cosmos/cosmos-sdk/x/auth/exported"
|
|
"github.com/cosmos/cosmos-sdk/x/crisis"
|
|
distr "github.com/cosmos/cosmos-sdk/x/distribution"
|
|
"github.com/cosmos/cosmos-sdk/x/genutil"
|
|
"github.com/cosmos/cosmos-sdk/x/mint"
|
|
"github.com/cosmos/cosmos-sdk/x/staking"
|
|
"github.com/cosmos/cosmos-sdk/x/supply"
|
|
|
|
tmcfg "github.com/tendermint/tendermint/config"
|
|
"github.com/tendermint/tendermint/crypto"
|
|
"github.com/tendermint/tendermint/crypto/ed25519"
|
|
"github.com/tendermint/tendermint/crypto/secp256k1"
|
|
"github.com/tendermint/tendermint/libs/cli"
|
|
"github.com/tendermint/tendermint/libs/log"
|
|
nm "github.com/tendermint/tendermint/node"
|
|
"github.com/tendermint/tendermint/p2p"
|
|
pvm "github.com/tendermint/tendermint/privval"
|
|
"github.com/tendermint/tendermint/proxy"
|
|
ctypes "github.com/tendermint/tendermint/rpc/core/types"
|
|
tmrpc "github.com/tendermint/tendermint/rpc/lib/server"
|
|
tmtypes "github.com/tendermint/tendermint/types"
|
|
dbm "github.com/tendermint/tm-db"
|
|
|
|
"github.com/cosmos/gaia/app"
|
|
)
|
|
|
|
// TODO: Make InitializeTestLCD safe to call in multiple tests at the same time
|
|
// InitializeLCD starts Tendermint and the LCD in process, listening on
|
|
// their respective sockets where nValidators is the total number of validators
|
|
// and initAddrs are the accounts to initialize with some stake tokens. It
|
|
// returns a cleanup function, a set of validator public keys, and a port.
|
|
func InitializeLCD(nValidators int, initAddrs []sdk.AccAddress, minting bool, portExt ...string) (
|
|
cleanup func(), valConsPubKeys []crypto.PubKey, valOperAddrs []sdk.ValAddress, port string, err error) {
|
|
|
|
config, err := GetConfig()
|
|
if err != nil {
|
|
return
|
|
}
|
|
config.Consensus.TimeoutCommit = 100
|
|
config.Consensus.SkipTimeoutCommit = false
|
|
config.TxIndex.IndexAllTags = true
|
|
|
|
logger := log.NewTMLogger(log.NewSyncWriter(os.Stdout))
|
|
logger = log.NewFilter(logger, log.AllowError())
|
|
|
|
db := dbm.NewMemDB()
|
|
gapp := app.NewGaiaApp(logger, db, nil, true, 0, baseapp.SetPruning(store.PruneNothing))
|
|
cdc = app.MakeCodec()
|
|
|
|
genDoc, valConsPubKeys, valOperAddrs, privVal, err := defaultGenesis(config, nValidators, initAddrs, minting)
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
var listenAddr string
|
|
|
|
if len(portExt) == 0 {
|
|
listenAddr, port, err = server.FreeTCPAddr()
|
|
if err != nil {
|
|
return
|
|
}
|
|
} else {
|
|
listenAddr = fmt.Sprintf("tcp://0.0.0.0:%s", portExt[0])
|
|
port = portExt[0]
|
|
}
|
|
|
|
// XXX: Need to set this so LCD knows the tendermint node address!
|
|
viper.Set(client.FlagNode, config.RPC.ListenAddress)
|
|
viper.Set(client.FlagChainID, genDoc.ChainID)
|
|
// TODO Set to false once the upstream Tendermint proof verification issue is fixed.
|
|
viper.Set(client.FlagTrustNode, true)
|
|
|
|
node, err := startTM(config, logger, genDoc, privVal, gapp)
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
tests.WaitForNextHeightTM(tests.ExtractPortFromAddress(config.RPC.ListenAddress))
|
|
lcdInstance, err := startLCD(logger, listenAddr, cdc)
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
tests.WaitForLCDStart(port)
|
|
tests.WaitForHeight(1, port)
|
|
|
|
cleanup = func() {
|
|
logger.Debug("cleaning up LCD initialization")
|
|
err = node.Stop()
|
|
if err != nil {
|
|
logger.Error(err.Error())
|
|
}
|
|
|
|
node.Wait()
|
|
err = lcdInstance.Close()
|
|
if err != nil {
|
|
logger.Error(err.Error())
|
|
}
|
|
}
|
|
|
|
return cleanup, valConsPubKeys, valOperAddrs, port, err
|
|
}
|
|
|
|
func defaultGenesis(config *tmcfg.Config, nValidators int, initAddrs []sdk.AccAddress, minting bool) (
|
|
genDoc *tmtypes.GenesisDoc, valConsPubKeys []crypto.PubKey, valOperAddrs []sdk.ValAddress, privVal *pvm.FilePV, err error) {
|
|
privVal = pvm.LoadOrGenFilePV(config.PrivValidatorKeyFile(),
|
|
config.PrivValidatorStateFile())
|
|
privVal.Reset()
|
|
|
|
if nValidators < 1 {
|
|
err = errors.New("initializeLCD must use at least one validator")
|
|
return
|
|
}
|
|
|
|
genesisFile := config.GenesisFile()
|
|
genDoc, err = tmtypes.GenesisDocFromFile(genesisFile)
|
|
if err != nil {
|
|
return
|
|
}
|
|
genDoc.Validators = nil
|
|
err = genDoc.SaveAs(genesisFile)
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
// append any additional (non-proposing) validators
|
|
//nolint:prealloc
|
|
var (
|
|
genTxs []auth.StdTx
|
|
genAccounts []authexported.GenesisAccount
|
|
)
|
|
totalSupply := sdk.ZeroInt()
|
|
|
|
for i := 0; i < nValidators; i++ {
|
|
operPrivKey := secp256k1.GenPrivKey()
|
|
operAddr := operPrivKey.PubKey().Address()
|
|
pubKey := privVal.GetPubKey()
|
|
|
|
power := int64(100)
|
|
if i > 0 {
|
|
pubKey = ed25519.GenPrivKey().PubKey()
|
|
power = 1
|
|
}
|
|
|
|
startTokens := sdk.TokensFromConsensusPower(power)
|
|
|
|
msg := staking.NewMsgCreateValidator(
|
|
sdk.ValAddress(operAddr),
|
|
pubKey,
|
|
sdk.NewCoin(sdk.DefaultBondDenom, startTokens),
|
|
staking.NewDescription(fmt.Sprintf("validator-%d", i+1), "", "", "", ""),
|
|
staking.NewCommissionRates(sdk.ZeroDec(), sdk.ZeroDec(), sdk.ZeroDec()),
|
|
sdk.OneInt(),
|
|
)
|
|
|
|
stdSignMsg := auth.StdSignMsg{
|
|
ChainID: genDoc.ChainID,
|
|
Msgs: []sdk.Msg{msg},
|
|
}
|
|
|
|
var sig []byte
|
|
sig, err = operPrivKey.Sign(stdSignMsg.Bytes())
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
transaction := auth.NewStdTx([]sdk.Msg{msg}, auth.StdFee{}, []auth.StdSignature{{Signature: sig, PubKey: operPrivKey.PubKey()}}, "")
|
|
genTxs = append(genTxs, transaction)
|
|
valConsPubKeys = append(valConsPubKeys, pubKey)
|
|
valOperAddrs = append(valOperAddrs, sdk.ValAddress(operAddr))
|
|
|
|
account := auth.NewBaseAccountWithAddress(sdk.AccAddress(operAddr))
|
|
accTokens := sdk.TokensFromConsensusPower(150)
|
|
totalSupply = totalSupply.Add(accTokens)
|
|
|
|
account.Coins = sdk.NewCoins(sdk.NewCoin(sdk.DefaultBondDenom, accTokens))
|
|
genAccounts = append(genAccounts, &account)
|
|
}
|
|
|
|
genesisState := app.NewDefaultGenesisState()
|
|
genDoc.AppState, err = cdc.MarshalJSON(genesisState)
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
genesisState, err = genutil.SetGenTxsInAppGenesisState(cdc, genesisState, genTxs)
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
// add some tokens to init accounts
|
|
for _, addr := range initAddrs {
|
|
accAuth := auth.NewBaseAccountWithAddress(addr)
|
|
accTokens := sdk.TokensFromConsensusPower(100)
|
|
accAuth.Coins = sdk.Coins{sdk.NewCoin(sdk.DefaultBondDenom, accTokens)}
|
|
totalSupply = totalSupply.Add(accTokens)
|
|
|
|
genAccounts = append(genAccounts, &accAuth)
|
|
}
|
|
|
|
// auth genesis state: params and genesis accounts
|
|
authDataBz := genesisState[auth.ModuleName]
|
|
var authGenState auth.GenesisState
|
|
cdc.MustUnmarshalJSON(authDataBz, &authGenState)
|
|
authGenState.Accounts = genAccounts
|
|
genesisState[auth.ModuleName] = cdc.MustMarshalJSON(authGenState)
|
|
|
|
stakingDataBz := genesisState[staking.ModuleName]
|
|
var stakingData staking.GenesisState
|
|
cdc.MustUnmarshalJSON(stakingDataBz, &stakingData)
|
|
genesisState[staking.ModuleName] = cdc.MustMarshalJSON(stakingData)
|
|
|
|
// distr data
|
|
distrDataBz := genesisState[distr.ModuleName]
|
|
var distrData distr.GenesisState
|
|
cdc.MustUnmarshalJSON(distrDataBz, &distrData)
|
|
|
|
commPoolAmt := sdk.NewInt(10)
|
|
distrData.FeePool.CommunityPool = sdk.DecCoins{sdk.NewDecCoin(sdk.DefaultBondDenom, commPoolAmt)}
|
|
distrDataBz = cdc.MustMarshalJSON(distrData)
|
|
genesisState[distr.ModuleName] = distrDataBz
|
|
|
|
// supply data
|
|
supplyDataBz := genesisState[supply.ModuleName]
|
|
var supplyData supply.GenesisState
|
|
cdc.MustUnmarshalJSON(supplyDataBz, &supplyData)
|
|
|
|
supplyData.Supply = sdk.NewCoins(sdk.NewCoin(sdk.DefaultBondDenom, totalSupply.Add(commPoolAmt)))
|
|
supplyDataBz = cdc.MustMarshalJSON(supplyData)
|
|
genesisState[supply.ModuleName] = supplyDataBz
|
|
|
|
// mint genesis (none set within genesisState)
|
|
mintData := mint.DefaultGenesisState()
|
|
inflationMin := sdk.ZeroDec()
|
|
if minting {
|
|
inflationMin = sdk.MustNewDecFromStr("10000.0")
|
|
mintData.Params.InflationMax = sdk.MustNewDecFromStr("15000.0")
|
|
} else {
|
|
mintData.Params.InflationMax = inflationMin
|
|
}
|
|
mintData.Minter.Inflation = inflationMin
|
|
mintData.Params.InflationMin = inflationMin
|
|
mintDataBz := cdc.MustMarshalJSON(mintData)
|
|
genesisState[mint.ModuleName] = mintDataBz
|
|
|
|
// initialize crisis data
|
|
crisisDataBz := genesisState[crisis.ModuleName]
|
|
var crisisData crisis.GenesisState
|
|
cdc.MustUnmarshalJSON(crisisDataBz, &crisisData)
|
|
crisisData.ConstantFee = sdk.NewInt64Coin(sdk.DefaultBondDenom, 1000)
|
|
crisisDataBz = cdc.MustMarshalJSON(crisisData)
|
|
genesisState[crisis.ModuleName] = crisisDataBz
|
|
|
|
//// double check inflation is set according to the minting boolean flag
|
|
if minting {
|
|
if !(mintData.Params.InflationMax.Equal(sdk.MustNewDecFromStr("15000.0")) &&
|
|
mintData.Minter.Inflation.Equal(sdk.MustNewDecFromStr("10000.0")) &&
|
|
mintData.Params.InflationMin.Equal(sdk.MustNewDecFromStr("10000.0"))) {
|
|
err = errors.New("mint parameters does not correspond to their defaults")
|
|
return
|
|
}
|
|
} else {
|
|
if !(mintData.Params.InflationMax.Equal(sdk.ZeroDec()) &&
|
|
mintData.Minter.Inflation.Equal(sdk.ZeroDec()) &&
|
|
mintData.Params.InflationMin.Equal(sdk.ZeroDec())) {
|
|
err = errors.New("mint parameters not equal to decimal 0")
|
|
return
|
|
}
|
|
}
|
|
|
|
appState, err := codec.MarshalJSONIndent(cdc, genesisState)
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
genDoc.AppState = appState
|
|
return genDoc, valConsPubKeys, valOperAddrs, privVal, err
|
|
}
|
|
|
|
// startTM creates and starts an in-process Tendermint node with memDB and
|
|
// in-process ABCI application. It returns the new node or any error that
|
|
// occurred.
|
|
//
|
|
// TODO: Clean up the WAL dir or enable it to be not persistent!
|
|
func startTM(
|
|
tmcfg *tmcfg.Config, logger log.Logger, genDoc *tmtypes.GenesisDoc,
|
|
privVal tmtypes.PrivValidator, app *app.GaiaApp,
|
|
) (*nm.Node, error) {
|
|
|
|
genDocProvider := func() (*tmtypes.GenesisDoc, error) { return genDoc, nil }
|
|
dbProvider := func(*nm.DBContext) (dbm.DB, error) { return dbm.NewMemDB(), nil }
|
|
nodeKey, err := p2p.LoadOrGenNodeKey(tmcfg.NodeKeyFile())
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
node, err := nm.NewNode(
|
|
tmcfg,
|
|
privVal,
|
|
nodeKey,
|
|
proxy.NewLocalClientCreator(app),
|
|
genDocProvider,
|
|
dbProvider,
|
|
nm.DefaultMetricsProvider(tmcfg.Instrumentation),
|
|
logger.With("module", "node"),
|
|
)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
err = node.Start()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
tests.WaitForRPC(tmcfg.RPC.ListenAddress)
|
|
logger.Info("Tendermint running!")
|
|
|
|
return node, err
|
|
}
|
|
|
|
// startLCD starts the LCD.
|
|
func startLCD(logger log.Logger, listenAddr string, cdc *codec.Codec) (net.Listener, error) {
|
|
rs := lcd.NewRestServer(cdc)
|
|
registerRoutes(rs)
|
|
listener, err := tmrpc.Listen(listenAddr, tmrpc.DefaultConfig())
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
go tmrpc.StartHTTPServer(listener, rs.Mux, logger, tmrpc.DefaultConfig()) //nolint:errcheck
|
|
return listener, nil
|
|
}
|
|
|
|
// NOTE: If making updates here also update cmd/gaia/cmd/gaiacli/main.go
|
|
func registerRoutes(rs *lcd.RestServer) {
|
|
client.RegisterRoutes(rs.CliCtx, rs.Mux)
|
|
authrest.RegisterTxRoutes(rs.CliCtx, rs.Mux)
|
|
app.ModuleBasics.RegisterRESTRoutes(rs.CliCtx, rs.Mux)
|
|
}
|
|
|
|
var cdc = codec.New()
|
|
|
|
func init() {
|
|
ctypes.RegisterAmino(cdc)
|
|
}
|
|
|
|
// CreateAddr adds an address to the key store and returns an address and seed.
|
|
// It also requires that the key could be created.
|
|
func CreateAddr(name string, kb crkeys.Keybase) (sdk.AccAddress, string, error) {
|
|
var (
|
|
err error
|
|
info crkeys.Info
|
|
seed string
|
|
)
|
|
info, seed, err = kb.CreateMnemonic(name, crkeys.English, client.DefaultKeyPass, crkeys.Secp256k1)
|
|
return sdk.AccAddress(info.GetPubKey().Address()), seed, err
|
|
}
|
|
|
|
// CreateAddr adds multiple address to the key store and returns the addresses and associated seeds in lexographical order by address.
|
|
// It also requires that the keys could be created.
|
|
func CreateAddrs(kb crkeys.Keybase, numAddrs int) (addrs []sdk.AccAddress, seeds, names []string, errs []error) {
|
|
var (
|
|
err error
|
|
info crkeys.Info
|
|
seed string
|
|
)
|
|
|
|
addrSeeds := AddrSeedSlice{}
|
|
|
|
for i := 0; i < numAddrs; i++ {
|
|
name := fmt.Sprintf("test%d", i)
|
|
info, seed, err = kb.CreateMnemonic(name, crkeys.English, client.DefaultKeyPass, crkeys.Secp256k1)
|
|
if err != nil {
|
|
errs = append(errs, err)
|
|
}
|
|
addrSeeds = append(addrSeeds, AddrSeed{Address: sdk.AccAddress(info.GetPubKey().Address()), Seed: seed, Name: name})
|
|
}
|
|
if len(errs) > 0 {
|
|
return
|
|
}
|
|
|
|
sort.Sort(addrSeeds)
|
|
|
|
for i := range addrSeeds {
|
|
addrs = append(addrs, addrSeeds[i].Address)
|
|
seeds = append(seeds, addrSeeds[i].Seed)
|
|
names = append(names, addrSeeds[i].Name)
|
|
}
|
|
|
|
return addrs, seeds, names, errs
|
|
}
|
|
|
|
// AddrSeed combines an Address with the mnemonic of the private key to that address
|
|
type AddrSeed struct {
|
|
Address sdk.AccAddress
|
|
Seed string
|
|
Name string
|
|
}
|
|
|
|
// AddrSeedSlice implements `Interface` in sort package.
|
|
type AddrSeedSlice []AddrSeed
|
|
|
|
func (b AddrSeedSlice) Len() int {
|
|
return len(b)
|
|
}
|
|
|
|
// Less sorts lexicographically by Address
|
|
func (b AddrSeedSlice) Less(i, j int) bool {
|
|
// bytes package already implements Comparable for []byte.
|
|
switch bytes.Compare(b[i].Address.Bytes(), b[j].Address.Bytes()) {
|
|
case -1:
|
|
return true
|
|
case 0, 1:
|
|
return false
|
|
default:
|
|
panic("not fail-able with `bytes.Comparable` bounded [-1, 1].")
|
|
}
|
|
}
|
|
|
|
func (b AddrSeedSlice) Swap(i, j int) {
|
|
b[j], b[i] = b[i], b[j]
|
|
}
|
|
|
|
// InitClientHome initialises client home dir.
|
|
func InitClientHome(dir string) string {
|
|
var err error
|
|
if dir == "" {
|
|
dir, err = ioutil.TempDir("", "lcd_test")
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
}
|
|
// TODO: this should be set in NewRestServer
|
|
// and pass down the CLIContext to achieve
|
|
// parallelism.
|
|
viper.Set(cli.HomeFlag, dir)
|
|
return dir
|
|
}
|
|
|
|
// makePathname creates a unique pathname for each test.
|
|
func makePathname() (string, error) {
|
|
p, err := os.Getwd()
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
sep := string(filepath.Separator)
|
|
return strings.Replace(p, sep, "_", -1), nil
|
|
}
|
|
|
|
// GetConfig returns a Tendermint config for the test cases.
|
|
func GetConfig() (*tmcfg.Config, error) {
|
|
pathname, err := makePathname()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
config := tmcfg.ResetTestRoot(pathname)
|
|
|
|
tmAddr, _, err := server.FreeTCPAddr()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
rcpAddr, _, err := server.FreeTCPAddr()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
grpcAddr, _, err := server.FreeTCPAddr()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
config.P2P.ListenAddress = tmAddr
|
|
config.RPC.ListenAddress = rcpAddr
|
|
config.RPC.GRPCListenAddress = grpcAddr
|
|
|
|
return config, nil
|
|
}
|