diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 9d234c326..caa541510 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -1,8 +1,12 @@ - + * [ ] Updated all relevant documentation in docs * [ ] Updated all code comments where relevant * [ ] Wrote tests * [ ] Updated CHANGELOG.md -* [ ] Updated Basecoin / other examples -* [ ] Squashed related commits and prefixed with PR number per [coding standards](https://github.com/tendermint/coding/blob/master/README.md#merging-a-pr) +* [ ] Updated Gaia/Examples +* [ ] Squashed all commits, uses message "Merge pull request #XYZ: [title]" ([coding standards](https://github.com/tendermint/coding/blob/master/README.md#merging-a-pr)) diff --git a/.gitignore b/.gitignore index 494e72452..da467c151 100644 --- a/.gitignore +++ b/.gitignore @@ -14,6 +14,7 @@ docs/_build # Data - ideally these don't exist examples/basecoin/app/data baseapp/data/* +client/lcd/keys/* # Testing coverage.txt @@ -26,4 +27,4 @@ profile.out vagrant # Graphviz -dependency-graph.png \ No newline at end of file +dependency-graph.png diff --git a/CHANGELOG.md b/CHANGELOG.md index e2c7a09c4..4428f7a4c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,17 +1,31 @@ # Changelog +## 0.19.0 + +*June 13, 2018* + BREAKING CHANGES * msg.GetSignBytes() now returns bech32-encoded addresses in all cases +* [lcd] REST end-points now include gas FEATURES +* [x/auth] Added AccountNumbers to BaseAccount and StdTxs to allow for replay protection with account pruning IMPROVEMENTS * export command now writes current validator set for Tendermint * [tests] Application module tests now use a mock application +* [gaiacli] Fix error message when account isn't found when running gaiacli account +* [lcd] refactored to eliminate use of global variables, and interdependent tests +* [x/stake] More stake tests added to test ByPower index FIXES +* Fixes consensus fault on testnet - see postmortem [here](https://github.com/cosmos/cosmos-sdk/issues/1197#issuecomment-396823021) +* [x/stake] bonded inflation removed, non-bonded inflation partially implemented * [lcd] Switch to bech32 for addresses on all human readable inputs and outputs +* [lcd] fixed tx indexing/querying * [cli] Added `--gas` flag to specify transaction gas limit +* [gaia] Registered slashing message handler +* [x/slashing] Set signInfo.StartHeight correctly for newly bonded validators ## 0.18.0 @@ -81,6 +95,25 @@ BUG FIXES * [gaiacli] Fix error message when account isn't found when running gaiacli account +## 0.17.5 + +*June 5, 2018* + +Update to Tendermint v0.19.9 (Fix evidence reactor, mempool deadlock, WAL panic, +memory leak) + +## 0.17.4 + +*May 31, 2018* + +Update to Tendermint v0.19.7 (WAL fixes and more) + +## 0.17.3 + +*May 29, 2018* + +Update to Tendermint v0.19.6 (fix fast-sync halt) + ## 0.17.5 *June 5, 2018* diff --git a/Gopkg.lock b/Gopkg.lock index 612e8e3b3..07a3c6d5b 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -197,8 +197,8 @@ ".", "mem" ] - revision = "63644898a8da0bc22138abf860edaf5277b6102e" - version = "v1.1.0" + revision = "787d034dfe70e44075ccc060d346146ef53270ad" + version = "v1.1.1" [[projects]] name = "github.com/spf13/cast" @@ -236,8 +236,8 @@ "assert", "require" ] - revision = "12b6f73e6084dad08a7c6e575284b177ecafbc71" - version = "v1.2.1" + revision = "f35b8ab0b5a2cef36673838d662e249dd9c94686" + version = "v1.2.2" [[projects]] branch = "master" @@ -366,7 +366,7 @@ "merkle", "merkle/tmhash" ] - revision = "640af0205d98d1f45fb2f912f9c35c8bf816adc9" + revision = "0c98d10b4ffbd87978d79c160e835b3d3df241ec" [[projects]] branch = "master" @@ -396,13 +396,13 @@ "internal/timeseries", "trace" ] - revision = "1e491301e022f8f977054da4c2d852decd59571f" + revision = "db08ff08e8622530d9ed3a0e8ac279f6d4c02196" [[projects]] branch = "master" name = "golang.org/x/sys" packages = ["unix"] - revision = "9527bec2660bd847c050fda93a0f0c6dee0800bb" + revision = "bff228c7b664c5fce602223a05fb708fd8654986" [[projects]] name = "golang.org/x/text" diff --git a/Makefile b/Makefile index f7e2083c7..c0d18c4a3 100644 --- a/Makefile +++ b/Makefile @@ -46,6 +46,9 @@ install_examples: go install $(BUILD_FLAGS) ./examples/democoin/cmd/democoind go install $(BUILD_FLAGS) ./examples/democoin/cmd/democli +install_debug: + go install $(BUILD_FLAGS) ./cmd/gaia/cmd/gaiadebug + dist: @bash publish/dist.sh @bash publish/publish.sh @@ -92,6 +95,9 @@ test_cli: test_unit: @go test $(PACKAGES_NOCLITEST) +test_race: + @go test -race $(PACKAGES_NOCLITEST) + test_cover: @bash tests/test_cover.sh @@ -154,4 +160,4 @@ remotenet-status: # To avoid unintended conflicts with file names, always add to .PHONY # unless there is a reason not to. # https://www.gnu.org/software/make/manual/html_node/Phony-Targets.html -.PHONY: build build_examples install install_examples dist check_tools get_tools get_vendor_deps draw_deps test test_cli test_unit test_cover test_lint benchmark devdoc_init devdoc devdoc_save devdoc_update remotenet-start remotenet-stop remotenet-status +.PHONY: build build_examples install install_examples install_debug dist check_tools get_tools get_vendor_deps draw_deps test test_cli test_unit test_cover test_lint benchmark devdoc_init devdoc devdoc_save devdoc_update remotenet-start remotenet-stop remotenet-status diff --git a/client/context/helpers.go b/client/context/helpers.go index 880c4adb3..0229827fe 100644 --- a/client/context/helpers.go +++ b/client/context/helpers.go @@ -109,12 +109,15 @@ func (ctx CoreContext) SignAndBuild(name, passphrase string, msg sdk.Msg, cdc *w if chainID == "" { return nil, errors.Errorf("Chain ID required but not specified") } + accnum := ctx.AccountNumber sequence := ctx.Sequence + signMsg := auth.StdSignMsg{ - ChainID: chainID, - Sequences: []int64{sequence}, - Msg: msg, - Fee: auth.NewStdFee(ctx.Gas, sdk.Coin{}), // TODO run simulate to estimate gas? + ChainID: chainID, + AccountNumbers: []int64{accnum}, + Sequences: []int64{sequence}, + Msg: msg, + Fee: auth.NewStdFee(ctx.Gas, sdk.Coin{}), // TODO run simulate to estimate gas? } keybase, err := keys.GetKeyBase() @@ -130,9 +133,10 @@ func (ctx CoreContext) SignAndBuild(name, passphrase string, msg sdk.Msg, cdc *w return nil, err } sigs := []auth.StdSignature{{ - PubKey: pubkey, - Signature: sig, - Sequence: sequence, + PubKey: pubkey, + Signature: sig, + AccountNumber: accnum, + Sequence: sequence, }} // marshal bytes @@ -144,6 +148,10 @@ func (ctx CoreContext) SignAndBuild(name, passphrase string, msg sdk.Msg, cdc *w // sign and build the transaction from the msg func (ctx CoreContext) EnsureSignBuildBroadcast(name string, msg sdk.Msg, cdc *wire.Codec) (res *ctypes.ResultBroadcastTxCommit, err error) { + ctx, err = EnsureAccountNumber(ctx) + if err != nil { + return nil, err + } // default to next sequence number if none provided ctx, err = EnsureSequence(ctx) if err != nil { @@ -163,13 +171,37 @@ func (ctx CoreContext) EnsureSignBuildBroadcast(name string, msg sdk.Msg, cdc *w return ctx.BroadcastTx(txBytes) } +// get the next sequence for the account address +func (ctx CoreContext) GetAccountNumber(address []byte) (int64, error) { + if ctx.Decoder == nil { + return 0, errors.New("AccountDecoder required but not provided") + } + + res, err := ctx.Query(auth.AddressStoreKey(address), ctx.AccountStore) + if err != nil { + return 0, err + } + + if len(res) == 0 { + fmt.Printf("No account found. Returning 0.\n") + return 0, err + } + + account, err := ctx.Decoder(res) + if err != nil { + panic(err) + } + + return account.GetAccountNumber(), nil +} + // get the next sequence for the account address func (ctx CoreContext) NextSequence(address []byte) (int64, error) { if ctx.Decoder == nil { return 0, errors.New("AccountDecoder required but not provided") } - res, err := ctx.Query(address, ctx.AccountStore) + res, err := ctx.Query(auth.AddressStoreKey(address), ctx.AccountStore) if err != nil { return 0, err } diff --git a/client/context/types.go b/client/context/types.go index e9c97ffbc..791ffb23a 100644 --- a/client/context/types.go +++ b/client/context/types.go @@ -14,6 +14,7 @@ type CoreContext struct { TrustNode bool NodeURI string FromAddressName string + AccountNumber int64 Sequence int64 Client rpcclient.Client Decoder auth.AccountDecoder @@ -57,6 +58,12 @@ func (c CoreContext) WithFromAddressName(fromAddressName string) CoreContext { return c } +// WithSequence - return a copy of the context with an account number +func (c CoreContext) WithAccountNumber(accnum int64) CoreContext { + c.AccountNumber = accnum + return c +} + // WithSequence - return a copy of the context with an updated sequence number func (c CoreContext) WithSequence(sequence int64) CoreContext { c.Sequence = sequence diff --git a/client/context/viper.go b/client/context/viper.go index 081c9f5c2..5f262d56f 100644 --- a/client/context/viper.go +++ b/client/context/viper.go @@ -34,6 +34,7 @@ func NewCoreContextFromViper() CoreContext { TrustNode: viper.GetBool(client.FlagTrustNode), FromAddressName: viper.GetString(client.FlagName), NodeURI: nodeURI, + AccountNumber: viper.GetInt64(client.FlagAccountNumber), Sequence: viper.GetInt64(client.FlagSequence), Client: rpc, Decoder: nil, @@ -54,6 +55,25 @@ func defaultChainID() (string, error) { return doc.ChainID, nil } +// EnsureSequence - automatically set sequence number if none provided +func EnsureAccountNumber(ctx CoreContext) (CoreContext, error) { + // Should be viper.IsSet, but this does not work - https://github.com/spf13/viper/pull/331 + if viper.GetInt64(client.FlagAccountNumber) != 0 { + return ctx, nil + } + from, err := ctx.GetFromAddress() + if err != nil { + return ctx, err + } + accnum, err := ctx.GetAccountNumber(from) + if err != nil { + return ctx, err + } + fmt.Printf("Defaulting to account number: %d\n", accnum) + ctx = ctx.WithAccountNumber(accnum) + return ctx, nil +} + // EnsureSequence - automatically set sequence number if none provided func EnsureSequence(ctx CoreContext) (CoreContext, error) { // Should be viper.IsSet, but this does not work - https://github.com/spf13/viper/pull/331 diff --git a/client/flags.go b/client/flags.go index f4ee4279b..4991b9a77 100644 --- a/client/flags.go +++ b/client/flags.go @@ -4,14 +4,15 @@ import "github.com/spf13/cobra" // nolint const ( - FlagChainID = "chain-id" - FlagNode = "node" - FlagHeight = "height" - FlagGas = "gas" - FlagTrustNode = "trust-node" - FlagName = "name" - FlagSequence = "sequence" - FlagFee = "fee" + FlagChainID = "chain-id" + FlagNode = "node" + FlagHeight = "height" + FlagGas = "gas" + FlagTrustNode = "trust-node" + FlagName = "name" + FlagAccountNumber = "account-number" + FlagSequence = "sequence" + FlagFee = "fee" ) // LineBreak can be included in a command list to provide a blank line @@ -26,7 +27,6 @@ func GetCommands(cmds ...*cobra.Command) []*cobra.Command { c.Flags().String(FlagChainID, "", "Chain ID of tendermint node") c.Flags().String(FlagNode, "tcp://localhost:46657", ": to tendermint rpc interface for this chain") c.Flags().Int64(FlagHeight, 0, "block height to query, omit to get most recent provable block") - c.Flags().Int64(FlagGas, 200000, "gas limit to set per-transaction") } return cmds } @@ -35,6 +35,7 @@ func GetCommands(cmds ...*cobra.Command) []*cobra.Command { func PostCommands(cmds ...*cobra.Command) []*cobra.Command { for _, c := range cmds { c.Flags().String(FlagName, "", "Name of private key with which to sign") + c.Flags().Int64(FlagAccountNumber, 0, "AccountNumber number to sign the tx") c.Flags().Int64(FlagSequence, 0, "Sequence number to sign the tx") c.Flags().String(FlagFee, "", "Fee to pay along with transaction") c.Flags().String(FlagChainID, "", "Chain ID of tendermint node") diff --git a/client/keys/add.go b/client/keys/add.go index da368a3a6..7ad9474ce 100644 --- a/client/keys/add.go +++ b/client/keys/add.go @@ -102,12 +102,6 @@ func runAddCmd(cmd *cobra.Command, args []string) error { return nil } -// addOutput lets us json format the data -type addOutput struct { - Key keys.Info `json:"key"` - Seed string `json:"seed"` -} - func printCreate(info keys.Info, seed string) { output := viper.Get(cli.OutputFlag) switch output { @@ -121,7 +115,10 @@ func printCreate(info keys.Info, seed string) { fmt.Println(seed) } case "json": - out := addOutput{Key: info} + out, err := Bech32KeyOutput(info) + if err != nil { + panic(err) + } if !viper.GetBool(flagNoBackup) { out.Seed = seed } diff --git a/client/keys/list.go b/client/keys/list.go index 9af511e5c..22f163f1d 100644 --- a/client/keys/list.go +++ b/client/keys/list.go @@ -4,7 +4,6 @@ import ( "encoding/json" "net/http" - sdk "github.com/cosmos/cosmos-sdk/types" "github.com/spf13/cobra" ) @@ -54,9 +53,11 @@ func QueryKeysRequestHandler(w http.ResponseWriter, r *http.Request) { w.Write([]byte("[]")) return } - keysOutput := make([]KeyOutput, len(infos)) - for i, info := range infos { - keysOutput[i] = KeyOutput{Name: info.Name, Address: sdk.Address(info.PubKey.Address().Bytes())} + keysOutput, err := Bech32KeysOutput(infos) + if err != nil { + w.WriteHeader(500) + w.Write([]byte(err.Error())) + return } output, err := json.MarshalIndent(keysOutput, "", " ") if err != nil { diff --git a/client/keys/show.go b/client/keys/show.go index c7be9cc9d..9051aba16 100644 --- a/client/keys/show.go +++ b/client/keys/show.go @@ -4,7 +4,6 @@ import ( "encoding/json" "net/http" - sdk "github.com/cosmos/cosmos-sdk/types" "github.com/gorilla/mux" keys "github.com/tendermint/go-crypto/keys" @@ -51,7 +50,12 @@ func GetKeyRequestHandler(w http.ResponseWriter, r *http.Request) { return } - keyOutput := KeyOutput{Name: info.Name, Address: sdk.Address(info.PubKey.Address())} + keyOutput, err := Bech32KeyOutput(info) + if err != nil { + w.WriteHeader(500) + w.Write([]byte(err.Error())) + return + } output, err := json.MarshalIndent(keyOutput, "", " ") if err != nil { w.WriteHeader(500) diff --git a/client/keys/utils.go b/client/keys/utils.go index 1c1fd0979..8b6cfcb35 100644 --- a/client/keys/utils.go +++ b/client/keys/utils.go @@ -6,7 +6,6 @@ import ( "github.com/spf13/viper" - crypto "github.com/tendermint/go-crypto" keys "github.com/tendermint/go-crypto/keys" "github.com/tendermint/tmlibs/cli" dbm "github.com/tendermint/tmlibs/db" @@ -22,6 +21,8 @@ const KeyDBName = "keys" // keybase is used to make GetKeyBase a singleton var keybase keys.Keybase +// TODO make keybase take a database not load from the directory + // initialize a keybase based on the configuration func GetKeyBase() (keys.Keybase, error) { rootDir := viper.GetString(cli.HomeFlag) @@ -47,29 +48,47 @@ func SetKeyBase(kb keys.Keybase) { // used for outputting keys.Info over REST type KeyOutput struct { - Name string `json:"name"` - Address sdk.Address `json:"address"` - PubKey crypto.PubKey `json:"pub_key"` + Name string `json:"name"` + Address string `json:"address"` + PubKey string `json:"pub_key"` + Seed string `json:"seed,omitempty"` } -func NewKeyOutput(info keys.Info) KeyOutput { - return KeyOutput{ - Name: info.Name, - Address: sdk.Address(info.PubKey.Address().Bytes()), - PubKey: info.PubKey, - } -} - -func NewKeyOutputs(infos []keys.Info) []KeyOutput { +// create a list of KeyOutput in bech32 format +func Bech32KeysOutput(infos []keys.Info) ([]KeyOutput, error) { kos := make([]KeyOutput, len(infos)) for i, info := range infos { - kos[i] = NewKeyOutput(info) + ko, err := Bech32KeyOutput(info) + if err != nil { + return nil, err + } + kos[i] = ko } - return kos + return kos, nil +} + +// create a KeyOutput in bech32 format +func Bech32KeyOutput(info keys.Info) (KeyOutput, error) { + bechAccount, err := sdk.Bech32ifyAcc(sdk.Address(info.PubKey.Address().Bytes())) + if err != nil { + return KeyOutput{}, err + } + bechPubKey, err := sdk.Bech32ifyAccPub(info.PubKey) + if err != nil { + return KeyOutput{}, err + } + return KeyOutput{ + Name: info.Name, + Address: bechAccount, + PubKey: bechPubKey, + }, nil } func printInfo(info keys.Info) { - ko := NewKeyOutput(info) + ko, err := Bech32KeyOutput(info) + if err != nil { + panic(err) + } switch viper.Get(cli.OutputFlag) { case "text": fmt.Printf("NAME:\tADDRESS:\t\t\t\t\t\tPUBKEY:\n") @@ -84,7 +103,10 @@ func printInfo(info keys.Info) { } func printInfos(infos []keys.Info) { - kos := NewKeyOutputs(infos) + kos, err := Bech32KeysOutput(infos) + if err != nil { + panic(err) + } switch viper.Get(cli.OutputFlag) { case "text": fmt.Printf("NAME:\tADDRESS:\t\t\t\t\t\tPUBKEY:\n") @@ -101,13 +123,5 @@ func printInfos(infos []keys.Info) { } func printKeyOutput(ko KeyOutput) { - bechAccount, err := sdk.Bech32ifyAcc(ko.Address) - if err != nil { - panic(err) - } - bechPubKey, err := sdk.Bech32ifyAccPub(ko.PubKey) - if err != nil { - panic(err) - } - fmt.Printf("%s\t%s\t%s\n", ko.Name, bechAccount, bechPubKey) + fmt.Printf("%s\t%s\t%s\n", ko.Name, ko.Address, ko.PubKey) } diff --git a/client/lcd/helpers.go b/client/lcd/helpers.go deleted file mode 100644 index 367e1a53d..000000000 --- a/client/lcd/helpers.go +++ /dev/null @@ -1,54 +0,0 @@ -package lcd - -// NOTE: COPIED VERBATIM FROM tendermint/tendermint/rpc/test/helpers.go - -import ( - "fmt" - "os" - "path/filepath" - "strings" - - cmn "github.com/tendermint/tmlibs/common" - - cfg "github.com/tendermint/tendermint/config" -) - -var globalConfig *cfg.Config - -// f**ing long, but unique for each test -func makePathname() string { - // get path - p, err := os.Getwd() - if err != nil { - panic(err) - } - // fmt.Println(p) - sep := string(filepath.Separator) - return strings.Replace(p, sep, "_", -1) -} - -func randPort() int { - return int(cmn.RandUint16()/2 + 10000) -} - -func makeAddrs() (string, string, string) { - start := randPort() - return fmt.Sprintf("tcp://0.0.0.0:%d", start), - fmt.Sprintf("tcp://0.0.0.0:%d", start+1), - fmt.Sprintf("tcp://0.0.0.0:%d", start+2) -} - -// GetConfig returns a config for the test cases as a singleton -func GetConfig() *cfg.Config { - if globalConfig == nil { - pathname := makePathname() - globalConfig = cfg.ResetTestRoot(pathname) - - // and we use random ports to run in parallel - tm, rpc, _ := makeAddrs() - globalConfig.P2P.ListenAddress = tm - globalConfig.RPC.ListenAddress = rpc - globalConfig.TxIndex.IndexTags = "app.creator" // see kvstore application - } - return globalConfig -} diff --git a/client/lcd/lcd_test.go b/client/lcd/lcd_test.go index 1b1257f34..ba2937e05 100644 --- a/client/lcd/lcd_test.go +++ b/client/lcd/lcd_test.go @@ -1,142 +1,115 @@ package lcd import ( - "bytes" "encoding/hex" "encoding/json" "fmt" - "io/ioutil" - "net" "net/http" - "os" "regexp" "testing" - "github.com/spf13/viper" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" abci "github.com/tendermint/abci/types" - crypto "github.com/tendermint/go-crypto" cryptoKeys "github.com/tendermint/go-crypto/keys" - tmcfg "github.com/tendermint/tendermint/config" - nm "github.com/tendermint/tendermint/node" p2p "github.com/tendermint/tendermint/p2p" - pvm "github.com/tendermint/tendermint/privval" - "github.com/tendermint/tendermint/proxy" ctypes "github.com/tendermint/tendermint/rpc/core/types" - tmrpc "github.com/tendermint/tendermint/rpc/lib/server" - tmtypes "github.com/tendermint/tendermint/types" - "github.com/tendermint/tmlibs/cli" - dbm "github.com/tendermint/tmlibs/db" - "github.com/tendermint/tmlibs/log" client "github.com/cosmos/cosmos-sdk/client" keys "github.com/cosmos/cosmos-sdk/client/keys" - gapp "github.com/cosmos/cosmos-sdk/cmd/gaia/app" - "github.com/cosmos/cosmos-sdk/server" + rpc "github.com/cosmos/cosmos-sdk/client/rpc" tests "github.com/cosmos/cosmos-sdk/tests" sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/cosmos/cosmos-sdk/wire" "github.com/cosmos/cosmos-sdk/x/auth" "github.com/cosmos/cosmos-sdk/x/stake" -) - -var ( - coinDenom = "steak" - coinAmount = int64(10000000) - - validatorAddr1 = "" - validatorAddr2 = "" - - // XXX bad globals - name = "test" - password = "0123456789" - port string - seed string - sendAddr string + stakerest "github.com/cosmos/cosmos-sdk/x/stake/client/rest" ) func TestKeys(t *testing.T) { - - // empty keys - // XXX: the test comes with a key setup - /* - res, body := request(t, port, "GET", "/keys", nil) - require.Equal(t, http.StatusOK, res.StatusCode, body) - assert.Equal(t, "[]", body, "Expected an empty array") - */ + name, password := "test", "1234567890" + addr, seed := CreateAddr(t, "test", password, GetKB(t)) + cleanup, _, port := InitializeTestLCD(t, 2, []sdk.Address{addr}) + defer cleanup() // get seed - res, body := request(t, port, "GET", "/keys/seed", nil) + res, body := Request(t, port, "GET", "/keys/seed", nil) require.Equal(t, http.StatusOK, res.StatusCode, body) newSeed := body reg, err := regexp.Compile(`([a-z]+ ){12}`) require.Nil(t, err) match := reg.MatchString(seed) - assert.True(t, match, "Returned seed has wrong foramt", seed) + assert.True(t, match, "Returned seed has wrong format", seed) newName := "test_newname" newPassword := "0987654321" // add key var jsonStr = []byte(fmt.Sprintf(`{"name":"test_fail", "password":"%s"}`, password)) - res, body = request(t, port, "POST", "/keys", jsonStr) + res, body = Request(t, port, "POST", "/keys", jsonStr) assert.Equal(t, http.StatusBadRequest, res.StatusCode, "Account creation should require a seed") jsonStr = []byte(fmt.Sprintf(`{"name":"%s", "password":"%s", "seed": "%s"}`, newName, newPassword, newSeed)) - res, body = request(t, port, "POST", "/keys", jsonStr) + res, body = Request(t, port, "POST", "/keys", jsonStr) require.Equal(t, http.StatusOK, res.StatusCode, body) - addr := body - assert.Len(t, addr, 40, "Returned address has wrong format", addr) + addr2 := body + assert.Len(t, addr2, 40, "Returned address has wrong format", addr2) // existing keys - res, body = request(t, port, "GET", "/keys", nil) + res, body = Request(t, port, "GET", "/keys", nil) require.Equal(t, http.StatusOK, res.StatusCode, body) var m [2]keys.KeyOutput err = cdc.UnmarshalJSON([]byte(body), &m) require.Nil(t, err) - sendAddrAcc, _ := sdk.GetAccAddressHex(sendAddr) - addrAcc, _ := sdk.GetAccAddressHex(addr) + addr2Acc, err := sdk.GetAccAddressHex(addr2) + require.Nil(t, err) + addr2Bech32 := sdk.MustBech32ifyAcc(addr2Acc) + addrBech32 := sdk.MustBech32ifyAcc(addr) - assert.Equal(t, m[0].Name, name, "Did not serve keys name correctly") - assert.Equal(t, m[0].Address, sendAddrAcc, "Did not serve keys Address correctly") - assert.Equal(t, m[1].Name, newName, "Did not serve keys name correctly") - assert.Equal(t, m[1].Address, addrAcc, "Did not serve keys Address correctly") + assert.Equal(t, name, m[0].Name, "Did not serve keys name correctly") + assert.Equal(t, addrBech32, m[0].Address, "Did not serve keys Address correctly") + assert.Equal(t, newName, m[1].Name, "Did not serve keys name correctly") + assert.Equal(t, addr2Bech32, m[1].Address, "Did not serve keys Address correctly") // select key keyEndpoint := fmt.Sprintf("/keys/%s", newName) - res, body = request(t, port, "GET", keyEndpoint, nil) + res, body = Request(t, port, "GET", keyEndpoint, nil) require.Equal(t, http.StatusOK, res.StatusCode, body) var m2 keys.KeyOutput err = cdc.UnmarshalJSON([]byte(body), &m2) require.Nil(t, err) assert.Equal(t, newName, m2.Name, "Did not serve keys name correctly") - assert.Equal(t, addrAcc, m2.Address, "Did not serve keys Address correctly") + assert.Equal(t, addr2Bech32, m2.Address, "Did not serve keys Address correctly") // update key - jsonStr = []byte(fmt.Sprintf(`{"old_password":"%s", "new_password":"12345678901"}`, newPassword)) - res, body = request(t, port, "PUT", keyEndpoint, jsonStr) + jsonStr = []byte(fmt.Sprintf(`{ + "old_password":"%s", + "new_password":"12345678901" + }`, newPassword)) + + res, body = Request(t, port, "PUT", keyEndpoint, jsonStr) require.Equal(t, http.StatusOK, res.StatusCode, body) // here it should say unauthorized as we changed the password before - res, body = request(t, port, "PUT", keyEndpoint, jsonStr) + res, body = Request(t, port, "PUT", keyEndpoint, jsonStr) require.Equal(t, http.StatusUnauthorized, res.StatusCode, body) // delete key jsonStr = []byte(`{"password":"12345678901"}`) - res, body = request(t, port, "DELETE", keyEndpoint, jsonStr) + res, body = Request(t, port, "DELETE", keyEndpoint, jsonStr) require.Equal(t, http.StatusOK, res.StatusCode, body) } func TestVersion(t *testing.T) { + cleanup, _, port := InitializeTestLCD(t, 1, []sdk.Address{}) + defer cleanup() // node info - res, body := request(t, port, "GET", "/version", nil) + res, body := Request(t, port, "GET", "/version", nil) require.Equal(t, http.StatusOK, res.StatusCode, body) reg, err := regexp.Compile(`\d+\.\d+\.\d+(-dev)?`) @@ -146,9 +119,11 @@ func TestVersion(t *testing.T) { } func TestNodeStatus(t *testing.T) { + cleanup, _, port := InitializeTestLCD(t, 1, []sdk.Address{}) + defer cleanup() // node info - res, body := request(t, port, "GET", "/node_info", nil) + res, body := Request(t, port, "GET", "/node_info", nil) require.Equal(t, http.StatusOK, res.StatusCode, body) var nodeInfo p2p.NodeInfo @@ -158,21 +133,20 @@ func TestNodeStatus(t *testing.T) { assert.NotEqual(t, p2p.NodeInfo{}, nodeInfo, "res: %v", res) // syncing - res, body = request(t, port, "GET", "/syncing", nil) + res, body = Request(t, port, "GET", "/syncing", nil) require.Equal(t, http.StatusOK, res.StatusCode, body) // we expect that there is no other node running so the syncing state is "false" - // we c assert.Equal(t, "false", body) } func TestBlock(t *testing.T) { - - tests.WaitForHeight(2, port) + cleanup, _, port := InitializeTestLCD(t, 1, []sdk.Address{}) + defer cleanup() var resultBlock ctypes.ResultBlock - res, body := request(t, port, "GET", "/blocks/latest", nil) + res, body := Request(t, port, "GET", "/blocks/latest", nil) require.Equal(t, http.StatusOK, res.StatusCode, body) err := cdc.UnmarshalJSON([]byte(body), &resultBlock) @@ -182,7 +156,7 @@ func TestBlock(t *testing.T) { // -- - res, body = request(t, port, "GET", "/blocks/1", nil) + res, body = Request(t, port, "GET", "/blocks/1", nil) require.Equal(t, http.StatusOK, res.StatusCode, body) err = json.Unmarshal([]byte(body), &resultBlock) @@ -192,50 +166,62 @@ func TestBlock(t *testing.T) { // -- - res, body = request(t, port, "GET", "/blocks/1000000000", nil) + res, body = Request(t, port, "GET", "/blocks/1000000000", nil) require.Equal(t, http.StatusNotFound, res.StatusCode, body) } func TestValidators(t *testing.T) { + cleanup, _, port := InitializeTestLCD(t, 1, []sdk.Address{}) + defer cleanup() - var resultVals ctypes.ResultValidators + var resultVals rpc.ResultValidatorsOutput - res, body := request(t, port, "GET", "/validatorsets/latest", nil) + res, body := Request(t, port, "GET", "/validatorsets/latest", nil) require.Equal(t, http.StatusOK, res.StatusCode, body) err := cdc.UnmarshalJSON([]byte(body), &resultVals) require.Nil(t, err, "Couldn't parse validatorset") - assert.NotEqual(t, ctypes.ResultValidators{}, resultVals) + assert.NotEqual(t, rpc.ResultValidatorsOutput{}, resultVals) + + assert.Contains(t, resultVals.Validators[0].Address, "cosmosvaladdr") + assert.Contains(t, resultVals.Validators[0].PubKey, "cosmosvalpub") // -- - res, body = request(t, port, "GET", "/validatorsets/1", nil) + res, body = Request(t, port, "GET", "/validatorsets/1", nil) require.Equal(t, http.StatusOK, res.StatusCode, body) err = cdc.UnmarshalJSON([]byte(body), &resultVals) require.Nil(t, err, "Couldn't parse validatorset") - assert.NotEqual(t, ctypes.ResultValidators{}, resultVals) + assert.NotEqual(t, rpc.ResultValidatorsOutput{}, resultVals) // -- - res, body = request(t, port, "GET", "/validatorsets/1000000000", nil) + res, body = Request(t, port, "GET", "/validatorsets/1000000000", nil) require.Equal(t, http.StatusNotFound, res.StatusCode) } func TestCoinSend(t *testing.T) { + name, password := "test", "1234567890" + addr, seed := CreateAddr(t, "test", password, GetKB(t)) + cleanup, _, port := InitializeTestLCD(t, 2, []sdk.Address{addr}) + defer cleanup() + + bz, err := hex.DecodeString("8FA6AB57AD6870F6B5B2E57735F38F2F30E73CB6") + require.NoError(t, err) + someFakeAddr := sdk.MustBech32ifyAcc(bz) // query empty - //res, body := request(t, port, "GET", "/accounts/8FA6AB57AD6870F6B5B2E57735F38F2F30E73CB6", nil) - res, body := request(t, port, "GET", "/accounts/8FA6AB57AD6870F6B5B2E57735F38F2F30E73CB6", nil) + res, body := Request(t, port, "GET", "/accounts/"+someFakeAddr, nil) require.Equal(t, http.StatusNoContent, res.StatusCode, body) - acc := getAccount(t, sendAddr) + acc := getAccount(t, port, addr) initialBalance := acc.GetCoins() // create TX - receiveAddr, resultTx := doSend(t, port, seed) + receiveAddr, resultTx := doSend(t, port, seed, name, password, addr) tests.WaitForHeight(resultTx.Height+1, port) // check if tx was commited @@ -243,27 +229,31 @@ func TestCoinSend(t *testing.T) { assert.Equal(t, uint32(0), resultTx.DeliverTx.Code) // query sender - acc = getAccount(t, sendAddr) + acc = getAccount(t, port, addr) coins := acc.GetCoins() mycoins := coins[0] - assert.Equal(t, coinDenom, mycoins.Denom) + assert.Equal(t, "steak", mycoins.Denom) assert.Equal(t, initialBalance[0].Amount-1, mycoins.Amount) // query receiver - acc = getAccount(t, receiveAddr) + acc = getAccount(t, port, receiveAddr) coins = acc.GetCoins() mycoins = coins[0] - assert.Equal(t, coinDenom, mycoins.Denom) + assert.Equal(t, "steak", mycoins.Denom) assert.Equal(t, int64(1), mycoins.Amount) } func TestIBCTransfer(t *testing.T) { + name, password := "test", "1234567890" + addr, seed := CreateAddr(t, "test", password, GetKB(t)) + cleanup, _, port := InitializeTestLCD(t, 2, []sdk.Address{addr}) + defer cleanup() - acc := getAccount(t, sendAddr) + acc := getAccount(t, port, addr) initialBalance := acc.GetCoins() // create TX - resultTx := doIBCTransfer(t, port, seed) + resultTx := doIBCTransfer(t, port, seed, name, password, addr) tests.WaitForHeight(resultTx.Height+1, port) @@ -272,72 +262,108 @@ func TestIBCTransfer(t *testing.T) { assert.Equal(t, uint32(0), resultTx.DeliverTx.Code) // query sender - acc = getAccount(t, sendAddr) + acc = getAccount(t, port, addr) coins := acc.GetCoins() mycoins := coins[0] - assert.Equal(t, coinDenom, mycoins.Denom) + assert.Equal(t, "steak", mycoins.Denom) assert.Equal(t, initialBalance[0].Amount-1, mycoins.Amount) // TODO: query ibc egress packet state } func TestTxs(t *testing.T) { - - // TODO: re-enable once we can get txs by tag + name, password := "test", "1234567890" + addr, seed := CreateAddr(t, "test", password, GetKB(t)) + cleanup, _, port := InitializeTestLCD(t, 2, []sdk.Address{addr}) + defer cleanup() // query wrong - // res, body := request(t, port, "GET", "/txs", nil) - // require.Equal(t, http.StatusBadRequest, res.StatusCode, body) + res, body := Request(t, port, "GET", "/txs", nil) + require.Equal(t, http.StatusBadRequest, res.StatusCode, body) // query empty - // res, body = request(t, port, "GET", fmt.Sprintf("/txs?tag=coin.sender='%s'", "8FA6AB57AD6870F6B5B2E57735F38F2F30E73CB6"), nil) - // require.Equal(t, http.StatusOK, res.StatusCode, body) - - // assert.Equal(t, "[]", body) + res, body = Request(t, port, "GET", fmt.Sprintf("/txs?tag=sender_bech32='%s'", "cosmosaccaddr1jawd35d9aq4u76sr3fjalmcqc8hqygs9gtnmv3"), nil) + require.Equal(t, http.StatusOK, res.StatusCode, body) + assert.Equal(t, "[]", body) // create TX - _, resultTx := doSend(t, port, seed) + receiveAddr, resultTx := doSend(t, port, seed, name, password, addr) tests.WaitForHeight(resultTx.Height+1, port) // check if tx is findable - res, body := request(t, port, "GET", fmt.Sprintf("/txs/%s", resultTx.Hash), nil) + res, body = Request(t, port, "GET", fmt.Sprintf("/txs/%s", resultTx.Hash), nil) require.Equal(t, http.StatusOK, res.StatusCode, body) - // // query sender - // res, body = request(t, port, "GET", fmt.Sprintf("/txs?tag=coin.sender='%s'", addr), nil) - // require.Equal(t, http.StatusOK, res.StatusCode, body) + type txInfo struct { + Height int64 `json:"height"` + Tx sdk.Tx `json:"tx"` + Result abci.ResponseDeliverTx `json:"result"` + } + var indexedTxs []txInfo - // assert.NotEqual(t, "[]", body) + // check if tx is queryable + res, body = Request(t, port, "GET", fmt.Sprintf("/txs?tag=tx.hash='%s'", resultTx.Hash), nil) + require.Equal(t, http.StatusOK, res.StatusCode, body) + assert.NotEqual(t, "[]", body) - // // query receiver - // res, body = request(t, port, "GET", fmt.Sprintf("/txs?tag=coin.receiver='%s'", receiveAddr), nil) - // require.Equal(t, http.StatusOK, res.StatusCode, body) + err := cdc.UnmarshalJSON([]byte(body), &indexedTxs) + require.NoError(t, err) + assert.Equal(t, 1, len(indexedTxs)) - // assert.NotEqual(t, "[]", body) + // query sender + addrBech := sdk.MustBech32ifyAcc(addr) + res, body = Request(t, port, "GET", fmt.Sprintf("/txs?tag=sender_bech32='%s'", addrBech), nil) + require.Equal(t, http.StatusOK, res.StatusCode, body) + + err = cdc.UnmarshalJSON([]byte(body), &indexedTxs) + require.NoError(t, err) + require.Equal(t, 1, len(indexedTxs), "%v", indexedTxs) // there are 2 txs created with doSend + assert.Equal(t, resultTx.Height, indexedTxs[0].Height) + + // query recipient + receiveAddrBech := sdk.MustBech32ifyAcc(receiveAddr) + res, body = Request(t, port, "GET", fmt.Sprintf("/txs?tag=recipient_bech32='%s'", receiveAddrBech), nil) + require.Equal(t, http.StatusOK, res.StatusCode, body) + + err = cdc.UnmarshalJSON([]byte(body), &indexedTxs) + require.NoError(t, err) + require.Equal(t, 1, len(indexedTxs)) + assert.Equal(t, resultTx.Height, indexedTxs[0].Height) } func TestValidatorsQuery(t *testing.T) { - validators := getValidators(t) + cleanup, pks, port := InitializeTestLCD(t, 2, []sdk.Address{}) + require.Equal(t, 2, len(pks)) + defer cleanup() + + validators := getValidators(t, port) assert.Equal(t, len(validators), 2) // make sure all the validators were found (order unknown because sorted by owner addr) foundVal1, foundVal2 := false, false - res1, res2 := hex.EncodeToString(validators[0].Owner), hex.EncodeToString(validators[1].Owner) - if res1 == validatorAddr1 || res2 == validatorAddr1 { + pk1Bech := sdk.MustBech32ifyValPub(pks[0]) + pk2Bech := sdk.MustBech32ifyValPub(pks[1]) + if validators[0].PubKey == pk1Bech || validators[1].PubKey == pk1Bech { foundVal1 = true } - if res1 == validatorAddr2 || res2 == validatorAddr2 { + if validators[0].PubKey == pk2Bech || validators[1].PubKey == pk2Bech { foundVal2 = true } - assert.True(t, foundVal1, "validatorAddr1 %v, res1 %v, res2 %v", validatorAddr1, res1, res2) - assert.True(t, foundVal2, "validatorAddr2 %v, res1 %v, res2 %v", validatorAddr2, res1, res2) + assert.True(t, foundVal1, "pk1Bech %v, owner1 %v, owner2 %v", pk1Bech, validators[0].Owner, validators[1].Owner) + assert.True(t, foundVal2, "pk2Bech %v, owner1 %v, owner2 %v", pk2Bech, validators[0].Owner, validators[1].Owner) } -func TestBond(t *testing.T) { +func TestBonding(t *testing.T) { + name, password, denom := "test", "1234567890", "steak" + addr, seed := CreateAddr(t, "test", password, GetKB(t)) + cleanup, pks, port := InitializeTestLCD(t, 2, []sdk.Address{addr}) + defer cleanup() + + validator1Owner := pks[0].Address() // create bond TX - resultTx := doBond(t, port, seed) + resultTx := doBond(t, port, seed, name, password, addr, validator1Owner) tests.WaitForHeight(resultTx.Height+1, port) // check if tx was commited @@ -345,197 +371,42 @@ func TestBond(t *testing.T) { assert.Equal(t, uint32(0), resultTx.DeliverTx.Code) // query sender - acc := getAccount(t, sendAddr) + acc := getAccount(t, port, addr) coins := acc.GetCoins() - assert.Equal(t, int64(87), coins.AmountOf(coinDenom)) + assert.Equal(t, int64(40), coins.AmountOf(denom)) - // query candidate - bond := getDelegation(t, sendAddr, validatorAddr1) - assert.Equal(t, "10/1", bond.Shares.String()) -} + // query validator + bond := getDelegation(t, port, addr, validator1Owner) + assert.Equal(t, "60/1", bond.Shares.String()) -func TestUnbond(t *testing.T) { + ////////////////////// + // testing unbonding // create unbond TX - resultTx := doUnbond(t, port, seed) + resultTx = doUnbond(t, port, seed, name, password, addr, validator1Owner) tests.WaitForHeight(resultTx.Height+1, port) + // query validator + bond = getDelegation(t, port, addr, validator1Owner) + assert.Equal(t, "30/1", bond.Shares.String()) + // check if tx was commited assert.Equal(t, uint32(0), resultTx.CheckTx.Code) assert.Equal(t, uint32(0), resultTx.DeliverTx.Code) + // TODO fix shares fn in staking // query sender - acc := getAccount(t, sendAddr) - coins := acc.GetCoins() - assert.Equal(t, int64(98), coins.AmountOf(coinDenom)) + //acc := getAccount(t, sendAddr) + //coins := acc.GetCoins() + //assert.Equal(t, int64(98), coins.AmountOf(coinDenom)) - // query candidate - bond := getDelegation(t, sendAddr, validatorAddr1) - assert.Equal(t, "9/1", bond.Shares.String()) } -//__________________________________________________________ -// helpers - -// strt TM and the LCD in process, listening on their respective sockets -func startTMAndLCD() (*nm.Node, net.Listener, error) { - - dir, err := ioutil.TempDir("", "lcd_test") - if err != nil { - return nil, nil, err - } - viper.Set(cli.HomeFlag, dir) - viper.Set(client.FlagGas, 200000) - kb, err := keys.GetKeyBase() // dbm.NewMemDB()) // :( - if err != nil { - return nil, nil, err - } - - config := GetConfig() - config.Consensus.TimeoutCommit = 1000 - config.Consensus.SkipTimeoutCommit = false - - logger := log.NewTMLogger(log.NewSyncWriter(os.Stdout)) - logger = log.NewFilter(logger, log.AllowError()) - privValidatorFile := config.PrivValidatorFile() - privVal := pvm.LoadOrGenFilePV(privValidatorFile) - db := dbm.NewMemDB() - app := gapp.NewGaiaApp(logger, db) - cdc = gapp.MakeCodec() // XXX - - genesisFile := config.GenesisFile() - genDoc, err := tmtypes.GenesisDocFromFile(genesisFile) - if err != nil { - return nil, nil, err - } - - genDoc.Validators = append(genDoc.Validators, - tmtypes.GenesisValidator{ - PubKey: crypto.GenPrivKeyEd25519().PubKey(), - Power: 1, - Name: "val", - }, - ) - - pk1 := genDoc.Validators[0].PubKey - pk2 := genDoc.Validators[1].PubKey - validatorAddr1 = hex.EncodeToString(pk1.Address()) - validatorAddr2 = hex.EncodeToString(pk2.Address()) - - // NOTE it's bad practice to reuse pk address for the owner address but doing in the - // test for simplicity - var appGenTxs [2]json.RawMessage - appGenTxs[0], _, _, err = gapp.GaiaAppGenTxNF(cdc, pk1, pk1.Address(), "test_val1", true) - if err != nil { - return nil, nil, err - } - appGenTxs[1], _, _, err = gapp.GaiaAppGenTxNF(cdc, pk2, pk2.Address(), "test_val2", true) - if err != nil { - return nil, nil, err - } - - genesisState, err := gapp.GaiaAppGenState(cdc, appGenTxs[:]) - if err != nil { - return nil, nil, err - } - - // add the sendAddr to genesis - var info cryptoKeys.Info - info, seed, err = kb.Create(name, password, cryptoKeys.AlgoEd25519) // XXX global seed - if err != nil { - return nil, nil, err - } - sendAddr = info.PubKey.Address().String() // XXX global - accAuth := auth.NewBaseAccountWithAddress(info.PubKey.Address()) - accAuth.Coins = sdk.Coins{{"steak", 100}} - acc := gapp.NewGenesisAccount(&accAuth) - genesisState.Accounts = append(genesisState.Accounts, acc) - - appState, err := wire.MarshalJSONIndent(cdc, genesisState) - if err != nil { - return nil, nil, err - } - genDoc.AppStateJSON = appState - - // LCD listen address - var listenAddr string - listenAddr, port, err = server.FreeTCPAddr() - if err != nil { - return nil, nil, err - } - - // XXX: need to set this so LCD knows the tendermint node address! - viper.Set(client.FlagNode, config.RPC.ListenAddress) - viper.Set(client.FlagChainID, genDoc.ChainID) - - node, err := startTM(config, logger, genDoc, privVal, app) - if err != nil { - return nil, nil, err - } - lcd, err := startLCD(logger, listenAddr, cdc) - if err != nil { - return nil, nil, err - } - - tests.WaitForStart(port) - - return node, lcd, nil -} - -// Create & start in-process tendermint node with memdb -// and in-process abci application. -// TODO: need to clean up the WAL dir or enable it to be not persistent -func startTM(cfg *tmcfg.Config, logger log.Logger, genDoc *tmtypes.GenesisDoc, privVal tmtypes.PrivValidator, app abci.Application) (*nm.Node, error) { - genDocProvider := func() (*tmtypes.GenesisDoc, error) { return genDoc, nil } - dbProvider := func(*nm.DBContext) (dbm.DB, error) { return dbm.NewMemDB(), nil } - n, err := nm.NewNode(cfg, - privVal, - proxy.NewLocalClientCreator(app), - genDocProvider, - dbProvider, - logger.With("module", "node")) - if err != nil { - return nil, err - } - - err = n.Start() - if err != nil { - return nil, err - } - - // wait for rpc - tests.WaitForRPC(GetConfig().RPC.ListenAddress) - - logger.Info("Tendermint running!") - return n, err -} - -// start the LCD. note this blocks! -func startLCD(logger log.Logger, listenAddr string, cdc *wire.Codec) (net.Listener, error) { - handler := createHandler(cdc) - return tmrpc.StartHTTPServer(listenAddr, handler, logger) -} - -func request(t *testing.T, port, method, path string, payload []byte) (*http.Response, string) { - var res *http.Response - var err error - url := fmt.Sprintf("http://localhost:%v%v", port, path) - req, err := http.NewRequest(method, url, bytes.NewBuffer(payload)) - require.Nil(t, err) - res, err = http.DefaultClient.Do(req) - // res, err = http.Post(url, "application/json", bytes.NewBuffer(payload)) - require.Nil(t, err) - - output, err := ioutil.ReadAll(res.Body) - res.Body.Close() - require.Nil(t, err) - - return res, string(output) -} - -func getAccount(t *testing.T, sendAddr string) auth.Account { - // get the account to get the sequence - res, body := request(t, port, "GET", "/accounts/"+sendAddr, nil) +//_____________________________________________________________________________ +// get the account to get the sequence +func getAccount(t *testing.T, port string, addr sdk.Address) auth.Account { + addrBech32 := sdk.MustBech32ifyAcc(addr) + res, body := Request(t, port, "GET", "/accounts/"+addrBech32, nil) require.Equal(t, http.StatusOK, res.StatusCode, body) var acc auth.Account err := cdc.UnmarshalJSON([]byte(body), &acc) @@ -543,20 +414,34 @@ func getAccount(t *testing.T, sendAddr string) auth.Account { return acc } -func doSend(t *testing.T, port, seed string) (receiveAddr string, resultTx ctypes.ResultBroadcastTxCommit) { +func doSend(t *testing.T, port, seed, name, password string, addr sdk.Address) (receiveAddr sdk.Address, resultTx ctypes.ResultBroadcastTxCommit) { // create receive address kb := client.MockKeyBase() receiveInfo, _, err := kb.Create("receive_address", "1234567890", cryptoKeys.CryptoAlgo("ed25519")) require.Nil(t, err) - receiveAddr = receiveInfo.PubKey.Address().String() + receiveAddr = receiveInfo.PubKey.Address() + receiveAddrBech := sdk.MustBech32ifyAcc(receiveAddr) - acc := getAccount(t, sendAddr) + acc := getAccount(t, port, addr) + accnum := acc.GetAccountNumber() sequence := acc.GetSequence() // send - jsonStr := []byte(fmt.Sprintf(`{ "name":"%s", "password":"%s", "sequence":%d, "amount":[{ "denom": "%s", "amount": 1 }] }`, name, password, sequence, coinDenom)) - res, body := request(t, port, "POST", "/accounts/"+receiveAddr+"/send", jsonStr) + jsonStr := []byte(fmt.Sprintf(`{ + "name":"%s", + "password":"%s", + "account_number":%d, + "sequence":%d, + "gas": 10000, + "amount":[ + { + "denom": "%s", + "amount": 1 + } + ] + }`, name, password, accnum, sequence, "steak")) + res, body := Request(t, port, "POST", "/accounts/"+receiveAddrBech+"/send", jsonStr) require.Equal(t, http.StatusOK, res.StatusCode, body) err = cdc.UnmarshalJSON([]byte(body), &resultTx) @@ -565,21 +450,34 @@ func doSend(t *testing.T, port, seed string) (receiveAddr string, resultTx ctype return receiveAddr, resultTx } -func doIBCTransfer(t *testing.T, port, seed string) (resultTx ctypes.ResultBroadcastTxCommit) { - +func doIBCTransfer(t *testing.T, port, seed, name, password string, addr sdk.Address) (resultTx ctypes.ResultBroadcastTxCommit) { // create receive address kb := client.MockKeyBase() receiveInfo, _, err := kb.Create("receive_address", "1234567890", cryptoKeys.CryptoAlgo("ed25519")) require.Nil(t, err) - receiveAddr := receiveInfo.PubKey.Address().String() + receiveAddr := receiveInfo.PubKey.Address() + receiveAddrBech := sdk.MustBech32ifyAcc(receiveAddr) // get the account to get the sequence - acc := getAccount(t, sendAddr) + acc := getAccount(t, port, addr) + accnum := acc.GetAccountNumber() sequence := acc.GetSequence() // send - jsonStr := []byte(fmt.Sprintf(`{ "name":"%s", "password":"%s", "sequence":%d, "amount":[{ "denom": "%s", "amount": 1 }] }`, name, password, sequence, coinDenom)) - res, body := request(t, port, "POST", "/ibc/testchain/"+receiveAddr+"/send", jsonStr) + jsonStr := []byte(fmt.Sprintf(`{ + "name":"%s", + "password": "%s", + "account_number":%d, + "sequence": %d, + "gas": 100000, + "amount":[ + { + "denom": "%s", + "amount": 1 + } + ] + }`, name, password, accnum, sequence, "steak")) + res, body := Request(t, port, "POST", "/ibc/testchain/"+receiveAddrBech+"/send", jsonStr) require.Equal(t, http.StatusOK, res.StatusCode, body) err = cdc.UnmarshalJSON([]byte(body), &resultTx) @@ -588,9 +486,13 @@ func doIBCTransfer(t *testing.T, port, seed string) (resultTx ctypes.ResultBroad return resultTx } -func getDelegation(t *testing.T, delegatorAddr, candidateAddr string) stake.Delegation { +func getDelegation(t *testing.T, port string, delegatorAddr, validatorAddr sdk.Address) stake.Delegation { + + delegatorAddrBech := sdk.MustBech32ifyAcc(delegatorAddr) + validatorAddrBech := sdk.MustBech32ifyVal(validatorAddr) + // get the account to get the sequence - res, body := request(t, port, "GET", "/stake/"+delegatorAddr+"/bonding_status/"+candidateAddr, nil) + res, body := Request(t, port, "GET", "/stake/"+delegatorAddrBech+"/bonding_status/"+validatorAddrBech, nil) require.Equal(t, http.StatusOK, res.StatusCode, body) var bond stake.Delegation err := cdc.UnmarshalJSON([]byte(body), &bond) @@ -598,26 +500,32 @@ func getDelegation(t *testing.T, delegatorAddr, candidateAddr string) stake.Dele return bond } -func doBond(t *testing.T, port, seed string) (resultTx ctypes.ResultBroadcastTxCommit) { +func doBond(t *testing.T, port, seed, name, password string, delegatorAddr, validatorAddr sdk.Address) (resultTx ctypes.ResultBroadcastTxCommit) { // get the account to get the sequence - acc := getAccount(t, sendAddr) + acc := getAccount(t, port, delegatorAddr) + accnum := acc.GetAccountNumber() sequence := acc.GetSequence() + delegatorAddrBech := sdk.MustBech32ifyAcc(delegatorAddr) + validatorAddrBech := sdk.MustBech32ifyVal(validatorAddr) + // send jsonStr := []byte(fmt.Sprintf(`{ "name": "%s", "password": "%s", + "account_number": %d, "sequence": %d, + "gas": 10000, "delegate": [ { - "delegator_addr": "%x", + "delegator_addr": "%s", "validator_addr": "%s", - "bond": { "denom": "%s", "amount": 10 } + "bond": { "denom": "%s", "amount": 60 } } ], "unbond": [] - }`, name, password, sequence, acc.GetAddress(), validatorAddr1, coinDenom)) - res, body := request(t, port, "POST", "/stake/delegations", jsonStr) + }`, name, password, accnum, sequence, delegatorAddrBech, validatorAddrBech, "steak")) + res, body := Request(t, port, "POST", "/stake/delegations", jsonStr) require.Equal(t, http.StatusOK, res.StatusCode, body) var results []ctypes.ResultBroadcastTxCommit @@ -627,26 +535,32 @@ func doBond(t *testing.T, port, seed string) (resultTx ctypes.ResultBroadcastTxC return results[0] } -func doUnbond(t *testing.T, port, seed string) (resultTx ctypes.ResultBroadcastTxCommit) { +func doUnbond(t *testing.T, port, seed, name, password string, delegatorAddr, validatorAddr sdk.Address) (resultTx ctypes.ResultBroadcastTxCommit) { // get the account to get the sequence - acc := getAccount(t, sendAddr) + acc := getAccount(t, port, delegatorAddr) + accnum := acc.GetAccountNumber() sequence := acc.GetSequence() + delegatorAddrBech := sdk.MustBech32ifyAcc(delegatorAddr) + validatorAddrBech := sdk.MustBech32ifyVal(validatorAddr) + // send jsonStr := []byte(fmt.Sprintf(`{ "name": "%s", "password": "%s", + "account_number": %d, "sequence": %d, - "bond": [], + "gas": 10000, + "delegate": [], "unbond": [ { - "delegator_addr": "%x", + "delegator_addr": "%s", "validator_addr": "%s", - "shares": "1" + "shares": "30" } ] - }`, name, password, sequence, acc.GetAddress(), validatorAddr1)) - res, body := request(t, port, "POST", "/stake/delegations", jsonStr) + }`, name, password, accnum, sequence, delegatorAddrBech, validatorAddrBech)) + res, body := Request(t, port, "POST", "/stake/delegations", jsonStr) require.Equal(t, http.StatusOK, res.StatusCode, body) var results []ctypes.ResultBroadcastTxCommit @@ -656,11 +570,11 @@ func doUnbond(t *testing.T, port, seed string) (resultTx ctypes.ResultBroadcastT return results[0] } -func getValidators(t *testing.T) []stake.Validator { +func getValidators(t *testing.T, port string) []stakerest.StakeValidatorOutput { // get the account to get the sequence - res, body := request(t, port, "GET", "/stake/validators", nil) + res, body := Request(t, port, "GET", "/stake/validators", nil) require.Equal(t, http.StatusOK, res.StatusCode, body) - var validators stake.Validators + var validators []stakerest.StakeValidatorOutput err := cdc.UnmarshalJSON([]byte(body), &validators) require.Nil(t, err) return validators diff --git a/client/lcd/main_test.go b/client/lcd/main_test.go deleted file mode 100644 index 9f0e2bd4f..000000000 --- a/client/lcd/main_test.go +++ /dev/null @@ -1,38 +0,0 @@ -package lcd - -import ( - "fmt" - "os" - "testing" - - nm "github.com/tendermint/tendermint/node" -) - -var node *nm.Node - -// See https://golang.org/pkg/testing/#hdr-Main -// for more details -func TestMain(m *testing.M) { - // start a basecoind node and LCD server in the background to test against - - // run all the tests against a single server instance - node, lcd, err := startTMAndLCD() - if err != nil { - fmt.Println(err) - os.Exit(1) - } - - code := m.Run() - - // tear down - // TODO: cleanup - // TODO: it would be great if TM could run without - // persiting anything in the first place - node.Stop() - node.Wait() - - // just a listener ... - lcd.Close() - - os.Exit(code) -} diff --git a/client/lcd/root.go b/client/lcd/root.go index 507134302..c3ec75c96 100644 --- a/client/lcd/root.go +++ b/client/lcd/root.go @@ -25,19 +25,34 @@ import ( stake "github.com/cosmos/cosmos-sdk/x/stake/client/rest" ) -const ( - flagListenAddr = "laddr" - flagCORS = "cors" -) - // ServeCommand will generate a long-running rest server // (aka Light Client Daemon) that exposes functionality similar // to the cli, but over rest func ServeCommand(cdc *wire.Codec) *cobra.Command { + flagListenAddr := "laddr" + flagCORS := "cors" + cmd := &cobra.Command{ Use: "rest-server", Short: "Start LCD (light-client daemon), a local REST server", - RunE: startRESTServerFn(cdc), + RunE: func(cmd *cobra.Command, args []string) error { + listenAddr := viper.GetString(flagListenAddr) + handler := createHandler(cdc) + logger := log.NewTMLogger(log.NewSyncWriter(os.Stdout)). + With("module", "rest-server") + listener, err := tmserver.StartHTTPServer(listenAddr, handler, logger) + if err != nil { + return err + } + logger.Info("REST server started") + + // Wait forever and cleanup + cmn.TrapSignal(func() { + err := listener.Close() + logger.Error("Error closing listener", "err", err) + }) + return nil + }, } cmd.Flags().StringP(flagListenAddr, "a", "tcp://localhost:1317", "Address for server to listen on") cmd.Flags().String(flagCORS, "", "Set to domains that can make CORS requests (* for all)") @@ -46,27 +61,6 @@ func ServeCommand(cdc *wire.Codec) *cobra.Command { return cmd } -func startRESTServerFn(cdc *wire.Codec) func(cmd *cobra.Command, args []string) error { - return func(cmd *cobra.Command, args []string) error { - listenAddr := viper.GetString(flagListenAddr) - handler := createHandler(cdc) - logger := log.NewTMLogger(log.NewSyncWriter(os.Stdout)). - With("module", "rest-server") - listener, err := tmserver.StartHTTPServer(listenAddr, handler, logger) - if err != nil { - return err - } - logger.Info("REST server started") - - // Wait forever and cleanup - cmn.TrapSignal(func() { - err := listener.Close() - logger.Error("Error closing listener", "err", err) - }) - return nil - } -} - func createHandler(cdc *wire.Codec) http.Handler { r := mux.NewRouter() r.HandleFunc("/version", version.RequestHandler).Methods("GET") diff --git a/client/lcd/test_helpers.go b/client/lcd/test_helpers.go new file mode 100644 index 000000000..c1898ff81 --- /dev/null +++ b/client/lcd/test_helpers.go @@ -0,0 +1,234 @@ +package lcd + +import ( + "bytes" + "encoding/json" + "fmt" + "io/ioutil" + "net" + "net/http" + "os" + "path/filepath" + "strings" + "testing" + + "github.com/spf13/viper" + "github.com/stretchr/testify/require" + + abci "github.com/tendermint/abci/types" + crypto "github.com/tendermint/go-crypto" + crkeys "github.com/tendermint/go-crypto/keys" + tmcfg "github.com/tendermint/tendermint/config" + nm "github.com/tendermint/tendermint/node" + pvm "github.com/tendermint/tendermint/privval" + "github.com/tendermint/tendermint/proxy" + tmrpc "github.com/tendermint/tendermint/rpc/lib/server" + tmtypes "github.com/tendermint/tendermint/types" + "github.com/tendermint/tmlibs/cli" + dbm "github.com/tendermint/tmlibs/db" + "github.com/tendermint/tmlibs/log" + + "github.com/cosmos/cosmos-sdk/client" + keys "github.com/cosmos/cosmos-sdk/client/keys" + gapp "github.com/cosmos/cosmos-sdk/cmd/gaia/app" + "github.com/cosmos/cosmos-sdk/server" + "github.com/cosmos/cosmos-sdk/tests" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/wire" + "github.com/cosmos/cosmos-sdk/x/auth" +) + +// f**ing long, but unique for each test +func makePathname() string { + // get path + p, err := os.Getwd() + if err != nil { + panic(err) + } + sep := string(filepath.Separator) + return strings.Replace(p, sep, "_", -1) +} + +// GetConfig returns a config for the test cases as a singleton +func GetConfig() *tmcfg.Config { + pathname := makePathname() + config := tmcfg.ResetTestRoot(pathname) + + tmAddr, _, err := server.FreeTCPAddr() + if err != nil { + panic(err) + } + rcpAddr, _, err := server.FreeTCPAddr() + if err != nil { + panic(err) + } + + config.P2P.ListenAddress = tmAddr + config.RPC.ListenAddress = rcpAddr + return config +} + +// get the lcd test keybase +// note can't use a memdb because the request is expecting to interact with the default location +func GetKB(t *testing.T) crkeys.Keybase { + dir, err := ioutil.TempDir("", "lcd_test") + require.NoError(t, err) + viper.Set(cli.HomeFlag, dir) + keybase, err := keys.GetKeyBase() // dbm.NewMemDB()) // :( + require.NoError(t, err) + return keybase +} + +// add an address to the store return name and password +func CreateAddr(t *testing.T, name, password string, kb crkeys.Keybase) (addr sdk.Address, seed string) { + var info crkeys.Info + var err error + info, seed, err = kb.Create(name, password, crkeys.AlgoEd25519) + require.NoError(t, err) + addr = info.PubKey.Address() + return +} + +// strt TM and the LCD in process, listening on their respective sockets +// nValidators = number of validators +// initAddrs = accounts to initialize with some steaks +func InitializeTestLCD(t *testing.T, nValidators int, initAddrs []sdk.Address) (cleanup func(), validatorsPKs []crypto.PubKey, port string) { + + config := GetConfig() + config.Consensus.TimeoutCommit = 1000 + config.Consensus.SkipTimeoutCommit = false + config.TxIndex.IndexAllTags = true + + logger := log.NewTMLogger(log.NewSyncWriter(os.Stdout)) + logger = log.NewFilter(logger, log.AllowError()) + privValidatorFile := config.PrivValidatorFile() + privVal := pvm.LoadOrGenFilePV(privValidatorFile) + privVal.Reset() + db := dbm.NewMemDB() + app := gapp.NewGaiaApp(logger, db) + cdc = gapp.MakeCodec() // XXX + + genesisFile := config.GenesisFile() + genDoc, err := tmtypes.GenesisDocFromFile(genesisFile) + require.NoError(t, err) + + // add more validators + if nValidators < 1 { + panic("InitializeTestLCD must use at least one validator") + } + for i := 1; i < nValidators; i++ { + genDoc.Validators = append(genDoc.Validators, + tmtypes.GenesisValidator{ + PubKey: crypto.GenPrivKeyEd25519().PubKey(), + Power: 1, + Name: "val", + }, + ) + } + + // NOTE it's bad practice to reuse pk address for the owner address but doing in the + // test for simplicity + var appGenTxs []json.RawMessage + for _, gdValidator := range genDoc.Validators { + pk := gdValidator.PubKey + validatorsPKs = append(validatorsPKs, pk) // append keys for output + appGenTx, _, _, err := gapp.GaiaAppGenTxNF(cdc, pk, pk.Address(), "test_val1", true) + require.NoError(t, err) + appGenTxs = append(appGenTxs, appGenTx) + } + + genesisState, err := gapp.GaiaAppGenState(cdc, appGenTxs[:]) + require.NoError(t, err) + + // add some tokens to init accounts + for _, addr := range initAddrs { + accAuth := auth.NewBaseAccountWithAddress(addr) + accAuth.Coins = sdk.Coins{{"steak", 100}} + acc := gapp.NewGenesisAccount(&accAuth) + genesisState.Accounts = append(genesisState.Accounts, acc) + } + + appState, err := wire.MarshalJSONIndent(cdc, genesisState) + require.NoError(t, err) + genDoc.AppStateJSON = appState + + // LCD listen address + var listenAddr string + listenAddr, port, err = server.FreeTCPAddr() + require.NoError(t, err) + + // XXX: need to set this so LCD knows the tendermint node address! + viper.Set(client.FlagNode, config.RPC.ListenAddress) + viper.Set(client.FlagChainID, genDoc.ChainID) + + node, err := startTM(config, logger, genDoc, privVal, app) + require.NoError(t, err) + lcd, err := startLCD(logger, listenAddr, cdc) + require.NoError(t, err) + + //time.Sleep(time.Second) + //tests.WaitForHeight(2, port) + tests.WaitForStart(port) + tests.WaitForHeight(1, port) + + // for use in defer + cleanup = func() { + node.Stop() + node.Wait() + lcd.Close() + } + + return +} + +// Create & start in-process tendermint node with memdb +// and in-process abci application. +// TODO: need to clean up the WAL dir or enable it to be not persistent +func startTM(tmcfg *tmcfg.Config, logger log.Logger, genDoc *tmtypes.GenesisDoc, privVal tmtypes.PrivValidator, app abci.Application) (*nm.Node, error) { + genDocProvider := func() (*tmtypes.GenesisDoc, error) { return genDoc, nil } + dbProvider := func(*nm.DBContext) (dbm.DB, error) { return dbm.NewMemDB(), nil } + n, err := nm.NewNode(tmcfg, + privVal, + proxy.NewLocalClientCreator(app), + genDocProvider, + dbProvider, + logger.With("module", "node")) + if err != nil { + return nil, err + } + + err = n.Start() + if err != nil { + return nil, err + } + + // wait for rpc + tests.WaitForRPC(tmcfg.RPC.ListenAddress) + + logger.Info("Tendermint running!") + return n, err +} + +// start the LCD. note this blocks! +func startLCD(logger log.Logger, listenAddr string, cdc *wire.Codec) (net.Listener, error) { + handler := createHandler(cdc) + return tmrpc.StartHTTPServer(listenAddr, handler, logger) +} + +// make a test lcd test request +func Request(t *testing.T, port, method, path string, payload []byte) (*http.Response, string) { + var res *http.Response + var err error + url := fmt.Sprintf("http://localhost:%v%v", port, path) + req, err := http.NewRequest(method, url, bytes.NewBuffer(payload)) + require.Nil(t, err) + res, err = http.DefaultClient.Do(req) + // res, err = http.Post(url, "application/json", bytes.NewBuffer(payload)) + require.Nil(t, err) + + output, err := ioutil.ReadAll(res.Body) + res.Body.Close() + require.Nil(t, err) + + return res, string(output) +} diff --git a/client/rpc/validators.go b/client/rpc/validators.go index d1aa6c9c1..f8835d737 100644 --- a/client/rpc/validators.go +++ b/client/rpc/validators.go @@ -10,6 +10,8 @@ import ( "github.com/cosmos/cosmos-sdk/client" "github.com/cosmos/cosmos-sdk/client/context" + sdk "github.com/cosmos/cosmos-sdk/types" + tmtypes "github.com/tendermint/tendermint/types" ) // TODO these next two functions feel kinda hacky based on their placement @@ -28,6 +30,38 @@ func ValidatorCommand() *cobra.Command { return cmd } +// Validator output in bech32 format +type ValidatorOutput struct { + Address string `json:"address"` // in bech32 + PubKey string `json:"pub_key"` // in bech32 + Accum int64 `json:"accum"` + VotingPower int64 `json:"voting_power"` +} + +// Validators at a certain height output in bech32 format +type ResultValidatorsOutput struct { + BlockHeight int64 `json:"block_height"` + Validators []ValidatorOutput `json:"validators"` +} + +func bech32ValidatorOutput(validator *tmtypes.Validator) (ValidatorOutput, error) { + bechAddress, err := sdk.Bech32ifyVal(validator.Address) + if err != nil { + return ValidatorOutput{}, err + } + bechValPubkey, err := sdk.Bech32ifyValPub(validator.PubKey) + if err != nil { + return ValidatorOutput{}, err + } + + return ValidatorOutput{ + Address: bechAddress, + PubKey: bechValPubkey, + Accum: validator.Accum, + VotingPower: validator.VotingPower, + }, nil +} + func getValidators(ctx context.CoreContext, height *int64) ([]byte, error) { // get the node node, err := ctx.GetNode() @@ -35,12 +69,23 @@ func getValidators(ctx context.CoreContext, height *int64) ([]byte, error) { return nil, err } - res, err := node.Validators(height) + validatorsRes, err := node.Validators(height) if err != nil { return nil, err } - output, err := cdc.MarshalJSON(res) + outputValidatorsRes := ResultValidatorsOutput{ + BlockHeight: validatorsRes.BlockHeight, + Validators: make([]ValidatorOutput, len(validatorsRes.Validators)), + } + for i := 0; i < len(validatorsRes.Validators); i++ { + outputValidatorsRes.Validators[i], err = bech32ValidatorOutput(validatorsRes.Validators[i]) + if err != nil { + return nil, err + } + } + + output, err := cdc.MarshalJSON(outputValidatorsRes) if err != nil { return nil, err } @@ -96,6 +141,7 @@ func ValidatorSetRequestHandlerFn(ctx context.CoreContext) http.HandlerFunc { w.Write([]byte(err.Error())) return } + w.Write(output) } } diff --git a/client/tx/root.go b/client/tx/root.go index e8abf3318..5def5a544 100644 --- a/client/tx/root.go +++ b/client/tx/root.go @@ -19,7 +19,7 @@ func AddCommands(cmd *cobra.Command, cdc *wire.Codec) { // register REST routes func RegisterRoutes(ctx context.CoreContext, r *mux.Router, cdc *wire.Codec) { r.HandleFunc("/txs/{hash}", QueryTxRequestHandlerFn(cdc, ctx)).Methods("GET") - // r.HandleFunc("/txs", SearchTxRequestHandler(cdc)).Methods("GET") + r.HandleFunc("/txs", SearchTxRequestHandlerFn(ctx, cdc)).Methods("GET") // r.HandleFunc("/txs/sign", SignTxRequstHandler).Methods("POST") // r.HandleFunc("/txs/broadcast", BroadcastTxRequestHandler).Methods("POST") } diff --git a/client/tx/search.go b/client/tx/search.go index 527661626..3ab3a3df1 100644 --- a/client/tx/search.go +++ b/client/tx/search.go @@ -13,6 +13,7 @@ import ( "github.com/cosmos/cosmos-sdk/client" "github.com/cosmos/cosmos-sdk/client/context" + sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/wire" ) @@ -29,7 +30,11 @@ func SearchTxCmd(cdc *wire.Codec) *cobra.Command { RunE: func(cmd *cobra.Command, args []string) error { tags := viper.GetStringSlice(flagTags) - output, err := searchTx(context.NewCoreContextFromViper(), cdc, tags) + txs, err := searchTxs(context.NewCoreContextFromViper(), cdc, tags) + if err != nil { + return err + } + output, err := cdc.MarshalJSON(txs) if err != nil { return err } @@ -47,13 +52,12 @@ func SearchTxCmd(cdc *wire.Codec) *cobra.Command { return cmd } -func searchTx(ctx context.CoreContext, cdc *wire.Codec, tags []string) ([]byte, error) { +func searchTxs(ctx context.CoreContext, cdc *wire.Codec, tags []string) ([]txInfo, error) { if len(tags) == 0 { return nil, errors.New("Must declare at least one tag to search") } // XXX: implement ANY query := strings.Join(tags, " AND ") - // get the node node, err := ctx.GetNode() if err != nil { @@ -74,11 +78,7 @@ func searchTx(ctx context.CoreContext, cdc *wire.Codec, tags []string) ([]byte, return nil, err } - output, err := cdc.MarshalJSON(info) - if err != nil { - return nil, err - } - return output, nil + return info, nil } func formatTxResults(cdc *wire.Codec, res []*ctypes.ResultTx) ([]txInfo, error) { @@ -102,17 +102,44 @@ func SearchTxRequestHandlerFn(ctx context.CoreContext, cdc *wire.Codec) http.Han tag := r.FormValue("tag") if tag == "" { w.WriteHeader(400) - w.Write([]byte("You need to provide a tag to search for.")) + w.Write([]byte("You need to provide at least a tag as a key=value pair to search for. Postfix the key with _bech32 to search bech32-encoded addresses or public keys")) return } + keyValue := strings.Split(tag, "=") + key := keyValue[0] + value := keyValue[1] + if strings.HasSuffix(key, "_bech32") { + bech32address := strings.Trim(value, "'") + prefix := strings.Split(bech32address, "1")[0] + bz, err := sdk.GetFromBech32(bech32address, prefix) + if err != nil { + w.WriteHeader(400) + w.Write([]byte(err.Error())) + return + } - tags := []string{tag} - output, err := searchTx(ctx, cdc, tags) + tag = strings.TrimRight(key, "_bech32") + "='" + sdk.Address(bz).String() + "'" + } + + txs, err := searchTxs(ctx, cdc, []string{tag}) if err != nil { w.WriteHeader(500) w.Write([]byte(err.Error())) return } + + if len(txs) == 0 { + w.Write([]byte("[]")) + return + } + + output, err := cdc.MarshalJSON(txs) + if err != nil { + w.WriteHeader(500) + w.Write([]byte(err.Error())) + return + } + w.Write(output) } } diff --git a/cmd/gaia/app/app.go b/cmd/gaia/app/app.go index 8f8ed281e..627eee763 100644 --- a/cmd/gaia/app/app.go +++ b/cmd/gaia/app/app.go @@ -5,6 +5,7 @@ import ( "os" abci "github.com/tendermint/abci/types" + tmtypes "github.com/tendermint/tendermint/types" cmn "github.com/tendermint/tmlibs/common" dbm "github.com/tendermint/tmlibs/db" "github.com/tendermint/tmlibs/log" @@ -81,7 +82,8 @@ func NewGaiaApp(logger log.Logger, db dbm.DB) *GaiaApp { app.Router(). AddRoute("bank", bank.NewHandler(app.coinKeeper)). AddRoute("ibc", ibc.NewHandler(app.ibcMapper, app.coinKeeper)). - AddRoute("stake", stake.NewHandler(app.stakeKeeper)) + AddRoute("stake", stake.NewHandler(app.stakeKeeper)). + AddRoute("slashing", slashing.NewHandler(app.slashingKeeper)) // initialize BaseApp app.SetInitChainer(app.initChainer) @@ -143,6 +145,7 @@ func (app *GaiaApp) initChainer(ctx sdk.Context, req abci.RequestInitChain) abci // load the accounts for _, gacc := range genesisState.Accounts { acc := gacc.ToAccount() + acc.AccountNumber = app.accountMapper.GetNextAccountNumber(ctx) app.accountMapper.SetAccount(ctx, acc) } @@ -153,7 +156,7 @@ func (app *GaiaApp) initChainer(ctx sdk.Context, req abci.RequestInitChain) abci } // export the state of gaia for a genesis file -func (app *GaiaApp) ExportAppStateJSON() (appState json.RawMessage, err error) { +func (app *GaiaApp) ExportAppStateAndValidators() (appState json.RawMessage, validators []tmtypes.GenesisValidator, err error) { ctx := app.NewContext(true, abci.Header{}) // iterate to get the accounts @@ -169,5 +172,10 @@ func (app *GaiaApp) ExportAppStateJSON() (appState json.RawMessage, err error) { Accounts: accounts, StakeData: stake.WriteGenesis(ctx, app.stakeKeeper), } - return wire.MarshalJSONIndent(app.cdc, genState) + appState, err = wire.MarshalJSONIndent(app.cdc, genState) + if err != nil { + return nil, nil, err + } + validators = stake.WriteValidators(ctx, app.stakeKeeper) + return appState, validators, nil } diff --git a/cmd/gaia/app/app_test.go b/cmd/gaia/app/app_test.go index b6fffac32..0523c5499 100644 --- a/cmd/gaia/app/app_test.go +++ b/cmd/gaia/app/app_test.go @@ -1,101 +1,13 @@ package app import ( - "fmt" - "os" - "testing" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - - sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/wire" "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/x/stake" abci "github.com/tendermint/abci/types" - crypto "github.com/tendermint/go-crypto" - dbm "github.com/tendermint/tmlibs/db" - "github.com/tendermint/tmlibs/log" ) -// Construct some global addrs and txs for tests. -var ( - chainID = "" // TODO - - accName = "foobart" - - priv1 = crypto.GenPrivKeyEd25519() - addr1 = priv1.PubKey().Address() - priv2 = crypto.GenPrivKeyEd25519() - addr2 = priv2.PubKey().Address() - addr3 = crypto.GenPrivKeyEd25519().PubKey().Address() - priv4 = crypto.GenPrivKeyEd25519() - addr4 = priv4.PubKey().Address() - coins = sdk.Coins{{"foocoin", 10}} - halfCoins = sdk.Coins{{"foocoin", 5}} - manyCoins = sdk.Coins{{"foocoin", 1}, {"barcoin", 1}} - fee = auth.StdFee{ - sdk.Coins{{"foocoin", 0}}, - 100000, - } - - sendMsg1 = bank.MsgSend{ - Inputs: []bank.Input{bank.NewInput(addr1, coins)}, - Outputs: []bank.Output{bank.NewOutput(addr2, coins)}, - } - - sendMsg2 = bank.MsgSend{ - Inputs: []bank.Input{bank.NewInput(addr1, coins)}, - Outputs: []bank.Output{ - bank.NewOutput(addr2, halfCoins), - bank.NewOutput(addr3, halfCoins), - }, - } - - sendMsg3 = bank.MsgSend{ - Inputs: []bank.Input{ - bank.NewInput(addr1, coins), - bank.NewInput(addr4, coins), - }, - Outputs: []bank.Output{ - bank.NewOutput(addr2, coins), - bank.NewOutput(addr3, coins), - }, - } - - sendMsg4 = bank.MsgSend{ - Inputs: []bank.Input{ - bank.NewInput(addr2, coins), - }, - Outputs: []bank.Output{ - bank.NewOutput(addr1, coins), - }, - } - - sendMsg5 = bank.MsgSend{ - Inputs: []bank.Input{ - bank.NewInput(addr1, manyCoins), - }, - Outputs: []bank.Output{ - bank.NewOutput(addr2, manyCoins), - }, - } -) - -func loggerAndDB() (log.Logger, dbm.DB) { - logger := log.NewTMLogger(log.NewSyncWriter(os.Stdout)).With("module", "sdk/app") - db := dbm.NewMemDB() - return logger, db -} - -func newGaiaApp() *GaiaApp { - logger, db := loggerAndDB() - return NewGaiaApp(logger, db) -} - func setGenesis(gapp *GaiaApp, accs ...*auth.BaseAccount) error { genaccs := make([]GenesisAccount, len(accs)) for i, acc := range accs { @@ -119,382 +31,3 @@ func setGenesis(gapp *GaiaApp, accs ...*auth.BaseAccount) error { return nil } - -//_______________________________________________________________________ - -func TestMsgs(t *testing.T) { - gapp := newGaiaApp() - require.Nil(t, setGenesis(gapp)) - - msgs := []struct { - msg sdk.Msg - }{ - {sendMsg1}, - } - - for i, m := range msgs { - // Run a CheckDeliver - SignCheckDeliver(t, gapp, m.msg, []int64{int64(i)}, false, priv1) - } -} - -func TestGenesis(t *testing.T) { - logger, dbs := loggerAndDB() - gapp := NewGaiaApp(logger, dbs) - - // Construct some genesis bytes to reflect GaiaAccount - pk := crypto.GenPrivKeyEd25519().PubKey() - addr := pk.Address() - coins, err := sdk.ParseCoins("77foocoin,99barcoin") - require.Nil(t, err) - baseAcc := &auth.BaseAccount{ - Address: addr, - Coins: coins, - } - - err = setGenesis(gapp, baseAcc) - require.Nil(t, err) - - // A checkTx context - ctx := gapp.BaseApp.NewContext(true, abci.Header{}) - res1 := gapp.accountMapper.GetAccount(ctx, baseAcc.Address) - assert.Equal(t, baseAcc, res1) - - // reload app and ensure the account is still there - gapp = NewGaiaApp(logger, dbs) - ctx = gapp.BaseApp.NewContext(true, abci.Header{}) - res1 = gapp.accountMapper.GetAccount(ctx, baseAcc.Address) - assert.Equal(t, baseAcc, res1) -} - -func TestMsgSendWithAccounts(t *testing.T) { - gapp := newGaiaApp() - - // Construct some genesis bytes to reflect GaiaAccount - // Give 77 foocoin to the first key - coins, err := sdk.ParseCoins("77foocoin") - require.Nil(t, err) - baseAcc := &auth.BaseAccount{ - Address: addr1, - Coins: coins, - } - - // Construct genesis state - err = setGenesis(gapp, baseAcc) - require.Nil(t, err) - - // A checkTx context (true) - ctxCheck := gapp.BaseApp.NewContext(true, abci.Header{}) - res1 := gapp.accountMapper.GetAccount(ctxCheck, addr1) - assert.Equal(t, baseAcc, res1.(*auth.BaseAccount)) - - // Run a CheckDeliver - SignCheckDeliver(t, gapp, sendMsg1, []int64{0}, true, priv1) - - // Check balances - CheckBalance(t, gapp, addr1, "67foocoin") - CheckBalance(t, gapp, addr2, "10foocoin") - - // Delivering again should cause replay error - SignCheckDeliver(t, gapp, sendMsg1, []int64{0}, false, priv1) - - // bumping the txnonce number without resigning should be an auth error - tx := genTx(sendMsg1, []int64{0}, priv1) - tx.Signatures[0].Sequence = 1 - res := gapp.Deliver(tx) - - assert.Equal(t, sdk.ToABCICode(sdk.CodespaceRoot, sdk.CodeUnauthorized), res.Code, res.Log) - - // resigning the tx with the bumped sequence should work - SignCheckDeliver(t, gapp, sendMsg1, []int64{1}, true, priv1) -} - -func TestMsgSendMultipleOut(t *testing.T) { - gapp := newGaiaApp() - - genCoins, err := sdk.ParseCoins("42foocoin") - require.Nil(t, err) - - acc1 := &auth.BaseAccount{ - Address: addr1, - Coins: genCoins, - } - - acc2 := &auth.BaseAccount{ - Address: addr2, - Coins: genCoins, - } - - err = setGenesis(gapp, acc1, acc2) - require.Nil(t, err) - - // Simulate a Block - SignCheckDeliver(t, gapp, sendMsg2, []int64{0}, true, priv1) - - // Check balances - CheckBalance(t, gapp, addr1, "32foocoin") - CheckBalance(t, gapp, addr2, "47foocoin") - CheckBalance(t, gapp, addr3, "5foocoin") -} - -func TestSengMsgMultipleInOut(t *testing.T) { - gapp := newGaiaApp() - - genCoins, err := sdk.ParseCoins("42foocoin") - require.Nil(t, err) - - acc1 := &auth.BaseAccount{ - Address: addr1, - Coins: genCoins, - } - acc2 := &auth.BaseAccount{ - Address: addr2, - Coins: genCoins, - } - acc4 := &auth.BaseAccount{ - Address: addr4, - Coins: genCoins, - } - - err = setGenesis(gapp, acc1, acc2, acc4) - assert.Nil(t, err) - - // CheckDeliver - SignCheckDeliver(t, gapp, sendMsg3, []int64{0, 0}, true, priv1, priv4) - - // Check balances - CheckBalance(t, gapp, addr1, "32foocoin") - CheckBalance(t, gapp, addr4, "32foocoin") - CheckBalance(t, gapp, addr2, "52foocoin") - CheckBalance(t, gapp, addr3, "10foocoin") -} - -func TestMsgSendDependent(t *testing.T) { - gapp := newGaiaApp() - - genCoins, err := sdk.ParseCoins("42foocoin") - require.Nil(t, err) - - acc1 := &auth.BaseAccount{ - Address: addr1, - Coins: genCoins, - } - - err = setGenesis(gapp, acc1) - require.Nil(t, err) - - // CheckDeliver - SignCheckDeliver(t, gapp, sendMsg1, []int64{0}, true, priv1) - - // Check balances - CheckBalance(t, gapp, addr1, "32foocoin") - CheckBalance(t, gapp, addr2, "10foocoin") - - // Simulate a Block - SignCheckDeliver(t, gapp, sendMsg4, []int64{0}, true, priv2) - - // Check balances - CheckBalance(t, gapp, addr1, "42foocoin") -} - -func TestIBCMsgs(t *testing.T) { - gapp := newGaiaApp() - - sourceChain := "source-chain" - destChain := "dest-chain" - - baseAcc := &auth.BaseAccount{ - Address: addr1, - Coins: coins, - } - - err := setGenesis(gapp, baseAcc) - require.Nil(t, err) - - // A checkTx context (true) - ctxCheck := gapp.BaseApp.NewContext(true, abci.Header{}) - res1 := gapp.accountMapper.GetAccount(ctxCheck, addr1) - assert.Equal(t, baseAcc, res1) - - packet := ibc.IBCPacket{ - SrcAddr: addr1, - DestAddr: addr1, - Coins: coins, - SrcChain: sourceChain, - DestChain: destChain, - } - - transferMsg := ibc.IBCTransferMsg{ - IBCPacket: packet, - } - - receiveMsg := ibc.IBCReceiveMsg{ - IBCPacket: packet, - Relayer: addr1, - Sequence: 0, - } - - SignCheckDeliver(t, gapp, transferMsg, []int64{0}, true, priv1) - CheckBalance(t, gapp, addr1, "") - SignCheckDeliver(t, gapp, transferMsg, []int64{1}, false, priv1) - SignCheckDeliver(t, gapp, receiveMsg, []int64{2}, true, priv1) - CheckBalance(t, gapp, addr1, "10foocoin") - SignCheckDeliver(t, gapp, receiveMsg, []int64{3}, false, priv1) -} - -func TestStakeMsgs(t *testing.T) { - gapp := newGaiaApp() - - genCoins, err := sdk.ParseCoins("42steak") - require.Nil(t, err) - bondCoin, err := sdk.ParseCoin("10steak") - require.Nil(t, err) - - acc1 := &auth.BaseAccount{ - Address: addr1, - Coins: genCoins, - } - acc2 := &auth.BaseAccount{ - Address: addr2, - Coins: genCoins, - } - - err = setGenesis(gapp, acc1, acc2) - require.Nil(t, err) - - // A checkTx context (true) - ctxCheck := gapp.BaseApp.NewContext(true, abci.Header{}) - res1 := gapp.accountMapper.GetAccount(ctxCheck, addr1) - res2 := gapp.accountMapper.GetAccount(ctxCheck, addr2) - require.Equal(t, acc1, res1) - require.Equal(t, acc2, res2) - - // Create Validator - - description := stake.NewDescription("foo_moniker", "", "", "") - createValidatorMsg := stake.NewMsgCreateValidator( - addr1, priv1.PubKey(), bondCoin, description, - ) - SignCheckDeliver(t, gapp, createValidatorMsg, []int64{0}, true, priv1) - - ctxDeliver := gapp.BaseApp.NewContext(false, abci.Header{}) - res1 = gapp.accountMapper.GetAccount(ctxDeliver, addr1) - require.Equal(t, genCoins.Minus(sdk.Coins{bondCoin}), res1.GetCoins()) - validator, found := gapp.stakeKeeper.GetValidator(ctxDeliver, addr1) - require.True(t, found) - require.Equal(t, addr1, validator.Owner) - require.Equal(t, sdk.Bonded, validator.Status()) - require.True(sdk.RatEq(t, sdk.NewRat(10), validator.PoolShares.Bonded())) - - // check the bond that should have been created as well - bond, found := gapp.stakeKeeper.GetDelegation(ctxDeliver, addr1, addr1) - require.True(sdk.RatEq(t, sdk.NewRat(10), bond.Shares)) - - // Edit Validator - - description = stake.NewDescription("bar_moniker", "", "", "") - editValidatorMsg := stake.NewMsgEditValidator( - addr1, description, - ) - SignDeliver(t, gapp, editValidatorMsg, []int64{1}, true, priv1) - - validator, found = gapp.stakeKeeper.GetValidator(ctxDeliver, addr1) - require.True(t, found) - require.Equal(t, description, validator.Description) - - // Delegate - - delegateMsg := stake.NewMsgDelegate( - addr2, addr1, bondCoin, - ) - SignDeliver(t, gapp, delegateMsg, []int64{0}, true, priv2) - - res2 = gapp.accountMapper.GetAccount(ctxDeliver, addr2) - require.Equal(t, genCoins.Minus(sdk.Coins{bondCoin}), res2.GetCoins()) - bond, found = gapp.stakeKeeper.GetDelegation(ctxDeliver, addr2, addr1) - require.True(t, found) - require.Equal(t, addr2, bond.DelegatorAddr) - require.Equal(t, addr1, bond.ValidatorAddr) - require.True(sdk.RatEq(t, sdk.NewRat(10), bond.Shares)) - - // Unbond - - unbondMsg := stake.NewMsgUnbond( - addr2, addr1, "MAX", - ) - SignDeliver(t, gapp, unbondMsg, []int64{1}, true, priv2) - - res2 = gapp.accountMapper.GetAccount(ctxDeliver, addr2) - require.Equal(t, genCoins, res2.GetCoins()) - _, found = gapp.stakeKeeper.GetDelegation(ctxDeliver, addr2, addr1) - require.False(t, found) -} - -//____________________________________________________________________________________ - -func CheckBalance(t *testing.T, gapp *GaiaApp, addr sdk.Address, balExpected string) { - ctxDeliver := gapp.BaseApp.NewContext(false, abci.Header{}) - res2 := gapp.accountMapper.GetAccount(ctxDeliver, addr) - assert.Equal(t, balExpected, fmt.Sprintf("%v", res2.GetCoins())) -} - -func genTx(msg sdk.Msg, seq []int64, priv ...crypto.PrivKeyEd25519) auth.StdTx { - sigs := make([]auth.StdSignature, len(priv)) - for i, p := range priv { - sigs[i] = auth.StdSignature{ - PubKey: p.PubKey(), - Signature: p.Sign(auth.StdSignBytes(chainID, seq, fee, msg)), - Sequence: seq[i], - } - } - - return auth.NewStdTx(msg, fee, sigs) - -} - -func SignCheckDeliver(t *testing.T, gapp *GaiaApp, msg sdk.Msg, seq []int64, expPass bool, priv ...crypto.PrivKeyEd25519) { - - // Sign the tx - tx := genTx(msg, seq, priv...) - - // Run a Check - res := gapp.Check(tx) - if expPass { - require.Equal(t, sdk.ABCICodeOK, res.Code, res.Log) - } else { - require.NotEqual(t, sdk.ABCICodeOK, res.Code, res.Log) - } - - // Simulate a Block - gapp.BeginBlock(abci.RequestBeginBlock{}) - res = gapp.Deliver(tx) - if expPass { - require.Equal(t, sdk.ABCICodeOK, res.Code, res.Log) - } else { - require.NotEqual(t, sdk.ABCICodeOK, res.Code, res.Log) - } - gapp.EndBlock(abci.RequestEndBlock{}) - - // XXX fix code or add explaination as to why using commit breaks a bunch of these tests - //gapp.Commit() -} - -// XXX the only reason we are using Sign Deliver here is because the tests -// break on check tx the second time you use SignCheckDeliver in a test because -// the checktx state has not been updated likely because commit is not being -// called! -func SignDeliver(t *testing.T, gapp *GaiaApp, msg sdk.Msg, seq []int64, expPass bool, priv ...crypto.PrivKeyEd25519) { - - // Sign the tx - tx := genTx(msg, seq, priv...) - - // Simulate a Block - gapp.BeginBlock(abci.RequestBeginBlock{}) - res := gapp.Deliver(tx) - if expPass { - require.Equal(t, sdk.ABCICodeOK, res.Code, res.Log) - } else { - require.NotEqual(t, sdk.ABCICodeOK, res.Code, res.Log) - } - gapp.EndBlock(abci.RequestEndBlock{}) -} diff --git a/cmd/gaia/app/genesis.go b/cmd/gaia/app/genesis.go index 813796c0d..558bca38a 100644 --- a/cmd/gaia/app/genesis.go +++ b/cmd/gaia/app/genesis.go @@ -108,7 +108,8 @@ func GaiaAppGenTx(cdc *wire.Codec, pk crypto.PubKey) ( return } cliPrint = json.RawMessage(bz) - return GaiaAppGenTxNF(cdc, pk, addr, name, overwrite) + appGenTx,_,validator,err = GaiaAppGenTxNF(cdc, pk, addr, name, overwrite) + return } // Generate a gaia genesis transaction without flags diff --git a/cmd/gaia/cli_test/cli_test.go b/cmd/gaia/cli_test/cli_test.go index ae46a623c..1868452a6 100644 --- a/cmd/gaia/cli_test/cli_test.go +++ b/cmd/gaia/cli_test/cli_test.go @@ -170,7 +170,13 @@ func executeGetAddrPK(t *testing.T, cmdStr string) (sdk.Address, crypto.PubKey) var ko keys.KeyOutput keys.UnmarshalJSON([]byte(out), &ko) - return ko.Address, ko.PubKey + address, err := sdk.GetAccAddressBech32(ko.Address) + require.NoError(t, err) + + pk, err := sdk.GetAccPubKeyBech32(ko.PubKey) + require.NoError(t, err) + + return address, pk } func executeGetAccount(t *testing.T, cmdStr string) auth.BaseAccount { diff --git a/cmd/gaia/cmd/gaiad/main.go b/cmd/gaia/cmd/gaiad/main.go index 30cb276ee..5d0eb9030 100644 --- a/cmd/gaia/cmd/gaiad/main.go +++ b/cmd/gaia/cmd/gaiad/main.go @@ -6,6 +6,7 @@ import ( "github.com/spf13/cobra" abci "github.com/tendermint/abci/types" + tmtypes "github.com/tendermint/tendermint/types" "github.com/tendermint/tmlibs/cli" dbm "github.com/tendermint/tmlibs/db" "github.com/tendermint/tmlibs/log" @@ -26,7 +27,7 @@ func main() { server.AddCommands(ctx, cdc, rootCmd, app.GaiaAppInit(), server.ConstructAppCreator(newApp, "gaia"), - server.ConstructAppExporter(exportAppState, "gaia")) + server.ConstructAppExporter(exportAppStateAndTMValidators, "gaia")) // prepare and add flags executor := cli.PrepareBaseCmd(rootCmd, "GA", app.DefaultNodeHome) @@ -37,7 +38,7 @@ func newApp(logger log.Logger, db dbm.DB) abci.Application { return app.NewGaiaApp(logger, db) } -func exportAppState(logger log.Logger, db dbm.DB) (json.RawMessage, error) { +func exportAppStateAndTMValidators(logger log.Logger, db dbm.DB) (json.RawMessage, []tmtypes.GenesisValidator, error) { gapp := app.NewGaiaApp(logger, db) - return gapp.ExportAppStateJSON() + return gapp.ExportAppStateAndValidators() } diff --git a/cmd/gaia/cmd/gaiadebug/hack.go b/cmd/gaia/cmd/gaiadebug/hack.go new file mode 100644 index 000000000..2c84184bf --- /dev/null +++ b/cmd/gaia/cmd/gaiadebug/hack.go @@ -0,0 +1,243 @@ +package main + +import ( + "encoding/base64" + "encoding/hex" + "fmt" + "os" + "path" + + "github.com/spf13/cobra" + abci "github.com/tendermint/abci/types" + crypto "github.com/tendermint/go-crypto" + cmn "github.com/tendermint/tmlibs/common" + dbm "github.com/tendermint/tmlibs/db" + "github.com/tendermint/tmlibs/log" + + bam "github.com/cosmos/cosmos-sdk/baseapp" + sdk "github.com/cosmos/cosmos-sdk/types" + + "github.com/cosmos/cosmos-sdk/wire" + "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/x/slashing" + "github.com/cosmos/cosmos-sdk/x/stake" + + gaia "github.com/cosmos/cosmos-sdk/cmd/gaia/app" +) + +func runHackCmd(cmd *cobra.Command, args []string) error { + + if len(args) != 1 { + return fmt.Errorf("Expected 1 arg") + } + + // ".gaiad" + dataDir := args[0] + dataDir = path.Join(dataDir, "data") + + // load the app + logger := log.NewTMLogger(log.NewSyncWriter(os.Stdout)) + db, err := dbm.NewGoLevelDB("gaia", dataDir) + if err != nil { + fmt.Println(err) + os.Exit(1) + } + app := NewGaiaApp(logger, db) + + // print some info + id := app.LastCommitID() + lastBlockHeight := app.LastBlockHeight() + fmt.Println("ID", id) + fmt.Println("LastBlockHeight", lastBlockHeight) + + //---------------------------------------------------- + // XXX: start hacking! + //---------------------------------------------------- + // eg. gaia-6001 testnet bug + // We paniced when iterating through the "bypower" keys. + // The following powerKey was there, but the corresponding "trouble" validator did not exist. + // So here we do a binary search on the past states to find when the powerKey first showed up ... + + // owner of the validator the bonds, gets revoked, later unbonds, and then later is still found in the bypower store + trouble := hexToBytes("D3DC0FF59F7C3B548B7AFA365561B87FD0208AF8") + // this is his "bypower" key + powerKey := hexToBytes("05303030303030303030303033FFFFFFFFFFFF4C0C0000FFFED3DC0FF59F7C3B548B7AFA365561B87FD0208AF8") + + topHeight := lastBlockHeight + bottomHeight := int64(0) + checkHeight := topHeight + for { + // load the given version of the state + err = app.LoadVersion(checkHeight, app.keyMain) + if err != nil { + fmt.Println(err) + os.Exit(1) + } + ctx := app.NewContext(true, abci.Header{}) + + // check for the powerkey and the validator from the store + store := ctx.KVStore(app.keyStake) + res := store.Get(powerKey) + val, _ := app.stakeKeeper.GetValidator(ctx, trouble) + fmt.Println("checking height", checkHeight, res, val) + if res == nil { + bottomHeight = checkHeight + } else { + topHeight = checkHeight + } + checkHeight = (topHeight + bottomHeight) / 2 + } +} + +func base64ToPub(b64 string) crypto.PubKeyEd25519 { + data, _ := base64.StdEncoding.DecodeString(b64) + var pubKey crypto.PubKeyEd25519 + copy(pubKey[:], data) + return pubKey + +} + +func hexToBytes(h string) []byte { + trouble, _ := hex.DecodeString(h) + return trouble + +} + +//-------------------------------------------------------------------------------- +// NOTE: This is all copied from gaia/app/app.go +// so we can access internal fields! + +const ( + appName = "GaiaApp" +) + +// default home directories for expected binaries +var ( + DefaultCLIHome = os.ExpandEnv("$HOME/.gaiacli") + DefaultNodeHome = os.ExpandEnv("$HOME/.gaiad") +) + +// Extended ABCI application +type GaiaApp struct { + *bam.BaseApp + cdc *wire.Codec + + // keys to access the substores + keyMain *sdk.KVStoreKey + keyAccount *sdk.KVStoreKey + keyIBC *sdk.KVStoreKey + keyStake *sdk.KVStoreKey + keySlashing *sdk.KVStoreKey + + // Manage getting and setting accounts + accountMapper auth.AccountMapper + feeCollectionKeeper auth.FeeCollectionKeeper + coinKeeper bank.Keeper + ibcMapper ibc.Mapper + stakeKeeper stake.Keeper + slashingKeeper slashing.Keeper +} + +func NewGaiaApp(logger log.Logger, db dbm.DB) *GaiaApp { + cdc := MakeCodec() + + // create your application object + var app = &GaiaApp{ + BaseApp: bam.NewBaseApp(appName, cdc, logger, db), + cdc: cdc, + keyMain: sdk.NewKVStoreKey("main"), + keyAccount: sdk.NewKVStoreKey("acc"), + keyIBC: sdk.NewKVStoreKey("ibc"), + keyStake: sdk.NewKVStoreKey("stake"), + keySlashing: sdk.NewKVStoreKey("slashing"), + } + + // define the accountMapper + app.accountMapper = auth.NewAccountMapper( + app.cdc, + app.keyAccount, // target store + &auth.BaseAccount{}, // prototype + ) + + // add handlers + app.coinKeeper = bank.NewKeeper(app.accountMapper) + app.ibcMapper = ibc.NewMapper(app.cdc, app.keyIBC, app.RegisterCodespace(ibc.DefaultCodespace)) + app.stakeKeeper = stake.NewKeeper(app.cdc, app.keyStake, app.coinKeeper, app.RegisterCodespace(stake.DefaultCodespace)) + app.slashingKeeper = slashing.NewKeeper(app.cdc, app.keySlashing, app.stakeKeeper, app.RegisterCodespace(slashing.DefaultCodespace)) + + // register message routes + app.Router(). + AddRoute("bank", bank.NewHandler(app.coinKeeper)). + AddRoute("ibc", ibc.NewHandler(app.ibcMapper, app.coinKeeper)). + AddRoute("stake", stake.NewHandler(app.stakeKeeper)) + + // initialize BaseApp + app.SetInitChainer(app.initChainer) + app.SetBeginBlocker(app.BeginBlocker) + app.SetEndBlocker(app.EndBlocker) + app.SetAnteHandler(auth.NewAnteHandler(app.accountMapper, app.feeCollectionKeeper)) + app.MountStoresIAVL(app.keyMain, app.keyAccount, app.keyIBC, app.keyStake, app.keySlashing) + err := app.LoadLatestVersion(app.keyMain) + if err != nil { + cmn.Exit(err.Error()) + } + + return app +} + +// custom tx codec +func MakeCodec() *wire.Codec { + var cdc = wire.NewCodec() + ibc.RegisterWire(cdc) + bank.RegisterWire(cdc) + stake.RegisterWire(cdc) + slashing.RegisterWire(cdc) + auth.RegisterWire(cdc) + sdk.RegisterWire(cdc) + wire.RegisterCrypto(cdc) + return cdc +} + +// application updates every end block +func (app *GaiaApp) BeginBlocker(ctx sdk.Context, req abci.RequestBeginBlock) abci.ResponseBeginBlock { + tags := slashing.BeginBlocker(ctx, req, app.slashingKeeper) + + return abci.ResponseBeginBlock{ + Tags: tags.ToKVPairs(), + } +} + +// application updates every end block +func (app *GaiaApp) EndBlocker(ctx sdk.Context, req abci.RequestEndBlock) abci.ResponseEndBlock { + validatorUpdates := stake.EndBlocker(ctx, app.stakeKeeper) + + return abci.ResponseEndBlock{ + ValidatorUpdates: validatorUpdates, + } +} + +// custom logic for gaia initialization +func (app *GaiaApp) initChainer(ctx sdk.Context, req abci.RequestInitChain) abci.ResponseInitChain { + stateJSON := req.AppStateBytes + // TODO is this now the whole genesis file? + + var genesisState gaia.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, "") + } + + // load the accounts + for _, gacc := range genesisState.Accounts { + acc := gacc.ToAccount() + app.accountMapper.SetAccount(ctx, acc) + } + + // load the initial stake information + stake.InitGenesis(ctx, app.stakeKeeper, genesisState.StakeData) + return abci.ResponseInitChain{} + +} diff --git a/cmd/gaia/cmd/gaiadebug/main.go b/cmd/gaia/cmd/gaiadebug/main.go new file mode 100644 index 000000000..79045c07b --- /dev/null +++ b/cmd/gaia/cmd/gaiadebug/main.go @@ -0,0 +1,159 @@ +package main + +import ( + "bytes" + "encoding/base64" + "encoding/hex" + "encoding/json" + "fmt" + "os" + "strconv" + "strings" + + gaia "github.com/cosmos/cosmos-sdk/cmd/gaia/app" + "github.com/cosmos/cosmos-sdk/x/auth" + "github.com/spf13/cobra" + crypto "github.com/tendermint/go-crypto" +) + +func init() { + rootCmd.AddCommand(txCmd) + rootCmd.AddCommand(pubkeyCmd) + rootCmd.AddCommand(hackCmd) + rootCmd.AddCommand(rawBytesCmd) +} + +var rootCmd = &cobra.Command{ + Use: "gaiadebug", + Short: "Gaia debug tool", + SilenceUsage: true, +} + +var txCmd = &cobra.Command{ + Use: "tx", + Short: "Decode a gaia tx from hex or base64", + RunE: runTxCmd, +} + +var pubkeyCmd = &cobra.Command{ + Use: "pubkey", + Short: "Decode a pubkey from hex or base64", + RunE: runPubKeyCmd, +} + +var hackCmd = &cobra.Command{ + Use: "hack", + Short: "Boilerplate to Hack on an existing state by scripting some Go...", + RunE: runHackCmd, +} + +var rawBytesCmd = &cobra.Command{ + Use: "raw-bytes", + Short: "Convert raw bytes output (eg. [10 21 13 255]) to hex", + RunE: runRawBytesCmd, +} + +func runRawBytesCmd(cmd *cobra.Command, args []string) error { + if len(args) != 1 { + return fmt.Errorf("Expected single arg") + } + stringBytes := args[0] + stringBytes = strings.Trim(stringBytes, "[") + stringBytes = strings.Trim(stringBytes, "]") + spl := strings.Split(stringBytes, " ") + + byteArray := []byte{} + for _, s := range spl { + b, err := strconv.Atoi(s) + if err != nil { + return err + } + byteArray = append(byteArray, byte(b)) + } + fmt.Printf("%X\n", byteArray) + return nil +} + +func runPubKeyCmd(cmd *cobra.Command, args []string) error { + if len(args) != 1 { + return fmt.Errorf("Expected single arg") + } + + pubkeyString := args[0] + + // try hex, then base64 + pubkeyBytes, err := hex.DecodeString(pubkeyString) + if err != nil { + var err2 error + pubkeyBytes, err2 = base64.StdEncoding.DecodeString(pubkeyString) + if err2 != nil { + return fmt.Errorf(`Expected hex or base64. Got errors: + hex: %v, + base64: %v + `, err, err2) + } + } + + cdc := gaia.MakeCodec() + var pubKey crypto.PubKeyEd25519 + copy(pubKey[:], pubkeyBytes) + pubKeyJSONBytes, err := cdc.MarshalJSON(pubKey) + if err != nil { + return err + } + fmt.Println("Address:", pubKey.Address()) + fmt.Printf("Hex: %X\n", pubkeyBytes) + fmt.Println("JSON (base64):", string(pubKeyJSONBytes)) + return nil +} + +func runTxCmd(cmd *cobra.Command, args []string) error { + if len(args) != 1 { + return fmt.Errorf("Expected single arg") + } + + txString := args[0] + + // try hex, then base64 + txBytes, err := hex.DecodeString(txString) + if err != nil { + var err2 error + txBytes, err2 = base64.StdEncoding.DecodeString(txString) + if err2 != nil { + return fmt.Errorf(`Expected hex or base64. Got errors: + hex: %v, + base64: %v + `, err, err2) + } + } + + var tx = auth.StdTx{} + cdc := gaia.MakeCodec() + + err = cdc.UnmarshalBinary(txBytes, &tx) + if err != nil { + return err + } + + bz, err := cdc.MarshalJSON(tx) + if err != nil { + return err + } + + buf := bytes.NewBuffer([]byte{}) + err = json.Indent(buf, bz, "", " ") + if err != nil { + return err + } + + fmt.Println(buf.String()) + return nil +} + +func main() { + err := rootCmd.Execute() + if err != nil { + os.Exit(1) + } + os.Exit(0) +} diff --git a/docs/sdk/lcd-rest-api.yaml b/docs/sdk/lcd-rest-api.yaml index 408b2a792..3008d7f73 100644 --- a/docs/sdk/lcd-rest-api.yaml +++ b/docs/sdk/lcd-rest-api.yaml @@ -102,7 +102,7 @@ paths: - application/json responses: 200: - description: 12 word Seed + description: 16 word Seed schema: type: string /keys/{name}: @@ -204,7 +204,7 @@ paths: parameters: - in: path name: address - description: Account address + description: Account address in bech32 format required: true type: string get: @@ -222,7 +222,7 @@ paths: parameters: - in: path name: address - description: Account address + description: Account address in bech32 format required: true type: string post: @@ -255,18 +255,6 @@ paths: description: Tx was send and will probably be added to the next block 400: description: The Tx was malformated - /accounts/{address}/nonce: - parameters: - - in: path - name: address - description: Account address - required: true - type: string - get: - summary: Get the nonce for a certain account - responses: - 200: - description: Plaintext nonce i.e. "4" defaults to "0" /blocks/latest: get: summary: Get the latest block @@ -304,9 +292,14 @@ paths: 200: description: The validator set at the latest block height schema: - type: array - items: - $ref: "#/definitions/Delegate" + type: object + properties: + block_height: + type: number + validators: + type: array + items: + $ref: "#/definitions/Validator" /validatorsets/{height}: parameters: - in: path @@ -322,9 +315,14 @@ paths: 200: description: The validator set at a specific block height schema: - type: array - items: - $ref: "#/definitions/Delegate" + type: object + properties: + block_height: + type: number + validators: + type: array + items: + $ref: "#/definitions/Validator" 404: description: Block at height not available # /txs: @@ -549,7 +547,20 @@ paths: definitions: Address: type: string - example: DF096FDE8D380FA5B2AD20DB2962C82DDEA1ED9B + description: bech32 encoded addres + example: cosmosaccaddr:zgnkwr7eyyv643dllwfpdwensmgdtz89yu73zq + ValidatorAddress: + type: string + description: bech32 encoded addres + example: cosmosvaladdr:zgnkwr7eyyv643dllwfpdwensmgdtz89yu73zq + PubKey: + type: string + description: bech32 encoded public key + example: cosmosaccpub:zgnkwr7eyyv643dllwfpdwensmgdtz89yu73zq + ValidatorPubKey: + type: string + description: bech32 encoded public key + example: cosmosvalpub:zgnkwr7eyyv643dllwfpdwensmgdtz89yu73zq Coins: type: object properties: @@ -652,16 +663,6 @@ definitions: example: 81B11E717789600CC192B26F452A983DF13B985EE75ABD9DD9E68D7BA007A958 Pubkey: $ref: "#/definitions/PubKey" - PubKey: - type: object - properties: - type: - type: string - enum: - - ed25519 - data: - type: string - example: 81B11E717789600CC192B26F452A983DF13B985EE75ABD9DD9E68D7BA007A958 Account: type: object properties: @@ -753,17 +754,19 @@ definitions: type: array items: type: object - Delegate: + Validator: type: object properties: + address: + $ref: '#/definitions/ValidatorAddress' pub_key: - $ref: "#/definitions/PubKey" + $ref: "#/definitions/ValidatorPubKey" power: type: number example: 1000 - name: - type: string - example: "159.89.3.34" + accum: + type: number + example: 1000 # Added by API Auto Mocking Plugin host: virtserver.swaggerhub.com basePath: /faboweb1/Cosmos-LCD-2/1.0.0 diff --git a/docs/sdk/overview.rst b/docs/sdk/overview.rst index 8a1350906..0cb7e7304 100644 --- a/docs/sdk/overview.rst +++ b/docs/sdk/overview.rst @@ -232,12 +232,14 @@ a standard form: type StdSignature struct { crypto.PubKey // optional crypto.Signature - Sequence int64 + AccountNumber int64 + Sequence int64 } -It contains the signature itself, as well as the corresponding account's -sequence number. The sequence number is expected to increment every time a -message is signed by a given account. This prevents "replay attacks", where +It contains the signature itself, as well as the corresponding account's account and +sequence numbers. The sequence number is expected to increment every time a +message is signed by a given account. The account number stays the same and is assigned +when the account is first generated. These prevent "replay attacks", where the same message could be executed over and over again. The ``StdSignature`` can also optionally include the public key for verifying the diff --git a/examples/basecoin/app/app.go b/examples/basecoin/app/app.go index 1718b8be2..f654ba05e 100644 --- a/examples/basecoin/app/app.go +++ b/examples/basecoin/app/app.go @@ -4,6 +4,7 @@ import ( "encoding/json" abci "github.com/tendermint/abci/types" + tmtypes "github.com/tendermint/tendermint/types" cmn "github.com/tendermint/tmlibs/common" dbm "github.com/tendermint/tmlibs/db" "github.com/tendermint/tmlibs/log" @@ -145,6 +146,7 @@ func (app *BasecoinApp) initChainer(ctx sdk.Context, req abci.RequestInitChain) panic(err) // TODO https://github.com/cosmos/cosmos-sdk/issues/468 // return sdk.ErrGenesisParse("").TraceCause(err, "") } + acc.AccountNumber = app.accountMapper.GetNextAccountNumber(ctx) app.accountMapper.SetAccount(ctx, acc) } @@ -155,7 +157,7 @@ func (app *BasecoinApp) initChainer(ctx sdk.Context, req abci.RequestInitChain) } // Custom logic for state export -func (app *BasecoinApp) ExportAppStateJSON() (appState json.RawMessage, err error) { +func (app *BasecoinApp) ExportAppStateAndValidators() (appState json.RawMessage, validators []tmtypes.GenesisValidator, err error) { ctx := app.NewContext(true, abci.Header{}) // iterate to get the accounts @@ -173,5 +175,10 @@ func (app *BasecoinApp) ExportAppStateJSON() (appState json.RawMessage, err erro genState := types.GenesisState{ Accounts: accounts, } - return wire.MarshalJSONIndent(app.cdc, genState) + appState, err = wire.MarshalJSONIndent(app.cdc, genState) + if err != nil { + return nil, nil, err + } + validators = stake.WriteValidators(ctx, app.stakeKeeper) + return appState, validators, err } diff --git a/examples/basecoin/app/app_test.go b/examples/basecoin/app/app_test.go index 3027a8470..23bc531c0 100644 --- a/examples/basecoin/app/app_test.go +++ b/examples/basecoin/app/app_test.go @@ -1,8 +1,6 @@ package app import ( - "encoding/json" - "fmt" "os" "testing" @@ -13,8 +11,6 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/wire" "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/x/stake" abci "github.com/tendermint/abci/types" @@ -23,74 +19,10 @@ import ( "github.com/tendermint/tmlibs/log" ) -// Construct some global addrs and txs for tests. -var ( - chainID = "" // TODO - - accName = "foobart" - - priv1 = crypto.GenPrivKeyEd25519() - addr1 = priv1.PubKey().Address() - priv2 = crypto.GenPrivKeyEd25519() - addr2 = priv2.PubKey().Address() - addr3 = crypto.GenPrivKeyEd25519().PubKey().Address() - priv4 = crypto.GenPrivKeyEd25519() - addr4 = priv4.PubKey().Address() - coins = sdk.Coins{{"foocoin", 10}} - halfCoins = sdk.Coins{{"foocoin", 5}} - manyCoins = sdk.Coins{{"foocoin", 1}, {"barcoin", 1}} - fee = auth.StdFee{ - sdk.Coins{{"foocoin", 0}}, - 100000, - } - - sendMsg1 = bank.MsgSend{ - Inputs: []bank.Input{bank.NewInput(addr1, coins)}, - Outputs: []bank.Output{bank.NewOutput(addr2, coins)}, - } - - sendMsg2 = bank.MsgSend{ - Inputs: []bank.Input{bank.NewInput(addr1, coins)}, - Outputs: []bank.Output{ - bank.NewOutput(addr2, halfCoins), - bank.NewOutput(addr3, halfCoins), - }, - } - - sendMsg3 = bank.MsgSend{ - Inputs: []bank.Input{ - bank.NewInput(addr1, coins), - bank.NewInput(addr4, coins), - }, - Outputs: []bank.Output{ - bank.NewOutput(addr2, coins), - bank.NewOutput(addr3, coins), - }, - } - - sendMsg4 = bank.MsgSend{ - Inputs: []bank.Input{ - bank.NewInput(addr2, coins), - }, - Outputs: []bank.Output{ - bank.NewOutput(addr1, coins), - }, - } - - sendMsg5 = bank.MsgSend{ - Inputs: []bank.Input{ - bank.NewInput(addr1, manyCoins), - }, - Outputs: []bank.Output{ - bank.NewOutput(addr2, manyCoins), - }, - } -) - func setGenesis(bapp *BasecoinApp, accs ...auth.BaseAccount) error { genaccs := make([]*types.GenesisAccount, len(accs)) for i, acc := range accs { - genaccs[i] = types.NewGenesisAccount(&types.AppAccount{acc, accName}) + genaccs[i] = types.NewGenesisAccount(&types.AppAccount{acc, "foobart"}) } genesisState := types.GenesisState{ @@ -111,79 +43,11 @@ func setGenesis(bapp *BasecoinApp, accs ...auth.BaseAccount) error { return nil } -func loggerAndDB() (log.Logger, dbm.DB) { - logger := log.NewTMLogger(log.NewSyncWriter(os.Stdout)).With("module", "sdk/app") - db := dbm.NewMemDB() - return logger, db -} - -func newBasecoinApp() *BasecoinApp { - logger, db := loggerAndDB() - return NewBasecoinApp(logger, db) -} - //_______________________________________________________________________ -func TestMsgs(t *testing.T) { - bapp := newBasecoinApp() - require.Nil(t, setGenesis(bapp)) - - msgs := []struct { - msg sdk.Msg - }{ - {sendMsg1}, - } - - for i, m := range msgs { - // Run a CheckDeliver - SignCheckDeliver(t, bapp, m.msg, []int64{int64(i)}, false, priv1) - } -} - -func TestSortGenesis(t *testing.T) { - logger, db := loggerAndDB() - bapp := NewBasecoinApp(logger, db) - - // Note the order: the coins are unsorted! - coinDenom1, coinDenom2 := "foocoin", "barcoin" - - genState := fmt.Sprintf(`{ - "accounts": [{ - "address": "%s", - "coins": [ - { - "denom": "%s", - "amount": 10 - }, - { - "denom": "%s", - "amount": 20 - } - ] - }] - }`, addr1.String(), coinDenom1, coinDenom2) - - // Initialize the chain - vals := []abci.Validator{} - bapp.InitChain(abci.RequestInitChain{Validators: vals, AppStateBytes: []byte(genState)}) - bapp.Commit() - - // Unsorted coins means invalid - err := sendMsg5.ValidateBasic() - require.Equal(t, sdk.CodeInvalidCoins, err.Code(), err.ABCILog()) - - // Sort coins, should be valid - sendMsg5.Inputs[0].Coins.Sort() - sendMsg5.Outputs[0].Coins.Sort() - err = sendMsg5.ValidateBasic() - require.Nil(t, err) - - // Ensure we can send - SignCheckDeliver(t, bapp, sendMsg5, []int64{0}, true, priv1) -} - func TestGenesis(t *testing.T) { - logger, db := loggerAndDB() + logger := log.NewTMLogger(log.NewSyncWriter(os.Stdout)).With("module", "sdk/app") + db := dbm.NewMemDB() bapp := NewBasecoinApp(logger, db) // Construct some genesis bytes to reflect basecoin/types/AppAccount @@ -211,318 +75,3 @@ func TestGenesis(t *testing.T) { res1 = bapp.accountMapper.GetAccount(ctx, baseAcc.Address) assert.Equal(t, acc, res1) } - -func TestMsgChangePubKey(t *testing.T) { - - bapp := newBasecoinApp() - - // Construct some genesis bytes to reflect basecoin/types/AppAccount - // Give 77 foocoin to the first key - coins, err := sdk.ParseCoins("77foocoin") - require.Nil(t, err) - baseAcc := auth.BaseAccount{ - Address: addr1, - Coins: coins, - } - - // Construct genesis state - err = setGenesis(bapp, baseAcc) - require.Nil(t, err) - - // A checkTx context (true) - ctxCheck := bapp.BaseApp.NewContext(true, abci.Header{}) - res1 := bapp.accountMapper.GetAccount(ctxCheck, addr1) - assert.Equal(t, baseAcc, res1.(*types.AppAccount).BaseAccount) - - // Run a CheckDeliver - SignCheckDeliver(t, bapp, sendMsg1, []int64{0}, true, priv1) - - // Check balances - CheckBalance(t, bapp, addr1, "67foocoin") - CheckBalance(t, bapp, addr2, "10foocoin") - - changePubKeyMsg := auth.MsgChangeKey{ - Address: addr1, - NewPubKey: priv2.PubKey(), - } - - ctxDeliver := bapp.BaseApp.NewContext(false, abci.Header{}) - acc := bapp.accountMapper.GetAccount(ctxDeliver, addr1) - - // send a MsgChangePubKey - SignCheckDeliver(t, bapp, changePubKeyMsg, []int64{1}, true, priv1) - acc = bapp.accountMapper.GetAccount(ctxDeliver, addr1) - - assert.True(t, priv2.PubKey().Equals(acc.GetPubKey())) - - // signing a SendMsg with the old privKey should be an auth error - tx := genTx(sendMsg1, []int64{2}, priv1) - res := bapp.Deliver(tx) - assert.Equal(t, sdk.ToABCICode(sdk.CodespaceRoot, sdk.CodeUnauthorized), res.Code, res.Log) - - // resigning the tx with the new correct priv key should work - SignCheckDeliver(t, bapp, sendMsg1, []int64{2}, true, priv2) - - // Check balances - CheckBalance(t, bapp, addr1, "57foocoin") - CheckBalance(t, bapp, addr2, "20foocoin") -} - -func TestMsgSendWithAccounts(t *testing.T) { - bapp := newBasecoinApp() - - // Construct some genesis bytes to reflect basecoin/types/AppAccount - // Give 77 foocoin to the first key - coins, err := sdk.ParseCoins("77foocoin") - require.Nil(t, err) - baseAcc := auth.BaseAccount{ - Address: addr1, - Coins: coins, - } - - // Construct genesis state - err = setGenesis(bapp, baseAcc) - require.Nil(t, err) - - // A checkTx context (true) - ctxCheck := bapp.BaseApp.NewContext(true, abci.Header{}) - res1 := bapp.accountMapper.GetAccount(ctxCheck, addr1) - assert.Equal(t, baseAcc, res1.(*types.AppAccount).BaseAccount) - - // Run a CheckDeliver - SignCheckDeliver(t, bapp, sendMsg1, []int64{0}, true, priv1) - - // Check balances - CheckBalance(t, bapp, addr1, "67foocoin") - CheckBalance(t, bapp, addr2, "10foocoin") - - // Delivering again should cause replay error - SignCheckDeliver(t, bapp, sendMsg1, []int64{0}, false, priv1) - - // bumping the txnonce number without resigning should be an auth error - tx := genTx(sendMsg1, []int64{0}, priv1) - tx.Signatures[0].Sequence = 1 - res := bapp.Deliver(tx) - - assert.Equal(t, sdk.ToABCICode(sdk.CodespaceRoot, sdk.CodeUnauthorized), res.Code, res.Log) - - // resigning the tx with the bumped sequence should work - SignCheckDeliver(t, bapp, sendMsg1, []int64{1}, true, priv1) -} - -func TestMsgSendMultipleOut(t *testing.T) { - bapp := newBasecoinApp() - - genCoins, err := sdk.ParseCoins("42foocoin") - require.Nil(t, err) - - acc1 := auth.BaseAccount{ - Address: addr1, - Coins: genCoins, - } - - acc2 := auth.BaseAccount{ - Address: addr2, - Coins: genCoins, - } - - // Construct genesis state - err = setGenesis(bapp, acc1, acc2) - require.Nil(t, err) - - // Simulate a Block - SignCheckDeliver(t, bapp, sendMsg2, []int64{0}, true, priv1) - - // Check balances - CheckBalance(t, bapp, addr1, "32foocoin") - CheckBalance(t, bapp, addr2, "47foocoin") - CheckBalance(t, bapp, addr3, "5foocoin") -} - -func TestSengMsgMultipleInOut(t *testing.T) { - bapp := newBasecoinApp() - - genCoins, err := sdk.ParseCoins("42foocoin") - require.Nil(t, err) - - acc1 := auth.BaseAccount{ - Address: addr1, - Coins: genCoins, - } - - acc2 := auth.BaseAccount{ - Address: addr2, - Coins: genCoins, - } - - acc4 := auth.BaseAccount{ - Address: addr4, - Coins: genCoins, - } - - err = setGenesis(bapp, acc1, acc2, acc4) - assert.Nil(t, err) - - // CheckDeliver - SignCheckDeliver(t, bapp, sendMsg3, []int64{0, 0}, true, priv1, priv4) - - // Check balances - CheckBalance(t, bapp, addr1, "32foocoin") - CheckBalance(t, bapp, addr4, "32foocoin") - CheckBalance(t, bapp, addr2, "52foocoin") - CheckBalance(t, bapp, addr3, "10foocoin") -} - -func TestMsgSendDependent(t *testing.T) { - bapp := newBasecoinApp() - - genCoins, err := sdk.ParseCoins("42foocoin") - require.Nil(t, err) - - acc1 := auth.BaseAccount{ - Address: addr1, - Coins: genCoins, - } - - // Construct genesis state - err = setGenesis(bapp, acc1) - require.Nil(t, err) - - err = setGenesis(bapp, acc1) - assert.Nil(t, err) - - // CheckDeliver - SignCheckDeliver(t, bapp, sendMsg1, []int64{0}, true, priv1) - - // Check balances - CheckBalance(t, bapp, addr1, "32foocoin") - CheckBalance(t, bapp, addr2, "10foocoin") - - // Simulate a Block - SignCheckDeliver(t, bapp, sendMsg4, []int64{0}, true, priv2) - - // Check balances - CheckBalance(t, bapp, addr1, "42foocoin") -} - -func TestMsgQuiz(t *testing.T) { - bapp := newBasecoinApp() - - // Construct genesis state - // Construct some genesis bytes to reflect basecoin/types/AppAccount - baseAcc := auth.BaseAccount{ - Address: addr1, - Coins: nil, - } - acc1 := &types.AppAccount{baseAcc, "foobart"} - - // Construct genesis state - genesisState := map[string]interface{}{ - "accounts": []*types.GenesisAccount{ - types.NewGenesisAccount(acc1), - }, - } - stateBytes, err := json.MarshalIndent(genesisState, "", "\t") - require.Nil(t, err) - - // Initialize the chain (nil) - vals := []abci.Validator{} - bapp.InitChain(abci.RequestInitChain{Validators: vals, AppStateBytes: stateBytes}) - bapp.Commit() - - // A checkTx context (true) - ctxCheck := bapp.BaseApp.NewContext(true, abci.Header{}) - res1 := bapp.accountMapper.GetAccount(ctxCheck, addr1) - assert.Equal(t, acc1, res1) - -} - -func TestIBCMsgs(t *testing.T) { - bapp := newBasecoinApp() - - sourceChain := "source-chain" - destChain := "dest-chain" - - baseAcc := auth.BaseAccount{ - Address: addr1, - Coins: coins, - } - acc1 := &types.AppAccount{baseAcc, "foobart"} - - err := setGenesis(bapp, baseAcc) - assert.Nil(t, err) - - // A checkTx context (true) - ctxCheck := bapp.BaseApp.NewContext(true, abci.Header{}) - res1 := bapp.accountMapper.GetAccount(ctxCheck, addr1) - assert.Equal(t, acc1, res1) - - packet := ibc.IBCPacket{ - SrcAddr: addr1, - DestAddr: addr1, - Coins: coins, - SrcChain: sourceChain, - DestChain: destChain, - } - - transferMsg := ibc.IBCTransferMsg{ - IBCPacket: packet, - } - - receiveMsg := ibc.IBCReceiveMsg{ - IBCPacket: packet, - Relayer: addr1, - Sequence: 0, - } - - SignCheckDeliver(t, bapp, transferMsg, []int64{0}, true, priv1) - CheckBalance(t, bapp, addr1, "") - SignCheckDeliver(t, bapp, transferMsg, []int64{1}, false, priv1) - SignCheckDeliver(t, bapp, receiveMsg, []int64{2}, true, priv1) - CheckBalance(t, bapp, addr1, "10foocoin") - SignCheckDeliver(t, bapp, receiveMsg, []int64{3}, false, priv1) -} - -func genTx(msg sdk.Msg, seq []int64, priv ...crypto.PrivKeyEd25519) auth.StdTx { - sigs := make([]auth.StdSignature, len(priv)) - for i, p := range priv { - sigs[i] = auth.StdSignature{ - PubKey: p.PubKey(), - Signature: p.Sign(auth.StdSignBytes(chainID, seq, fee, msg)), - Sequence: seq[i], - } - } - - return auth.NewStdTx(msg, fee, sigs) - -} - -func SignCheckDeliver(t *testing.T, bapp *BasecoinApp, msg sdk.Msg, seq []int64, expPass bool, priv ...crypto.PrivKeyEd25519) { - - // Sign the tx - tx := genTx(msg, seq, priv...) - // Run a Check - res := bapp.Check(tx) - if expPass { - require.Equal(t, sdk.ABCICodeOK, res.Code, res.Log) - } else { - require.NotEqual(t, sdk.ABCICodeOK, res.Code, res.Log) - } - - // Simulate a Block - bapp.BeginBlock(abci.RequestBeginBlock{}) - res = bapp.Deliver(tx) - if expPass { - require.Equal(t, sdk.ABCICodeOK, res.Code, res.Log) - } else { - require.NotEqual(t, sdk.ABCICodeOK, res.Code, res.Log) - } - bapp.EndBlock(abci.RequestEndBlock{}) - //bapp.Commit() -} - -func CheckBalance(t *testing.T, bapp *BasecoinApp, addr sdk.Address, balExpected string) { - ctxDeliver := bapp.BaseApp.NewContext(false, abci.Header{}) - res2 := bapp.accountMapper.GetAccount(ctxDeliver, addr) - assert.Equal(t, balExpected, fmt.Sprintf("%v", res2.GetCoins())) -} diff --git a/examples/basecoin/cmd/basecoind/main.go b/examples/basecoin/cmd/basecoind/main.go index 4e6a30a08..4a6498e1b 100644 --- a/examples/basecoin/cmd/basecoind/main.go +++ b/examples/basecoin/cmd/basecoind/main.go @@ -7,6 +7,7 @@ import ( "github.com/spf13/cobra" abci "github.com/tendermint/abci/types" + tmtypes "github.com/tendermint/tendermint/types" "github.com/tendermint/tmlibs/cli" dbm "github.com/tendermint/tmlibs/db" "github.com/tendermint/tmlibs/log" @@ -27,7 +28,7 @@ func main() { server.AddCommands(ctx, cdc, rootCmd, server.DefaultAppInit, server.ConstructAppCreator(newApp, "basecoin"), - server.ConstructAppExporter(exportAppState, "basecoin")) + server.ConstructAppExporter(exportAppStateAndTMValidators, "basecoin")) // prepare and add flags rootDir := os.ExpandEnv("$HOME/.basecoind") @@ -39,7 +40,7 @@ func newApp(logger log.Logger, db dbm.DB) abci.Application { return app.NewBasecoinApp(logger, db) } -func exportAppState(logger log.Logger, db dbm.DB) (json.RawMessage, error) { +func exportAppStateAndTMValidators(logger log.Logger, db dbm.DB) (json.RawMessage, []tmtypes.GenesisValidator, error) { bapp := app.NewBasecoinApp(logger, db) - return bapp.ExportAppStateJSON() + return bapp.ExportAppStateAndValidators() } diff --git a/examples/democoin/app/app.go b/examples/democoin/app/app.go index 2075a64da..dc7c3c9d8 100644 --- a/examples/democoin/app/app.go +++ b/examples/democoin/app/app.go @@ -4,6 +4,7 @@ import ( "encoding/json" abci "github.com/tendermint/abci/types" + tmtypes "github.com/tendermint/tendermint/types" cmn "github.com/tendermint/tmlibs/common" dbm "github.com/tendermint/tmlibs/db" "github.com/tendermint/tmlibs/log" @@ -154,7 +155,7 @@ func (app *DemocoinApp) initChainerFn(coolKeeper cool.Keeper, powKeeper pow.Keep } // Custom logic for state export -func (app *DemocoinApp) ExportAppStateJSON() (appState json.RawMessage, err error) { +func (app *DemocoinApp) ExportAppStateAndValidators() (appState json.RawMessage, validators []tmtypes.GenesisValidator, err error) { ctx := app.NewContext(true, abci.Header{}) // iterate to get the accounts @@ -174,5 +175,9 @@ func (app *DemocoinApp) ExportAppStateJSON() (appState json.RawMessage, err erro POWGenesis: pow.WriteGenesis(ctx, app.powKeeper), CoolGenesis: cool.WriteGenesis(ctx, app.coolKeeper), } - return wire.MarshalJSONIndent(app.cdc, genState) + appState, err = wire.MarshalJSONIndent(app.cdc, genState) + if err != nil { + return nil, nil, err + } + return appState, validators, nil } diff --git a/examples/democoin/app/app_test.go b/examples/democoin/app/app_test.go index ba041bcff..01264399a 100644 --- a/examples/democoin/app/app_test.go +++ b/examples/democoin/app/app_test.go @@ -2,7 +2,6 @@ package app import ( "encoding/json" - "fmt" "os" "testing" @@ -10,12 +9,8 @@ import ( "github.com/stretchr/testify/require" "github.com/cosmos/cosmos-sdk/examples/democoin/types" - "github.com/cosmos/cosmos-sdk/examples/democoin/x/cool" - "github.com/cosmos/cosmos-sdk/examples/democoin/x/pow" 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" abci "github.com/tendermint/abci/types" crypto "github.com/tendermint/go-crypto" @@ -23,101 +18,9 @@ import ( "github.com/tendermint/tmlibs/log" ) -// Construct some global addrs and txs for tests. -var ( - chainID = "" // TODO - - priv1 = crypto.GenPrivKeyEd25519() - addr1 = priv1.PubKey().Address() - addr2 = crypto.GenPrivKeyEd25519().PubKey().Address() - coins = sdk.Coins{{"foocoin", 10}} - fee = auth.StdFee{ - sdk.Coins{{"foocoin", 0}}, - 1000000, - } - - sendMsg = bank.MsgSend{ - Inputs: []bank.Input{bank.NewInput(addr1, coins)}, - Outputs: []bank.Output{bank.NewOutput(addr2, coins)}, - } - - quizMsg1 = cool.MsgQuiz{ - Sender: addr1, - CoolAnswer: "icecold", - } - - quizMsg2 = cool.MsgQuiz{ - Sender: addr1, - CoolAnswer: "badvibesonly", - } - - setTrendMsg1 = cool.MsgSetTrend{ - Sender: addr1, - Cool: "icecold", - } - - setTrendMsg2 = cool.MsgSetTrend{ - Sender: addr1, - Cool: "badvibesonly", - } - - setTrendMsg3 = cool.MsgSetTrend{ - Sender: addr1, - Cool: "warmandkind", - } -) - -func loggerAndDB() (log.Logger, dbm.DB) { +func TestGenesis(t *testing.T) { logger := log.NewTMLogger(log.NewSyncWriter(os.Stdout)).With("module", "sdk/app") db := dbm.NewMemDB() - return logger, db -} - -func newDemocoinApp() *DemocoinApp { - logger, db := loggerAndDB() - return NewDemocoinApp(logger, db) -} - -//_______________________________________________________________________ - -func TestMsgs(t *testing.T) { - bapp := newDemocoinApp() - - msgs := []struct { - msg sdk.Msg - }{ - {sendMsg}, - {quizMsg1}, - {setTrendMsg1}, - } - - sequences := []int64{0} - for i, m := range msgs { - sig := priv1.Sign(auth.StdSignBytes(chainID, sequences, fee, m.msg)) - tx := auth.NewStdTx(m.msg, fee, []auth.StdSignature{{ - PubKey: priv1.PubKey(), - Signature: sig, - }}) - - // just marshal/unmarshal! - txBytes, err := bapp.cdc.MarshalBinary(tx) - require.NoError(t, err, "i: %v", i) - - // Run a Check - cres := bapp.CheckTx(txBytes) - assert.Equal(t, sdk.CodeUnknownAddress, - sdk.CodeType(cres.Code), "i: %v, log: %v", i, cres.Log) - - // Simulate a Block - bapp.BeginBlock(abci.RequestBeginBlock{}) - dres := bapp.DeliverTx(txBytes) - assert.Equal(t, sdk.CodeUnknownAddress, - sdk.CodeType(dres.Code), "i: %v, log: %v", i, dres.Log) - } -} - -func TestGenesis(t *testing.T) { - logger, db := loggerAndDB() bapp := NewDemocoinApp(logger, db) // Construct some genesis bytes to reflect democoin/types/AppAccount @@ -156,272 +59,3 @@ func TestGenesis(t *testing.T) { res1 = bapp.accountMapper.GetAccount(ctx, baseAcc.Address) assert.Equal(t, acc, res1) } - -func TestMsgSendWithAccounts(t *testing.T) { - bapp := newDemocoinApp() - - // Construct some genesis bytes to reflect democoin/types/AppAccount - // Give 77 foocoin to the first key - coins, err := sdk.ParseCoins("77foocoin") - require.Nil(t, err) - baseAcc := auth.BaseAccount{ - Address: addr1, - Coins: coins, - } - acc1 := &types.AppAccount{baseAcc, "foobart"} - - // Construct genesis state - genesisState := map[string]interface{}{ - "accounts": []*types.GenesisAccount{ - types.NewGenesisAccount(acc1), - }, - "cool": map[string]string{ - "trend": "ice-cold", - }, - } - stateBytes, err := json.MarshalIndent(genesisState, "", "\t") - require.Nil(t, err) - - // Initialize the chain - vals := []abci.Validator{} - bapp.InitChain(abci.RequestInitChain{Validators: vals, AppStateBytes: stateBytes}) - bapp.Commit() - - // A checkTx context (true) - ctxCheck := bapp.BaseApp.NewContext(true, abci.Header{}) - res1 := bapp.accountMapper.GetAccount(ctxCheck, addr1) - assert.Equal(t, acc1, res1) - - // Sign the tx - sequences := []int64{0} - sig := priv1.Sign(auth.StdSignBytes(chainID, sequences, fee, sendMsg)) - tx := auth.NewStdTx(sendMsg, fee, []auth.StdSignature{{ - PubKey: priv1.PubKey(), - Signature: sig, - }}) - - // Run a Check - res := bapp.Check(tx) - assert.Equal(t, sdk.ABCICodeOK, res.Code, res.Log) - - // Simulate a Block - bapp.BeginBlock(abci.RequestBeginBlock{}) - res = bapp.Deliver(tx) - assert.Equal(t, sdk.ABCICodeOK, res.Code, res.Log) - - // Check balances - ctxDeliver := bapp.BaseApp.NewContext(false, abci.Header{}) - res2 := bapp.accountMapper.GetAccount(ctxDeliver, addr1) - res3 := bapp.accountMapper.GetAccount(ctxDeliver, addr2) - assert.Equal(t, fmt.Sprintf("%v", res2.GetCoins()), "67foocoin") - assert.Equal(t, fmt.Sprintf("%v", res3.GetCoins()), "10foocoin") - - // Delivering again should cause replay error - res = bapp.Deliver(tx) - assert.Equal(t, sdk.ToABCICode(sdk.CodespaceRoot, sdk.CodeInvalidSequence), sdk.ABCICodeType(res.Code), res.Log) - - // bumping the txnonce number without resigning should be an auth error - tx.Signatures[0].Sequence = 1 - res = bapp.Deliver(tx) - assert.Equal(t, sdk.ToABCICode(sdk.CodespaceRoot, sdk.CodeUnauthorized), sdk.ABCICodeType(res.Code), res.Log) - - // resigning the tx with the bumped sequence should work - sequences = []int64{1} - sig = priv1.Sign(auth.StdSignBytes(chainID, sequences, fee, tx.Msg)) - tx.Signatures[0].Signature = sig - res = bapp.Deliver(tx) - assert.Equal(t, sdk.ABCICodeOK, res.Code, res.Log) -} - -func TestMsgMine(t *testing.T) { - bapp := newDemocoinApp() - - // Construct genesis state - // Construct some genesis bytes to reflect democoin/types/AppAccount - baseAcc := auth.BaseAccount{ - Address: addr1, - Coins: nil, - } - acc1 := &types.AppAccount{baseAcc, "foobart"} - - // Construct genesis state - genesisState := map[string]interface{}{ - "accounts": []*types.GenesisAccount{ - types.NewGenesisAccount(acc1), - }, - "cool": map[string]string{ - "trend": "ice-cold", - }, - "pow": map[string]uint64{ - "difficulty": 1, - "count": 0, - }, - } - stateBytes, err := json.MarshalIndent(genesisState, "", "\t") - require.Nil(t, err) - - // Initialize the chain (nil) - vals := []abci.Validator{} - bapp.InitChain(abci.RequestInitChain{Validators: vals, AppStateBytes: stateBytes}) - bapp.Commit() - - // A checkTx context (true) - ctxCheck := bapp.BaseApp.NewContext(true, abci.Header{}) - res1 := bapp.accountMapper.GetAccount(ctxCheck, addr1) - assert.Equal(t, acc1, res1) - - // Mine and check for reward - mineMsg1 := pow.GenerateMsgMine(addr1, 1, 2) - SignCheckDeliver(t, bapp, mineMsg1, 0, true) - CheckBalance(t, bapp, "1pow") - // Mine again and check for reward - mineMsg2 := pow.GenerateMsgMine(addr1, 2, 3) - SignCheckDeliver(t, bapp, mineMsg2, 1, true) - CheckBalance(t, bapp, "2pow") - // Mine again - should be invalid - SignCheckDeliver(t, bapp, mineMsg2, 1, false) - CheckBalance(t, bapp, "2pow") - -} - -func TestMsgQuiz(t *testing.T) { - bapp := newDemocoinApp() - - // Construct genesis state - // Construct some genesis bytes to reflect democoin/types/AppAccount - baseAcc := auth.BaseAccount{ - Address: addr1, - Coins: nil, - } - acc1 := &types.AppAccount{baseAcc, "foobart"} - - // Construct genesis state - genesisState := map[string]interface{}{ - "accounts": []*types.GenesisAccount{ - types.NewGenesisAccount(acc1), - }, - "cool": map[string]string{ - "trend": "ice-cold", - }, - } - stateBytes, err := json.MarshalIndent(genesisState, "", "\t") - require.Nil(t, err) - - // Initialize the chain (nil) - vals := []abci.Validator{} - bapp.InitChain(abci.RequestInitChain{Validators: vals, AppStateBytes: stateBytes}) - bapp.Commit() - - // A checkTx context (true) - ctxCheck := bapp.BaseApp.NewContext(true, abci.Header{}) - res1 := bapp.accountMapper.GetAccount(ctxCheck, addr1) - assert.Equal(t, acc1, res1) - - // Set the trend, submit a really cool quiz and check for reward - SignCheckDeliver(t, bapp, setTrendMsg1, 0, true) - SignCheckDeliver(t, bapp, quizMsg1, 1, true) - CheckBalance(t, bapp, "69icecold") - SignCheckDeliver(t, bapp, quizMsg2, 2, false) // result without reward - CheckBalance(t, bapp, "69icecold") - SignCheckDeliver(t, bapp, quizMsg1, 3, true) - CheckBalance(t, bapp, "138icecold") - SignCheckDeliver(t, bapp, setTrendMsg2, 4, true) // reset the trend - SignCheckDeliver(t, bapp, quizMsg1, 5, false) // the same answer will nolonger do! - CheckBalance(t, bapp, "138icecold") - SignCheckDeliver(t, bapp, quizMsg2, 6, true) // earlier answer now relavent again - CheckBalance(t, bapp, "69badvibesonly,138icecold") - SignCheckDeliver(t, bapp, setTrendMsg3, 7, false) // expect to fail to set the trend to something which is not cool - -} - -func TestHandler(t *testing.T) { - bapp := newDemocoinApp() - - sourceChain := "source-chain" - destChain := "dest-chain" - - vals := []abci.Validator{} - baseAcc := auth.BaseAccount{ - Address: addr1, - Coins: coins, - } - acc1 := &types.AppAccount{baseAcc, "foobart"} - genesisState := map[string]interface{}{ - "accounts": []*types.GenesisAccount{ - types.NewGenesisAccount(acc1), - }, - "cool": map[string]string{ - "trend": "ice-cold", - }, - } - stateBytes, err := json.MarshalIndent(genesisState, "", "\t") - require.Nil(t, err) - bapp.InitChain(abci.RequestInitChain{Validators: vals, AppStateBytes: stateBytes}) - bapp.Commit() - - // A checkTx context (true) - ctxCheck := bapp.BaseApp.NewContext(true, abci.Header{}) - res1 := bapp.accountMapper.GetAccount(ctxCheck, addr1) - assert.Equal(t, acc1, res1) - - packet := ibc.IBCPacket{ - SrcAddr: addr1, - DestAddr: addr1, - Coins: coins, - SrcChain: sourceChain, - DestChain: destChain, - } - - transferMsg := ibc.IBCTransferMsg{ - IBCPacket: packet, - } - - receiveMsg := ibc.IBCReceiveMsg{ - IBCPacket: packet, - Relayer: addr1, - Sequence: 0, - } - - SignCheckDeliver(t, bapp, transferMsg, 0, true) - CheckBalance(t, bapp, "") - SignCheckDeliver(t, bapp, transferMsg, 1, false) - SignCheckDeliver(t, bapp, receiveMsg, 2, true) - CheckBalance(t, bapp, "10foocoin") - SignCheckDeliver(t, bapp, receiveMsg, 3, false) -} - -// TODO describe the use of this function -func SignCheckDeliver(t *testing.T, bapp *DemocoinApp, msg sdk.Msg, seq int64, expPass bool) { - - // Sign the tx - tx := auth.NewStdTx(msg, fee, []auth.StdSignature{{ - PubKey: priv1.PubKey(), - Signature: priv1.Sign(auth.StdSignBytes(chainID, []int64{seq}, fee, msg)), - Sequence: seq, - }}) - - // Run a Check - res := bapp.Check(tx) - if expPass { - require.Equal(t, sdk.ABCICodeOK, res.Code, res.Log) - } else { - require.NotEqual(t, sdk.ABCICodeOK, res.Code, res.Log) - } - - // Simulate a Block - bapp.BeginBlock(abci.RequestBeginBlock{}) - res = bapp.Deliver(tx) - if expPass { - require.Equal(t, sdk.ABCICodeOK, res.Code, res.Log) - } else { - require.NotEqual(t, sdk.ABCICodeOK, res.Code, res.Log) - } - bapp.EndBlock(abci.RequestEndBlock{}) - //bapp.Commit() -} - -func CheckBalance(t *testing.T, bapp *DemocoinApp, balExpected string) { - ctxDeliver := bapp.BaseApp.NewContext(false, abci.Header{}) - res2 := bapp.accountMapper.GetAccount(ctxDeliver, addr1) - assert.Equal(t, balExpected, fmt.Sprintf("%v", res2.GetCoins())) -} diff --git a/examples/democoin/cmd/democoind/main.go b/examples/democoin/cmd/democoind/main.go index 3283da58a..76d29075e 100644 --- a/examples/democoin/cmd/democoind/main.go +++ b/examples/democoin/cmd/democoind/main.go @@ -7,6 +7,7 @@ import ( "github.com/spf13/cobra" abci "github.com/tendermint/abci/types" + tmtypes "github.com/tendermint/tendermint/types" "github.com/tendermint/tmlibs/cli" dbm "github.com/tendermint/tmlibs/db" "github.com/tendermint/tmlibs/log" @@ -46,9 +47,9 @@ func newApp(logger log.Logger, db dbm.DB) abci.Application { return app.NewDemocoinApp(logger, db) } -func exportAppState(logger log.Logger, db dbm.DB) (json.RawMessage, error) { +func exportAppStateAndTMValidators(logger log.Logger, db dbm.DB) (json.RawMessage, []tmtypes.GenesisValidator, error) { dapp := app.NewDemocoinApp(logger, db) - return dapp.ExportAppStateJSON() + return dapp.ExportAppStateAndValidators() } func main() { @@ -63,7 +64,7 @@ func main() { server.AddCommands(ctx, cdc, rootCmd, CoolAppInit, server.ConstructAppCreator(newApp, "democoin"), - server.ConstructAppExporter(exportAppState, "democoin")) + server.ConstructAppExporter(exportAppStateAndTMValidators, "democoin")) // prepare and add flags rootDir := os.ExpandEnv("$HOME/.democoind") diff --git a/examples/democoin/x/cool/app_test.go b/examples/democoin/x/cool/app_test.go new file mode 100644 index 000000000..8d3f347b3 --- /dev/null +++ b/examples/democoin/x/cool/app_test.go @@ -0,0 +1,105 @@ +package cool + +import ( + "testing" + + "github.com/stretchr/testify/assert" + + abci "github.com/tendermint/abci/types" + crypto "github.com/tendermint/go-crypto" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/auth" + "github.com/cosmos/cosmos-sdk/x/auth/mock" + bank "github.com/cosmos/cosmos-sdk/x/bank" +) + +var ( + priv1 = crypto.GenPrivKeyEd25519() + addr1 = priv1.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() + + RegisterWire(mapp.Cdc) + keyCool := sdk.NewKVStoreKey("cool") + coinKeeper := bank.NewKeeper(mapp.AccountMapper) + keeper := NewKeeper(keyCool, coinKeeper, mapp.RegisterCodespace(DefaultCodespace)) + mapp.Router().AddRoute("cool", NewHandler(keeper)) + + mapp.SetInitChainer(getInitChainer(mapp, keeper, "ice-cold")) + + mapp.CompleteSetup(t, []*sdk.KVStoreKey{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.AccountMapper.GetAccount(ctxCheck, addr1) + assert.Equal(t, acc1, res1) + + // Set the trend, submit a really cool quiz and check for reward + mock.SignCheckDeliver(t, mapp.BaseApp, setTrendMsg1, []int64{0}, []int64{0}, true, priv1) + mock.SignCheckDeliver(t, mapp.BaseApp, quizMsg1, []int64{0}, []int64{1}, true, priv1) + mock.CheckBalance(t, mapp, addr1, sdk.Coins{{"icecold", 69}}) + mock.SignCheckDeliver(t, mapp.BaseApp, quizMsg2, []int64{0}, []int64{2}, false, priv1) // result without reward + mock.CheckBalance(t, mapp, addr1, sdk.Coins{{"icecold", 69}}) + mock.SignCheckDeliver(t, mapp.BaseApp, quizMsg1, []int64{0}, []int64{3}, true, priv1) + mock.CheckBalance(t, mapp, addr1, sdk.Coins{{"icecold", 138}}) + mock.SignCheckDeliver(t, mapp.BaseApp, setTrendMsg2, []int64{0}, []int64{4}, true, priv1) // reset the trend + mock.SignCheckDeliver(t, mapp.BaseApp, quizMsg1, []int64{0}, []int64{5}, false, priv1) // the same answer will nolonger do! + mock.CheckBalance(t, mapp, addr1, sdk.Coins{{"icecold", 138}}) + mock.SignCheckDeliver(t, mapp.BaseApp, quizMsg2, []int64{0}, []int64{6}, true, priv1) // earlier answer now relavent again + mock.CheckBalance(t, mapp, addr1, sdk.Coins{{"badvibesonly", 69}, {"icecold", 138}}) + mock.SignCheckDeliver(t, mapp.BaseApp, setTrendMsg3, []int64{0}, []int64{7}, false, priv1) // expect to fail to set the trend to something which is not cool +} diff --git a/examples/democoin/x/pow/app_test.go b/examples/democoin/x/pow/app_test.go new file mode 100644 index 000000000..aa71fb080 --- /dev/null +++ b/examples/democoin/x/pow/app_test.go @@ -0,0 +1,83 @@ +package pow + +import ( + "testing" + + "github.com/stretchr/testify/assert" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/auth" + "github.com/cosmos/cosmos-sdk/x/auth/mock" + "github.com/cosmos/cosmos-sdk/x/bank" + + abci "github.com/tendermint/abci/types" + crypto "github.com/tendermint/go-crypto" +) + +var ( + priv1 = crypto.GenPrivKeyEd25519() + addr1 = priv1.PubKey().Address() +) + +// initialize the mock application for this module +func getMockApp(t *testing.T) *mock.App { + mapp := mock.NewApp() + + RegisterWire(mapp.Cdc) + keyPOW := sdk.NewKVStoreKey("pow") + coinKeeper := bank.NewKeeper(mapp.AccountMapper) + config := Config{"pow", 1} + keeper := NewKeeper(keyPOW, config, coinKeeper, mapp.RegisterCodespace(DefaultCodespace)) + mapp.Router().AddRoute("pow", keeper.Handler) + + mapp.SetInitChainer(getInitChainer(mapp, keeper)) + + mapp.CompleteSetup(t, []*sdk.KVStoreKey{keyPOW}) + 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.AccountMapper.GetAccount(ctxCheck, addr1) + assert.Equal(t, acc1, res1) + + // Mine and check for reward + mineMsg1 := GenerateMsgMine(addr1, 1, 2) + mock.SignCheckDeliver(t, mapp.BaseApp, mineMsg1, []int64{0}, []int64{0}, true, priv1) + mock.CheckBalance(t, mapp, addr1, sdk.Coins{{"pow", 1}}) + // Mine again and check for reward + mineMsg2 := GenerateMsgMine(addr1, 2, 3) + mock.SignCheckDeliver(t, mapp.BaseApp, mineMsg2, []int64{0}, []int64{1}, true, priv1) + mock.CheckBalance(t, mapp, addr1, sdk.Coins{{"pow", 2}}) + // Mine again - should be invalid + mock.SignCheckDeliver(t, mapp.BaseApp, mineMsg2, []int64{0}, []int64{1}, false, priv1) + mock.CheckBalance(t, mapp, addr1, sdk.Coins{{"pow", 2}}) +} diff --git a/server/constructors.go b/server/constructors.go index 3d6950062..c91c67a18 100644 --- a/server/constructors.go +++ b/server/constructors.go @@ -5,6 +5,7 @@ import ( "path/filepath" abci "github.com/tendermint/abci/types" + tmtypes "github.com/tendermint/tendermint/types" dbm "github.com/tendermint/tmlibs/db" "github.com/tendermint/tmlibs/log" ) @@ -13,8 +14,8 @@ import ( // and other flags (?) to start type AppCreator func(string, log.Logger) (abci.Application, error) -// AppExporter dumps all app state to JSON-serializable structure -type AppExporter func(home string, log log.Logger) (json.RawMessage, error) +// AppExporter dumps all app state to JSON-serializable structure and returns the current validator set +type AppExporter func(home string, log log.Logger) (json.RawMessage, []tmtypes.GenesisValidator, error) // ConstructAppCreator returns an application generation function func ConstructAppCreator(appFn func(log.Logger, dbm.DB) abci.Application, name string) AppCreator { @@ -30,12 +31,12 @@ func ConstructAppCreator(appFn func(log.Logger, dbm.DB) abci.Application, name s } // ConstructAppExporter returns an application export function -func ConstructAppExporter(appFn func(log.Logger, dbm.DB) (json.RawMessage, error), name string) AppExporter { - return func(rootDir string, logger log.Logger) (json.RawMessage, error) { +func ConstructAppExporter(appFn func(log.Logger, dbm.DB) (json.RawMessage, []tmtypes.GenesisValidator, error), name string) AppExporter { + return func(rootDir string, logger log.Logger) (json.RawMessage, []tmtypes.GenesisValidator, error) { dataDir := filepath.Join(rootDir, "data") db, err := dbm.NewGoLevelDB(name, dataDir) if err != nil { - return nil, err + return nil, nil, err } return appFn(logger, db) } diff --git a/server/export.go b/server/export.go index dad2df9cc..794235f62 100644 --- a/server/export.go +++ b/server/export.go @@ -18,7 +18,7 @@ func ExportCmd(ctx *Context, cdc *wire.Codec, appExporter AppExporter) *cobra.Co Short: "Export state to JSON", RunE: func(cmd *cobra.Command, args []string) error { home := viper.GetString("home") - appState, err := appExporter(home, ctx.Logger) + appState, validators, err := appExporter(home, ctx.Logger) if err != nil { return errors.Errorf("Error exporting state: %v\n", err) } @@ -27,6 +27,7 @@ func ExportCmd(ctx *Context, cdc *wire.Codec, appExporter AppExporter) *cobra.Co return err } doc.AppStateJSON = appState + doc.Validators = validators encoded, err := wire.MarshalJSONIndent(cdc, doc) if err != nil { return err diff --git a/server/init_test.go b/server/init_test.go index eca529505..300decf33 100644 --- a/server/init_test.go +++ b/server/init_test.go @@ -7,7 +7,7 @@ import ( "github.com/tendermint/tmlibs/log" - "github.com/cosmos/cosmos-sdk/mock" + "github.com/cosmos/cosmos-sdk/server/mock" "github.com/cosmos/cosmos-sdk/wire" tcmd "github.com/tendermint/tendermint/cmd/tendermint/commands" ) diff --git a/mock/app.go b/server/mock/app.go similarity index 100% rename from mock/app.go rename to server/mock/app.go diff --git a/mock/app_test.go b/server/mock/app_test.go similarity index 100% rename from mock/app_test.go rename to server/mock/app_test.go diff --git a/mock/helpers.go b/server/mock/helpers.go similarity index 100% rename from mock/helpers.go rename to server/mock/helpers.go diff --git a/mock/store.go b/server/mock/store.go similarity index 100% rename from mock/store.go rename to server/mock/store.go diff --git a/mock/store_test.go b/server/mock/store_test.go similarity index 100% rename from mock/store_test.go rename to server/mock/store_test.go diff --git a/mock/tx.go b/server/mock/tx.go similarity index 100% rename from mock/tx.go rename to server/mock/tx.go diff --git a/server/start_test.go b/server/start_test.go index 454f2d492..1c1ad671e 100644 --- a/server/start_test.go +++ b/server/start_test.go @@ -9,7 +9,7 @@ import ( "github.com/spf13/viper" "github.com/stretchr/testify/require" - "github.com/cosmos/cosmos-sdk/mock" + "github.com/cosmos/cosmos-sdk/server/mock" "github.com/cosmos/cosmos-sdk/wire" "github.com/tendermint/abci/server" tcmd "github.com/tendermint/tendermint/cmd/tendermint/commands" diff --git a/types/account.go b/types/account.go index ad0c58231..a7dd50ead 100644 --- a/types/account.go +++ b/types/account.go @@ -26,21 +26,57 @@ func Bech32ifyAcc(addr Address) (string, error) { return bech32.ConvertAndEncode(Bech32PrefixAccAddr, addr.Bytes()) } +// MustBech32ifyAcc panics on bech32-encoding failure +func MustBech32ifyAcc(addr Address) string { + enc, err := Bech32ifyAcc(addr) + if err != nil { + panic(err) + } + return enc +} + // Bech32ifyAccPub takes AccountPubKey and returns the bech32 encoded string func Bech32ifyAccPub(pub crypto.PubKey) (string, error) { return bech32.ConvertAndEncode(Bech32PrefixAccPub, pub.Bytes()) } +// MustBech32ifyAccPub panics on bech32-encoding failure +func MustBech32ifyAccPub(pub crypto.PubKey) string { + enc, err := Bech32ifyAccPub(pub) + if err != nil { + panic(err) + } + return enc +} + // Bech32ifyVal returns the bech32 encoded string for a validator address -func bech32ifyVal(addr Address) (string, error) { +func Bech32ifyVal(addr Address) (string, error) { return bech32.ConvertAndEncode(Bech32PrefixValAddr, addr.Bytes()) } +// MustBech32ifyVal panics on bech32-encoding failure +func MustBech32ifyVal(addr Address) string { + enc, err := Bech32ifyVal(addr) + if err != nil { + panic(err) + } + return enc +} + // Bech32ifyValPub returns the bech32 encoded string for a validator pubkey func Bech32ifyValPub(pub crypto.PubKey) (string, error) { return bech32.ConvertAndEncode(Bech32PrefixValPub, pub.Bytes()) } +// MustBech32ifyValPub pancis on bech32-encoding failure +func MustBech32ifyValPub(pub crypto.PubKey) string { + enc, err := Bech32ifyValPub(pub) + if err != nil { + panic(err) + } + return enc +} + // create an Address from a string func GetAccAddressHex(address string) (addr Address, err error) { if len(address) == 0 { @@ -55,13 +91,28 @@ func GetAccAddressHex(address string) (addr Address, err error) { // create an Address from a string func GetAccAddressBech32(address string) (addr Address, err error) { - bz, err := getFromBech32(address, Bech32PrefixAccAddr) + bz, err := GetFromBech32(address, Bech32PrefixAccAddr) if err != nil { return nil, err } return Address(bz), nil } +// create a Pubkey from a string +func GetAccPubKeyBech32(address string) (pk crypto.PubKey, err error) { + bz, err := GetFromBech32(address, Bech32PrefixAccPub) + if err != nil { + return nil, err + } + + pk, err = crypto.PubKeyFromBytes(bz) + if err != nil { + return nil, err + } + + return pk, nil +} + // create an Address from a hex string func GetValAddressHex(address string) (addr Address, err error) { if len(address) == 0 { @@ -76,16 +127,16 @@ func GetValAddressHex(address string) (addr Address, err error) { // create an Address from a bech32 string func GetValAddressBech32(address string) (addr Address, err error) { - bz, err := getFromBech32(address, Bech32PrefixValAddr) + bz, err := GetFromBech32(address, Bech32PrefixValAddr) if err != nil { return nil, err } return Address(bz), nil } -//Decode a validator publickey into a public key +// decode a validator public key into a PubKey func GetValPubKeyBech32(pubkey string) (pk crypto.PubKey, err error) { - bz, err := getFromBech32(pubkey, Bech32PrefixValPub) + bz, err := GetFromBech32(pubkey, Bech32PrefixValPub) if err != nil { return nil, err } @@ -98,7 +149,8 @@ func GetValPubKeyBech32(pubkey string) (pk crypto.PubKey, err error) { return pk, nil } -func getFromBech32(bech32str, prefix string) ([]byte, error) { +// decode a bytestring from a bech32-encoded string +func GetFromBech32(bech32str, prefix string) ([]byte, error) { if len(bech32str) == 0 { return nil, errors.New("must provide non-empty string") } diff --git a/types/stake.go b/types/stake.go index 6a620db76..7484295cc 100644 --- a/types/stake.go +++ b/types/stake.go @@ -32,6 +32,7 @@ func BondStatusToString(b BondStatus) string { // validator for a delegated proof of stake system type Validator interface { + GetMoniker() string // moniker of the validator GetStatus() BondStatus // status of the validator GetOwner() Address // owner address to receive/return validators coins GetPubKey() crypto.PubKey // validation pubkey diff --git a/types/wire.go b/types/wire.go index 245b3677c..2ef28820d 100644 --- a/types/wire.go +++ b/types/wire.go @@ -5,4 +5,5 @@ import wire "github.com/cosmos/cosmos-sdk/wire" // Register the sdk message type func RegisterWire(cdc *wire.Codec) { cdc.RegisterInterface((*Msg)(nil), nil) + cdc.RegisterInterface((*Tx)(nil), nil) } diff --git a/version/version.go b/version/version.go index 20074c7bc..e24ef62f1 100644 --- a/version/version.go +++ b/version/version.go @@ -6,10 +6,10 @@ package version // TODO improve const Maj = "0" -const Min = "18" +const Min = "19" const Fix = "0" -const Version = "0.18.0" +const Version = "0.19.0" // GitCommit set by build flags var GitCommit = "" diff --git a/x/auth/account.go b/x/auth/account.go index 0ae72a8a6..77966b8e7 100644 --- a/x/auth/account.go +++ b/x/auth/account.go @@ -17,6 +17,9 @@ type Account interface { GetPubKey() crypto.PubKey // can return nil. SetPubKey(crypto.PubKey) error + GetAccountNumber() int64 + SetAccountNumber(int64) error + GetSequence() int64 SetSequence(int64) error @@ -36,10 +39,11 @@ var _ Account = (*BaseAccount)(nil) // Extend this by embedding this in your AppAccount. // See the examples/basecoin/types/account.go for an example. type BaseAccount struct { - Address sdk.Address `json:"address"` - Coins sdk.Coins `json:"coins"` - PubKey crypto.PubKey `json:"public_key"` - Sequence int64 `json:"sequence"` + Address sdk.Address `json:"address"` + Coins sdk.Coins `json:"coins"` + PubKey crypto.PubKey `json:"public_key"` + AccountNumber int64 `json:"account_number"` + Sequence int64 `json:"sequence"` } func NewBaseAccountWithAddress(addr sdk.Address) BaseAccount { @@ -84,6 +88,17 @@ func (acc *BaseAccount) SetCoins(coins sdk.Coins) error { return nil } +// Implements Account +func (acc *BaseAccount) GetAccountNumber() int64 { + return acc.AccountNumber +} + +// Implements Account +func (acc *BaseAccount) SetAccountNumber(accNumber int64) error { + acc.AccountNumber = accNumber + return nil +} + // Implements sdk.Account. func (acc *BaseAccount) GetSequence() int64 { return acc.Sequence diff --git a/x/auth/ante.go b/x/auth/ante.go index 9663bcfe4..c50da0c32 100644 --- a/x/auth/ante.go +++ b/x/auth/ante.go @@ -14,7 +14,7 @@ const ( ) // NewAnteHandler returns an AnteHandler that checks -// and increments sequence numbers, checks signatures, +// and increments sequence numbers, checks signatures & account numbers, // and deducts fees from the first signer. func NewAnteHandler(am AccountMapper, fck FeeCollectionKeeper) sdk.AnteHandler { @@ -46,11 +46,15 @@ func NewAnteHandler(am AccountMapper, fck FeeCollectionKeeper) sdk.AnteHandler { true } - // Get the sign bytes (requires all sequence numbers and the fee) + // Get the sign bytes (requires all account & sequence numbers and the fee) sequences := make([]int64, len(signerAddrs)) for i := 0; i < len(signerAddrs); i++ { sequences[i] = sigs[i].Sequence } + accNums := make([]int64, len(signerAddrs)) + for i := 0; i < len(signerAddrs); i++ { + accNums[i] = sigs[i].AccountNumber + } fee := stdTx.Fee chainID := ctx.ChainID() // XXX: major hack; need to get ChainID @@ -58,7 +62,7 @@ func NewAnteHandler(am AccountMapper, fck FeeCollectionKeeper) sdk.AnteHandler { if chainID == "" { chainID = viper.GetString("chain-id") } - signBytes := StdSignBytes(ctx.ChainID(), sequences, fee, msg) + signBytes := StdSignBytes(ctx.ChainID(), accNums, sequences, fee, msg) // Check sig and nonce and collect signer accounts. var signerAccs = make([]Account, len(signerAddrs)) @@ -117,6 +121,13 @@ func processSig( return nil, sdk.ErrUnknownAddress(addr.String()).Result() } + // Check account number. + accnum := acc.GetAccountNumber() + if accnum != sig.AccountNumber { + return nil, sdk.ErrInvalidSequence( + fmt.Sprintf("Invalid account number. Got %d, expected %d", sig.AccountNumber, accnum)).Result() + } + // Check and increment sequence number. seq := acc.GetSequence() if seq != sig.Sequence { diff --git a/x/auth/ante_test.go b/x/auth/ante_test.go index b7f22e5d5..aff80cb59 100644 --- a/x/auth/ante_test.go +++ b/x/auth/ante_test.go @@ -52,15 +52,15 @@ func checkInvalidTx(t *testing.T, anteHandler sdk.AnteHandler, ctx sdk.Context, assert.Equal(t, sdk.ToABCICode(sdk.CodespaceRoot, code), result.Code) } -func newTestTx(ctx sdk.Context, msg sdk.Msg, privs []crypto.PrivKey, seqs []int64, fee StdFee) sdk.Tx { - signBytes := StdSignBytes(ctx.ChainID(), seqs, fee, msg) - return newTestTxWithSignBytes(msg, privs, seqs, fee, signBytes) +func newTestTx(ctx sdk.Context, msg sdk.Msg, privs []crypto.PrivKey, accNums []int64, seqs []int64, fee StdFee) sdk.Tx { + signBytes := StdSignBytes(ctx.ChainID(), accNums, seqs, fee, msg) + return newTestTxWithSignBytes(msg, privs, accNums, seqs, fee, signBytes) } -func newTestTxWithSignBytes(msg sdk.Msg, privs []crypto.PrivKey, seqs []int64, fee StdFee, signBytes []byte) sdk.Tx { +func newTestTxWithSignBytes(msg sdk.Msg, privs []crypto.PrivKey, accNums []int64, seqs []int64, fee StdFee, signBytes []byte) sdk.Tx { sigs := make([]StdSignature, len(privs)) for i, priv := range privs { - sigs[i] = StdSignature{PubKey: priv.PubKey(), Signature: priv.Sign(signBytes), Sequence: seqs[i]} + sigs[i] = StdSignature{PubKey: priv.PubKey(), Signature: priv.Sign(signBytes), AccountNumber: accNums[i], Sequence: seqs[i]} } tx := NewStdTx(msg, fee, sigs) return tx @@ -87,18 +87,18 @@ func TestAnteHandlerSigErrors(t *testing.T) { fee := newStdFee() // test no signatures - privs, seqs := []crypto.PrivKey{}, []int64{} - tx = newTestTx(ctx, msg, privs, seqs, fee) + privs, accNums, seqs := []crypto.PrivKey{}, []int64{}, []int64{} + tx = newTestTx(ctx, msg, privs, accNums, seqs, fee) checkInvalidTx(t, anteHandler, ctx, tx, sdk.CodeUnauthorized) // test num sigs dont match GetSigners - privs, seqs = []crypto.PrivKey{priv1}, []int64{0} - tx = newTestTx(ctx, msg, privs, seqs, fee) + privs, accNums, seqs = []crypto.PrivKey{priv1}, []int64{0}, []int64{0} + tx = newTestTx(ctx, msg, privs, accNums, seqs, fee) checkInvalidTx(t, anteHandler, ctx, tx, sdk.CodeUnauthorized) // test an unrecognized account - privs, seqs = []crypto.PrivKey{priv1, priv2}, []int64{0, 0} - tx = newTestTx(ctx, msg, privs, seqs, fee) + privs, accNums, seqs = []crypto.PrivKey{priv1, priv2}, []int64{0, 1}, []int64{0, 0} + tx = newTestTx(ctx, msg, privs, accNums, seqs, fee) checkInvalidTx(t, anteHandler, ctx, tx, sdk.CodeUnknownAddress) // save the first account, but second is still unrecognized @@ -108,6 +108,61 @@ func TestAnteHandlerSigErrors(t *testing.T) { checkInvalidTx(t, anteHandler, ctx, tx, sdk.CodeUnknownAddress) } +// Test logic around account number checking with one signer and many signers. +func TestAnteHandlerAccountNumbers(t *testing.T) { + // setup + ms, capKey, capKey2 := setupMultiStore() + cdc := wire.NewCodec() + RegisterBaseAccount(cdc) + mapper := NewAccountMapper(cdc, capKey, &BaseAccount{}) + feeCollector := NewFeeCollectionKeeper(cdc, capKey2) + anteHandler := NewAnteHandler(mapper, feeCollector) + ctx := sdk.NewContext(ms, abci.Header{ChainID: "mychainid"}, false, nil, log.NewNopLogger()) + + // keys and addresses + priv1, addr1 := privAndAddr() + priv2, addr2 := privAndAddr() + + // set the accounts + acc1 := mapper.NewAccountWithAddress(ctx, addr1) + acc1.SetCoins(newCoins()) + mapper.SetAccount(ctx, acc1) + acc2 := mapper.NewAccountWithAddress(ctx, addr2) + acc2.SetCoins(newCoins()) + mapper.SetAccount(ctx, acc2) + + // msg and signatures + var tx sdk.Tx + msg := newTestMsg(addr1) + fee := newStdFee() + + // test good tx from one signer + privs, accnums, seqs := []crypto.PrivKey{priv1}, []int64{0}, []int64{0} + tx = newTestTx(ctx, msg, privs, accnums, seqs, fee) + checkValidTx(t, anteHandler, ctx, tx) + + // new tx from wrong account number + seqs = []int64{1} + tx = newTestTx(ctx, msg, privs, []int64{1}, seqs, fee) + checkInvalidTx(t, anteHandler, ctx, tx, sdk.CodeInvalidSequence) + + // from correct account number + seqs = []int64{1} + tx = newTestTx(ctx, msg, privs, []int64{0}, seqs, fee) + checkValidTx(t, anteHandler, ctx, tx) + + // new tx with another signer and incorrect account numbers + msg = newTestMsg(addr1, addr2) + privs, accnums, seqs = []crypto.PrivKey{priv1, priv2}, []int64{1, 0}, []int64{2, 0} + tx = newTestTx(ctx, msg, privs, accnums, seqs, fee) + checkInvalidTx(t, anteHandler, ctx, tx, sdk.CodeInvalidSequence) + + // correct account numbers + privs, accnums, seqs = []crypto.PrivKey{priv1, priv2}, []int64{0, 1}, []int64{2, 0} + tx = newTestTx(ctx, msg, privs, accnums, seqs, fee) + checkValidTx(t, anteHandler, ctx, tx) +} + // Test logic around sequence checking with one signer and many signers. func TestAnteHandlerSequences(t *testing.T) { // setup @@ -137,8 +192,8 @@ func TestAnteHandlerSequences(t *testing.T) { fee := newStdFee() // test good tx from one signer - privs, seqs := []crypto.PrivKey{priv1}, []int64{0} - tx = newTestTx(ctx, msg, privs, seqs, fee) + privs, accnums, seqs := []crypto.PrivKey{priv1}, []int64{0}, []int64{0} + tx = newTestTx(ctx, msg, privs, accnums, seqs, fee) checkValidTx(t, anteHandler, ctx, tx) // test sending it again fails (replay protection) @@ -146,13 +201,13 @@ func TestAnteHandlerSequences(t *testing.T) { // fix sequence, should pass seqs = []int64{1} - tx = newTestTx(ctx, msg, privs, seqs, fee) + tx = newTestTx(ctx, msg, privs, accnums, seqs, fee) checkValidTx(t, anteHandler, ctx, tx) // new tx with another signer and correct sequences msg = newTestMsg(addr1, addr2) - privs, seqs = []crypto.PrivKey{priv1, priv2}, []int64{2, 0} - tx = newTestTx(ctx, msg, privs, seqs, fee) + privs, accnums, seqs = []crypto.PrivKey{priv1, priv2}, []int64{0, 1}, []int64{2, 0} + tx = newTestTx(ctx, msg, privs, accnums, seqs, fee) checkValidTx(t, anteHandler, ctx, tx) // replay fails @@ -160,18 +215,18 @@ func TestAnteHandlerSequences(t *testing.T) { // tx from just second signer with incorrect sequence fails msg = newTestMsg(addr2) - privs, seqs = []crypto.PrivKey{priv2}, []int64{0} - tx = newTestTx(ctx, msg, privs, seqs, fee) + privs, accnums, seqs = []crypto.PrivKey{priv2}, []int64{1}, []int64{0} + tx = newTestTx(ctx, msg, privs, accnums, seqs, fee) checkInvalidTx(t, anteHandler, ctx, tx, sdk.CodeInvalidSequence) // fix the sequence and it passes - tx = newTestTx(ctx, msg, []crypto.PrivKey{priv2}, []int64{1}, fee) + tx = newTestTx(ctx, msg, []crypto.PrivKey{priv2}, []int64{1}, []int64{1}, fee) checkValidTx(t, anteHandler, ctx, tx) // another tx from both of them that passes msg = newTestMsg(addr1, addr2) - privs, seqs = []crypto.PrivKey{priv1, priv2}, []int64{3, 2} - tx = newTestTx(ctx, msg, privs, seqs, fee) + privs, accnums, seqs = []crypto.PrivKey{priv1, priv2}, []int64{0, 1}, []int64{3, 2} + tx = newTestTx(ctx, msg, privs, accnums, seqs, fee) checkValidTx(t, anteHandler, ctx, tx) } @@ -196,13 +251,13 @@ func TestAnteHandlerFees(t *testing.T) { // msg and signatures var tx sdk.Tx msg := newTestMsg(addr1) - privs, seqs := []crypto.PrivKey{priv1}, []int64{0} + privs, accnums, seqs := []crypto.PrivKey{priv1}, []int64{0}, []int64{0} fee := NewStdFee(100, sdk.Coin{"atom", 150}, ) // signer does not have enough funds to pay the fee - tx = newTestTx(ctx, msg, privs, seqs, fee) + tx = newTestTx(ctx, msg, privs, accnums, seqs, fee) checkInvalidTx(t, anteHandler, ctx, tx, sdk.CodeInsufficientFunds) acc1.SetCoins(sdk.Coins{{"atom", 149}}) @@ -249,8 +304,8 @@ func TestAnteHandlerBadSignBytes(t *testing.T) { fee3.Amount[0].Amount += 100 // test good tx and signBytes - privs, seqs := []crypto.PrivKey{priv1}, []int64{0} - tx = newTestTx(ctx, msg, privs, seqs, fee) + privs, accnums, seqs := []crypto.PrivKey{priv1}, []int64{0}, []int64{0} + tx = newTestTx(ctx, msg, privs, accnums, seqs, fee) checkValidTx(t, anteHandler, ctx, tx) chainID := ctx.ChainID() @@ -259,37 +314,39 @@ func TestAnteHandlerBadSignBytes(t *testing.T) { cases := []struct { chainID string + accnums []int64 seqs []int64 fee StdFee msg sdk.Msg code sdk.CodeType }{ - {chainID2, []int64{1}, fee, msg, codeUnauth}, // test wrong chain_id - {chainID, []int64{2}, fee, msg, codeUnauth}, // test wrong seqs - {chainID, []int64{1, 2}, fee, msg, codeUnauth}, // test wrong seqs - {chainID, []int64{1}, fee, newTestMsg(addr2), codeUnauth}, // test wrong msg - {chainID, []int64{1}, fee2, newTestMsg(addr2), codeUnauth}, // test wrong fee - {chainID, []int64{1}, fee3, newTestMsg(addr2), codeUnauth}, // test wrong fee + {chainID2, []int64{0}, []int64{1}, fee, msg, codeUnauth}, // test wrong chain_id + {chainID, []int64{0}, []int64{2}, fee, msg, codeUnauth}, // test wrong seqs + {chainID, []int64{0}, []int64{1, 2}, fee, msg, codeUnauth}, // test wrong seqs + {chainID, []int64{1}, []int64{1}, fee, msg, codeUnauth}, // test wrong accnum + {chainID, []int64{0}, []int64{1}, fee, newTestMsg(addr2), codeUnauth}, // test wrong msg + {chainID, []int64{0}, []int64{1}, fee2, msg, codeUnauth}, // test wrong fee + {chainID, []int64{0}, []int64{1}, fee3, msg, codeUnauth}, // test wrong fee } privs, seqs = []crypto.PrivKey{priv1}, []int64{1} for _, cs := range cases { tx := newTestTxWithSignBytes( - msg, privs, seqs, fee, - StdSignBytes(cs.chainID, cs.seqs, cs.fee, cs.msg), + msg, privs, accnums, seqs, fee, + StdSignBytes(cs.chainID, cs.accnums, cs.seqs, cs.fee, cs.msg), ) checkInvalidTx(t, anteHandler, ctx, tx, cs.code) } // test wrong signer if public key exist - privs, seqs = []crypto.PrivKey{priv2}, []int64{1} - tx = newTestTx(ctx, msg, privs, seqs, fee) + privs, accnums, seqs = []crypto.PrivKey{priv2}, []int64{0}, []int64{1} + tx = newTestTx(ctx, msg, privs, accnums, seqs, fee) checkInvalidTx(t, anteHandler, ctx, tx, sdk.CodeUnauthorized) // test wrong signer if public doesn't exist msg = newTestMsg(addr2) - privs, seqs = []crypto.PrivKey{priv1}, []int64{0} - tx = newTestTx(ctx, msg, privs, seqs, fee) + privs, accnums, seqs = []crypto.PrivKey{priv1}, []int64{1}, []int64{0} + tx = newTestTx(ctx, msg, privs, accnums, seqs, fee) checkInvalidTx(t, anteHandler, ctx, tx, sdk.CodeInvalidPubKey) } @@ -320,9 +377,9 @@ func TestAnteHandlerSetPubKey(t *testing.T) { // test good tx and set public key msg := newTestMsg(addr1) - privs, seqs := []crypto.PrivKey{priv1}, []int64{0} + privs, accnums, seqs := []crypto.PrivKey{priv1}, []int64{0}, []int64{0} fee := newStdFee() - tx = newTestTx(ctx, msg, privs, seqs, fee) + tx = newTestTx(ctx, msg, privs, accnums, seqs, fee) checkValidTx(t, anteHandler, ctx, tx) acc1 = mapper.GetAccount(ctx, addr1) @@ -330,7 +387,7 @@ func TestAnteHandlerSetPubKey(t *testing.T) { // test public key not found msg = newTestMsg(addr2) - tx = newTestTx(ctx, msg, privs, seqs, fee) + tx = newTestTx(ctx, msg, privs, []int64{1}, seqs, fee) sigs := tx.(StdTx).GetSignatures() sigs[0].PubKey = nil checkInvalidTx(t, anteHandler, ctx, tx, sdk.CodeInvalidPubKey) @@ -339,7 +396,7 @@ func TestAnteHandlerSetPubKey(t *testing.T) { assert.Nil(t, acc2.GetPubKey()) // test invalid signature and public key - tx = newTestTx(ctx, msg, privs, seqs, fee) + tx = newTestTx(ctx, msg, privs, []int64{1}, seqs, fee) checkInvalidTx(t, anteHandler, ctx, tx, sdk.CodeInvalidPubKey) acc2 = mapper.GetAccount(ctx, addr2) diff --git a/x/auth/client/cli/account.go b/x/auth/client/cli/account.go index e82b07414..a3265a78c 100644 --- a/x/auth/client/cli/account.go +++ b/x/auth/client/cli/account.go @@ -47,7 +47,7 @@ func GetAccountCmd(storeName string, cdc *wire.Codec, decoder auth.AccountDecode // perform query ctx := context.NewCoreContextFromViper() - res, err := ctx.Query(key, storeName) + res, err := ctx.Query(auth.AddressStoreKey(key), storeName) if err != nil { return err } diff --git a/x/auth/client/rest/query.go b/x/auth/client/rest/query.go index a60ce5cdb..9ccbe8e14 100644 --- a/x/auth/client/rest/query.go +++ b/x/auth/client/rest/query.go @@ -1,7 +1,6 @@ package rest import ( - "encoding/hex" "fmt" "net/http" @@ -26,17 +25,16 @@ func RegisterRoutes(ctx context.CoreContext, r *mux.Router, cdc *wire.Codec, sto func QueryAccountRequestHandlerFn(storeName string, cdc *wire.Codec, decoder auth.AccountDecoder, ctx context.CoreContext) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { vars := mux.Vars(r) - addr := vars["address"] + bech32addr := vars["address"] - bz, err := hex.DecodeString(addr) + addr, err := sdk.GetAccAddressBech32(bech32addr) if err != nil { w.WriteHeader(http.StatusBadRequest) w.Write([]byte(err.Error())) return } - key := sdk.Address(bz) - res, err := ctx.Query(key, storeName) + res, err := ctx.Query(auth.AddressStoreKey(addr), storeName) if err != nil { w.WriteHeader(http.StatusInternalServerError) w.Write([]byte(fmt.Sprintf("Could't query account. Error: %s", err.Error()))) diff --git a/x/auth/mapper.go b/x/auth/mapper.go index cdab2480e..b4364f768 100644 --- a/x/auth/mapper.go +++ b/x/auth/mapper.go @@ -9,6 +9,8 @@ import ( crypto "github.com/tendermint/go-crypto" ) +var globalAccountNumberKey = []byte("globalAccountNumber") + // This AccountMapper encodes/decodes accounts using the // go-amino (binary) encoding/decoding library. type AccountMapper struct { @@ -38,13 +40,25 @@ func NewAccountMapper(cdc *wire.Codec, key sdk.StoreKey, proto Account) AccountM func (am AccountMapper) NewAccountWithAddress(ctx sdk.Context, addr sdk.Address) Account { acc := am.clonePrototype() acc.SetAddress(addr) + acc.SetAccountNumber(am.GetNextAccountNumber(ctx)) return acc } +// New Account +func (am AccountMapper) NewAccount(ctx sdk.Context, acc Account) Account { + acc.SetAccountNumber(am.GetNextAccountNumber(ctx)) + return acc +} + +// Turn an address to key used to get it from the account store +func AddressStoreKey(addr sdk.Address) []byte { + return append([]byte("account:"), addr.Bytes()...) +} + // Implements sdk.AccountMapper. func (am AccountMapper) GetAccount(ctx sdk.Context, addr sdk.Address) Account { store := ctx.KVStore(am.key) - bz := store.Get(addr) + bz := store.Get(AddressStoreKey(addr)) if bz == nil { return nil } @@ -57,13 +71,13 @@ func (am AccountMapper) SetAccount(ctx sdk.Context, acc Account) { addr := acc.GetAddress() store := ctx.KVStore(am.key) bz := am.encodeAccount(acc) - store.Set(addr, bz) + store.Set(AddressStoreKey(addr), bz) } // Implements sdk.AccountMapper. func (am AccountMapper) IterateAccounts(ctx sdk.Context, process func(Account) (stop bool)) { store := ctx.KVStore(am.key) - iter := store.Iterator(nil, nil) + iter := sdk.KVStorePrefixIterator(store, []byte("account:")) for { if !iter.Valid() { return @@ -116,6 +130,26 @@ func (am AccountMapper) setSequence(ctx sdk.Context, addr sdk.Address, newSequen return nil } +// Returns and increments the global account number counter +func (am AccountMapper) GetNextAccountNumber(ctx sdk.Context) int64 { + var accNumber int64 + store := ctx.KVStore(am.key) + bz := store.Get(globalAccountNumberKey) + if bz == nil { + accNumber = 0 + } else { + err := am.cdc.UnmarshalBinary(bz, &accNumber) + if err != nil { + panic(err) + } + } + + bz = am.cdc.MustMarshalBinary(accNumber + 1) + store.Set(globalAccountNumberKey, bz) + + return accNumber +} + //---------------------------------------- // misc. diff --git a/x/auth/mock/app.go b/x/auth/mock/app.go new file mode 100644 index 000000000..953008807 --- /dev/null +++ b/x/auth/mock/app.go @@ -0,0 +1,88 @@ +package mock + +import ( + "testing" + + "os" + + "github.com/stretchr/testify/require" + abci "github.com/tendermint/abci/types" + dbm "github.com/tendermint/tmlibs/db" + "github.com/tendermint/tmlibs/log" + + bam "github.com/cosmos/cosmos-sdk/baseapp" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/wire" + "github.com/cosmos/cosmos-sdk/x/auth" +) + +// Extended ABCI application +type App struct { + *bam.BaseApp + Cdc *wire.Codec // public since the codec is passed into the module anyways. + KeyMain *sdk.KVStoreKey + KeyAccount *sdk.KVStoreKey + + // TODO: Abstract this out from not needing to be auth specifically + AccountMapper auth.AccountMapper + FeeCollectionKeeper auth.FeeCollectionKeeper + + GenesisAccounts []auth.Account +} + +// partially construct a new app on the memstore for module and genesis testing +func NewApp() *App { + logger := log.NewTMLogger(log.NewSyncWriter(os.Stdout)).With("module", "sdk/app") + db := dbm.NewMemDB() + + // create the cdc with some standard codecs + cdc := wire.NewCodec() + sdk.RegisterWire(cdc) + wire.RegisterCrypto(cdc) + auth.RegisterWire(cdc) + + // create your application object + app := &App{ + BaseApp: bam.NewBaseApp("mock", cdc, logger, db), + Cdc: cdc, + KeyMain: sdk.NewKVStoreKey("main"), + KeyAccount: sdk.NewKVStoreKey("acc"), + } + + // define the accountMapper + app.AccountMapper = auth.NewAccountMapper( + app.Cdc, + app.KeyAccount, // target store + &auth.BaseAccount{}, // prototype + ) + + // initialize the app, the chainers and blockers can be overwritten before calling complete setup + app.SetInitChainer(app.InitChainer) + + app.SetAnteHandler(auth.NewAnteHandler(app.AccountMapper, app.FeeCollectionKeeper)) + + return app +} + +// complete the application setup after the routes have been registered +func (app *App) CompleteSetup(t *testing.T, newKeys []*sdk.KVStoreKey) { + + newKeys = append(newKeys, app.KeyMain) + newKeys = append(newKeys, app.KeyAccount) + app.MountStoresIAVL(newKeys...) + err := app.LoadLatestVersion(app.KeyMain) + require.NoError(t, err) +} + +// custom logic for initialization +func (app *App) InitChainer(ctx sdk.Context, _ abci.RequestInitChain) abci.ResponseInitChain { + + // load the accounts + for _, genacc := range app.GenesisAccounts { + acc := app.AccountMapper.NewAccountWithAddress(ctx, genacc.GetAddress()) + acc.SetCoins(genacc.GetCoins()) + app.AccountMapper.SetAccount(ctx, acc) + } + + return abci.ResponseInitChain{} +} diff --git a/x/auth/mock/auth_app_test.go b/x/auth/mock/auth_app_test.go new file mode 100644 index 000000000..bb4d7007b --- /dev/null +++ b/x/auth/mock/auth_app_test.go @@ -0,0 +1,97 @@ +package mock + +import ( + "testing" + + "github.com/stretchr/testify/assert" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/auth" + "github.com/cosmos/cosmos-sdk/x/bank" + + abci "github.com/tendermint/abci/types" + crypto "github.com/tendermint/go-crypto" +) + +// test auth module messages + +var ( + priv1 = crypto.GenPrivKeyEd25519() + addr1 = priv1.PubKey().Address() + priv2 = crypto.GenPrivKeyEd25519() + addr2 = priv2.PubKey().Address() + + coins = sdk.Coins{{"foocoin", 10}} + sendMsg1 = bank.MsgSend{ + Inputs: []bank.Input{bank.NewInput(addr1, coins)}, + Outputs: []bank.Output{bank.NewOutput(addr2, coins)}, + } +) + +// initialize the mock application for this module +func getMockApp(t *testing.T) *App { + mapp := NewApp() + + coinKeeper := bank.NewKeeper(mapp.AccountMapper) + mapp.Router().AddRoute("bank", bank.NewHandler(coinKeeper)) + mapp.Router().AddRoute("auth", auth.NewHandler(mapp.AccountMapper)) + + mapp.CompleteSetup(t, []*sdk.KVStoreKey{}) + return mapp +} + +func TestMsgChangePubKey(t *testing.T) { + mapp := getMockApp(t) + + // Construct some genesis bytes to reflect basecoin/types/AppAccount + // Give 77 foocoin to the first key + coins := sdk.Coins{{"foocoin", 77}} + acc1 := &auth.BaseAccount{ + Address: addr1, + Coins: coins, + } + accs := []auth.Account{acc1} + + // Construct genesis state + SetGenesis(mapp, accs) + + // A checkTx context (true) + ctxCheck := mapp.BaseApp.NewContext(true, abci.Header{}) + res1 := mapp.AccountMapper.GetAccount(ctxCheck, addr1) + assert.Equal(t, acc1, res1.(*auth.BaseAccount)) + + // Run a CheckDeliver + SignCheckDeliver(t, mapp.BaseApp, sendMsg1, []int64{0}, []int64{0}, true, priv1) + + // Check balances + CheckBalance(t, mapp, addr1, sdk.Coins{{"foocoin", 67}}) + CheckBalance(t, mapp, addr2, sdk.Coins{{"foocoin", 10}}) + + changePubKeyMsg := auth.MsgChangeKey{ + Address: addr1, + NewPubKey: priv2.PubKey(), + } + + mapp.BeginBlock(abci.RequestBeginBlock{}) + ctxDeliver := mapp.BaseApp.NewContext(false, abci.Header{}) + acc2 := mapp.AccountMapper.GetAccount(ctxDeliver, addr1) + + // send a MsgChangePubKey + SignCheckDeliver(t, mapp.BaseApp, changePubKeyMsg, []int64{0}, []int64{1}, true, priv1) + acc2 = mapp.AccountMapper.GetAccount(ctxDeliver, addr1) + + assert.True(t, priv2.PubKey().Equals(acc2.GetPubKey())) + + // signing a SendMsg with the old privKey should be an auth error + mapp.BeginBlock(abci.RequestBeginBlock{}) + tx := GenTx(sendMsg1, []int64{0}, []int64{2}, priv1) + res := mapp.Deliver(tx) + assert.Equal(t, sdk.ToABCICode(sdk.CodespaceRoot, sdk.CodeUnauthorized), res.Code, res.Log) + + // resigning the tx with the new correct priv key should work + SignCheckDeliver(t, mapp.BaseApp, sendMsg1, []int64{0}, []int64{2}, true, priv2) + + // Check balances + CheckBalance(t, mapp, addr1, sdk.Coins{{"foocoin", 57}}) + CheckBalance(t, mapp, addr2, sdk.Coins{{"foocoin", 20}}) +} diff --git a/x/auth/mock/simulate_block.go b/x/auth/mock/simulate_block.go new file mode 100644 index 000000000..7a77e0f09 --- /dev/null +++ b/x/auth/mock/simulate_block.go @@ -0,0 +1,88 @@ +package mock + +import ( + "testing" + + "github.com/cosmos/cosmos-sdk/baseapp" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/auth" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + crypto "github.com/tendermint/go-crypto" + + abci "github.com/tendermint/abci/types" +) + +var chainID = "" // TODO + +// set the mock app genesis +func SetGenesis(app *App, accs []auth.Account) { + + // pass the accounts in via the application (lazy) instead of through RequestInitChain + app.GenesisAccounts = accs + + app.InitChain(abci.RequestInitChain{}) + app.Commit() +} + +// check an account balance +func CheckBalance(t *testing.T, app *App, addr sdk.Address, exp sdk.Coins) { + ctxCheck := app.BaseApp.NewContext(true, abci.Header{}) + res := app.AccountMapper.GetAccount(ctxCheck, addr) + assert.Equal(t, exp, res.GetCoins()) +} + +// generate a signed transaction +func GenTx(msg sdk.Msg, accnums []int64, seq []int64, priv ...crypto.PrivKeyEd25519) auth.StdTx { + + // make the transaction free + fee := auth.StdFee{ + sdk.Coins{{"foocoin", 0}}, + 100000, + } + + sigs := make([]auth.StdSignature, len(priv)) + for i, p := range priv { + sigs[i] = auth.StdSignature{ + PubKey: p.PubKey(), + Signature: p.Sign(auth.StdSignBytes(chainID, accnums, seq, fee, msg)), + AccountNumber: accnums[i], + Sequence: seq[i], + } + } + return auth.NewStdTx(msg, fee, sigs) +} + +// check a transaction result +func SignCheck(t *testing.T, app *baseapp.BaseApp, msg sdk.Msg, accnums []int64, seq []int64, priv ...crypto.PrivKeyEd25519) sdk.Result { + tx := GenTx(msg, accnums, seq, priv...) + res := app.Check(tx) + return res +} + +// simulate a block +func SignCheckDeliver(t *testing.T, app *baseapp.BaseApp, msg sdk.Msg, accnums []int64, seq []int64, expPass bool, priv ...crypto.PrivKeyEd25519) { + + // Sign the tx + tx := GenTx(msg, accnums, seq, priv...) + + // Run a Check + res := app.Check(tx) + if expPass { + require.Equal(t, sdk.ABCICodeOK, res.Code, res.Log) + } else { + require.NotEqual(t, sdk.ABCICodeOK, res.Code, res.Log) + } + + // Simulate a Block + app.BeginBlock(abci.RequestBeginBlock{}) + res = app.Deliver(tx) + if expPass { + require.Equal(t, sdk.ABCICodeOK, res.Code, res.Log) + } else { + require.NotEqual(t, sdk.ABCICodeOK, res.Code, res.Log) + } + app.EndBlock(abci.RequestEndBlock{}) + + app.Commit() +} diff --git a/x/auth/stdtx.go b/x/auth/stdtx.go index 4858ae0b4..5c43a3717 100644 --- a/x/auth/stdtx.go +++ b/x/auth/stdtx.go @@ -1,6 +1,8 @@ package auth import ( + "encoding/json" + sdk "github.com/cosmos/cosmos-sdk/types" crypto "github.com/tendermint/go-crypto" ) @@ -83,21 +85,23 @@ func (fee StdFee) Bytes() []byte { // and the Sequence numbers for each signature (prevent // inchain replay and enforce tx ordering per account). type StdSignDoc struct { - ChainID string `json:"chain_id"` - Sequences []int64 `json:"sequences"` - FeeBytes []byte `json:"fee_bytes"` - MsgBytes []byte `json:"msg_bytes"` - AltBytes []byte `json:"alt_bytes"` + ChainID string `json:"chain_id"` + AccountNumbers []int64 `json:"account_numbers"` + Sequences []int64 `json:"sequences"` + FeeBytes []byte `json:"fee_bytes"` + MsgBytes []byte `json:"msg_bytes"` + AltBytes []byte `json:"alt_bytes"` } // StdSignBytes returns the bytes to sign for a transaction. // TODO: change the API to just take a chainID and StdTx ? -func StdSignBytes(chainID string, sequences []int64, fee StdFee, msg sdk.Msg) []byte { - bz, err := msgCdc.MarshalJSON(StdSignDoc{ - ChainID: chainID, - Sequences: sequences, - FeeBytes: fee.Bytes(), - MsgBytes: msg.GetSignBytes(), +func StdSignBytes(chainID string, accnums []int64, sequences []int64, fee StdFee, msg sdk.Msg) []byte { + bz, err := json.Marshal(StdSignDoc{ + ChainID: chainID, + AccountNumbers: accnums, + Sequences: sequences, + FeeBytes: fee.Bytes(), + MsgBytes: msg.GetSignBytes(), }) if err != nil { panic(err) @@ -109,21 +113,23 @@ func StdSignBytes(chainID string, sequences []int64, fee StdFee, msg sdk.Msg) [] // a Msg with the other requirements for a StdSignDoc before // it is signed. For use in the CLI. type StdSignMsg struct { - ChainID string - Sequences []int64 - Fee StdFee - Msg sdk.Msg + ChainID string + AccountNumbers []int64 + Sequences []int64 + Fee StdFee + Msg sdk.Msg // XXX: Alt } // get message bytes func (msg StdSignMsg) Bytes() []byte { - return StdSignBytes(msg.ChainID, msg.Sequences, msg.Fee, msg.Msg) + return StdSignBytes(msg.ChainID, msg.AccountNumbers, msg.Sequences, msg.Fee, msg.Msg) } // Standard Signature type StdSignature struct { crypto.PubKey `json:"pub_key"` // optional crypto.Signature `json:"signature"` + AccountNumber int64 `json:"account_number"` Sequence int64 `json:"sequence"` } diff --git a/x/auth/wire.go b/x/auth/wire.go index 309464c86..6e430be4c 100644 --- a/x/auth/wire.go +++ b/x/auth/wire.go @@ -9,6 +9,7 @@ func RegisterWire(cdc *wire.Codec) { cdc.RegisterInterface((*Account)(nil), nil) cdc.RegisterConcrete(&BaseAccount{}, "auth/Account", nil) cdc.RegisterConcrete(MsgChangeKey{}, "auth/ChangeKey", nil) + cdc.RegisterConcrete(StdTx{}, "auth/StdTx", nil) } var msgCdc = wire.NewCodec() diff --git a/x/bank/app_test.go b/x/bank/app_test.go new file mode 100644 index 000000000..d0c112b3d --- /dev/null +++ b/x/bank/app_test.go @@ -0,0 +1,208 @@ +package bank + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "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/auth/mock" + + abci "github.com/tendermint/abci/types" + crypto "github.com/tendermint/go-crypto" +) + +// test bank module in a mock application +var ( + priv1 = crypto.GenPrivKeyEd25519() + addr1 = priv1.PubKey().Address() + priv2 = crypto.GenPrivKeyEd25519() + addr2 = priv2.PubKey().Address() + addr3 = crypto.GenPrivKeyEd25519().PubKey().Address() + priv4 = crypto.GenPrivKeyEd25519() + addr4 = priv4.PubKey().Address() + coins = sdk.Coins{{"foocoin", 10}} + halfCoins = sdk.Coins{{"foocoin", 5}} + manyCoins = sdk.Coins{{"foocoin", 1}, {"barcoin", 1}} + + freeFee = auth.StdFee{ // no fees for a buncha gas + sdk.Coins{{"foocoin", 0}}, + 100000, + } + + sendMsg1 = MsgSend{ + Inputs: []Input{NewInput(addr1, coins)}, + Outputs: []Output{NewOutput(addr2, coins)}, + } + + sendMsg2 = MsgSend{ + Inputs: []Input{NewInput(addr1, coins)}, + Outputs: []Output{ + NewOutput(addr2, halfCoins), + NewOutput(addr3, halfCoins), + }, + } + + sendMsg3 = MsgSend{ + Inputs: []Input{ + NewInput(addr1, coins), + NewInput(addr4, coins), + }, + Outputs: []Output{ + NewOutput(addr2, coins), + NewOutput(addr3, coins), + }, + } + + sendMsg4 = MsgSend{ + Inputs: []Input{ + NewInput(addr2, coins), + }, + Outputs: []Output{ + NewOutput(addr1, coins), + }, + } + + sendMsg5 = MsgSend{ + Inputs: []Input{ + NewInput(addr1, manyCoins), + }, + Outputs: []Output{ + NewOutput(addr2, manyCoins), + }, + } +) + +// initialize the mock application for this module +func getMockApp(t *testing.T) *mock.App { + mapp := mock.NewApp() + + RegisterWire(mapp.Cdc) + coinKeeper := NewKeeper(mapp.AccountMapper) + mapp.Router().AddRoute("bank", NewHandler(coinKeeper)) + + mapp.CompleteSetup(t, []*sdk.KVStoreKey{}) + return mapp +} + +func TestMsgSendWithAccounts(t *testing.T) { + mapp := getMockApp(t) + + // Add an account at genesis + acc := &auth.BaseAccount{ + Address: addr1, + Coins: sdk.Coins{{"foocoin", 67}}, + } + accs := []auth.Account{acc} + + // Construct genesis state + mock.SetGenesis(mapp, accs) + + // A checkTx context (true) + ctxCheck := mapp.BaseApp.NewContext(true, abci.Header{}) + res1 := mapp.AccountMapper.GetAccount(ctxCheck, addr1) + require.NotNil(t, res1) + assert.Equal(t, acc, res1.(*auth.BaseAccount)) + + // Run a CheckDeliver + mock.SignCheckDeliver(t, mapp.BaseApp, sendMsg1, []int64{0}, []int64{0}, true, priv1) + + // Check balances + mock.CheckBalance(t, mapp, addr1, sdk.Coins{{"foocoin", 57}}) + mock.CheckBalance(t, mapp, addr2, sdk.Coins{{"foocoin", 10}}) + + // Delivering again should cause replay error + mock.SignCheckDeliver(t, mapp.BaseApp, sendMsg1, []int64{0}, []int64{0}, false, priv1) + + // bumping the txnonce number without resigning should be an auth error + mapp.BeginBlock(abci.RequestBeginBlock{}) + tx := mock.GenTx(sendMsg1, []int64{0}, []int64{0}, priv1) + tx.Signatures[0].Sequence = 1 + res := mapp.Deliver(tx) + + assert.Equal(t, sdk.ToABCICode(sdk.CodespaceRoot, sdk.CodeUnauthorized), res.Code, res.Log) + + // resigning the tx with the bumped sequence should work + mock.SignCheckDeliver(t, mapp.BaseApp, sendMsg1, []int64{0}, []int64{1}, true, priv1) +} + +func TestMsgSendMultipleOut(t *testing.T) { + mapp := getMockApp(t) + + acc1 := &auth.BaseAccount{ + Address: addr1, + Coins: sdk.Coins{{"foocoin", 42}}, + } + + acc2 := &auth.BaseAccount{ + Address: addr2, + Coins: sdk.Coins{{"foocoin", 42}}, + } + accs := []auth.Account{acc1, acc2} + + mock.SetGenesis(mapp, accs) + + // Simulate a Block + mock.SignCheckDeliver(t, mapp.BaseApp, sendMsg2, []int64{0}, []int64{0}, true, priv1) + + // Check balances + mock.CheckBalance(t, mapp, addr1, sdk.Coins{{"foocoin", 32}}) + mock.CheckBalance(t, mapp, addr2, sdk.Coins{{"foocoin", 47}}) + mock.CheckBalance(t, mapp, addr3, sdk.Coins{{"foocoin", 5}}) +} + +func TestSengMsgMultipleInOut(t *testing.T) { + mapp := getMockApp(t) + + acc1 := &auth.BaseAccount{ + Address: addr1, + Coins: sdk.Coins{{"foocoin", 42}}, + } + acc2 := &auth.BaseAccount{ + Address: addr2, + Coins: sdk.Coins{{"foocoin", 42}}, + } + acc4 := &auth.BaseAccount{ + Address: addr4, + Coins: sdk.Coins{{"foocoin", 42}}, + } + accs := []auth.Account{acc1, acc2, acc4} + + mock.SetGenesis(mapp, accs) + + // CheckDeliver + mock.SignCheckDeliver(t, mapp.BaseApp, sendMsg3, []int64{0, 2}, []int64{0, 0}, true, priv1, priv4) + + // Check balances + mock.CheckBalance(t, mapp, addr1, sdk.Coins{{"foocoin", 32}}) + mock.CheckBalance(t, mapp, addr4, sdk.Coins{{"foocoin", 32}}) + mock.CheckBalance(t, mapp, addr2, sdk.Coins{{"foocoin", 52}}) + mock.CheckBalance(t, mapp, addr3, sdk.Coins{{"foocoin", 10}}) +} + +func TestMsgSendDependent(t *testing.T) { + mapp := getMockApp(t) + + acc1 := &auth.BaseAccount{ + Address: addr1, + Coins: sdk.Coins{{"foocoin", 42}}, + } + accs := []auth.Account{acc1} + + mock.SetGenesis(mapp, accs) + + // CheckDeliver + mock.SignCheckDeliver(t, mapp.BaseApp, sendMsg1, []int64{0}, []int64{0}, true, priv1) + + // Check balances + mock.CheckBalance(t, mapp, addr1, sdk.Coins{{"foocoin", 32}}) + mock.CheckBalance(t, mapp, addr2, sdk.Coins{{"foocoin", 10}}) + + // Simulate a Block + mock.SignCheckDeliver(t, mapp.BaseApp, sendMsg4, []int64{1}, []int64{0}, true, priv2) + + // Check balances + mock.CheckBalance(t, mapp, addr1, sdk.Coins{{"foocoin", 42}}) +} diff --git a/x/bank/client/rest/sendtx.go b/x/bank/client/rest/sendtx.go index defed3f14..2639d2788 100644 --- a/x/bank/client/rest/sendtx.go +++ b/x/bank/client/rest/sendtx.go @@ -27,7 +27,9 @@ type sendBody struct { LocalAccountName string `json:"name"` Password string `json:"password"` ChainID string `json:"chain_id"` + AccountNumber int64 `json:"account_number"` Sequence int64 `json:"sequence"` + Gas int64 `json:"gas"` } var msgCdc = wire.NewCodec() @@ -41,7 +43,14 @@ func SendRequestHandlerFn(cdc *wire.Codec, kb keys.Keybase, ctx context.CoreCont return func(w http.ResponseWriter, r *http.Request) { // collect data vars := mux.Vars(r) - address := vars["address"] + bech32addr := vars["address"] + + address, err := sdk.GetAccAddressBech32(bech32addr) + if err != nil { + w.WriteHeader(http.StatusBadRequest) + w.Write([]byte(err.Error())) + return + } var m sendBody body, err := ioutil.ReadAll(r.Body) @@ -64,7 +73,7 @@ func SendRequestHandlerFn(cdc *wire.Codec, kb keys.Keybase, ctx context.CoreCont return } - to, err := sdk.GetAccAddressHex(address) + to, err := sdk.GetAccAddressHex(address.String()) if err != nil { w.WriteHeader(http.StatusBadRequest) w.Write([]byte(err.Error())) @@ -79,7 +88,11 @@ func SendRequestHandlerFn(cdc *wire.Codec, kb keys.Keybase, ctx context.CoreCont return } + // add gas to context + ctx = ctx.WithGas(m.Gas) + // sign + ctx = ctx.WithAccountNumber(m.AccountNumber) ctx = ctx.WithSequence(m.Sequence) txBytes, err := ctx.SignAndBuild(m.LocalAccountName, m.Password, msg, cdc) if err != nil { diff --git a/x/bank/keeper.go b/x/bank/keeper.go index b14da4d81..71c884ffe 100644 --- a/x/bank/keeper.go +++ b/x/bank/keeper.go @@ -151,7 +151,7 @@ func subtractCoins(ctx sdk.Context, am auth.AccountMapper, addr sdk.Address, amt return amt, nil, sdk.ErrInsufficientCoins(fmt.Sprintf("%s < %s", oldCoins, amt)) } err := setCoins(ctx, am, addr, newCoins) - tags := sdk.NewTags("sender", addr.Bytes()) + tags := sdk.NewTags("sender", []byte(addr.String())) return newCoins, tags, err } @@ -164,7 +164,7 @@ func addCoins(ctx sdk.Context, am auth.AccountMapper, addr sdk.Address, amt sdk. return amt, nil, sdk.ErrInsufficientCoins(fmt.Sprintf("%s < %s", oldCoins, amt)) } err := setCoins(ctx, am, addr, newCoins) - tags := sdk.NewTags("recipient", addr.Bytes()) + tags := sdk.NewTags("recipient", []byte(addr.String())) return newCoins, tags, err } diff --git a/x/bank/msgs.go b/x/bank/msgs.go index 7836056de..48e62d835 100644 --- a/x/bank/msgs.go +++ b/x/bank/msgs.go @@ -1,6 +1,8 @@ package bank import ( + "encoding/json" + sdk "github.com/cosmos/cosmos-sdk/types" ) @@ -53,7 +55,20 @@ func (msg MsgSend) ValidateBasic() sdk.Error { // Implements Msg. func (msg MsgSend) GetSignBytes() []byte { - b, err := msgCdc.MarshalJSON(msg) // XXX: ensure some canonical form + var inputs, outputs []json.RawMessage + for _, input := range msg.Inputs { + inputs = append(inputs, input.GetSignBytes()) + } + for _, output := range msg.Outputs { + outputs = append(outputs, output.GetSignBytes()) + } + b, err := msgCdc.MarshalJSON(struct { + Inputs []json.RawMessage `json:"inputs"` + Outputs []json.RawMessage `json:"outputs"` + }{ + Inputs: inputs, + Outputs: outputs, + }) if err != nil { panic(err) } @@ -78,6 +93,8 @@ type MsgIssue struct { Outputs []Output `json:"outputs"` } +var _ sdk.Msg = MsgIssue{} + // NewMsgIssue - construct arbitrary multi-in, multi-out send msg. func NewMsgIssue(banker sdk.Address, out []Output) MsgIssue { return MsgIssue{Banker: banker, Outputs: out} @@ -102,7 +119,17 @@ func (msg MsgIssue) ValidateBasic() sdk.Error { // Implements Msg. func (msg MsgIssue) GetSignBytes() []byte { - b, err := msgCdc.MarshalJSON(msg) // XXX: ensure some canonical form + var outputs []json.RawMessage + for _, output := range msg.Outputs { + outputs = append(outputs, output.GetSignBytes()) + } + b, err := msgCdc.MarshalJSON(struct { + Banker string `json:"banker"` + Outputs []json.RawMessage `json:"outputs"` + }{ + Banker: sdk.MustBech32ifyAcc(msg.Banker), + Outputs: outputs, + }) if err != nil { panic(err) } @@ -123,6 +150,21 @@ type Input struct { Coins sdk.Coins `json:"coins"` } +// Return bytes to sign for Input +func (in Input) GetSignBytes() []byte { + bin, err := msgCdc.MarshalJSON(struct { + Address string `json:"address"` + Coins sdk.Coins `json:"coins"` + }{ + Address: sdk.MustBech32ifyAcc(in.Address), + Coins: in.Coins, + }) + if err != nil { + panic(err) + } + return bin +} + // ValidateBasic - validate transaction input func (in Input) ValidateBasic() sdk.Error { if len(in.Address) == 0 { @@ -155,6 +197,21 @@ type Output struct { Coins sdk.Coins `json:"coins"` } +// Return bytes to sign for Output +func (out Output) GetSignBytes() []byte { + bin, err := msgCdc.MarshalJSON(struct { + Address string `json:"address"` + Coins sdk.Coins `json:"coins"` + }{ + Address: sdk.MustBech32ifyAcc(out.Address), + Coins: out.Coins, + }) + if err != nil { + panic(err) + } + return bin +} + // ValidateBasic - validate transaction output func (out Output) ValidateBasic() sdk.Error { if len(out.Address) == 0 { diff --git a/x/bank/msgs_test.go b/x/bank/msgs_test.go index 8f9791c8d..90b4869db 100644 --- a/x/bank/msgs_test.go +++ b/x/bank/msgs_test.go @@ -187,12 +187,7 @@ func TestMsgSendGetSignBytes(t *testing.T) { } res := msg.GetSignBytes() - unmarshaledMsg := &MsgSend{} - msgCdc.UnmarshalJSON(res, unmarshaledMsg) - assert.Equal(t, &msg, unmarshaledMsg) - - // TODO bad results - expected := `{"type":"EAFDE32A2C87F8","value":{"inputs":[{"address":"696E707574","coins":[{"denom":"atom","amount":10}]}],"outputs":[{"address":"6F7574707574","coins":[{"denom":"atom","amount":10}]}]}}` + expected := `{"inputs":[{"address":"cosmosaccaddr1d9h8qat5e4ehc5","coins":[{"denom":"atom","amount":10}]}],"outputs":[{"address":"cosmosaccaddr1da6hgur4wse3jx32","coins":[{"denom":"atom","amount":10}]}]}` assert.Equal(t, expected, string(res)) } @@ -262,12 +257,7 @@ func TestMsgIssueGetSignBytes(t *testing.T) { } res := msg.GetSignBytes() - unmarshaledMsg := &MsgIssue{} - msgCdc.UnmarshalJSON(res, unmarshaledMsg) - assert.Equal(t, &msg, unmarshaledMsg) - - // TODO bad results - expected := `{"type":"72E617C06ABAD0","value":{"banker":"696E707574","outputs":[{"address":"6C6F616E2D66726F6D2D62616E6B","coins":[{"denom":"atom","amount":10}]}]}}` + expected := `{"banker":"cosmosaccaddr1d9h8qat5e4ehc5","outputs":[{"address":"cosmosaccaddr1d3hkzm3dveex7mfdvfsku6cwsauqd","coins":[{"denom":"atom","amount":10}]}]}` assert.Equal(t, expected, string(res)) } diff --git a/x/ibc/app_test.go b/x/ibc/app_test.go new file mode 100644 index 000000000..90d426882 --- /dev/null +++ b/x/ibc/app_test.go @@ -0,0 +1,79 @@ +package ibc + +import ( + "testing" + + "github.com/stretchr/testify/assert" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/auth" + "github.com/cosmos/cosmos-sdk/x/auth/mock" + "github.com/cosmos/cosmos-sdk/x/bank" + + abci "github.com/tendermint/abci/types" + crypto "github.com/tendermint/go-crypto" +) + +// initialize the mock application for this module +func getMockApp(t *testing.T) *mock.App { + mapp := mock.NewApp() + + RegisterWire(mapp.Cdc) + keyIBC := sdk.NewKVStoreKey("ibc") + ibcMapper := NewMapper(mapp.Cdc, keyIBC, mapp.RegisterCodespace(DefaultCodespace)) + coinKeeper := bank.NewKeeper(mapp.AccountMapper) + mapp.Router().AddRoute("ibc", NewHandler(ibcMapper, coinKeeper)) + + mapp.CompleteSetup(t, []*sdk.KVStoreKey{keyIBC}) + return mapp +} + +func TestIBCMsgs(t *testing.T) { + mapp := getMockApp(t) + + sourceChain := "source-chain" + destChain := "dest-chain" + + priv1 := crypto.GenPrivKeyEd25519() + addr1 := priv1.PubKey().Address() + coins := sdk.Coins{{"foocoin", 10}} + var emptyCoins sdk.Coins + + acc := &auth.BaseAccount{ + Address: addr1, + Coins: coins, + } + accs := []auth.Account{acc} + + mock.SetGenesis(mapp, accs) + + // A checkTx context (true) + ctxCheck := mapp.BaseApp.NewContext(true, abci.Header{}) + res1 := mapp.AccountMapper.GetAccount(ctxCheck, addr1) + assert.Equal(t, acc, res1) + + packet := IBCPacket{ + SrcAddr: addr1, + DestAddr: addr1, + Coins: coins, + SrcChain: sourceChain, + DestChain: destChain, + } + + transferMsg := IBCTransferMsg{ + IBCPacket: packet, + } + + receiveMsg := IBCReceiveMsg{ + IBCPacket: packet, + Relayer: addr1, + Sequence: 0, + } + + mock.SignCheckDeliver(t, mapp.BaseApp, transferMsg, []int64{0},[]int64{0}, true, priv1) + mock.CheckBalance(t, mapp, addr1, emptyCoins) + mock.SignCheckDeliver(t, mapp.BaseApp, transferMsg, []int64{0}, []int64{1}, false, priv1) + mock.SignCheckDeliver(t, mapp.BaseApp, receiveMsg, []int64{0}, []int64{2}, true, priv1) + mock.CheckBalance(t, mapp, addr1, coins) + mock.SignCheckDeliver(t, mapp.BaseApp, receiveMsg, []int64{0}, []int64{3}, false, priv1) +} diff --git a/x/ibc/client/rest/transfer.go b/x/ibc/client/rest/transfer.go index 48a29ee80..b77a6f5eb 100644 --- a/x/ibc/client/rest/transfer.go +++ b/x/ibc/client/rest/transfer.go @@ -25,7 +25,9 @@ type transferBody struct { LocalAccountName string `json:"name"` Password string `json:"password"` SrcChainID string `json:"src_chain_id"` + AccountNumber int64 `json:"account_number"` Sequence int64 `json:"sequence"` + Gas int64 `json:"gas"` } // TransferRequestHandler - http request handler to transfer coins to a address @@ -35,7 +37,14 @@ func TransferRequestHandlerFn(cdc *wire.Codec, kb keys.Keybase, ctx context.Core // collect data vars := mux.Vars(r) destChainID := vars["destchain"] - address := vars["address"] + bech32addr := vars["address"] + + address, err := sdk.GetAccAddressBech32(bech32addr) + if err != nil { + w.WriteHeader(http.StatusBadRequest) + w.Write([]byte(err.Error())) + return + } var m transferBody body, err := ioutil.ReadAll(r.Body) @@ -58,7 +67,7 @@ func TransferRequestHandlerFn(cdc *wire.Codec, kb keys.Keybase, ctx context.Core return } - bz, err := hex.DecodeString(address) + bz, err := hex.DecodeString(address.String()) if err != nil { w.WriteHeader(http.StatusBadRequest) w.Write([]byte(err.Error())) @@ -70,7 +79,11 @@ func TransferRequestHandlerFn(cdc *wire.Codec, kb keys.Keybase, ctx context.Core packet := ibc.NewIBCPacket(info.PubKey.Address(), to, m.Amount, m.SrcChainID, destChainID) msg := ibc.IBCTransferMsg{packet} + // add gas to context + ctx = ctx.WithGas(m.Gas) + // sign + ctx = ctx.WithAccountNumber(m.AccountNumber) ctx = ctx.WithSequence(m.Sequence) txBytes, err := ctx.SignAndBuild(m.LocalAccountName, m.Password, msg, cdc) if err != nil { diff --git a/x/ibc/types.go b/x/ibc/types.go index 09a853b20..4924aec4b 100644 --- a/x/ibc/types.go +++ b/x/ibc/types.go @@ -1,11 +1,20 @@ package ibc import ( - sdk "github.com/cosmos/cosmos-sdk/types" + "encoding/json" + sdk "github.com/cosmos/cosmos-sdk/types" wire "github.com/cosmos/cosmos-sdk/wire" ) +var ( + msgCdc *wire.Codec +) + +func init() { + msgCdc = wire.NewCodec() +} + // ------------------------------ // IBCPacket @@ -32,12 +41,33 @@ func NewIBCPacket(srcAddr sdk.Address, destAddr sdk.Address, coins sdk.Coins, } } +//nolint +func (p IBCPacket) GetSignBytes() []byte { + b, err := msgCdc.MarshalJSON(struct { + SrcAddr string + DestAddr string + Coins sdk.Coins + SrcChain string + DestChain string + }{ + SrcAddr: sdk.MustBech32ifyAcc(p.SrcAddr), + DestAddr: sdk.MustBech32ifyAcc(p.DestAddr), + Coins: p.Coins, + SrcChain: p.SrcChain, + DestChain: p.DestChain, + }) + if err != nil { + panic(err) + } + return b +} + // validator the ibc packey -func (ibcp IBCPacket) ValidateBasic() sdk.Error { - if ibcp.SrcChain == ibcp.DestChain { +func (p IBCPacket) ValidateBasic() sdk.Error { + if p.SrcChain == p.DestChain { return ErrIdenticalChains(DefaultCodespace).Trace("") } - if !ibcp.Coins.IsValid() { + if !p.Coins.IsValid() { return sdk.ErrInvalidCoins("") } return nil @@ -60,12 +90,7 @@ func (msg IBCTransferMsg) GetSigners() []sdk.Address { return []sdk.Address{msg. // get the sign bytes for ibc transfer message func (msg IBCTransferMsg) GetSignBytes() []byte { - cdc := wire.NewCodec() - bz, err := cdc.MarshalBinary(msg) - if err != nil { - panic(err) - } - return bz + return msg.IBCPacket.GetSignBytes() } // validate ibc transfer message @@ -94,10 +119,17 @@ func (msg IBCReceiveMsg) GetSigners() []sdk.Address { return []sdk.Address{msg.R // get the sign bytes for ibc receive message func (msg IBCReceiveMsg) GetSignBytes() []byte { - cdc := wire.NewCodec() - bz, err := cdc.MarshalBinary(msg) + b, err := msgCdc.MarshalJSON(struct { + IBCPacket json.RawMessage + Relayer string + Sequence int64 + }{ + IBCPacket: json.RawMessage(msg.IBCPacket.GetSignBytes()), + Relayer: sdk.MustBech32ifyAcc(msg.Relayer), + Sequence: msg.Sequence, + }) if err != nil { panic(err) } - return bz + return b } diff --git a/x/slashing/app_test.go b/x/slashing/app_test.go new file mode 100644 index 000000000..2a99795c6 --- /dev/null +++ b/x/slashing/app_test.go @@ -0,0 +1,111 @@ +package slashing + +import ( + "testing" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/auth" + "github.com/cosmos/cosmos-sdk/x/auth/mock" + "github.com/cosmos/cosmos-sdk/x/bank" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/cosmos/cosmos-sdk/x/stake" + abci "github.com/tendermint/abci/types" + crypto "github.com/tendermint/go-crypto" +) + +var ( + priv1 = crypto.GenPrivKeyEd25519() + addr1 = priv1.PubKey().Address() + coins = sdk.Coins{{"foocoin", 10}} +) + +// initialize the mock application for this module +func getMockApp(t *testing.T) (*mock.App, stake.Keeper, Keeper) { + mapp := mock.NewApp() + + RegisterWire(mapp.Cdc) + keyStake := sdk.NewKVStoreKey("stake") + keySlashing := sdk.NewKVStoreKey("slashing") + coinKeeper := bank.NewKeeper(mapp.AccountMapper) + stakeKeeper := stake.NewKeeper(mapp.Cdc, keyStake, coinKeeper, mapp.RegisterCodespace(stake.DefaultCodespace)) + keeper := NewKeeper(mapp.Cdc, keySlashing, stakeKeeper, mapp.RegisterCodespace(DefaultCodespace)) + mapp.Router().AddRoute("stake", stake.NewHandler(stakeKeeper)) + mapp.Router().AddRoute("slashing", NewHandler(keeper)) + + mapp.SetEndBlocker(getEndBlocker(stakeKeeper)) + mapp.SetInitChainer(getInitChainer(mapp, stakeKeeper)) + mapp.CompleteSetup(t, []*sdk.KVStoreKey{keyStake, keySlashing}) + + return mapp, stakeKeeper, keeper +} + +// stake endblocker +func getEndBlocker(keeper stake.Keeper) sdk.EndBlocker { + return func(ctx sdk.Context, req abci.RequestEndBlock) abci.ResponseEndBlock { + validatorUpdates := stake.EndBlocker(ctx, keeper) + return abci.ResponseEndBlock{ + ValidatorUpdates: validatorUpdates, + } + } +} + +// overwrite the mock init chainer +func getInitChainer(mapp *mock.App, keeper stake.Keeper) sdk.InitChainer { + return func(ctx sdk.Context, req abci.RequestInitChain) abci.ResponseInitChain { + mapp.InitChainer(ctx, req) + stake.InitGenesis(ctx, keeper, stake.DefaultGenesisState()) + return abci.ResponseInitChain{} + } +} + +func checkValidator(t *testing.T, mapp *mock.App, keeper stake.Keeper, + addr sdk.Address, expFound bool) stake.Validator { + ctxCheck := mapp.BaseApp.NewContext(true, abci.Header{}) + validator, found := keeper.GetValidator(ctxCheck, addr1) + assert.Equal(t, expFound, found) + return validator +} + +func checkValidatorSigningInfo(t *testing.T, mapp *mock.App, keeper Keeper, + addr sdk.Address, expFound bool) ValidatorSigningInfo { + ctxCheck := mapp.BaseApp.NewContext(true, abci.Header{}) + signingInfo, found := keeper.getValidatorSigningInfo(ctxCheck, addr) + assert.Equal(t, expFound, found) + return signingInfo +} + +func TestSlashingMsgs(t *testing.T) { + mapp, stakeKeeper, keeper := getMockApp(t) + + genCoin := sdk.Coin{"steak", 42} + bondCoin := sdk.Coin{"steak", 10} + + acc1 := &auth.BaseAccount{ + Address: addr1, + Coins: sdk.Coins{genCoin}, + } + accs := []auth.Account{acc1} + mock.SetGenesis(mapp, accs) + description := stake.NewDescription("foo_moniker", "", "", "") + createValidatorMsg := stake.NewMsgCreateValidator( + addr1, priv1.PubKey(), bondCoin, description, + ) + mock.SignCheckDeliver(t, mapp.BaseApp, createValidatorMsg, []int64{0}, []int64{0}, true, priv1) + mock.CheckBalance(t, mapp, addr1, sdk.Coins{genCoin.Minus(bondCoin)}) + mapp.BeginBlock(abci.RequestBeginBlock{}) + + validator := checkValidator(t, mapp, stakeKeeper, addr1, true) + require.Equal(t, addr1, validator.Owner) + require.Equal(t, sdk.Bonded, validator.Status()) + require.True(sdk.RatEq(t, sdk.NewRat(10), validator.PoolShares.Bonded())) + unrevokeMsg := MsgUnrevoke{ValidatorAddr: validator.PubKey.Address()} + + // no signing info yet + checkValidatorSigningInfo(t, mapp, keeper, addr1, false) + + // unrevoke should fail with unknown validator + res := mock.SignCheck(t, mapp.BaseApp, unrevokeMsg, []int64{0}, []int64{1}, priv1) + require.Equal(t, sdk.ToABCICode(DefaultCodespace, CodeInvalidValidator), res.Code) +} diff --git a/x/slashing/keeper.go b/x/slashing/keeper.go index d558cc04b..d5ae09ef2 100644 --- a/x/slashing/keeper.go +++ b/x/slashing/keeper.go @@ -55,8 +55,12 @@ func (k Keeper) handleValidatorSignature(ctx sdk.Context, pubkey crypto.PubKey, address := pubkey.Address() // Local index, so counts blocks validator *should* have signed - // Will use the 0-value default signing info if not present - signInfo, _ := k.getValidatorSigningInfo(ctx, address) + // Will use the 0-value default signing info if not present, except for start height + signInfo, found := k.getValidatorSigningInfo(ctx, address) + if !found { + // If this validator has never been seen before, construct a new SigningInfo with the correct start height + signInfo = NewValidatorSigningInfo(height, 0, 0, 0) + } index := signInfo.IndexOffset % SignedBlocksWindow signInfo.IndexOffset++ diff --git a/x/slashing/keeper_test.go b/x/slashing/keeper_test.go index 25ae1686d..1f8f9db07 100644 --- a/x/slashing/keeper_test.go +++ b/x/slashing/keeper_test.go @@ -11,6 +11,8 @@ import ( "github.com/cosmos/cosmos-sdk/x/stake" ) +// Test that a validator is slashed correctly +// when we discover evidence of equivocation func TestHandleDoubleSign(t *testing.T) { // initial setup @@ -32,6 +34,8 @@ func TestHandleDoubleSign(t *testing.T) { require.Equal(t, sdk.NewRat(amt).Mul(sdk.NewRat(19).Quo(sdk.NewRat(20))), sk.Validator(ctx, addr).GetPower()) } +// Test a validator through uptime, downtime, revocation, +// unrevocation, starting height reset, and revocation again func TestHandleAbsentValidator(t *testing.T) { // initial setup @@ -129,3 +133,39 @@ func TestHandleAbsentValidator(t *testing.T) { validator, _ = sk.GetValidatorByPubKey(ctx, val) require.Equal(t, sdk.Unbonded, validator.GetStatus()) } + +// Test a new validator entering the validator set +// Ensure that SigningInfo.StartHeight is set correctly +// and that they are not immediately revoked +func TestHandleNewValidator(t *testing.T) { + // initial setup + ctx, ck, sk, keeper := createTestInput(t) + addr, val, amt := addrs[0], pks[0], int64(100) + sh := stake.NewHandler(sk) + got := sh(ctx, newTestMsgCreateValidator(addr, val, amt)) + require.True(t, got.IsOK()) + stake.EndBlocker(ctx, sk) + require.Equal(t, ck.GetCoins(ctx, addr), sdk.Coins{{sk.GetParams(ctx).BondDenom, initCoins - amt}}) + require.Equal(t, sdk.NewRat(amt), sk.Validator(ctx, addr).GetPower()) + + // 1000 first blocks not a validator + ctx = ctx.WithBlockHeight(1001) + + // Now a validator, for two blocks + keeper.handleValidatorSignature(ctx, val, true) + ctx = ctx.WithBlockHeight(1002) + keeper.handleValidatorSignature(ctx, val, false) + + info, found := keeper.getValidatorSigningInfo(ctx, val.Address()) + require.True(t, found) + require.Equal(t, int64(1001), info.StartHeight) + require.Equal(t, int64(2), info.IndexOffset) + require.Equal(t, int64(1), info.SignedBlocksCounter) + require.Equal(t, int64(0), info.JailedUntil) + + // validator should be bonded still, should not have been revoked or slashed + validator, _ := sk.GetValidatorByPubKey(ctx, val) + require.Equal(t, sdk.Bonded, validator.GetStatus()) + pool := sk.GetPool(ctx) + require.Equal(t, int64(100), pool.BondedTokens) +} diff --git a/x/slashing/msg.go b/x/slashing/msg.go index d2676af81..561c92266 100644 --- a/x/slashing/msg.go +++ b/x/slashing/msg.go @@ -30,7 +30,11 @@ func (msg MsgUnrevoke) GetSigners() []sdk.Address { return []sdk.Address{msg.Val // get the bytes for the message signer to sign on func (msg MsgUnrevoke) GetSignBytes() []byte { - b, err := cdc.MarshalJSON(msg) + b, err := cdc.MarshalJSON(struct { + ValidatorAddr string `json:"address"` + }{ + ValidatorAddr: sdk.MustBech32ifyVal(msg.ValidatorAddr), + }) if err != nil { panic(err) } diff --git a/x/slashing/msg_test.go b/x/slashing/msg_test.go new file mode 100644 index 000000000..be7797107 --- /dev/null +++ b/x/slashing/msg_test.go @@ -0,0 +1,16 @@ +package slashing + +import ( + "testing" + + "github.com/stretchr/testify/assert" + + sdk "github.com/cosmos/cosmos-sdk/types" +) + +func TestMsgUnrevokeGetSignBytes(t *testing.T) { + addr := sdk.Address("abcd") + msg := NewMsgUnrevoke(addr) + bytes := msg.GetSignBytes() + assert.Equal(t, string(bytes), `{"address":"cosmosvaladdr1v93xxeqamr0mv"}`) +} diff --git a/x/slashing/signing_info.go b/x/slashing/signing_info.go index a2df0505a..acbe1738b 100644 --- a/x/slashing/signing_info.go +++ b/x/slashing/signing_info.go @@ -47,6 +47,16 @@ func (k Keeper) setValidatorSigningBitArray(ctx sdk.Context, address sdk.Address store.Set(GetValidatorSigningBitArrayKey(address, index), bz) } +// Construct a new `ValidatorSigningInfo` struct +func NewValidatorSigningInfo(startHeight int64, indexOffset int64, jailedUntil int64, signedBlocksCounter int64) ValidatorSigningInfo { + return ValidatorSigningInfo{ + StartHeight: startHeight, + IndexOffset: indexOffset, + JailedUntil: jailedUntil, + SignedBlocksCounter: signedBlocksCounter, + } +} + // Signing info for a validator type ValidatorSigningInfo struct { StartHeight int64 `json:"start_height"` // height at which validator was first a candidate OR was unrevoked diff --git a/x/stake/app_test.go b/x/stake/app_test.go new file mode 100644 index 000000000..940d4db2b --- /dev/null +++ b/x/stake/app_test.go @@ -0,0 +1,157 @@ +package stake + +import ( + "testing" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/auth" + "github.com/cosmos/cosmos-sdk/x/auth/mock" + "github.com/cosmos/cosmos-sdk/x/bank" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + abci "github.com/tendermint/abci/types" + crypto "github.com/tendermint/go-crypto" +) + +var ( + priv1 = crypto.GenPrivKeyEd25519() + addr1 = priv1.PubKey().Address() + priv2 = crypto.GenPrivKeyEd25519() + addr2 = priv2.PubKey().Address() + addr3 = crypto.GenPrivKeyEd25519().PubKey().Address() + priv4 = crypto.GenPrivKeyEd25519() + addr4 = priv4.PubKey().Address() + coins = sdk.Coins{{"foocoin", 10}} + fee = auth.StdFee{ + sdk.Coins{{"foocoin", 0}}, + 100000, + } +) + +// initialize the mock application for this module +func getMockApp(t *testing.T) (*mock.App, Keeper) { + mapp := mock.NewApp() + + RegisterWire(mapp.Cdc) + keyStake := sdk.NewKVStoreKey("stake") + coinKeeper := bank.NewKeeper(mapp.AccountMapper) + keeper := NewKeeper(mapp.Cdc, keyStake, coinKeeper, mapp.RegisterCodespace(DefaultCodespace)) + mapp.Router().AddRoute("stake", NewHandler(keeper)) + + mapp.SetEndBlocker(getEndBlocker(keeper)) + mapp.SetInitChainer(getInitChainer(mapp, keeper)) + + mapp.CompleteSetup(t, []*sdk.KVStoreKey{keyStake}) + return mapp, keeper +} + +// stake endblocker +func getEndBlocker(keeper Keeper) sdk.EndBlocker { + return func(ctx sdk.Context, req abci.RequestEndBlock) abci.ResponseEndBlock { + validatorUpdates := EndBlocker(ctx, keeper) + return abci.ResponseEndBlock{ + ValidatorUpdates: validatorUpdates, + } + } +} + +// 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) + InitGenesis(ctx, keeper, DefaultGenesisState()) + + return abci.ResponseInitChain{} + } +} + +//__________________________________________________________________________________________ + +func checkValidator(t *testing.T, mapp *mock.App, keeper Keeper, + addr sdk.Address, expFound bool) Validator { + + ctxCheck := mapp.BaseApp.NewContext(true, abci.Header{}) + validator, found := keeper.GetValidator(ctxCheck, addr1) + assert.Equal(t, expFound, found) + return validator +} + +func checkDelegation(t *testing.T, mapp *mock.App, keeper Keeper, delegatorAddr, + validatorAddr sdk.Address, expFound bool, expShares sdk.Rat) { + + ctxCheck := mapp.BaseApp.NewContext(true, abci.Header{}) + delegation, found := keeper.GetDelegation(ctxCheck, delegatorAddr, validatorAddr) + if expFound { + assert.True(t, found) + assert.True(sdk.RatEq(t, expShares, delegation.Shares)) + return + } + assert.False(t, found) +} + +func TestStakeMsgs(t *testing.T) { + mapp, keeper := getMockApp(t) + + genCoin := sdk.Coin{"steak", 42} + bondCoin := sdk.Coin{"steak", 10} + + acc1 := &auth.BaseAccount{ + Address: addr1, + Coins: sdk.Coins{genCoin}, + } + acc2 := &auth.BaseAccount{ + Address: addr2, + Coins: sdk.Coins{genCoin}, + } + accs := []auth.Account{acc1, acc2} + + mock.SetGenesis(mapp, accs) + mock.CheckBalance(t, mapp, addr1, sdk.Coins{genCoin}) + mock.CheckBalance(t, mapp, addr2, sdk.Coins{genCoin}) + + //////////////////// + // Create Validator + + description := NewDescription("foo_moniker", "", "", "") + createValidatorMsg := NewMsgCreateValidator( + addr1, priv1.PubKey(), bondCoin, description, + ) + mock.SignCheckDeliver(t, mapp.BaseApp, createValidatorMsg, []int64{0}, []int64{0}, true, priv1) + mock.CheckBalance(t, mapp, addr1, sdk.Coins{genCoin.Minus(bondCoin)}) + mapp.BeginBlock(abci.RequestBeginBlock{}) + + validator := checkValidator(t, mapp, keeper, addr1, true) + require.Equal(t, addr1, validator.Owner) + require.Equal(t, sdk.Bonded, validator.Status()) + require.True(sdk.RatEq(t, sdk.NewRat(10), validator.PoolShares.Bonded())) + + // check the bond that should have been created as well + checkDelegation(t, mapp, keeper, addr1, addr1, true, sdk.NewRat(10)) + + //////////////////// + // Edit Validator + + description = NewDescription("bar_moniker", "", "", "") + editValidatorMsg := NewMsgEditValidator(addr1, description) + mock.SignCheckDeliver(t, mapp.BaseApp, editValidatorMsg, []int64{0}, []int64{1}, true, priv1) + validator = checkValidator(t, mapp, keeper, addr1, true) + require.Equal(t, description, validator.Description) + + //////////////////// + // Delegate + + mock.CheckBalance(t, mapp, addr2, sdk.Coins{genCoin}) + delegateMsg := NewMsgDelegate(addr2, addr1, bondCoin) + mock.SignCheckDeliver(t, mapp.BaseApp, delegateMsg, []int64{1}, []int64{0}, true, priv2) + mock.CheckBalance(t, mapp, addr2, sdk.Coins{genCoin.Minus(bondCoin)}) + checkDelegation(t, mapp, keeper, addr2, addr1, true, sdk.NewRat(10)) + + //////////////////// + // Unbond + + unbondMsg := NewMsgUnbond(addr2, addr1, "MAX") + mock.SignCheckDeliver(t, mapp.BaseApp, unbondMsg, []int64{1}, []int64{1}, true, priv2) + mock.CheckBalance(t, mapp, addr2, sdk.Coins{genCoin}) + checkDelegation(t, mapp, keeper, addr2, addr1, false, sdk.Rat{}) +} diff --git a/x/stake/client/rest/query.go b/x/stake/client/rest/query.go index 3e439c2b4..f8a2f00e5 100644 --- a/x/stake/client/rest/query.go +++ b/x/stake/client/rest/query.go @@ -1,7 +1,6 @@ package rest import ( - "encoding/hex" "fmt" "net/http" @@ -30,24 +29,22 @@ func bondingStatusHandlerFn(ctx context.CoreContext, storeName string, cdc *wire // read parameters vars := mux.Vars(r) - delegator := vars["delegator"] - validator := vars["validator"] + bech32delegator := vars["delegator"] + bech32validator := vars["validator"] - bz, err := hex.DecodeString(delegator) + delegatorAddr, err := sdk.GetAccAddressBech32(bech32delegator) if err != nil { w.WriteHeader(http.StatusBadRequest) w.Write([]byte(err.Error())) return } - delegatorAddr := sdk.Address(bz) - bz, err = hex.DecodeString(validator) + validatorAddr, err := sdk.GetValAddressBech32(bech32validator) if err != nil { w.WriteHeader(http.StatusBadRequest) w.Write([]byte(err.Error())) return } - validatorAddr := sdk.Address(bz) key := stake.GetDelegationKey(delegatorAddr, validatorAddr, cdc) @@ -83,6 +80,62 @@ func bondingStatusHandlerFn(ctx context.CoreContext, storeName string, cdc *wire } } +// TODO move exist next to validator struct for maintainability +type StakeValidatorOutput struct { + Owner string `json:"owner"` // in bech32 + PubKey string `json:"pub_key"` // in bech32 + Revoked bool `json:"revoked"` // has the validator been revoked from bonded status? + + PoolShares stake.PoolShares `json:"pool_shares"` // total shares for tokens held in the pool + DelegatorShares sdk.Rat `json:"delegator_shares"` // total shares issued to a validator's delegators + + Description stake.Description `json:"description"` // description terms for the validator + BondHeight int64 `json:"bond_height"` // earliest height as a bonded validator + BondIntraTxCounter int16 `json:"bond_intra_tx_counter"` // block-local tx index of validator change + ProposerRewardPool sdk.Coins `json:"proposer_reward_pool"` // XXX reward pool collected from being the proposer + + Commission sdk.Rat `json:"commission"` // XXX the commission rate of fees charged to any delegators + CommissionMax sdk.Rat `json:"commission_max"` // XXX maximum commission rate which this validator can ever charge + CommissionChangeRate sdk.Rat `json:"commission_change_rate"` // XXX maximum daily increase of the validator commission + CommissionChangeToday sdk.Rat `json:"commission_change_today"` // XXX commission rate change today, reset each day (UTC time) + + // fee related + PrevBondedShares sdk.Rat `json:"prev_bonded_shares"` // total shares of a global hold pools +} + +func bech32StakeValidatorOutput(validator stake.Validator) (StakeValidatorOutput, error) { + bechOwner, err := sdk.Bech32ifyVal(validator.Owner) + if err != nil { + return StakeValidatorOutput{}, err + } + bechValPubkey, err := sdk.Bech32ifyValPub(validator.PubKey) + if err != nil { + return StakeValidatorOutput{}, err + } + + return StakeValidatorOutput{ + Owner: bechOwner, + PubKey: bechValPubkey, + Revoked: validator.Revoked, + + PoolShares: validator.PoolShares, + DelegatorShares: validator.DelegatorShares, + + Description: validator.Description, + BondHeight: validator.BondHeight, + BondIntraTxCounter: validator.BondIntraTxCounter, + ProposerRewardPool: validator.ProposerRewardPool, + + Commission: validator.Commission, + CommissionMax: validator.CommissionMax, + CommissionChangeRate: validator.CommissionChangeRate, + CommissionChangeToday: validator.CommissionChangeToday, + + PrevBondedShares: validator.PrevBondedShares, + }, nil +} + +// TODO bech32 // http request handler to query list of validators func validatorsHandlerFn(ctx context.CoreContext, storeName string, cdc *wire.Codec) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { @@ -100,16 +153,20 @@ func validatorsHandlerFn(ctx context.CoreContext, storeName string, cdc *wire.Co } // parse out the validators - validators := make([]stake.Validator, len(kvs)) + validators := make([]StakeValidatorOutput, len(kvs)) for i, kv := range kvs { var validator stake.Validator + var bech32Validator StakeValidatorOutput err = cdc.UnmarshalBinary(kv.Value, &validator) + if err == nil { + bech32Validator, err = bech32StakeValidatorOutput(validator) + } if err != nil { w.WriteHeader(http.StatusInternalServerError) w.Write([]byte(fmt.Sprintf("Couldn't decode validator. Error: %s", err.Error()))) return } - validators[i] = validator + validators[i] = bech32Validator } output, err := cdc.MarshalJSON(validators) diff --git a/x/stake/client/rest/tx.go b/x/stake/client/rest/tx.go index eaf206bf6..77a6540ee 100644 --- a/x/stake/client/rest/tx.go +++ b/x/stake/client/rest/tx.go @@ -3,6 +3,7 @@ package rest import ( "bytes" "encoding/json" + "fmt" "io/ioutil" "net/http" @@ -23,13 +24,26 @@ func registerTxRoutes(ctx context.CoreContext, r *mux.Router, cdc *wire.Codec, k ).Methods("POST") } +type msgDelegateInput struct { + DelegatorAddr string `json:"delegator_addr"` // in bech32 + ValidatorAddr string `json:"validator_addr"` // in bech32 + Bond sdk.Coin `json:"bond"` +} +type msgUnbondInput struct { + DelegatorAddr string `json:"delegator_addr"` // in bech32 + ValidatorAddr string `json:"validator_addr"` // in bech32 + Shares string `json:"shares"` +} + type editDelegationsBody struct { - LocalAccountName string `json:"name"` - Password string `json:"password"` - ChainID string `json:"chain_id"` - Sequence int64 `json:"sequence"` - Delegate []stake.MsgDelegate `json:"delegate"` - Unbond []stake.MsgUnbond `json:"unbond"` + LocalAccountName string `json:"name"` + Password string `json:"password"` + ChainID string `json:"chain_id"` + AccountNumber int64 `json:"account_number"` + Sequence int64 `json:"sequence"` + Gas int64 `json:"gas"` + Delegate []msgDelegateInput `json:"delegate"` + Unbond []msgUnbondInput `json:"unbond"` } func editDelegationsRequestHandlerFn(cdc *wire.Codec, kb keys.Keybase, ctx context.CoreContext) http.HandlerFunc { @@ -59,28 +73,64 @@ func editDelegationsRequestHandlerFn(cdc *wire.Codec, kb keys.Keybase, ctx conte messages := make([]sdk.Msg, len(m.Delegate)+len(m.Unbond)) i := 0 for _, msg := range m.Delegate { - if !bytes.Equal(info.Address(), msg.DelegatorAddr) { + delegatorAddr, err := sdk.GetAccAddressBech32(msg.DelegatorAddr) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + w.Write([]byte(fmt.Sprintf("Couldn't decode delegator. Error: %s", err.Error()))) + return + } + validatorAddr, err := sdk.GetValAddressBech32(msg.ValidatorAddr) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + w.Write([]byte(fmt.Sprintf("Couldn't decode validator. Error: %s", err.Error()))) + return + } + if !bytes.Equal(info.Address(), delegatorAddr) { w.WriteHeader(http.StatusUnauthorized) w.Write([]byte("Must use own delegator address")) return } - messages[i] = msg + messages[i] = stake.MsgDelegate{ + DelegatorAddr: delegatorAddr, + ValidatorAddr: validatorAddr, + Bond: msg.Bond, + } i++ } for _, msg := range m.Unbond { - if !bytes.Equal(info.Address(), msg.DelegatorAddr) { + delegatorAddr, err := sdk.GetAccAddressBech32(msg.DelegatorAddr) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + w.Write([]byte(fmt.Sprintf("Couldn't decode delegator. Error: %s", err.Error()))) + return + } + validatorAddr, err := sdk.GetValAddressBech32(msg.ValidatorAddr) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + w.Write([]byte(fmt.Sprintf("Couldn't decode validator. Error: %s", err.Error()))) + return + } + if !bytes.Equal(info.Address(), delegatorAddr) { w.WriteHeader(http.StatusUnauthorized) w.Write([]byte("Must use own delegator address")) return } - messages[i] = msg + messages[i] = stake.MsgUnbond{ + DelegatorAddr: delegatorAddr, + ValidatorAddr: validatorAddr, + Shares: msg.Shares, + } i++ } + // add gas to context + ctx = ctx.WithGas(m.Gas) + // sign messages signedTxs := make([][]byte, len(messages[:])) for i, msg := range messages { // increment sequence for each message + ctx = ctx.WithAccountNumber(m.AccountNumber) ctx = ctx.WithSequence(m.Sequence) m.Sequence++ diff --git a/x/stake/genesis.go b/x/stake/genesis.go index 0a7c528bf..43ea61d8b 100644 --- a/x/stake/genesis.go +++ b/x/stake/genesis.go @@ -1,6 +1,10 @@ package stake -import sdk "github.com/cosmos/cosmos-sdk/types" +import ( + tmtypes "github.com/tendermint/tendermint/types" + + sdk "github.com/cosmos/cosmos-sdk/types" +) // GenesisState - all staking state that must be provided at genesis type GenesisState struct { @@ -63,3 +67,16 @@ func WriteGenesis(ctx sdk.Context, k Keeper) GenesisState { bonds, } } + +// WriteValidators - output current validator set +func WriteValidators(ctx sdk.Context, k Keeper) (vals []tmtypes.GenesisValidator) { + k.IterateValidatorsBonded(ctx, func(_ int64, validator sdk.Validator) (stop bool) { + vals = append(vals, tmtypes.GenesisValidator{ + PubKey: validator.GetPubKey(), + Power: validator.GetPower().Evaluate(), + Name: validator.GetMoniker(), + }) + return false + }) + return +} diff --git a/x/stake/handler_test.go b/x/stake/handler_test.go index 0c086f06d..6dcf3e66d 100644 --- a/x/stake/handler_test.go +++ b/x/stake/handler_test.go @@ -33,6 +33,78 @@ func newTestMsgDelegate(delegatorAddr, validatorAddr sdk.Address, amt int64) Msg //______________________________________________________________________ +func TestValidatorByPowerIndex(t *testing.T) { + validatorAddr, validatorAddr3 := addrs[0], addrs[1] + + initBond := int64(1000000) + initBondStr := "1000" + ctx, _, keeper := createTestInput(t, false, initBond) + + // create validator + msgCreateValidator := newTestMsgCreateValidator(validatorAddr, pks[0], initBond) + got := handleMsgCreateValidator(ctx, msgCreateValidator, keeper) + assert.True(t, got.IsOK(), "expected create-validator to be ok, got %v", got) + + // verify the self-delegation exists + bond, found := keeper.GetDelegation(ctx, validatorAddr, validatorAddr) + require.True(t, found) + gotBond := bond.Shares.Evaluate() + require.Equal(t, initBond, gotBond, + "initBond: %v\ngotBond: %v\nbond: %v\n", + initBond, gotBond, bond) + + // verify that the by power index exists + validator, found := keeper.GetValidator(ctx, validatorAddr) + require.True(t, found) + pool := keeper.GetPool(ctx) + power := GetValidatorsByPowerKey(validator, pool) + require.True(t, keeper.validatorByPowerIndexExists(ctx, power)) + + // create a second validator keep it bonded + msgCreateValidator = newTestMsgCreateValidator(validatorAddr3, pks[2], int64(1000000)) + got = handleMsgCreateValidator(ctx, msgCreateValidator, keeper) + assert.True(t, got.IsOK(), "expected create-validator to be ok, got %v", got) + + // slash and revoke the first validator + keeper.Slash(ctx, pks[0], 0, sdk.NewRat(1, 2)) + keeper.Revoke(ctx, pks[0]) + validator, found = keeper.GetValidator(ctx, validatorAddr) + require.True(t, found) + require.Equal(t, sdk.Unbonded, validator.PoolShares.Status) // ensure is unbonded + require.Equal(t, int64(500000), validator.PoolShares.Amount.Evaluate()) // ensure is unbonded + + // the old power record should have been deleted as the power changed + assert.False(t, keeper.validatorByPowerIndexExists(ctx, power)) + + // but the new power record should have been created + validator, found = keeper.GetValidator(ctx, validatorAddr) + require.True(t, found) + pool = keeper.GetPool(ctx) + power2 := GetValidatorsByPowerKey(validator, pool) + require.True(t, keeper.validatorByPowerIndexExists(ctx, power2)) + + // inflate a bunch + for i := 0; i < 20000; i++ { + pool = keeper.processProvisions(ctx) + keeper.setPool(ctx, pool) + } + + // now the new record power index should be the same as the original record + power3 := GetValidatorsByPowerKey(validator, pool) + assert.Equal(t, power2, power3) + + // unbond self-delegation + msgUnbond := NewMsgUnbond(validatorAddr, validatorAddr, "MAX") + got = handleMsgUnbond(ctx, msgUnbond, keeper) + assert.True(t, got.IsOK(), + "got: %v\nmsgUnbond: %v\ninitBondStr: %v\n", got, msgUnbond, initBondStr) + + // verify that by power key nolonger exists + _, found = keeper.GetValidator(ctx, validatorAddr) + require.False(t, found) + assert.False(t, keeper.validatorByPowerIndexExists(ctx, power3)) +} + func TestDuplicatesMsgCreateValidator(t *testing.T) { ctx, _, keeper := createTestInput(t, false, 1000) @@ -42,6 +114,7 @@ func TestDuplicatesMsgCreateValidator(t *testing.T) { got := handleMsgCreateValidator(ctx, msgCreateValidator, keeper) assert.True(t, got.IsOK(), "%v", got) validator, found := keeper.GetValidator(ctx, validatorAddr) + require.True(t, found) assert.Equal(t, sdk.Bonded, validator.Status()) assert.Equal(t, validatorAddr, validator.Owner) diff --git a/x/stake/inflation.go b/x/stake/inflation.go index f385e9d82..fe3f59435 100644 --- a/x/stake/inflation.go +++ b/x/stake/inflation.go @@ -5,8 +5,8 @@ import ( ) const ( - hrsPerYr = 8766 // as defined by a julian year of 365.25 days - precision = 1000000000 + hrsPerYr = 8766 // as defined by a julian year of 365.25 days + precision = 100000000000 // increased to this precision for accuracy with tests on tick_test.go ) var hrsPerYrRat = sdk.NewRat(hrsPerYr) // as defined by a julian year of 365.25 days @@ -22,7 +22,9 @@ func (k Keeper) processProvisions(ctx sdk.Context) Pool { // which needs to be updated is the `BondedPool`. So for each previsions cycle: provisions := pool.Inflation.Mul(sdk.NewRat(pool.TokenSupply())).Quo(hrsPerYrRat).Evaluate() - pool.BondedTokens += provisions + + // TODO add to the fees provisions + pool.LooseUnbondedTokens += provisions return pool } @@ -32,7 +34,7 @@ func (k Keeper) nextInflation(ctx sdk.Context) (inflation sdk.Rat) { params := k.GetParams(ctx) pool := k.GetPool(ctx) // The target annual inflation rate is recalculated for each previsions cycle. The - // inflation is also subject to a rate change (positive of negative) depending or + // inflation is also subject to a rate change (positive or negative) depending on // the distance from the desired ratio (67%). The maximum rate change possible is // defined to be 13% per year, however the annual inflation is capped as between // 7% and 20%. diff --git a/x/stake/inflation_test.go b/x/stake/inflation_test.go index 438b678f1..0d5183f4c 100644 --- a/x/stake/inflation_test.go +++ b/x/stake/inflation_test.go @@ -1,6 +1,8 @@ package stake import ( + "math/rand" + "strconv" "testing" sdk "github.com/cosmos/cosmos-sdk/types" @@ -8,6 +10,9 @@ import ( "github.com/stretchr/testify/require" ) +//changing the int in NewSource will allow you to test different, deterministic, sets of operations +var r = rand.New(rand.NewSource(6595)) + func TestGetInflation(t *testing.T) { ctx, _, keeper := createTestInput(t, false, 0) pool := keeper.GetPool(ctx) @@ -59,82 +64,307 @@ func TestGetInflation(t *testing.T) { } } +// Test that provisions are correctly added to the pool and validators each hour for 1 year func TestProcessProvisions(t *testing.T) { ctx, _, keeper := createTestInput(t, false, 0) - params := DefaultParams() - params.MaxValidators = 2 - keeper.setParams(ctx, params) pool := keeper.GetPool(ctx) - var tokenSupply int64 = 550000000 - var bondedShares int64 = 150000000 - var unbondedShares int64 = 400000000 + var ( + initialTotalTokens int64 = 550000000 + initialBondedTokens int64 = 250000000 + initialUnbondedTokens int64 = 300000000 + cumulativeExpProvs int64 + validatorTokens = []int64{150000000, 100000000, 100000000, 100000000, 100000000} + bondedValidators uint16 = 2 + ) // create some validators some bonded, some unbonded - var validators [5]Validator - validators[0] = NewValidator(addrs[0], pks[0], Description{}) - validators[0], pool, _ = validators[0].addTokensFromDel(pool, 150000000) - keeper.setPool(ctx, pool) - validators[0] = keeper.updateValidator(ctx, validators[0]) - pool = keeper.GetPool(ctx) - require.Equal(t, bondedShares, pool.BondedTokens, "%v", pool) + _, keeper, pool = setupTestValidators(pool, keeper, ctx, validatorTokens, bondedValidators) + checkValidatorSetup(t, pool, initialTotalTokens, initialBondedTokens, initialUnbondedTokens) - validators[1] = NewValidator(addrs[1], pks[1], Description{}) - validators[1], pool, _ = validators[1].addTokensFromDel(pool, 100000000) - keeper.setPool(ctx, pool) - validators[1] = keeper.updateValidator(ctx, validators[1]) - validators[2] = NewValidator(addrs[2], pks[2], Description{}) - validators[2], pool, _ = validators[2].addTokensFromDel(pool, 100000000) - keeper.setPool(ctx, pool) - validators[2] = keeper.updateValidator(ctx, validators[2]) - validators[3] = NewValidator(addrs[3], pks[3], Description{}) - validators[3], pool, _ = validators[3].addTokensFromDel(pool, 100000000) - keeper.setPool(ctx, pool) - validators[3] = keeper.updateValidator(ctx, validators[3]) - validators[4] = NewValidator(addrs[4], pks[4], Description{}) - validators[4], pool, _ = validators[4].addTokensFromDel(pool, 100000000) - keeper.setPool(ctx, pool) - validators[4] = keeper.updateValidator(ctx, validators[4]) - - assert.Equal(t, tokenSupply, pool.TokenSupply()) - assert.Equal(t, bondedShares, pool.BondedTokens) - assert.Equal(t, unbondedShares, pool.UnbondedTokens) - - // initial bonded ratio ~ 27% - assert.True(t, pool.bondedRatio().Equal(sdk.NewRat(bondedShares, tokenSupply)), "%v", pool.bondedRatio()) - - // test the value of validator shares - assert.True(t, pool.bondedShareExRate().Equal(sdk.OneRat()), "%v", pool.bondedShareExRate()) - - initialSupply := pool.TokenSupply() - initialUnbonded := pool.TokenSupply() - pool.BondedTokens - - // process the provisions a year + // process the provisions for a year for hr := 0; hr < 8766; hr++ { pool := keeper.GetPool(ctx) - expInflation := keeper.nextInflation(ctx).Round(1000000000) - expProvisions := (expInflation.Mul(sdk.NewRat(pool.TokenSupply())).Quo(hrsPerYrRat)).Evaluate() - startBondedTokens := pool.BondedTokens - startTotalSupply := pool.TokenSupply() - pool = keeper.processProvisions(ctx) - keeper.setPool(ctx, pool) - //fmt.Printf("hr %v, startBondedTokens %v, expProvisions %v, pool.BondedTokens %v\n", hr, startBondedTokens, expProvisions, pool.BondedTokens) - require.Equal(t, startBondedTokens+expProvisions, pool.BondedTokens, "hr %v", hr) - require.Equal(t, startTotalSupply+expProvisions, pool.TokenSupply()) + _, expProvisions, _ := updateProvisions(t, keeper, pool, ctx, hr) + cumulativeExpProvs = cumulativeExpProvs + expProvisions } + + //get the pool and do the final value checks from checkFinalPoolValues pool = keeper.GetPool(ctx) - assert.NotEqual(t, initialSupply, pool.TokenSupply()) - assert.Equal(t, initialUnbonded, pool.UnbondedTokens) - //panic(fmt.Sprintf("debug total %v, bonded %v, diff %v\n", p.TotalSupply, p.BondedTokens, pool.TokenSupply()-pool.BondedTokens)) - - // initial bonded ratio ~ from 27% to 40% increase for bonded holders ownership of total supply - assert.True(t, pool.bondedRatio().Equal(sdk.NewRat(211813022, 611813022)), "%v", pool.bondedRatio()) - - // global supply - assert.Equal(t, int64(611813022), pool.TokenSupply()) - assert.Equal(t, int64(211813022), pool.BondedTokens) - assert.Equal(t, unbondedShares, pool.UnbondedTokens) - - // test the value of validator shares - assert.True(t, pool.bondedShareExRate().Mul(sdk.NewRat(bondedShares)).Equal(sdk.NewRat(211813022)), "%v", pool.bondedShareExRate()) + checkFinalPoolValues(t, pool, initialTotalTokens, cumulativeExpProvs) +} + +// Tests that the hourly rate of change of inflation will be positive, negative, or zero, depending on bonded ratio and inflation rate +// Cycles through the whole gambit of inflation possibilities, starting at 7% inflation, up to 20%, back down to 7% (it takes ~11.4 years) +func TestHourlyInflationRateOfChange(t *testing.T) { + ctx, _, keeper := createTestInput(t, false, 0) + pool := keeper.GetPool(ctx) + + var ( + initialTotalTokens int64 = 550000000 + initialBondedTokens int64 = 150000000 + initialUnbondedTokens int64 = 400000000 + cumulativeExpProvs int64 + validatorTokens = []int64{150000000, 100000000, 100000000, 100000000, 100000000} + bondedValidators uint16 = 1 + ) + + // create some validators some bonded, some unbonded + _, keeper, pool = setupTestValidators(pool, keeper, ctx, validatorTokens, bondedValidators) + checkValidatorSetup(t, pool, initialTotalTokens, initialBondedTokens, initialUnbondedTokens) + + // ~11.4 years to go from 7%, up to 20%, back down to 7% + for hr := 0; hr < 100000; hr++ { + pool := keeper.GetPool(ctx) + previousInflation := pool.Inflation + updatedInflation, expProvisions, pool := updateProvisions(t, keeper, pool, ctx, hr) + cumulativeExpProvs = cumulativeExpProvs + expProvisions + msg := strconv.Itoa(hr) + checkInflation(t, pool, previousInflation, updatedInflation, msg) + } + + // Final check that the pool equals initial values + cumulative provisions and adjustments we recorded + pool = keeper.GetPool(ctx) + checkFinalPoolValues(t, pool, initialTotalTokens, cumulativeExpProvs) +} + +//Test that a large unbonding will significantly lower the bonded ratio +func TestLargeUnbond(t *testing.T) { + ctx, _, keeper := createTestInput(t, false, 0) + pool := keeper.GetPool(ctx) + + var ( + initialTotalTokens int64 = 1200000000 + initialBondedTokens int64 = 900000000 + initialUnbondedTokens int64 = 300000000 + val0UnbondedTokens int64 + bondedShares = sdk.NewRat(900000000, 1) + unbondedShares = sdk.NewRat(300000000, 1) + bondSharesVal0 = sdk.NewRat(300000000, 1) + validatorTokens = []int64{300000000, 100000000, 100000000, 100000000, 100000000, 100000000, 100000000, 100000000, 100000000, 100000000} + bondedValidators uint16 = 7 + ) + + _, keeper, pool = setupTestValidators(pool, keeper, ctx, validatorTokens, bondedValidators) + checkValidatorSetup(t, pool, initialTotalTokens, initialBondedTokens, initialUnbondedTokens) + + pool = keeper.GetPool(ctx) + validator, found := keeper.GetValidator(ctx, addrs[0]) + assert.True(t, found) + + // initialBondedRatio that we can use to compare to the new values after the unbond + initialBondedRatio := pool.bondedRatio() + + // validator[0] will be unbonded, bringing us from 75% bonded ratio to ~50% (unbonding 300,000,000) + pool, validator, _, _ = OpBondOrUnbond(r, pool, validator) + keeper.setPool(ctx, pool) + + // process provisions after the bonding, to compare the difference in expProvisions and expInflation + _, expProvisionsAfter, pool := updateProvisions(t, keeper, pool, ctx, 0) + + bondedShares = bondedShares.Sub(bondSharesVal0) + val0UnbondedTokens = pool.unbondedShareExRate().Mul(validator.PoolShares.Unbonded()).Evaluate() + unbondedShares = unbondedShares.Add(sdk.NewRat(val0UnbondedTokens, 1).Mul(pool.unbondedShareExRate())) + + // unbonded shares should increase + assert.True(t, unbondedShares.GT(sdk.NewRat(300000000, 1))) + // Ensure that new bonded ratio is less than old bonded ratio , because before they were increasing (i.e. 50% < 75) + assert.True(t, (pool.bondedRatio().LT(initialBondedRatio))) + + // Final check that the pool equals initial values + provisions and adjustments we recorded + pool = keeper.GetPool(ctx) + checkFinalPoolValues(t, pool, initialTotalTokens, expProvisionsAfter) +} + +//Test that a large bonding will significantly increase the bonded ratio +func TestLargeBond(t *testing.T) { + ctx, _, keeper := createTestInput(t, false, 0) + pool := keeper.GetPool(ctx) + + var ( + initialTotalTokens int64 = 1600000000 + initialBondedTokens int64 = 400000000 + initialUnbondedTokens int64 = 1200000000 + unbondedShares = sdk.NewRat(1200000000, 1) + unbondedSharesVal9 = sdk.NewRat(400000000, 1) + validatorTokens = []int64{400000000, 100000000, 100000000, 100000000, 100000000, 100000000, 100000000, 100000000, 100000000, 400000000} + bondedValidators uint16 = 1 + ) + + _, keeper, pool = setupTestValidators(pool, keeper, ctx, validatorTokens, bondedValidators) + checkValidatorSetup(t, pool, initialTotalTokens, initialBondedTokens, initialUnbondedTokens) + + pool = keeper.GetPool(ctx) + validator, found := keeper.GetValidator(ctx, addrs[9]) + assert.True(t, found) + + // initialBondedRatio that we can use to compare to the new values after the unbond + initialBondedRatio := pool.bondedRatio() + + params := DefaultParams() + params.MaxValidators = bondedValidators + 1 //must do this to allow for an extra validator to bond + keeper.setParams(ctx, params) + + // validator[9] will be bonded, bringing us from 25% to ~50% (bonding 400,000,000 tokens) + pool, validator, _, _ = OpBondOrUnbond(r, pool, validator) + keeper.setPool(ctx, pool) + + // process provisions after the bonding, to compare the difference in expProvisions and expInflation + _, expProvisionsAfter, pool := updateProvisions(t, keeper, pool, ctx, 0) + unbondedShares = unbondedShares.Sub(unbondedSharesVal9) + + // unbonded shares should decrease + assert.True(t, unbondedShares.LT(sdk.NewRat(1200000000, 1))) + // Ensure that new bonded ratio is greater than old bonded ratio (i.e. 50% > 25%) + assert.True(t, (pool.bondedRatio().GT(initialBondedRatio))) + // Final check that the pool equals initial values + provisions and adjustments we recorded + pool = keeper.GetPool(ctx) + + checkFinalPoolValues(t, pool, initialTotalTokens, expProvisionsAfter) +} + +// Tests that inflation increases or decreases as expected when we do a random operation on 20 different validators +func TestInflationWithRandomOperations(t *testing.T) { + ctx, _, keeper := createTestInput(t, false, 0) + params := DefaultParams() + keeper.setParams(ctx, params) + numValidators := 20 + + // start off by randomly setting up 20 validators + pool, validators := randomSetup(r, numValidators) + require.Equal(t, numValidators, len(validators)) + + for i := 0; i < len(validators); i++ { + keeper.setValidator(ctx, validators[i]) + } + keeper.setPool(ctx, pool) + + // Used to rotate validators so each random operation is applied to a different validator + validatorCounter := 0 + + // Loop through 20 random operations, and check the inflation after each operation + for i := 0; i < numValidators; i++ { + pool := keeper.GetPool(ctx) + + // Get inflation before randomOperation, for comparison later + previousInflation := pool.Inflation + + // Perform the random operation, and record how validators are modified + poolMod, validatorMod, tokens, msg := randomOperation(r)(r, pool, validators[validatorCounter]) + validatorsMod := make([]Validator, len(validators)) + copy(validatorsMod[:], validators[:]) + require.Equal(t, numValidators, len(validators), "i %v", validatorCounter) + require.Equal(t, numValidators, len(validatorsMod), "i %v", validatorCounter) + validatorsMod[validatorCounter] = validatorMod + + assertInvariants(t, msg, + pool, validators, + poolMod, validatorsMod, tokens) + + // set pool and validators after the random operation + pool = poolMod + keeper.setPool(ctx, pool) + validators = validatorsMod + + // Must set inflation here manually, as opposed to most other tests in this suite, where we call keeper.processProvisions(), which updates pool.Inflation + updatedInflation := keeper.nextInflation(ctx) + pool.Inflation = updatedInflation + keeper.setPool(ctx, pool) + + // Ensure inflation changes as expected when random operations are applied. + checkInflation(t, pool, previousInflation, updatedInflation, msg) + validatorCounter++ + } +} + +//_________________________________________________________________________________________ +////////////////////////////////HELPER FUNCTIONS BELOW///////////////////////////////////// + +// Final check on the global pool values for what the total tokens accumulated from each hour of provisions +func checkFinalPoolValues(t *testing.T, pool Pool, initialTotalTokens, cumulativeExpProvs int64) { + calculatedTotalTokens := initialTotalTokens + cumulativeExpProvs + assert.Equal(t, calculatedTotalTokens, pool.TokenSupply()) +} + +// Processes provisions are added to the pool correctly every hour +// Returns expected Provisions, expected Inflation, and pool, to help with cumulative calculations back in main Tests +func updateProvisions(t *testing.T, keeper Keeper, pool Pool, ctx sdk.Context, hr int) (sdk.Rat, int64, Pool) { + expInflation := keeper.nextInflation(ctx) + expProvisions := (expInflation.Mul(sdk.NewRat(pool.TokenSupply())).Quo(hrsPerYrRat)).Evaluate() + startTotalSupply := pool.TokenSupply() + pool = keeper.processProvisions(ctx) + keeper.setPool(ctx, pool) + + //check provisions were added to pool + require.Equal(t, startTotalSupply+expProvisions, pool.TokenSupply()) + + return expInflation, expProvisions, pool +} + +// Deterministic setup of validators and pool +// Allows you to decide how many validators to setup +// Allows you to pick which validators are bonded by adjusting the MaxValidators of params +func setupTestValidators(pool Pool, keeper Keeper, ctx sdk.Context, validatorTokens []int64, maxValidators uint16) ([]Validator, Keeper, Pool) { + params := DefaultParams() + params.MaxValidators = maxValidators + keeper.setParams(ctx, params) + numValidators := len(validatorTokens) + validators := make([]Validator, numValidators) + + for i := 0; i < numValidators; i++ { + validators[i] = NewValidator(addrs[i], pks[i], Description{}) + validators[i], pool, _ = validators[i].addTokensFromDel(pool, validatorTokens[i]) + keeper.setPool(ctx, pool) + validators[i] = keeper.updateValidator(ctx, validators[i]) //will kick out lower power validators. Keep this in mind when setting up the test validators order + pool = keeper.GetPool(ctx) + } + + return validators, keeper, pool +} + +// Checks that the deterministic validator setup you wanted matches the values in the pool +func checkValidatorSetup(t *testing.T, pool Pool, initialTotalTokens, initialBondedTokens, initialUnbondedTokens int64) { + assert.Equal(t, initialTotalTokens, pool.TokenSupply()) + assert.Equal(t, initialBondedTokens, pool.BondedTokens) + assert.Equal(t, initialUnbondedTokens, pool.UnbondedTokens) + + // test initial bonded ratio + assert.True(t, pool.bondedRatio().Equal(sdk.NewRat(initialBondedTokens, initialTotalTokens)), "%v", pool.bondedRatio()) + // test the value of validator shares + assert.True(t, pool.bondedShareExRate().Equal(sdk.OneRat()), "%v", pool.bondedShareExRate()) +} + +// Checks that The inflation will correctly increase or decrease after an update to the pool +func checkInflation(t *testing.T, pool Pool, previousInflation, updatedInflation sdk.Rat, msg string) { + inflationChange := updatedInflation.Sub(previousInflation) + + switch { + //BELOW 67% - Rate of change positive and increasing, while we are between 7% <= and < 20% inflation + case pool.bondedRatio().LT(sdk.NewRat(67, 100)) && updatedInflation.LT(sdk.NewRat(20, 100)): + assert.Equal(t, true, inflationChange.GT(sdk.ZeroRat()), msg) + + //BELOW 67% - Rate of change should be 0 while inflation continually stays at 20% until we reach 67% bonded ratio + case pool.bondedRatio().LT(sdk.NewRat(67, 100)) && updatedInflation.Equal(sdk.NewRat(20, 100)): + if previousInflation.Equal(sdk.NewRat(20, 100)) { + assert.Equal(t, true, inflationChange.IsZero(), msg) + + //This else statement covers the one off case where we first hit 20%, but we still needed a positive ROC to get to 67% bonded ratio (i.e. we went from 19.99999% to 20%) + } else { + assert.Equal(t, true, inflationChange.GT(sdk.ZeroRat()), msg) + } + + //ABOVE 67% - Rate of change should be negative while the bond is above 67, and should stay negative until we reach inflation of 7% + case pool.bondedRatio().GT(sdk.NewRat(67, 100)) && updatedInflation.LT(sdk.NewRat(20, 100)) && updatedInflation.GT(sdk.NewRat(7, 100)): + assert.Equal(t, true, inflationChange.LT(sdk.ZeroRat()), msg) + + //ABOVE 67% - Rate of change should be 0 while inflation continually stays at 7%. + case pool.bondedRatio().GT(sdk.NewRat(67, 100)) && updatedInflation.Equal(sdk.NewRat(7, 100)): + if previousInflation.Equal(sdk.NewRat(7, 100)) { + assert.Equal(t, true, inflationChange.IsZero(), msg) + + //This else statement covers the one off case where we first hit 7%, but we still needed a negative ROC to continue to get down to 67%. (i.e. we went from 7.00001% to 7%) + } else { + assert.Equal(t, true, inflationChange.LT(sdk.ZeroRat()), msg) + } + } } diff --git a/x/stake/keeper.go b/x/stake/keeper.go index 9f554c764..4a2e6ff4b 100644 --- a/x/stake/keeper.go +++ b/x/stake/keeper.go @@ -78,6 +78,12 @@ func (k Keeper) setValidatorByPowerIndex(ctx sdk.Context, validator Validator, p store.Set(GetValidatorsByPowerKey(validator, pool), validator.Owner) } +// used in testing +func (k Keeper) validatorByPowerIndexExists(ctx sdk.Context, power []byte) bool { + store := ctx.KVStore(k.storeKey) + return store.Get(power) != nil +} + // Get the set of all validators with no limits, used during genesis dump func (k Keeper) getAllValidators(ctx sdk.Context) (validators Validators) { store := ctx.KVStore(k.storeKey) @@ -167,6 +173,12 @@ func (k Keeper) GetValidatorsByPower(ctx sdk.Context) []Validator { if !found { panic(fmt.Sprintf("validator record not found for address: %v\n", address)) } + + // Reached to revoked validators, stop iterating + if validator.Revoked { + iterator.Close() + break + } if validator.Status() == sdk.Bonded { validators[i] = validator i++ diff --git a/x/stake/keeper_keys.go b/x/stake/keeper_keys.go index 632a86ec3..20062f00e 100644 --- a/x/stake/keeper_keys.go +++ b/x/stake/keeper_keys.go @@ -51,15 +51,22 @@ func GetValidatorsByPowerKey(validator Validator, pool Pool) []byte { powerBytes := []byte(power.ToLeftPadded(maxDigitsForAccount)) // power big-endian (more powerful validators first) // TODO ensure that the key will be a readable string.. probably should add seperators and have + revokedBytes := make([]byte, 1) + if validator.Revoked { + revokedBytes[0] = byte(0x01) + } else { + revokedBytes[0] = byte(0x00) + } // heightBytes and counterBytes represent strings like powerBytes does heightBytes := make([]byte, binary.MaxVarintLen64) binary.BigEndian.PutUint64(heightBytes, ^uint64(validator.BondHeight)) // invert height (older validators first) counterBytes := make([]byte, 2) binary.BigEndian.PutUint16(counterBytes, ^uint16(validator.BondIntraTxCounter)) // invert counter (first txns have priority) return append(ValidatorsByPowerKey, - append(powerBytes, - append(heightBytes, - append(counterBytes, validator.Owner.Bytes()...)...)...)...) // TODO don't technically need to store owner + append(revokedBytes, + append(powerBytes, + append(heightBytes, + append(counterBytes, validator.Owner.Bytes()...)...)...)...)...) // TODO don't technically need to store owner } // get the key for the accumulated update validators diff --git a/x/stake/keeper_test.go b/x/stake/keeper_test.go index 69a214428..76bfc507a 100644 --- a/x/stake/keeper_test.go +++ b/x/stake/keeper_test.go @@ -24,6 +24,47 @@ var ( } ) +func TestUpdateValidatorByPowerIndex(t *testing.T) { + ctx, _, keeper := createTestInput(t, false, 0) + pool := keeper.GetPool(ctx) + + // create a random pool + pool.BondedTokens = 1234 + pool.BondedShares = sdk.NewRat(124) + pool.UnbondingTokens = 13934 + pool.UnbondingShares = sdk.NewRat(145) + pool.UnbondedTokens = 154 + pool.UnbondedShares = sdk.NewRat(1333) + keeper.setPool(ctx, pool) + + // add a validator + validator := NewValidator(addrVals[0], pks[0], Description{}) + validator, pool, delSharesCreated := validator.addTokensFromDel(pool, 100) + require.Equal(t, sdk.Unbonded, validator.Status()) + assert.Equal(t, int64(100), validator.PoolShares.Tokens(pool).Evaluate()) + keeper.setPool(ctx, pool) + keeper.updateValidator(ctx, validator) + validator, found := keeper.GetValidator(ctx, addrVals[0]) + require.True(t, found) + assert.Equal(t, int64(100), validator.PoolShares.Tokens(pool).Evaluate(), "\nvalidator %v\npool %v", validator, pool) + + pool = keeper.GetPool(ctx) + power := GetValidatorsByPowerKey(validator, pool) + assert.True(t, keeper.validatorByPowerIndexExists(ctx, power)) + + // burn half the delegator shares + validator, pool, burned := validator.removeDelShares(pool, delSharesCreated.Quo(sdk.NewRat(2))) + assert.Equal(t, int64(50), burned) + keeper.setPool(ctx, pool) // update the pool + keeper.updateValidator(ctx, validator) // update the validator, possibly kicking it out + assert.False(t, keeper.validatorByPowerIndexExists(ctx, power)) + + pool = keeper.GetPool(ctx) + validator, found = keeper.GetValidator(ctx, addrVals[0]) + power = GetValidatorsByPowerKey(validator, pool) + assert.True(t, keeper.validatorByPowerIndexExists(ctx, power)) +} + func TestSetValidator(t *testing.T) { ctx, _, keeper := createTestInput(t, false, 0) pool := keeper.GetPool(ctx) @@ -117,8 +158,8 @@ func TestValidatorBasics(t *testing.T) { resVals = keeper.GetValidatorsBonded(ctx) require.Equal(t, 3, len(resVals)) assert.True(ValEq(t, validators[0], resVals[2])) // order doesn't matter here - assert.True(ValEq(t, validators[1], resVals[0])) - assert.True(ValEq(t, validators[2], resVals[1])) + assert.True(ValEq(t, validators[1], resVals[1])) + assert.True(ValEq(t, validators[2], resVals[0])) // remove a record keeper.removeValidator(ctx, validators[1].Owner) diff --git a/x/stake/msg.go b/x/stake/msg.go index 40bf609ee..a0922bb87 100644 --- a/x/stake/msg.go +++ b/x/stake/msg.go @@ -45,7 +45,20 @@ func (msg MsgCreateValidator) GetSigners() []sdk.Address { // get the bytes for the message signer to sign on func (msg MsgCreateValidator) GetSignBytes() []byte { - return msgCdc.MustMarshalBinary(msg) + b, err := msgCdc.MarshalJSON(struct { + Description + ValidatorAddr string `json:"address"` + PubKey string `json:"pubkey"` + Bond sdk.Coin `json:"bond"` + }{ + Description: msg.Description, + ValidatorAddr: sdk.MustBech32ifyVal(msg.ValidatorAddr), + PubKey: sdk.MustBech32ifyValPub(msg.PubKey), + }) + if err != nil { + panic(err) + } + return b } // quick validity check @@ -89,7 +102,13 @@ func (msg MsgEditValidator) GetSigners() []sdk.Address { // get the bytes for the message signer to sign on func (msg MsgEditValidator) GetSignBytes() []byte { - b, err := msgCdc.MarshalJSON(msg) + b, err := msgCdc.MarshalJSON(struct { + Description + ValidatorAddr string `json:"address"` + }{ + Description: msg.Description, + ValidatorAddr: sdk.MustBech32ifyVal(msg.ValidatorAddr), + }) if err != nil { panic(err) } @@ -133,7 +152,15 @@ func (msg MsgDelegate) GetSigners() []sdk.Address { // get the bytes for the message signer to sign on func (msg MsgDelegate) GetSignBytes() []byte { - b, err := msgCdc.MarshalJSON(msg) + b, err := msgCdc.MarshalJSON(struct { + DelegatorAddr string `json:"delegator_addr"` + ValidatorAddr string `json:"validator_addr"` + Bond sdk.Coin `json:"bond"` + }{ + DelegatorAddr: sdk.MustBech32ifyAcc(msg.DelegatorAddr), + ValidatorAddr: sdk.MustBech32ifyVal(msg.ValidatorAddr), + Bond: msg.Bond, + }) if err != nil { panic(err) } @@ -180,7 +207,15 @@ func (msg MsgUnbond) GetSigners() []sdk.Address { return []sdk.Address{msg.Deleg // get the bytes for the message signer to sign on func (msg MsgUnbond) GetSignBytes() []byte { - b, err := msgCdc.MarshalJSON(msg) + b, err := msgCdc.MarshalJSON(struct { + DelegatorAddr string `json:"delegator_addr"` + ValidatorAddr string `json:"validator_addr"` + Shares string `json:"shares"` + }{ + DelegatorAddr: sdk.MustBech32ifyAcc(msg.DelegatorAddr), + ValidatorAddr: sdk.MustBech32ifyVal(msg.ValidatorAddr), + Shares: msg.Shares, + }) if err != nil { panic(err) } diff --git a/x/stake/shares.go b/x/stake/shares.go index d5fe93844..e30fa3738 100644 --- a/x/stake/shares.go +++ b/x/stake/shares.go @@ -118,13 +118,14 @@ func (s PoolShares) ToBonded(p Pool) PoolShares { //_________________________________________________________________________________________________________ +// TODO better tests // get the equivalent amount of tokens contained by the shares func (s PoolShares) Tokens(p Pool) sdk.Rat { switch s.Status { case sdk.Bonded: - return p.unbondedShareExRate().Mul(s.Amount) // (tokens/shares) * shares + return p.bondedShareExRate().Mul(s.Amount) // (tokens/shares) * shares case sdk.Unbonding: - return p.unbondedShareExRate().Mul(s.Amount) + return p.unbondingShareExRate().Mul(s.Amount) case sdk.Unbonded: return p.unbondedShareExRate().Mul(s.Amount) default: diff --git a/x/stake/test_common.go b/x/stake/test_common.go index d06ac8d0a..3cea0b281 100644 --- a/x/stake/test_common.go +++ b/x/stake/test_common.go @@ -3,6 +3,7 @@ package stake import ( "bytes" "encoding/hex" + "strconv" "testing" "github.com/stretchr/testify/require" @@ -21,33 +22,8 @@ import ( // dummy addresses used for testing var ( - addrs = []sdk.Address{ - testAddr("A58856F0FD53BF058B4909A21AEC019107BA6160", "cosmosaccaddr15ky9du8a2wlstz6fpx3p4mqpjyrm5ctqyxjnwh"), - testAddr("A58856F0FD53BF058B4909A21AEC019107BA6161", "cosmosaccaddr15ky9du8a2wlstz6fpx3p4mqpjyrm5ctpesxxn9"), - testAddr("A58856F0FD53BF058B4909A21AEC019107BA6162", "cosmosaccaddr15ky9du8a2wlstz6fpx3p4mqpjyrm5ctzhrnsa6"), - testAddr("A58856F0FD53BF058B4909A21AEC019107BA6163", "cosmosaccaddr15ky9du8a2wlstz6fpx3p4mqpjyrm5ctr2489qg"), - testAddr("A58856F0FD53BF058B4909A21AEC019107BA6164", "cosmosaccaddr15ky9du8a2wlstz6fpx3p4mqpjyrm5ctytvs4pd"), - testAddr("A58856F0FD53BF058B4909A21AEC019107BA6165", "cosmosaccaddr15ky9du8a2wlstz6fpx3p4mqpjyrm5ct9k6yqul"), - testAddr("A58856F0FD53BF058B4909A21AEC019107BA6166", "cosmosaccaddr15ky9du8a2wlstz6fpx3p4mqpjyrm5ctxcf3kjq"), - testAddr("A58856F0FD53BF058B4909A21AEC019107BA6167", "cosmosaccaddr15ky9du8a2wlstz6fpx3p4mqpjyrm5ct89l9r0j"), - testAddr("A58856F0FD53BF058B4909A21AEC019107BA6168", "cosmosaccaddr15ky9du8a2wlstz6fpx3p4mqpjyrm5ctg6jkls2"), - testAddr("A58856F0FD53BF058B4909A21AEC019107BA6169", "cosmosaccaddr15ky9du8a2wlstz6fpx3p4mqpjyrm5ctf8yz2dc"), - } - - // dummy pubkeys used for testing - pks = []crypto.PubKey{ - newPubKey("0B485CFC0EECC619440448436F8FC9DF40566F2369E72400281454CB552AFB50"), - newPubKey("0B485CFC0EECC619440448436F8FC9DF40566F2369E72400281454CB552AFB51"), - newPubKey("0B485CFC0EECC619440448436F8FC9DF40566F2369E72400281454CB552AFB52"), - newPubKey("0B485CFC0EECC619440448436F8FC9DF40566F2369E72400281454CB552AFB53"), - newPubKey("0B485CFC0EECC619440448436F8FC9DF40566F2369E72400281454CB552AFB54"), - newPubKey("0B485CFC0EECC619440448436F8FC9DF40566F2369E72400281454CB552AFB55"), - newPubKey("0B485CFC0EECC619440448436F8FC9DF40566F2369E72400281454CB552AFB56"), - newPubKey("0B485CFC0EECC619440448436F8FC9DF40566F2369E72400281454CB552AFB57"), - newPubKey("0B485CFC0EECC619440448436F8FC9DF40566F2369E72400281454CB552AFB58"), - newPubKey("0B485CFC0EECC619440448436F8FC9DF40566F2369E72400281454CB552AFB59"), - } - + addrs = createTestAddrs(100) + pks = createTestPubKeys(100) emptyAddr sdk.Address emptyPubkey crypto.PubKey ) @@ -162,3 +138,36 @@ func testAddr(addr string, bech string) sdk.Address { return res } + +func createTestAddrs(numAddrs int) []sdk.Address { + var addresses []sdk.Address + var buffer bytes.Buffer + + // start at 100 so we can make up to 999 test addresses with valid test addresses + for i := 100; i < (numAddrs + 100); i++ { + numString := strconv.Itoa(i) + buffer.WriteString("A58856F0FD53BF058B4909A21AEC019107BA6") //base address string + + buffer.WriteString(numString) //adding on final two digits to make addresses unique + res, _ := sdk.GetAccAddressHex(buffer.String()) + bech, _ := sdk.Bech32ifyAcc(res) + addresses = append(addresses, testAddr(buffer.String(), bech)) + buffer.Reset() + } + return addresses +} + +func createTestPubKeys(numPubKeys int) []crypto.PubKey { + var publicKeys []crypto.PubKey + var buffer bytes.Buffer + + //start at 10 to avoid changing 1 to 01, 2 to 02, etc + for i := 100; i < (numPubKeys + 100); i++ { + numString := strconv.Itoa(i) + buffer.WriteString("0B485CFC0EECC619440448436F8FC9DF40566F2369E72400281454CB552AF") //base pubkey string + buffer.WriteString(numString) //adding on final two digits to make pubkeys unique + publicKeys = append(publicKeys, newPubKey(buffer.String())) + buffer.Reset() + } + return publicKeys +} diff --git a/x/stake/validator.go b/x/stake/validator.go index a0b484d71..3b135c12b 100644 --- a/x/stake/validator.go +++ b/x/stake/validator.go @@ -251,6 +251,7 @@ func (v Validator) DelegatorShareExRate(pool Pool) sdk.Rat { var _ sdk.Validator = Validator{} // nolint - for sdk.Validator +func (v Validator) GetMoniker() string { return v.Description.Moniker } func (v Validator) GetStatus() sdk.BondStatus { return v.Status() } func (v Validator) GetOwner() sdk.Address { return v.Owner } func (v Validator) GetPubKey() crypto.PubKey { return v.PubKey } diff --git a/x/stake/validator_test.go b/x/stake/validator_test.go index db6ab6f4c..5834e5989 100644 --- a/x/stake/validator_test.go +++ b/x/stake/validator_test.go @@ -148,7 +148,7 @@ func TestUpdateStatus(t *testing.T) { // TODO refactor this random setup // generate a random validator -func randomValidator(r *rand.Rand) Validator { +func randomValidator(r *rand.Rand, i int) Validator { poolSharesAmt := sdk.NewRat(int64(r.Int31n(10000))) delShares := sdk.NewRat(int64(r.Int31n(10000))) @@ -160,8 +160,8 @@ func randomValidator(r *rand.Rand) Validator { pShares = NewUnbondedShares(poolSharesAmt) } return Validator{ - Owner: addrs[0], - PubKey: pks[0], + Owner: addrs[i], + PubKey: pks[i], PoolShares: pShares, DelegatorShares: delShares, } @@ -173,7 +173,7 @@ func randomSetup(r *rand.Rand, numValidators int) (Pool, Validators) { validators := make([]Validator, numValidators) for i := 0; i < numValidators; i++ { - validator := randomValidator(r) + validator := randomValidator(r, i) if validator.Status() == sdk.Bonded { pool.BondedShares = pool.BondedShares.Add(validator.PoolShares.Bonded()) pool.BondedTokens += validator.PoolShares.Bonded().Evaluate()