working gaia store

This commit is contained in:
rigelrozanski 2018-01-18 08:39:16 +00:00
parent 266a8392d3
commit c2ddc582c4
21 changed files with 100 additions and 3024 deletions

View File

@ -1,97 +0,0 @@
package main
import (
"github.com/spf13/cobra"
"github.com/cosmos/cosmos-sdk/client/commands"
"github.com/cosmos/cosmos-sdk/client/commands/commits"
"github.com/cosmos/cosmos-sdk/client/commands/keys"
"github.com/cosmos/cosmos-sdk/client/commands/proxy"
"github.com/cosmos/cosmos-sdk/client/commands/query"
rpccmd "github.com/cosmos/cosmos-sdk/client/commands/rpc"
txcmd "github.com/cosmos/cosmos-sdk/client/commands/txs"
authcmd "github.com/cosmos/cosmos-sdk/modules/auth/commands"
basecmd "github.com/cosmos/cosmos-sdk/modules/base/commands"
coincmd "github.com/cosmos/cosmos-sdk/modules/coin/commands"
feecmd "github.com/cosmos/cosmos-sdk/modules/fee/commands"
ibccmd "github.com/cosmos/cosmos-sdk/modules/ibc/commands"
noncecmd "github.com/cosmos/cosmos-sdk/modules/nonce/commands"
rolecmd "github.com/cosmos/cosmos-sdk/modules/roles/commands"
stakecmd "github.com/cosmos/gaia/modules/stake/commands"
)
// clientCmd is the entry point for this binary
var clientCmd = &cobra.Command{
Use: "client",
Short: "Gaia light client",
Run: func(cmd *cobra.Command, args []string) {
cmd.Help()
},
}
func prepareClientCommands() {
commands.AddBasicFlags(clientCmd)
// Prepare queries
query.RootCmd.AddCommand(
// These are default parsers, but optional in your app (you can remove key)
query.TxQueryCmd,
query.KeyQueryCmd,
coincmd.AccountQueryCmd,
noncecmd.NonceQueryCmd,
rolecmd.RoleQueryCmd,
ibccmd.IBCQueryCmd,
//stakecmd.CmdQueryValidator,
stakecmd.CmdQueryCandidates,
stakecmd.CmdQueryCandidate,
stakecmd.CmdQueryDelegatorBond,
stakecmd.CmdQueryDelegatorCandidates,
)
// set up the middleware
txcmd.Middleware = txcmd.Wrappers{
feecmd.FeeWrapper{},
rolecmd.RoleWrapper{},
noncecmd.NonceWrapper{},
basecmd.ChainWrapper{},
authcmd.SigWrapper{},
}
txcmd.Middleware.Register(txcmd.RootCmd.PersistentFlags())
// you will always want this for the base send command
txcmd.RootCmd.AddCommand(
// This is the default transaction, optional in your app
coincmd.SendTxCmd,
coincmd.CreditTxCmd,
// this enables creating roles
rolecmd.CreateRoleTxCmd,
// these are for handling ibc
ibccmd.RegisterChainTxCmd,
ibccmd.UpdateChainTxCmd,
ibccmd.PostPacketTxCmd,
stakecmd.CmdDeclareCandidacy,
stakecmd.CmdEditCandidacy,
stakecmd.CmdDelegate,
stakecmd.CmdUnbond,
)
clientCmd.AddCommand(
proxy.RootCmd,
lineBreak,
txcmd.RootCmd,
query.RootCmd,
rpccmd.RootCmd,
lineBreak,
keys.RootCmd,
commands.InitCmd,
commands.ResetCmd,
commits.RootCmd,
lineBreak,
)
}

View File

@ -1,50 +0,0 @@
package main
import (
"os"
"github.com/spf13/cobra"
"github.com/tendermint/tmlibs/cli"
basecmd "github.com/cosmos/cosmos-sdk/server/commands"
"github.com/cosmos/gaia/version"
)
// GaiaCmd is the entry point for this binary
var (
GaiaCmd = &cobra.Command{
Use: "gaia",
Short: "The Cosmos Network delegation-game test",
Run: func(cmd *cobra.Command, args []string) {
cmd.Help()
},
}
lineBreak = &cobra.Command{Run: func(*cobra.Command, []string) {}}
)
func main() {
// disable sorting
cobra.EnableCommandSorting = false
// add commands
prepareNodeCommands()
prepareRestServerCommands()
prepareClientCommands()
GaiaCmd.AddCommand(
nodeCmd,
restServerCmd,
clientCmd,
lineBreak,
version.VersionCmd,
//auto.AutoCompleteCmd,
)
// prepare and add flags
basecmd.SetUpRoot(GaiaCmd)
executor := cli.PrepareMainCmd(GaiaCmd, "GA", os.ExpandEnv("$HOME/.cosmos-gaia-cli"))
executor.Execute()
}

View File

@ -1,69 +0,0 @@
package main
import (
"github.com/spf13/cobra"
abci "github.com/tendermint/abci/types"
sdk "github.com/cosmos/cosmos-sdk"
"github.com/cosmos/cosmos-sdk/modules/auth"
"github.com/cosmos/cosmos-sdk/modules/base"
"github.com/cosmos/cosmos-sdk/modules/coin"
"github.com/cosmos/cosmos-sdk/modules/fee"
"github.com/cosmos/cosmos-sdk/modules/ibc"
"github.com/cosmos/cosmos-sdk/modules/nonce"
"github.com/cosmos/cosmos-sdk/modules/roles"
basecmd "github.com/cosmos/cosmos-sdk/server/commands"
"github.com/cosmos/cosmos-sdk/stack"
"github.com/cosmos/cosmos-sdk/state"
"github.com/cosmos/gaia/modules/stake"
)
// nodeCmd is the entry point for this binary
var nodeCmd = &cobra.Command{
Use: "node",
Short: "The Cosmos Network delegation-game blockchain test",
Run: func(cmd *cobra.Command, args []string) { cmd.Help() },
}
func prepareNodeCommands() {
basecmd.Handler = stack.New(
base.Logger{},
stack.Recovery{},
auth.Signatures{},
base.Chain{},
stack.Checkpoint{OnCheck: true},
nonce.ReplayCheck{},
).
IBC(ibc.NewMiddleware()).
Apps(
roles.NewMiddleware(),
fee.NewSimpleFeeMiddleware(coin.Coin{"fermion", 0}, fee.Bank),
stack.Checkpoint{OnDeliver: true},
).
Dispatch(
coin.NewHandler(),
stack.WrapHandler(roles.NewHandler()),
stack.WrapHandler(ibc.NewHandler()),
stake.NewHandler(),
)
nodeCmd.AddCommand(
basecmd.GetInitCmd("fermion", []string{"stake/allowed_bond_denom/fermion"}),
basecmd.GetTickStartCmd(sdk.TickerFunc(tickFn)),
basecmd.UnsafeResetAllCmd,
)
}
// Tick - Called every block even if no transaction, process all queues,
// validator rewards, and calculate the validator set difference
func tickFn(ctx sdk.Context, store state.SimpleDB) (change []*abci.Validator, err error) {
// first need to prefix the store, at this point it's a global store
store = stack.PrefixedStore(stake.Name(), store)
// execute Tick
change, err = stake.Tick(ctx, store)
return
}

View File

@ -1,89 +0,0 @@
package main
import (
"fmt"
"log"
"net/http"
"github.com/gorilla/mux"
"github.com/spf13/cobra"
"github.com/spf13/viper"
"github.com/tendermint/tmlibs/cli"
"github.com/cosmos/cosmos-sdk/client"
"github.com/cosmos/cosmos-sdk/client/commands"
rest "github.com/cosmos/cosmos-sdk/client/rest"
coinrest "github.com/cosmos/cosmos-sdk/modules/coin/rest"
noncerest "github.com/cosmos/cosmos-sdk/modules/nonce/rest"
rolerest "github.com/cosmos/cosmos-sdk/modules/roles/rest"
stakerest "github.com/cosmos/gaia/modules/stake/rest"
)
const defaultAlgo = "ed25519"
var (
restServerCmd = &cobra.Command{
Use: "rest-server",
Short: "REST client for gaia commands",
Long: `Gaiaserver presents a nice (not raw hex) interface to the gaia blockchain structure.`,
RunE: func(cmd *cobra.Command, args []string) error {
return cmdRestServer(cmd, args)
},
}
flagPort = "port"
)
func prepareRestServerCommands() {
commands.AddBasicFlags(restServerCmd)
restServerCmd.PersistentFlags().IntP(flagPort, "p", 8998, "port to run the server on")
}
func cmdRestServer(cmd *cobra.Command, args []string) error {
router := mux.NewRouter()
rootDir := viper.GetString(cli.HomeFlag)
keyMan := client.GetKeyManager(rootDir)
serviceKeys := rest.NewServiceKeys(keyMan)
serviceTxs := rest.NewServiceTxs(commands.GetNode())
routeRegistrars := []func(*mux.Router) error{
// rest.Keys handlers
serviceKeys.RegisterCRUD,
// Coin handlers (Send, Query, SearchSent)
coinrest.RegisterAll,
// Roles createRole handler
rolerest.RegisterCreateRole,
// Gaia sign transactions handler
serviceKeys.RegisterSignTx,
// Gaia post transaction handler
serviceTxs.RegisterPostTx,
// Nonce query handler
noncerest.RegisterQueryNonce,
// Staking query handlers
stakerest.RegisterQueryCandidate,
stakerest.RegisterQueryCandidates,
stakerest.RegisterQueryDelegatorBond,
stakerest.RegisterQueryDelegatorCandidates,
// Staking tx builders
stakerest.RegisterDelegate,
stakerest.RegisterUnbond,
}
for _, routeRegistrar := range routeRegistrars {
if err := routeRegistrar(router); err != nil {
log.Fatal(err)
}
}
addr := fmt.Sprintf(":%d", viper.GetInt(flagPort))
log.Printf("Serving on %q", addr)
return http.ListenAndServe(addr, router)
}

View File

@ -1,275 +0,0 @@
#!/bin/bash
set -u
# These global variables are required for common.sh
SERVER_EXE="gaia node"
CLIENT_EXE="gaia client"
ACCOUNTS=(jae ethan bucky rigel igor)
RICH=${ACCOUNTS[0]}
DELEGATOR=${ACCOUNTS[2]}
POOR=${ACCOUNTS[4]}
BASE_DIR=$HOME/stake_test
BASE_DIR2=$HOME/stake_test2
SERVER1=$BASE_DIR/server
SERVER2=$BASE_DIR2/server
oneTimeSetUp() {
#[ "$2" ] || echo "missing parameters, line=${LINENO}" ; exit 1;
# These are passed in as args
CHAIN_ID="stake_test"
# TODO Make this more robust
if [ "$BASE_DIR" == "$HOME/" ]; then
echo "Must be called with argument, or it will wipe your home directory"
exit 1
fi
rm -rf $BASE_DIR 2>/dev/null
mkdir -p $BASE_DIR
if [ "$BASE_DIR2" == "$HOME/" ]; then
echo "Must be called with argument, or it will wipe your home directory"
exit 1
fi
rm -rf $BASE_DIR2 2>/dev/null
mkdir -p $BASE_DIR2
# Set up client - make sure you use the proper prefix if you set
# a custom CLIENT_EXE
export BC_HOME=${BASE_DIR}/client
prepareClient
# start the node server
set +u ; initServer $BASE_DIR $CHAIN_ID ; set -u
if [ $? != 0 ]; then return 1; fi
set +u ; initClient $CHAIN_ID ; set -u
if [ $? != 0 ]; then return 1; fi
printf "...Testing may begin!\n\n\n"
}
oneTimeTearDown() {
kill -9 $PID_SERVER2 >/dev/null 2>&1
set +u ; quickTearDown ; set -u
}
# Ex Usage: checkCandidate $PUBKEY $EXPECTED_VOTING_POWER
checkCandidate() {
CANDIDATE=$(${CLIENT_EXE} query candidate --pubkey=$1)
if ! assertTrue "line=${LINENO}, bad query" $?; then
return 1
fi
assertEquals "line=${LINENO}, proper voting power" "$2" $(echo $CANDIDATE | jq .data.voting_power)
return $?
}
# Ex Usage: checkCandidate $PUBKEY
checkCandidateEmpty() {
CANDIDATE=$(${CLIENT_EXE} query candidate --pubkey=$1 2>/dev/null)
if ! assertFalse "line=${LINENO}, expected empty query" $?; then
return 1
fi
}
# Ex Usage: checkCandidate $DELEGATOR_ADDR $PUBKEY $EXPECTED_SHARES
checkDelegatorBond() {
BOND=$(${CLIENT_EXE} query delegator-bond --delegator-address=$1 --pubkey=$2)
if ! assertTrue "line=${LINENO}, account must exist" $?; then
return 1
fi
assertEquals "line=${LINENO}, proper bond amount" "$3" $(echo $BOND | jq .data.Shares)
return $?
}
# Ex Usage: checkCandidate $DELEGATOR_ADDR $PUBKEY
checkDelegatorBondEmpty() {
BOND=$(${CLIENT_EXE} query delegator-bond --delegator-address=$1 --pubkey=$2 2>/dev/null)
if ! assertFalse "line=${LINENO}, expected empty query" $?; then
return 1
fi
}
#______________________________________________________________________________________
test00GetAccount() {
SENDER=$(getAddr $RICH)
RECV=$(getAddr $POOR)
assertFalse "line=${LINENO}, requires arg" "${CLIENT_EXE} query account"
set +u ; checkAccount $SENDER "9007199254740992" ; set -u
ACCT2=$(${CLIENT_EXE} query account $RECV 2>/dev/null)
assertFalse "line=${LINENO}, has no genesis account" $?
}
test01SendTx() {
assertFalse "line=${LINENO}, missing dest" "${CLIENT_EXE} tx send --amount=992fermion --sequence=1"
assertFalse "line=${LINENO}, bad password" "echo foo | ${CLIENT_EXE} tx send --amount=992fermion --sequence=1 --to=$RECV --name=$RICH"
TX=$(echo qwertyuiop | ${CLIENT_EXE} tx send --amount=992fermion --sequence=1 --to=$RECV --name=$RICH)
txSucceeded $? "$TX" "$RECV"
HASH=$(echo $TX | jq .hash | tr -d \")
TX_HEIGHT=$(echo $TX | jq .height)
set +u
checkAccount $SENDER "9007199254740000" $TX_HEIGHT
# make sure 0x prefix also works
checkAccount "0x$SENDER" "9007199254740000" $TX_HEIGHT
checkAccount $RECV "992" $TX_HEIGHT
# Make sure tx is indexed
checkSendTx $HASH $TX_HEIGHT $SENDER "992"
set -u
}
test02DeclareCandidacy() {
# the premise of this test is to run a second validator (from rich) and then bond and unbond some tokens
# first create a second node to run and connect to the system
# init the second node
SERVER_LOG2=$BASE_DIR2/node2.log
GENKEY=$(${CLIENT_EXE} keys get ${RICH} | awk '{print $2}')
${SERVER_EXE} init $GENKEY --chain-id $CHAIN_ID --home=$SERVER2 >>$SERVER_LOG2
if [ $? != 0 ]; then return 1; fi
# copy in the genesis from the first initialization to the new server
cp $SERVER1/genesis.json $SERVER2/genesis.json
# point the new config to the old server location
rm $SERVER2/config.toml
echo 'proxy_app = "tcp://127.0.0.1:46668"
moniker = "anonymous"
fast_sync = true
db_backend = "leveldb"
log_level = "state:info,*:error"
[rpc]
laddr = "tcp://0.0.0.0:46667"
[p2p]
laddr = "tcp://0.0.0.0:46666"
seeds = "0.0.0.0:46656"' >$SERVER2/config.toml
# start the second node
${SERVER_EXE} start --home=$SERVER2 >>$SERVER_LOG2 2>&1 &
sleep 1
PID_SERVER2=$!
disown
if ! ps $PID_SERVER2 >/dev/null; then
echo "**FAILED**"
cat $SERVER_LOG2
return 1
fi
# get the pubkey of the second validator
PK2=$(cat $SERVER2/priv_validator.json | jq -r .pub_key.data)
CAND_ADDR=$(getAddr $POOR)
TX=$(echo qwertyuiop | ${CLIENT_EXE} tx declare-candidacy --sequence=1 --amount=10fermion --name=$POOR --pubkey=$PK2 --moniker=rigey)
if [ $? != 0 ]; then return 1; fi
HASH=$(echo $TX | jq .hash | tr -d \")
TX_HEIGHT=$(echo $TX | jq .height)
set +u ; checkAccount $CAND_ADDR "982" $TX_HEIGHT ; set -u
checkCandidate $PK2 "10"
checkDelegatorBond $CAND_ADDR $PK2 "10"
}
test03Delegate() {
# send some coins to a delegator
DELA_ADDR=$(getAddr $DELEGATOR)
TX=$(echo qwertyuiop | ${CLIENT_EXE} tx send --sequence=2 --amount=15fermion --to=$DELA_ADDR --name=$RICH)
txSucceeded $? "$TX" "$DELA_ADDR"
TX_HEIGHT=$(echo $TX | jq .height)
set +u ; checkAccount $DELA_ADDR "15" $TX_HEIGHT ; set -u
# delegate some coins to the new
TX=$(echo qwertyuiop | ${CLIENT_EXE} tx delegate --sequence=1 --amount=10fermion --name=$DELEGATOR --pubkey=$PK2)
if [ $? != 0 ]; then return 1; fi
TX_HEIGHT=$(echo $TX | jq .height)
set +u ; checkAccount $DELA_ADDR "5" $TX_HEIGHT ; set -u
checkCandidate $PK2 "20"
checkDelegatorBond $DELA_ADDR $PK2 "10"
TX=$(echo qwertyuiop | ${CLIENT_EXE} tx delegate --sequence=2 --amount=3fermion --name=$DELEGATOR --pubkey=$PK2)
if [ $? != 0 ]; then return 1; fi
TX_HEIGHT=$(echo $TX | jq .height)
set +u ; checkAccount $DELA_ADDR "2" $TX_HEIGHT ; set -u
checkCandidate $PK2 "23"
checkDelegatorBond $DELA_ADDR $PK2 "13"
# attempt a delegation without enough funds
# NOTE the sequence number still increments here because it will fail
# only during DeliverTx - however this should be updated (TODO) in new
# SDK when we can fail in CheckTx
TX=$(echo qwertyuiop | ${CLIENT_EXE} tx delegate --sequence=3 --amount=3fermion --name=$DELEGATOR --pubkey=$PK2 2>/dev/null)
if [ $? == 0 ]; then return 1; fi
TX_HEIGHT=$(echo $TX | jq .height)
set +u ; checkAccount $DELA_ADDR "2" $TX_HEIGHT ; set -u
checkCandidate $PK2 "23"
checkDelegatorBond $DELA_ADDR $PK2 "13"
# perform the final delegation which should empty the delegators account
TX=$(echo qwertyuiop | ${CLIENT_EXE} tx delegate --sequence=4 --amount=2fermion --name=$DELEGATOR --pubkey=$PK2)
if [ $? != 0 ]; then return 1; fi
TX_HEIGHT=$(echo $TX | jq .height)
set +u ; checkAccount $DELA_ADDR "null" $TX_HEIGHT ; set -u #empty account is null
checkCandidate $PK2 "25"
}
test04Unbond() {
# unbond from the delegator a bit
TX=$(echo qwertyuiop | ${CLIENT_EXE} tx unbond --sequence=5 --shares=10 --name=$DELEGATOR --pubkey=$PK2)
TX_HEIGHT=$(echo $TX | jq .height)
set +u ; checkAccount $DELA_ADDR "10" $TX_HEIGHT ; set -u
checkCandidate $PK2 "15"
checkDelegatorBond $DELA_ADDR $PK2 "5"
# attempt to unbond more shares than exist
TX=$(echo qwertyuiop | ${CLIENT_EXE} tx unbond --sequence=6 --shares=10 --name=$DELEGATOR --pubkey=$PK2 2>/dev/null)
if [ $? == 0 ]; then return 1; fi
TX_HEIGHT=$(echo $TX | jq .height)
set +u ; checkAccount $DELA_ADDR "10" $TX_HEIGHT ; set -u
checkCandidate $PK2 "15"
checkDelegatorBond $DELA_ADDR $PK2 "5"
# unbond entirely from the delegator
TX=$(echo qwertyuiop | ${CLIENT_EXE} tx unbond --sequence=6 --shares=5 --name=$DELEGATOR --pubkey=$PK2)
TX_HEIGHT=$(echo $TX | jq .height)
set +u ; checkAccount $DELA_ADDR "15" $TX_HEIGHT ; set -u
checkCandidate $PK2 "10"
checkDelegatorBondEmpty $DELA_ADDR $PK2
# unbond a bit from the owner
TX=$(echo qwertyuiop | ${CLIENT_EXE} tx unbond --sequence=2 --shares=5 --name=$POOR --pubkey=$PK2)
TX_HEIGHT=$(echo $TX | jq .height)
set +u ; checkAccount $CAND_ADDR "987" $TX_HEIGHT ; set -u
checkCandidate $PK2 "5"
checkDelegatorBond $CAND_ADDR $PK2 "5"
# attempt to unbond more shares than exist
TX=$(echo qwertyuiop | ${CLIENT_EXE} tx unbond --sequence=3 --shares=10 --name=$POOR --pubkey=$PK2 2>/dev/null)
if [ $? == 0 ]; then return 1; fi
TX_HEIGHT=$(echo $TX | jq .height)
set +u ; checkAccount $CAND_ADDR "987" $TX_HEIGHT ; set -u
checkCandidate $PK2 "5"
checkDelegatorBond $CAND_ADDR $PK2 "5"
# unbond entirely from the validator
TX=$(echo qwertyuiop | ${CLIENT_EXE} tx unbond --sequence=3 --shares=5 --name=$POOR --pubkey=$PK2)
TX_HEIGHT=$(echo $TX | jq .height)
set +u ; checkAccount $CAND_ADDR "992" $TX_HEIGHT ; set -u
checkCandidateEmpty $PK2
checkDelegatorBondEmpty $CAND_ADDR $PK2
}
# Load common then run these tests with shunit2!
CLI_DIR=$GOPATH/src/github.com/cosmos/gaia/vendor/github.com/cosmos/cosmos-sdk/tests/cli
. $CLI_DIR/common.sh
. $CLI_DIR/shunit2

View File

@ -1,136 +0,0 @@
package commands
import (
"github.com/spf13/cobra"
flag "github.com/spf13/pflag"
"github.com/spf13/viper"
crypto "github.com/tendermint/go-crypto"
"github.com/cosmos/cosmos-sdk/client/commands"
"github.com/cosmos/cosmos-sdk/client/commands/query"
"github.com/cosmos/cosmos-sdk/modules/coin"
"github.com/cosmos/cosmos-sdk/stack"
"github.com/cosmos/gaia/modules/stake"
)
//nolint
var (
CmdQueryCandidates = &cobra.Command{
Use: "candidates",
Short: "Query for the set of validator-candidates pubkeys",
RunE: cmdQueryCandidates,
}
CmdQueryCandidate = &cobra.Command{
Use: "candidate",
Short: "Query a validator-candidate account",
RunE: cmdQueryCandidate,
}
CmdQueryDelegatorBond = &cobra.Command{
Use: "delegator-bond",
Short: "Query a delegators bond based on address and candidate pubkey",
RunE: cmdQueryDelegatorBond,
}
CmdQueryDelegatorCandidates = &cobra.Command{
Use: "delegator-candidates",
RunE: cmdQueryDelegatorCandidates,
Short: "Query all delegators candidates' pubkeys based on address",
}
FlagDelegatorAddress = "delegator-address"
)
func init() {
//Add Flags
fsPk := flag.NewFlagSet("", flag.ContinueOnError)
fsPk.String(FlagPubKey, "", "PubKey of the validator-candidate")
fsAddr := flag.NewFlagSet("", flag.ContinueOnError)
fsAddr.String(FlagDelegatorAddress, "", "Delegator Hex Address")
CmdQueryCandidate.Flags().AddFlagSet(fsPk)
CmdQueryDelegatorBond.Flags().AddFlagSet(fsPk)
CmdQueryDelegatorBond.Flags().AddFlagSet(fsAddr)
CmdQueryDelegatorCandidates.Flags().AddFlagSet(fsAddr)
}
func cmdQueryCandidates(cmd *cobra.Command, args []string) error {
var pks []crypto.PubKey
prove := !viper.GetBool(commands.FlagTrustNode)
key := stack.PrefixedKey(stake.Name(), stake.CandidatesPubKeysKey)
height, err := query.GetParsed(key, &pks, query.GetHeight(), prove)
if err != nil {
return err
}
return query.OutputProof(pks, height)
}
func cmdQueryCandidate(cmd *cobra.Command, args []string) error {
var candidate stake.Candidate
pk, err := GetPubKey(viper.GetString(FlagPubKey))
if err != nil {
return err
}
prove := !viper.GetBool(commands.FlagTrustNode)
key := stack.PrefixedKey(stake.Name(), stake.GetCandidateKey(pk))
height, err := query.GetParsed(key, &candidate, query.GetHeight(), prove)
if err != nil {
return err
}
return query.OutputProof(candidate, height)
}
func cmdQueryDelegatorBond(cmd *cobra.Command, args []string) error {
var bond stake.DelegatorBond
pk, err := GetPubKey(viper.GetString(FlagPubKey))
if err != nil {
return err
}
delegatorAddr := viper.GetString(FlagDelegatorAddress)
delegator, err := commands.ParseActor(delegatorAddr)
if err != nil {
return err
}
delegator = coin.ChainAddr(delegator)
prove := !viper.GetBool(commands.FlagTrustNode)
key := stack.PrefixedKey(stake.Name(), stake.GetDelegatorBondKey(delegator, pk))
height, err := query.GetParsed(key, &bond, query.GetHeight(), prove)
if err != nil {
return err
}
return query.OutputProof(bond, height)
}
func cmdQueryDelegatorCandidates(cmd *cobra.Command, args []string) error {
delegatorAddr := viper.GetString(FlagDelegatorAddress)
delegator, err := commands.ParseActor(delegatorAddr)
if err != nil {
return err
}
delegator = coin.ChainAddr(delegator)
prove := !viper.GetBool(commands.FlagTrustNode)
key := stack.PrefixedKey(stake.Name(), stake.GetDelegatorBondsKey(delegator))
var candidates []crypto.PubKey
height, err := query.GetParsed(key, &candidates, query.GetHeight(), prove)
if err != nil {
return err
}
return query.OutputProof(candidates, height)
}

View File

@ -1,195 +0,0 @@
package commands
import (
"encoding/hex"
"fmt"
"github.com/spf13/cobra"
flag "github.com/spf13/pflag"
"github.com/spf13/viper"
crypto "github.com/tendermint/go-crypto"
"github.com/tendermint/tmlibs/rational"
txcmd "github.com/cosmos/cosmos-sdk/client/commands/txs"
"github.com/cosmos/cosmos-sdk/modules/coin"
"github.com/cosmos/gaia/modules/stake"
)
// nolint
const (
FlagPubKey = "pubkey"
FlagAmount = "amount"
FlagShares = "shares"
FlagMoniker = "moniker"
FlagIdentity = "keybase-sig"
FlagWebsite = "website"
FlagDetails = "details"
)
// nolint
var (
CmdDeclareCandidacy = &cobra.Command{
Use: "declare-candidacy",
Short: "create new validator-candidate account and delegate some coins to it",
RunE: cmdDeclareCandidacy,
}
CmdEditCandidacy = &cobra.Command{
Use: "edit-candidacy",
Short: "edit and existing validator-candidate account",
RunE: cmdEditCandidacy,
}
CmdDelegate = &cobra.Command{
Use: "delegate",
Short: "delegate coins to an existing validator/candidate",
RunE: cmdDelegate,
}
CmdUnbond = &cobra.Command{
Use: "unbond",
Short: "unbond coins from a validator/candidate",
RunE: cmdUnbond,
}
)
func init() {
// define the flags
fsPk := flag.NewFlagSet("", flag.ContinueOnError)
fsPk.String(FlagPubKey, "", "PubKey of the validator-candidate")
fsAmount := flag.NewFlagSet("", flag.ContinueOnError)
fsAmount.String(FlagAmount, "1fermion", "Amount of coins to bond")
fsShares := flag.NewFlagSet("", flag.ContinueOnError)
fsShares.String(FlagShares, "", "Amount of shares to unbond, either in decimal or keyword MAX (ex. 1.23456789, 99, MAX)")
fsCandidate := flag.NewFlagSet("", flag.ContinueOnError)
fsCandidate.String(FlagMoniker, "", "validator-candidate name")
fsCandidate.String(FlagIdentity, "", "optional keybase signature")
fsCandidate.String(FlagWebsite, "", "optional website")
fsCandidate.String(FlagDetails, "", "optional detailed description space")
// add the flags
CmdDelegate.Flags().AddFlagSet(fsPk)
CmdDelegate.Flags().AddFlagSet(fsAmount)
CmdUnbond.Flags().AddFlagSet(fsPk)
CmdUnbond.Flags().AddFlagSet(fsShares)
CmdDeclareCandidacy.Flags().AddFlagSet(fsPk)
CmdDeclareCandidacy.Flags().AddFlagSet(fsAmount)
CmdDeclareCandidacy.Flags().AddFlagSet(fsCandidate)
CmdEditCandidacy.Flags().AddFlagSet(fsPk)
CmdEditCandidacy.Flags().AddFlagSet(fsCandidate)
}
func cmdDeclareCandidacy(cmd *cobra.Command, args []string) error {
amount, err := coin.ParseCoin(viper.GetString(FlagAmount))
if err != nil {
return err
}
pk, err := GetPubKey(viper.GetString(FlagPubKey))
if err != nil {
return err
}
if viper.GetString(FlagMoniker) == "" {
return fmt.Errorf("please enter a moniker for the validator-candidate using --moniker")
}
description := stake.Description{
Moniker: viper.GetString(FlagMoniker),
Identity: viper.GetString(FlagIdentity),
Website: viper.GetString(FlagWebsite),
Details: viper.GetString(FlagDetails),
}
tx := stake.NewTxDeclareCandidacy(amount, pk, description)
return txcmd.DoTx(tx)
}
func cmdEditCandidacy(cmd *cobra.Command, args []string) error {
pk, err := GetPubKey(viper.GetString(FlagPubKey))
if err != nil {
return err
}
description := stake.Description{
Moniker: viper.GetString(FlagMoniker),
Identity: viper.GetString(FlagIdentity),
Website: viper.GetString(FlagWebsite),
Details: viper.GetString(FlagDetails),
}
tx := stake.NewTxEditCandidacy(pk, description)
return txcmd.DoTx(tx)
}
func cmdDelegate(cmd *cobra.Command, args []string) error {
amount, err := coin.ParseCoin(viper.GetString(FlagAmount))
if err != nil {
return err
}
pk, err := GetPubKey(viper.GetString(FlagPubKey))
if err != nil {
return err
}
tx := stake.NewTxDelegate(amount, pk)
return txcmd.DoTx(tx)
}
func cmdUnbond(cmd *cobra.Command, args []string) error {
// TODO once go-wire refactored the shares can be broadcast as a Rat instead of a string
// check the shares before broadcasting
sharesStr := viper.GetString(FlagShares)
var shares rational.Rat
if sharesStr != "MAX" {
var err error
shares, err = rational.NewFromDecimal(sharesStr)
if err != nil {
return err
}
if !shares.GT(rational.Zero) {
return fmt.Errorf("shares must be positive integer or decimal (ex. 123, 1.23456789)")
}
}
pk, err := GetPubKey(viper.GetString(FlagPubKey))
if err != nil {
return err
}
tx := stake.NewTxUnbond(sharesStr, pk)
return txcmd.DoTx(tx)
}
// GetPubKey - create the pubkey from a pubkey string
func GetPubKey(pubKeyStr string) (pk crypto.PubKey, err error) {
if len(pubKeyStr) == 0 {
err = fmt.Errorf("must use --pubkey flag")
return
}
if len(pubKeyStr) != 64 { //if len(pkBytes) != 32 {
err = fmt.Errorf("pubkey must be Ed25519 hex encoded string which is 64 characters long")
return
}
var pkBytes []byte
pkBytes, err = hex.DecodeString(pubKeyStr)
if err != nil {
return
}
var pkEd crypto.PubKeyEd25519
copy(pkEd[:], pkBytes[:])
pk = pkEd.Wrap()
return
}

View File

@ -1,53 +0,0 @@
// nolint
package stake
import (
"fmt"
"github.com/cosmos/cosmos-sdk/errors"
)
var (
errCandidateEmpty = fmt.Errorf("Cannot bond to an empty candidate")
errBadBondingDenom = fmt.Errorf("Invalid coin denomination")
errBadBondingAmount = fmt.Errorf("Amount must be > 0")
errNoBondingAcct = fmt.Errorf("No bond account for this (address, validator) pair")
errCommissionNegative = fmt.Errorf("Commission must be positive")
errCommissionHuge = fmt.Errorf("Commission cannot be more than 100%")
errBadValidatorAddr = fmt.Errorf("Validator does not exist for that address")
errCandidateExistsAddr = fmt.Errorf("Candidate already exist, cannot re-declare candidacy")
errMissingSignature = fmt.Errorf("Missing signature")
errBondNotNominated = fmt.Errorf("Cannot bond to non-nominated account")
errNoCandidateForAddress = fmt.Errorf("Validator does not exist for that address")
errNoDelegatorForAddress = fmt.Errorf("Delegator does not contain validator bond")
errInsufficientFunds = fmt.Errorf("Insufficient bond shares")
errBadRemoveValidator = fmt.Errorf("Error removing validator")
invalidInput = errors.CodeTypeBaseInvalidInput
)
func ErrBadValidatorAddr() error {
return errors.WithCode(errBadValidatorAddr, errors.CodeTypeBaseUnknownAddress)
}
func ErrCandidateExistsAddr() error {
return errors.WithCode(errCandidateExistsAddr, errors.CodeTypeBaseInvalidInput)
}
func ErrMissingSignature() error {
return errors.WithCode(errMissingSignature, errors.CodeTypeUnauthorized)
}
func ErrBondNotNominated() error {
return errors.WithCode(errBondNotNominated, errors.CodeTypeBaseInvalidOutput)
}
func ErrNoCandidateForAddress() error {
return errors.WithCode(errNoCandidateForAddress, errors.CodeTypeBaseUnknownAddress)
}
func ErrNoDelegatorForAddress() error {
return errors.WithCode(errNoDelegatorForAddress, errors.CodeTypeBaseInvalidInput)
}
func ErrInsufficientFunds() error {
return errors.WithCode(errInsufficientFunds, errors.CodeTypeBaseInvalidInput)
}
func ErrBadRemoveValidator() error {
return errors.WithCode(errBadRemoveValidator, errors.CodeTypeInternalErr)
}

View File

@ -1,529 +0,0 @@
package stake
import (
"fmt"
"strconv"
"github.com/spf13/viper"
"github.com/tendermint/tmlibs/log"
"github.com/tendermint/tmlibs/rational"
"github.com/cosmos/cosmos-sdk"
"github.com/cosmos/cosmos-sdk/errors"
"github.com/cosmos/cosmos-sdk/modules/auth"
"github.com/cosmos/cosmos-sdk/modules/coin"
"github.com/cosmos/cosmos-sdk/stack"
"github.com/cosmos/cosmos-sdk/state"
)
// nolint
const stakingModuleName = "stake"
// Name is the name of the modules.
func Name() string {
return stakingModuleName
}
//_______________________________________________________________________
// DelegatedProofOfStake - interface to enforce delegation stake
type delegatedProofOfStake interface {
declareCandidacy(TxDeclareCandidacy) error
editCandidacy(TxEditCandidacy) error
delegate(TxDelegate) error
unbond(TxUnbond) error
}
type coinSend interface {
transferFn(sender, receiver sdk.Actor, coins coin.Coins) error
}
//_______________________________________________________________________
// Handler - the transaction processing handler
type Handler struct {
stack.PassInitValidate
}
var _ stack.Dispatchable = Handler{} // enforce interface at compile time
// NewHandler returns a new Handler with the default Params
func NewHandler() Handler {
return Handler{}
}
// Name - return stake namespace
func (Handler) Name() string {
return stakingModuleName
}
// AssertDispatcher - placeholder for stack.Dispatchable
func (Handler) AssertDispatcher() {}
// InitState - set genesis parameters for staking
func (h Handler) InitState(l log.Logger, store state.SimpleDB,
module, key, value string, cb sdk.InitStater) (log string, err error) {
return "", h.initState(module, key, value, store)
}
// separated for testing
func (Handler) initState(module, key, value string, store state.SimpleDB) error {
if module != stakingModuleName {
return errors.ErrUnknownModule(module)
}
params := loadParams(store)
switch key {
case "allowed_bond_denom":
params.AllowedBondDenom = value
case "max_vals",
"gas_bond",
"gas_unbond":
// TODO: enforce non-negative integers in input
i, err := strconv.Atoi(value)
if err != nil {
return fmt.Errorf("input must be integer, Error: %v", err.Error())
}
switch key {
case "max_vals":
params.MaxVals = uint16(i)
case "gas_bond":
params.GasDelegate = int64(i)
case "gas_unbound":
params.GasUnbond = int64(i)
}
default:
return errors.ErrUnknownKey(key)
}
saveParams(store, params)
return nil
}
// CheckTx checks if the tx is properly structured
func (h Handler) CheckTx(ctx sdk.Context, store state.SimpleDB,
tx sdk.Tx, _ sdk.Checker) (res sdk.CheckResult, err error) {
err = tx.ValidateBasic()
if err != nil {
return res, err
}
// get the sender
sender, err := getTxSender(ctx)
if err != nil {
return res, err
}
params := loadParams(store)
// create the new checker object to
checker := check{
store: store,
sender: sender,
}
// return the fee for each tx type
switch txInner := tx.Unwrap().(type) {
case TxDeclareCandidacy:
return sdk.NewCheck(params.GasDeclareCandidacy, ""),
checker.declareCandidacy(txInner)
case TxEditCandidacy:
return sdk.NewCheck(params.GasEditCandidacy, ""),
checker.editCandidacy(txInner)
case TxDelegate:
return sdk.NewCheck(params.GasDelegate, ""),
checker.delegate(txInner)
case TxUnbond:
return sdk.NewCheck(params.GasUnbond, ""),
checker.unbond(txInner)
}
return res, errors.ErrUnknownTxType(tx)
}
// DeliverTx executes the tx if valid
func (h Handler) DeliverTx(ctx sdk.Context, store state.SimpleDB,
tx sdk.Tx, dispatch sdk.Deliver) (res sdk.DeliverResult, err error) {
// TODO: remove redundancy
// also we don't need to check the res - gas is already deducted in sdk
_, err = h.CheckTx(ctx, store, tx, nil)
if err != nil {
return
}
sender, err := getTxSender(ctx)
if err != nil {
return
}
params := loadParams(store)
deliverer := deliver{
store: store,
sender: sender,
params: params,
transfer: coinSender{
store: store,
dispatch: dispatch,
ctx: ctx,
}.transferFn,
}
// Run the transaction
switch _tx := tx.Unwrap().(type) {
case TxDeclareCandidacy:
res.GasUsed = params.GasDeclareCandidacy
return res, deliverer.declareCandidacy(_tx)
case TxEditCandidacy:
res.GasUsed = params.GasEditCandidacy
return res, deliverer.editCandidacy(_tx)
case TxDelegate:
res.GasUsed = params.GasDelegate
return res, deliverer.delegate(_tx)
case TxUnbond:
//context with hold account permissions
params := loadParams(store)
res.GasUsed = params.GasUnbond
ctx2 := ctx.WithPermissions(params.HoldBonded)
deliverer.transfer = coinSender{
store: store,
dispatch: dispatch,
ctx: ctx2,
}.transferFn
return res, deliverer.unbond(_tx)
}
return
}
// get the sender from the ctx and ensure it matches the tx pubkey
func getTxSender(ctx sdk.Context) (sender sdk.Actor, err error) {
senders := ctx.GetPermissions("", auth.NameSigs)
if len(senders) != 1 {
return sender, ErrMissingSignature()
}
return senders[0], nil
}
//_______________________________________________________________________
type coinSender struct {
store state.SimpleDB
dispatch sdk.Deliver
ctx sdk.Context
}
var _ coinSend = coinSender{} // enforce interface at compile time
func (c coinSender) transferFn(sender, receiver sdk.Actor, coins coin.Coins) error {
send := coin.NewSendOneTx(sender, receiver, coins)
// If the deduction fails (too high), abort the command
_, err := c.dispatch.DeliverTx(c.ctx, c.store, send)
return err
}
//_____________________________________________________________________
type check struct {
store state.SimpleDB
sender sdk.Actor
}
var _ delegatedProofOfStake = check{} // enforce interface at compile time
func (c check) declareCandidacy(tx TxDeclareCandidacy) error {
// check to see if the pubkey or sender has been registered before
candidate := loadCandidate(c.store, tx.PubKey)
if candidate != nil {
return fmt.Errorf("cannot bond to pubkey which is already declared candidacy"+
" PubKey %v already registered with %v candidate address",
candidate.PubKey, candidate.Owner)
}
return checkDenom(tx.BondUpdate, c.store)
}
func (c check) editCandidacy(tx TxEditCandidacy) error {
// candidate must already be registered
candidate := loadCandidate(c.store, tx.PubKey)
if candidate == nil { // does PubKey exist
return fmt.Errorf("cannot delegate to non-existant PubKey %v", tx.PubKey)
}
return nil
}
func (c check) delegate(tx TxDelegate) error {
candidate := loadCandidate(c.store, tx.PubKey)
if candidate == nil { // does PubKey exist
return fmt.Errorf("cannot delegate to non-existant PubKey %v", tx.PubKey)
}
return checkDenom(tx.BondUpdate, c.store)
}
func (c check) unbond(tx TxUnbond) error {
// check if bond has any shares in it unbond
bond := loadDelegatorBond(c.store, c.sender, tx.PubKey)
sharesStr := viper.GetString(tx.Shares)
if bond.Shares.LT(rational.Zero) { // bond shares < tx shares
return fmt.Errorf("no shares in account to unbond")
}
// if shares set to maximum shares then we're good
if sharesStr == "MAX" {
return nil
}
// test getting rational number from decimal provided
shares, err := rational.NewFromDecimal(sharesStr)
if err != nil {
return err
}
// test that there are enough shares to unbond
if bond.Shares.LT(shares) {
return fmt.Errorf("not enough bond shares to unbond, have %v, trying to unbond %v",
bond.Shares, tx.Shares)
}
return nil
}
func checkDenom(tx BondUpdate, store state.SimpleDB) error {
if tx.Bond.Denom != loadParams(store).AllowedBondDenom {
return fmt.Errorf("Invalid coin denomination")
}
return nil
}
//_____________________________________________________________________
type deliver struct {
store state.SimpleDB
sender sdk.Actor
params Params
gs *GlobalState
transfer transferFn
}
type transferFn func(sender, receiver sdk.Actor, coins coin.Coins) error
var _ delegatedProofOfStake = deliver{} // enforce interface at compile time
//_____________________________________________________________________
// deliver helper functions
// TODO move from deliver with new SDK should only be dependant on store to send coins in NEW SDK
// move a candidates asset pool from bonded to unbonded pool
func (d deliver) bondedToUnbondedPool(candidate *Candidate) error {
// replace bonded shares with unbonded shares
tokens := d.gs.removeSharesBonded(candidate.Assets)
candidate.Assets = d.gs.addTokensUnbonded(tokens)
candidate.Status = Unbonded
return d.transfer(d.params.HoldBonded, d.params.HoldUnbonded,
coin.Coins{{d.params.AllowedBondDenom, tokens}})
}
// move a candidates asset pool from unbonded to bonded pool
func (d deliver) unbondedToBondedPool(candidate *Candidate) error {
// replace bonded shares with unbonded shares
tokens := d.gs.removeSharesUnbonded(candidate.Assets)
candidate.Assets = d.gs.addTokensBonded(tokens)
candidate.Status = Bonded
return d.transfer(d.params.HoldUnbonded, d.params.HoldBonded,
coin.Coins{{d.params.AllowedBondDenom, tokens}})
}
//_____________________________________________________________________
// These functions assume everything has been authenticated,
// now we just perform action and save
func (d deliver) declareCandidacy(tx TxDeclareCandidacy) error {
// create and save the empty candidate
bond := loadCandidate(d.store, tx.PubKey)
if bond != nil {
return ErrCandidateExistsAddr()
}
candidate := NewCandidate(tx.PubKey, d.sender, tx.Description)
saveCandidate(d.store, candidate)
// move coins from the d.sender account to a (self-bond) delegator account
// the candidate account and global shares are updated within here
txDelegate := TxDelegate{tx.BondUpdate}
return d.delegateWithCandidate(txDelegate, candidate)
}
func (d deliver) editCandidacy(tx TxEditCandidacy) error {
// Get the pubKey bond account
candidate := loadCandidate(d.store, tx.PubKey)
if candidate == nil {
return ErrBondNotNominated()
}
if candidate.Status == Unbonded { //candidate has been withdrawn
return ErrBondNotNominated()
}
//check and edit any of the editable terms
if tx.Description.Moniker != "" {
candidate.Description.Moniker = tx.Description.Moniker
}
if tx.Description.Identity != "" {
candidate.Description.Identity = tx.Description.Identity
}
if tx.Description.Website != "" {
candidate.Description.Website = tx.Description.Website
}
if tx.Description.Details != "" {
candidate.Description.Details = tx.Description.Details
}
saveCandidate(d.store, candidate)
return nil
}
func (d deliver) delegate(tx TxDelegate) error {
// Get the pubKey bond account
candidate := loadCandidate(d.store, tx.PubKey)
if candidate == nil {
return ErrBondNotNominated()
}
return d.delegateWithCandidate(tx, candidate)
}
func (d deliver) delegateWithCandidate(tx TxDelegate, candidate *Candidate) error {
if candidate.Status == Revoked { //candidate has been withdrawn
return ErrBondNotNominated()
}
var poolAccount sdk.Actor
if candidate.Status == Bonded {
poolAccount = d.params.HoldBonded
} else {
poolAccount = d.params.HoldUnbonded
}
// TODO maybe refactor into GlobalState.addBondedTokens(), maybe with new SDK
// Move coins from the delegator account to the bonded pool account
err := d.transfer(d.sender, poolAccount, coin.Coins{tx.Bond})
if err != nil {
return err
}
// Get or create the delegator bond
bond := loadDelegatorBond(d.store, d.sender, tx.PubKey)
if bond == nil {
bond = &DelegatorBond{
PubKey: tx.PubKey,
Shares: rational.Zero,
}
}
// Account new shares, save
bond.Shares = bond.Shares.Add(candidate.addTokens(tx.Bond.Amount, d.gs))
saveCandidate(d.store, candidate)
saveDelegatorBond(d.store, d.sender, bond)
saveGlobalState(d.store, d.gs)
return nil
}
func (d deliver) unbond(tx TxUnbond) error {
// get delegator bond
bond := loadDelegatorBond(d.store, d.sender, tx.PubKey)
if bond == nil {
return ErrNoDelegatorForAddress()
}
// retrieve the amount of bonds to remove (TODO remove redundancy already serialized)
var shares rational.Rat
if tx.Shares == "MAX" {
shares = bond.Shares
} else {
var err error
shares, err = rational.NewFromDecimal(tx.Shares)
if err != nil {
return err
}
}
// subtract bond tokens from delegator bond
if bond.Shares.LT(shares) { // bond shares < tx shares
return ErrInsufficientFunds()
}
bond.Shares = bond.Shares.Sub(shares)
// get pubKey candidate
candidate := loadCandidate(d.store, tx.PubKey)
if candidate == nil {
return ErrNoCandidateForAddress()
}
revokeCandidacy := false
if bond.Shares.IsZero() {
// if the bond is the owner of the candidate then
// trigger a revoke candidacy
if d.sender.Equals(candidate.Owner) &&
candidate.Status != Revoked {
revokeCandidacy = true
}
// remove the bond
removeDelegatorBond(d.store, d.sender, tx.PubKey)
} else {
saveDelegatorBond(d.store, d.sender, bond)
}
// transfer coins back to account
var poolAccount sdk.Actor
if candidate.Status == Bonded {
poolAccount = d.params.HoldBonded
} else {
poolAccount = d.params.HoldUnbonded
}
returnCoins := candidate.removeShares(shares, d.gs)
err := d.transfer(poolAccount, d.sender,
coin.Coins{{d.params.AllowedBondDenom, returnCoins}})
if err != nil {
return err
}
// lastly if an revoke candidate if necessary
if revokeCandidacy {
// change the share types to unbonded if they were not already
if candidate.Status == Bonded {
err = d.bondedToUnbondedPool(candidate)
if err != nil {
return err
}
}
// lastly update the status
candidate.Status = Revoked
}
// deduct shares from the candidate and save
if candidate.Liabilities.IsZero() {
removeCandidate(d.store, tx.PubKey)
} else {
saveCandidate(d.store, candidate)
}
saveGlobalState(d.store, d.gs)
return nil
}

View File

@ -1,336 +0,0 @@
package stake
import (
"strconv"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
crypto "github.com/tendermint/go-crypto"
"github.com/tendermint/tmlibs/rational"
sdk "github.com/cosmos/cosmos-sdk"
"github.com/cosmos/cosmos-sdk/modules/coin"
"github.com/cosmos/cosmos-sdk/state"
)
//______________________________________________________________________
// dummy transfer functions, represents store operations on account balances
type testCoinSender struct {
store map[string]int64
}
var _ coinSend = testCoinSender{} // enforce interface at compile time
func (c testCoinSender) transferFn(sender, receiver sdk.Actor, coins coin.Coins) error {
c.store[string(sender.Address)] -= coins[0].Amount
c.store[string(receiver.Address)] += coins[0].Amount
return nil
}
//______________________________________________________________________
func initAccounts(n int, amount int64) ([]sdk.Actor, map[string]int64) {
accStore := map[string]int64{}
senders := newActors(n)
for _, sender := range senders {
accStore[string(sender.Address)] = amount
}
return senders, accStore
}
func newTxDeclareCandidacy(amt int64, pubKey crypto.PubKey) TxDeclareCandidacy {
return TxDeclareCandidacy{
BondUpdate{
PubKey: pubKey,
Bond: coin.Coin{"fermion", amt},
},
Description{},
}
}
func newTxDelegate(amt int64, pubKey crypto.PubKey) TxDelegate {
return TxDelegate{BondUpdate{
PubKey: pubKey,
Bond: coin.Coin{"fermion", amt},
}}
}
func newTxUnbond(shares string, pubKey crypto.PubKey) TxUnbond {
return TxUnbond{
PubKey: pubKey,
Shares: shares,
}
}
func paramsNoInflation() Params {
return Params{
HoldBonded: sdk.NewActor(stakingModuleName, []byte("77777777777777777777777777777777")),
HoldUnbonded: sdk.NewActor(stakingModuleName, []byte("88888888888888888888888888888888")),
InflationRateChange: rational.Zero,
InflationMax: rational.Zero,
InflationMin: rational.Zero,
GoalBonded: rational.New(67, 100),
MaxVals: 100,
AllowedBondDenom: "fermion",
GasDeclareCandidacy: 20,
GasEditCandidacy: 20,
GasDelegate: 20,
GasUnbond: 20,
}
}
func newDeliver(sender sdk.Actor, accStore map[string]int64) deliver {
store := state.NewMemKVStore()
params := paramsNoInflation()
saveParams(store, params)
return deliver{
store: store,
sender: sender,
params: params,
gs: loadGlobalState(store),
transfer: testCoinSender{accStore}.transferFn,
}
}
func TestDuplicatesTxDeclareCandidacy(t *testing.T) {
assert := assert.New(t)
senders, accStore := initAccounts(2, 1000) // for accounts
deliverer := newDeliver(senders[0], accStore)
checker := check{
store: deliverer.store,
sender: senders[0],
}
txDeclareCandidacy := newTxDeclareCandidacy(10, pks[0])
got := deliverer.declareCandidacy(txDeclareCandidacy)
assert.NoError(got, "expected no error on runTxDeclareCandidacy")
// one sender can bond to two different pubKeys
txDeclareCandidacy.PubKey = pks[1]
err := checker.declareCandidacy(txDeclareCandidacy)
assert.Nil(err, "didn't expected error on checkTx")
// two senders cant bond to the same pubkey
checker.sender = senders[1]
txDeclareCandidacy.PubKey = pks[0]
err = checker.declareCandidacy(txDeclareCandidacy)
assert.NotNil(err, "expected error on checkTx")
}
func TestIncrementsTxDelegate(t *testing.T) {
assert := assert.New(t)
initSender := int64(1000)
senders, accStore := initAccounts(1, initSender) // for accounts
deliverer := newDeliver(senders[0], accStore)
// first declare candidacy
bondAmount := int64(10)
txDeclareCandidacy := newTxDeclareCandidacy(bondAmount, pks[0])
got := deliverer.declareCandidacy(txDeclareCandidacy)
assert.NoError(got, "expected declare candidacy tx to be ok, got %v", got)
expectedBond := bondAmount // 1 since we send 1 at the start of loop,
// just send the same txbond multiple times
holder := deliverer.params.HoldUnbonded // XXX this should be HoldBonded, new SDK updates
txDelegate := newTxDelegate(bondAmount, pks[0])
for i := 0; i < 5; i++ {
got := deliverer.delegate(txDelegate)
assert.NoError(got, "expected tx %d to be ok, got %v", i, got)
//Check that the accounts and the bond account have the appropriate values
candidates := loadCandidates(deliverer.store)
expectedBond += bondAmount
expectedSender := initSender - expectedBond
gotBonded := candidates[0].Liabilities.Evaluate()
gotHolder := accStore[string(holder.Address)]
gotSender := accStore[string(deliverer.sender.Address)]
assert.Equal(expectedBond, gotBonded, "i: %v, %v, %v", i, expectedBond, gotBonded)
assert.Equal(expectedBond, gotHolder, "i: %v, %v, %v", i, expectedBond, gotHolder)
assert.Equal(expectedSender, gotSender, "i: %v, %v, %v", i, expectedSender, gotSender)
}
}
func TestIncrementsTxUnbond(t *testing.T) {
assert := assert.New(t)
initSender := int64(0)
senders, accStore := initAccounts(1, initSender) // for accounts
deliverer := newDeliver(senders[0], accStore)
// set initial bond
initBond := int64(1000)
accStore[string(deliverer.sender.Address)] = initBond
got := deliverer.declareCandidacy(newTxDeclareCandidacy(initBond, pks[0]))
assert.NoError(got, "expected initial bond tx to be ok, got %v", got)
// just send the same txunbond multiple times
holder := deliverer.params.HoldUnbonded // XXX new SDK, this should be HoldBonded
// XXX use decimals here
unbondShares, unbondSharesStr := int64(10), "10"
txUndelegate := newTxUnbond(unbondSharesStr, pks[0])
nUnbonds := 5
for i := 0; i < nUnbonds; i++ {
got := deliverer.unbond(txUndelegate)
assert.NoError(got, "expected tx %d to be ok, got %v", i, got)
//Check that the accounts and the bond account have the appropriate values
candidates := loadCandidates(deliverer.store)
expectedBond := initBond - int64(i+1)*unbondShares // +1 since we send 1 at the start of loop
expectedSender := initSender + (initBond - expectedBond)
gotBonded := candidates[0].Liabilities.Evaluate()
gotHolder := accStore[string(holder.Address)]
gotSender := accStore[string(deliverer.sender.Address)]
assert.Equal(expectedBond, gotBonded, "%v, %v", expectedBond, gotBonded)
assert.Equal(expectedBond, gotHolder, "%v, %v", expectedBond, gotHolder)
assert.Equal(expectedSender, gotSender, "%v, %v", expectedSender, gotSender)
}
// these are more than we have bonded now
errorCases := []int64{
//1<<64 - 1, // more than int64
//1<<63 + 1, // more than int64
1<<63 - 1,
1 << 31,
initBond,
}
for _, c := range errorCases {
unbondShares := strconv.Itoa(int(c))
txUndelegate := newTxUnbond(unbondShares, pks[0])
got = deliverer.unbond(txUndelegate)
assert.Error(got, "expected unbond tx to fail")
}
leftBonded := initBond - unbondShares*int64(nUnbonds)
// should be unable to unbond one more than we have
txUndelegate = newTxUnbond(strconv.Itoa(int(leftBonded)+1), pks[0])
got = deliverer.unbond(txUndelegate)
assert.Error(got, "expected unbond tx to fail")
// should be able to unbond just what we have
txUndelegate = newTxUnbond(strconv.Itoa(int(leftBonded)), pks[0])
got = deliverer.unbond(txUndelegate)
assert.NoError(got, "expected unbond tx to pass")
}
func TestMultipleTxDeclareCandidacy(t *testing.T) {
assert := assert.New(t)
initSender := int64(1000)
senders, accStore := initAccounts(3, initSender)
pubKeys := []crypto.PubKey{pks[0], pks[1], pks[2]}
deliverer := newDeliver(senders[0], accStore)
// bond them all
for i, sender := range senders {
txDeclareCandidacy := newTxDeclareCandidacy(10, pubKeys[i])
deliverer.sender = sender
got := deliverer.declareCandidacy(txDeclareCandidacy)
assert.NoError(got, "expected tx %d to be ok, got %v", i, got)
//Check that the account is bonded
candidates := loadCandidates(deliverer.store)
val := candidates[i]
balanceGot, balanceExpd := accStore[string(val.Owner.Address)], initSender-10
assert.Equal(i+1, len(candidates), "expected %d candidates got %d, candidates: %v", i+1, len(candidates), candidates)
assert.Equal(10, int(val.Liabilities.Evaluate()), "expected %d shares, got %d", 10, val.Liabilities)
assert.Equal(balanceExpd, balanceGot, "expected account to have %d, got %d", balanceExpd, balanceGot)
}
// unbond them all
for i, sender := range senders {
candidatePre := loadCandidate(deliverer.store, pubKeys[i])
txUndelegate := newTxUnbond("10", pubKeys[i])
deliverer.sender = sender
got := deliverer.unbond(txUndelegate)
assert.NoError(got, "expected tx %d to be ok, got %v", i, got)
//Check that the account is unbonded
candidates := loadCandidates(deliverer.store)
assert.Equal(len(senders)-(i+1), len(candidates), "expected %d candidates got %d", len(senders)-(i+1), len(candidates))
candidatePost := loadCandidate(deliverer.store, pubKeys[i])
balanceGot, balanceExpd := accStore[string(candidatePre.Owner.Address)], initSender
assert.Nil(candidatePost, "expected nil candidate retrieve, got %d", 0, candidatePost)
assert.Equal(balanceExpd, balanceGot, "expected account to have %d, got %d", balanceExpd, balanceGot)
}
}
func TestMultipleTxDelegate(t *testing.T) {
assert, require := assert.New(t), require.New(t)
accounts, accStore := initAccounts(3, 1000)
sender, delegators := accounts[0], accounts[1:]
deliverer := newDeliver(sender, accStore)
//first make a candidate
txDeclareCandidacy := newTxDeclareCandidacy(10, pks[0])
got := deliverer.declareCandidacy(txDeclareCandidacy)
require.NoError(got, "expected tx to be ok, got %v", got)
// delegate multiple parties
for i, delegator := range delegators {
txDelegate := newTxDelegate(10, pks[0])
deliverer.sender = delegator
got := deliverer.delegate(txDelegate)
require.NoError(got, "expected tx %d to be ok, got %v", i, got)
//Check that the account is bonded
bond := loadDelegatorBond(deliverer.store, delegator, pks[0])
assert.NotNil(bond, "expected delegatee bond %d to exist", bond)
}
// unbond them all
for i, delegator := range delegators {
txUndelegate := newTxUnbond("10", pks[0])
deliverer.sender = delegator
got := deliverer.unbond(txUndelegate)
require.NoError(got, "expected tx %d to be ok, got %v", i, got)
//Check that the account is unbonded
bond := loadDelegatorBond(deliverer.store, delegator, pks[0])
assert.Nil(bond, "expected delegatee bond %d to be nil", bond)
}
}
func TestVoidCandidacy(t *testing.T) {
assert, require := assert.New(t), require.New(t)
accounts, accStore := initAccounts(2, 1000) // for accounts
sender, delegator := accounts[0], accounts[1]
deliverer := newDeliver(sender, accStore)
// create the candidate
txDeclareCandidacy := newTxDeclareCandidacy(10, pks[0])
got := deliverer.declareCandidacy(txDeclareCandidacy)
require.NoError(got, "expected no error on runTxDeclareCandidacy")
// bond a delegator
txDelegate := newTxDelegate(10, pks[0])
deliverer.sender = delegator
got = deliverer.delegate(txDelegate)
require.NoError(got, "expected ok, got %v", got)
// unbond the candidates bond portion
txUndelegate := newTxUnbond("10", pks[0])
deliverer.sender = sender
got = deliverer.unbond(txUndelegate)
require.NoError(got, "expected no error on runTxDeclareCandidacy")
// test that this pubkey cannot yet be bonded too
deliverer.sender = delegator
got = deliverer.delegate(txDelegate)
assert.Error(got, "expected error, got %v", got)
// test that the delegator can still withdraw their bonds
got = deliverer.unbond(txUndelegate)
require.NoError(got, "expected no error on runTxDeclareCandidacy")
// verify that the pubkey can now be reused
got = deliverer.declareCandidacy(txDeclareCandidacy)
assert.NoError(got, "expected ok, got %v", got)
}

View File

@ -1,188 +0,0 @@
package rest
import (
"fmt"
"net/http"
"github.com/gorilla/mux"
"github.com/spf13/viper"
"github.com/cosmos/cosmos-sdk/client"
"github.com/cosmos/cosmos-sdk/client/commands"
"github.com/cosmos/cosmos-sdk/client/commands/query"
"github.com/cosmos/cosmos-sdk/modules/coin"
"github.com/cosmos/cosmos-sdk/stack"
"github.com/cosmos/gaia/modules/stake"
scmds "github.com/cosmos/gaia/modules/stake/commands"
crypto "github.com/tendermint/go-crypto"
"github.com/tendermint/tmlibs/common"
)
// RegisterQueryCandidate is a mux.Router handler that exposes GET
// method access on route /query/stake/candidate/{pubkey} to query a candidate
func RegisterQueryCandidate(r *mux.Router) error {
r.HandleFunc("/query/stake/candidate/{pubkey}", queryCandidate).Methods("GET")
return nil
}
// RegisterQueryCandidates is a mux.Router handler that exposes GET
// method access on route /query/stake/candidate to query the group of all candidates
func RegisterQueryCandidates(r *mux.Router) error {
r.HandleFunc("/query/stake/candidates", queryCandidates).Methods("GET")
return nil
}
// RegisterQueryDelegatorBond is a mux.Router handler that exposes GET
// method access on route /query/stake/candidate/{pubkey} to query a candidate
func RegisterQueryDelegatorBond(r *mux.Router) error {
r.HandleFunc("/query/stake/delegator/{address}/{pubkey}", queryDelegatorBond).Methods("GET")
return nil
}
// RegisterQueryDelegatorCandidates is a mux.Router handler that exposes GET
// method access on route /query/stake/candidate to query the group of all candidates
func RegisterQueryDelegatorCandidates(r *mux.Router) error {
r.HandleFunc("/query/stake/delegator_candidates/{address}", queryDelegatorCandidates).Methods("GET")
return nil
}
//---------------------------------------------------------------------
// queryCandidate is the HTTP handlerfunc to query a candidate
// it expects a query string
func queryCandidate(w http.ResponseWriter, r *http.Request) {
// get the arguments object
args := mux.Vars(r)
prove := !viper.GetBool(commands.FlagTrustNode) // from viper because defined when starting server
// get the pubkey
pkArg := args["pubkey"]
pk, err := scmds.GetPubKey(pkArg)
if err != nil {
common.WriteError(w, err)
return
}
// get the candidate
var candidate stake.Candidate
key := stack.PrefixedKey(stake.Name(), stake.GetCandidateKey(pk))
height, err := query.GetParsed(key, &candidate, query.GetHeight(), prove)
if client.IsNoDataErr(err) {
err := fmt.Errorf("candidate bytes are empty for pubkey: %q", pkArg)
common.WriteError(w, err)
return
} else if err != nil {
common.WriteError(w, err)
return
}
// write the output
err = query.FoutputProof(w, candidate, height)
if err != nil {
common.WriteError(w, err)
}
}
// queryCandidates is the HTTP handlerfunc to query the group of all candidates
func queryCandidates(w http.ResponseWriter, r *http.Request) {
var pks []crypto.PubKey
prove := !viper.GetBool(commands.FlagTrustNode) // from viper because defined when starting server
key := stack.PrefixedKey(stake.Name(), stake.CandidatesPubKeysKey)
height, err := query.GetParsed(key, &pks, query.GetHeight(), prove)
if err != nil {
common.WriteError(w, err)
return
}
err = query.FoutputProof(w, pks, height)
if err != nil {
common.WriteError(w, err)
}
}
// queryDelegatorBond is the HTTP handlerfunc to query a delegator bond it
// expects a query string
func queryDelegatorBond(w http.ResponseWriter, r *http.Request) {
// get the arguments object
args := mux.Vars(r)
prove := !viper.GetBool(commands.FlagTrustNode) // from viper because defined when starting server
// get the pubkey
pkArg := args["pubkey"]
pk, err := scmds.GetPubKey(pkArg)
if err != nil {
common.WriteError(w, err)
return
}
// get the delegator actor
delegatorAddr := args["address"]
delegator, err := commands.ParseActor(delegatorAddr)
if err != nil {
common.WriteError(w, err)
return
}
delegator = coin.ChainAddr(delegator)
// get the bond
var bond stake.DelegatorBond
key := stack.PrefixedKey(stake.Name(), stake.GetDelegatorBondKey(delegator, pk))
height, err := query.GetParsed(key, &bond, query.GetHeight(), prove)
if client.IsNoDataErr(err) {
err := fmt.Errorf("bond bytes are empty for pubkey: %q, address: %q", pkArg, delegatorAddr)
common.WriteError(w, err)
return
} else if err != nil {
common.WriteError(w, err)
return
}
// write the output
err = query.FoutputProof(w, bond, height)
if err != nil {
common.WriteError(w, err)
}
}
// queryDelegatorCandidates is the HTTP handlerfunc to query a delegator bond it
// expects a query string
func queryDelegatorCandidates(w http.ResponseWriter, r *http.Request) {
// get the arguments object
args := mux.Vars(r)
prove := !viper.GetBool(commands.FlagTrustNode) // from viper because defined when starting server
// get the delegator actor
delegatorAddr := args["address"]
delegator, err := commands.ParseActor(delegatorAddr)
if err != nil {
common.WriteError(w, err)
return
}
delegator = coin.ChainAddr(delegator)
// get the bond
var bond stake.DelegatorBond
key := stack.PrefixedKey(stake.Name(), stake.GetDelegatorBondsKey(delegator))
height, err := query.GetParsed(key, &bond, query.GetHeight(), prove)
if client.IsNoDataErr(err) {
err := fmt.Errorf("bond bytes are empty for address: %q", delegatorAddr)
common.WriteError(w, err)
return
} else if err != nil {
common.WriteError(w, err)
return
}
// write the output
err = query.FoutputProof(w, bond, height)
if err != nil {
common.WriteError(w, err)
}
}

View File

@ -1,159 +0,0 @@
package rest
import (
"net/http"
"strings"
"github.com/gorilla/mux"
crypto "github.com/tendermint/go-crypto"
"github.com/tendermint/tmlibs/common"
sdk "github.com/cosmos/cosmos-sdk"
"github.com/cosmos/cosmos-sdk/client/commands"
"github.com/cosmos/cosmos-sdk/modules/auth"
"github.com/cosmos/cosmos-sdk/modules/base"
"github.com/cosmos/cosmos-sdk/modules/coin"
"github.com/cosmos/cosmos-sdk/modules/fee"
"github.com/cosmos/cosmos-sdk/modules/nonce"
"github.com/cosmos/gaia/modules/stake"
)
const (
//parameters used in urls
paramPubKey = "pubkey"
paramAmount = "amount"
paramShares = "shares"
paramName = "name"
paramKeybase = "keybase"
paramWebsite = "website"
paramDetails = "details"
)
type delegateInput struct {
Fees *coin.Coin `json:"fees"`
Sequence uint32 `json:"sequence"`
Pubkey crypto.PubKey `json:"pub_key"`
From *sdk.Actor `json:"from"`
Amount coin.Coin `json:"amount"`
}
type unbondInput struct {
Fees *coin.Coin `json:"fees"`
Sequence uint32 `json:"sequence"`
Pubkey crypto.PubKey `json:"pub_key"`
From *sdk.Actor `json:"from"`
Shares string `json:"amount"`
}
// RegisterDelegate is a mux.Router handler that exposes
// POST method access on route /tx/stake/delegate to create a
// transaction for delegate to a candidaate/validator
func RegisterDelegate(r *mux.Router) error {
r.HandleFunc("/build/stake/delegate", delegate).Methods("POST")
return nil
}
// RegisterUnbond is a mux.Router handler that exposes
// POST method access on route /tx/stake/unbond to create a
// transaction for unbonding delegated coins
func RegisterUnbond(r *mux.Router) error {
r.HandleFunc("/build/stake/unbond", unbond).Methods("POST")
return nil
}
func prepareDelegateTx(di *delegateInput) sdk.Tx {
tx := stake.NewTxDelegate(di.Amount, di.Pubkey)
// fees are optional
if di.Fees != nil && !di.Fees.IsZero() {
tx = fee.NewFee(tx, *di.Fees, *di.From)
}
// only add the actual signer to the nonce
signers := []sdk.Actor{*di.From}
tx = nonce.NewTx(di.Sequence, signers, tx)
tx = base.NewChainTx(commands.GetChainID(), 0, tx)
tx = auth.NewSig(tx).Wrap()
return tx
}
func delegate(w http.ResponseWriter, r *http.Request) {
defer r.Body.Close()
di := new(delegateInput)
if err := common.ParseRequestAndValidateJSON(r, di); err != nil {
common.WriteError(w, err)
return
}
var errsList []string
if di.From == nil {
errsList = append(errsList, `"from" cannot be nil`)
}
if di.Sequence <= 0 {
errsList = append(errsList, `"sequence" must be > 0`)
}
if di.Pubkey.Empty() {
errsList = append(errsList, `"pubkey" cannot be empty`)
}
if len(errsList) > 0 {
code := http.StatusBadRequest
err := &common.ErrorResponse{
Err: strings.Join(errsList, ", "),
Code: code,
}
common.WriteCode(w, err, code)
return
}
tx := prepareDelegateTx(di)
common.WriteSuccess(w, tx)
}
func prepareUnbondTx(ui *unbondInput) sdk.Tx {
tx := stake.NewTxUnbond(ui.Shares, ui.Pubkey)
// fees are optional
if ui.Fees != nil && !ui.Fees.IsZero() {
tx = fee.NewFee(tx, *ui.Fees, *ui.From)
}
// only add the actual signer to the nonce
signers := []sdk.Actor{*ui.From}
tx = nonce.NewTx(ui.Sequence, signers, tx)
tx = base.NewChainTx(commands.GetChainID(), 0, tx)
tx = auth.NewSig(tx).Wrap()
return tx
}
func unbond(w http.ResponseWriter, r *http.Request) {
defer r.Body.Close()
ui := new(unbondInput)
if err := common.ParseRequestAndValidateJSON(r, ui); err != nil {
common.WriteError(w, err)
return
}
var errsList []string
if ui.From == nil {
errsList = append(errsList, `"from" cannot be nil`)
}
if ui.Sequence <= 0 {
errsList = append(errsList, `"sequence" must be > 0`)
}
if ui.Pubkey.Empty() {
errsList = append(errsList, `"pubkey" cannot be empty`)
}
if len(errsList) > 0 {
code := http.StatusBadRequest
err := &common.ErrorResponse{
Err: strings.Join(errsList, ", "),
Code: code,
}
common.WriteCode(w, err, code)
return
}
tx := prepareUnbondTx(ui)
common.WriteSuccess(w, tx)
}

View File

@ -6,8 +6,7 @@ import (
crypto "github.com/tendermint/go-crypto"
"github.com/tendermint/go-wire"
sdk "github.com/cosmos/cosmos-sdk"
"github.com/cosmos/cosmos-sdk/state"
"github.com/cosmos/cosmos-sdk/types"
)
// nolint
@ -29,41 +28,52 @@ func GetCandidateKey(pubKey crypto.PubKey) []byte {
}
// GetDelegatorBondKey - get the key for delegator bond with candidate
func GetDelegatorBondKey(delegator sdk.Actor, candidate crypto.PubKey) []byte {
func GetDelegatorBondKey(delegator crypto.Address, candidate crypto.PubKey) []byte {
return append(GetDelegatorBondKeyPrefix(delegator), candidate.Bytes()...)
}
// GetDelegatorBondKeyPrefix - get the prefix for a delegator for all candidates
func GetDelegatorBondKeyPrefix(delegator sdk.Actor) []byte {
return append(DelegatorBondKeyPrefix, wire.BinaryBytes(&delegator)...)
func GetDelegatorBondKeyPrefix(delegator crypto.Address) []byte {
res, err := wire.MarshalBinary(&delegator)
if err != nil {
panic(err)
}
return append(DelegatorBondKeyPrefix, res...)
}
// GetDelegatorBondsKey - get the key for list of all the delegator's bonds
func GetDelegatorBondsKey(delegator sdk.Actor) []byte {
return append(DelegatorBondsKeyPrefix, wire.BinaryBytes(&delegator)...)
func GetDelegatorBondsKey(delegator crypto.Address) []byte {
res, err := wire.MarshalBinary(&delegator)
if err != nil {
panic(err)
}
return append(DelegatorBondsKeyPrefix, res...)
}
//---------------------------------------------------------------------
// Get the active list of all the candidate pubKeys and owners
func loadCandidatesPubKeys(store state.SimpleDB) (pubKeys []crypto.PubKey) {
func loadCandidatesPubKeys(store types.KVStore) (pubKeys []crypto.PubKey) {
bytes := store.Get(CandidatesPubKeysKey)
if bytes == nil {
return
}
err := wire.ReadBinaryBytes(bytes, &pubKeys)
err := wire.UnmarshalBinary(bytes, &pubKeys)
if err != nil {
panic(err)
}
return
}
func saveCandidatesPubKeys(store state.SimpleDB, pubKeys []crypto.PubKey) {
b := wire.BinaryBytes(pubKeys)
func saveCandidatesPubKeys(store types.KVStore, pubKeys []crypto.PubKey) {
b, err := wire.MarshalBinary(pubKeys)
if err != nil {
panic(err)
}
store.Set(CandidatesPubKeysKey, b)
}
// loadCandidates - get the active list of all candidates TODO replace with multistore
func loadCandidates(store state.SimpleDB) (candidates Candidates) {
func loadCandidates(store types.KVStore) (candidates Candidates) {
pks := loadCandidatesPubKeys(store)
for _, pk := range pks {
candidates = append(candidates, loadCandidate(store, pk))
@ -74,10 +84,10 @@ func loadCandidates(store state.SimpleDB) (candidates Candidates) {
//---------------------------------------------------------------------
// loadCandidate - loads the candidate object for the provided pubkey
func loadCandidate(store state.SimpleDB, pubKey crypto.PubKey) *Candidate {
if pubKey.Empty() {
return nil
}
func loadCandidate(store types.KVStore, pubKey crypto.PubKey) *Candidate {
//if pubKey.Empty() {
//return nil
//}
b := store.Get(GetCandidateKey(pubKey))
if b == nil {
return nil
@ -90,7 +100,7 @@ func loadCandidate(store state.SimpleDB, pubKey crypto.PubKey) *Candidate {
return candidate
}
func saveCandidate(store state.SimpleDB, candidate *Candidate) {
func saveCandidate(store types.KVStore, candidate *Candidate) {
if !store.Has(GetCandidateKey(candidate.PubKey)) {
// TODO to be replaced with iteration in the multistore?
@ -105,8 +115,8 @@ func saveCandidate(store state.SimpleDB, candidate *Candidate) {
store.Set(GetCandidateKey(candidate.PubKey), b)
}
func removeCandidate(store state.SimpleDB, pubKey crypto.PubKey) {
store.Remove(GetCandidateKey(pubKey))
func removeCandidate(store types.KVStore, pubKey crypto.PubKey) {
store.Delete(GetCandidateKey(pubKey))
// TODO to be replaced with iteration in the multistore?
pks := loadCandidatesPubKeys(store)
@ -122,15 +132,15 @@ func removeCandidate(store state.SimpleDB, pubKey crypto.PubKey) {
//---------------------------------------------------------------------
// load the pubkeys of all candidates a delegator is delegated too
func loadDelegatorCandidates(store state.SimpleDB,
delegator sdk.Actor) (candidates []crypto.PubKey) {
func loadDelegatorCandidates(store types.KVStore,
delegator crypto.Address) (candidates []crypto.PubKey) {
candidateBytes := store.Get(GetDelegatorBondsKey(delegator))
if candidateBytes == nil {
return nil
}
err := wire.ReadBinaryBytes(candidateBytes, &candidates)
err := wire.UnmarshalBinary(candidateBytes, &candidates)
if err != nil {
panic(err)
}
@ -139,8 +149,8 @@ func loadDelegatorCandidates(store state.SimpleDB,
//---------------------------------------------------------------------
func loadDelegatorBond(store state.SimpleDB,
delegator sdk.Actor, candidate crypto.PubKey) *DelegatorBond {
func loadDelegatorBond(store types.KVStore,
delegator crypto.Address, candidate crypto.PubKey) *DelegatorBond {
delegatorBytes := store.Get(GetDelegatorBondKey(delegator, candidate))
if delegatorBytes == nil {
@ -155,13 +165,16 @@ func loadDelegatorBond(store state.SimpleDB,
return bond
}
func saveDelegatorBond(store state.SimpleDB, delegator sdk.Actor, bond *DelegatorBond) {
func saveDelegatorBond(store types.KVStore, delegator crypto.Address, bond *DelegatorBond) {
// if a new bond add to the list of bonds
if loadDelegatorBond(store, delegator, bond.PubKey) == nil {
pks := loadDelegatorCandidates(store, delegator)
pks = append(pks, (*bond).PubKey)
b := wire.BinaryBytes(pks)
b, err := wire.MarshalBinary(pks)
if err != nil {
panic(err)
}
store.Set(GetDelegatorBondsKey(delegator), b)
}
@ -174,7 +187,7 @@ func saveDelegatorBond(store state.SimpleDB, delegator sdk.Actor, bond *Delegato
//updateDelegatorBonds(store, delegator)
}
func removeDelegatorBond(store state.SimpleDB, delegator sdk.Actor, candidate crypto.PubKey) {
func removeDelegatorBond(store types.KVStore, delegator crypto.Address, candidate crypto.PubKey) {
// TODO use list queries on multistore to remove iterations here!
// first remove from the list of bonds
@ -184,16 +197,19 @@ func removeDelegatorBond(store state.SimpleDB, delegator sdk.Actor, candidate cr
pks = append(pks[:i], pks[i+1:]...)
}
}
b := wire.BinaryBytes(pks)
b, err := wire.MarshalBinary(pks)
if err != nil {
panic(err)
}
store.Set(GetDelegatorBondsKey(delegator), b)
// now remove the actual bond
store.Remove(GetDelegatorBondKey(delegator, candidate))
store.Delete(GetDelegatorBondKey(delegator, candidate))
//updateDelegatorBonds(store, delegator)
}
//func updateDelegatorBonds(store state.SimpleDB,
//delegator sdk.Actor) {
//func updateDelegatorBonds(store types.KVStore,
//delegator crypto.Address) {
//var bonds []*DelegatorBond
@ -209,7 +225,7 @@ func removeDelegatorBond(store state.SimpleDB, delegator sdk.Actor, candidate cr
//}
//bond := new(DelegatorBond)
//err := wire.ReadBinaryBytes(delegatorBytes, bond)
//err := wire.UnmarshalBinary(delegatorBytes, bond)
//if err != nil {
//panic(err)
//}
@ -221,14 +237,14 @@ func removeDelegatorBond(store state.SimpleDB, delegator sdk.Actor, candidate cr
//return
//}
//b := wire.BinaryBytes(bonds)
//b := wire.MarshalBinary(bonds)
//store.Set(GetDelegatorBondsKey(delegator), b)
//}
//_______________________________________________________________________
// load/save the global staking params
func loadParams(store state.SimpleDB) (params Params) {
func loadParams(store types.KVStore) (params Params) {
b := store.Get(ParamKey)
if b == nil {
return defaultParams()
@ -241,7 +257,7 @@ func loadParams(store state.SimpleDB) (params Params) {
return
}
func saveParams(store state.SimpleDB, params Params) {
func saveParams(store types.KVStore, params Params) {
b, err := json.Marshal(params)
if err != nil {
panic(err)
@ -252,7 +268,7 @@ func saveParams(store state.SimpleDB, params Params) {
//_______________________________________________________________________
// load/save the global staking state
func loadGlobalState(store state.SimpleDB) (gs *GlobalState) {
func loadGlobalState(store types.KVStore) (gs *GlobalState) {
b := store.Get(GlobalStateKey)
if b == nil {
return initialGlobalState()
@ -264,7 +280,7 @@ func loadGlobalState(store state.SimpleDB) (gs *GlobalState) {
}
return
}
func saveGlobalState(store state.SimpleDB, gs *GlobalState) {
func saveGlobalState(store types.KVStore, gs *GlobalState) {
b, err := json.Marshal(*gs)
if err != nil {
panic(err)

View File

@ -1,31 +1,51 @@
package stake
import (
"bytes"
"encoding/hex"
"testing"
sdk "github.com/cosmos/cosmos-sdk"
"github.com/cosmos/cosmos-sdk/modules/auth"
"github.com/cosmos/cosmos-sdk/store"
sdkstore "github.com/cosmos/cosmos-sdk/store"
"github.com/cosmos/cosmos-sdk/types"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
crypto "github.com/tendermint/go-crypto"
dbm "github.com/tendermint/tmlibs/db"
"github.com/tendermint/tmlibs/rational"
)
func newPubKey(pk string) (res crypto.PubKey, err error) {
pkBytes, err := hex.DecodeString(pk)
if err != nil {
return
}
//res, err = crypto.PubKeyFromBytes(pkBytes)
var pkEd crypto.PubKeyEd25519
copy(pkEd[:], pkBytes[:])
return pkEd, nil
}
func TestState(t *testing.T) {
assert, require := assert.New(t), require.New(t)
db, err := dbm.NewGoLevelDB("basecoin", "basecoin-data")
require.Nil(err)
mainLoader := store.NewIAVLStoreLoader(int64(100), 10000, numHistory)
var mainStoreKey = sdk.NewKVStoreKey("main")
multiStore := store.NewCommitMultiStore(db)
multiStore.SetSubstoreLoader(mainStoreKey, mainLoader)
var store = auth.NewAccountStore(mainStoreKey, bcm.AppAccountCodec{})
cacheSize := 10000
numHistory := int64(100)
stakeLoader := sdkstore.NewIAVLStoreLoader(db, cacheSize, numHistory)
var stakeStoreKey = types.NewKVStoreKey("stake")
multiStore := sdkstore.NewCommitMultiStore(db)
multiStore.SetSubstoreLoader(stakeStoreKey, stakeLoader)
multiStore.LoadLatestVersion()
store := multiStore.GetKVStore(stakeStoreKey)
delegator := sdk.Actor{"testChain", "testapp", []byte("addressdelegator")}
validator := sdk.Actor{"testChain", "testapp", []byte("addressvalidator")}
//delegator := crypto.Address{[]byte("addressdelegator")}
//validator := crypto.Address{[]byte("addressvalidator")}
delegator := []byte("addressdelegator")
validator := []byte("addressvalidator")
pk := newPubKey("0B485CFC0EECC619440448436F8FC9DF40566F2369E72400281454CB552AFB57")
pk, err := newPubKey("0B485CFC0EECC619440448436F8FC9DF40566F2369E72400281454CB552AFB57")
require.Nil(err)
//----------------------------------------------------------------------
// Candidate checks
@ -42,7 +62,7 @@ func TestState(t *testing.T) {
candidatesEqual := func(c1, c2 *Candidate) bool {
return c1.Status == c2.Status &&
c1.PubKey.Equals(c2.PubKey) &&
c1.Owner.Equals(c2.Owner) &&
bytes.Equal(c1.Owner, c2.Owner) &&
c1.Assets.Equal(c2.Assets) &&
c1.Liabilities.Equal(c2.Liabilities) &&
c1.VotingPower.Equal(c2.VotingPower) &&

View File

@ -1,91 +0,0 @@
package stake
import (
"encoding/hex"
"fmt"
"testing"
"github.com/stretchr/testify/assert"
abci "github.com/tendermint/abci/types"
crypto "github.com/tendermint/go-crypto"
"github.com/tendermint/tmlibs/rational"
"github.com/cosmos/cosmos-sdk"
)
func newActors(n int) (actors []sdk.Actor) {
for i := 0; i < n; i++ {
actors = append(actors, sdk.Actor{
"testChain", "testapp", []byte(fmt.Sprintf("addr%d", i))})
}
return
}
func newPubKey(pk string) crypto.PubKey {
pkBytes, _ := hex.DecodeString(pk)
var pkEd crypto.PubKeyEd25519
copy(pkEd[:], pkBytes[:])
return pkEd.Wrap()
}
// dummy pubkeys used for testing
var pks = []crypto.PubKey{
newPubKey("0B485CFC0EECC619440448436F8FC9DF40566F2369E72400281454CB552AFB51"),
newPubKey("0B485CFC0EECC619440448436F8FC9DF40566F2369E72400281454CB552AFB52"),
newPubKey("0B485CFC0EECC619440448436F8FC9DF40566F2369E72400281454CB552AFB53"),
newPubKey("0B485CFC0EECC619440448436F8FC9DF40566F2369E72400281454CB552AFB54"),
newPubKey("0B485CFC0EECC619440448436F8FC9DF40566F2369E72400281454CB552AFB55"),
newPubKey("0B485CFC0EECC619440448436F8FC9DF40566F2369E72400281454CB552AFB56"),
newPubKey("0B485CFC0EECC619440448436F8FC9DF40566F2369E72400281454CB552AFB57"),
newPubKey("0B485CFC0EECC619440448436F8FC9DF40566F2369E72400281454CB552AFB58"),
newPubKey("0B485CFC0EECC619440448436F8FC9DF40566F2369E72400281454CB552AFB59"),
newPubKey("0B485CFC0EECC619440448436F8FC9DF40566F2369E72400281454CB552AFB60"),
}
// NOTE: PubKey is supposed to be the binaryBytes of the crypto.PubKey
// instead this is just being set the address here for testing purposes
func candidatesFromActors(actors []sdk.Actor, amts []int64) (candidates Candidates) {
for i := 0; i < len(actors); i++ {
c := &Candidate{
Status: Unbonded,
PubKey: pks[i],
Owner: actors[i],
Assets: rational.New(amts[i]),
Liabilities: rational.New(amts[i]),
VotingPower: rational.New(amts[i]),
}
candidates = append(candidates, c)
}
return
}
func candidatesFromActorsEmpty(actors []sdk.Actor) (candidates Candidates) {
for i := 0; i < len(actors); i++ {
c := &Candidate{
Status: Unbonded,
PubKey: pks[i],
Owner: actors[i],
Assets: rational.Zero,
Liabilities: rational.Zero,
VotingPower: rational.Zero,
}
candidates = append(candidates, c)
}
return
}
// helper function test if Candidate is changed asabci.Validator
func testChange(t *testing.T, val Validator, chg *abci.Validator) {
assert := assert.New(t)
assert.Equal(val.PubKey.Bytes(), chg.PubKey)
assert.Equal(val.VotingPower.Evaluate(), chg.Power)
}
// helper function test if Candidate is removed as abci.Validator
func testRemove(t *testing.T, val Validator, chg *abci.Validator) {
assert := assert.New(t)
assert.Equal(val.PubKey.Bytes(), chg.PubKey)
assert.Equal(int64(0), chg.Power)
}

View File

@ -1,75 +0,0 @@
package stake
import (
sdk "github.com/cosmos/cosmos-sdk"
"github.com/cosmos/cosmos-sdk/state"
abci "github.com/tendermint/abci/types"
"github.com/tendermint/tmlibs/rational"
)
// Tick - called at the end of every block
func Tick(ctx sdk.Context, store state.SimpleDB) (change []*abci.Validator, err error) {
// retrieve params
params := loadParams(store)
gs := loadGlobalState(store)
height := ctx.BlockHeight()
// Process Validator Provisions
// XXX right now just process every 5 blocks, in new SDK make hourly
if gs.InflationLastTime+5 <= height {
gs.InflationLastTime = height
processProvisions(store, gs, params)
}
return UpdateValidatorSet(store, gs, params)
}
var hrsPerYr = rational.New(8766) // as defined by a julian year of 365.25 days
// process provisions for an hour period
func processProvisions(store state.SimpleDB, gs *GlobalState, params Params) {
gs.Inflation = nextInflation(gs, params).Round(1000000000)
// Because the validators hold a relative bonded share (`GlobalStakeShare`), when
// more bonded tokens are added proportionally to all validators the only term
// which needs to be updated is the `BondedPool`. So for each previsions cycle:
provisions := gs.Inflation.Mul(rational.New(gs.TotalSupply)).Quo(hrsPerYr).Evaluate()
gs.BondedPool += provisions
gs.TotalSupply += provisions
// XXX XXX XXX XXX XXX XXX XXX XXX XXX
// XXX Mint them to the hold account
// XXX XXX XXX XXX XXX XXX XXX XXX XXX
// save the params
saveGlobalState(store, gs)
}
// get the next inflation rate for the hour
func nextInflation(gs *GlobalState, params Params) (inflation rational.Rat) {
// 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
// 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%.
// (1 - bondedRatio/GoalBonded) * InflationRateChange
inflationRateChangePerYear := rational.One.Sub(gs.bondedRatio().Quo(params.GoalBonded)).Mul(params.InflationRateChange)
inflationRateChange := inflationRateChangePerYear.Quo(hrsPerYr)
// increase the new annual inflation for this next cycle
inflation = gs.Inflation.Add(inflationRateChange)
if inflation.GT(params.InflationMax) {
inflation = params.InflationMax
}
if inflation.LT(params.InflationMin) {
inflation = params.InflationMin
}
return
}

View File

@ -1,120 +0,0 @@
package stake
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/tendermint/tmlibs/rational"
"github.com/cosmos/cosmos-sdk/state"
)
func TestGetInflation(t *testing.T) {
assert := assert.New(t)
store := state.NewMemKVStore()
params := loadParams(store)
gs := loadGlobalState(store)
// Governing Mechanism:
// bondedRatio = BondedPool / TotalSupply
// inflationRateChangePerYear = (1- bondedRatio/ GoalBonded) * MaxInflationRateChange
tests := []struct {
setBondedPool, setTotalSupply int64
setInflation, expectedChange rational.Rat
}{
// with 0% bonded atom supply the inflation should increase by InflationRateChange
{0, 0, rational.New(7, 100), params.InflationRateChange.Quo(hrsPerYr)},
// 100% bonded, starting at 20% inflation and being reduced
{1, 1, rational.New(20, 100), rational.One.Sub(rational.One.Quo(params.GoalBonded)).Mul(params.InflationRateChange).Quo(hrsPerYr)},
// 50% bonded, starting at 10% inflation and being increased
{1, 2, rational.New(10, 100), rational.One.Sub(rational.New(1, 2).Quo(params.GoalBonded)).Mul(params.InflationRateChange).Quo(hrsPerYr)},
// test 7% minimum stop (testing with 100% bonded)
{1, 1, rational.New(7, 100), rational.Zero},
{1, 1, rational.New(70001, 1000000), rational.New(-1, 1000000)},
// test 20% maximum stop (testing with 0% bonded)
{0, 0, rational.New(20, 100), rational.Zero},
{0, 0, rational.New(199999, 1000000), rational.New(1, 1000000)},
// perfect balance shouldn't change inflation
{67, 100, rational.New(15, 100), rational.Zero},
}
for _, tc := range tests {
gs.BondedPool, gs.TotalSupply = tc.setBondedPool, tc.setTotalSupply
gs.Inflation = tc.setInflation
inflation := nextInflation(gs, params)
diffInflation := inflation.Sub(tc.setInflation)
assert.True(diffInflation.Equal(tc.expectedChange),
"%v, %v", diffInflation, tc.expectedChange)
}
}
func TestProcessProvisions(t *testing.T) {
assert := assert.New(t)
store := state.NewMemKVStore()
params := loadParams(store)
gs := loadGlobalState(store)
// create some candidates some bonded, some unbonded
n := 10
actors := newActors(n)
candidates := candidatesFromActorsEmpty(actors)
for i, candidate := range candidates {
if i < 5 {
candidate.Status = Bonded
}
mintedTokens := int64((i + 1) * 10000000)
gs.TotalSupply += mintedTokens
candidate.addTokens(mintedTokens, gs)
saveCandidate(store, candidate)
}
var totalSupply int64 = 550000000
var bondedShares int64 = 150000000
var unbondedShares int64 = 400000000
// initial bonded ratio ~ 27%
assert.True(gs.bondedRatio().Equal(rational.New(bondedShares, totalSupply)), "%v", gs.bondedRatio())
// Supplies
assert.Equal(totalSupply, gs.TotalSupply)
assert.Equal(bondedShares, gs.BondedPool)
assert.Equal(unbondedShares, gs.UnbondedPool)
// test the value of candidate shares
assert.True(gs.bondedShareExRate().Equal(rational.One), "%v", gs.bondedShareExRate())
initialSupply := gs.TotalSupply
initialUnbonded := gs.TotalSupply - gs.BondedPool
// process the provisions a year
for hr := 0; hr < 8766; hr++ {
expInflation := nextInflation(gs, params).Round(1000000000)
expProvisions := (expInflation.Mul(rational.New(gs.TotalSupply)).Quo(hrsPerYr)).Evaluate()
startBondedPool := gs.BondedPool
startTotalSupply := gs.TotalSupply
processProvisions(store, gs, params)
assert.Equal(startBondedPool+expProvisions, gs.BondedPool)
assert.Equal(startTotalSupply+expProvisions, gs.TotalSupply)
}
assert.NotEqual(initialSupply, gs.TotalSupply)
assert.Equal(initialUnbonded, gs.UnbondedPool)
//panic(fmt.Sprintf("debug total %v, bonded %v, diff %v\n", gs.TotalSupply, gs.BondedPool, gs.TotalSupply-gs.BondedPool))
// initial bonded ratio ~ 35% ~ 30% increase for bonded holders
assert.True(gs.bondedRatio().Equal(rational.New(105906511, 305906511)), "%v", gs.bondedRatio())
// global supply
assert.Equal(int64(611813022), gs.TotalSupply)
assert.Equal(int64(211813022), gs.BondedPool)
assert.Equal(unbondedShares, gs.UnbondedPool)
// test the value of candidate shares
assert.True(gs.bondedShareExRate().Mul(rational.New(bondedShares)).Equal(rational.New(211813022)), "%v", gs.bondedShareExRate())
}

View File

@ -1,147 +0,0 @@
package stake
import (
"fmt"
sdk "github.com/cosmos/cosmos-sdk"
"github.com/cosmos/cosmos-sdk/modules/coin"
crypto "github.com/tendermint/go-crypto"
)
// Tx
//--------------------------------------------------------------------------------
// register the tx type with its validation logic
// make sure to use the name of the handler as the prefix in the tx type,
// so it gets routed properly
const (
ByteTxDeclareCandidacy = 0x55
ByteTxEditCandidacy = 0x56
ByteTxDelegate = 0x57
ByteTxUnbond = 0x58
TypeTxDeclareCandidacy = stakingModuleName + "/declareCandidacy"
TypeTxEditCandidacy = stakingModuleName + "/editCandidacy"
TypeTxDelegate = stakingModuleName + "/delegate"
TypeTxUnbond = stakingModuleName + "/unbond"
)
func init() {
sdk.TxMapper.RegisterImplementation(TxDeclareCandidacy{}, TypeTxDeclareCandidacy, ByteTxDeclareCandidacy)
sdk.TxMapper.RegisterImplementation(TxEditCandidacy{}, TypeTxEditCandidacy, ByteTxEditCandidacy)
sdk.TxMapper.RegisterImplementation(TxDelegate{}, TypeTxDelegate, ByteTxDelegate)
sdk.TxMapper.RegisterImplementation(TxUnbond{}, TypeTxUnbond, ByteTxUnbond)
}
//Verify interface at compile time
var _, _, _, _ sdk.TxInner = &TxDeclareCandidacy{}, &TxEditCandidacy{}, &TxDelegate{}, &TxUnbond{}
// BondUpdate - struct for bonding or unbonding transactions
type BondUpdate struct {
PubKey crypto.PubKey `json:"pub_key"`
Bond coin.Coin `json:"amount"`
}
// ValidateBasic - Check for non-empty candidate, and valid coins
func (tx BondUpdate) ValidateBasic() error {
if tx.PubKey.Empty() {
return errCandidateEmpty
}
coins := coin.Coins{tx.Bond}
if !coins.IsValid() {
return coin.ErrInvalidCoins()
}
if !coins.IsPositive() {
return fmt.Errorf("Amount must be > 0")
}
return nil
}
// TxDeclareCandidacy - struct for unbonding transactions
type TxDeclareCandidacy struct {
BondUpdate
Description
}
// NewTxDeclareCandidacy - new TxDeclareCandidacy
func NewTxDeclareCandidacy(bond coin.Coin, pubKey crypto.PubKey, description Description) sdk.Tx {
return TxDeclareCandidacy{
BondUpdate{
PubKey: pubKey,
Bond: bond,
},
description,
}.Wrap()
}
// Wrap - Wrap a Tx as a Basecoin Tx
func (tx TxDeclareCandidacy) Wrap() sdk.Tx { return sdk.Tx{tx} }
// TxEditCandidacy - struct for editing a candidate
type TxEditCandidacy struct {
PubKey crypto.PubKey `json:"pub_key"`
Description
}
// NewTxEditCandidacy - new TxEditCandidacy
func NewTxEditCandidacy(pubKey crypto.PubKey, description Description) sdk.Tx {
return TxEditCandidacy{
PubKey: pubKey,
Description: description,
}.Wrap()
}
// Wrap - Wrap a Tx as a Basecoin Tx
func (tx TxEditCandidacy) Wrap() sdk.Tx { return sdk.Tx{tx} }
// ValidateBasic - Check for non-empty candidate,
func (tx TxEditCandidacy) ValidateBasic() error {
if tx.PubKey.Empty() {
return errCandidateEmpty
}
empty := Description{}
if tx.Description == empty {
return fmt.Errorf("Transaction must include some information to modify")
}
return nil
}
// TxDelegate - struct for bonding transactions
type TxDelegate struct{ BondUpdate }
// NewTxDelegate - new TxDelegate
func NewTxDelegate(bond coin.Coin, pubKey crypto.PubKey) sdk.Tx {
return TxDelegate{BondUpdate{
PubKey: pubKey,
Bond: bond,
}}.Wrap()
}
// Wrap - Wrap a Tx as a Basecoin Tx
func (tx TxDelegate) Wrap() sdk.Tx { return sdk.Tx{tx} }
// TxUnbond - struct for unbonding transactions
type TxUnbond struct {
PubKey crypto.PubKey `json:"pub_key"`
Shares string `json:"amount"`
}
// NewTxUnbond - new TxUnbond
func NewTxUnbond(shares string, pubKey crypto.PubKey) sdk.Tx {
return TxUnbond{
PubKey: pubKey,
Shares: shares,
}.Wrap()
}
// Wrap - Wrap a Tx as a Basecoin Tx
func (tx TxUnbond) Wrap() sdk.Tx { return sdk.Tx{tx} }
// ValidateBasic - Check for non-empty candidate, positive shares
func (tx TxUnbond) ValidateBasic() error {
if tx.PubKey.Empty() {
return errCandidateEmpty
}
return nil
}

View File

@ -1,104 +0,0 @@
package stake
import (
"strconv"
"testing"
"github.com/stretchr/testify/assert"
"github.com/cosmos/cosmos-sdk"
"github.com/cosmos/cosmos-sdk/modules/coin"
crypto "github.com/tendermint/go-crypto"
wire "github.com/tendermint/go-wire"
)
var (
validator = sdk.Actor{"testChain", "testapp", []byte("addressvalidator1")}
empty sdk.Actor
coinPos = coin.Coin{"fermion", 1000}
coinZero = coin.Coin{"fermion", 0}
coinNeg = coin.Coin{"fermion", -10000}
coinPosNotAtoms = coin.Coin{"foo", 10000}
coinZeroNotAtoms = coin.Coin{"foo", 0}
coinNegNotAtoms = coin.Coin{"foo", -10000}
)
func TestBondUpdateValidateBasic(t *testing.T) {
tests := []struct {
name string
PubKey crypto.PubKey
Bond coin.Coin
wantErr bool
}{
{"basic good", pks[0], coinPos, false},
{"empty delegator", crypto.PubKey{}, coinPos, true},
{"zero coin", pks[0], coinZero, true},
{"neg coin", pks[0], coinNeg, true},
}
for _, tc := range tests {
tx := TxDelegate{BondUpdate{
PubKey: tc.PubKey,
Bond: tc.Bond,
}}
assert.Equal(t, tc.wantErr, tx.ValidateBasic() != nil,
"test: %v, tx.ValidateBasic: %v", tc.name, tx.ValidateBasic())
}
}
func TestAllAreTx(t *testing.T) {
assert := assert.New(t)
// make sure all types construct properly
pubKey := newPubKey("1234567890")
bondAmt := 1234321
bond := coin.Coin{Denom: "ATOM", Amount: int64(bondAmt)}
// Note that Wrap is only defined on BondUpdate, so when you call it,
// you lose all info on the embedding type. Please add Wrap()
// method to all the parents
txDelegate := NewTxDelegate(bond, pubKey)
_, ok := txDelegate.Unwrap().(TxDelegate)
assert.True(ok, "%#v", txDelegate)
txUnbond := NewTxUnbond(strconv.Itoa(bondAmt), pubKey)
_, ok = txUnbond.Unwrap().(TxUnbond)
assert.True(ok, "%#v", txUnbond)
txDecl := NewTxDeclareCandidacy(bond, pubKey, Description{})
_, ok = txDecl.Unwrap().(TxDeclareCandidacy)
assert.True(ok, "%#v", txDecl)
txEditCan := NewTxEditCandidacy(pubKey, Description{})
_, ok = txEditCan.Unwrap().(TxEditCandidacy)
assert.True(ok, "%#v", txEditCan)
}
func TestSerializeTx(t *testing.T) {
assert := assert.New(t)
// make sure all types construct properly
pubKey := newPubKey("1234567890")
bondAmt := 1234321
bond := coin.Coin{Denom: "ATOM", Amount: int64(bondAmt)}
tests := []struct {
tx sdk.Tx
}{
{NewTxUnbond(strconv.Itoa(bondAmt), pubKey)},
{NewTxDeclareCandidacy(bond, pubKey, Description{})},
{NewTxDeclareCandidacy(bond, pubKey, Description{})},
// {NewTxRevokeCandidacy(pubKey)},
}
for i, tc := range tests {
var tx sdk.Tx
bs := wire.BinaryBytes(tc.tx)
err := wire.ReadBinaryBytes(bs, &tx)
if assert.NoError(err, "%d", i) {
assert.Equal(tc.tx, tx, "%d", i)
}
}
}

View File

@ -4,8 +4,7 @@ import (
"bytes"
"sort"
"github.com/cosmos/cosmos-sdk"
"github.com/cosmos/cosmos-sdk/state"
"github.com/cosmos/cosmos-sdk/types"
abci "github.com/tendermint/abci/types"
crypto "github.com/tendermint/go-crypto"
@ -15,8 +14,8 @@ import (
// Params defines the high level settings for staking
type Params struct {
HoldBonded sdk.Actor `json:"hold_bonded"` // account where all bonded coins are held
HoldUnbonded sdk.Actor `json:"hold_unbonded"` // account where all delegated but unbonded coins are held
HoldBonded crypto.Address `json:"hold_bonded"` // account where all bonded coins are held
HoldUnbonded crypto.Address `json:"hold_unbonded"` // account where all delegated but unbonded coins are held
InflationRateChange rational.Rat `json:"inflation_rate_change"` // maximum annual change in inflation rate
InflationMax rational.Rat `json:"inflation_max"` // maximum inflation rate
@ -35,8 +34,8 @@ type Params struct {
func defaultParams() Params {
return Params{
HoldBonded: sdk.NewActor(stakingModuleName, []byte("77777777777777777777777777777777")),
HoldUnbonded: sdk.NewActor(stakingModuleName, []byte("88888888888888888888888888888888")),
HoldBonded: []byte("77777777777777777777777777777777"),
HoldUnbonded: []byte("88888888888888888888888888888888"),
InflationRateChange: rational.New(13, 100),
InflationMax: rational.New(20, 100),
InflationMin: rational.New(7, 100),
@ -151,7 +150,7 @@ const (
type Candidate struct {
Status CandidateStatus `json:"status"` // Bonded status
PubKey crypto.PubKey `json:"pub_key"` // Pubkey of candidate
Owner sdk.Actor `json:"owner"` // Sender of BondTx - UnbondTx returns here
Owner crypto.Address `json:"owner"` // Sender of BondTx - UnbondTx returns here
Assets rational.Rat `json:"assets"` // total shares of a global hold pools TODO custom type PoolShares
Liabilities rational.Rat `json:"liabilities"` // total shares issued to a candidate's delegators TODO custom type DelegatorShares
VotingPower rational.Rat `json:"voting_power"` // Voting power if considered a validator
@ -167,7 +166,7 @@ type Description struct {
}
// NewCandidate - initialize a new candidate
func NewCandidate(pubKey crypto.PubKey, owner sdk.Actor, description Description) *Candidate {
func NewCandidate(pubKey crypto.PubKey, owner crypto.Address, description Description) *Candidate {
return &Candidate{
Status: Unbonded,
PubKey: pubKey,
@ -234,8 +233,13 @@ type Validator Candidate
// ABCIValidator - Get the validator from a bond value
func (v Validator) ABCIValidator() *abci.Validator {
pk, err := wire.MarshalBinary(v.PubKey)
if err != nil {
panic(err)
}
return &abci.Validator{
PubKey: wire.BinaryBytes(v.PubKey),
PubKey: pk,
Power: v.VotingPower.Evaluate(),
}
}
@ -269,7 +273,7 @@ func (cs Candidates) Sort() {
}
// update the voting power and save
func (cs Candidates) updateVotingPower(store state.SimpleDB, gs *GlobalState, params Params) Candidates {
func (cs Candidates) updateVotingPower(store types.KVStore, gs *GlobalState, params Params) Candidates {
// update voting power
for _, c := range cs {
@ -391,7 +395,7 @@ func (vs Validators) validatorsUpdated(vs2 Validators) (updated []*abci.Validato
// UpdateValidatorSet - Updates the voting power for the candidate set and
// returns the subset of validators which have been updated for Tendermint
func UpdateValidatorSet(store state.SimpleDB, gs *GlobalState, params Params) (change []*abci.Validator, err error) {
func UpdateValidatorSet(store types.KVStore, gs *GlobalState, params Params) (change []*abci.Validator, err error) {
// get the validators before update
candidates := loadCandidates(store)

View File

@ -1,251 +0,0 @@
package stake
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/tendermint/tmlibs/rational"
"github.com/cosmos/cosmos-sdk/state"
)
func TestCandidatesSort(t *testing.T) {
assert, require := assert.New(t), require.New(t)
N := 5
actors := newActors(N)
candidates := candidatesFromActors(actors, []int64{10, 300, 123, 4, 200})
expectedOrder := []int{1, 4, 2, 0, 3}
// test basic sort
candidates.Sort()
vals := candidates.Validators()
require.Equal(N, len(vals))
for i, val := range vals {
expectedIdx := expectedOrder[i]
assert.Equal(val.PubKey, pks[expectedIdx])
}
}
func TestValidatorsSort(t *testing.T) {
assert := assert.New(t)
v1 := (&Candidate{PubKey: pks[0], VotingPower: rational.New(25)}).validator()
v2 := (&Candidate{PubKey: pks[1], VotingPower: rational.New(1234)}).validator()
v3 := (&Candidate{PubKey: pks[2], VotingPower: rational.New(122)}).validator()
v4 := (&Candidate{PubKey: pks[3], VotingPower: rational.New(13)}).validator()
v5 := (&Candidate{PubKey: pks[4], VotingPower: rational.New(1111)}).validator()
// test from nothing to something
vs := Validators{v4, v2, v5, v1, v3}
// test basic sort
vs.Sort()
for i, v := range vs {
assert.True(v.PubKey.Equals(pks[i]))
}
}
func TestUpdateVotingPower(t *testing.T) {
assert := assert.New(t)
store := state.NewMemKVStore()
params := loadParams(store)
gs := loadGlobalState(store)
N := 5
actors := newActors(N)
candidates := candidatesFromActors(actors, []int64{400, 200, 100, 10, 1})
// test a basic change in voting power
candidates[0].Assets = rational.New(500)
candidates.updateVotingPower(store, gs, params)
assert.Equal(int64(500), candidates[0].VotingPower.Evaluate(), "%v", candidates[0])
// test a swap in voting power
candidates[1].Assets = rational.New(600)
candidates.updateVotingPower(store, gs, params)
assert.Equal(int64(600), candidates[0].VotingPower.Evaluate(), "%v", candidates[0])
assert.Equal(int64(500), candidates[1].VotingPower.Evaluate(), "%v", candidates[1])
// test the max validators term
params.MaxVals = 4
saveParams(store, params)
candidates.updateVotingPower(store, gs, params)
assert.Equal(int64(0), candidates[4].VotingPower.Evaluate(), "%v", candidates[4])
}
func TestGetValidators(t *testing.T) {
assert, require := assert.New(t), require.New(t)
N := 5
actors := newActors(N)
candidates := candidatesFromActors(actors, []int64{400, 200, 0, 0, 0})
validators := candidates.Validators()
require.Equal(2, len(validators))
assert.Equal(candidates[0].PubKey, validators[0].PubKey)
assert.Equal(candidates[1].PubKey, validators[1].PubKey)
}
func TestValidatorsChanged(t *testing.T) {
require := require.New(t)
v1 := (&Candidate{PubKey: pks[0], VotingPower: rational.New(10)}).validator()
v2 := (&Candidate{PubKey: pks[1], VotingPower: rational.New(10)}).validator()
v3 := (&Candidate{PubKey: pks[2], VotingPower: rational.New(10)}).validator()
v4 := (&Candidate{PubKey: pks[3], VotingPower: rational.New(10)}).validator()
v5 := (&Candidate{PubKey: pks[4], VotingPower: rational.New(10)}).validator()
// test from nothing to something
vs1 := Validators{}
vs2 := Validators{v1, v2}
changed := vs1.validatorsUpdated(vs2)
require.Equal(2, len(changed))
testChange(t, vs2[0], changed[0])
testChange(t, vs2[1], changed[1])
// test from something to nothing
vs1 = Validators{v1, v2}
vs2 = Validators{}
changed = vs1.validatorsUpdated(vs2)
require.Equal(2, len(changed))
testRemove(t, vs1[0], changed[0])
testRemove(t, vs1[1], changed[1])
// test identical
vs1 = Validators{v1, v2, v4}
vs2 = Validators{v1, v2, v4}
changed = vs1.validatorsUpdated(vs2)
require.Zero(len(changed))
// test single value change
vs2[2].VotingPower = rational.One
changed = vs1.validatorsUpdated(vs2)
require.Equal(1, len(changed))
testChange(t, vs2[2], changed[0])
// test multiple value change
vs2[0].VotingPower = rational.New(11)
vs2[2].VotingPower = rational.New(5)
changed = vs1.validatorsUpdated(vs2)
require.Equal(2, len(changed))
testChange(t, vs2[0], changed[0])
testChange(t, vs2[2], changed[1])
// test validator added at the beginning
vs1 = Validators{v2, v4}
vs2 = Validators{v2, v4, v1}
changed = vs1.validatorsUpdated(vs2)
require.Equal(1, len(changed))
testChange(t, vs2[0], changed[0])
// test validator added in the middle
vs1 = Validators{v1, v2, v4}
vs2 = Validators{v3, v1, v4, v2}
changed = vs1.validatorsUpdated(vs2)
require.Equal(1, len(changed))
testChange(t, vs2[2], changed[0])
// test validator added at the end
vs2 = Validators{v1, v2, v4, v5}
changed = vs1.validatorsUpdated(vs2)
require.Equal(1, len(changed))
testChange(t, vs2[3], changed[0])
// test multiple validators added
vs2 = Validators{v1, v2, v3, v4, v5}
changed = vs1.validatorsUpdated(vs2)
require.Equal(2, len(changed))
testChange(t, vs2[2], changed[0])
testChange(t, vs2[4], changed[1])
// test validator removed at the beginning
vs2 = Validators{v2, v4}
changed = vs1.validatorsUpdated(vs2)
require.Equal(1, len(changed))
testRemove(t, vs1[0], changed[0])
// test validator removed in the middle
vs2 = Validators{v1, v4}
changed = vs1.validatorsUpdated(vs2)
require.Equal(1, len(changed))
testRemove(t, vs1[1], changed[0])
// test validator removed at the end
vs2 = Validators{v1, v2}
changed = vs1.validatorsUpdated(vs2)
require.Equal(1, len(changed))
testRemove(t, vs1[2], changed[0])
// test multiple validators removed
vs2 = Validators{v1}
changed = vs1.validatorsUpdated(vs2)
require.Equal(2, len(changed))
testRemove(t, vs1[1], changed[0])
testRemove(t, vs1[2], changed[1])
// test many types of changes
vs2 = Validators{v1, v3, v4, v5}
vs2[2].VotingPower = rational.New(11)
changed = vs1.validatorsUpdated(vs2)
require.Equal(4, len(changed), "%v", changed) // change 1, remove 1, add 2
testRemove(t, vs1[1], changed[0])
testChange(t, vs2[1], changed[1])
testChange(t, vs2[2], changed[2])
testChange(t, vs2[3], changed[3])
}
func TestUpdateValidatorSet(t *testing.T) {
assert, require := assert.New(t), require.New(t)
store := state.NewMemKVStore()
params := loadParams(store)
gs := loadGlobalState(store)
N := 5
actors := newActors(N)
candidates := candidatesFromActors(actors, []int64{400, 200, 100, 10, 1})
for _, c := range candidates {
saveCandidate(store, c)
}
// they should all already be validators
change, err := UpdateValidatorSet(store, gs, params)
require.Nil(err)
require.Equal(0, len(change), "%v", change) // change 1, remove 1, add 2
// test the max value and test again
params.MaxVals = 4
saveParams(store, params)
change, err = UpdateValidatorSet(store, gs, params)
require.Nil(err)
require.Equal(1, len(change), "%v", change)
testRemove(t, candidates[4].validator(), change[0])
candidates = loadCandidates(store)
assert.Equal(int64(0), candidates[4].VotingPower.Evaluate())
// mess with the power's of the candidates and test
candidates[0].Assets = rational.New(10)
candidates[1].Assets = rational.New(600)
candidates[2].Assets = rational.New(1000)
candidates[3].Assets = rational.One
candidates[4].Assets = rational.New(10)
for _, c := range candidates {
saveCandidate(store, c)
}
change, err = UpdateValidatorSet(store, gs, params)
require.Nil(err)
require.Equal(5, len(change), "%v", change) // 3 changed, 1 added, 1 removed
candidates = loadCandidates(store)
testChange(t, candidates[0].validator(), change[0])
testChange(t, candidates[1].validator(), change[1])
testChange(t, candidates[2].validator(), change[2])
testRemove(t, candidates[3].validator(), change[3])
testChange(t, candidates[4].validator(), change[4])
}
// XXX test global state functions, candidate exchange rate functions etc.