From 5d3dc29ce1303e30ff5336272bced8e997b9a5df Mon Sep 17 00:00:00 2001 From: rigelrozanski Date: Sat, 24 Feb 2018 13:19:32 +0000 Subject: [PATCH] bringing in more --- x/stake/commands/query.go | 141 +++++++++++++++++++++++++++ x/stake/commands/tx.go | 195 ++++++++++++++++++++++++++++++++++++++ x/stake/tx.go | 146 ++++++++++++++++++++++++++++ x/stake/tx_test.go | 104 ++++++++++++++++++++ 4 files changed, 586 insertions(+) create mode 100644 x/stake/commands/query.go create mode 100644 x/stake/commands/tx.go create mode 100644 x/stake/tx.go create mode 100644 x/stake/tx_test.go diff --git a/x/stake/commands/query.go b/x/stake/commands/query.go new file mode 100644 index 0000000000..aa455d3474 --- /dev/null +++ b/x/stake/commands/query.go @@ -0,0 +1,141 @@ +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" + coin "github.com/cosmos/cosmos-sdk/x/bank" // XXX fix + "github.com/cosmos/gaia/x/stake" +) + +// XXX remove dependancy +func PrefixedKey(app string, key []byte) []byte { + prefix := append([]byte(app), byte(0)) + return append(prefix, key...) +} + +//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 := 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 := 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 := 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 := 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) +} diff --git a/x/stake/commands/tx.go b/x/stake/commands/tx.go new file mode 100644 index 0000000000..e14ac8cec1 --- /dev/null +++ b/x/stake/commands/tx.go @@ -0,0 +1,195 @@ +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 +} diff --git a/x/stake/tx.go b/x/stake/tx.go new file mode 100644 index 0000000000..3d5a6c4083 --- /dev/null +++ b/x/stake/tx.go @@ -0,0 +1,146 @@ +package stake + +import ( + "fmt" + + sdk "github.com/cosmos/cosmos-sdk/types" + coin "github.com/cosmos/cosmos-sdk/x/bank" // XXX fix + 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 +} diff --git a/x/stake/tx_test.go b/x/stake/tx_test.go new file mode 100644 index 0000000000..f6d814589d --- /dev/null +++ b/x/stake/tx_test.go @@ -0,0 +1,104 @@ +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) + } + } +}