Documentation Structure Change and Cleanup (#2808)
* Update docs/sdk/clients.md * organize ADR directory like tendermint * docs: move spec-proposals into spec/ * remove lotion, moved to website repo * move getting-started to cosmos-hub, and voyager to website * docs: move lite/ into clients/lite/ * move introduction/ content to website repo * move resources/ content to website repo * mv sdk/clients.md to clients/clients.md * mv validators to cosmos-hub/validators * move deprecated sdk/ content to _attic * sdk/modules.md is duplicate with modules/README.md * consolidate remianing sdk/ files into a single sdk.md * move examples/ to docs/examples/ * mv docs/cosmos-hub to docs/gaia * Add keys/accounts section to localnet docs
This commit is contained in:
@@ -0,0 +1,197 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"os"
|
||||
|
||||
abci "github.com/tendermint/tendermint/abci/types"
|
||||
cmn "github.com/tendermint/tendermint/libs/common"
|
||||
dbm "github.com/tendermint/tendermint/libs/db"
|
||||
"github.com/tendermint/tendermint/libs/log"
|
||||
tmtypes "github.com/tendermint/tendermint/types"
|
||||
|
||||
bam "github.com/cosmos/cosmos-sdk/baseapp"
|
||||
"github.com/cosmos/cosmos-sdk/codec"
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
"github.com/cosmos/cosmos-sdk/x/auth"
|
||||
"github.com/cosmos/cosmos-sdk/x/bank"
|
||||
"github.com/cosmos/cosmos-sdk/x/ibc"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/docs/examples/democoin/types"
|
||||
"github.com/cosmos/cosmos-sdk/docs/examples/democoin/x/cool"
|
||||
"github.com/cosmos/cosmos-sdk/docs/examples/democoin/x/pow"
|
||||
"github.com/cosmos/cosmos-sdk/docs/examples/democoin/x/simplestake"
|
||||
"github.com/cosmos/cosmos-sdk/docs/examples/democoin/x/sketchy"
|
||||
)
|
||||
|
||||
const (
|
||||
appName = "DemocoinApp"
|
||||
)
|
||||
|
||||
// default home directories for expected binaries
|
||||
var (
|
||||
DefaultCLIHome = os.ExpandEnv("$HOME/.democli")
|
||||
DefaultNodeHome = os.ExpandEnv("$HOME/.democoind")
|
||||
)
|
||||
|
||||
// Extended ABCI application
|
||||
type DemocoinApp struct {
|
||||
*bam.BaseApp
|
||||
cdc *codec.Codec
|
||||
|
||||
// keys to access the substores
|
||||
capKeyMainStore *sdk.KVStoreKey
|
||||
capKeyAccountStore *sdk.KVStoreKey
|
||||
capKeyPowStore *sdk.KVStoreKey
|
||||
capKeyIBCStore *sdk.KVStoreKey
|
||||
capKeyStakingStore *sdk.KVStoreKey
|
||||
|
||||
// keepers
|
||||
feeCollectionKeeper auth.FeeCollectionKeeper
|
||||
bankKeeper bank.Keeper
|
||||
coolKeeper cool.Keeper
|
||||
powKeeper pow.Keeper
|
||||
ibcMapper ibc.Mapper
|
||||
stakeKeeper simplestake.Keeper
|
||||
|
||||
// Manage getting and setting accounts
|
||||
accountKeeper auth.AccountKeeper
|
||||
}
|
||||
|
||||
func NewDemocoinApp(logger log.Logger, db dbm.DB) *DemocoinApp {
|
||||
|
||||
// Create app-level codec for txs and accounts.
|
||||
var cdc = MakeCodec()
|
||||
|
||||
// Create your application object.
|
||||
var app = &DemocoinApp{
|
||||
BaseApp: bam.NewBaseApp(appName, logger, db, auth.DefaultTxDecoder(cdc)),
|
||||
cdc: cdc,
|
||||
capKeyMainStore: sdk.NewKVStoreKey("main"),
|
||||
capKeyAccountStore: sdk.NewKVStoreKey("acc"),
|
||||
capKeyPowStore: sdk.NewKVStoreKey("pow"),
|
||||
capKeyIBCStore: sdk.NewKVStoreKey("ibc"),
|
||||
capKeyStakingStore: sdk.NewKVStoreKey("stake"),
|
||||
}
|
||||
|
||||
// Define the accountKeeper.
|
||||
app.accountKeeper = auth.NewAccountKeeper(
|
||||
cdc,
|
||||
app.capKeyAccountStore, // target store
|
||||
types.ProtoAppAccount, // prototype
|
||||
)
|
||||
|
||||
// Add handlers.
|
||||
app.bankKeeper = bank.NewBaseKeeper(app.accountKeeper)
|
||||
app.coolKeeper = cool.NewKeeper(app.capKeyMainStore, app.bankKeeper, app.RegisterCodespace(cool.DefaultCodespace))
|
||||
app.powKeeper = pow.NewKeeper(app.capKeyPowStore, pow.NewConfig("pow", int64(1)), app.bankKeeper, app.RegisterCodespace(pow.DefaultCodespace))
|
||||
app.ibcMapper = ibc.NewMapper(app.cdc, app.capKeyIBCStore, app.RegisterCodespace(ibc.DefaultCodespace))
|
||||
app.stakeKeeper = simplestake.NewKeeper(app.capKeyStakingStore, app.bankKeeper, app.RegisterCodespace(simplestake.DefaultCodespace))
|
||||
app.Router().
|
||||
AddRoute("bank", bank.NewHandler(app.bankKeeper)).
|
||||
AddRoute("cool", cool.NewHandler(app.coolKeeper)).
|
||||
AddRoute("pow", app.powKeeper.Handler).
|
||||
AddRoute("sketchy", sketchy.NewHandler()).
|
||||
AddRoute("ibc", ibc.NewHandler(app.ibcMapper, app.bankKeeper)).
|
||||
AddRoute("simplestake", simplestake.NewHandler(app.stakeKeeper))
|
||||
|
||||
// Initialize BaseApp.
|
||||
app.SetInitChainer(app.initChainerFn(app.coolKeeper, app.powKeeper))
|
||||
app.MountStoresIAVL(app.capKeyMainStore, app.capKeyAccountStore, app.capKeyPowStore, app.capKeyIBCStore, app.capKeyStakingStore)
|
||||
app.SetAnteHandler(auth.NewAnteHandler(app.accountKeeper, app.feeCollectionKeeper))
|
||||
err := app.LoadLatestVersion(app.capKeyMainStore)
|
||||
if err != nil {
|
||||
cmn.Exit(err.Error())
|
||||
}
|
||||
|
||||
app.Seal()
|
||||
|
||||
return app
|
||||
}
|
||||
|
||||
// custom tx codec
|
||||
func MakeCodec() *codec.Codec {
|
||||
var cdc = codec.New()
|
||||
codec.RegisterCrypto(cdc) // Register crypto.
|
||||
sdk.RegisterCodec(cdc) // Register Msgs
|
||||
cool.RegisterCodec(cdc)
|
||||
pow.RegisterCodec(cdc)
|
||||
bank.RegisterCodec(cdc)
|
||||
ibc.RegisterCodec(cdc)
|
||||
simplestake.RegisterCodec(cdc)
|
||||
|
||||
// Register AppAccount
|
||||
cdc.RegisterInterface((*auth.Account)(nil), nil)
|
||||
cdc.RegisterConcrete(&types.AppAccount{}, "democoin/Account", nil)
|
||||
|
||||
cdc.Seal()
|
||||
|
||||
return cdc
|
||||
}
|
||||
|
||||
// custom logic for democoin initialization
|
||||
// nolint: unparam
|
||||
func (app *DemocoinApp) initChainerFn(coolKeeper cool.Keeper, powKeeper pow.Keeper) sdk.InitChainer {
|
||||
return func(ctx sdk.Context, req abci.RequestInitChain) abci.ResponseInitChain {
|
||||
stateJSON := req.AppStateBytes
|
||||
|
||||
genesisState := new(types.GenesisState)
|
||||
err := app.cdc.UnmarshalJSON(stateJSON, genesisState)
|
||||
if err != nil {
|
||||
panic(err) // TODO https://github.com/cosmos/cosmos-sdk/issues/468
|
||||
// return sdk.ErrGenesisParse("").TraceCause(err, "")
|
||||
}
|
||||
|
||||
for _, gacc := range genesisState.Accounts {
|
||||
acc, err := gacc.ToAppAccount()
|
||||
if err != nil {
|
||||
panic(err) // TODO https://github.com/cosmos/cosmos-sdk/issues/468
|
||||
// return sdk.ErrGenesisParse("").TraceCause(err, "")
|
||||
}
|
||||
app.accountKeeper.SetAccount(ctx, acc)
|
||||
}
|
||||
|
||||
// Application specific genesis handling
|
||||
err = cool.InitGenesis(ctx, app.coolKeeper, genesisState.CoolGenesis)
|
||||
if err != nil {
|
||||
panic(err) // TODO https://github.com/cosmos/cosmos-sdk/issues/468
|
||||
// return sdk.ErrGenesisParse("").TraceCause(err, "")
|
||||
}
|
||||
|
||||
err = pow.InitGenesis(ctx, app.powKeeper, genesisState.POWGenesis)
|
||||
if err != nil {
|
||||
panic(err) // TODO https://github.com/cosmos/cosmos-sdk/issues/468
|
||||
// return sdk.ErrGenesisParse("").TraceCause(err, "")
|
||||
}
|
||||
|
||||
return abci.ResponseInitChain{}
|
||||
}
|
||||
}
|
||||
|
||||
// Custom logic for state export
|
||||
func (app *DemocoinApp) ExportAppStateAndValidators() (appState json.RawMessage, validators []tmtypes.GenesisValidator, err error) {
|
||||
ctx := app.NewContext(true, abci.Header{})
|
||||
|
||||
// iterate to get the accounts
|
||||
accounts := []*types.GenesisAccount{}
|
||||
appendAccount := func(acc auth.Account) (stop bool) {
|
||||
account := &types.GenesisAccount{
|
||||
Address: acc.GetAddress(),
|
||||
Coins: acc.GetCoins(),
|
||||
}
|
||||
accounts = append(accounts, account)
|
||||
return false
|
||||
}
|
||||
app.accountKeeper.IterateAccounts(ctx, appendAccount)
|
||||
|
||||
genState := types.GenesisState{
|
||||
Accounts: accounts,
|
||||
POWGenesis: pow.ExportGenesis(ctx, app.powKeeper),
|
||||
CoolGenesis: cool.ExportGenesis(ctx, app.coolKeeper),
|
||||
}
|
||||
appState, err = codec.MarshalJSONIndent(app.cdc, genState)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
return appState, validators, nil
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/codec"
|
||||
"github.com/cosmos/cosmos-sdk/docs/examples/democoin/types"
|
||||
"github.com/cosmos/cosmos-sdk/docs/examples/democoin/x/cool"
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
"github.com/cosmos/cosmos-sdk/x/auth"
|
||||
"github.com/stretchr/testify/require"
|
||||
abci "github.com/tendermint/tendermint/abci/types"
|
||||
"github.com/tendermint/tendermint/crypto/ed25519"
|
||||
dbm "github.com/tendermint/tendermint/libs/db"
|
||||
"github.com/tendermint/tendermint/libs/log"
|
||||
)
|
||||
|
||||
func setGenesis(bapp *DemocoinApp, trend string, accs ...auth.BaseAccount) error {
|
||||
genaccs := make([]*types.GenesisAccount, len(accs))
|
||||
for i, acc := range accs {
|
||||
genaccs[i] = types.NewGenesisAccount(&types.AppAccount{acc, "foobart"})
|
||||
}
|
||||
|
||||
genesisState := types.GenesisState{
|
||||
Accounts: genaccs,
|
||||
CoolGenesis: cool.Genesis{trend},
|
||||
}
|
||||
|
||||
stateBytes, err := codec.MarshalJSONIndent(bapp.cdc, genesisState)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Initialize the chain
|
||||
vals := []abci.ValidatorUpdate{}
|
||||
bapp.InitChain(abci.RequestInitChain{Validators: vals, AppStateBytes: stateBytes})
|
||||
bapp.Commit()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func TestGenesis(t *testing.T) {
|
||||
logger := log.NewTMLogger(log.NewSyncWriter(os.Stdout)).With("module", "sdk/app")
|
||||
db := dbm.NewMemDB()
|
||||
bapp := NewDemocoinApp(logger, db)
|
||||
|
||||
// Construct some genesis bytes to reflect democoin/types/AppAccount
|
||||
pk := ed25519.GenPrivKey().PubKey()
|
||||
addr := sdk.AccAddress(pk.Address())
|
||||
coins, err := sdk.ParseCoins("77foocoin,99barcoin")
|
||||
require.Nil(t, err)
|
||||
baseAcc := auth.BaseAccount{
|
||||
Address: addr,
|
||||
Coins: coins,
|
||||
}
|
||||
acc := &types.AppAccount{baseAcc, "foobart"}
|
||||
|
||||
err = setGenesis(bapp, "ice-cold", baseAcc)
|
||||
require.Nil(t, err)
|
||||
// A checkTx context
|
||||
ctx := bapp.BaseApp.NewContext(true, abci.Header{})
|
||||
res1 := bapp.accountKeeper.GetAccount(ctx, baseAcc.Address)
|
||||
require.Equal(t, acc, res1)
|
||||
|
||||
// reload app and ensure the account is still there
|
||||
bapp = NewDemocoinApp(logger, db)
|
||||
bapp.InitChain(abci.RequestInitChain{AppStateBytes: []byte("{}")})
|
||||
ctx = bapp.BaseApp.NewContext(true, abci.Header{})
|
||||
res1 = bapp.accountKeeper.GetAccount(ctx, baseAcc.Address)
|
||||
require.Equal(t, acc, res1)
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
package clitest
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/cmd/gaia/app"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/server"
|
||||
"github.com/cosmos/cosmos-sdk/tests"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
var (
|
||||
democoindHome = ""
|
||||
democliHome = ""
|
||||
)
|
||||
|
||||
func init() {
|
||||
democoindHome, democliHome = getTestingHomeDirs()
|
||||
}
|
||||
|
||||
func TestInitStartSequence(t *testing.T) {
|
||||
os.RemoveAll(democoindHome)
|
||||
servAddr, port, err := server.FreeTCPAddr()
|
||||
require.NoError(t, err)
|
||||
executeInit(t)
|
||||
executeStart(t, servAddr, port)
|
||||
}
|
||||
|
||||
func executeInit(t *testing.T) {
|
||||
var (
|
||||
chainID string
|
||||
initRes map[string]json.RawMessage
|
||||
)
|
||||
_, stderr := tests.ExecuteT(t, fmt.Sprintf("democoind --home=%s --home-client=%s init --name=test", democoindHome, democliHome), app.DefaultKeyPass)
|
||||
err := json.Unmarshal([]byte(stderr), &initRes)
|
||||
require.NoError(t, err)
|
||||
err = json.Unmarshal(initRes["chain_id"], &chainID)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
func executeStart(t *testing.T, servAddr, port string) {
|
||||
proc := tests.GoExecuteTWithStdout(t, fmt.Sprintf("democoind start --home=%s --rpc.laddr=%v", democoindHome, servAddr))
|
||||
defer proc.Stop(false)
|
||||
tests.WaitForTMStart(port)
|
||||
}
|
||||
|
||||
func getTestingHomeDirs() (string, string) {
|
||||
tmpDir := os.TempDir()
|
||||
democoindHome := fmt.Sprintf("%s%s.test_democoind", tmpDir, string(os.PathSeparator))
|
||||
democliHome := fmt.Sprintf("%s%s.test_democli", tmpDir, string(os.PathSeparator))
|
||||
return democoindHome, democliHome
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/tendermint/tendermint/libs/cli"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/client"
|
||||
"github.com/cosmos/cosmos-sdk/client/keys"
|
||||
"github.com/cosmos/cosmos-sdk/client/lcd"
|
||||
"github.com/cosmos/cosmos-sdk/client/rpc"
|
||||
"github.com/cosmos/cosmos-sdk/client/tx"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/version"
|
||||
authcmd "github.com/cosmos/cosmos-sdk/x/auth/client/cli"
|
||||
bankcmd "github.com/cosmos/cosmos-sdk/x/bank/client/cli"
|
||||
ibccmd "github.com/cosmos/cosmos-sdk/x/ibc/client/cli"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/docs/examples/democoin/app"
|
||||
"github.com/cosmos/cosmos-sdk/docs/examples/democoin/types"
|
||||
coolcmd "github.com/cosmos/cosmos-sdk/docs/examples/democoin/x/cool/client/cli"
|
||||
powcmd "github.com/cosmos/cosmos-sdk/docs/examples/democoin/x/pow/client/cli"
|
||||
simplestakingcmd "github.com/cosmos/cosmos-sdk/docs/examples/democoin/x/simplestake/client/cli"
|
||||
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
)
|
||||
|
||||
// rootCmd is the entry point for this binary
|
||||
var (
|
||||
rootCmd = &cobra.Command{
|
||||
Use: "democli",
|
||||
Short: "Democoin light-client",
|
||||
}
|
||||
)
|
||||
|
||||
func main() {
|
||||
// disable sorting
|
||||
cobra.EnableCommandSorting = false
|
||||
|
||||
// get the codec
|
||||
cdc := app.MakeCodec()
|
||||
|
||||
// Setup certain SDK config
|
||||
config := sdk.GetConfig()
|
||||
config.SetBech32PrefixForAccount("demoacc", "demopub")
|
||||
config.SetBech32PrefixForValidator("demoval", "demovalpub")
|
||||
config.SetBech32PrefixForConsensusNode("democons", "democonspub")
|
||||
config.Seal()
|
||||
|
||||
// TODO: setup keybase, viper object, etc. to be passed into
|
||||
// the below functions and eliminate global vars, like we do
|
||||
// with the cdc
|
||||
|
||||
// add standard rpc, and tx commands
|
||||
|
||||
rootCmd.AddCommand(
|
||||
rpc.InitClientCommand(),
|
||||
rpc.StatusCommand(),
|
||||
client.LineBreak,
|
||||
tx.SearchTxCmd(cdc),
|
||||
tx.QueryTxCmd(cdc),
|
||||
client.LineBreak,
|
||||
)
|
||||
|
||||
// add query/post commands (custom to binary)
|
||||
// start with commands common to basecoin
|
||||
rootCmd.AddCommand(
|
||||
client.GetCommands(
|
||||
authcmd.GetAccountCmd("acc", cdc, types.GetAccountDecoder(cdc)),
|
||||
)...)
|
||||
rootCmd.AddCommand(
|
||||
client.PostCommands(
|
||||
bankcmd.SendTxCmd(cdc),
|
||||
)...)
|
||||
rootCmd.AddCommand(
|
||||
client.PostCommands(
|
||||
ibccmd.IBCTransferCmd(cdc),
|
||||
)...)
|
||||
rootCmd.AddCommand(
|
||||
client.PostCommands(
|
||||
ibccmd.IBCRelayCmd(cdc),
|
||||
simplestakingcmd.BondTxCmd(cdc),
|
||||
)...)
|
||||
rootCmd.AddCommand(
|
||||
client.PostCommands(
|
||||
simplestakingcmd.UnbondTxCmd(cdc),
|
||||
)...)
|
||||
// and now democoin specific commands
|
||||
rootCmd.AddCommand(
|
||||
client.PostCommands(
|
||||
coolcmd.QuizTxCmd(cdc),
|
||||
coolcmd.SetTrendTxCmd(cdc),
|
||||
powcmd.MineCmd(cdc),
|
||||
)...)
|
||||
|
||||
// add proxy, version and key info
|
||||
rootCmd.AddCommand(
|
||||
client.LineBreak,
|
||||
lcd.ServeCommand(cdc),
|
||||
keys.Commands(),
|
||||
client.LineBreak,
|
||||
version.VersionCmd,
|
||||
)
|
||||
|
||||
// prepare and add flags
|
||||
executor := cli.PrepareMainCmd(rootCmd, "BC", app.DefaultCLIHome)
|
||||
err := executor.Execute()
|
||||
if err != nil {
|
||||
// handle with #870
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,174 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/client"
|
||||
"github.com/spf13/viper"
|
||||
"github.com/tendermint/tendermint/libs/common"
|
||||
"github.com/tendermint/tendermint/p2p"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
abci "github.com/tendermint/tendermint/abci/types"
|
||||
"github.com/tendermint/tendermint/libs/cli"
|
||||
dbm "github.com/tendermint/tendermint/libs/db"
|
||||
"github.com/tendermint/tendermint/libs/log"
|
||||
tmtypes "github.com/tendermint/tendermint/types"
|
||||
|
||||
gaiaInit "github.com/cosmos/cosmos-sdk/cmd/gaia/init"
|
||||
"github.com/cosmos/cosmos-sdk/codec"
|
||||
"github.com/cosmos/cosmos-sdk/docs/examples/democoin/app"
|
||||
"github.com/cosmos/cosmos-sdk/server"
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
)
|
||||
|
||||
const (
|
||||
flagClientHome = "home-client"
|
||||
)
|
||||
|
||||
// init parameters
|
||||
var CoolAppInit = server.AppInit{
|
||||
AppGenState: CoolAppGenState,
|
||||
}
|
||||
|
||||
// coolGenAppParams sets up the app_state and appends the cool app state
|
||||
func CoolAppGenState(cdc *codec.Codec, genDoc tmtypes.GenesisDoc, appGenTxs []json.RawMessage) (
|
||||
appState json.RawMessage, err error) {
|
||||
appState, err = server.SimpleAppGenState(cdc, tmtypes.GenesisDoc{}, appGenTxs)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
key := "cool"
|
||||
value := json.RawMessage(`{
|
||||
"trend": "ice-cold"
|
||||
}`)
|
||||
|
||||
appState, err = server.InsertKeyJSON(cdc, appState, key, value)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
key = "pow"
|
||||
value = json.RawMessage(`{
|
||||
"difficulty": "1",
|
||||
"count": "0"
|
||||
}`)
|
||||
|
||||
appState, err = server.InsertKeyJSON(cdc, appState, key, value)
|
||||
return
|
||||
}
|
||||
|
||||
// get cmd to initialize all files for tendermint and application
|
||||
// nolint: errcheck
|
||||
func InitCmd(ctx *server.Context, cdc *codec.Codec, appInit server.AppInit) *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "init",
|
||||
Short: "Initialize genesis config, priv-validator file, and p2p-node file",
|
||||
Args: cobra.NoArgs,
|
||||
RunE: func(_ *cobra.Command, _ []string) error {
|
||||
|
||||
config := ctx.Config
|
||||
config.SetRoot(viper.GetString(cli.HomeFlag))
|
||||
chainID := viper.GetString(client.FlagChainID)
|
||||
if chainID == "" {
|
||||
chainID = fmt.Sprintf("test-chain-%v", common.RandStr(6))
|
||||
}
|
||||
|
||||
nodeKey, err := p2p.LoadOrGenNodeKey(config.NodeKeyFile())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
nodeID := string(nodeKey.ID())
|
||||
|
||||
pk := gaiaInit.ReadOrCreatePrivValidator(config.PrivValidatorFile())
|
||||
genTx, appMessage, validator, err := server.SimpleAppGenTx(cdc, pk)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
appState, err := appInit.AppGenState(cdc, tmtypes.GenesisDoc{},
|
||||
[]json.RawMessage{genTx})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
appStateJSON, err := cdc.MarshalJSON(appState)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
toPrint := struct {
|
||||
ChainID string `json:"chain_id"`
|
||||
NodeID string `json:"node_id"`
|
||||
AppMessage json.RawMessage `json:"app_message"`
|
||||
}{
|
||||
chainID,
|
||||
nodeID,
|
||||
appMessage,
|
||||
}
|
||||
out, err := codec.MarshalJSONIndent(cdc, toPrint)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Fprintf(os.Stderr, "%s\n", string(out))
|
||||
return gaiaInit.ExportGenesisFile(config.GenesisFile(), chainID,
|
||||
[]tmtypes.GenesisValidator{validator}, appStateJSON)
|
||||
},
|
||||
}
|
||||
|
||||
cmd.Flags().String(cli.HomeFlag, app.DefaultNodeHome, "node's home directory")
|
||||
cmd.Flags().String(flagClientHome, app.DefaultCLIHome, "client's home directory")
|
||||
cmd.Flags().String(client.FlagChainID, "",
|
||||
"genesis file chain-id, if left blank will be randomly created")
|
||||
cmd.Flags().String(client.FlagName, "", "validator's moniker")
|
||||
cmd.MarkFlagRequired(client.FlagName)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func newApp(logger log.Logger, db dbm.DB, _ io.Writer) abci.Application {
|
||||
return app.NewDemocoinApp(logger, db)
|
||||
}
|
||||
|
||||
func exportAppStateAndTMValidators(logger log.Logger, db dbm.DB, _ io.Writer) (
|
||||
json.RawMessage, []tmtypes.GenesisValidator, error) {
|
||||
dapp := app.NewDemocoinApp(logger, db)
|
||||
return dapp.ExportAppStateAndValidators()
|
||||
}
|
||||
|
||||
func main() {
|
||||
cdc := app.MakeCodec()
|
||||
|
||||
// Setup certain SDK config
|
||||
config := sdk.GetConfig()
|
||||
config.SetBech32PrefixForAccount("demoacc", "demopub")
|
||||
config.SetBech32PrefixForValidator("demoval", "demovalpub")
|
||||
config.SetBech32PrefixForConsensusNode("democons", "democonspub")
|
||||
config.Seal()
|
||||
|
||||
ctx := server.NewDefaultContext()
|
||||
|
||||
rootCmd := &cobra.Command{
|
||||
Use: "democoind",
|
||||
Short: "Democoin Daemon (server)",
|
||||
PersistentPreRunE: server.PersistentPreRunEFn(ctx),
|
||||
}
|
||||
|
||||
rootCmd.AddCommand(InitCmd(ctx, cdc, CoolAppInit))
|
||||
rootCmd.AddCommand(gaiaInit.TestnetFilesCmd(ctx, cdc, CoolAppInit))
|
||||
|
||||
server.AddCommands(ctx, cdc, rootCmd, CoolAppInit,
|
||||
newApp, exportAppStateAndTMValidators)
|
||||
|
||||
// prepare and add flags
|
||||
rootDir := os.ExpandEnv("$HOME/.democoind")
|
||||
executor := cli.PrepareBaseCmd(rootCmd, "BC", rootDir)
|
||||
err := executor.Execute()
|
||||
if err != nil {
|
||||
// handle with #870
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,162 @@
|
||||
package mock
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
"github.com/tendermint/tendermint/crypto"
|
||||
)
|
||||
|
||||
// Validator implements sdk.Validator
|
||||
type Validator struct {
|
||||
Address sdk.ValAddress
|
||||
Power sdk.Dec
|
||||
}
|
||||
|
||||
// Implements sdk.Validator
|
||||
func (v Validator) GetStatus() sdk.BondStatus {
|
||||
return sdk.Bonded
|
||||
}
|
||||
|
||||
// Implements sdk.Validator
|
||||
func (v Validator) GetOperator() sdk.ValAddress {
|
||||
return v.Address
|
||||
}
|
||||
|
||||
// Implements sdk.Validator
|
||||
func (v Validator) GetConsPubKey() crypto.PubKey {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Implements sdk.Validator
|
||||
func (v Validator) GetConsAddr() sdk.ConsAddress {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Implements sdk.Validator
|
||||
func (v Validator) GetTokens() sdk.Dec {
|
||||
return sdk.ZeroDec()
|
||||
}
|
||||
|
||||
// Implements sdk.Validator
|
||||
func (v Validator) GetPower() sdk.Dec {
|
||||
return v.Power
|
||||
}
|
||||
|
||||
// Implements sdk.Validator
|
||||
func (v Validator) GetDelegatorShares() sdk.Dec {
|
||||
return sdk.ZeroDec()
|
||||
}
|
||||
|
||||
// Implements sdk.Validator
|
||||
func (v Validator) GetCommission() sdk.Dec {
|
||||
return sdk.ZeroDec()
|
||||
}
|
||||
|
||||
// Implements sdk.Validator
|
||||
func (v Validator) GetJailed() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// Implements sdk.Validator
|
||||
func (v Validator) GetBondHeight() int64 {
|
||||
return 0
|
||||
}
|
||||
|
||||
// Implements sdk.Validator
|
||||
func (v Validator) GetMoniker() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
// Implements sdk.Validator
|
||||
type ValidatorSet struct {
|
||||
Validators []Validator
|
||||
}
|
||||
|
||||
// IterateValidators implements sdk.ValidatorSet
|
||||
func (vs *ValidatorSet) IterateValidators(ctx sdk.Context, fn func(index int64, Validator sdk.Validator) bool) {
|
||||
for i, val := range vs.Validators {
|
||||
if fn(int64(i), val) {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// IterateBondedValidatorsByPower implements sdk.ValidatorSet
|
||||
func (vs *ValidatorSet) IterateBondedValidatorsByPower(ctx sdk.Context, fn func(index int64, Validator sdk.Validator) bool) {
|
||||
vs.IterateValidators(ctx, fn)
|
||||
}
|
||||
|
||||
// IterateLastValidators implements sdk.ValidatorSet
|
||||
func (vs *ValidatorSet) IterateLastValidators(ctx sdk.Context, fn func(index int64, Validator sdk.Validator) bool) {
|
||||
vs.IterateValidators(ctx, fn)
|
||||
}
|
||||
|
||||
// Validator implements sdk.ValidatorSet
|
||||
func (vs *ValidatorSet) Validator(ctx sdk.Context, addr sdk.ValAddress) sdk.Validator {
|
||||
for _, val := range vs.Validators {
|
||||
if bytes.Equal(val.Address.Bytes(), addr.Bytes()) {
|
||||
return val
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ValidatorByPubKey implements sdk.ValidatorSet
|
||||
func (vs *ValidatorSet) ValidatorByConsPubKey(_ sdk.Context, _ crypto.PubKey) sdk.Validator {
|
||||
panic("not implemented")
|
||||
}
|
||||
|
||||
// ValidatorByPubKey implements sdk.ValidatorSet
|
||||
func (vs *ValidatorSet) ValidatorByConsAddr(_ sdk.Context, _ sdk.ConsAddress) sdk.Validator {
|
||||
panic("not implemented")
|
||||
}
|
||||
|
||||
// TotalPower implements sdk.ValidatorSet
|
||||
func (vs *ValidatorSet) TotalPower(ctx sdk.Context) sdk.Dec {
|
||||
res := sdk.ZeroDec()
|
||||
for _, val := range vs.Validators {
|
||||
res = res.Add(val.Power)
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
// Helper function for adding new validator
|
||||
func (vs *ValidatorSet) AddValidator(val Validator) {
|
||||
vs.Validators = append(vs.Validators, val)
|
||||
}
|
||||
|
||||
// Helper function for removing exsting validator
|
||||
func (vs *ValidatorSet) RemoveValidator(addr sdk.AccAddress) {
|
||||
pos := -1
|
||||
for i, val := range vs.Validators {
|
||||
if bytes.Equal(val.Address, addr) {
|
||||
pos = i
|
||||
break
|
||||
}
|
||||
}
|
||||
if pos == -1 {
|
||||
return
|
||||
}
|
||||
vs.Validators = append(vs.Validators[:pos], vs.Validators[pos+1:]...)
|
||||
}
|
||||
|
||||
// Implements sdk.ValidatorSet
|
||||
func (vs *ValidatorSet) Slash(_ sdk.Context, _ sdk.ConsAddress, _ int64, _ int64, _ sdk.Dec) {
|
||||
panic("not implemented")
|
||||
}
|
||||
|
||||
// Implements sdk.ValidatorSet
|
||||
func (vs *ValidatorSet) Jail(_ sdk.Context, _ sdk.ConsAddress) {
|
||||
panic("not implemented")
|
||||
}
|
||||
|
||||
// Implements sdk.ValidatorSet
|
||||
func (vs *ValidatorSet) Unjail(_ sdk.Context, _ sdk.ConsAddress) {
|
||||
panic("not implemented")
|
||||
}
|
||||
|
||||
// Implements sdk.ValidatorSet
|
||||
func (vs *ValidatorSet) Delegation(_ sdk.Context, _ sdk.AccAddress, _ sdk.ValAddress) sdk.Delegation {
|
||||
panic("not implemented")
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
package types
|
||||
|
||||
import (
|
||||
"github.com/cosmos/cosmos-sdk/codec"
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
"github.com/cosmos/cosmos-sdk/x/auth"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/docs/examples/democoin/x/cool"
|
||||
"github.com/cosmos/cosmos-sdk/docs/examples/democoin/x/pow"
|
||||
)
|
||||
|
||||
var _ auth.Account = (*AppAccount)(nil)
|
||||
|
||||
// Custom extensions for this application. This is just an example of
|
||||
// extending auth.BaseAccount with custom fields.
|
||||
//
|
||||
// This is compatible with the stock auth.AccountStore, since
|
||||
// auth.AccountStore uses the flexible go-amino library.
|
||||
type AppAccount struct {
|
||||
auth.BaseAccount
|
||||
Name string `json:"name"`
|
||||
}
|
||||
|
||||
// Constructor for AppAccount
|
||||
func ProtoAppAccount() auth.Account {
|
||||
return &AppAccount{}
|
||||
}
|
||||
|
||||
// nolint
|
||||
func (acc AppAccount) GetName() string { return acc.Name }
|
||||
func (acc *AppAccount) SetName(name string) { acc.Name = name }
|
||||
|
||||
// Get the AccountDecoder function for the custom AppAccount
|
||||
func GetAccountDecoder(cdc *codec.Codec) auth.AccountDecoder {
|
||||
return func(accBytes []byte) (res auth.Account, err error) {
|
||||
if len(accBytes) == 0 {
|
||||
return nil, sdk.ErrTxDecode("accBytes are empty")
|
||||
}
|
||||
acct := new(AppAccount)
|
||||
err = cdc.UnmarshalBinaryBare(accBytes, &acct)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return acct, err
|
||||
}
|
||||
}
|
||||
|
||||
//___________________________________________________________________________________
|
||||
|
||||
// State to Unmarshal
|
||||
type GenesisState struct {
|
||||
Accounts []*GenesisAccount `json:"accounts"`
|
||||
POWGenesis pow.Genesis `json:"pow"`
|
||||
CoolGenesis cool.Genesis `json:"cool"`
|
||||
}
|
||||
|
||||
// GenesisAccount doesn't need pubkey or sequence
|
||||
type GenesisAccount struct {
|
||||
Name string `json:"name"`
|
||||
Address sdk.AccAddress `json:"address"`
|
||||
Coins sdk.Coins `json:"coins"`
|
||||
}
|
||||
|
||||
func NewGenesisAccount(aa *AppAccount) *GenesisAccount {
|
||||
return &GenesisAccount{
|
||||
Name: aa.Name,
|
||||
Address: aa.Address,
|
||||
Coins: aa.Coins.Sort(),
|
||||
}
|
||||
}
|
||||
|
||||
// convert GenesisAccount to AppAccount
|
||||
func (ga *GenesisAccount) ToAppAccount() (acc *AppAccount, err error) {
|
||||
baseAcc := auth.BaseAccount{
|
||||
Address: ga.Address,
|
||||
Coins: ga.Coins.Sort(),
|
||||
}
|
||||
return &AppAccount{
|
||||
BaseAccount: baseAcc,
|
||||
Name: ga.Name,
|
||||
}, nil
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
package assoc
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/codec"
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
)
|
||||
|
||||
// ValidatorSet defines
|
||||
type ValidatorSet struct {
|
||||
sdk.ValidatorSet
|
||||
|
||||
store sdk.KVStore
|
||||
cdc *codec.Codec
|
||||
|
||||
maxAssoc int
|
||||
addrLen int
|
||||
}
|
||||
|
||||
var _ sdk.ValidatorSet = ValidatorSet{}
|
||||
|
||||
// NewValidatorSet returns new ValidatorSet with underlying ValidatorSet
|
||||
func NewValidatorSet(cdc *codec.Codec, store sdk.KVStore, valset sdk.ValidatorSet, maxAssoc int, addrLen int) ValidatorSet {
|
||||
if maxAssoc < 0 || addrLen < 0 {
|
||||
panic("Cannot use negative integer for NewValidatorSet")
|
||||
}
|
||||
return ValidatorSet{
|
||||
ValidatorSet: valset,
|
||||
|
||||
store: store,
|
||||
cdc: cdc,
|
||||
|
||||
maxAssoc: maxAssoc,
|
||||
addrLen: addrLen,
|
||||
}
|
||||
}
|
||||
|
||||
// Implements sdk.ValidatorSet
|
||||
func (valset ValidatorSet) Validator(ctx sdk.Context, addr sdk.ValAddress) (res sdk.Validator) {
|
||||
base := valset.store.Get(GetBaseKey(addr))
|
||||
res = valset.ValidatorSet.Validator(ctx, base)
|
||||
if res == nil {
|
||||
res = valset.ValidatorSet.Validator(ctx, addr)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// GetBaseKey :: sdk.ValAddress -> sdk.ValAddress
|
||||
func GetBaseKey(addr sdk.ValAddress) []byte {
|
||||
return append([]byte{0x00}, addr...)
|
||||
}
|
||||
|
||||
// GetAssocPrefix :: sdk.ValAddress -> (sdk.ValAddress -> byte)
|
||||
func GetAssocPrefix(base sdk.ValAddress) []byte {
|
||||
return append([]byte{0x01}, base...)
|
||||
}
|
||||
|
||||
// GetAssocKey :: (sdk.ValAddress, sdk.ValAddress) -> byte
|
||||
func GetAssocKey(base sdk.ValAddress, assoc sdk.ValAddress) []byte {
|
||||
return append(append([]byte{0x01}, base...), assoc...)
|
||||
}
|
||||
|
||||
// Associate associates new address with validator address
|
||||
// nolint: unparam
|
||||
func (valset ValidatorSet) Associate(ctx sdk.Context, base sdk.ValAddress, assoc sdk.ValAddress) bool {
|
||||
if len(base) != valset.addrLen || len(assoc) != valset.addrLen {
|
||||
return false
|
||||
}
|
||||
// If someone already owns the associated address
|
||||
if valset.store.Get(GetBaseKey(assoc)) != nil {
|
||||
return false
|
||||
}
|
||||
valset.store.Set(GetBaseKey(assoc), base)
|
||||
valset.store.Set(GetAssocKey(base, assoc), []byte{0x00})
|
||||
return true
|
||||
}
|
||||
|
||||
// Dissociate removes association between addresses
|
||||
// nolint: unparam
|
||||
func (valset ValidatorSet) Dissociate(ctx sdk.Context, base sdk.ValAddress, assoc sdk.ValAddress) bool {
|
||||
if len(base) != valset.addrLen || len(assoc) != valset.addrLen {
|
||||
return false
|
||||
}
|
||||
// No associated address found for given validator
|
||||
if !bytes.Equal(valset.store.Get(GetBaseKey(assoc)), base) {
|
||||
return false
|
||||
}
|
||||
valset.store.Delete(GetBaseKey(assoc))
|
||||
valset.store.Delete(GetAssocKey(base, assoc))
|
||||
return true
|
||||
}
|
||||
|
||||
// Associations returns all associated addresses with a validator
|
||||
// nolint: unparam
|
||||
func (valset ValidatorSet) Associations(ctx sdk.Context, base sdk.ValAddress) (res []sdk.ValAddress) {
|
||||
res = make([]sdk.ValAddress, valset.maxAssoc)
|
||||
iter := sdk.KVStorePrefixIterator(valset.store, GetAssocPrefix(base))
|
||||
defer iter.Close()
|
||||
i := 0
|
||||
for ; iter.Valid(); iter.Next() {
|
||||
key := iter.Key()
|
||||
res[i] = key[len(key)-valset.addrLen:]
|
||||
i++
|
||||
}
|
||||
return res[:i]
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
package assoc
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
abci "github.com/tendermint/tendermint/abci/types"
|
||||
dbm "github.com/tendermint/tendermint/libs/db"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/codec"
|
||||
"github.com/cosmos/cosmos-sdk/docs/examples/democoin/mock"
|
||||
"github.com/cosmos/cosmos-sdk/store"
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
)
|
||||
|
||||
func defaultContext(key sdk.StoreKey) sdk.Context {
|
||||
db := dbm.NewMemDB()
|
||||
cms := store.NewCommitMultiStore(db)
|
||||
cms.MountStoreWithDB(key, sdk.StoreTypeIAVL, db)
|
||||
cms.LoadLatestVersion()
|
||||
ctx := sdk.NewContext(cms, abci.Header{}, false, nil)
|
||||
return ctx
|
||||
}
|
||||
|
||||
func TestValidatorSet(t *testing.T) {
|
||||
key := sdk.NewKVStoreKey("test")
|
||||
ctx := defaultContext(key)
|
||||
|
||||
addr1 := []byte("addr1")
|
||||
addr2 := []byte("addr2")
|
||||
|
||||
base := &mock.ValidatorSet{[]mock.Validator{
|
||||
{addr1, sdk.NewDec(1)},
|
||||
{addr2, sdk.NewDec(2)},
|
||||
}}
|
||||
|
||||
valset := NewValidatorSet(codec.New(), ctx.KVStore(key).Prefix([]byte("assoc")), base, 1, 5)
|
||||
|
||||
require.Equal(t, base.Validator(ctx, addr1), valset.Validator(ctx, addr1))
|
||||
require.Equal(t, base.Validator(ctx, addr2), valset.Validator(ctx, addr2))
|
||||
|
||||
assoc1 := []byte("asso1")
|
||||
assoc2 := []byte("asso2")
|
||||
|
||||
require.True(t, valset.Associate(ctx, addr1, assoc1))
|
||||
require.True(t, valset.Associate(ctx, addr2, assoc2))
|
||||
|
||||
require.Equal(t, base.Validator(ctx, addr1), valset.Validator(ctx, assoc1))
|
||||
require.Equal(t, base.Validator(ctx, addr2), valset.Validator(ctx, assoc2))
|
||||
|
||||
require.Equal(t, base.Validator(ctx, addr1), valset.Validator(ctx, addr1))
|
||||
require.Equal(t, base.Validator(ctx, addr2), valset.Validator(ctx, addr2))
|
||||
|
||||
assocs := valset.Associations(ctx, addr1)
|
||||
require.Equal(t, 1, len(assocs))
|
||||
require.True(t, bytes.Equal(assoc1, assocs[0]))
|
||||
|
||||
require.False(t, valset.Associate(ctx, addr1, assoc2))
|
||||
require.False(t, valset.Associate(ctx, addr2, assoc1))
|
||||
|
||||
valset.Dissociate(ctx, addr1, assoc1)
|
||||
valset.Dissociate(ctx, addr2, assoc2)
|
||||
|
||||
require.Equal(t, base.Validator(ctx, addr1), valset.Validator(ctx, addr1))
|
||||
require.Equal(t, base.Validator(ctx, addr2), valset.Validator(ctx, addr2))
|
||||
|
||||
require.Nil(t, valset.Validator(ctx, assoc1))
|
||||
require.Nil(t, valset.Validator(ctx, assoc2))
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
package cool
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
"github.com/cosmos/cosmos-sdk/x/auth"
|
||||
bank "github.com/cosmos/cosmos-sdk/x/bank"
|
||||
"github.com/cosmos/cosmos-sdk/x/mock"
|
||||
"github.com/stretchr/testify/require"
|
||||
abci "github.com/tendermint/tendermint/abci/types"
|
||||
"github.com/tendermint/tendermint/crypto/ed25519"
|
||||
)
|
||||
|
||||
var (
|
||||
priv1 = ed25519.GenPrivKey()
|
||||
pubKey = priv1.PubKey()
|
||||
addr1 = sdk.AccAddress(pubKey.Address())
|
||||
|
||||
quizMsg1 = MsgQuiz{
|
||||
Sender: addr1,
|
||||
CoolAnswer: "icecold",
|
||||
}
|
||||
|
||||
quizMsg2 = MsgQuiz{
|
||||
Sender: addr1,
|
||||
CoolAnswer: "badvibesonly",
|
||||
}
|
||||
|
||||
setTrendMsg1 = MsgSetTrend{
|
||||
Sender: addr1,
|
||||
Cool: "icecold",
|
||||
}
|
||||
|
||||
setTrendMsg2 = MsgSetTrend{
|
||||
Sender: addr1,
|
||||
Cool: "badvibesonly",
|
||||
}
|
||||
|
||||
setTrendMsg3 = MsgSetTrend{
|
||||
Sender: addr1,
|
||||
Cool: "warmandkind",
|
||||
}
|
||||
)
|
||||
|
||||
// initialize the mock application for this module
|
||||
func getMockApp(t *testing.T) *mock.App {
|
||||
mapp := mock.NewApp()
|
||||
|
||||
RegisterCodec(mapp.Cdc)
|
||||
keyCool := sdk.NewKVStoreKey("cool")
|
||||
bankKeeper := bank.NewBaseKeeper(mapp.AccountKeeper)
|
||||
keeper := NewKeeper(keyCool, bankKeeper, mapp.RegisterCodespace(DefaultCodespace))
|
||||
mapp.Router().AddRoute("cool", NewHandler(keeper))
|
||||
|
||||
mapp.SetInitChainer(getInitChainer(mapp, keeper, "ice-cold"))
|
||||
|
||||
require.NoError(t, mapp.CompleteSetup(keyCool))
|
||||
return mapp
|
||||
}
|
||||
|
||||
// overwrite the mock init chainer
|
||||
func getInitChainer(mapp *mock.App, keeper Keeper, newTrend string) sdk.InitChainer {
|
||||
return func(ctx sdk.Context, req abci.RequestInitChain) abci.ResponseInitChain {
|
||||
mapp.InitChainer(ctx, req)
|
||||
keeper.setTrend(ctx, newTrend)
|
||||
|
||||
return abci.ResponseInitChain{}
|
||||
}
|
||||
}
|
||||
|
||||
func TestMsgQuiz(t *testing.T) {
|
||||
mapp := getMockApp(t)
|
||||
|
||||
// Construct genesis state
|
||||
acc1 := &auth.BaseAccount{
|
||||
Address: addr1,
|
||||
Coins: nil,
|
||||
}
|
||||
accs := []auth.Account{acc1}
|
||||
|
||||
// Initialize the chain (nil)
|
||||
mock.SetGenesis(mapp, accs)
|
||||
|
||||
// A checkTx context (true)
|
||||
ctxCheck := mapp.BaseApp.NewContext(true, abci.Header{})
|
||||
res1 := mapp.AccountKeeper.GetAccount(ctxCheck, addr1)
|
||||
require.Equal(t, acc1, res1)
|
||||
|
||||
// Set the trend, submit a really cool quiz and check for reward
|
||||
mock.SignCheckDeliver(t, mapp.BaseApp, []sdk.Msg{setTrendMsg1}, []int64{0}, []int64{0}, true, true, priv1)
|
||||
mock.SignCheckDeliver(t, mapp.BaseApp, []sdk.Msg{quizMsg1}, []int64{0}, []int64{1}, true, true, priv1)
|
||||
mock.CheckBalance(t, mapp, addr1, sdk.Coins{{"icecold", sdk.NewInt(69)}})
|
||||
mock.SignCheckDeliver(t, mapp.BaseApp, []sdk.Msg{quizMsg2}, []int64{0}, []int64{2}, false, false, priv1) // result without reward
|
||||
mock.CheckBalance(t, mapp, addr1, sdk.Coins{{"icecold", sdk.NewInt(69)}})
|
||||
mock.SignCheckDeliver(t, mapp.BaseApp, []sdk.Msg{quizMsg1}, []int64{0}, []int64{3}, true, true, priv1)
|
||||
mock.CheckBalance(t, mapp, addr1, sdk.Coins{{"icecold", sdk.NewInt(138)}})
|
||||
mock.SignCheckDeliver(t, mapp.BaseApp, []sdk.Msg{setTrendMsg2}, []int64{0}, []int64{4}, true, true, priv1) // reset the trend
|
||||
mock.SignCheckDeliver(t, mapp.BaseApp, []sdk.Msg{quizMsg1}, []int64{0}, []int64{5}, false, false, priv1) // the same answer will nolonger do!
|
||||
mock.CheckBalance(t, mapp, addr1, sdk.Coins{{"icecold", sdk.NewInt(138)}})
|
||||
mock.SignCheckDeliver(t, mapp.BaseApp, []sdk.Msg{quizMsg2}, []int64{0}, []int64{6}, true, true, priv1) // earlier answer now relevant again
|
||||
mock.CheckBalance(t, mapp, addr1, sdk.Coins{{"badvibesonly", sdk.NewInt(69)}, {"icecold", sdk.NewInt(138)}})
|
||||
mock.SignCheckDeliver(t, mapp.BaseApp, []sdk.Msg{setTrendMsg3}, []int64{0}, []int64{7}, false, false, priv1) // expect to fail to set the trend to something which is not cool
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/client/context"
|
||||
"github.com/cosmos/cosmos-sdk/client/utils"
|
||||
"github.com/cosmos/cosmos-sdk/codec"
|
||||
"github.com/cosmos/cosmos-sdk/docs/examples/democoin/x/cool"
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
authcmd "github.com/cosmos/cosmos-sdk/x/auth/client/cli"
|
||||
authtxb "github.com/cosmos/cosmos-sdk/x/auth/client/txbuilder"
|
||||
)
|
||||
|
||||
// QuizTxCmd invokes the coolness quiz transaction.
|
||||
func QuizTxCmd(cdc *codec.Codec) *cobra.Command {
|
||||
return &cobra.Command{
|
||||
Use: "cool [answer]",
|
||||
Short: "What's cooler than being cool?",
|
||||
Args: cobra.ExactArgs(1),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
txBldr := authtxb.NewTxBuilderFromCLI().WithCodec(cdc)
|
||||
cliCtx := context.NewCLIContext().
|
||||
WithCodec(cdc).
|
||||
WithAccountDecoder(authcmd.GetAccountDecoder(cdc))
|
||||
|
||||
from, err := cliCtx.GetFromAddress()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
msg := cool.NewMsgQuiz(from, args[0])
|
||||
|
||||
return utils.CompleteAndBroadcastTxCli(txBldr, cliCtx, []sdk.Msg{msg})
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// SetTrendTxCmd sends a new cool trend transaction.
|
||||
func SetTrendTxCmd(cdc *codec.Codec) *cobra.Command {
|
||||
return &cobra.Command{
|
||||
Use: "setcool [answer]",
|
||||
Short: "You're so cool, tell us what is cool!",
|
||||
Args: cobra.ExactArgs(1),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
txBldr := authtxb.NewTxBuilderFromCLI().WithCodec(cdc)
|
||||
cliCtx := context.NewCLIContext().
|
||||
WithCodec(cdc).
|
||||
WithAccountDecoder(authcmd.GetAccountDecoder(cdc))
|
||||
|
||||
from, err := cliCtx.GetFromAddress()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
msg := cool.NewMsgSetTrend(from, args[0])
|
||||
|
||||
return utils.CompleteAndBroadcastTxCli(txBldr, cliCtx, []sdk.Msg{msg})
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package cool
|
||||
|
||||
import (
|
||||
"github.com/cosmos/cosmos-sdk/codec"
|
||||
)
|
||||
|
||||
// Register concrete types on codec codec
|
||||
func RegisterCodec(cdc *codec.Codec) {
|
||||
cdc.RegisterConcrete(MsgQuiz{}, "cool/Quiz", nil)
|
||||
cdc.RegisterConcrete(MsgSetTrend{}, "cool/SetTrend", nil)
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package cool
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
)
|
||||
|
||||
// Cool errors reserve 400 ~ 499.
|
||||
const (
|
||||
DefaultCodespace sdk.CodespaceType = 6
|
||||
|
||||
// Cool module reserves error 400-499 lawl
|
||||
CodeIncorrectCoolAnswer sdk.CodeType = 400
|
||||
)
|
||||
|
||||
// ErrIncorrectCoolAnswer - Error returned upon an incorrect guess
|
||||
func ErrIncorrectCoolAnswer(codespace sdk.CodespaceType, answer string) sdk.Error {
|
||||
return sdk.NewError(codespace, CodeIncorrectCoolAnswer, fmt.Sprintf("incorrect cool answer: %v", answer))
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
package cool
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
)
|
||||
|
||||
// This is just an example to demonstrate a functional custom module
|
||||
// with full feature set functionality.
|
||||
//
|
||||
// /$$$$$$$ /$$$$$$ /$$$$$$ /$$
|
||||
// /$$_____/ /$$__ $$ /$$__ $$| $$
|
||||
//| $$ | $$ \ $$| $$ \ $$| $$
|
||||
//| $$ | $$ | $$| $$ | $$| $$
|
||||
//| $$$$$$$| $$$$$$/| $$$$$$/| $$$$$$$
|
||||
// \_______/ \______/ \______/ |______/
|
||||
|
||||
// NewHandler returns a handler for "cool" type messages.
|
||||
func NewHandler(k Keeper) sdk.Handler {
|
||||
return func(ctx sdk.Context, msg sdk.Msg) sdk.Result {
|
||||
switch msg := msg.(type) {
|
||||
case MsgSetTrend:
|
||||
return handleMsgSetTrend(ctx, k, msg)
|
||||
case MsgQuiz:
|
||||
return handleMsgQuiz(ctx, k, msg)
|
||||
default:
|
||||
errMsg := fmt.Sprintf("Unrecognized cool Msg type: %v", msg.Type())
|
||||
return sdk.ErrUnknownRequest(errMsg).Result()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Handle MsgQuiz This is the engine of your module
|
||||
func handleMsgSetTrend(ctx sdk.Context, k Keeper, msg MsgSetTrend) sdk.Result {
|
||||
k.setTrend(ctx, msg.Cool)
|
||||
return sdk.Result{}
|
||||
}
|
||||
|
||||
// Handle MsgQuiz This is the engine of your module
|
||||
func handleMsgQuiz(ctx sdk.Context, k Keeper, msg MsgQuiz) sdk.Result {
|
||||
|
||||
correct := k.CheckTrend(ctx, msg.CoolAnswer)
|
||||
|
||||
if !correct {
|
||||
return ErrIncorrectCoolAnswer(k.codespace, msg.CoolAnswer).Result()
|
||||
}
|
||||
|
||||
bonusCoins := sdk.Coins{sdk.NewInt64Coin(msg.CoolAnswer, 69)}
|
||||
|
||||
_, _, err := k.ck.AddCoins(ctx, msg.Sender, bonusCoins)
|
||||
if err != nil {
|
||||
return err.Result()
|
||||
}
|
||||
|
||||
return sdk.Result{}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
package cool
|
||||
|
||||
import (
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
"github.com/cosmos/cosmos-sdk/x/bank"
|
||||
)
|
||||
|
||||
// Keeper - handlers sets/gets of custom variables for your module
|
||||
type Keeper struct {
|
||||
ck bank.Keeper
|
||||
|
||||
storeKey sdk.StoreKey // The (unexposed) key used to access the store from the Context.
|
||||
|
||||
codespace sdk.CodespaceType
|
||||
}
|
||||
|
||||
// NewKeeper - Returns the Keeper
|
||||
func NewKeeper(key sdk.StoreKey, bankKeeper bank.Keeper, codespace sdk.CodespaceType) Keeper {
|
||||
return Keeper{bankKeeper, key, codespace}
|
||||
}
|
||||
|
||||
// Key to knowing the trend on the streets!
|
||||
var trendKey = []byte("TrendKey")
|
||||
|
||||
// GetTrend - returns the current cool trend
|
||||
func (k Keeper) GetTrend(ctx sdk.Context) string {
|
||||
store := ctx.KVStore(k.storeKey)
|
||||
bz := store.Get(trendKey)
|
||||
return string(bz)
|
||||
}
|
||||
|
||||
// Implements sdk.AccountKeeper.
|
||||
func (k Keeper) setTrend(ctx sdk.Context, newTrend string) {
|
||||
store := ctx.KVStore(k.storeKey)
|
||||
store.Set(trendKey, []byte(newTrend))
|
||||
}
|
||||
|
||||
// CheckTrend - Returns true or false based on whether guessedTrend is currently cool or not
|
||||
func (k Keeper) CheckTrend(ctx sdk.Context, guessedTrend string) bool {
|
||||
if guessedTrend == k.GetTrend(ctx) {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// InitGenesis - store the genesis trend
|
||||
func InitGenesis(ctx sdk.Context, k Keeper, data Genesis) error {
|
||||
k.setTrend(ctx, data.Trend)
|
||||
return nil
|
||||
}
|
||||
|
||||
// ExportGenesis - output the genesis trend
|
||||
func ExportGenesis(ctx sdk.Context, k Keeper) Genesis {
|
||||
trend := k.GetTrend(ctx)
|
||||
return Genesis{trend}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
package cool
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
abci "github.com/tendermint/tendermint/abci/types"
|
||||
dbm "github.com/tendermint/tendermint/libs/db"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/codec"
|
||||
"github.com/cosmos/cosmos-sdk/store"
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
auth "github.com/cosmos/cosmos-sdk/x/auth"
|
||||
bank "github.com/cosmos/cosmos-sdk/x/bank"
|
||||
)
|
||||
|
||||
func setupMultiStore() (sdk.MultiStore, *sdk.KVStoreKey) {
|
||||
db := dbm.NewMemDB()
|
||||
capKey := sdk.NewKVStoreKey("capkey")
|
||||
ms := store.NewCommitMultiStore(db)
|
||||
ms.MountStoreWithDB(capKey, sdk.StoreTypeIAVL, db)
|
||||
ms.LoadLatestVersion()
|
||||
return ms, capKey
|
||||
}
|
||||
|
||||
func TestCoolKeeper(t *testing.T) {
|
||||
ms, capKey := setupMultiStore()
|
||||
cdc := codec.New()
|
||||
auth.RegisterBaseAccount(cdc)
|
||||
|
||||
am := auth.NewAccountKeeper(cdc, capKey, auth.ProtoBaseAccount)
|
||||
ctx := sdk.NewContext(ms, abci.Header{}, false, nil)
|
||||
ck := bank.NewBaseKeeper(am)
|
||||
keeper := NewKeeper(capKey, ck, DefaultCodespace)
|
||||
|
||||
err := InitGenesis(ctx, keeper, Genesis{"icy"})
|
||||
require.Nil(t, err)
|
||||
|
||||
genesis := ExportGenesis(ctx, keeper)
|
||||
require.Nil(t, err)
|
||||
require.Equal(t, genesis, Genesis{"icy"})
|
||||
|
||||
res := keeper.GetTrend(ctx)
|
||||
require.Equal(t, res, "icy")
|
||||
|
||||
keeper.setTrend(ctx, "fiery")
|
||||
res = keeper.GetTrend(ctx)
|
||||
require.Equal(t, res, "fiery")
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
package cool
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
)
|
||||
|
||||
// a really cool msg type, these fields are can be entirely arbitrary and
|
||||
// custom to your message
|
||||
type MsgSetTrend struct {
|
||||
Sender sdk.AccAddress
|
||||
Cool string
|
||||
}
|
||||
|
||||
// genesis state - specify genesis trend
|
||||
type Genesis struct {
|
||||
Trend string `json:"trend"`
|
||||
}
|
||||
|
||||
// new cool message
|
||||
func NewMsgSetTrend(sender sdk.AccAddress, cool string) MsgSetTrend {
|
||||
return MsgSetTrend{
|
||||
Sender: sender,
|
||||
Cool: cool,
|
||||
}
|
||||
}
|
||||
|
||||
// enforce the msg type at compile time
|
||||
var _ sdk.Msg = MsgSetTrend{}
|
||||
|
||||
// nolint
|
||||
func (msg MsgSetTrend) Route() string { return "cool" }
|
||||
func (msg MsgSetTrend) Type() string { return "set_trend" }
|
||||
func (msg MsgSetTrend) GetSigners() []sdk.AccAddress { return []sdk.AccAddress{msg.Sender} }
|
||||
func (msg MsgSetTrend) String() string {
|
||||
return fmt.Sprintf("MsgSetTrend{Sender: %v, Cool: %v}", msg.Sender, msg.Cool)
|
||||
}
|
||||
|
||||
// Validate Basic is used to quickly disqualify obviously invalid messages quickly
|
||||
func (msg MsgSetTrend) ValidateBasic() sdk.Error {
|
||||
if len(msg.Sender) == 0 {
|
||||
return sdk.ErrUnknownAddress(msg.Sender.String()).TraceSDK("")
|
||||
}
|
||||
if strings.Contains(msg.Cool, "hot") {
|
||||
return sdk.ErrUnauthorized("").TraceSDK("hot is not cool")
|
||||
}
|
||||
if strings.Contains(msg.Cool, "warm") {
|
||||
return sdk.ErrUnauthorized("").TraceSDK("warm is not very cool")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Get the bytes for the message signer to sign on
|
||||
func (msg MsgSetTrend) GetSignBytes() []byte {
|
||||
b, err := json.Marshal(msg)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return sdk.MustSortJSON(b)
|
||||
}
|
||||
|
||||
//_______________________________________________________________________
|
||||
|
||||
// A message type to quiz how cool you are. these fields are can be entirely
|
||||
// arbitrary and custom to your message
|
||||
type MsgQuiz struct {
|
||||
Sender sdk.AccAddress
|
||||
CoolAnswer string
|
||||
}
|
||||
|
||||
// New cool message
|
||||
func NewMsgQuiz(sender sdk.AccAddress, coolerthancool string) MsgQuiz {
|
||||
return MsgQuiz{
|
||||
Sender: sender,
|
||||
CoolAnswer: coolerthancool,
|
||||
}
|
||||
}
|
||||
|
||||
// enforce the msg type at compile time
|
||||
var _ sdk.Msg = MsgQuiz{}
|
||||
|
||||
// nolint
|
||||
func (msg MsgQuiz) Route() string { return "cool" }
|
||||
func (msg MsgQuiz) Type() string { return "quiz" }
|
||||
func (msg MsgQuiz) GetSigners() []sdk.AccAddress { return []sdk.AccAddress{msg.Sender} }
|
||||
func (msg MsgQuiz) String() string {
|
||||
return fmt.Sprintf("MsgQuiz{Sender: %v, CoolAnswer: %v}", msg.Sender, msg.CoolAnswer)
|
||||
}
|
||||
|
||||
// Validate Basic is used to quickly disqualify obviously invalid messages quickly
|
||||
func (msg MsgQuiz) ValidateBasic() sdk.Error {
|
||||
if len(msg.Sender) == 0 {
|
||||
return sdk.ErrUnknownAddress(msg.Sender.String()).TraceSDK("")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Get the bytes for the message signer to sign on
|
||||
func (msg MsgQuiz) GetSignBytes() []byte {
|
||||
b, err := json.Marshal(msg)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return sdk.MustSortJSON(b)
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
# Oracle Module
|
||||
|
||||
`x/oracle` provides a way to receive external information(real world price, events from other chains, etc.) with validators' vote. Each validator make transaction which contains those informations, and Oracle aggregates them until the supermajority signed on it. After then, Oracle sends the information to the actual module that processes the information, and prune the votes from the state.
|
||||
|
||||
## Integration
|
||||
|
||||
See `x/oracle/oracle_test.go` for the code that using Oracle
|
||||
|
||||
To use Oracle in your module, first define a `payload`. It should implement `oracle.Payload` and contain nessesary information for your module. Including nonce is recommended.
|
||||
|
||||
```go
|
||||
type MyPayload struct {
|
||||
Data int
|
||||
Nonce int
|
||||
}
|
||||
```
|
||||
|
||||
When you write a payload, its `.Route()` should return same route with your module is registered on the router. It is because `oracle.Msg` inherits `.Route()` from its embedded payload and it should be handled on the user modules.
|
||||
|
||||
Then route every incoming `oracle.Msg` to `oracle.Keeper.Handler()` with the function that implements `oracle.Handler`.
|
||||
|
||||
```go
|
||||
func NewHandler(keeper Keeper) sdk.Handler {
|
||||
return func(ctx sdk.Context, msg sdk.Msg) sdk.Result {
|
||||
switch msg := msg.(type) {
|
||||
case oracle.Msg:
|
||||
return keeper.oracle.Handle(ctx sdk.Context, p oracle.Payload) sdk.Error {
|
||||
switch p := p.(type) {
|
||||
case MyPayload:
|
||||
return handleMyPayload(ctx, keeper, p)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
In the previous example, the keeper has an `oracle.Keeper`. `oracle.Keeper`s are generated by `NewKeeper`.
|
||||
|
||||
```go
|
||||
func NewKeeper(key sdk.StoreKey, cdc *codec.Codec, valset sdk.ValidatorSet, supermaj sdk.Dec, timeout int64) Keeper {
|
||||
return Keeper {
|
||||
cdc: cdc,
|
||||
key: key,
|
||||
|
||||
// ValidatorSet to get validators infor
|
||||
valset: valset,
|
||||
|
||||
// The keeper will pass payload
|
||||
// when more than 2/3 signed on it
|
||||
supermaj: supermaj,
|
||||
// The keeper will prune votes after 100 blocks from last sign
|
||||
timeout: timeout,
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Now the validators can send `oracle.Msg`s with `MyPayload` when they want to witness external events.
|
||||
@@ -0,0 +1,31 @@
|
||||
package oracle
|
||||
|
||||
import (
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
)
|
||||
|
||||
// Oracle errors reserve 1101-1199
|
||||
const (
|
||||
CodeNotValidator sdk.CodeType = 1101
|
||||
CodeAlreadyProcessed sdk.CodeType = 1102
|
||||
CodeAlreadySigned sdk.CodeType = 1103
|
||||
CodeUnknownRequest sdk.CodeType = sdk.CodeUnknownRequest
|
||||
)
|
||||
|
||||
// ----------------------------------------
|
||||
// Error constructors
|
||||
|
||||
// ErrNotValidator called when the signer of a Msg is not a validator
|
||||
func ErrNotValidator(codespace sdk.CodespaceType, address sdk.AccAddress) sdk.Error {
|
||||
return sdk.NewError(codespace, CodeNotValidator, address.String())
|
||||
}
|
||||
|
||||
// ErrAlreadyProcessed called when a payload is already processed
|
||||
func ErrAlreadyProcessed(codespace sdk.CodespaceType) sdk.Error {
|
||||
return sdk.NewError(codespace, CodeAlreadyProcessed, "")
|
||||
}
|
||||
|
||||
// ErrAlreadySigned called when the signer is trying to double signing
|
||||
func ErrAlreadySigned(codespace sdk.CodespaceType) sdk.Error {
|
||||
return sdk.NewError(codespace, CodeAlreadySigned, "")
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
package oracle
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
)
|
||||
|
||||
// Handler handles payload after it passes voting process
|
||||
type Handler func(ctx sdk.Context, p Payload) sdk.Error
|
||||
|
||||
func (keeper Keeper) update(ctx sdk.Context, val sdk.Validator, valset sdk.ValidatorSet, p Payload, info Info) Info {
|
||||
info.Power = info.Power.Add(val.GetPower())
|
||||
|
||||
// Return if the voted power is not bigger than required power
|
||||
totalPower := valset.TotalPower(ctx)
|
||||
requiredPower := totalPower.Mul(keeper.supermaj)
|
||||
if !info.Power.GT(requiredPower) {
|
||||
return info
|
||||
}
|
||||
|
||||
// Check if the validators hash has been changed during the vote process
|
||||
// and recalculate voted power
|
||||
hash := ctx.BlockHeader().ValidatorsHash
|
||||
if !bytes.Equal(hash, info.Hash) {
|
||||
info.Power = sdk.ZeroDec()
|
||||
info.Hash = hash
|
||||
prefix := GetSignPrefix(p, keeper.cdc)
|
||||
store := ctx.KVStore(keeper.key)
|
||||
iter := sdk.KVStorePrefixIterator(store, prefix)
|
||||
defer iter.Close()
|
||||
for ; iter.Valid(); iter.Next() {
|
||||
if valset.Validator(ctx, iter.Value()) != nil {
|
||||
store.Delete(iter.Key())
|
||||
continue
|
||||
}
|
||||
info.Power = info.Power.Add(val.GetPower())
|
||||
}
|
||||
if !info.Power.GT(totalPower.Mul(keeper.supermaj)) {
|
||||
return info
|
||||
}
|
||||
}
|
||||
|
||||
info.Status = Processed
|
||||
return info
|
||||
}
|
||||
|
||||
// Handle is used by other modules to handle Msg
|
||||
func (keeper Keeper) Handle(h Handler, ctx sdk.Context, o Msg, codespace sdk.CodespaceType) sdk.Result {
|
||||
valset := keeper.valset
|
||||
|
||||
signer := o.Signer
|
||||
payload := o.Payload
|
||||
|
||||
// Check the oracle is not in process
|
||||
info := keeper.Info(ctx, payload)
|
||||
if info.Status != Pending {
|
||||
return ErrAlreadyProcessed(codespace).Result()
|
||||
}
|
||||
|
||||
// Check if it is reporting timeout
|
||||
now := ctx.BlockHeight()
|
||||
if now > info.LastSigned+keeper.timeout {
|
||||
info = Info{Status: Timeout}
|
||||
keeper.setInfo(ctx, payload, info)
|
||||
keeper.clearSigns(ctx, payload)
|
||||
return sdk.Result{}
|
||||
}
|
||||
info.LastSigned = ctx.BlockHeight()
|
||||
|
||||
// check the signer is a validator
|
||||
val := valset.Validator(ctx, sdk.ValAddress(signer))
|
||||
if val == nil {
|
||||
return ErrNotValidator(codespace, signer).Result()
|
||||
}
|
||||
|
||||
// Check double signing
|
||||
if keeper.signed(ctx, payload, signer) {
|
||||
return ErrAlreadySigned(codespace).Result()
|
||||
}
|
||||
|
||||
keeper.sign(ctx, payload, signer)
|
||||
|
||||
info = keeper.update(ctx, val, valset, payload, info)
|
||||
if info.Status == Processed {
|
||||
info = Info{Status: Processed}
|
||||
}
|
||||
|
||||
keeper.setInfo(ctx, payload, info)
|
||||
|
||||
if info.Status == Processed {
|
||||
keeper.clearSigns(ctx, payload)
|
||||
cctx, write := ctx.CacheContext()
|
||||
err := h(cctx, payload)
|
||||
if err != nil {
|
||||
return sdk.Result{
|
||||
Code: sdk.ABCICodeOK,
|
||||
Log: err.ABCILog(),
|
||||
}
|
||||
}
|
||||
write()
|
||||
|
||||
}
|
||||
|
||||
return sdk.Result{}
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
package oracle
|
||||
|
||||
import (
|
||||
"github.com/cosmos/cosmos-sdk/codec"
|
||||
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
)
|
||||
|
||||
// Keeper of the oracle store
|
||||
type Keeper struct {
|
||||
key sdk.StoreKey
|
||||
cdc *codec.Codec
|
||||
|
||||
valset sdk.ValidatorSet
|
||||
|
||||
supermaj sdk.Dec
|
||||
timeout int64
|
||||
}
|
||||
|
||||
// NewKeeper constructs a new keeper
|
||||
func NewKeeper(key sdk.StoreKey, cdc *codec.Codec, valset sdk.ValidatorSet, supermaj sdk.Dec, timeout int64) Keeper {
|
||||
if timeout < 0 {
|
||||
panic("Timeout should not be negative")
|
||||
}
|
||||
|
||||
return Keeper{
|
||||
key: key,
|
||||
cdc: cdc,
|
||||
|
||||
valset: valset,
|
||||
|
||||
supermaj: supermaj,
|
||||
timeout: timeout,
|
||||
}
|
||||
}
|
||||
|
||||
// InfoStatus - current status of an Info
|
||||
type InfoStatus int8
|
||||
|
||||
// Define InfoStatus
|
||||
const (
|
||||
Pending = InfoStatus(iota)
|
||||
Processed
|
||||
Timeout
|
||||
)
|
||||
|
||||
// Info for each payload
|
||||
type Info struct {
|
||||
Power sdk.Dec
|
||||
Hash []byte
|
||||
LastSigned int64
|
||||
Status InfoStatus
|
||||
}
|
||||
|
||||
// EmptyInfo construct an empty Info
|
||||
func EmptyInfo(ctx sdk.Context) Info {
|
||||
return Info{
|
||||
Power: sdk.ZeroDec(),
|
||||
Hash: ctx.BlockHeader().ValidatorsHash,
|
||||
LastSigned: ctx.BlockHeight(),
|
||||
Status: Pending,
|
||||
}
|
||||
}
|
||||
|
||||
// Info returns the information about a payload
|
||||
func (keeper Keeper) Info(ctx sdk.Context, p Payload) (res Info) {
|
||||
store := ctx.KVStore(keeper.key)
|
||||
|
||||
key := GetInfoKey(p, keeper.cdc)
|
||||
bz := store.Get(key)
|
||||
if bz == nil {
|
||||
return EmptyInfo(ctx)
|
||||
}
|
||||
keeper.cdc.MustUnmarshalBinaryLengthPrefixed(bz, &res)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (keeper Keeper) setInfo(ctx sdk.Context, p Payload, info Info) {
|
||||
store := ctx.KVStore(keeper.key)
|
||||
|
||||
key := GetInfoKey(p, keeper.cdc)
|
||||
bz := keeper.cdc.MustMarshalBinaryLengthPrefixed(info)
|
||||
store.Set(key, bz)
|
||||
}
|
||||
|
||||
func (keeper Keeper) sign(ctx sdk.Context, p Payload, signer sdk.AccAddress) {
|
||||
store := ctx.KVStore(keeper.key)
|
||||
|
||||
key := GetSignKey(p, signer, keeper.cdc)
|
||||
store.Set(key, signer)
|
||||
}
|
||||
|
||||
func (keeper Keeper) signed(ctx sdk.Context, p Payload, signer sdk.AccAddress) bool {
|
||||
store := ctx.KVStore(keeper.key)
|
||||
|
||||
key := GetSignKey(p, signer, keeper.cdc)
|
||||
return store.Has(key)
|
||||
}
|
||||
|
||||
func (keeper Keeper) clearSigns(ctx sdk.Context, p Payload) {
|
||||
store := ctx.KVStore(keeper.key)
|
||||
|
||||
prefix := GetSignPrefix(p, keeper.cdc)
|
||||
|
||||
iter := sdk.KVStorePrefixIterator(store, prefix)
|
||||
for ; iter.Valid(); iter.Next() {
|
||||
store.Delete(iter.Key())
|
||||
}
|
||||
iter.Close()
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package oracle
|
||||
|
||||
import (
|
||||
"github.com/cosmos/cosmos-sdk/codec"
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
)
|
||||
|
||||
// GetInfoKey returns the key for OracleInfo
|
||||
func GetInfoKey(p Payload, cdc *codec.Codec) []byte {
|
||||
bz := cdc.MustMarshalBinaryLengthPrefixed(p)
|
||||
return append([]byte{0x00}, bz...)
|
||||
}
|
||||
|
||||
// GetSignPrefix returns the prefix for signs
|
||||
func GetSignPrefix(p Payload, cdc *codec.Codec) []byte {
|
||||
bz := cdc.MustMarshalBinaryLengthPrefixed(p)
|
||||
return append([]byte{0x01}, bz...)
|
||||
}
|
||||
|
||||
// GetSignKey returns the key for sign
|
||||
func GetSignKey(p Payload, signer sdk.AccAddress, cdc *codec.Codec) []byte {
|
||||
return append(GetSignPrefix(p, cdc), signer...)
|
||||
}
|
||||
@@ -0,0 +1,225 @@
|
||||
package oracle
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
abci "github.com/tendermint/tendermint/abci/types"
|
||||
dbm "github.com/tendermint/tendermint/libs/db"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/codec"
|
||||
"github.com/cosmos/cosmos-sdk/docs/examples/democoin/mock"
|
||||
"github.com/cosmos/cosmos-sdk/store"
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
)
|
||||
|
||||
func defaultContext(keys ...sdk.StoreKey) sdk.Context {
|
||||
db := dbm.NewMemDB()
|
||||
cms := store.NewCommitMultiStore(db)
|
||||
for _, key := range keys {
|
||||
cms.MountStoreWithDB(key, sdk.StoreTypeIAVL, db)
|
||||
}
|
||||
cms.LoadLatestVersion()
|
||||
ctx := sdk.NewContext(cms, abci.Header{}, false, nil)
|
||||
return ctx
|
||||
}
|
||||
|
||||
type seqOracle struct {
|
||||
Seq int
|
||||
Nonce int
|
||||
}
|
||||
|
||||
func (o seqOracle) Route() string {
|
||||
return "seq"
|
||||
}
|
||||
func (o seqOracle) Type() string {
|
||||
return "seq"
|
||||
}
|
||||
|
||||
func (o seqOracle) ValidateBasic() sdk.Error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func makeCodec() *codec.Codec {
|
||||
var cdc = codec.New()
|
||||
|
||||
cdc.RegisterInterface((*sdk.Msg)(nil), nil)
|
||||
cdc.RegisterConcrete(Msg{}, "test/Oracle", nil)
|
||||
|
||||
cdc.RegisterInterface((*Payload)(nil), nil)
|
||||
cdc.RegisterConcrete(seqOracle{}, "test/oracle/seqOracle", nil)
|
||||
|
||||
cdc.Seal()
|
||||
|
||||
return cdc
|
||||
}
|
||||
|
||||
func seqHandler(ork Keeper, key sdk.StoreKey, codespace sdk.CodespaceType) sdk.Handler {
|
||||
return func(ctx sdk.Context, msg sdk.Msg) sdk.Result {
|
||||
switch msg := msg.(type) {
|
||||
case Msg:
|
||||
return ork.Handle(func(ctx sdk.Context, p Payload) sdk.Error {
|
||||
switch p := p.(type) {
|
||||
case seqOracle:
|
||||
return handleSeqOracle(ctx, key, p)
|
||||
default:
|
||||
return sdk.ErrUnknownRequest("")
|
||||
}
|
||||
}, ctx, msg, codespace)
|
||||
default:
|
||||
return sdk.ErrUnknownRequest("").Result()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func getSequence(ctx sdk.Context, key sdk.StoreKey) int {
|
||||
store := ctx.KVStore(key)
|
||||
seqbz := store.Get([]byte("seq"))
|
||||
|
||||
var seq int
|
||||
if seqbz == nil {
|
||||
seq = 0
|
||||
} else {
|
||||
codec.New().MustUnmarshalBinaryLengthPrefixed(seqbz, &seq)
|
||||
}
|
||||
|
||||
return seq
|
||||
}
|
||||
|
||||
func handleSeqOracle(ctx sdk.Context, key sdk.StoreKey, o seqOracle) sdk.Error {
|
||||
store := ctx.KVStore(key)
|
||||
|
||||
seq := getSequence(ctx, key)
|
||||
if seq != o.Seq {
|
||||
return sdk.NewError(sdk.CodespaceRoot, 1, "")
|
||||
}
|
||||
|
||||
bz := codec.New().MustMarshalBinaryLengthPrefixed(seq + 1)
|
||||
store.Set([]byte("seq"), bz)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func TestOracle(t *testing.T) {
|
||||
cdc := makeCodec()
|
||||
|
||||
addr1 := []byte("addr1")
|
||||
addr2 := []byte("addr2")
|
||||
addr3 := []byte("addr3")
|
||||
addr4 := []byte("addr4")
|
||||
valset := &mock.ValidatorSet{[]mock.Validator{
|
||||
{addr1, sdk.NewDec(7)},
|
||||
{addr2, sdk.NewDec(7)},
|
||||
{addr3, sdk.NewDec(1)},
|
||||
}}
|
||||
|
||||
key := sdk.NewKVStoreKey("testkey")
|
||||
ctx := defaultContext(key)
|
||||
|
||||
bz, err := json.Marshal(valset)
|
||||
require.Nil(t, err)
|
||||
ctx = ctx.WithBlockHeader(abci.Header{ValidatorsHash: bz})
|
||||
|
||||
ork := NewKeeper(key, cdc, valset, sdk.NewDecWithPrec(667, 3), 100) // 66.7%
|
||||
h := seqHandler(ork, key, sdk.CodespaceRoot)
|
||||
|
||||
// Nonmock.Validator signed, transaction failed
|
||||
msg := Msg{seqOracle{0, 0}, []byte("randomguy")}
|
||||
res := h(ctx, msg)
|
||||
require.False(t, res.IsOK())
|
||||
require.Equal(t, 0, getSequence(ctx, key))
|
||||
|
||||
// Less than 2/3 signed, msg not processed
|
||||
msg.Signer = addr1
|
||||
res = h(ctx, msg)
|
||||
require.True(t, res.IsOK())
|
||||
require.Equal(t, 0, getSequence(ctx, key))
|
||||
|
||||
// Double signed, transaction failed
|
||||
res = h(ctx, msg)
|
||||
require.False(t, res.IsOK())
|
||||
require.Equal(t, 0, getSequence(ctx, key))
|
||||
|
||||
// More than 2/3 signed, msg processed
|
||||
msg.Signer = addr2
|
||||
res = h(ctx, msg)
|
||||
require.True(t, res.IsOK())
|
||||
require.Equal(t, 1, getSequence(ctx, key))
|
||||
|
||||
// Already processed, transaction failed
|
||||
msg.Signer = addr3
|
||||
res = h(ctx, msg)
|
||||
require.False(t, res.IsOK())
|
||||
require.Equal(t, 1, getSequence(ctx, key))
|
||||
|
||||
// Less than 2/3 signed, msg not processed
|
||||
msg = Msg{seqOracle{100, 1}, addr1}
|
||||
res = h(ctx, msg)
|
||||
require.True(t, res.IsOK())
|
||||
require.Equal(t, 1, getSequence(ctx, key))
|
||||
|
||||
// More than 2/3 signed but payload is invalid
|
||||
msg.Signer = addr2
|
||||
res = h(ctx, msg)
|
||||
require.True(t, res.IsOK())
|
||||
require.NotEqual(t, "", res.Log)
|
||||
require.Equal(t, 1, getSequence(ctx, key))
|
||||
|
||||
// Already processed, transaction failed
|
||||
msg.Signer = addr3
|
||||
res = h(ctx, msg)
|
||||
require.False(t, res.IsOK())
|
||||
require.Equal(t, 1, getSequence(ctx, key))
|
||||
|
||||
// Should handle mock.Validator set change
|
||||
valset.AddValidator(mock.Validator{addr4, sdk.NewDec(12)})
|
||||
bz, err = json.Marshal(valset)
|
||||
require.Nil(t, err)
|
||||
ctx = ctx.WithBlockHeader(abci.Header{ValidatorsHash: bz})
|
||||
|
||||
// Less than 2/3 signed, msg not processed
|
||||
msg = Msg{seqOracle{1, 2}, addr1}
|
||||
res = h(ctx, msg)
|
||||
require.True(t, res.IsOK())
|
||||
require.Equal(t, 1, getSequence(ctx, key))
|
||||
|
||||
// Less than 2/3 signed, msg not processed
|
||||
msg.Signer = addr2
|
||||
res = h(ctx, msg)
|
||||
require.True(t, res.IsOK())
|
||||
require.Equal(t, 1, getSequence(ctx, key))
|
||||
|
||||
// More than 2/3 signed, msg processed
|
||||
msg.Signer = addr4
|
||||
res = h(ctx, msg)
|
||||
require.True(t, res.IsOK())
|
||||
require.Equal(t, 2, getSequence(ctx, key))
|
||||
|
||||
// Should handle mock.Validator set change while oracle process is happening
|
||||
msg = Msg{seqOracle{2, 3}, addr4}
|
||||
|
||||
// Less than 2/3 signed, msg not processed
|
||||
res = h(ctx, msg)
|
||||
require.True(t, res.IsOK())
|
||||
require.Equal(t, 2, getSequence(ctx, key))
|
||||
|
||||
// Signed mock.Validator is kicked out
|
||||
valset.RemoveValidator(addr4)
|
||||
bz, err = json.Marshal(valset)
|
||||
require.Nil(t, err)
|
||||
ctx = ctx.WithBlockHeader(abci.Header{ValidatorsHash: bz})
|
||||
|
||||
// Less than 2/3 signed, msg not processed
|
||||
msg.Signer = addr1
|
||||
res = h(ctx, msg)
|
||||
require.True(t, res.IsOK())
|
||||
require.Equal(t, 2, getSequence(ctx, key))
|
||||
|
||||
// More than 2/3 signed, msg processed
|
||||
msg.Signer = addr2
|
||||
res = h(ctx, msg)
|
||||
require.True(t, res.IsOK())
|
||||
require.Equal(t, 3, getSequence(ctx, key))
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
package oracle
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
)
|
||||
|
||||
// Msg - struct for voting on payloads
|
||||
type Msg struct {
|
||||
Payload
|
||||
Signer sdk.AccAddress
|
||||
}
|
||||
|
||||
// GetSignBytes implements sdk.Msg
|
||||
func (msg Msg) GetSignBytes() []byte {
|
||||
bz, err := json.Marshal(msg)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return sdk.MustSortJSON(bz)
|
||||
}
|
||||
|
||||
// GetSigners implements sdk.Msg
|
||||
func (msg Msg) GetSigners() []sdk.AccAddress {
|
||||
return []sdk.AccAddress{msg.Signer}
|
||||
}
|
||||
|
||||
// Payload defines inner data for actual execution
|
||||
type Payload interface {
|
||||
Route() string
|
||||
Type() string
|
||||
ValidateBasic() sdk.Error
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
package pow
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
"github.com/cosmos/cosmos-sdk/x/auth"
|
||||
"github.com/cosmos/cosmos-sdk/x/bank"
|
||||
"github.com/cosmos/cosmos-sdk/x/mock"
|
||||
|
||||
abci "github.com/tendermint/tendermint/abci/types"
|
||||
"github.com/tendermint/tendermint/crypto/ed25519"
|
||||
)
|
||||
|
||||
var (
|
||||
priv1 = ed25519.GenPrivKey()
|
||||
addr1 = sdk.AccAddress(priv1.PubKey().Address())
|
||||
)
|
||||
|
||||
// initialize the mock application for this module
|
||||
func getMockApp(t *testing.T) *mock.App {
|
||||
mapp := mock.NewApp()
|
||||
|
||||
RegisterCodec(mapp.Cdc)
|
||||
keyPOW := sdk.NewKVStoreKey("pow")
|
||||
bankKeeper := bank.NewBaseKeeper(mapp.AccountKeeper)
|
||||
config := Config{"pow", 1}
|
||||
keeper := NewKeeper(keyPOW, config, bankKeeper, mapp.RegisterCodespace(DefaultCodespace))
|
||||
mapp.Router().AddRoute("pow", keeper.Handler)
|
||||
|
||||
mapp.SetInitChainer(getInitChainer(mapp, keeper))
|
||||
|
||||
require.NoError(t, mapp.CompleteSetup(keyPOW))
|
||||
|
||||
mapp.Seal()
|
||||
|
||||
return mapp
|
||||
}
|
||||
|
||||
// overwrite the mock init chainer
|
||||
func getInitChainer(mapp *mock.App, keeper Keeper) sdk.InitChainer {
|
||||
return func(ctx sdk.Context, req abci.RequestInitChain) abci.ResponseInitChain {
|
||||
mapp.InitChainer(ctx, req)
|
||||
|
||||
genesis := Genesis{
|
||||
Difficulty: 1,
|
||||
Count: 0,
|
||||
}
|
||||
InitGenesis(ctx, keeper, genesis)
|
||||
|
||||
return abci.ResponseInitChain{}
|
||||
}
|
||||
}
|
||||
|
||||
func TestMsgMine(t *testing.T) {
|
||||
mapp := getMockApp(t)
|
||||
|
||||
// Construct genesis state
|
||||
acc1 := &auth.BaseAccount{
|
||||
Address: addr1,
|
||||
Coins: nil,
|
||||
}
|
||||
accs := []auth.Account{acc1}
|
||||
|
||||
// Initialize the chain (nil)
|
||||
mock.SetGenesis(mapp, accs)
|
||||
|
||||
// A checkTx context (true)
|
||||
ctxCheck := mapp.BaseApp.NewContext(true, abci.Header{})
|
||||
res1 := mapp.AccountKeeper.GetAccount(ctxCheck, addr1)
|
||||
require.Equal(t, acc1, res1)
|
||||
|
||||
// Mine and check for reward
|
||||
mineMsg1 := GenerateMsgMine(addr1, 1, 2)
|
||||
mock.SignCheckDeliver(t, mapp.BaseApp, []sdk.Msg{mineMsg1}, []int64{0}, []int64{0}, true, true, priv1)
|
||||
mock.CheckBalance(t, mapp, addr1, sdk.Coins{{"pow", sdk.NewInt(1)}})
|
||||
// Mine again and check for reward
|
||||
mineMsg2 := GenerateMsgMine(addr1, 2, 3)
|
||||
mock.SignCheckDeliver(t, mapp.BaseApp, []sdk.Msg{mineMsg2}, []int64{0}, []int64{1}, true, true, priv1)
|
||||
mock.CheckBalance(t, mapp, addr1, sdk.Coins{{"pow", sdk.NewInt(2)}})
|
||||
// Mine again - should be invalid
|
||||
mock.SignCheckDeliver(t, mapp.BaseApp, []sdk.Msg{mineMsg2}, []int64{0}, []int64{1}, false, false, priv1)
|
||||
mock.CheckBalance(t, mapp, addr1, sdk.Coins{{"pow", sdk.NewInt(2)}})
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/client/context"
|
||||
"github.com/cosmos/cosmos-sdk/client/utils"
|
||||
"github.com/cosmos/cosmos-sdk/codec"
|
||||
"github.com/cosmos/cosmos-sdk/docs/examples/democoin/x/pow"
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
authcmd "github.com/cosmos/cosmos-sdk/x/auth/client/cli"
|
||||
authtxb "github.com/cosmos/cosmos-sdk/x/auth/client/txbuilder"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
// command to mine some pow!
|
||||
func MineCmd(cdc *codec.Codec) *cobra.Command {
|
||||
return &cobra.Command{
|
||||
Use: "mine [difficulty] [count] [nonce] [solution]",
|
||||
Short: "Mine some coins with proof-of-work!",
|
||||
Args: cobra.ExactArgs(4),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
txBldr := authtxb.NewTxBuilderFromCLI().WithCodec(cdc)
|
||||
cliCtx := context.NewCLIContext().
|
||||
WithCodec(cdc).
|
||||
WithAccountDecoder(authcmd.GetAccountDecoder(cdc))
|
||||
|
||||
from, err := cliCtx.GetFromAddress()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
difficulty, err := strconv.ParseUint(args[0], 0, 64)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
count, err := strconv.ParseUint(args[1], 0, 64)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
nonce, err := strconv.ParseUint(args[2], 0, 64)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
solution := []byte(args[3])
|
||||
msg := pow.NewMsgMine(from, difficulty, count, nonce, solution)
|
||||
|
||||
// Build and sign the transaction, then broadcast to a Tendermint
|
||||
// node.
|
||||
return utils.CompleteAndBroadcastTxCli(txBldr, cliCtx, []sdk.Msg{msg})
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
package pow
|
||||
|
||||
import (
|
||||
"github.com/cosmos/cosmos-sdk/codec"
|
||||
)
|
||||
|
||||
// Register concrete types on codec codec
|
||||
func RegisterCodec(cdc *codec.Codec) {
|
||||
cdc.RegisterConcrete(MsgMine{}, "pow/Mine", nil)
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
package pow
|
||||
|
||||
import (
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
)
|
||||
|
||||
// TODO remove, seems hacky
|
||||
type CodeType = sdk.CodeType
|
||||
|
||||
// POW errors reserve 200 ~ 299
|
||||
const (
|
||||
DefaultCodespace sdk.CodespaceType = 5
|
||||
CodeInvalidDifficulty CodeType = 201
|
||||
CodeNonexistentDifficulty CodeType = 202
|
||||
CodeNonexistentReward CodeType = 203
|
||||
CodeNonexistentCount CodeType = 204
|
||||
CodeInvalidProof CodeType = 205
|
||||
CodeNotBelowTarget CodeType = 206
|
||||
CodeInvalidCount CodeType = 207
|
||||
CodeUnknownRequest CodeType = sdk.CodeUnknownRequest
|
||||
)
|
||||
|
||||
func codeToDefaultMsg(code CodeType) string {
|
||||
switch code {
|
||||
case CodeInvalidDifficulty:
|
||||
return "insuffient difficulty"
|
||||
case CodeNonexistentDifficulty:
|
||||
return "nonexistent difficulty"
|
||||
case CodeNonexistentReward:
|
||||
return "nonexistent reward"
|
||||
case CodeNonexistentCount:
|
||||
return "nonexistent count"
|
||||
case CodeInvalidProof:
|
||||
return "invalid proof"
|
||||
case CodeNotBelowTarget:
|
||||
return "not below target"
|
||||
case CodeInvalidCount:
|
||||
return "invalid count"
|
||||
case CodeUnknownRequest:
|
||||
return "unknown request"
|
||||
default:
|
||||
return sdk.CodeToDefaultMsg(code)
|
||||
}
|
||||
}
|
||||
|
||||
// nolint
|
||||
func ErrInvalidDifficulty(codespace sdk.CodespaceType, msg string) sdk.Error {
|
||||
return newError(codespace, CodeInvalidDifficulty, msg)
|
||||
}
|
||||
func ErrNonexistentDifficulty(codespace sdk.CodespaceType) sdk.Error {
|
||||
return newError(codespace, CodeNonexistentDifficulty, "")
|
||||
}
|
||||
func ErrNonexistentReward(codespace sdk.CodespaceType) sdk.Error {
|
||||
return newError(codespace, CodeNonexistentReward, "")
|
||||
}
|
||||
func ErrNonexistentCount(codespace sdk.CodespaceType) sdk.Error {
|
||||
return newError(codespace, CodeNonexistentCount, "")
|
||||
}
|
||||
func ErrInvalidProof(codespace sdk.CodespaceType, msg string) sdk.Error {
|
||||
return newError(codespace, CodeInvalidProof, msg)
|
||||
}
|
||||
func ErrNotBelowTarget(codespace sdk.CodespaceType, msg string) sdk.Error {
|
||||
return newError(codespace, CodeNotBelowTarget, msg)
|
||||
}
|
||||
func ErrInvalidCount(codespace sdk.CodespaceType, msg string) sdk.Error {
|
||||
return newError(codespace, CodeInvalidCount, msg)
|
||||
}
|
||||
|
||||
func msgOrDefaultMsg(msg string, code CodeType) string {
|
||||
if msg != "" {
|
||||
return msg
|
||||
}
|
||||
return codeToDefaultMsg(code)
|
||||
}
|
||||
|
||||
func newError(codespace sdk.CodespaceType, code CodeType, msg string) sdk.Error {
|
||||
msg = msgOrDefaultMsg(msg, code)
|
||||
return sdk.NewError(codespace, code, msg)
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package pow
|
||||
|
||||
import (
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
)
|
||||
|
||||
// POW handler
|
||||
func (pk Keeper) Handler(ctx sdk.Context, msg sdk.Msg) sdk.Result {
|
||||
switch msg := msg.(type) {
|
||||
case MsgMine:
|
||||
return handleMsgMine(ctx, pk, msg)
|
||||
default:
|
||||
errMsg := "Unrecognized pow Msg type: " + msg.Type()
|
||||
return sdk.ErrUnknownRequest(errMsg).Result()
|
||||
}
|
||||
}
|
||||
|
||||
func handleMsgMine(ctx sdk.Context, pk Keeper, msg MsgMine) sdk.Result {
|
||||
|
||||
// precondition: msg has passed ValidateBasic
|
||||
|
||||
newDiff, newCount, err := pk.CheckValid(ctx, msg.Difficulty, msg.Count)
|
||||
if err != nil {
|
||||
return err.Result()
|
||||
}
|
||||
|
||||
err = pk.ApplyValid(ctx, msg.Sender, newDiff, newCount)
|
||||
if err != nil {
|
||||
return err.Result()
|
||||
}
|
||||
|
||||
return sdk.Result{}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
package pow
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
abci "github.com/tendermint/tendermint/abci/types"
|
||||
"github.com/tendermint/tendermint/libs/log"
|
||||
|
||||
codec "github.com/cosmos/cosmos-sdk/codec"
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
auth "github.com/cosmos/cosmos-sdk/x/auth"
|
||||
bank "github.com/cosmos/cosmos-sdk/x/bank"
|
||||
)
|
||||
|
||||
func TestPowHandler(t *testing.T) {
|
||||
ms, capKey := setupMultiStore()
|
||||
cdc := codec.New()
|
||||
auth.RegisterBaseAccount(cdc)
|
||||
|
||||
am := auth.NewAccountKeeper(cdc, capKey, auth.ProtoBaseAccount)
|
||||
ctx := sdk.NewContext(ms, abci.Header{}, false, log.NewNopLogger())
|
||||
config := NewConfig("pow", int64(1))
|
||||
ck := bank.NewBaseKeeper(am)
|
||||
keeper := NewKeeper(capKey, config, ck, DefaultCodespace)
|
||||
|
||||
handler := keeper.Handler
|
||||
|
||||
addr := sdk.AccAddress([]byte("sender"))
|
||||
count := uint64(1)
|
||||
difficulty := uint64(2)
|
||||
|
||||
err := InitGenesis(ctx, keeper, Genesis{uint64(1), uint64(0)})
|
||||
require.Nil(t, err)
|
||||
|
||||
nonce, proof := mine(addr, count, difficulty)
|
||||
msg := NewMsgMine(addr, difficulty, count, nonce, proof)
|
||||
|
||||
result := handler(ctx, msg)
|
||||
require.Equal(t, result, sdk.Result{})
|
||||
|
||||
newDiff, err := keeper.GetLastDifficulty(ctx)
|
||||
require.Nil(t, err)
|
||||
require.Equal(t, newDiff, uint64(2))
|
||||
|
||||
newCount, err := keeper.GetLastCount(ctx)
|
||||
require.Nil(t, err)
|
||||
require.Equal(t, newCount, uint64(1))
|
||||
|
||||
// todo assert correct coin change, awaiting https://github.com/cosmos/cosmos-sdk/pull/691
|
||||
|
||||
difficulty = uint64(4)
|
||||
nonce, proof = mine(addr, count, difficulty)
|
||||
msg = NewMsgMine(addr, difficulty, count, nonce, proof)
|
||||
|
||||
result = handler(ctx, msg)
|
||||
require.NotEqual(t, result, sdk.Result{})
|
||||
}
|
||||
@@ -0,0 +1,135 @@
|
||||
package pow
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
bank "github.com/cosmos/cosmos-sdk/x/bank"
|
||||
)
|
||||
|
||||
// module users must specify coin denomination and reward (constant) per PoW solution
|
||||
type Config struct {
|
||||
Denomination string
|
||||
Reward int64
|
||||
}
|
||||
|
||||
// genesis info must specify starting difficulty and starting count
|
||||
type Genesis struct {
|
||||
Difficulty uint64 `json:"difficulty"`
|
||||
Count uint64 `json:"count"`
|
||||
}
|
||||
|
||||
// POW Keeper
|
||||
type Keeper struct {
|
||||
key sdk.StoreKey
|
||||
config Config
|
||||
ck bank.Keeper
|
||||
codespace sdk.CodespaceType
|
||||
}
|
||||
|
||||
func NewConfig(denomination string, reward int64) Config {
|
||||
return Config{denomination, reward}
|
||||
}
|
||||
|
||||
func NewKeeper(key sdk.StoreKey, config Config, ck bank.Keeper, codespace sdk.CodespaceType) Keeper {
|
||||
return Keeper{key, config, ck, codespace}
|
||||
}
|
||||
|
||||
// InitGenesis for the POW module
|
||||
func InitGenesis(ctx sdk.Context, k Keeper, genesis Genesis) error {
|
||||
k.SetLastDifficulty(ctx, genesis.Difficulty)
|
||||
k.SetLastCount(ctx, genesis.Count)
|
||||
return nil
|
||||
}
|
||||
|
||||
// ExportGenesis for the PoW module
|
||||
func ExportGenesis(ctx sdk.Context, k Keeper) Genesis {
|
||||
difficulty, err := k.GetLastDifficulty(ctx)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
count, err := k.GetLastCount(ctx)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return Genesis{
|
||||
difficulty,
|
||||
count,
|
||||
}
|
||||
}
|
||||
|
||||
var lastDifficultyKey = []byte("lastDifficultyKey")
|
||||
|
||||
// get the last mining difficulty
|
||||
func (k Keeper) GetLastDifficulty(ctx sdk.Context) (uint64, error) {
|
||||
store := ctx.KVStore(k.key)
|
||||
stored := store.Get(lastDifficultyKey)
|
||||
if stored == nil {
|
||||
panic("no stored difficulty")
|
||||
} else {
|
||||
return strconv.ParseUint(string(stored), 0, 64)
|
||||
}
|
||||
}
|
||||
|
||||
// set the last mining difficulty
|
||||
func (k Keeper) SetLastDifficulty(ctx sdk.Context, diff uint64) {
|
||||
store := ctx.KVStore(k.key)
|
||||
store.Set(lastDifficultyKey, []byte(strconv.FormatUint(diff, 16)))
|
||||
}
|
||||
|
||||
var countKey = []byte("count")
|
||||
|
||||
// get the last count
|
||||
func (k Keeper) GetLastCount(ctx sdk.Context) (uint64, error) {
|
||||
store := ctx.KVStore(k.key)
|
||||
stored := store.Get(countKey)
|
||||
if stored == nil {
|
||||
panic("no stored count")
|
||||
} else {
|
||||
return strconv.ParseUint(string(stored), 0, 64)
|
||||
}
|
||||
}
|
||||
|
||||
// set the last count
|
||||
func (k Keeper) SetLastCount(ctx sdk.Context, count uint64) {
|
||||
store := ctx.KVStore(k.key)
|
||||
store.Set(countKey, []byte(strconv.FormatUint(count, 16)))
|
||||
}
|
||||
|
||||
// Is the keeper state valid?
|
||||
func (k Keeper) CheckValid(ctx sdk.Context, difficulty uint64, count uint64) (uint64, uint64, sdk.Error) {
|
||||
|
||||
lastDifficulty, err := k.GetLastDifficulty(ctx)
|
||||
if err != nil {
|
||||
return 0, 0, ErrNonexistentDifficulty(k.codespace)
|
||||
}
|
||||
|
||||
newDifficulty := lastDifficulty + 1
|
||||
|
||||
lastCount, err := k.GetLastCount(ctx)
|
||||
if err != nil {
|
||||
return 0, 0, ErrNonexistentCount(k.codespace)
|
||||
}
|
||||
|
||||
newCount := lastCount + 1
|
||||
|
||||
if count != newCount {
|
||||
return 0, 0, ErrInvalidCount(k.codespace, fmt.Sprintf("invalid count: was %d, should have been %d", count, newCount))
|
||||
}
|
||||
if difficulty != newDifficulty {
|
||||
return 0, 0, ErrInvalidDifficulty(k.codespace, fmt.Sprintf("invalid difficulty: was %d, should have been %d", difficulty, newDifficulty))
|
||||
}
|
||||
return newDifficulty, newCount, nil
|
||||
}
|
||||
|
||||
// Add some coins for a POW well done
|
||||
func (k Keeper) ApplyValid(ctx sdk.Context, sender sdk.AccAddress, newDifficulty uint64, newCount uint64) sdk.Error {
|
||||
_, _, ckErr := k.ck.AddCoins(ctx, sender, []sdk.Coin{sdk.NewInt64Coin(k.config.Denomination, k.config.Reward)})
|
||||
if ckErr != nil {
|
||||
return ckErr
|
||||
}
|
||||
k.SetLastDifficulty(ctx, newDifficulty)
|
||||
k.SetLastCount(ctx, newCount)
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
package pow
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
abci "github.com/tendermint/tendermint/abci/types"
|
||||
dbm "github.com/tendermint/tendermint/libs/db"
|
||||
"github.com/tendermint/tendermint/libs/log"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/codec"
|
||||
"github.com/cosmos/cosmos-sdk/store"
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
auth "github.com/cosmos/cosmos-sdk/x/auth"
|
||||
bank "github.com/cosmos/cosmos-sdk/x/bank"
|
||||
)
|
||||
|
||||
// possibly share this kind of setup functionality between module testsuites?
|
||||
func setupMultiStore() (sdk.MultiStore, *sdk.KVStoreKey) {
|
||||
db := dbm.NewMemDB()
|
||||
capKey := sdk.NewKVStoreKey("capkey")
|
||||
ms := store.NewCommitMultiStore(db)
|
||||
ms.MountStoreWithDB(capKey, sdk.StoreTypeIAVL, db)
|
||||
ms.LoadLatestVersion()
|
||||
|
||||
return ms, capKey
|
||||
}
|
||||
|
||||
func TestPowKeeperGetSet(t *testing.T) {
|
||||
ms, capKey := setupMultiStore()
|
||||
cdc := codec.New()
|
||||
auth.RegisterBaseAccount(cdc)
|
||||
|
||||
am := auth.NewAccountKeeper(cdc, capKey, auth.ProtoBaseAccount)
|
||||
ctx := sdk.NewContext(ms, abci.Header{}, false, log.NewNopLogger())
|
||||
config := NewConfig("pow", int64(1))
|
||||
ck := bank.NewBaseKeeper(am)
|
||||
keeper := NewKeeper(capKey, config, ck, DefaultCodespace)
|
||||
|
||||
err := InitGenesis(ctx, keeper, Genesis{uint64(1), uint64(0)})
|
||||
require.Nil(t, err)
|
||||
|
||||
genesis := ExportGenesis(ctx, keeper)
|
||||
require.Nil(t, err)
|
||||
require.Equal(t, genesis, Genesis{uint64(1), uint64(0)})
|
||||
|
||||
res, err := keeper.GetLastDifficulty(ctx)
|
||||
require.Nil(t, err)
|
||||
require.Equal(t, res, uint64(1))
|
||||
|
||||
keeper.SetLastDifficulty(ctx, 2)
|
||||
|
||||
res, err = keeper.GetLastDifficulty(ctx)
|
||||
require.Nil(t, err)
|
||||
require.Equal(t, res, uint64(2))
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
package pow
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"math"
|
||||
"strconv"
|
||||
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
"github.com/tendermint/tendermint/crypto"
|
||||
)
|
||||
|
||||
// generate the mine message
|
||||
func GenerateMsgMine(sender sdk.AccAddress, count uint64, difficulty uint64) MsgMine {
|
||||
nonce, hash := mine(sender, count, difficulty)
|
||||
return NewMsgMine(sender, difficulty, count, nonce, hash)
|
||||
}
|
||||
|
||||
func hash(sender sdk.AccAddress, count uint64, nonce uint64) []byte {
|
||||
var bytes []byte
|
||||
bytes = append(bytes, []byte(sender)...)
|
||||
countBytes := strconv.FormatUint(count, 16)
|
||||
bytes = append(bytes, countBytes...)
|
||||
nonceBytes := strconv.FormatUint(nonce, 16)
|
||||
bytes = append(bytes, nonceBytes...)
|
||||
hash := crypto.Sha256(bytes)
|
||||
// uint64, so we just use the first 8 bytes of the hash
|
||||
// this limits the range of possible difficulty values (as compared to uint256), but fine for proof-of-concept
|
||||
ret := make([]byte, hex.EncodedLen(len(hash)))
|
||||
hex.Encode(ret, hash)
|
||||
return ret[:16]
|
||||
}
|
||||
|
||||
func mine(sender sdk.AccAddress, count uint64, difficulty uint64) (uint64, []byte) {
|
||||
target := math.MaxUint64 / difficulty
|
||||
for nonce := uint64(0); ; nonce++ {
|
||||
hash := hash(sender, count, nonce)
|
||||
hashuint, err := strconv.ParseUint(string(hash), 16, 64)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if hashuint < target {
|
||||
return nonce, hash
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
package pow
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"math"
|
||||
"strconv"
|
||||
|
||||
"github.com/tendermint/tendermint/crypto"
|
||||
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
)
|
||||
|
||||
// MsgMine - mine some coins with PoW
|
||||
type MsgMine struct {
|
||||
Sender sdk.AccAddress `json:"sender"`
|
||||
Difficulty uint64 `json:"difficulty"`
|
||||
Count uint64 `json:"count"`
|
||||
Nonce uint64 `json:"nonce"`
|
||||
Proof []byte `json:"proof"`
|
||||
}
|
||||
|
||||
// enforce the msg type at compile time
|
||||
var _ sdk.Msg = MsgMine{}
|
||||
|
||||
// NewMsgMine - construct mine message
|
||||
func NewMsgMine(sender sdk.AccAddress, difficulty uint64, count uint64, nonce uint64, proof []byte) MsgMine {
|
||||
return MsgMine{sender, difficulty, count, nonce, proof}
|
||||
}
|
||||
|
||||
// nolint
|
||||
func (msg MsgMine) Route() string { return "pow" }
|
||||
func (msg MsgMine) Type() string { return "mine" }
|
||||
func (msg MsgMine) GetSigners() []sdk.AccAddress { return []sdk.AccAddress{msg.Sender} }
|
||||
func (msg MsgMine) String() string {
|
||||
return fmt.Sprintf("MsgMine{Sender: %s, Difficulty: %d, Count: %d, Nonce: %d, Proof: %s}", msg.Sender, msg.Difficulty, msg.Count, msg.Nonce, msg.Proof)
|
||||
}
|
||||
|
||||
// validate the mine message
|
||||
func (msg MsgMine) ValidateBasic() sdk.Error {
|
||||
// check hash
|
||||
var data []byte
|
||||
// hash must include sender, so no other users can race the tx
|
||||
data = append(data, []byte(msg.Sender)...)
|
||||
countBytes := strconv.FormatUint(msg.Count, 16)
|
||||
// hash must include count so proof-of-work solutions cannot be replayed
|
||||
data = append(data, countBytes...)
|
||||
nonceBytes := strconv.FormatUint(msg.Nonce, 16)
|
||||
data = append(data, nonceBytes...)
|
||||
hash := crypto.Sha256(data)
|
||||
hashHex := make([]byte, hex.EncodedLen(len(hash)))
|
||||
hex.Encode(hashHex, hash)
|
||||
hashHex = hashHex[:16]
|
||||
if !bytes.Equal(hashHex, msg.Proof) {
|
||||
return ErrInvalidProof(DefaultCodespace, fmt.Sprintf("hashHex: %s, proof: %s", hashHex, msg.Proof))
|
||||
}
|
||||
|
||||
// check proof below difficulty
|
||||
// difficulty is linear - 1 = all hashes, 2 = half of hashes, 3 = third of hashes, etc
|
||||
target := math.MaxUint64 / msg.Difficulty
|
||||
hashUint, err := strconv.ParseUint(string(msg.Proof), 16, 64)
|
||||
if err != nil {
|
||||
return ErrInvalidProof(DefaultCodespace, fmt.Sprintf("proof: %s", msg.Proof))
|
||||
}
|
||||
if hashUint >= target {
|
||||
return ErrNotBelowTarget(DefaultCodespace, fmt.Sprintf("hashuint: %d, target: %d", hashUint, target))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// get the mine message sign bytes
|
||||
func (msg MsgMine) GetSignBytes() []byte {
|
||||
b, err := json.Marshal(msg)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return sdk.MustSortJSON(b)
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
package pow
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
)
|
||||
|
||||
func TestNewMsgMine(t *testing.T) {
|
||||
addr := sdk.AccAddress([]byte("sender"))
|
||||
msg := MsgMine{addr, 0, 0, 0, []byte("")}
|
||||
equiv := NewMsgMine(addr, 0, 0, 0, []byte(""))
|
||||
require.Equal(t, msg, equiv, "%s != %s", msg, equiv)
|
||||
}
|
||||
|
||||
func TestMsgMineType(t *testing.T) {
|
||||
addr := sdk.AccAddress([]byte("sender"))
|
||||
msg := MsgMine{addr, 0, 0, 0, []byte("")}
|
||||
require.Equal(t, msg.Route(), "pow")
|
||||
}
|
||||
|
||||
func TestMsgMineValidation(t *testing.T) {
|
||||
addr := sdk.AccAddress([]byte("sender"))
|
||||
otherAddr := sdk.AccAddress([]byte("another"))
|
||||
count := uint64(0)
|
||||
|
||||
for difficulty := uint64(1); difficulty < 1000; difficulty += 100 {
|
||||
|
||||
count++
|
||||
nonce, proof := mine(addr, count, difficulty)
|
||||
msg := MsgMine{addr, difficulty, count, nonce, proof}
|
||||
err := msg.ValidateBasic()
|
||||
require.Nil(t, err, "error with difficulty %d - %+v", difficulty, err)
|
||||
|
||||
msg.Count++
|
||||
err = msg.ValidateBasic()
|
||||
require.NotNil(t, err, "count was wrong, should have thrown error with msg %s", msg)
|
||||
|
||||
msg.Count--
|
||||
msg.Nonce++
|
||||
err = msg.ValidateBasic()
|
||||
require.NotNil(t, err, "nonce was wrong, should have thrown error with msg %s", msg)
|
||||
|
||||
msg.Nonce--
|
||||
msg.Sender = otherAddr
|
||||
err = msg.ValidateBasic()
|
||||
require.NotNil(t, err, "sender was wrong, should have thrown error with msg %s", msg)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMsgMineString(t *testing.T) {
|
||||
addr := sdk.AccAddress([]byte("sender"))
|
||||
msg := MsgMine{addr, 0, 0, 0, []byte("abc")}
|
||||
res := msg.String()
|
||||
require.Equal(t, res, "MsgMine{Sender: cosmos1wdjkuer9wgh76ts6, Difficulty: 0, Count: 0, Nonce: 0, Proof: abc}")
|
||||
}
|
||||
|
||||
func TestMsgMineGetSignBytes(t *testing.T) {
|
||||
addr := sdk.AccAddress([]byte("sender"))
|
||||
msg := MsgMine{addr, 1, 1, 1, []byte("abc")}
|
||||
res := msg.GetSignBytes()
|
||||
require.Equal(t, string(res), `{"count":1,"difficulty":1,"nonce":1,"proof":"YWJj","sender":"cosmos1wdjkuer9wgh76ts6"}`)
|
||||
}
|
||||
|
||||
func TestMsgMineGetSigners(t *testing.T) {
|
||||
addr := sdk.AccAddress([]byte("sender"))
|
||||
msg := MsgMine{addr, 1, 1, 1, []byte("abc")}
|
||||
res := msg.GetSigners()
|
||||
require.Equal(t, fmt.Sprintf("%v", res), "[73656E646572]")
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/client/context"
|
||||
"github.com/cosmos/cosmos-sdk/client/utils"
|
||||
"github.com/cosmos/cosmos-sdk/codec"
|
||||
"github.com/cosmos/cosmos-sdk/docs/examples/democoin/x/simplestake"
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
authcmd "github.com/cosmos/cosmos-sdk/x/auth/client/cli"
|
||||
authtxb "github.com/cosmos/cosmos-sdk/x/auth/client/txbuilder"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
|
||||
"github.com/tendermint/tendermint/crypto/ed25519"
|
||||
)
|
||||
|
||||
const (
|
||||
flagStake = "stake"
|
||||
flagValidator = "validator"
|
||||
)
|
||||
|
||||
// simple bond tx
|
||||
func BondTxCmd(cdc *codec.Codec) *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "bond",
|
||||
Short: "Bond to a validator",
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
txBldr := authtxb.NewTxBuilderFromCLI().WithCodec(cdc)
|
||||
cliCtx := context.NewCLIContext().
|
||||
WithCodec(cdc).
|
||||
WithAccountDecoder(authcmd.GetAccountDecoder(cdc))
|
||||
|
||||
from, err := cliCtx.GetFromAddress()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
stakeString := viper.GetString(flagStake)
|
||||
if len(stakeString) == 0 {
|
||||
return fmt.Errorf("specify coins to bond with --stake")
|
||||
}
|
||||
|
||||
valString := viper.GetString(flagValidator)
|
||||
if len(valString) == 0 {
|
||||
return fmt.Errorf("specify pubkey to bond to with --validator")
|
||||
}
|
||||
|
||||
stake, err := sdk.ParseCoin(stakeString)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// TODO: bech32 ...
|
||||
rawPubKey, err := hex.DecodeString(valString)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var pubKeyEd ed25519.PubKeyEd25519
|
||||
copy(pubKeyEd[:], rawPubKey)
|
||||
|
||||
msg := simplestake.NewMsgBond(from, stake, pubKeyEd)
|
||||
|
||||
// Build and sign the transaction, then broadcast to a Tendermint
|
||||
// node.
|
||||
return utils.CompleteAndBroadcastTxCli(txBldr, cliCtx, []sdk.Msg{msg})
|
||||
},
|
||||
}
|
||||
|
||||
cmd.Flags().String(flagStake, "", "Amount of coins to stake")
|
||||
cmd.Flags().String(flagValidator, "", "Validator address to stake")
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
// simple unbond tx
|
||||
func UnbondTxCmd(cdc *codec.Codec) *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "unbond",
|
||||
Short: "Unbond from a validator",
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
txBldr := authtxb.NewTxBuilderFromCLI().WithCodec(cdc)
|
||||
cliCtx := context.NewCLIContext().
|
||||
WithCodec(cdc)
|
||||
|
||||
from, err := cliCtx.GetFromAddress()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
msg := simplestake.NewMsgUnbond(from)
|
||||
|
||||
// Build and sign the transaction, then broadcast to a Tendermint
|
||||
// node.
|
||||
return utils.CompleteAndBroadcastTxCli(txBldr, cliCtx, []sdk.Msg{msg})
|
||||
},
|
||||
}
|
||||
|
||||
return cmd
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package simplestake
|
||||
|
||||
import (
|
||||
"github.com/cosmos/cosmos-sdk/codec"
|
||||
)
|
||||
|
||||
// Register concrete types on codec codec
|
||||
func RegisterCodec(cdc *codec.Codec) {
|
||||
cdc.RegisterConcrete(MsgBond{}, "simplestake/BondMsg", nil)
|
||||
cdc.RegisterConcrete(MsgUnbond{}, "simplestake/UnbondMsg", nil)
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
package simplestake
|
||||
|
||||
import (
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
)
|
||||
|
||||
// simple stake errors reserve 300 ~ 399.
|
||||
const (
|
||||
DefaultCodespace sdk.CodespaceType = 4
|
||||
|
||||
// simplestake errors reserve 300 - 399.
|
||||
CodeEmptyValidator sdk.CodeType = 300
|
||||
CodeInvalidUnbond sdk.CodeType = 301
|
||||
CodeEmptyStake sdk.CodeType = 302
|
||||
CodeIncorrectStakingToken sdk.CodeType = 303
|
||||
)
|
||||
|
||||
// nolint
|
||||
func ErrIncorrectStakingToken(codespace sdk.CodespaceType) sdk.Error {
|
||||
return newError(codespace, CodeIncorrectStakingToken, "")
|
||||
}
|
||||
func ErrEmptyValidator(codespace sdk.CodespaceType) sdk.Error {
|
||||
return newError(codespace, CodeEmptyValidator, "")
|
||||
}
|
||||
func ErrInvalidUnbond(codespace sdk.CodespaceType) sdk.Error {
|
||||
return newError(codespace, CodeInvalidUnbond, "")
|
||||
}
|
||||
func ErrEmptyStake(codespace sdk.CodespaceType) sdk.Error {
|
||||
return newError(codespace, CodeEmptyStake, "")
|
||||
}
|
||||
|
||||
// -----------------------------
|
||||
// Helpers
|
||||
|
||||
// nolint: unparam
|
||||
func newError(codespace sdk.CodespaceType, code sdk.CodeType, msg string) sdk.Error {
|
||||
return sdk.NewError(codespace, code, msg)
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package simplestake
|
||||
|
||||
import (
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
)
|
||||
|
||||
// NewHandler returns a handler for "simplestake" type messages.
|
||||
func NewHandler(k Keeper) sdk.Handler {
|
||||
return func(ctx sdk.Context, msg sdk.Msg) sdk.Result {
|
||||
switch msg.(type) {
|
||||
case MsgBond:
|
||||
return handleMsgBond()
|
||||
case MsgUnbond:
|
||||
return handleMsgUnbond()
|
||||
default:
|
||||
return sdk.ErrUnknownRequest("No match for message type.").Result()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func handleMsgBond() sdk.Result {
|
||||
// Removed ValidatorSet from result because it does not get used.
|
||||
// TODO: Implement correct bond/unbond handling
|
||||
return sdk.Result{
|
||||
Code: sdk.ABCICodeOK,
|
||||
}
|
||||
}
|
||||
|
||||
func handleMsgUnbond() sdk.Result {
|
||||
return sdk.Result{
|
||||
Code: sdk.ABCICodeOK,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,135 @@
|
||||
package simplestake
|
||||
|
||||
import (
|
||||
"github.com/tendermint/tendermint/crypto"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/codec"
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
"github.com/cosmos/cosmos-sdk/x/bank"
|
||||
)
|
||||
|
||||
const stakingToken = "stake"
|
||||
|
||||
const moduleName = "simplestake"
|
||||
|
||||
// simple stake keeper
|
||||
type Keeper struct {
|
||||
ck bank.Keeper
|
||||
|
||||
key sdk.StoreKey
|
||||
cdc *codec.Codec
|
||||
codespace sdk.CodespaceType
|
||||
}
|
||||
|
||||
func NewKeeper(key sdk.StoreKey, bankKeeper bank.Keeper, codespace sdk.CodespaceType) Keeper {
|
||||
cdc := codec.New()
|
||||
codec.RegisterCrypto(cdc)
|
||||
return Keeper{
|
||||
key: key,
|
||||
cdc: cdc,
|
||||
ck: bankKeeper,
|
||||
codespace: codespace,
|
||||
}
|
||||
}
|
||||
|
||||
func (k Keeper) getBondInfo(ctx sdk.Context, addr sdk.AccAddress) bondInfo {
|
||||
store := ctx.KVStore(k.key)
|
||||
bz := store.Get(addr)
|
||||
if bz == nil {
|
||||
return bondInfo{}
|
||||
}
|
||||
var bi bondInfo
|
||||
err := k.cdc.UnmarshalBinaryLengthPrefixed(bz, &bi)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return bi
|
||||
}
|
||||
|
||||
func (k Keeper) setBondInfo(ctx sdk.Context, addr sdk.AccAddress, bi bondInfo) {
|
||||
store := ctx.KVStore(k.key)
|
||||
bz, err := k.cdc.MarshalBinaryLengthPrefixed(bi)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
store.Set(addr, bz)
|
||||
}
|
||||
|
||||
func (k Keeper) deleteBondInfo(ctx sdk.Context, addr sdk.AccAddress) {
|
||||
store := ctx.KVStore(k.key)
|
||||
store.Delete(addr)
|
||||
}
|
||||
|
||||
// register a bond with the keeper
|
||||
func (k Keeper) Bond(ctx sdk.Context, addr sdk.AccAddress, pubKey crypto.PubKey, stake sdk.Coin) (int64, sdk.Error) {
|
||||
if stake.Denom != stakingToken {
|
||||
return 0, ErrIncorrectStakingToken(k.codespace)
|
||||
}
|
||||
|
||||
_, _, err := k.ck.SubtractCoins(ctx, addr, []sdk.Coin{stake})
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
bi := k.getBondInfo(ctx, addr)
|
||||
if bi.isEmpty() {
|
||||
bi = bondInfo{
|
||||
PubKey: pubKey,
|
||||
Power: 0,
|
||||
}
|
||||
}
|
||||
|
||||
bi.Power = bi.Power + stake.Amount.Int64()
|
||||
|
||||
k.setBondInfo(ctx, addr, bi)
|
||||
return bi.Power, nil
|
||||
}
|
||||
|
||||
// register an unbond with the keeper
|
||||
func (k Keeper) Unbond(ctx sdk.Context, addr sdk.AccAddress) (crypto.PubKey, int64, sdk.Error) {
|
||||
bi := k.getBondInfo(ctx, addr)
|
||||
if bi.isEmpty() {
|
||||
return nil, 0, ErrInvalidUnbond(k.codespace)
|
||||
}
|
||||
k.deleteBondInfo(ctx, addr)
|
||||
|
||||
returnedBond := sdk.NewInt64Coin(stakingToken, bi.Power)
|
||||
|
||||
_, _, err := k.ck.AddCoins(ctx, addr, []sdk.Coin{returnedBond})
|
||||
if err != nil {
|
||||
return bi.PubKey, bi.Power, err
|
||||
}
|
||||
|
||||
return bi.PubKey, bi.Power, nil
|
||||
}
|
||||
|
||||
// FOR TESTING PURPOSES -------------------------------------------------
|
||||
|
||||
func (k Keeper) bondWithoutCoins(ctx sdk.Context, addr sdk.AccAddress, pubKey crypto.PubKey, stake sdk.Coin) (int64, sdk.Error) {
|
||||
if stake.Denom != stakingToken {
|
||||
return 0, ErrIncorrectStakingToken(k.codespace)
|
||||
}
|
||||
|
||||
bi := k.getBondInfo(ctx, addr)
|
||||
if bi.isEmpty() {
|
||||
bi = bondInfo{
|
||||
PubKey: pubKey,
|
||||
Power: 0,
|
||||
}
|
||||
}
|
||||
|
||||
bi.Power = bi.Power + stake.Amount.Int64()
|
||||
|
||||
k.setBondInfo(ctx, addr, bi)
|
||||
return bi.Power, nil
|
||||
}
|
||||
|
||||
func (k Keeper) unbondWithoutCoins(ctx sdk.Context, addr sdk.AccAddress) (crypto.PubKey, int64, sdk.Error) {
|
||||
bi := k.getBondInfo(ctx, addr)
|
||||
if bi.isEmpty() {
|
||||
return nil, 0, ErrInvalidUnbond(k.codespace)
|
||||
}
|
||||
k.deleteBondInfo(ctx, addr)
|
||||
|
||||
return bi.PubKey, bi.Power, nil
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
package simplestake
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
abci "github.com/tendermint/tendermint/abci/types"
|
||||
"github.com/tendermint/tendermint/crypto/ed25519"
|
||||
dbm "github.com/tendermint/tendermint/libs/db"
|
||||
"github.com/tendermint/tendermint/libs/log"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/codec"
|
||||
"github.com/cosmos/cosmos-sdk/store"
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
"github.com/cosmos/cosmos-sdk/x/auth"
|
||||
"github.com/cosmos/cosmos-sdk/x/bank"
|
||||
)
|
||||
|
||||
func setupMultiStore() (sdk.MultiStore, *sdk.KVStoreKey, *sdk.KVStoreKey) {
|
||||
db := dbm.NewMemDB()
|
||||
authKey := sdk.NewKVStoreKey("authkey")
|
||||
capKey := sdk.NewKVStoreKey("capkey")
|
||||
ms := store.NewCommitMultiStore(db)
|
||||
ms.MountStoreWithDB(capKey, sdk.StoreTypeIAVL, db)
|
||||
ms.MountStoreWithDB(authKey, sdk.StoreTypeIAVL, db)
|
||||
ms.LoadLatestVersion()
|
||||
return ms, authKey, capKey
|
||||
}
|
||||
|
||||
func TestKeeperGetSet(t *testing.T) {
|
||||
ms, authKey, capKey := setupMultiStore()
|
||||
cdc := codec.New()
|
||||
auth.RegisterBaseAccount(cdc)
|
||||
|
||||
accountKeeper := auth.NewAccountKeeper(cdc, authKey, auth.ProtoBaseAccount)
|
||||
stakeKeeper := NewKeeper(capKey, bank.NewBaseKeeper(accountKeeper), DefaultCodespace)
|
||||
ctx := sdk.NewContext(ms, abci.Header{}, false, log.NewNopLogger())
|
||||
addr := sdk.AccAddress([]byte("some-address"))
|
||||
|
||||
bi := stakeKeeper.getBondInfo(ctx, addr)
|
||||
require.Equal(t, bi, bondInfo{})
|
||||
|
||||
privKey := ed25519.GenPrivKey()
|
||||
|
||||
bi = bondInfo{
|
||||
PubKey: privKey.PubKey(),
|
||||
Power: int64(10),
|
||||
}
|
||||
fmt.Printf("Pubkey: %v\n", privKey.PubKey())
|
||||
stakeKeeper.setBondInfo(ctx, addr, bi)
|
||||
|
||||
savedBi := stakeKeeper.getBondInfo(ctx, addr)
|
||||
require.NotNil(t, savedBi)
|
||||
fmt.Printf("Bond Info: %v\n", savedBi)
|
||||
require.Equal(t, int64(10), savedBi.Power)
|
||||
}
|
||||
|
||||
func TestBonding(t *testing.T) {
|
||||
ms, authKey, capKey := setupMultiStore()
|
||||
cdc := codec.New()
|
||||
auth.RegisterBaseAccount(cdc)
|
||||
|
||||
ctx := sdk.NewContext(ms, abci.Header{}, false, log.NewNopLogger())
|
||||
|
||||
accountKeeper := auth.NewAccountKeeper(cdc, authKey, auth.ProtoBaseAccount)
|
||||
bankKeeper := bank.NewBaseKeeper(accountKeeper)
|
||||
stakeKeeper := NewKeeper(capKey, bankKeeper, DefaultCodespace)
|
||||
addr := sdk.AccAddress([]byte("some-address"))
|
||||
privKey := ed25519.GenPrivKey()
|
||||
pubKey := privKey.PubKey()
|
||||
|
||||
_, _, err := stakeKeeper.unbondWithoutCoins(ctx, addr)
|
||||
require.Equal(t, err, ErrInvalidUnbond(DefaultCodespace))
|
||||
|
||||
_, err = stakeKeeper.bondWithoutCoins(ctx, addr, pubKey, sdk.NewInt64Coin("stake", 10))
|
||||
require.Nil(t, err)
|
||||
|
||||
power, err := stakeKeeper.bondWithoutCoins(ctx, addr, pubKey, sdk.NewInt64Coin("stake", 10))
|
||||
require.Nil(t, err)
|
||||
require.Equal(t, int64(20), power)
|
||||
|
||||
pk, _, err := stakeKeeper.unbondWithoutCoins(ctx, addr)
|
||||
require.Nil(t, err)
|
||||
require.Equal(t, pubKey, pk)
|
||||
|
||||
_, _, err = stakeKeeper.unbondWithoutCoins(ctx, addr)
|
||||
require.Equal(t, err, ErrInvalidUnbond(DefaultCodespace))
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
package simplestake
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
|
||||
"github.com/tendermint/tendermint/crypto"
|
||||
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
)
|
||||
|
||||
//_________________________________________________________----
|
||||
|
||||
// simple bond message
|
||||
type MsgBond struct {
|
||||
Address sdk.AccAddress `json:"address"`
|
||||
Stake sdk.Coin `json:"coins"`
|
||||
PubKey crypto.PubKey `json:"pub_key"`
|
||||
}
|
||||
|
||||
func NewMsgBond(addr sdk.AccAddress, stake sdk.Coin, pubKey crypto.PubKey) MsgBond {
|
||||
return MsgBond{
|
||||
Address: addr,
|
||||
Stake: stake,
|
||||
PubKey: pubKey,
|
||||
}
|
||||
}
|
||||
|
||||
//nolint
|
||||
func (msg MsgBond) Route() string { return moduleName } //TODO update "stake/createvalidator"
|
||||
func (msg MsgBond) Type() string { return "bond" }
|
||||
func (msg MsgBond) GetSigners() []sdk.AccAddress { return []sdk.AccAddress{msg.Address} }
|
||||
|
||||
// basic validation of the bond message
|
||||
func (msg MsgBond) ValidateBasic() sdk.Error {
|
||||
if msg.Stake.IsZero() {
|
||||
return ErrEmptyStake(DefaultCodespace)
|
||||
}
|
||||
|
||||
if msg.PubKey == nil {
|
||||
return sdk.ErrInvalidPubKey("MsgBond.PubKey must not be empty")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// get bond message sign bytes
|
||||
func (msg MsgBond) GetSignBytes() []byte {
|
||||
bz, err := json.Marshal(msg)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return sdk.MustSortJSON(bz)
|
||||
}
|
||||
|
||||
//_______________________________________________________________
|
||||
|
||||
// simple unbond message
|
||||
type MsgUnbond struct {
|
||||
Address sdk.AccAddress `json:"address"`
|
||||
}
|
||||
|
||||
func NewMsgUnbond(addr sdk.AccAddress) MsgUnbond {
|
||||
return MsgUnbond{
|
||||
Address: addr,
|
||||
}
|
||||
}
|
||||
|
||||
//nolint
|
||||
func (msg MsgUnbond) Route() string { return moduleName } //TODO update "stake/createvalidator"
|
||||
func (msg MsgUnbond) Type() string { return "unbond" }
|
||||
func (msg MsgUnbond) GetSigners() []sdk.AccAddress { return []sdk.AccAddress{msg.Address} }
|
||||
func (msg MsgUnbond) ValidateBasic() sdk.Error { return nil }
|
||||
|
||||
// get unbond message sign bytes
|
||||
func (msg MsgUnbond) GetSignBytes() []byte {
|
||||
bz, err := json.Marshal(msg)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return bz
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
package simplestake
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/tendermint/tendermint/crypto/ed25519"
|
||||
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
)
|
||||
|
||||
func TestBondMsgValidation(t *testing.T) {
|
||||
privKey := ed25519.GenPrivKey()
|
||||
cases := []struct {
|
||||
valid bool
|
||||
msgBond MsgBond
|
||||
}{
|
||||
{true, NewMsgBond(sdk.AccAddress{}, sdk.NewInt64Coin("mycoin", 5), privKey.PubKey())},
|
||||
{false, NewMsgBond(sdk.AccAddress{}, sdk.NewInt64Coin("mycoin", 0), privKey.PubKey())},
|
||||
}
|
||||
|
||||
for i, tc := range cases {
|
||||
err := tc.msgBond.ValidateBasic()
|
||||
if tc.valid {
|
||||
require.Nil(t, err, "%d: %+v", i, err)
|
||||
} else {
|
||||
require.NotNil(t, err, "%d", i)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package simplestake
|
||||
|
||||
import "github.com/tendermint/tendermint/crypto"
|
||||
|
||||
type bondInfo struct {
|
||||
PubKey crypto.PubKey
|
||||
Power int64
|
||||
}
|
||||
|
||||
func (bi bondInfo) isEmpty() bool {
|
||||
if bi == (bondInfo{}) {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package sketchy
|
||||
|
||||
import (
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
)
|
||||
|
||||
/*
|
||||
This is just an example to demonstrate a "sketchy" third-party handler module,
|
||||
to demonstrate the "object capability" model for security.
|
||||
|
||||
Since nothing is passed in via arguments to the NewHandler constructor,
|
||||
it cannot affect the handling of other transaction types.
|
||||
*/
|
||||
|
||||
// Handle all "sketchy" type messages.
|
||||
func NewHandler() sdk.Handler {
|
||||
|
||||
return func(ctx sdk.Context, msg sdk.Msg) sdk.Result {
|
||||
// There's nothing accessible from ctx or msg (even using reflection)
|
||||
// that can mutate the state of the application.
|
||||
return sdk.Result{}
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user