Merge pull request #1238 from cosmos/release/v0.19.0

Release/v0.19.0
This commit is contained in:
Ethan Buchman 2018-06-13 01:24:41 -07:00 committed by GitHub
commit 6967905690
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
92 changed files with 3428 additions and 2124 deletions

View File

@ -1,8 +1,12 @@
<!-- Thanks for filing a PR! Before hitting the button, please check the following items.-->
<!-- < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < ☺
v ✰ Thanks for creating a PR! ✰
v Before smashing the submit button please review the checkboxes.
v If a checkbox is n/a - please still include it but + a little note why
☺ > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > -->
* [ ] 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))

3
.gitignore vendored
View File

@ -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
dependency-graph.png

View File

@ -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*

14
Gopkg.lock generated
View File

@ -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"

View File

@ -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

View File

@ -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
}

View File

@ -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

View File

@ -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

View File

@ -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", "<host>:<port> 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")

View File

@ -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
}

View File

@ -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 {

View File

@ -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)

View File

@ -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)
}

View File

@ -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
}

View File

@ -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

View File

@ -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)
}

View File

@ -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")

234
client/lcd/test_helpers.go Normal file
View File

@ -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)
}

View File

@ -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)
}
}

View File

@ -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")
}

View File

@ -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)
}
}

View File

@ -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
}

View File

@ -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{})
}

View File

@ -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

View File

@ -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 {

View File

@ -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()
}

View File

@ -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{}
}

View File

@ -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)
}

View File

@ -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

View File

@ -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

View File

@ -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
}

View File

@ -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()))
}

View File

@ -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()
}

View File

@ -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
}

View File

@ -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()))
}

View File

@ -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")

View File

@ -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
}

View File

@ -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}})
}

View File

@ -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)
}

View File

@ -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

View File

@ -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"
)

View File

@ -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"

View File

@ -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")
}

View File

@ -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

View File

@ -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)
}

View File

@ -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 = ""

View File

@ -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

View File

@ -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 {

View File

@ -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)

View File

@ -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
}

View File

@ -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())))

View File

@ -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.

88
x/auth/mock/app.go Normal file
View File

@ -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{}
}

View File

@ -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}})
}

View File

@ -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()
}

View File

@ -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"`
}

View File

@ -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()

208
x/bank/app_test.go Normal file
View File

@ -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}})
}

View File

@ -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 {

View File

@ -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
}

View File

@ -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 {

View File

@ -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))
}

79
x/ibc/app_test.go Normal file
View File

@ -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)
}

View File

@ -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 {

View File

@ -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
}

111
x/slashing/app_test.go Normal file
View File

@ -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)
}

View File

@ -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++

View File

@ -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)
}

View File

@ -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)
}

16
x/slashing/msg_test.go Normal file
View File

@ -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"}`)
}

View File

@ -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

157
x/stake/app_test.go Normal file
View File

@ -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{})
}

View File

@ -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)

View File

@ -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++

View File

@ -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
}

View File

@ -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)

View File

@ -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%.

View File

@ -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)
}
}
}

View File

@ -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++

View File

@ -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

View File

@ -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)

View File

@ -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)
}

View File

@ -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:

View File

@ -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
}

View File

@ -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 }

View File

@ -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()