From bb624b36aa15e5b2ef28499ef16fc2b11b6d3f00 Mon Sep 17 00:00:00 2001 From: ValarDragon Date: Sat, 22 Sep 2018 20:20:53 -0700 Subject: [PATCH 01/26] simulation: Use a simulation.Account struct This removes privkeys and addresses from function signatures. This comes with a 11% performance improvement to the simulator, as we no longer keep recomputing the pubkeys. Once we move the transient store clearing to endblock, we can further raise the size of make test_sim_gaia_fast concretely, `make test_sim_gaia_fast` went from 16.8 seconds to 13.5 seconds on my system. --- PENDING.md | 1 + cmd/gaia/app/sim_test.go | 9 +-- x/bank/simulation/msgs.go | 21 +++--- x/bank/simulation/sim_test.go | 6 +- x/gov/simulation/msgs.go | 41 +++++------ x/gov/simulation/sim_test.go | 7 +- x/mock/simulation/random_simulate_blocks.go | 38 +++++----- x/mock/simulation/types.go | 18 ++++- x/mock/simulation/util.go | 40 ++++++++-- x/slashing/simulation/msgs.go | 8 +- x/stake/simulation/msgs.go | 81 ++++++++++----------- x/stake/simulation/sim_test.go | 5 +- 12 files changed, 151 insertions(+), 124 deletions(-) diff --git a/PENDING.md b/PENDING.md index ab0a42d45d..543010eb17 100644 --- a/PENDING.md +++ b/PENDING.md @@ -44,6 +44,7 @@ BREAKING CHANGES * [types] [\#2119](https://github.com/cosmos/cosmos-sdk/issues/2119) Parsed error messages and ABCI log errors to make them more human readable. * [simulation] Rename TestAndRunTx to Operation [#2153](https://github.com/cosmos/cosmos-sdk/pull/2153) * [simulation] Remove log and testing.TB from Operation and Invariants, in favor of using errors \#2282 + * [simulation] Remove usage of keys and addrs in the types, in favor of simulation.Account \#2384 * [tools] Removed gocyclo [#2211](https://github.com/cosmos/cosmos-sdk/issues/2211) * [baseapp] Remove `SetTxDecoder` in favor of requiring the decoder be set in baseapp initialization. [#1441](https://github.com/cosmos/cosmos-sdk/issues/1441) * [baseapp] [\#1921](https://github.com/cosmos/cosmos-sdk/issues/1921) Add minimumFees field to BaseApp. diff --git a/cmd/gaia/app/sim_test.go b/cmd/gaia/app/sim_test.go index 078eece110..6097b5a08c 100644 --- a/cmd/gaia/app/sim_test.go +++ b/cmd/gaia/app/sim_test.go @@ -10,7 +10,6 @@ import ( "github.com/stretchr/testify/require" - "github.com/tendermint/tendermint/crypto" dbm "github.com/tendermint/tendermint/libs/db" "github.com/tendermint/tendermint/libs/log" @@ -42,14 +41,14 @@ func init() { flag.BoolVar(&commit, "SimulationCommit", false, "Have the simulation commit") } -func appStateFn(r *rand.Rand, keys []crypto.PrivKey, accs []sdk.AccAddress) json.RawMessage { +func appStateFn(r *rand.Rand, accs []simulation.Account) json.RawMessage { var genesisAccounts []GenesisAccount // Randomly generate some genesis accounts for _, acc := range accs { coins := sdk.Coins{sdk.Coin{"steak", sdk.NewInt(100)}} genesisAccounts = append(genesisAccounts, GenesisAccount{ - Address: acc, + Address: acc.Address, Coins: coins, }) } @@ -61,10 +60,10 @@ func appStateFn(r *rand.Rand, keys []crypto.PrivKey, accs []sdk.AccAddress) json // XXX Try different numbers of initially bonded validators numInitiallyBonded := int64(50) for i := 0; i < int(numInitiallyBonded); i++ { - validator := stake.NewValidator(sdk.ValAddress(accs[i]), keys[i].PubKey(), stake.Description{}) + validator := stake.NewValidator(sdk.ValAddress(accs[i].Address), accs[i].PubKey, stake.Description{}) validator.Tokens = sdk.NewDec(100) validator.DelegatorShares = sdk.NewDec(100) - delegation := stake.Delegation{accs[i], sdk.ValAddress(accs[i]), sdk.NewDec(100), 0} + delegation := stake.Delegation{accs[i].Address, sdk.ValAddress(accs[i].Address), sdk.NewDec(100), 0} validators = append(validators, validator) delegations = append(delegations, delegation) } diff --git a/x/bank/simulation/msgs.go b/x/bank/simulation/msgs.go index cefa2a460b..0b8ec3026f 100644 --- a/x/bank/simulation/msgs.go +++ b/x/bank/simulation/msgs.go @@ -18,19 +18,18 @@ import ( // SimulateSingleInputMsgSend tests and runs a single msg send, with one input and one output, where both // accounts already exist. func SimulateSingleInputMsgSend(mapper auth.AccountMapper) simulation.Operation { - return func(r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, keys []crypto.PrivKey, event func(string)) (action string, fOps []simulation.FutureOperation, err error) { - fromKey := simulation.RandomKey(r, keys) - fromAddr := sdk.AccAddress(fromKey.PubKey().Address()) - toKey := simulation.RandomKey(r, keys) + return func(r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, accs []simulation.Account, event func(string)) (action string, fOps []simulation.FutureOperation, err error) { + fromAcc := simulation.RandomAcc(r, accs) + toAcc := simulation.RandomAcc(r, accs) // Disallow sending money to yourself for { - if !fromKey.Equals(toKey) { + if !fromAcc.PubKey.Equals(toAcc.PubKey) { break } - toKey = simulation.RandomKey(r, keys) + toAcc = simulation.RandomAcc(r, accs) } - toAddr := sdk.AccAddress(toKey.PubKey().Address()) - initFromCoins := mapper.GetAccount(ctx, fromAddr).GetCoins() + toAddr := toAcc.Address + initFromCoins := mapper.GetAccount(ctx, fromAcc.Address).GetCoins() if len(initFromCoins) == 0 { return "skipping, no coins at all", nil, nil @@ -43,7 +42,7 @@ func SimulateSingleInputMsgSend(mapper auth.AccountMapper) simulation.Operation } action = fmt.Sprintf("%s is sending %s %s to %s", - fromAddr.String(), + fromAcc.Address.String(), amt.String(), initFromCoins[denomIndex].Denom, toAddr.String(), @@ -51,10 +50,10 @@ func SimulateSingleInputMsgSend(mapper auth.AccountMapper) simulation.Operation coins := sdk.Coins{{initFromCoins[denomIndex].Denom, amt}} var msg = bank.MsgSend{ - Inputs: []bank.Input{bank.NewInput(fromAddr, coins)}, + Inputs: []bank.Input{bank.NewInput(fromAcc.Address, coins)}, Outputs: []bank.Output{bank.NewOutput(toAddr, coins)}, } - goErr = sendAndVerifyMsgSend(app, mapper, msg, ctx, []crypto.PrivKey{fromKey}) + goErr = sendAndVerifyMsgSend(app, mapper, msg, ctx, []crypto.PrivKey{fromAcc.PrivKey}) if goErr != nil { return "", nil, goErr } diff --git a/x/bank/simulation/sim_test.go b/x/bank/simulation/sim_test.go index 8115355077..819289845d 100644 --- a/x/bank/simulation/sim_test.go +++ b/x/bank/simulation/sim_test.go @@ -5,8 +5,6 @@ import ( "math/rand" "testing" - "github.com/tendermint/tendermint/crypto" - sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/x/bank" "github.com/cosmos/cosmos-sdk/x/mock" @@ -26,8 +24,8 @@ func TestBankWithRandomMessages(t *testing.T) { panic(err) } - appStateFn := func(r *rand.Rand, keys []crypto.PrivKey, accs []sdk.AccAddress) json.RawMessage { - mock.RandomSetGenesis(r, mapp, accs, []string{"stake"}) + appStateFn := func(r *rand.Rand, accs []simulation.Account) json.RawMessage { + simulation.RandomSetGenesis(r, mapp, accs, []string{"stake"}) return json.RawMessage("{}") } diff --git a/x/gov/simulation/msgs.go b/x/gov/simulation/msgs.go index f46fe995e1..d5ff1881e4 100644 --- a/x/gov/simulation/msgs.go +++ b/x/gov/simulation/msgs.go @@ -6,8 +6,6 @@ import ( "math/rand" "time" - "github.com/tendermint/tendermint/crypto" - "github.com/cosmos/cosmos-sdk/baseapp" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/x/gov" @@ -45,9 +43,9 @@ func SimulateSubmittingVotingAndSlashingForProposal(k gov.Keeper, sk stake.Keepe }) statePercentageArray := []float64{1, .9, .75, .4, .15, 0} curNumVotesState := 1 - return func(r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, keys []crypto.PrivKey, event func(string)) (action string, fOps []simulation.FutureOperation, err error) { + return func(r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, accs []simulation.Account, event func(string)) (action string, fOps []simulation.FutureOperation, err error) { // 1) submit proposal now - sender := simulation.RandomKey(r, keys) + sender := simulation.RandomAcc(r, accs) msg, err := simulationCreateMsgSubmitProposal(r, sender) if err != nil { return "", nil, err @@ -61,16 +59,16 @@ func SimulateSubmittingVotingAndSlashingForProposal(k gov.Keeper, sk stake.Keepe // 2) Schedule operations for votes // 2.1) first pick a number of people to vote. curNumVotesState = numVotesTransitionMatrix.NextState(r, curNumVotesState) - numVotes := int(math.Ceil(float64(len(keys)) * statePercentageArray[curNumVotesState])) + numVotes := int(math.Ceil(float64(len(accs)) * statePercentageArray[curNumVotesState])) // 2.2) select who votes and when - whoVotes := r.Perm(len(keys)) + whoVotes := r.Perm(len(accs)) // didntVote := whoVotes[numVotes:] whoVotes = whoVotes[:numVotes] votingPeriod := k.GetVotingProcedure(ctx).VotingPeriod fops := make([]simulation.FutureOperation, numVotes+1) for i := 0; i < numVotes; i++ { whenVote := ctx.BlockHeader().Time.Add(time.Duration(r.Int63n(int64(votingPeriod.Seconds()))) * time.Second) - fops[i] = simulation.FutureOperation{BlockTime: whenVote, Op: operationSimulateMsgVote(k, sk, keys[whoVotes[i]], proposalID)} + fops[i] = simulation.FutureOperation{BlockTime: whenVote, Op: operationSimulateMsgVote(k, sk, accs[whoVotes[i]], proposalID)} } // 3) Make an operation to ensure slashes were done correctly. (Really should be a future invariant) // TODO: Find a way to check if a validator was slashed other than just checking their balance a block @@ -84,8 +82,8 @@ func SimulateSubmittingVotingAndSlashingForProposal(k gov.Keeper, sk stake.Keepe // Note: Currently doesn't ensure that the proposal txt is in JSON form func SimulateMsgSubmitProposal(k gov.Keeper, sk stake.Keeper) simulation.Operation { handler := gov.NewHandler(k) - return func(r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, keys []crypto.PrivKey, event func(string)) (action string, fOps []simulation.FutureOperation, err error) { - sender := simulation.RandomKey(r, keys) + return func(r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, accs []simulation.Account, event func(string)) (action string, fOps []simulation.FutureOperation, err error) { + sender := simulation.RandomAcc(r, accs) msg, err := simulationCreateMsgSubmitProposal(r, sender) if err != nil { return "", nil, err @@ -111,14 +109,13 @@ func simulateHandleMsgSubmitProposal(msg gov.MsgSubmitProposal, sk stake.Keeper, return } -func simulationCreateMsgSubmitProposal(r *rand.Rand, sender crypto.PrivKey) (msg gov.MsgSubmitProposal, err error) { - addr := sdk.AccAddress(sender.PubKey().Address()) +func simulationCreateMsgSubmitProposal(r *rand.Rand, sender simulation.Account) (msg gov.MsgSubmitProposal, err error) { deposit := randomDeposit(r) msg = gov.NewMsgSubmitProposal( simulation.RandStringOfLength(r, 5), simulation.RandStringOfLength(r, 5), gov.ProposalTypeText, - addr, + sender.Address, deposit, ) if msg.ValidateBasic() != nil { @@ -129,15 +126,14 @@ func simulationCreateMsgSubmitProposal(r *rand.Rand, sender crypto.PrivKey) (msg // SimulateMsgDeposit func SimulateMsgDeposit(k gov.Keeper, sk stake.Keeper) simulation.Operation { - return func(r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, keys []crypto.PrivKey, event func(string)) (action string, fOp []simulation.FutureOperation, err error) { - key := simulation.RandomKey(r, keys) - addr := sdk.AccAddress(key.PubKey().Address()) + return func(r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, accs []simulation.Account, event func(string)) (action string, fOp []simulation.FutureOperation, err error) { + acc := simulation.RandomAcc(r, accs) proposalID, ok := randomProposalID(r, k, ctx) if !ok { return "no-operation", nil, nil } deposit := randomDeposit(r) - msg := gov.NewMsgDeposit(addr, proposalID, deposit) + msg := gov.NewMsgDeposit(acc.Address, proposalID, deposit) if msg.ValidateBasic() != nil { return "", nil, fmt.Errorf("expected msg to pass ValidateBasic: %s", msg.GetSignBytes()) } @@ -159,14 +155,14 @@ func SimulateMsgDeposit(k gov.Keeper, sk stake.Keeper) simulation.Operation { // SimulateMsgVote // nolint: unparam func SimulateMsgVote(k gov.Keeper, sk stake.Keeper) simulation.Operation { - return operationSimulateMsgVote(k, sk, nil, -1) + return operationSimulateMsgVote(k, sk, simulation.Account{}, -1) } // nolint: unparam -func operationSimulateMsgVote(k gov.Keeper, sk stake.Keeper, key crypto.PrivKey, proposalID int64) simulation.Operation { - return func(r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, keys []crypto.PrivKey, event func(string)) (action string, fOp []simulation.FutureOperation, err error) { - if key == nil { - key = simulation.RandomKey(r, keys) +func operationSimulateMsgVote(k gov.Keeper, sk stake.Keeper, acc simulation.Account, proposalID int64) simulation.Operation { + return func(r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, accs []simulation.Account, event func(string)) (action string, fOp []simulation.FutureOperation, err error) { + if acc.Equals(simulation.Account{}) { + acc = simulation.RandomAcc(r, accs) } var ok bool @@ -177,10 +173,9 @@ func operationSimulateMsgVote(k gov.Keeper, sk stake.Keeper, key crypto.PrivKey, return "no-operation", nil, nil } } - addr := sdk.AccAddress(key.PubKey().Address()) option := randomVotingOption(r) - msg := gov.NewMsgVote(addr, proposalID, option) + msg := gov.NewMsgVote(acc.Address, proposalID, option) if msg.ValidateBasic() != nil { return "", nil, fmt.Errorf("expected msg to pass ValidateBasic: %s", msg.GetSignBytes()) } diff --git a/x/gov/simulation/sim_test.go b/x/gov/simulation/sim_test.go index 35a11fb078..b0317d1161 100644 --- a/x/gov/simulation/sim_test.go +++ b/x/gov/simulation/sim_test.go @@ -6,7 +6,6 @@ import ( "testing" abci "github.com/tendermint/tendermint/abci/types" - "github.com/tendermint/tendermint/crypto" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/x/bank" @@ -43,12 +42,12 @@ func TestGovWithRandomMessages(t *testing.T) { panic(err) } - appStateFn := func(r *rand.Rand, keys []crypto.PrivKey, accs []sdk.AccAddress) json.RawMessage { - mock.RandomSetGenesis(r, mapp, accs, []string{"stake"}) + appStateFn := func(r *rand.Rand, accs []simulation.Account) json.RawMessage { + simulation.RandomSetGenesis(r, mapp, accs, []string{"stake"}) return json.RawMessage("{}") } - setup := func(r *rand.Rand, privKeys []crypto.PrivKey) { + setup := func(r *rand.Rand, accs []simulation.Account) { ctx := mapp.NewContext(false, abci.Header{}) stake.InitGenesis(ctx, stakeKeeper, stake.DefaultGenesisState()) gov.InitGenesis(ctx, govKeeper, gov.DefaultGenesisState()) diff --git a/x/mock/simulation/random_simulate_blocks.go b/x/mock/simulation/random_simulate_blocks.go index 96dbff5725..25748e15bd 100644 --- a/x/mock/simulation/random_simulate_blocks.go +++ b/x/mock/simulation/random_simulate_blocks.go @@ -15,17 +15,15 @@ import ( "time" abci "github.com/tendermint/tendermint/abci/types" - "github.com/tendermint/tendermint/crypto" tmtypes "github.com/tendermint/tendermint/types" "github.com/cosmos/cosmos-sdk/baseapp" sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/cosmos/cosmos-sdk/x/mock" ) // Simulate tests application by sending random messages. func Simulate(t *testing.T, app *baseapp.BaseApp, - appStateFn func(r *rand.Rand, keys []crypto.PrivKey, accs []sdk.AccAddress) json.RawMessage, + appStateFn func(r *rand.Rand, accs []Account) json.RawMessage, ops []WeightedOperation, setups []RandSetup, invariants []Invariant, numBlocks int, blockSize int, commit bool) error { @@ -33,16 +31,16 @@ func Simulate(t *testing.T, app *baseapp.BaseApp, return SimulateFromSeed(t, app, appStateFn, time, ops, setups, invariants, numBlocks, blockSize, commit) } -func initChain(r *rand.Rand, keys []crypto.PrivKey, accs []sdk.AccAddress, setups []RandSetup, app *baseapp.BaseApp, - appStateFn func(r *rand.Rand, keys []crypto.PrivKey, accs []sdk.AccAddress) json.RawMessage) (validators map[string]mockValidator) { - res := app.InitChain(abci.RequestInitChain{AppStateBytes: appStateFn(r, keys, accs)}) +func initChain(r *rand.Rand, accounts []Account, setups []RandSetup, app *baseapp.BaseApp, + appStateFn func(r *rand.Rand, accounts []Account) json.RawMessage) (validators map[string]mockValidator) { + res := app.InitChain(abci.RequestInitChain{AppStateBytes: appStateFn(r, accounts)}) validators = make(map[string]mockValidator) for _, validator := range res.Validators { validators[string(validator.Address)] = mockValidator{validator, GetMemberOfInitialState(r, initialLivenessWeightings)} } for i := 0; i < len(setups); i++ { - setups[i](r, keys) + setups[i](r, accounts) } return @@ -56,7 +54,7 @@ func randTimestamp(r *rand.Rand) time.Time { // SimulateFromSeed tests an application by running the provided // operations, testing the provided invariants, but using the provided seed. func SimulateFromSeed(tb testing.TB, app *baseapp.BaseApp, - appStateFn func(r *rand.Rand, keys []crypto.PrivKey, accs []sdk.AccAddress) json.RawMessage, + appStateFn func(r *rand.Rand, accs []Account) json.RawMessage, seed int64, ops []WeightedOperation, setups []RandSetup, invariants []Invariant, numBlocks int, blockSize int, commit bool) (simError error) { @@ -69,7 +67,7 @@ func SimulateFromSeed(tb testing.TB, app *baseapp.BaseApp, fmt.Printf("Starting the simulation from time %v, unixtime %v\n", timestamp.UTC().Format(time.UnixDate), timestamp.Unix()) timeDiff := maxTimePerBlock - minTimePerBlock - keys, accs := mock.GeneratePrivKeyAddressPairsFromRand(r, numKeys) + accs := RandomAccounts(r, numKeys) // Setup event stats events := make(map[string]uint) @@ -77,7 +75,7 @@ func SimulateFromSeed(tb testing.TB, app *baseapp.BaseApp, events[what]++ } - validators := initChain(r, keys, accs, setups, app, appStateFn) + validators := initChain(r, accs, setups, app, appStateFn) header := abci.Header{Height: 0, Time: timestamp} opCount := 0 @@ -139,10 +137,10 @@ func SimulateFromSeed(tb testing.TB, app *baseapp.BaseApp, thisBlockSize := getBlockSize(r, blockSize) // Run queued operations. Ignores blocksize if blocksize is too small - numQueuedOpsRan := runQueuedOperations(operationQueue, int(header.Height), tb, r, app, ctx, keys, logWriter, displayLogs, event) - numQueuedTimeOpsRan := runQueuedTimeOperations(timeOperationQueue, header.Time, tb, r, app, ctx, keys, logWriter, displayLogs, event) + numQueuedOpsRan := runQueuedOperations(operationQueue, int(header.Height), tb, r, app, ctx, accs, logWriter, displayLogs, event) + numQueuedTimeOpsRan := runQueuedTimeOperations(timeOperationQueue, header.Time, tb, r, app, ctx, accs, logWriter, displayLogs, event) thisBlockSize = thisBlockSize - numQueuedOpsRan - numQueuedTimeOpsRan - operations := blockSimulator(thisBlockSize, r, app, ctx, keys, header, logWriter) + operations := blockSimulator(thisBlockSize, r, app, ctx, accs, header, logWriter) opCount += operations + numQueuedOpsRan + numQueuedTimeOpsRan res := app.EndBlock(abci.RequestEndBlock{}) @@ -176,7 +174,7 @@ func SimulateFromSeed(tb testing.TB, app *baseapp.BaseApp, // Returns a function to simulate blocks. Written like this to avoid constant parameters being passed everytime, to minimize // memory overhead func createBlockSimulator(testingMode bool, tb testing.TB, t *testing.T, event func(string), invariants []Invariant, ops []WeightedOperation, operationQueue map[int][]Operation, timeOperationQueue []FutureOperation, totalNumBlocks int, displayLogs func()) func( - blocksize int, r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, privKeys []crypto.PrivKey, header abci.Header, logWriter func(string)) (opCount int) { + blocksize int, r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, accounts []Account, header abci.Header, logWriter func(string)) (opCount int) { totalOpWeight := 0 for i := 0; i < len(ops); i++ { totalOpWeight += ops[i].Weight @@ -193,9 +191,9 @@ func createBlockSimulator(testingMode bool, tb testing.TB, t *testing.T, event f return ops[0].Op } return func(blocksize int, r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, - keys []crypto.PrivKey, header abci.Header, logWriter func(string)) (opCount int) { + accounts []Account, header abci.Header, logWriter func(string)) (opCount int) { for j := 0; j < blocksize; j++ { - logUpdate, futureOps, err := selectOp(r)(r, app, ctx, keys, event) + logUpdate, futureOps, err := selectOp(r)(r, app, ctx, accounts, event) if err != nil { displayLogs() tb.Fatalf("error on operation %d within block %d, %v", header.Height, opCount, err) @@ -264,14 +262,14 @@ func queueOperations(queuedOperations map[int][]Operation, queuedTimeOperations // nolint: errcheck func runQueuedOperations(queueOperations map[int][]Operation, height int, tb testing.TB, r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, - privKeys []crypto.PrivKey, logWriter func(string), displayLogs func(), event func(string)) (numOpsRan int) { + accounts []Account, logWriter func(string), displayLogs func(), event func(string)) (numOpsRan int) { if queuedOps, ok := queueOperations[height]; ok { numOps := len(queuedOps) for i := 0; i < numOps; i++ { // For now, queued operations cannot queue more operations. // If a need arises for us to support queued messages to queue more messages, this can // be changed. - logUpdate, _, err := queuedOps[i](r, app, ctx, privKeys, event) + logUpdate, _, err := queuedOps[i](r, app, ctx, accounts, event) logWriter(logUpdate) if err != nil { displayLogs() @@ -285,14 +283,14 @@ func runQueuedOperations(queueOperations map[int][]Operation, height int, tb tes } func runQueuedTimeOperations(queueOperations []FutureOperation, currentTime time.Time, tb testing.TB, r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, - privKeys []crypto.PrivKey, logWriter func(string), displayLogs func(), event func(string)) (numOpsRan int) { + accounts []Account, logWriter func(string), displayLogs func(), event func(string)) (numOpsRan int) { numOpsRan = 0 for len(queueOperations) > 0 && currentTime.After(queueOperations[0].BlockTime) { // For now, queued operations cannot queue more operations. // If a need arises for us to support queued messages to queue more messages, this can // be changed. - logUpdate, _, err := queueOperations[0].Op(r, app, ctx, privKeys, event) + logUpdate, _, err := queueOperations[0].Op(r, app, ctx, accounts, event) logWriter(logUpdate) if err != nil { displayLogs() diff --git a/x/mock/simulation/types.go b/x/mock/simulation/types.go index a32edea6d6..2e68a428ea 100644 --- a/x/mock/simulation/types.go +++ b/x/mock/simulation/types.go @@ -23,11 +23,11 @@ type ( // Operations can optionally provide a list of "FutureOperations" to run later // These will be ran at the beginning of the corresponding block. Operation func(r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, - privKeys []crypto.PrivKey, event func(string), + accounts []Account, event func(string), ) (action string, futureOperations []FutureOperation, err error) // RandSetup performs the random setup the mock module needs. - RandSetup func(r *rand.Rand, privKeys []crypto.PrivKey) + RandSetup func(r *rand.Rand, accounts []Account) // An Invariant is a function which tests a particular invariant. // If the invariant has been broken, it should return an error @@ -35,6 +35,15 @@ type ( // The simulator will then halt and print the logs. Invariant func(app *baseapp.BaseApp) error + // Account contains a privkey, pubkey, address tuple + // eventually more useful data can be placed in here. + // (e.g. number of coins) + Account struct { + PrivKey crypto.PrivKey + PubKey crypto.PubKey + Address sdk.AccAddress + } + mockValidator struct { val abci.Validator livenessState int @@ -70,3 +79,8 @@ func PeriodicInvariant(invariant Invariant, period int, offset int) Invariant { return nil } } + +// nolint +func (acc Account) Equals(acc2 Account) bool { + return acc.Address.Equals(acc2.Address) +} diff --git a/x/mock/simulation/util.go b/x/mock/simulation/util.go index a46d6dd549..54bfabc73f 100644 --- a/x/mock/simulation/util.go +++ b/x/mock/simulation/util.go @@ -9,10 +9,12 @@ import ( "testing" "time" - "github.com/tendermint/tendermint/crypto" + "github.com/tendermint/tendermint/crypto/ed25519" + "github.com/tendermint/tendermint/crypto/secp256k1" "github.com/cosmos/cosmos-sdk/baseapp" sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/mock" ) // shamelessly copied from https://stackoverflow.com/questions/22892120/how-to-generate-a-random-string-of-a-fixed-length-in-golang#31832326 @@ -56,10 +58,10 @@ func DisplayEvents(events map[string]uint) { } } -// Pick a random key from an array -func RandomKey(r *rand.Rand, keys []crypto.PrivKey) crypto.PrivKey { - return keys[r.Intn( - len(keys), +// RandomAcc pick a random account from an array +func RandomAcc(r *rand.Rand, accs []Account) Account { + return accs[r.Intn( + len(accs), )] } @@ -68,6 +70,25 @@ func RandomAmount(r *rand.Rand, max sdk.Int) sdk.Int { return sdk.NewInt(int64(r.Intn(int(max.Int64())))) } +// RandomAccounts generates n random accounts +func RandomAccounts(r *rand.Rand, n int) []Account { + accs := make([]Account, n) + for i := 0; i < n; i++ { + // don't need that much entropy for simulation + privkeySeed := make([]byte, 15) + r.Read(privkeySeed) + useSecp := r.Int63()%2 == 0 + if useSecp { + accs[i].PrivKey = secp256k1.GenPrivKeySecp256k1(privkeySeed) + } else { + accs[i].PrivKey = ed25519.GenPrivKeyFromSecret(privkeySeed) + } + accs[i].PubKey = accs[i].PrivKey.PubKey() + accs[i].Address = sdk.AccAddress(accs[i].PubKey.Address()) + } + return accs +} + // Builds a function to add logs for this particular block func addLogMessage(testingmode bool, blockLogBuilders []*strings.Builder, height int) func(string) { if testingmode { @@ -92,6 +113,15 @@ func assertAllInvariants(t *testing.T, app *baseapp.BaseApp, invariants []Invari } } +// RandomSetGenesis wraps mock.RandomSetGenesis, but using simulation accounts +func RandomSetGenesis(r *rand.Rand, app *mock.App, accs []Account, denoms []string) { + addrs := make([]sdk.AccAddress, len(accs)) + for i := 0; i < len(accs); i++ { + addrs[i] = accs[i].Address + } + mock.RandomSetGenesis(r, app, addrs, denoms) +} + // Creates a function to print out the logs func logPrinter(testingmode bool, logs []*strings.Builder) func() { if testingmode { diff --git a/x/slashing/simulation/msgs.go b/x/slashing/simulation/msgs.go index da9340baf4..2b09226f23 100644 --- a/x/slashing/simulation/msgs.go +++ b/x/slashing/simulation/msgs.go @@ -4,8 +4,6 @@ import ( "fmt" "math/rand" - "github.com/tendermint/tendermint/crypto" - "github.com/cosmos/cosmos-sdk/baseapp" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/x/mock/simulation" @@ -14,9 +12,9 @@ import ( // SimulateMsgUnjail func SimulateMsgUnjail(k slashing.Keeper) simulation.Operation { - return func(r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, keys []crypto.PrivKey, event func(string)) (action string, fOp []simulation.FutureOperation, err error) { - key := simulation.RandomKey(r, keys) - address := sdk.ValAddress(key.PubKey().Address()) + return func(r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, accs []simulation.Account, event func(string)) (action string, fOp []simulation.FutureOperation, err error) { + acc := simulation.RandomAcc(r, accs) + address := sdk.ValAddress(acc.Address) msg := slashing.NewMsgUnjail(address) if msg.ValidateBasic() != nil { return "", nil, fmt.Errorf("expected msg to pass ValidateBasic: %s", msg.GetSignBytes()) diff --git a/x/stake/simulation/msgs.go b/x/stake/simulation/msgs.go index 5b2bc1ee81..906716ed15 100644 --- a/x/stake/simulation/msgs.go +++ b/x/stake/simulation/msgs.go @@ -11,22 +11,20 @@ import ( "github.com/cosmos/cosmos-sdk/x/mock/simulation" "github.com/cosmos/cosmos-sdk/x/stake" abci "github.com/tendermint/tendermint/abci/types" - "github.com/tendermint/tendermint/crypto" ) // SimulateMsgCreateValidator func SimulateMsgCreateValidator(m auth.AccountMapper, k stake.Keeper) simulation.Operation { handler := stake.NewHandler(k) - return func(r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, keys []crypto.PrivKey, event func(string)) (action string, fOp []simulation.FutureOperation, err error) { + return func(r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, accs []simulation.Account, event func(string)) (action string, fOp []simulation.FutureOperation, err error) { denom := k.GetParams(ctx).BondDenom description := stake.Description{ Moniker: simulation.RandStringOfLength(r, 10), } - key := simulation.RandomKey(r, keys) - pubkey := key.PubKey() - address := sdk.ValAddress(pubkey.Address()) - amount := m.GetAccount(ctx, sdk.AccAddress(address)).GetCoins().AmountOf(denom) + acc := simulation.RandomAcc(r, accs) + address := sdk.ValAddress(acc.Address) + amount := m.GetAccount(ctx, acc.Address).GetCoins().AmountOf(denom) if amount.GT(sdk.ZeroInt()) { amount = simulation.RandomAmount(r, amount) } @@ -36,8 +34,8 @@ func SimulateMsgCreateValidator(m auth.AccountMapper, k stake.Keeper) simulation msg := stake.MsgCreateValidator{ Description: description, ValidatorAddr: address, - DelegatorAddr: sdk.AccAddress(address), - PubKey: pubkey, + DelegatorAddr: acc.Address, + PubKey: acc.PubKey, Delegation: sdk.NewCoin(denom, amount), } if msg.ValidateBasic() != nil { @@ -58,7 +56,7 @@ func SimulateMsgCreateValidator(m auth.AccountMapper, k stake.Keeper) simulation // SimulateMsgEditValidator func SimulateMsgEditValidator(k stake.Keeper) simulation.Operation { handler := stake.NewHandler(k) - return func(r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, keys []crypto.PrivKey, event func(string)) (action string, fOp []simulation.FutureOperation, err error) { + return func(r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, accs []simulation.Account, event func(string)) (action string, fOp []simulation.FutureOperation, err error) { description := stake.Description{ Moniker: simulation.RandStringOfLength(r, 10), @@ -66,9 +64,8 @@ func SimulateMsgEditValidator(k stake.Keeper) simulation.Operation { Website: simulation.RandStringOfLength(r, 10), Details: simulation.RandStringOfLength(r, 10), } - key := simulation.RandomKey(r, keys) - pubkey := key.PubKey() - address := sdk.ValAddress(pubkey.Address()) + acc := simulation.RandomAcc(r, accs) + address := sdk.ValAddress(acc.Address) msg := stake.MsgEditValidator{ Description: description, ValidatorAddr: address, @@ -90,13 +87,13 @@ func SimulateMsgEditValidator(k stake.Keeper) simulation.Operation { // SimulateMsgDelegate func SimulateMsgDelegate(m auth.AccountMapper, k stake.Keeper) simulation.Operation { handler := stake.NewHandler(k) - return func(r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, keys []crypto.PrivKey, event func(string)) (action string, fOp []simulation.FutureOperation, err error) { + return func(r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, accs []simulation.Account, event func(string)) (action string, fOp []simulation.FutureOperation, err error) { denom := k.GetParams(ctx).BondDenom - validatorKey := simulation.RandomKey(r, keys) - validatorAddress := sdk.ValAddress(validatorKey.PubKey().Address()) - delegatorKey := simulation.RandomKey(r, keys) - delegatorAddress := sdk.AccAddress(delegatorKey.PubKey().Address()) + validatorAcc := simulation.RandomAcc(r, accs) + validatorAddress := sdk.ValAddress(validatorAcc.Address) + delegatorAcc := simulation.RandomAcc(r, accs) + delegatorAddress := delegatorAcc.Address amount := m.GetAccount(ctx, delegatorAddress).GetCoins().AmountOf(denom) if amount.GT(sdk.ZeroInt()) { amount = simulation.RandomAmount(r, amount) @@ -126,13 +123,13 @@ func SimulateMsgDelegate(m auth.AccountMapper, k stake.Keeper) simulation.Operat // SimulateMsgBeginUnbonding func SimulateMsgBeginUnbonding(m auth.AccountMapper, k stake.Keeper) simulation.Operation { handler := stake.NewHandler(k) - return func(r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, keys []crypto.PrivKey, event func(string)) (action string, fOp []simulation.FutureOperation, err error) { + return func(r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, accs []simulation.Account, event func(string)) (action string, fOp []simulation.FutureOperation, err error) { denom := k.GetParams(ctx).BondDenom - validatorKey := simulation.RandomKey(r, keys) - validatorAddress := sdk.ValAddress(validatorKey.PubKey().Address()) - delegatorKey := simulation.RandomKey(r, keys) - delegatorAddress := sdk.AccAddress(delegatorKey.PubKey().Address()) + validatorAcc := simulation.RandomAcc(r, accs) + validatorAddress := sdk.ValAddress(validatorAcc.Address) + delegatorAcc := simulation.RandomAcc(r, accs) + delegatorAddress := delegatorAcc.Address amount := m.GetAccount(ctx, delegatorAddress).GetCoins().AmountOf(denom) if amount.GT(sdk.ZeroInt()) { amount = simulation.RandomAmount(r, amount) @@ -162,12 +159,12 @@ func SimulateMsgBeginUnbonding(m auth.AccountMapper, k stake.Keeper) simulation. // SimulateMsgCompleteUnbonding func SimulateMsgCompleteUnbonding(k stake.Keeper) simulation.Operation { handler := stake.NewHandler(k) - return func(r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, keys []crypto.PrivKey, event func(string)) (action string, fOp []simulation.FutureOperation, err error) { + return func(r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, accs []simulation.Account, event func(string)) (action string, fOp []simulation.FutureOperation, err error) { - validatorKey := simulation.RandomKey(r, keys) - validatorAddress := sdk.ValAddress(validatorKey.PubKey().Address()) - delegatorKey := simulation.RandomKey(r, keys) - delegatorAddress := sdk.AccAddress(delegatorKey.PubKey().Address()) + validatorAcc := simulation.RandomAcc(r, accs) + validatorAddress := sdk.ValAddress(validatorAcc.Address) + delegatorAcc := simulation.RandomAcc(r, accs) + delegatorAddress := delegatorAcc.Address msg := stake.MsgCompleteUnbonding{ DelegatorAddr: delegatorAddress, ValidatorAddr: validatorAddress, @@ -189,15 +186,15 @@ func SimulateMsgCompleteUnbonding(k stake.Keeper) simulation.Operation { // SimulateMsgBeginRedelegate func SimulateMsgBeginRedelegate(m auth.AccountMapper, k stake.Keeper) simulation.Operation { handler := stake.NewHandler(k) - return func(r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, keys []crypto.PrivKey, event func(string)) (action string, fOp []simulation.FutureOperation, err error) { + return func(r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, accs []simulation.Account, event func(string)) (action string, fOp []simulation.FutureOperation, err error) { denom := k.GetParams(ctx).BondDenom - sourceValidatorKey := simulation.RandomKey(r, keys) - sourceValidatorAddress := sdk.ValAddress(sourceValidatorKey.PubKey().Address()) - destValidatorKey := simulation.RandomKey(r, keys) - destValidatorAddress := sdk.ValAddress(destValidatorKey.PubKey().Address()) - delegatorKey := simulation.RandomKey(r, keys) - delegatorAddress := sdk.AccAddress(delegatorKey.PubKey().Address()) + sourceValidatorAcc := simulation.RandomAcc(r, accs) + sourceValidatorAddress := sdk.ValAddress(sourceValidatorAcc.Address) + destValidatorAcc := simulation.RandomAcc(r, accs) + destValidatorAddress := sdk.ValAddress(destValidatorAcc.Address) + delegatorAcc := simulation.RandomAcc(r, accs) + delegatorAddress := delegatorAcc.Address // TODO amount := m.GetAccount(ctx, delegatorAddress).GetCoins().AmountOf(denom) if amount.GT(sdk.ZeroInt()) { @@ -229,14 +226,14 @@ func SimulateMsgBeginRedelegate(m auth.AccountMapper, k stake.Keeper) simulation // SimulateMsgCompleteRedelegate func SimulateMsgCompleteRedelegate(k stake.Keeper) simulation.Operation { handler := stake.NewHandler(k) - return func(r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, keys []crypto.PrivKey, event func(string)) (action string, fOp []simulation.FutureOperation, err error) { + return func(r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, accs []simulation.Account, event func(string)) (action string, fOp []simulation.FutureOperation, err error) { - validatorSrcKey := simulation.RandomKey(r, keys) - validatorSrcAddress := sdk.ValAddress(validatorSrcKey.PubKey().Address()) - validatorDstKey := simulation.RandomKey(r, keys) - validatorDstAddress := sdk.ValAddress(validatorDstKey.PubKey().Address()) - delegatorKey := simulation.RandomKey(r, keys) - delegatorAddress := sdk.AccAddress(delegatorKey.PubKey().Address()) + validatorSrcAcc := simulation.RandomAcc(r, accs) + validatorSrcAddress := sdk.ValAddress(validatorSrcAcc.Address) + validatorDstAcc := simulation.RandomAcc(r, accs) + validatorDstAddress := sdk.ValAddress(validatorDstAcc.Address) + delegatorAcc := simulation.RandomAcc(r, accs) + delegatorAddress := delegatorAcc.Address msg := stake.MsgCompleteRedelegate{ DelegatorAddr: delegatorAddress, ValidatorSrcAddr: validatorSrcAddress, @@ -259,7 +256,7 @@ func SimulateMsgCompleteRedelegate(k stake.Keeper) simulation.Operation { // Setup // nolint: errcheck func Setup(mapp *mock.App, k stake.Keeper) simulation.RandSetup { - return func(r *rand.Rand, privKeys []crypto.PrivKey) { + return func(r *rand.Rand, accs []simulation.Account) { ctx := mapp.NewContext(false, abci.Header{}) gen := stake.DefaultGenesisState() gen.Params.InflationMax = sdk.NewDec(0) diff --git a/x/stake/simulation/sim_test.go b/x/stake/simulation/sim_test.go index 53b9d826ce..b81555f030 100644 --- a/x/stake/simulation/sim_test.go +++ b/x/stake/simulation/sim_test.go @@ -6,7 +6,6 @@ import ( "testing" abci "github.com/tendermint/tendermint/abci/types" - "github.com/tendermint/tendermint/crypto" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/x/bank" @@ -38,8 +37,8 @@ func TestStakeWithRandomMessages(t *testing.T) { panic(err) } - appStateFn := func(r *rand.Rand, keys []crypto.PrivKey, accs []sdk.AccAddress) json.RawMessage { - mock.RandomSetGenesis(r, mapp, accs, []string{"stake"}) + appStateFn := func(r *rand.Rand, accs []simulation.Account) json.RawMessage { + simulation.RandomSetGenesis(r, mapp, accs, []string{"stake"}) return json.RawMessage("{}") } From a65c6eba00817b6e26ffb5d1fa99705c57806f55 Mon Sep 17 00:00:00 2001 From: Alessio Treglia Date: Mon, 24 Sep 2018 16:06:38 +0100 Subject: [PATCH 02/26] Merge PR #2389: Remove dependency on tendermint/tmlibs --- Gopkg.lock | 9 --------- PENDING.md | 1 + client/keys/show.go | 2 +- 3 files changed, 2 insertions(+), 10 deletions(-) diff --git a/Gopkg.lock b/Gopkg.lock index 05019f84a1..d64538d36b 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -489,14 +489,6 @@ revision = "81df19e68ab1519399fccf0cab81cb75bf9d782e" version = "v0.23.1-rc0" -[[projects]] - digest = "1:bf6d9a827ea3cad964c2f863302e4f6823170d0b5ed16f72cf1184a7c615067e" - name = "github.com/tendermint/tmlibs" - packages = ["cli"] - pruneopts = "UT" - revision = "49596e0a1f48866603813df843c9409fc19805c6" - version = "v0.9.0" - [[projects]] digest = "1:7886f86064faff6f8d08a3eb0e8c773648ff5a2e27730831e2bfbf07467f6666" name = "github.com/zondax/ledger-goclient" @@ -677,7 +669,6 @@ "github.com/tendermint/tendermint/rpc/lib/server", "github.com/tendermint/tendermint/types", "github.com/tendermint/tendermint/version", - "github.com/tendermint/tmlibs/cli", "github.com/zondax/ledger-goclient", "golang.org/x/crypto/blowfish", ] diff --git a/PENDING.md b/PENDING.md index ab0a42d45d..8a9fdebd5e 100644 --- a/PENDING.md +++ b/PENDING.md @@ -146,5 +146,6 @@ BUG FIXES loading a Ledger device at runtime. * [\#2158](https://github.com/cosmos/cosmos-sdk/issues/2158) Fix non-deterministic ordering of validator iteration when slashing in `gov EndBlocker` * [simulation] \#1924 Make simulation stop on SIGTERM + * [\#2388](https://github.com/cosmos/cosmos-sdk/issues/2388) Remove dependency on deprecated tendermint/tmlibs repository. * Tendermint diff --git a/client/keys/show.go b/client/keys/show.go index c7cae1f7cc..82c6f9883d 100644 --- a/client/keys/show.go +++ b/client/keys/show.go @@ -9,7 +9,7 @@ import ( "github.com/pkg/errors" "github.com/spf13/cobra" "github.com/spf13/viper" - "github.com/tendermint/tmlibs/cli" + "github.com/tendermint/tendermint/libs/cli" ) const ( From 8774adc7debb46da19a5e0546dd37be504c18ca4 Mon Sep 17 00:00:00 2001 From: Dev Ojha Date: Mon, 24 Sep 2018 13:08:43 -0700 Subject: [PATCH 03/26] Merge PR #2375: Add an example for the size of a transaction * Add an example for the size of a transaction This shows that the size of a send tx from 1 input to 1 output is 173 bytes This is good information to have, though we need to find the correct place to put it. * fix lint --- cmd/gaia/app/benchmarks/txsize_test.go | 35 ++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 cmd/gaia/app/benchmarks/txsize_test.go diff --git a/cmd/gaia/app/benchmarks/txsize_test.go b/cmd/gaia/app/benchmarks/txsize_test.go new file mode 100644 index 0000000000..83e51c0416 --- /dev/null +++ b/cmd/gaia/app/benchmarks/txsize_test.go @@ -0,0 +1,35 @@ +package app + +import ( + "fmt" + + "github.com/cosmos/cosmos-sdk/cmd/gaia/app" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/auth" + "github.com/cosmos/cosmos-sdk/x/bank" + "github.com/tendermint/tendermint/crypto/secp256k1" +) + +// This will fail half the time with the second output being 173 +// This is due to secp256k1 signatures not being constant size. +// This will be resolved when updating to tendermint v0.24.0 +// nolint: vet +func ExampleTxSendSize() { + cdc := app.MakeCodec() + priv1 := secp256k1.GenPrivKeySecp256k1([]byte{0}) + addr1 := sdk.AccAddress(priv1.PubKey().Address()) + priv2 := secp256k1.GenPrivKeySecp256k1([]byte{1}) + addr2 := sdk.AccAddress(priv2.PubKey().Address()) + coins := []sdk.Coin{sdk.NewCoin("denom", sdk.NewInt(10))} + msg1 := bank.MsgSend{ + Inputs: []bank.Input{bank.NewInput(addr1, coins)}, + Outputs: []bank.Output{bank.NewOutput(addr2, coins)}, + } + sig, _ := priv1.Sign(msg1.GetSignBytes()) + sigs := []auth.StdSignature{auth.StdSignature{nil, sig, 0, 0}} + tx := auth.NewStdTx([]sdk.Msg{msg1}, auth.NewStdFee(0, coins...), sigs, "") + fmt.Println(len(cdc.MustMarshalBinaryBare([]sdk.Msg{msg1}))) + fmt.Println(len(cdc.MustMarshalBinaryBare(tx))) + // output: 80 + // 173 +} From 9dafa3252d7a5a473bb1b5352dc5cb2281565167 Mon Sep 17 00:00:00 2001 From: Alexander Bezobchuk Date: Mon, 24 Sep 2018 22:23:58 +0000 Subject: [PATCH 04/26] Merge PR #2365: Validator Commission Model * Update validator commission fields * Remove CommissionChangeToday and update to use CommissionChangeTime * Implement commission as a first class citizen type * Implement stringer for Comission * Move commission type and logic to new file * Add new commission errors * Add commission to create validator message * Implement and call UpdateValidatorCommission * Update godoc for UpdateValidatorCommission * Add Abs to the decimal type * Implement new SetValidatorCommission * Update decimal short godocs * Move set initial commission logic * Move initial commission validation to Commission type * Update initial validator commission logic and unit tests * Remove commission update time from struct and move to validator * Update validator create handler tests * Implement commission logic for CLI * Fix make lint failure * Fix make cover failure * Update edit validator logic to handle new commission rate * Fix lint and cover * Update create/edit validator simulation to include commission params * Update MsgEditValidator godoc * Update pending log * Update staking tx docs * Fix CLI create validator test * Update variables names for commission strings * Merge UpdateTime into Commission type * Update create-validator usage in docs * Update more docs with examples * More doc updates --- PENDING.md | 4 + cmd/gaia/cli_test/cli_test.go | 3 + docs/spec/staking/transactions.md | 119 ++++++++++------- docs/validators/validator-setup.md | 19 ++- types/decimal.go | 6 +- x/gov/tally_test.go | 18 ++- x/slashing/app_test.go | 5 +- x/slashing/test_common.go | 6 +- x/stake/app_test.go | 14 +- x/stake/client/cli/flags.go | 10 ++ x/stake/client/cli/tx.go | 86 +++++------- x/stake/client/cli/utils.go | 88 ++++++++++++ x/stake/client/rest/utils.go | 3 +- x/stake/handler.go | 26 +++- x/stake/handler_test.go | 5 +- x/stake/keeper/validator.go | 17 +++ x/stake/keeper/validator_test.go | 54 ++++++++ x/stake/simulation/msgs.go | 28 +++- x/stake/stake.go | 18 ++- x/stake/types/commission.go | 128 ++++++++++++++++++ x/stake/types/errors.go | 20 +++ x/stake/types/msg.go | 34 +++-- x/stake/types/msg_test.go | 57 +++++--- x/stake/types/validator.go | 208 +++++++++++++---------------- x/stake/types/validator_test.go | 34 +++++ 25 files changed, 728 insertions(+), 282 deletions(-) create mode 100644 x/stake/client/cli/utils.go create mode 100644 x/stake/types/commission.go diff --git a/PENDING.md b/PENDING.md index 8a9fdebd5e..ec3c7f930f 100644 --- a/PENDING.md +++ b/PENDING.md @@ -76,6 +76,8 @@ FEATURES * [\#966](https://github.com/cosmos/cosmos-sdk/issues/966) Add --generate-only flag to build an unsigned transaction and write it to STDOUT. * [\#1953](https://github.com/cosmos/cosmos-sdk/issues/1953) New `sign` command to sign transactions generated with the --generate-only flag. * [\#1954](https://github.com/cosmos/cosmos-sdk/issues/1954) New `broadcast` command to broadcast transactions generated offline and signed with the `sign` command. + * [stake][cli] [\#1672](https://github.com/cosmos/cosmos-sdk/issues/1672) Introduced + new commission flags for validator commands `create-validator` and `edit-validator`. * Gaia * [cli] #2170 added ability to show the node's address via `gaiad tendermint show-address` @@ -88,6 +90,8 @@ FEATURES * [simulation] [\#1924](https://github.com/cosmos/cosmos-sdk/issues/1924) allow operations to specify future operations * [simulation] [\#1924](https://github.com/cosmos/cosmos-sdk/issues/1924) Add benchmarking capabilities, with makefile commands "test_sim_gaia_benchmark, test_sim_gaia_profile" * [simulation] [\#2349](https://github.com/cosmos/cosmos-sdk/issues/2349) Add time-based future scheduled operations to simulator + * [x/stake] [\#1672](https://github.com/cosmos/cosmos-sdk/issues/1672) Implement + basis for the validator commission model. * Tendermint diff --git a/cmd/gaia/cli_test/cli_test.go b/cmd/gaia/cli_test/cli_test.go index 8ac8f55598..3c847dc90e 100644 --- a/cmd/gaia/cli_test/cli_test.go +++ b/cmd/gaia/cli_test/cli_test.go @@ -238,6 +238,9 @@ func TestGaiaCLICreateValidator(t *testing.T) { cvStr += fmt.Sprintf(" --pubkey=%s", barCeshPubKey) cvStr += fmt.Sprintf(" --amount=%v", "2steak") cvStr += fmt.Sprintf(" --moniker=%v", "bar-vally") + cvStr += fmt.Sprintf(" --commission-rate=%v", "0.05") + cvStr += fmt.Sprintf(" --commission-max-rate=%v", "0.20") + cvStr += fmt.Sprintf(" --commission-max-change-rate=%v", "0.10") initialPool.BondedTokens = initialPool.BondedTokens.Add(sdk.NewDec(1)) diff --git a/docs/spec/staking/transactions.md b/docs/spec/staking/transactions.md index 148c1c415f..6ed90343b6 100644 --- a/docs/spec/staking/transactions.md +++ b/docs/spec/staking/transactions.md @@ -1,86 +1,107 @@ -## Transaction Overview +# Transaction Overview In this section we describe the processing of the transactions and the corresponding updates to the state. Transactions: - - TxCreateValidator - - TxEditValidator - - TxDelegation - - TxStartUnbonding - - TxCompleteUnbonding - - TxRedelegate - - TxCompleteRedelegation + +* TxCreateValidator +* TxEditValidator +* TxDelegation +* TxStartUnbonding +* TxCompleteUnbonding +* TxRedelegate +* TxCompleteRedelegation Other important state changes: - - Update Validators + +* Update Validators Other notes: - - `tx` denotes a reference to the transaction being processed - - `sender` denotes the address of the sender of the transaction - - `getXxx`, `setXxx`, and `removeXxx` functions are used to retrieve and - modify objects from the store - - `sdk.Dec` refers to a decimal type specified by the SDK. -### TxCreateValidator +* `tx` denotes a reference to the transaction being processed +* `sender` denotes the address of the sender of the transaction +* `getXxx`, `setXxx`, and `removeXxx` functions are used to retrieve and + modify objects from the store +* `sdk.Dec` refers to a decimal type specified by the SDK. - - triggers: `distribution.CreateValidatorDistribution` +## TxCreateValidator + +* triggers: `distribution.CreateValidatorDistribution` A validator is created using the `TxCreateValidator` transaction. ```golang type TxCreateValidator struct { - Operator sdk.Address - ConsensusPubKey crypto.PubKey - GovernancePubKey crypto.PubKey - SelfDelegation coin.Coin + Description Description + Commission Commission - Description Description - Commission sdk.Dec - CommissionMax sdk.Dec - CommissionMaxChange sdk.Dec + DelegatorAddr sdk.AccAddress + ValidatorAddr sdk.ValAddress + PubKey crypto.PubKey + Delegation sdk.Coin } - createValidator(tx TxCreateValidator): - validator = getValidator(tx.Operator) - if validator != nil return // only one validator per address + ok := validatorExists(tx.ValidatorAddr) + if ok return err // only one validator per address - validator = NewValidator(operatorAddr, ConsensusPubKey, GovernancePubKey, Description) - init validator poolShares, delegatorShares set to 0 - init validator commision fields from tx - validator.PoolShares = 0 + ok := validatorByPubKeyExists(tx.PubKey) + if ok return err // only one validator per public key + err := validateDenom(tx.Delegation.Denom) + if err != nil return err // denomination must be valid + + validator := NewValidator(tx.ValidatorAddr, tx.PubKey, tx.Description) + + err := setInitialCommission(validator, tx.Commission, blockTime) + if err != nil return err // must be able to set initial commission correctly + + // set the validator and public key setValidator(validator) + setValidatorByPubKeyIndex(validator) - txDelegate = TxDelegate(tx.Operator, tx.Operator, tx.SelfDelegation) - delegate(txDelegate, validator) // see delegate function in [TxDelegate](TxDelegate) - return + // delegate coins from tx.DelegatorAddr to the validator + err := delegate(tx.DelegatorAddr, tx.Delegation, validator) + if err != nil return err // must be able to set delegation correctly + + tags := createTags(tx) + return tags ``` -### TxEditValidator +## TxEditValidator -If either the `Description` (excluding `DateBonded` which is constant), -`Commission`, or the `GovernancePubKey` need to be updated, the -`TxEditCandidacy` transaction should be sent from the operator account: +If either the `Description`, `Commission`, or the `ValidatorAddr` need to be +updated, the `TxEditCandidacy` transaction should be sent from the operator +account: ```golang type TxEditCandidacy struct { - GovernancePubKey crypto.PubKey - Commission sdk.Dec - Description Description + Description Description + ValidatorAddr sdk.ValAddress + CommissionRate sdk.Dec } editCandidacy(tx TxEditCandidacy): - validator = getValidator(tx.ValidatorAddr) + validator, ok := getValidator(tx.ValidatorAddr) + if !ok return err // validator must exist - if tx.Commission > CommissionMax || tx.Commission < 0 then fail - if rateChange(tx.Commission) > CommissionMaxChange then fail - validator.Commission = tx.Commission + // Attempt to update the validator's description. The description provided + // must be valid. + description, err := updateDescription(validator, tx.Description) + if err != nil return err - if tx.GovernancePubKey != nil validator.GovernancePubKey = tx.GovernancePubKey - if tx.Description != nil validator.Description = tx.Description + // a validator is not required to update it's commission rate + if tx.CommissionRate != nil { + // Attempt to update a validator's commission rate. The rate provided + // must be valid. It's rate can only be updated once a day. + err := updateValidatorCommission(validator, tx.CommissionRate) + if err != nil return err + } - setValidator(store, validator) - return + // set the validator and public key + setValidator(validator) + + tags := createTags(tx) + return tags ``` ### TxDelegate diff --git a/docs/validators/validator-setup.md b/docs/validators/validator-setup.md index 8a842ea205..dbcc84b3d5 100644 --- a/docs/validators/validator-setup.md +++ b/docs/validators/validator-setup.md @@ -35,9 +35,16 @@ gaiacli stake create-validator \ --address-validator= --moniker="choose a moniker" \ --chain-id= \ - --name= + --name= \ + --commission-rate="0.10" \ + --commission-max-rate="0.20" \ + --commission-max-change-rate="0.01" ``` +__Note__: When specifying commission parameters, the `commission-max-change-rate` +is used to measure % _point_ change over the `commission-rate`. E.g. 1% to 2% is +a 100% rate increase, but only 1 percentage point. + ### Edit Validator Description You can edit your validator's public description. This info is to identify your validator, and will be relied on by delegators to decide which validators to stake to. Make sure to provide input for every flag below, otherwise the field will default to empty (`--moniker` defaults to the machine name). @@ -52,9 +59,17 @@ gaiacli stake edit-validator --identity=6A0D65E29A4CBC8E --details="To infinity and beyond!" --chain-id= \ - --name= + --name= \ + --commission-rate="0.10" ``` +__Note__: The `commission-rate` value must adhere to the following invariants: + +- Must be between 0 and the validator's `commission-max-rate` +- Must not exceed the validator's `commission-max-change-rate` which is maximum + % point change rate **per day**. In other words, a validator can only change + its commission once per day and within `commission-max-change-rate` bounds. + ### View Validator Description View the validator's information with this command: diff --git a/types/decimal.go b/types/decimal.go index 8e7db1340b..564d6322c0 100644 --- a/types/decimal.go +++ b/types/decimal.go @@ -174,13 +174,15 @@ func NewDecFromStr(str string) (d Dec, err Error) { //______________________________________________________________________________________________ //nolint -func (d Dec) IsZero() bool { return (d.Int).Sign() == 0 } // Is equal to zero -func (d Dec) Equal(d2 Dec) bool { return (d.Int).Cmp(d2.Int) == 0 } +func (d Dec) IsNil() bool { return d.Int == nil } // is decimal nil +func (d Dec) IsZero() bool { return (d.Int).Sign() == 0 } // is equal to zero +func (d Dec) Equal(d2 Dec) bool { return (d.Int).Cmp(d2.Int) == 0 } // equal decimals func (d Dec) GT(d2 Dec) bool { return (d.Int).Cmp(d2.Int) > 0 } // greater than func (d Dec) GTE(d2 Dec) bool { return (d.Int).Cmp(d2.Int) >= 0 } // greater than or equal func (d Dec) LT(d2 Dec) bool { return (d.Int).Cmp(d2.Int) < 0 } // less than func (d Dec) LTE(d2 Dec) bool { return (d.Int).Cmp(d2.Int) <= 0 } // less than or equal func (d Dec) Neg() Dec { return Dec{new(big.Int).Neg(d.Int)} } // reverse the decimal sign +func (d Dec) Abs() Dec { return Dec{new(big.Int).Abs(d.Int)} } // absolute value // addition func (d Dec) Add(d2 Dec) Dec { diff --git a/x/gov/tally_test.go b/x/gov/tally_test.go index 6c6d64aa67..71983bf593 100644 --- a/x/gov/tally_test.go +++ b/x/gov/tally_test.go @@ -15,13 +15,19 @@ import ( var ( pubkeys = []crypto.PubKey{ed25519.GenPrivKey().PubKey(), ed25519.GenPrivKey().PubKey(), ed25519.GenPrivKey().PubKey()} + + testDescription = stake.NewDescription("T", "E", "S", "T") + testCommissionMsg = stake.NewCommissionMsg(sdk.ZeroDec(), sdk.ZeroDec(), sdk.ZeroDec()) ) func createValidators(t *testing.T, stakeHandler sdk.Handler, ctx sdk.Context, addrs []sdk.ValAddress, coinAmt []int64) { require.True(t, len(addrs) <= len(pubkeys), "Not enough pubkeys specified at top of file.") - dummyDescription := stake.NewDescription("T", "E", "S", "T") + for i := 0; i < len(addrs); i++ { - valCreateMsg := stake.NewMsgCreateValidator(addrs[i], pubkeys[i], sdk.NewInt64Coin("steak", coinAmt[i]), dummyDescription) + valCreateMsg := stake.NewMsgCreateValidator( + addrs[i], pubkeys[i], sdk.NewInt64Coin("steak", coinAmt[i]), testDescription, testCommissionMsg, + ) + res := stakeHandler(ctx, valCreateMsg) require.True(t, res.IsOK()) } @@ -378,20 +384,18 @@ func TestTallyDelgatorMultipleInherit(t *testing.T) { ctx := mapp.BaseApp.NewContext(false, abci.Header{}) stakeHandler := stake.NewHandler(sk) - dummyDescription := stake.NewDescription("T", "E", "S", "T") - val1CreateMsg := stake.NewMsgCreateValidator( - sdk.ValAddress(addrs[0]), ed25519.GenPrivKey().PubKey(), sdk.NewInt64Coin("steak", 25), dummyDescription, + sdk.ValAddress(addrs[0]), ed25519.GenPrivKey().PubKey(), sdk.NewInt64Coin("steak", 25), testDescription, testCommissionMsg, ) stakeHandler(ctx, val1CreateMsg) val2CreateMsg := stake.NewMsgCreateValidator( - sdk.ValAddress(addrs[1]), ed25519.GenPrivKey().PubKey(), sdk.NewInt64Coin("steak", 6), dummyDescription, + sdk.ValAddress(addrs[1]), ed25519.GenPrivKey().PubKey(), sdk.NewInt64Coin("steak", 6), testDescription, testCommissionMsg, ) stakeHandler(ctx, val2CreateMsg) val3CreateMsg := stake.NewMsgCreateValidator( - sdk.ValAddress(addrs[2]), ed25519.GenPrivKey().PubKey(), sdk.NewInt64Coin("steak", 7), dummyDescription, + sdk.ValAddress(addrs[2]), ed25519.GenPrivKey().PubKey(), sdk.NewInt64Coin("steak", 7), testDescription, testCommissionMsg, ) stakeHandler(ctx, val3CreateMsg) diff --git a/x/slashing/app_test.go b/x/slashing/app_test.go index 497440a085..a3370b1d87 100644 --- a/x/slashing/app_test.go +++ b/x/slashing/app_test.go @@ -99,9 +99,12 @@ func TestSlashingMsgs(t *testing.T) { } accs := []auth.Account{acc1} mock.SetGenesis(mapp, accs) + description := stake.NewDescription("foo_moniker", "", "", "") + commission := stake.NewCommissionMsg(sdk.ZeroDec(), sdk.ZeroDec(), sdk.ZeroDec()) + createValidatorMsg := stake.NewMsgCreateValidator( - sdk.ValAddress(addr1), priv1.PubKey(), bondCoin, description, + sdk.ValAddress(addr1), priv1.PubKey(), bondCoin, description, commission, ) mock.SignCheckDeliver(t, mapp.BaseApp, []sdk.Msg{createValidatorMsg}, []int64{0}, []int64{0}, true, true, priv1) mock.CheckBalance(t, mapp, addr1, sdk.Coins{genCoin.Minus(bondCoin)}) diff --git a/x/slashing/test_common.go b/x/slashing/test_common.go index db2293a2f9..7c97a85372 100644 --- a/x/slashing/test_common.go +++ b/x/slashing/test_common.go @@ -103,12 +103,14 @@ func testAddr(addr string) sdk.AccAddress { } func newTestMsgCreateValidator(address sdk.ValAddress, pubKey crypto.PubKey, amt sdk.Int) stake.MsgCreateValidator { + commission := stake.NewCommissionMsg(sdk.ZeroDec(), sdk.ZeroDec(), sdk.ZeroDec()) return stake.MsgCreateValidator{ Description: stake.Description{}, + Commission: commission, DelegatorAddr: sdk.AccAddress(address), ValidatorAddr: address, PubKey: pubKey, - Delegation: sdk.Coin{"steak", amt}, + Delegation: sdk.NewCoin("steak", amt), } } @@ -116,6 +118,6 @@ func newTestMsgDelegate(delAddr sdk.AccAddress, valAddr sdk.ValAddress, delAmoun return stake.MsgDelegate{ DelegatorAddr: delAddr, ValidatorAddr: valAddr, - Delegation: sdk.Coin{"steak", delAmount}, + Delegation: sdk.NewCoin("steak", delAmount), } } diff --git a/x/stake/app_test.go b/x/stake/app_test.go index 922ce8ac3a..f96408c11f 100644 --- a/x/stake/app_test.go +++ b/x/stake/app_test.go @@ -21,10 +21,12 @@ var ( priv4 = ed25519.GenPrivKey() addr4 = sdk.AccAddress(priv4.PubKey().Address()) coins = sdk.Coins{sdk.NewCoin("foocoin", sdk.NewInt(10))} - fee = auth.StdFee{ - sdk.Coins{sdk.NewCoin("foocoin", sdk.NewInt(0))}, + fee = auth.NewStdFee( 100000, - } + sdk.Coins{sdk.NewCoin("foocoin", sdk.NewInt(0))}..., + ) + + commissionMsg = NewCommissionMsg(sdk.ZeroDec(), sdk.ZeroDec(), sdk.ZeroDec()) ) // getMockApp returns an initialized mock application for this module. @@ -129,7 +131,7 @@ func TestStakeMsgs(t *testing.T) { // create validator description := NewDescription("foo_moniker", "", "", "") createValidatorMsg := NewMsgCreateValidator( - sdk.ValAddress(addr1), priv1.PubKey(), bondCoin, description, + sdk.ValAddress(addr1), priv1.PubKey(), bondCoin, description, commissionMsg, ) mock.SignCheckDeliver(t, mApp.BaseApp, []sdk.Msg{createValidatorMsg}, []int64{0}, []int64{0}, true, true, priv1) @@ -143,7 +145,7 @@ func TestStakeMsgs(t *testing.T) { // addr1 create validator on behalf of addr2 createValidatorMsgOnBehalfOf := NewMsgCreateValidatorOnBehalfOf( - addr1, sdk.ValAddress(addr2), priv2.PubKey(), bondCoin, description, + addr1, sdk.ValAddress(addr2), priv2.PubKey(), bondCoin, description, commissionMsg, ) mock.SignCheckDeliver(t, mApp.BaseApp, []sdk.Msg{createValidatorMsgOnBehalfOf}, []int64{0, 1}, []int64{1, 0}, true, true, priv1, priv2) @@ -160,7 +162,7 @@ func TestStakeMsgs(t *testing.T) { // edit the validator description = NewDescription("bar_moniker", "", "", "") - editValidatorMsg := NewMsgEditValidator(sdk.ValAddress(addr1), description) + editValidatorMsg := NewMsgEditValidator(sdk.ValAddress(addr1), description, nil) mock.SignCheckDeliver(t, mApp.BaseApp, []sdk.Msg{editValidatorMsg}, []int64{0}, []int64{2}, true, true, priv1) validator = checkValidator(t, mApp, keeper, sdk.ValAddress(addr1), true) diff --git a/x/stake/client/cli/flags.go b/x/stake/client/cli/flags.go index d0e83ab3c6..1228bec2a8 100644 --- a/x/stake/client/cli/flags.go +++ b/x/stake/client/cli/flags.go @@ -21,6 +21,10 @@ const ( FlagIdentity = "identity" FlagWebsite = "website" FlagDetails = "details" + + FlagCommissionRate = "commission-rate" + FlagCommissionMaxRate = "commission-max-rate" + FlagCommissionMaxChangeRate = "commission-max-change-rate" ) // common flagsets to add to various functions @@ -29,6 +33,8 @@ var ( fsAmount = flag.NewFlagSet("", flag.ContinueOnError) fsShares = flag.NewFlagSet("", flag.ContinueOnError) fsDescriptionCreate = flag.NewFlagSet("", flag.ContinueOnError) + fsCommissionCreate = flag.NewFlagSet("", flag.ContinueOnError) + fsCommissionUpdate = flag.NewFlagSet("", flag.ContinueOnError) fsDescriptionEdit = flag.NewFlagSet("", flag.ContinueOnError) fsValidator = flag.NewFlagSet("", flag.ContinueOnError) fsDelegator = flag.NewFlagSet("", flag.ContinueOnError) @@ -44,6 +50,10 @@ func init() { fsDescriptionCreate.String(FlagIdentity, "", "optional identity signature (ex. UPort or Keybase)") fsDescriptionCreate.String(FlagWebsite, "", "optional website") fsDescriptionCreate.String(FlagDetails, "", "optional details") + fsCommissionUpdate.String(FlagCommissionRate, "", "The new commission rate percentage") + fsCommissionCreate.String(FlagCommissionRate, "", "The initial commission rate percentage") + fsCommissionCreate.String(FlagCommissionMaxRate, "", "The maximum commission rate percentage") + fsCommissionCreate.String(FlagCommissionMaxChangeRate, "", "The maximum commission change rate percentage (per day)") fsDescriptionEdit.String(FlagMoniker, types.DoNotModifyDesc, "validator name") fsDescriptionEdit.String(FlagIdentity, types.DoNotModifyDesc, "optional identity signature (ex. UPort or Keybase)") fsDescriptionEdit.String(FlagWebsite, types.DoNotModifyDesc, "optional website") diff --git a/x/stake/client/cli/tx.go b/x/stake/client/cli/tx.go index f917173283..74f4c6d6e7 100644 --- a/x/stake/client/cli/tx.go +++ b/x/stake/client/cli/tx.go @@ -12,9 +12,7 @@ import ( authcmd "github.com/cosmos/cosmos-sdk/x/auth/client/cli" authtxb "github.com/cosmos/cosmos-sdk/x/auth/client/txbuilder" "github.com/cosmos/cosmos-sdk/x/stake" - "github.com/cosmos/cosmos-sdk/x/stake/types" - "github.com/pkg/errors" "github.com/spf13/cobra" "github.com/spf13/viper" ) @@ -66,6 +64,15 @@ func GetCmdCreateValidator(cdc *codec.Codec) *cobra.Command { Details: viper.GetString(FlagDetails), } + // get the initial validator commission parameters + rateStr := viper.GetString(FlagCommissionRate) + maxRateStr := viper.GetString(FlagCommissionMaxRate) + maxChangeRateStr := viper.GetString(FlagCommissionMaxChangeRate) + commissionMsg, err := buildCommissionMsg(rateStr, maxRateStr, maxChangeRateStr) + if err != nil { + return err + } + var msg sdk.Msg if viper.GetString(FlagAddressDelegator) != "" { delAddr, err := sdk.AccAddressFromBech32(viper.GetString(FlagAddressDelegator)) @@ -73,13 +80,19 @@ func GetCmdCreateValidator(cdc *codec.Codec) *cobra.Command { return err } - msg = stake.NewMsgCreateValidatorOnBehalfOf(delAddr, sdk.ValAddress(valAddr), pk, amount, description) + msg = stake.NewMsgCreateValidatorOnBehalfOf( + delAddr, sdk.ValAddress(valAddr), pk, amount, description, commissionMsg, + ) } else { - msg = stake.NewMsgCreateValidator(sdk.ValAddress(valAddr), pk, amount, description) + msg = stake.NewMsgCreateValidator( + sdk.ValAddress(valAddr), pk, amount, description, commissionMsg, + ) } + if cliCtx.GenerateOnly { return utils.PrintUnsignedStdTx(txBldr, cliCtx, []sdk.Msg{msg}) } + // build and sign the transaction, then broadcast to Tendermint return utils.SendTx(txBldr, cliCtx, []sdk.Msg{msg}) }, @@ -88,6 +101,7 @@ func GetCmdCreateValidator(cdc *codec.Codec) *cobra.Command { cmd.Flags().AddFlagSet(fsPk) cmd.Flags().AddFlagSet(fsAmount) cmd.Flags().AddFlagSet(fsDescriptionCreate) + cmd.Flags().AddFlagSet(fsCommissionCreate) cmd.Flags().AddFlagSet(fsDelegator) return cmd @@ -117,17 +131,31 @@ func GetCmdEditValidator(cdc *codec.Codec) *cobra.Command { Details: viper.GetString(FlagDetails), } - msg := stake.NewMsgEditValidator(sdk.ValAddress(valAddr), description) + var newRate *sdk.Dec + + commissionRate := viper.GetString(FlagCommissionRate) + if commissionRate != "" { + rate, err := sdk.NewDecFromStr(commissionRate) + if err != nil { + return fmt.Errorf("invalid new commission rate: %v", err) + } + + newRate = &rate + } + + msg := stake.NewMsgEditValidator(sdk.ValAddress(valAddr), description, newRate) if cliCtx.GenerateOnly { return utils.PrintUnsignedStdTx(txBldr, cliCtx, []sdk.Msg{msg}) } + // build and sign the transaction, then broadcast to Tendermint return utils.SendTx(txBldr, cliCtx, []sdk.Msg{msg}) }, } cmd.Flags().AddFlagSet(fsDescriptionEdit) + cmd.Flags().AddFlagSet(fsCommissionUpdate) return cmd } @@ -247,54 +275,6 @@ func GetCmdBeginRedelegate(storeName string, cdc *codec.Codec) *cobra.Command { return cmd } -// nolint: gocyclo -// TODO: Make this pass gocyclo linting -func getShares( - storeName string, cdc *codec.Codec, sharesAmountStr, - sharesPercentStr string, delAddr sdk.AccAddress, valAddr sdk.ValAddress, -) (sharesAmount sdk.Dec, err error) { - switch { - case sharesAmountStr != "" && sharesPercentStr != "": - return sharesAmount, errors.Errorf("can either specify the amount OR the percent of the shares, not both") - case sharesAmountStr == "" && sharesPercentStr == "": - return sharesAmount, errors.Errorf("can either specify the amount OR the percent of the shares, not both") - case sharesAmountStr != "": - sharesAmount, err = sdk.NewDecFromStr(sharesAmountStr) - if err != nil { - return sharesAmount, err - } - if !sharesAmount.GT(sdk.ZeroDec()) { - return sharesAmount, errors.Errorf("shares amount must be positive number (ex. 123, 1.23456789)") - } - case sharesPercentStr != "": - var sharesPercent sdk.Dec - sharesPercent, err = sdk.NewDecFromStr(sharesPercentStr) - if err != nil { - return sharesAmount, err - } - if !sharesPercent.GT(sdk.ZeroDec()) || !sharesPercent.LTE(sdk.OneDec()) { - return sharesAmount, errors.Errorf("shares percent must be >0 and <=1 (ex. 0.01, 0.75, 1)") - } - - // make a query to get the existing delegation shares - key := stake.GetDelegationKey(delAddr, valAddr) - cliCtx := context.NewCLIContext(). - WithCodec(cdc). - WithAccountDecoder(authcmd.GetAccountDecoder(cdc)) - - resQuery, err := cliCtx.QueryStore(key, storeName) - if err != nil { - return sharesAmount, errors.Errorf("cannot find delegation to determine percent Error: %v", err) - } - delegation, err := types.UnmarshalDelegation(cdc, key, resQuery) - if err != nil { - return sdk.ZeroDec(), err - } - sharesAmount = sharesPercent.Mul(delegation.Shares) - } - return -} - // GetCmdCompleteRedelegate implements the complete redelegation command. func GetCmdCompleteRedelegate(cdc *codec.Codec) *cobra.Command { cmd := &cobra.Command{ diff --git a/x/stake/client/cli/utils.go b/x/stake/client/cli/utils.go new file mode 100644 index 0000000000..9aca2d8995 --- /dev/null +++ b/x/stake/client/cli/utils.go @@ -0,0 +1,88 @@ +package cli + +import ( + "github.com/cosmos/cosmos-sdk/client/context" + "github.com/cosmos/cosmos-sdk/codec" + sdk "github.com/cosmos/cosmos-sdk/types" + authcmd "github.com/cosmos/cosmos-sdk/x/auth/client/cli" + "github.com/cosmos/cosmos-sdk/x/stake" + "github.com/cosmos/cosmos-sdk/x/stake/types" + "github.com/pkg/errors" +) + +func getShares( + storeName string, cdc *codec.Codec, sharesAmountStr, + sharesPercentStr string, delAddr sdk.AccAddress, valAddr sdk.ValAddress, +) (sharesAmount sdk.Dec, err error) { + + switch { + case sharesAmountStr != "" && sharesPercentStr != "": + return sharesAmount, errors.Errorf("can either specify the amount OR the percent of the shares, not both") + + case sharesAmountStr == "" && sharesPercentStr == "": + return sharesAmount, errors.Errorf("can either specify the amount OR the percent of the shares, not both") + + case sharesAmountStr != "": + sharesAmount, err = sdk.NewDecFromStr(sharesAmountStr) + if err != nil { + return sharesAmount, err + } + if !sharesAmount.GT(sdk.ZeroDec()) { + return sharesAmount, errors.Errorf("shares amount must be positive number (ex. 123, 1.23456789)") + } + + case sharesPercentStr != "": + var sharesPercent sdk.Dec + sharesPercent, err = sdk.NewDecFromStr(sharesPercentStr) + if err != nil { + return sharesAmount, err + } + if !sharesPercent.GT(sdk.ZeroDec()) || !sharesPercent.LTE(sdk.OneDec()) { + return sharesAmount, errors.Errorf("shares percent must be >0 and <=1 (ex. 0.01, 0.75, 1)") + } + + // make a query to get the existing delegation shares + key := stake.GetDelegationKey(delAddr, valAddr) + cliCtx := context.NewCLIContext(). + WithCodec(cdc). + WithAccountDecoder(authcmd.GetAccountDecoder(cdc)) + + resQuery, err := cliCtx.QueryStore(key, storeName) + if err != nil { + return sharesAmount, errors.Errorf("cannot find delegation to determine percent Error: %v", err) + } + + delegation, err := types.UnmarshalDelegation(cdc, key, resQuery) + if err != nil { + return sdk.ZeroDec(), err + } + + sharesAmount = sharesPercent.Mul(delegation.Shares) + } + + return +} + +func buildCommissionMsg(rateStr, maxRateStr, maxChangeRateStr string) (commission types.CommissionMsg, err error) { + if rateStr == "" || maxRateStr == "" || maxChangeRateStr == "" { + return commission, errors.Errorf("must specify all validator commission parameters") + } + + rate, err := sdk.NewDecFromStr(rateStr) + if err != nil { + return commission, err + } + + maxRate, err := sdk.NewDecFromStr(maxRateStr) + if err != nil { + return commission, err + } + + maxChangeRate, err := sdk.NewDecFromStr(maxChangeRateStr) + if err != nil { + return commission, err + } + + commission = types.NewCommissionMsg(rate, maxRate, maxChangeRate) + return commission, nil +} diff --git a/x/stake/client/rest/utils.go b/x/stake/client/rest/utils.go index 8ab5655009..e7b8891eab 100644 --- a/x/stake/client/rest/utils.go +++ b/x/stake/client/rest/utils.go @@ -3,11 +3,12 @@ package rest import ( "fmt" + "github.com/cosmos/cosmos-sdk/client/context" "github.com/cosmos/cosmos-sdk/client/tx" "github.com/cosmos/cosmos-sdk/codec" "github.com/cosmos/cosmos-sdk/x/stake/tags" + rpcclient "github.com/tendermint/tendermint/rpc/client" - "github.com/cosmos/cosmos-sdk/client/context" ) // contains checks if the a given query contains one of the tx types diff --git a/x/stake/handler.go b/x/stake/handler.go index e7641393db..aaf372af2a 100644 --- a/x/stake/handler.go +++ b/x/stake/handler.go @@ -62,27 +62,38 @@ func EndBlocker(ctx sdk.Context, k keeper.Keeper) (ValidatorUpdates []abci.Valid // now we just perform action and save func handleMsgCreateValidator(ctx sdk.Context, msg types.MsgCreateValidator, k keeper.Keeper) sdk.Result { - // check to see if the pubkey or sender has been registered before _, found := k.GetValidator(ctx, msg.ValidatorAddr) if found { return ErrValidatorOwnerExists(k.Codespace()).Result() } + _, found = k.GetValidatorByPubKey(ctx, msg.PubKey) if found { return ErrValidatorPubKeyExists(k.Codespace()).Result() } + if msg.Delegation.Denom != k.GetParams(ctx).BondDenom { return ErrBadDenom(k.Codespace()).Result() } validator := NewValidator(msg.ValidatorAddr, msg.PubKey, msg.Description) + commission := NewCommissionWithTime( + msg.Commission.Rate, msg.Commission.MaxChangeRate, + msg.Commission.MaxChangeRate, ctx.BlockHeader().Time, + ) + + validator, err := validator.SetInitialCommission(commission) + if err != nil { + return err.Result() + } + k.SetValidator(ctx, validator) k.SetValidatorByPubKeyIndex(ctx, validator) // move coins from the msg.Address account to a (self-delegation) delegator account // the validator account and global shares are updated within here - _, err := k.Delegate(ctx, msg.DelegatorAddr, msg.Delegation, validator, true) + _, err = k.Delegate(ctx, msg.DelegatorAddr, msg.Delegation, validator, true) if err != nil { return err.Result() } @@ -93,13 +104,13 @@ func handleMsgCreateValidator(ctx sdk.Context, msg types.MsgCreateValidator, k k tags.Moniker, []byte(msg.Description.Moniker), tags.Identity, []byte(msg.Description.Identity), ) + return sdk.Result{ Tags: tags, } } func handleMsgEditValidator(ctx sdk.Context, msg types.MsgEditValidator, k keeper.Keeper) sdk.Result { - // validator must already be registered validator, found := k.GetValidator(ctx, msg.ValidatorAddr) if !found { @@ -111,17 +122,26 @@ func handleMsgEditValidator(ctx sdk.Context, msg types.MsgEditValidator, k keepe if err != nil { return err.Result() } + validator.Description = description + if msg.CommissionRate != nil { + if err := k.UpdateValidatorCommission(ctx, validator, *msg.CommissionRate); err != nil { + return err.Result() + } + } + // We don't need to run through all the power update logic within k.UpdateValidator // We just need to override the entry in state, since only the description has changed. k.SetValidator(ctx, validator) + tags := sdk.NewTags( tags.Action, tags.ActionEditValidator, tags.DstValidator, []byte(msg.ValidatorAddr.String()), tags.Moniker, []byte(description.Moniker), tags.Identity, []byte(description.Identity), ) + return sdk.Result{ Tags: tags, } diff --git a/x/stake/handler_test.go b/x/stake/handler_test.go index 70a21c171c..845844fe28 100644 --- a/x/stake/handler_test.go +++ b/x/stake/handler_test.go @@ -17,7 +17,9 @@ import ( //______________________________________________________________________ func newTestMsgCreateValidator(address sdk.ValAddress, pubKey crypto.PubKey, amt int64) MsgCreateValidator { - return types.NewMsgCreateValidator(address, pubKey, sdk.NewCoin("steak", sdk.NewInt(amt)), Description{}) + return types.NewMsgCreateValidator( + address, pubKey, sdk.NewCoin("steak", sdk.NewInt(amt)), Description{}, commissionMsg, + ) } func newTestMsgDelegate(delAddr sdk.AccAddress, valAddr sdk.ValAddress, amt int64) MsgDelegate { @@ -31,6 +33,7 @@ func newTestMsgDelegate(delAddr sdk.AccAddress, valAddr sdk.ValAddress, amt int6 func newTestMsgCreateValidatorOnBehalfOf(delAddr sdk.AccAddress, valAddr sdk.ValAddress, valPubKey crypto.PubKey, amt int64) MsgCreateValidator { return MsgCreateValidator{ Description: Description{}, + Commission: commissionMsg, DelegatorAddr: delAddr, ValidatorAddr: valAddr, PubKey: valPubKey, diff --git a/x/stake/keeper/validator.go b/x/stake/keeper/validator.go index 996bf06bf5..de4c4a187c 100644 --- a/x/stake/keeper/validator.go +++ b/x/stake/keeper/validator.go @@ -693,6 +693,23 @@ func (k Keeper) RemoveValidator(ctx sdk.Context, address sdk.ValAddress) { tstore.Set(GetTendermintUpdatesTKey(address), bz) } +// UpdateValidatorCommission attempts to update a validator's commission rate. +// An error is returned if the new commission rate is invalid. +func (k Keeper) UpdateValidatorCommission(ctx sdk.Context, validator types.Validator, newRate sdk.Dec) sdk.Error { + commission := validator.Commission + blockTime := ctx.BlockHeader().Time + + if err := commission.ValidateNewRate(newRate, blockTime); err != nil { + return err + } + + validator.Commission.Rate = newRate + validator.Commission.UpdateTime = blockTime + + k.SetValidator(ctx, validator) + return nil +} + //__________________________________________________________________________ // get the current validator on the cliff diff --git a/x/stake/keeper/validator_test.go b/x/stake/keeper/validator_test.go index e23d3b0aad..1a5cc3fee2 100644 --- a/x/stake/keeper/validator_test.go +++ b/x/stake/keeper/validator_test.go @@ -3,9 +3,11 @@ package keeper import ( "fmt" "testing" + "time" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/x/stake/types" + abci "github.com/tendermint/tendermint/abci/types" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -1043,3 +1045,55 @@ func TestGetValidTendermintUpdatesBondTransition(t *testing.T) { clearTendermintUpdates(ctx, keeper) require.Equal(t, 0, len(keeper.GetValidTendermintUpdates(ctx))) } + +func TestUpdateValidatorCommission(t *testing.T) { + ctx, _, keeper := CreateTestInput(t, false, 1000) + ctx = ctx.WithBlockHeader(abci.Header{Time: time.Now().UTC()}) + + commission1 := types.NewCommissionWithTime( + sdk.NewDecWithPrec(1, 1), sdk.NewDecWithPrec(3, 1), + sdk.NewDecWithPrec(1, 1), time.Now().UTC().Add(time.Duration(-1)*time.Hour), + ) + commission2 := types.NewCommission(sdk.NewDecWithPrec(1, 1), sdk.NewDecWithPrec(3, 1), sdk.NewDecWithPrec(1, 1)) + + val1 := types.NewValidator(addrVals[0], PKs[0], types.Description{}) + val2 := types.NewValidator(addrVals[1], PKs[1], types.Description{}) + + val1, _ = val1.SetInitialCommission(commission1) + val2, _ = val2.SetInitialCommission(commission2) + + testCases := []struct { + validator types.Validator + newRate sdk.Dec + expectedErr bool + }{ + {val1, sdk.ZeroDec(), true}, + {val2, sdk.NewDecWithPrec(-1, 1), true}, + {val2, sdk.NewDecWithPrec(4, 1), true}, + {val2, sdk.NewDecWithPrec(3, 1), true}, + {val2, sdk.NewDecWithPrec(2, 1), false}, + } + + for i, tc := range testCases { + err := keeper.UpdateValidatorCommission(ctx, tc.validator, tc.newRate) + + if tc.expectedErr { + require.Error(t, err, "expected error for test case #%d with rate: %s", i, tc.newRate) + } else { + val, found := keeper.GetValidator(ctx, tc.validator.OperatorAddr) + + require.True(t, found, + "expected to find validator for test case #%d with rate: %s", i, tc.newRate, + ) + require.NoError(t, err, + "unexpected error for test case #%d with rate: %s", i, tc.newRate, + ) + require.Equal(t, tc.newRate, val.Commission.Rate, + "expected new validator commission rate for test case #%d with rate: %s", i, tc.newRate, + ) + require.Equal(t, ctx.BlockHeader().Time, val.Commission.UpdateTime, + "expected new validator commission update time for test case #%d with rate: %s", i, tc.newRate, + ) + } + } +} diff --git a/x/stake/simulation/msgs.go b/x/stake/simulation/msgs.go index 5b2bc1ee81..76f778a15c 100644 --- a/x/stake/simulation/msgs.go +++ b/x/stake/simulation/msgs.go @@ -23,32 +23,48 @@ func SimulateMsgCreateValidator(m auth.AccountMapper, k stake.Keeper) simulation description := stake.Description{ Moniker: simulation.RandStringOfLength(r, 10), } + + maxCommission := sdk.NewInt(10) + commission := stake.NewCommissionMsg( + sdk.NewDecWithPrec(simulation.RandomAmount(r, maxCommission).Int64(), 1), + sdk.NewDecWithPrec(simulation.RandomAmount(r, maxCommission).Int64(), 1), + sdk.NewDecWithPrec(simulation.RandomAmount(r, maxCommission).Int64(), 1), + ) + key := simulation.RandomKey(r, keys) pubkey := key.PubKey() address := sdk.ValAddress(pubkey.Address()) amount := m.GetAccount(ctx, sdk.AccAddress(address)).GetCoins().AmountOf(denom) + if amount.GT(sdk.ZeroInt()) { amount = simulation.RandomAmount(r, amount) } + if amount.Equal(sdk.ZeroInt()) { return "no-operation", nil, nil } + msg := stake.MsgCreateValidator{ Description: description, + Commission: commission, ValidatorAddr: address, DelegatorAddr: sdk.AccAddress(address), PubKey: pubkey, Delegation: sdk.NewCoin(denom, amount), } + if msg.ValidateBasic() != nil { return "", nil, fmt.Errorf("expected msg to pass ValidateBasic: %s", msg.GetSignBytes()) } + ctx, write := ctx.CacheContext() result := handler(ctx, msg) if result.IsOK() { write() } + event(fmt.Sprintf("stake/MsgCreateValidator/%v", result.IsOK())) + // require.True(t, result.IsOK(), "expected OK result but instead got %v", result) action = fmt.Sprintf("TestMsgCreateValidator: ok %v, msg %s", result.IsOK(), msg.GetSignBytes()) return action, nil, nil @@ -66,16 +82,24 @@ func SimulateMsgEditValidator(k stake.Keeper) simulation.Operation { Website: simulation.RandStringOfLength(r, 10), Details: simulation.RandStringOfLength(r, 10), } + + maxCommission := sdk.NewInt(10) + newCommissionRate := sdk.NewDecWithPrec(simulation.RandomAmount(r, maxCommission).Int64(), 1) + key := simulation.RandomKey(r, keys) pubkey := key.PubKey() address := sdk.ValAddress(pubkey.Address()) + msg := stake.MsgEditValidator{ - Description: description, - ValidatorAddr: address, + Description: description, + ValidatorAddr: address, + CommissionRate: &newCommissionRate, } + if msg.ValidateBasic() != nil { return "", nil, fmt.Errorf("expected msg to pass ValidateBasic: %s", msg.GetSignBytes()) } + ctx, write := ctx.CacheContext() result := handler(ctx, msg) if result.IsOK() { diff --git a/x/stake/stake.go b/x/stake/stake.go index 18b99fd4a0..b421a5d25a 100644 --- a/x/stake/stake.go +++ b/x/stake/stake.go @@ -12,6 +12,7 @@ type ( Keeper = keeper.Keeper Validator = types.Validator Description = types.Description + Commission = types.Commission Delegation = types.Delegation DelegationSummary = types.DelegationSummary UnbondingDelegation = types.UnbondingDelegation @@ -64,13 +65,16 @@ var ( GetREDsToValDstIndexKey = keeper.GetREDsToValDstIndexKey GetREDsByDelToValDstIndexKey = keeper.GetREDsByDelToValDstIndexKey - DefaultParams = types.DefaultParams - InitialPool = types.InitialPool - NewValidator = types.NewValidator - NewDescription = types.NewDescription - NewGenesisState = types.NewGenesisState - DefaultGenesisState = types.DefaultGenesisState - RegisterCodec = types.RegisterCodec + DefaultParams = types.DefaultParams + InitialPool = types.InitialPool + NewValidator = types.NewValidator + NewDescription = types.NewDescription + NewCommission = types.NewCommission + NewCommissionMsg = types.NewCommissionMsg + NewCommissionWithTime = types.NewCommissionWithTime + NewGenesisState = types.NewGenesisState + DefaultGenesisState = types.DefaultGenesisState + RegisterCodec = types.RegisterCodec NewMsgCreateValidator = types.NewMsgCreateValidator NewMsgCreateValidatorOnBehalfOf = types.NewMsgCreateValidatorOnBehalfOf diff --git a/x/stake/types/commission.go b/x/stake/types/commission.go new file mode 100644 index 0000000000..b76971faa0 --- /dev/null +++ b/x/stake/types/commission.go @@ -0,0 +1,128 @@ +package types + +import ( + "fmt" + "time" + + sdk "github.com/cosmos/cosmos-sdk/types" +) + +type ( + // Commission defines a commission parameters for a given validator. + Commission struct { + Rate sdk.Dec `json:"rate"` // the commission rate charged to delegators + MaxRate sdk.Dec `json:"max_rate"` // maximum commission rate which validator can ever charge + MaxChangeRate sdk.Dec `json:"max_change_rate"` // maximum daily increase of the validator commission + UpdateTime time.Time `json:"update_time"` // the last time the commission rate was changed + } + + // CommissionMsg defines a commission message to be used for creating a + // validator. + CommissionMsg struct { + Rate sdk.Dec `json:"rate"` // the commission rate charged to delegators + MaxRate sdk.Dec `json:"max_rate"` // maximum commission rate which validator can ever charge + MaxChangeRate sdk.Dec `json:"max_change_rate"` // maximum daily increase of the validator commission + } +) + +// NewCommissionMsg returns an initialized validator commission message. +func NewCommissionMsg(rate, maxRate, maxChangeRate sdk.Dec) CommissionMsg { + return CommissionMsg{ + Rate: rate, + MaxRate: maxRate, + MaxChangeRate: maxChangeRate, + } +} + +// NewCommission returns an initialized validator commission. +func NewCommission(rate, maxRate, maxChangeRate sdk.Dec) Commission { + return Commission{ + Rate: rate, + MaxRate: maxRate, + MaxChangeRate: maxChangeRate, + UpdateTime: time.Unix(0, 0).UTC(), + } +} + +// NewCommission returns an initialized validator commission with a specified +// update time which should be the current block BFT time. +func NewCommissionWithTime(rate, maxRate, maxChangeRate sdk.Dec, updatedAt time.Time) Commission { + return Commission{ + Rate: rate, + MaxRate: maxRate, + MaxChangeRate: maxChangeRate, + UpdateTime: updatedAt, + } +} + +// Equal checks if the given Commission object is equal to the receiving +// Commission object. +func (c Commission) Equal(c2 Commission) bool { + return c.Rate.Equal(c2.Rate) && + c.MaxRate.Equal(c2.MaxRate) && + c.MaxChangeRate.Equal(c2.MaxChangeRate) && + c.UpdateTime.Equal(c2.UpdateTime) +} + +// String implements the Stringer interface for a Commission. +func (c Commission) String() string { + return fmt.Sprintf("rate: %s, maxRate: %s, maxChangeRate: %s, updateTime: %s", + c.Rate, c.MaxRate, c.MaxChangeRate, c.UpdateTime, + ) +} + +// Validate performs basic sanity validation checks of initial commission +// parameters. If validation fails, an SDK error is returned. +func (c Commission) Validate() sdk.Error { + switch { + case c.MaxRate.LT(sdk.ZeroDec()): + // max rate cannot be negative + return ErrCommissionNegative(DefaultCodespace) + + case c.MaxRate.GT(sdk.OneDec()): + // max rate cannot be greater than 100% + return ErrCommissionHuge(DefaultCodespace) + + case c.Rate.LT(sdk.ZeroDec()): + // rate cannot be negative + return ErrCommissionNegative(DefaultCodespace) + + case c.Rate.GT(c.MaxRate): + // rate cannot be greater than the max rate + return ErrCommissionGTMaxRate(DefaultCodespace) + + case c.MaxChangeRate.LT(sdk.ZeroDec()): + // change rate cannot be negative + return ErrCommissionChangeRateNegative(DefaultCodespace) + + case c.MaxChangeRate.GT(c.MaxRate): + // change rate cannot be greater than the max rate + return ErrCommissionChangeRateGTMaxRate(DefaultCodespace) + } + + return nil +} + +// ValidateNewRate performs basic sanity validation checks of a new commission +// rate. If validation fails, an SDK error is returned. +func (c Commission) ValidateNewRate(newRate sdk.Dec, blockTime time.Time) sdk.Error { + switch { + case blockTime.Sub(c.UpdateTime).Hours() < 24: + // new rate cannot be changed more than once within 24 hours + return ErrCommissionUpdateTime(DefaultCodespace) + + case newRate.LT(sdk.ZeroDec()): + // new rate cannot be negative + return ErrCommissionNegative(DefaultCodespace) + + case newRate.GT(c.MaxRate): + // new rate cannot be greater than the max rate + return ErrCommissionGTMaxRate(DefaultCodespace) + + case newRate.Sub(c.Rate).Abs().GT(c.MaxChangeRate): + // new rate % points change cannot be greater than the max change rate + return ErrCommissionGTMaxChangeRate(DefaultCodespace) + } + + return nil +} diff --git a/x/stake/types/errors.go b/x/stake/types/errors.go index 366012bbfb..84a7e5ae68 100644 --- a/x/stake/types/errors.go +++ b/x/stake/types/errors.go @@ -65,6 +65,26 @@ func ErrCommissionHuge(codespace sdk.CodespaceType) sdk.Error { return sdk.NewError(codespace, CodeInvalidValidator, "commission cannot be more than 100%") } +func ErrCommissionGTMaxRate(codespace sdk.CodespaceType) sdk.Error { + return sdk.NewError(codespace, CodeInvalidValidator, "commission cannot be more than the max rate") +} + +func ErrCommissionUpdateTime(codespace sdk.CodespaceType) sdk.Error { + return sdk.NewError(codespace, CodeInvalidValidator, "commission cannot be changed more than once in 24h") +} + +func ErrCommissionChangeRateNegative(codespace sdk.CodespaceType) sdk.Error { + return sdk.NewError(codespace, CodeInvalidValidator, "commission change rate must be positive") +} + +func ErrCommissionChangeRateGTMaxRate(codespace sdk.CodespaceType) sdk.Error { + return sdk.NewError(codespace, CodeInvalidValidator, "commission change rate cannot be more than the max rate") +} + +func ErrCommissionGTMaxChangeRate(codespace sdk.CodespaceType) sdk.Error { + return sdk.NewError(codespace, CodeInvalidValidator, "commission cannot be changed more than max change rate") +} + func ErrNilDelegatorAddr(codespace sdk.CodespaceType) sdk.Error { return sdk.NewError(codespace, CodeInvalidInput, "delegator address is nil") } diff --git a/x/stake/types/msg.go b/x/stake/types/msg.go index 558c913fc7..a313dd64f4 100644 --- a/x/stake/types/msg.go +++ b/x/stake/types/msg.go @@ -20,6 +20,7 @@ var _, _ sdk.Msg = &MsgBeginRedelegate{}, &MsgCompleteRedelegate{} // MsgCreateValidator - struct for unbonding transactions type MsgCreateValidator struct { Description + Commission CommissionMsg DelegatorAddr sdk.AccAddress `json:"delegator_address"` ValidatorAddr sdk.ValAddress `json:"validator_address"` PubKey crypto.PubKey `json:"pubkey"` @@ -28,22 +29,23 @@ type MsgCreateValidator struct { // Default way to create validator. Delegator address and validator address are the same func NewMsgCreateValidator(valAddr sdk.ValAddress, pubkey crypto.PubKey, - selfDelegation sdk.Coin, description Description) MsgCreateValidator { + selfDelegation sdk.Coin, description Description, commission CommissionMsg) MsgCreateValidator { return NewMsgCreateValidatorOnBehalfOf( - sdk.AccAddress(valAddr), valAddr, pubkey, selfDelegation, description, + sdk.AccAddress(valAddr), valAddr, pubkey, selfDelegation, description, commission, ) } // Creates validator msg by delegator address on behalf of validator address func NewMsgCreateValidatorOnBehalfOf(delAddr sdk.AccAddress, valAddr sdk.ValAddress, - pubkey crypto.PubKey, delegation sdk.Coin, description Description) MsgCreateValidator { + pubkey crypto.PubKey, delegation sdk.Coin, description Description, commission CommissionMsg) MsgCreateValidator { return MsgCreateValidator{ Description: description, DelegatorAddr: delAddr, ValidatorAddr: valAddr, PubKey: pubkey, Delegation: delegation, + Commission: commission, } } @@ -95,10 +97,13 @@ func (msg MsgCreateValidator) ValidateBasic() sdk.Error { if !(msg.Delegation.Amount.GT(sdk.ZeroInt())) { return ErrBadDelegationAmount(DefaultCodespace) } - empty := Description{} - if msg.Description == empty { + if msg.Description == (Description{}) { return sdk.NewError(DefaultCodespace, CodeInvalidInput, "description must be included") } + if msg.Commission == (CommissionMsg{}) { + return sdk.NewError(DefaultCodespace, CodeInvalidInput, "commission must be included") + } + return nil } @@ -108,12 +113,20 @@ func (msg MsgCreateValidator) ValidateBasic() sdk.Error { type MsgEditValidator struct { Description ValidatorAddr sdk.ValAddress `json:"address"` + + // We pass a reference to the new commission rate as it's not mandatory to + // update. If not updated, the deserialized rate will be zero with no way to + // distinguish if an update was intended. + // + // REF: #2373 + CommissionRate *sdk.Dec `json:"commission_rate"` } -func NewMsgEditValidator(valAddr sdk.ValAddress, description Description) MsgEditValidator { +func NewMsgEditValidator(valAddr sdk.ValAddress, description Description, newRate *sdk.Dec) MsgEditValidator { return MsgEditValidator{ - Description: description, - ValidatorAddr: valAddr, + Description: description, + CommissionRate: newRate, + ValidatorAddr: valAddr, } } @@ -144,10 +157,11 @@ func (msg MsgEditValidator) ValidateBasic() sdk.Error { if msg.ValidatorAddr == nil { return sdk.NewError(DefaultCodespace, CodeInvalidInput, "nil validator address") } - empty := Description{} - if msg.Description == empty { + + if msg.Description == (Description{}) { return sdk.NewError(DefaultCodespace, CodeInvalidInput, "transaction must include some information to modify") } + return nil } diff --git a/x/stake/types/msg_test.go b/x/stake/types/msg_test.go index eb66c04220..b5adbd0ad0 100644 --- a/x/stake/types/msg_test.go +++ b/x/stake/types/msg_test.go @@ -17,26 +17,30 @@ var ( // test ValidateBasic for MsgCreateValidator func TestMsgCreateValidator(t *testing.T) { + commission1 := NewCommissionMsg(sdk.ZeroDec(), sdk.ZeroDec(), sdk.ZeroDec()) + commission2 := NewCommissionMsg(sdk.NewDec(5), sdk.NewDec(5), sdk.NewDec(5)) + tests := []struct { name, moniker, identity, website, details string + commissionMsg CommissionMsg validatorAddr sdk.ValAddress pubkey crypto.PubKey bond sdk.Coin expectPass bool }{ - {"basic good", "a", "b", "c", "d", addr1, pk1, coinPos, true}, - {"partial description", "", "", "c", "", addr1, pk1, coinPos, true}, - {"empty description", "", "", "", "", addr1, pk1, coinPos, false}, - {"empty address", "a", "b", "c", "d", emptyAddr, pk1, coinPos, false}, - {"empty pubkey", "a", "b", "c", "d", addr1, emptyPubkey, coinPos, true}, - {"empty bond", "a", "b", "c", "d", addr1, pk1, coinZero, false}, - {"negative bond", "a", "b", "c", "d", addr1, pk1, coinNeg, false}, - {"negative bond", "a", "b", "c", "d", addr1, pk1, coinNeg, false}, + {"basic good", "a", "b", "c", "d", commission1, addr1, pk1, coinPos, true}, + {"partial description", "", "", "c", "", commission1, addr1, pk1, coinPos, true}, + {"empty description", "", "", "", "", commission2, addr1, pk1, coinPos, false}, + {"empty address", "a", "b", "c", "d", commission2, emptyAddr, pk1, coinPos, false}, + {"empty pubkey", "a", "b", "c", "d", commission1, addr1, emptyPubkey, coinPos, true}, + {"empty bond", "a", "b", "c", "d", commission2, addr1, pk1, coinZero, false}, + {"negative bond", "a", "b", "c", "d", commission2, addr1, pk1, coinNeg, false}, + {"negative bond", "a", "b", "c", "d", commission1, addr1, pk1, coinNeg, false}, } for _, tc := range tests { description := NewDescription(tc.moniker, tc.identity, tc.website, tc.details) - msg := NewMsgCreateValidator(tc.validatorAddr, tc.pubkey, tc.bond, description) + msg := NewMsgCreateValidator(tc.validatorAddr, tc.pubkey, tc.bond, description, tc.commissionMsg) if tc.expectPass { require.Nil(t, msg.ValidateBasic(), "test: %v", tc.name) } else { @@ -60,7 +64,9 @@ func TestMsgEditValidator(t *testing.T) { for _, tc := range tests { description := NewDescription(tc.moniker, tc.identity, tc.website, tc.details) - msg := NewMsgEditValidator(tc.validatorAddr, description) + newRate := sdk.ZeroDec() + + msg := NewMsgEditValidator(tc.validatorAddr, description, &newRate) if tc.expectPass { require.Nil(t, msg.ValidateBasic(), "test: %v", tc.name) } else { @@ -71,28 +77,35 @@ func TestMsgEditValidator(t *testing.T) { // test ValidateBasic and GetSigners for MsgCreateValidatorOnBehalfOf func TestMsgCreateValidatorOnBehalfOf(t *testing.T) { + commission1 := NewCommissionMsg(sdk.ZeroDec(), sdk.ZeroDec(), sdk.ZeroDec()) + commission2 := NewCommissionMsg(sdk.NewDec(5), sdk.NewDec(5), sdk.NewDec(5)) + tests := []struct { name, moniker, identity, website, details string + commissionMsg CommissionMsg delegatorAddr sdk.AccAddress validatorAddr sdk.ValAddress validatorPubKey crypto.PubKey bond sdk.Coin expectPass bool }{ - {"basic good", "a", "b", "c", "d", sdk.AccAddress(addr1), addr2, pk2, coinPos, true}, - {"partial description", "", "", "c", "", sdk.AccAddress(addr1), addr2, pk2, coinPos, true}, - {"empty description", "", "", "", "", sdk.AccAddress(addr1), addr2, pk2, coinPos, false}, - {"empty delegator address", "a", "b", "c", "d", sdk.AccAddress(emptyAddr), addr2, pk2, coinPos, false}, - {"empty validator address", "a", "b", "c", "d", sdk.AccAddress(addr1), emptyAddr, pk2, coinPos, false}, - {"empty pubkey", "a", "b", "c", "d", sdk.AccAddress(addr1), addr2, emptyPubkey, coinPos, true}, - {"empty bond", "a", "b", "c", "d", sdk.AccAddress(addr1), addr2, pk2, coinZero, false}, - {"negative bond", "a", "b", "c", "d", sdk.AccAddress(addr1), addr2, pk2, coinNeg, false}, - {"negative bond", "a", "b", "c", "d", sdk.AccAddress(addr1), addr2, pk2, coinNeg, false}, + {"basic good", "a", "b", "c", "d", commission2, sdk.AccAddress(addr1), addr2, pk2, coinPos, true}, + {"partial description", "", "", "c", "", commission2, sdk.AccAddress(addr1), addr2, pk2, coinPos, true}, + {"empty description", "", "", "", "", commission1, sdk.AccAddress(addr1), addr2, pk2, coinPos, false}, + {"empty delegator address", "a", "b", "c", "d", commission1, sdk.AccAddress(emptyAddr), addr2, pk2, coinPos, false}, + {"empty validator address", "a", "b", "c", "d", commission2, sdk.AccAddress(addr1), emptyAddr, pk2, coinPos, false}, + {"empty pubkey", "a", "b", "c", "d", commission1, sdk.AccAddress(addr1), addr2, emptyPubkey, coinPos, true}, + {"empty bond", "a", "b", "c", "d", commission2, sdk.AccAddress(addr1), addr2, pk2, coinZero, false}, + {"negative bond", "a", "b", "c", "d", commission1, sdk.AccAddress(addr1), addr2, pk2, coinNeg, false}, + {"negative bond", "a", "b", "c", "d", commission2, sdk.AccAddress(addr1), addr2, pk2, coinNeg, false}, } for _, tc := range tests { description := NewDescription(tc.moniker, tc.identity, tc.website, tc.details) - msg := NewMsgCreateValidatorOnBehalfOf(tc.delegatorAddr, tc.validatorAddr, tc.validatorPubKey, tc.bond, description) + msg := NewMsgCreateValidatorOnBehalfOf( + tc.delegatorAddr, tc.validatorAddr, tc.validatorPubKey, tc.bond, description, tc.commissionMsg, + ) + if tc.expectPass { require.Nil(t, msg.ValidateBasic(), "test: %v", tc.name) } else { @@ -100,11 +113,11 @@ func TestMsgCreateValidatorOnBehalfOf(t *testing.T) { } } - msg := NewMsgCreateValidator(addr1, pk1, coinPos, Description{}) + msg := NewMsgCreateValidator(addr1, pk1, coinPos, Description{}, CommissionMsg{}) addrs := msg.GetSigners() require.Equal(t, []sdk.AccAddress{sdk.AccAddress(addr1)}, addrs, "Signers on default msg is wrong") - msg = NewMsgCreateValidatorOnBehalfOf(sdk.AccAddress(addr2), addr1, pk1, coinPos, Description{}) + msg = NewMsgCreateValidatorOnBehalfOf(sdk.AccAddress(addr2), addr1, pk1, coinPos, Description{}, CommissionMsg{}) addrs = msg.GetSigners() require.Equal(t, []sdk.AccAddress{sdk.AccAddress(addr2), sdk.AccAddress(addr1)}, addrs, "Signers for onbehalfof msg is wrong") } diff --git a/x/stake/types/validator.go b/x/stake/types/validator.go index 6c5066a783..f124ab5959 100644 --- a/x/stake/types/validator.go +++ b/x/stake/types/validator.go @@ -36,68 +36,56 @@ type Validator struct { UnbondingHeight int64 `json:"unbonding_height"` // if unbonding, height at which this validator has begun unbonding UnbondingMinTime time.Time `json:"unbonding_time"` // if unbonding, min time for the validator to complete unbonding - Commission sdk.Dec `json:"commission"` // XXX the commission rate of fees charged to any delegators - CommissionMax sdk.Dec `json:"commission_max"` // XXX maximum commission rate which this validator can ever charge - CommissionChangeRate sdk.Dec `json:"commission_change_rate"` // XXX maximum daily increase of the validator commission - CommissionChangeToday sdk.Dec `json:"commission_change_today"` // XXX commission rate change today, reset each day (UTC time) + Commission Commission `json:"commission"` // commission parameters } // NewValidator - initialize a new validator func NewValidator(operator sdk.ValAddress, pubKey crypto.PubKey, description Description) Validator { return Validator{ - OperatorAddr: operator, - ConsPubKey: pubKey, - Jailed: false, - Status: sdk.Unbonded, - Tokens: sdk.ZeroDec(), - DelegatorShares: sdk.ZeroDec(), - Description: description, - BondHeight: int64(0), - BondIntraTxCounter: int16(0), - UnbondingHeight: int64(0), - UnbondingMinTime: time.Unix(0, 0).UTC(), - Commission: sdk.ZeroDec(), - CommissionMax: sdk.ZeroDec(), - CommissionChangeRate: sdk.ZeroDec(), - CommissionChangeToday: sdk.ZeroDec(), + OperatorAddr: operator, + ConsPubKey: pubKey, + Jailed: false, + Status: sdk.Unbonded, + Tokens: sdk.ZeroDec(), + DelegatorShares: sdk.ZeroDec(), + Description: description, + BondHeight: int64(0), + BondIntraTxCounter: int16(0), + UnbondingHeight: int64(0), + UnbondingMinTime: time.Unix(0, 0).UTC(), + Commission: NewCommission(sdk.ZeroDec(), sdk.ZeroDec(), sdk.ZeroDec()), } } // what's kept in the store value type validatorValue struct { - ConsPubKey crypto.PubKey - Jailed bool - Status sdk.BondStatus - Tokens sdk.Dec - DelegatorShares sdk.Dec - Description Description - BondHeight int64 - BondIntraTxCounter int16 - UnbondingHeight int64 - UnbondingMinTime time.Time - Commission sdk.Dec - CommissionMax sdk.Dec - CommissionChangeRate sdk.Dec - CommissionChangeToday sdk.Dec + ConsPubKey crypto.PubKey + Jailed bool + Status sdk.BondStatus + Tokens sdk.Dec + DelegatorShares sdk.Dec + Description Description + BondHeight int64 + BondIntraTxCounter int16 + UnbondingHeight int64 + UnbondingMinTime time.Time + Commission Commission } // return the redelegation without fields contained within the key for the store func MustMarshalValidator(cdc *codec.Codec, validator Validator) []byte { val := validatorValue{ - ConsPubKey: validator.ConsPubKey, - Jailed: validator.Jailed, - Status: validator.Status, - Tokens: validator.Tokens, - DelegatorShares: validator.DelegatorShares, - Description: validator.Description, - BondHeight: validator.BondHeight, - BondIntraTxCounter: validator.BondIntraTxCounter, - UnbondingHeight: validator.UnbondingHeight, - UnbondingMinTime: validator.UnbondingMinTime, - Commission: validator.Commission, - CommissionMax: validator.CommissionMax, - CommissionChangeRate: validator.CommissionChangeRate, - CommissionChangeToday: validator.CommissionChangeToday, + ConsPubKey: validator.ConsPubKey, + Jailed: validator.Jailed, + Status: validator.Status, + Tokens: validator.Tokens, + DelegatorShares: validator.DelegatorShares, + Description: validator.Description, + BondHeight: validator.BondHeight, + BondIntraTxCounter: validator.BondIntraTxCounter, + UnbondingHeight: validator.UnbondingHeight, + UnbondingMinTime: validator.UnbondingMinTime, + Commission: validator.Commission, } return cdc.MustMarshalBinary(val) } @@ -124,21 +112,18 @@ func UnmarshalValidator(cdc *codec.Codec, operatorAddr, value []byte) (validator } return Validator{ - OperatorAddr: operatorAddr, - ConsPubKey: storeValue.ConsPubKey, - Jailed: storeValue.Jailed, - Tokens: storeValue.Tokens, - Status: storeValue.Status, - DelegatorShares: storeValue.DelegatorShares, - Description: storeValue.Description, - BondHeight: storeValue.BondHeight, - BondIntraTxCounter: storeValue.BondIntraTxCounter, - UnbondingHeight: storeValue.UnbondingHeight, - UnbondingMinTime: storeValue.UnbondingMinTime, - Commission: storeValue.Commission, - CommissionMax: storeValue.CommissionMax, - CommissionChangeRate: storeValue.CommissionChangeRate, - CommissionChangeToday: storeValue.CommissionChangeToday, + OperatorAddr: operatorAddr, + ConsPubKey: storeValue.ConsPubKey, + Jailed: storeValue.Jailed, + Tokens: storeValue.Tokens, + Status: storeValue.Status, + DelegatorShares: storeValue.DelegatorShares, + Description: storeValue.Description, + BondHeight: storeValue.BondHeight, + BondIntraTxCounter: storeValue.BondIntraTxCounter, + UnbondingHeight: storeValue.UnbondingHeight, + UnbondingMinTime: storeValue.UnbondingMinTime, + Commission: storeValue.Commission, }, nil } @@ -156,16 +141,13 @@ func (v Validator) HumanReadableString() (string, error) { resp += fmt.Sprintf("Validator Consensus Pubkey: %s\n", bechConsPubKey) resp += fmt.Sprintf("Jailed: %v\n", v.Jailed) resp += fmt.Sprintf("Status: %s\n", sdk.BondStatusToString(v.Status)) - resp += fmt.Sprintf("Tokens: %s\n", v.Tokens.String()) - resp += fmt.Sprintf("Delegator Shares: %s\n", v.DelegatorShares.String()) + resp += fmt.Sprintf("Tokens: %s\n", v.Tokens) + resp += fmt.Sprintf("Delegator Shares: %s\n", v.DelegatorShares) resp += fmt.Sprintf("Description: %s\n", v.Description) resp += fmt.Sprintf("Bond Height: %d\n", v.BondHeight) resp += fmt.Sprintf("Unbonding Height: %d\n", v.UnbondingHeight) resp += fmt.Sprintf("Minimum Unbonding Time: %v\n", v.UnbondingMinTime) - resp += fmt.Sprintf("Commission: %s\n", v.Commission.String()) - resp += fmt.Sprintf("Max Commission Rate: %s\n", v.CommissionMax.String()) - resp += fmt.Sprintf("Commission Change Rate: %s\n", v.CommissionChangeRate.String()) - resp += fmt.Sprintf("Commission Change Today: %s\n", v.CommissionChangeToday.String()) + resp += fmt.Sprintf("Commission: {%s}\n", v.Commission) return resp, nil } @@ -189,10 +171,7 @@ type bechValidator struct { UnbondingHeight int64 `json:"unbonding_height"` // if unbonding, height at which this validator has begun unbonding UnbondingMinTime time.Time `json:"unbonding_time"` // if unbonding, min time for the validator to complete unbonding - Commission sdk.Dec `json:"commission"` // XXX the commission rate of fees charged to any delegators - CommissionMax sdk.Dec `json:"commission_max"` // XXX maximum commission rate which this validator can ever charge - CommissionChangeRate sdk.Dec `json:"commission_change_rate"` // XXX maximum daily increase of the validator commission - CommissionChangeToday sdk.Dec `json:"commission_change_today"` // XXX commission rate change today, reset each day (UTC time) + Commission Commission `json:"commission"` // commission parameters } // MarshalJSON marshals the validator to JSON using Bech32 @@ -203,21 +182,18 @@ func (v Validator) MarshalJSON() ([]byte, error) { } return codec.Cdc.MarshalJSON(bechValidator{ - OperatorAddr: v.OperatorAddr, - ConsPubKey: bechConsPubKey, - Jailed: v.Jailed, - Status: v.Status, - Tokens: v.Tokens, - DelegatorShares: v.DelegatorShares, - Description: v.Description, - BondHeight: v.BondHeight, - BondIntraTxCounter: v.BondIntraTxCounter, - UnbondingHeight: v.UnbondingHeight, - UnbondingMinTime: v.UnbondingMinTime, - Commission: v.Commission, - CommissionMax: v.CommissionMax, - CommissionChangeRate: v.CommissionChangeRate, - CommissionChangeToday: v.CommissionChangeToday, + OperatorAddr: v.OperatorAddr, + ConsPubKey: bechConsPubKey, + Jailed: v.Jailed, + Status: v.Status, + Tokens: v.Tokens, + DelegatorShares: v.DelegatorShares, + Description: v.Description, + BondHeight: v.BondHeight, + BondIntraTxCounter: v.BondIntraTxCounter, + UnbondingHeight: v.UnbondingHeight, + UnbondingMinTime: v.UnbondingMinTime, + Commission: v.Commission, }) } @@ -232,21 +208,18 @@ func (v *Validator) UnmarshalJSON(data []byte) error { return err } *v = Validator{ - OperatorAddr: bv.OperatorAddr, - ConsPubKey: consPubKey, - Jailed: bv.Jailed, - Tokens: bv.Tokens, - Status: bv.Status, - DelegatorShares: bv.DelegatorShares, - Description: bv.Description, - BondHeight: bv.BondHeight, - BondIntraTxCounter: bv.BondIntraTxCounter, - UnbondingHeight: bv.UnbondingHeight, - UnbondingMinTime: bv.UnbondingMinTime, - Commission: bv.Commission, - CommissionMax: bv.CommissionMax, - CommissionChangeRate: bv.CommissionChangeRate, - CommissionChangeToday: bv.CommissionChangeToday, + OperatorAddr: bv.OperatorAddr, + ConsPubKey: consPubKey, + Jailed: bv.Jailed, + Tokens: bv.Tokens, + Status: bv.Status, + DelegatorShares: bv.DelegatorShares, + Description: bv.Description, + BondHeight: bv.BondHeight, + BondIntraTxCounter: bv.BondIntraTxCounter, + UnbondingHeight: bv.UnbondingHeight, + UnbondingMinTime: bv.UnbondingMinTime, + Commission: bv.Commission, } return nil } @@ -254,18 +227,14 @@ func (v *Validator) UnmarshalJSON(data []byte) error { //___________________________________________________________________ // only the vitals - does not check bond height of IntraTxCounter -// nolint gocyclo - why dis fail? -func (v Validator) Equal(c2 Validator) bool { - return v.ConsPubKey.Equals(c2.ConsPubKey) && - bytes.Equal(v.OperatorAddr, c2.OperatorAddr) && - v.Status.Equal(c2.Status) && - v.Tokens.Equal(c2.Tokens) && - v.DelegatorShares.Equal(c2.DelegatorShares) && - v.Description == c2.Description && - v.Commission.Equal(c2.Commission) && - v.CommissionMax.Equal(c2.CommissionMax) && - v.CommissionChangeRate.Equal(c2.CommissionChangeRate) && - v.CommissionChangeToday.Equal(c2.CommissionChangeToday) +func (v Validator) Equal(v2 Validator) bool { + return v.ConsPubKey.Equals(v2.ConsPubKey) && + bytes.Equal(v.OperatorAddr, v2.OperatorAddr) && + v.Status.Equal(v2.Status) && + v.Tokens.Equal(v2.Tokens) && + v.DelegatorShares.Equal(v2.DelegatorShares) && + v.Description == v2.Description && + v.Commission.Equal(v2.Commission) } // return the TM validator address @@ -400,6 +369,17 @@ func (v Validator) RemoveTokens(pool Pool, tokens sdk.Dec) (Validator, Pool) { return v, pool } +// SetInitialCommission attempts to set a validator's initial commission. An +// error is returned if the commission is invalid. +func (v Validator) SetInitialCommission(commission Commission) (Validator, sdk.Error) { + if err := commission.Validate(); err != nil { + return v, err + } + + v.Commission = commission + return v, nil +} + //_________________________________________________________________________________________________________ // AddTokensFromDel adds tokens to a validator diff --git a/x/stake/types/validator_test.go b/x/stake/types/validator_test.go index eceffbad39..de9e184804 100644 --- a/x/stake/types/validator_test.go +++ b/x/stake/types/validator_test.go @@ -274,3 +274,37 @@ func TestValidatorMarshalUnmarshalJSON(t *testing.T) { assert.NoError(t, err) assert.Equal(t, validator, *got) } + +func TestValidatorSetInitialCommission(t *testing.T) { + val := NewValidator(addr1, pk1, Description{}) + testCases := []struct { + validator Validator + commission Commission + expectedErr bool + }{ + {val, NewCommission(sdk.ZeroDec(), sdk.ZeroDec(), sdk.ZeroDec()), false}, + {val, NewCommission(sdk.ZeroDec(), sdk.NewDecWithPrec(-1, 1), sdk.ZeroDec()), true}, + {val, NewCommission(sdk.ZeroDec(), sdk.NewDec(15000000000), sdk.ZeroDec()), true}, + {val, NewCommission(sdk.NewDecWithPrec(-1, 1), sdk.ZeroDec(), sdk.ZeroDec()), true}, + {val, NewCommission(sdk.NewDecWithPrec(2, 1), sdk.NewDecWithPrec(1, 1), sdk.ZeroDec()), true}, + {val, NewCommission(sdk.ZeroDec(), sdk.ZeroDec(), sdk.NewDecWithPrec(-1, 1)), true}, + {val, NewCommission(sdk.ZeroDec(), sdk.NewDecWithPrec(1, 1), sdk.NewDecWithPrec(2, 1)), true}, + } + + for i, tc := range testCases { + val, err := tc.validator.SetInitialCommission(tc.commission) + + if tc.expectedErr { + require.Error(t, err, + "expected error for test case #%d with commission: %s", i, tc.commission, + ) + } else { + require.NoError(t, err, + "unexpected error for test case #%d with commission: %s", i, tc.commission, + ) + require.Equal(t, tc.commission, val.Commission, + "invalid validator commission for test case #%d with commission: %s", i, tc.commission, + ) + } + } +} From 1a5d122c931d7d9b171d3402e78dff67b32e1bbe Mon Sep 17 00:00:00 2001 From: Dev Ojha Date: Mon, 24 Sep 2018 18:42:51 -0700 Subject: [PATCH 05/26] Merge PR #2392: remove redundant sprintf calls, improve a doc comment, write test * remove redundant sprintf calls, improve a doc comment, write test * One more super minor fix --- x/gov/keeper.go | 11 +++++------ x/gov/proposals.go | 13 ++++++------- x/gov/proposals_test.go | 40 ++++++++++++++++++++++++++++++++++++++++ x/gov/queryable.go | 4 ++-- 4 files changed, 53 insertions(+), 15 deletions(-) create mode 100644 x/gov/proposals_test.go diff --git a/x/gov/keeper.go b/x/gov/keeper.go index 8af2d1fd3a..2a462cfb42 100644 --- a/x/gov/keeper.go +++ b/x/gov/keeper.go @@ -38,7 +38,11 @@ type Keeper struct { codespace sdk.CodespaceType } -// NewGovernanceMapper returns a mapper that uses go-codec to (binary) encode and decode gov types. +// NewKeeper returns a governance keeper. It handles: +// - submitting governance proposals +// - depositing funds into proposals, and activating upon sufficient funds being deposited +// - users voting on proposals, with weight proportional to stake in the system +// - and tallying the result of the vote. func NewKeeper(cdc *codec.Codec, key sdk.StoreKey, ps params.Setter, ck bank.Keeper, ds sdk.DelegationSet, codespace sdk.CodespaceType) Keeper { return Keeper{ storeKey: key, @@ -51,11 +55,6 @@ func NewKeeper(cdc *codec.Codec, key sdk.StoreKey, ps params.Setter, ck bank.Kee } } -// Returns the go-codec codec. -func (keeper Keeper) WireCodec() *codec.Codec { - return keeper.cdc -} - // ===================================================== // Proposals diff --git a/x/gov/proposals.go b/x/gov/proposals.go index e680699575..37e29df704 100644 --- a/x/gov/proposals.go +++ b/x/gov/proposals.go @@ -192,8 +192,9 @@ func (pt ProposalKind) String() string { func (pt ProposalKind) Format(s fmt.State, verb rune) { switch verb { case 's': - s.Write([]byte(fmt.Sprintf("%s", pt.String()))) + s.Write([]byte(pt.String())) default: + // TODO: Do this conversion more directly s.Write([]byte(fmt.Sprintf("%v", byte(pt)))) } } @@ -295,8 +296,9 @@ func (status ProposalStatus) String() string { func (status ProposalStatus) Format(s fmt.State, verb rune) { switch verb { case 's': - s.Write([]byte(fmt.Sprintf("%s", status.String()))) + s.Write([]byte(status.String())) default: + // TODO: Do this conversion more directly s.Write([]byte(fmt.Sprintf("%v", byte(status)))) } } @@ -322,11 +324,8 @@ func EmptyTallyResult() TallyResult { // checks if two proposals are equal func (resultA TallyResult) Equals(resultB TallyResult) bool { - if resultA.Yes.Equal(resultB.Yes) && + return (resultA.Yes.Equal(resultB.Yes) && resultA.Abstain.Equal(resultB.Abstain) && resultA.No.Equal(resultB.No) && - resultA.NoWithVeto.Equal(resultB.NoWithVeto) { - return true - } - return false + resultA.NoWithVeto.Equal(resultB.NoWithVeto)) } diff --git a/x/gov/proposals_test.go b/x/gov/proposals_test.go new file mode 100644 index 0000000000..1d5f2cf389 --- /dev/null +++ b/x/gov/proposals_test.go @@ -0,0 +1,40 @@ +package gov + +import ( + "fmt" + "testing" + + "github.com/stretchr/testify/require" +) + +func TestProposalKind_Format(t *testing.T) { + typeText, _ := ProposalTypeFromString("Text") + tests := []struct { + pt ProposalKind + sprintFArgs string + expectedStringOutput string + }{ + {typeText, "%s", "Text"}, + {typeText, "%v", "1"}, + } + for _, tt := range tests { + got := fmt.Sprintf(tt.sprintFArgs, tt.pt) + require.Equal(t, tt.expectedStringOutput, got) + } +} + +func TestProposalStatus_Format(t *testing.T) { + statusDepositPeriod, _ := ProposalStatusFromString("DepositPeriod") + tests := []struct { + pt ProposalStatus + sprintFArgs string + expectedStringOutput string + }{ + {statusDepositPeriod, "%s", "DepositPeriod"}, + {statusDepositPeriod, "%v", "1"}, + } + for _, tt := range tests { + got := fmt.Sprintf(tt.sprintFArgs, tt.pt) + require.Equal(t, tt.expectedStringOutput, got) + } +} diff --git a/x/gov/queryable.go b/x/gov/queryable.go index f20bb46f78..44380fe91a 100644 --- a/x/gov/queryable.go +++ b/x/gov/queryable.go @@ -205,12 +205,12 @@ func queryTally(ctx sdk.Context, path []string, req abci.RequestQuery, keeper Ke var proposalID int64 err2 := keeper.cdc.UnmarshalJSON(req.Data, proposalID) if err2 != nil { - return []byte{}, sdk.ErrUnknownRequest(fmt.Sprintf("incorrectly formatted request data - %s", err2.Error())) + return res, sdk.ErrUnknownRequest(fmt.Sprintf("incorrectly formatted request data - %s", err2.Error())) } proposal := keeper.GetProposal(ctx, proposalID) if proposal == nil { - return []byte{}, ErrUnknownProposal(DefaultCodespace, proposalID) + return res, ErrUnknownProposal(DefaultCodespace, proposalID) } var tallyResult TallyResult From 6b595842ed43b498bb6c124db8ed6685c0915e50 Mon Sep 17 00:00:00 2001 From: Rigel Date: Tue, 25 Sep 2018 00:09:31 -0400 Subject: [PATCH 06/26] Distr-PR-1 Staking ConsPubKey -> ConsAddr index (#2369) * pulling in stuff from fee-distr PR * revert some gov changes * fix using cons address, also remove old commented distr code * doc update * val comments * remove GetValidatorByConsPubKey --- PENDING.md | 1 + docs/spec/staking/state.md | 11 +-- examples/democoin/mock/validator.go | 22 ++++-- types/address.go | 5 ++ types/stake.go | 35 ++++----- x/distribution/keeper.go | 91 ----------------------- x/distribution/keeper_test.go | 31 -------- x/distribution/movement.go | 72 ------------------- x/distribution/types.go | 107 ---------------------------- x/gov/handler.go | 2 +- x/gov/tally.go | 4 +- x/gov/tally_test.go | 2 +- x/slashing/genesis.go | 2 +- x/slashing/handler.go | 8 +-- x/slashing/hooks.go | 23 +++--- x/slashing/keeper.go | 37 +++++----- x/slashing/keeper_test.go | 41 ++++++----- x/slashing/tick_test.go | 2 +- x/stake/client/cli/flags.go | 8 +-- x/stake/genesis.go | 4 +- x/stake/handler.go | 4 +- x/stake/handler_test.go | 17 +++-- x/stake/keeper/delegation.go | 1 + x/stake/keeper/keeper.go | 28 ++++---- x/stake/keeper/key.go | 8 +-- x/stake/keeper/sdk_types.go | 23 +++--- x/stake/keeper/slash.go | 33 ++++----- x/stake/keeper/slash_test.go | 74 +++++++++---------- x/stake/keeper/validator.go | 26 +++---- x/stake/keeper/validator_test.go | 14 +++- x/stake/simulation/invariants.go | 2 +- x/stake/stake.go | 4 +- x/stake/types/codec.go | 3 +- x/stake/types/delegation.go | 2 +- x/stake/types/validator.go | 20 +++--- 35 files changed, 248 insertions(+), 519 deletions(-) delete mode 100644 x/distribution/keeper.go delete mode 100644 x/distribution/keeper_test.go delete mode 100644 x/distribution/movement.go delete mode 100644 x/distribution/types.go diff --git a/PENDING.md b/PENDING.md index ec3c7f930f..344e3628d6 100644 --- a/PENDING.md +++ b/PENDING.md @@ -51,6 +51,7 @@ BREAKING CHANGES * [codec] \#2324 All referrences to wire have been renamed to codec. Additionally, wire.NewCodec is now codec.New(). * [types] \#2343 Make sdk.Msg have a names field, to facilitate automatic tagging. * [baseapp] \#2366 Automatically add action tags to all messages + * [x/staking] \#2244 staking now holds a consensus-address-index instead of a consensus-pubkey-index * Tendermint diff --git a/docs/spec/staking/state.md b/docs/spec/staking/state.md index f2d6f98541..9454aca7dd 100644 --- a/docs/spec/staking/state.md +++ b/docs/spec/staking/state.md @@ -43,12 +43,13 @@ type Params struct { Validators are identified according to the `OperatorAddr`, an SDK validator address for the operator of the validator. -Validators also have a `ConsPubKey`, the public key of the validator. - -Validators are indexed in the store using the following maps: +Validators also have a `ConsPubKey`, the public key of the validator used in +Tendermint consensus. The validator can be retrieved from it's `ConsPubKey` +once it can be converted into the corresponding `ConsAddr`. Validators are +indexed in the store using the following maps: - Validators: `0x02 | OperatorAddr -> amino(validator)` -- ValidatorsByPubKey: `0x03 | ConsPubKey -> OperatorAddr` +- ValidatorsByConsAddr: `0x03 | ConsAddr -> OperatorAddr` - ValidatorsByPower: `0x05 | power | blockHeight | blockTx -> OperatorAddr` `Validators` is the primary index - it ensures that each operator can have only one @@ -69,7 +70,7 @@ validator. ```golang type Validator struct { - ConsensusPubKey crypto.PubKey // Tendermint consensus pubkey of validator + ConsPubKey crypto.PubKey // Tendermint consensus pubkey of validator Jailed bool // has the validator been jailed? Status sdk.BondStatus // validator status (bonded/unbonding/unbonded) diff --git a/examples/democoin/mock/validator.go b/examples/democoin/mock/validator.go index 9f84786adf..a54cdfedf6 100644 --- a/examples/democoin/mock/validator.go +++ b/examples/democoin/mock/validator.go @@ -24,7 +24,12 @@ func (v Validator) GetOperator() sdk.ValAddress { } // Implements sdk.Validator -func (v Validator) GetPubKey() crypto.PubKey { +func (v Validator) GetConsPubKey() crypto.PubKey { + return nil +} + +// Implements sdk.Validator +func (v Validator) GetConsAddr() sdk.ConsAddress { return nil } @@ -88,7 +93,12 @@ func (vs *ValidatorSet) Validator(ctx sdk.Context, addr sdk.ValAddress) sdk.Vali } // ValidatorByPubKey implements sdk.ValidatorSet -func (vs *ValidatorSet) ValidatorByPubKey(ctx sdk.Context, pubkey crypto.PubKey) sdk.Validator { +func (vs *ValidatorSet) ValidatorByConsPubKey(_ sdk.Context, _ crypto.PubKey) sdk.Validator { + panic("not implemented") +} + +// ValidatorByPubKey implements sdk.ValidatorSet +func (vs *ValidatorSet) ValidatorByConsAddr(_ sdk.Context, _ sdk.ConsAddress) sdk.Validator { panic("not implemented") } @@ -122,21 +132,21 @@ func (vs *ValidatorSet) RemoveValidator(addr sdk.AccAddress) { } // Implements sdk.ValidatorSet -func (vs *ValidatorSet) Slash(ctx sdk.Context, pubkey crypto.PubKey, height int64, power int64, amt sdk.Dec) { +func (vs *ValidatorSet) Slash(_ sdk.Context, _ sdk.ConsAddress, _ int64, _ int64, _ sdk.Dec) { panic("not implemented") } // Implements sdk.ValidatorSet -func (vs *ValidatorSet) Jail(ctx sdk.Context, pubkey crypto.PubKey) { +func (vs *ValidatorSet) Jail(_ sdk.Context, _ sdk.ConsAddress) { panic("not implemented") } // Implements sdk.ValidatorSet -func (vs *ValidatorSet) Unjail(ctx sdk.Context, pubkey crypto.PubKey) { +func (vs *ValidatorSet) Unjail(_ sdk.Context, _ sdk.ConsAddress) { panic("not implemented") } // Implements sdk.ValidatorSet -func (vs *ValidatorSet) Delegation(ctx sdk.Context, addrDel sdk.AccAddress, addrVal sdk.ValAddress) sdk.Delegation { +func (vs *ValidatorSet) Delegation(_ sdk.Context, _ sdk.AccAddress, _ sdk.ValAddress) sdk.Delegation { panic("not implemented") } diff --git a/types/address.go b/types/address.go index 58b694f5da..ae13b2ad0a 100644 --- a/types/address.go +++ b/types/address.go @@ -292,6 +292,11 @@ func ConsAddressFromBech32(address string) (addr ConsAddress, err error) { return ConsAddress(bz), nil } +// get ConsAddress from pubkey +func GetConsAddress(pubkey crypto.PubKey) ConsAddress { + return ConsAddress(pubkey.Address()) +} + // Returns boolean for whether two ConsAddress are Equal func (ca ConsAddress) Equals(ca2 ConsAddress) bool { if ca.Empty() && ca2.Empty() { diff --git a/types/stake.go b/types/stake.go index 0b9f32b4b8..e794ea7349 100644 --- a/types/stake.go +++ b/types/stake.go @@ -37,22 +37,23 @@ func (b BondStatus) Equal(b2 BondStatus) bool { // validator for a delegated proof of stake system type Validator interface { - GetJailed() bool // whether the validator is jailed - GetMoniker() string // moniker of the validator - GetStatus() BondStatus // status of the validator - GetOperator() ValAddress // operator address to receive/return validators coins - GetPubKey() crypto.PubKey // validation pubkey - GetPower() Dec // validation power - GetTokens() Dec // validation tokens - GetDelegatorShares() Dec // Total out standing delegator shares - GetBondHeight() int64 // height in which the validator became active + GetJailed() bool // whether the validator is jailed + GetMoniker() string // moniker of the validator + GetStatus() BondStatus // status of the validator + GetOperator() ValAddress // operator address to receive/return validators coins + GetConsPubKey() crypto.PubKey // validation consensus pubkey + GetConsAddr() ConsAddress // validation consensus address + GetPower() Dec // validation power + GetTokens() Dec // validation tokens + GetDelegatorShares() Dec // Total out standing delegator shares + GetBondHeight() int64 // height in which the validator became active } // validator which fulfills abci validator interface for use in Tendermint func ABCIValidator(v Validator) abci.Validator { return abci.Validator{ - PubKey: tmtypes.TM2PB.PubKey(v.GetPubKey()), - Address: v.GetPubKey().Address(), + PubKey: tmtypes.TM2PB.PubKey(v.GetConsPubKey()), + Address: v.GetConsPubKey().Address(), Power: v.GetPower().RoundInt64(), } } @@ -67,14 +68,14 @@ type ValidatorSet interface { IterateValidatorsBonded(Context, func(index int64, validator Validator) (stop bool)) - Validator(Context, ValAddress) Validator // get a particular validator by operator - ValidatorByPubKey(Context, crypto.PubKey) Validator // get a particular validator by signing PubKey + Validator(Context, ValAddress) Validator // get a particular validator by operator address + ValidatorByConsAddr(Context, ConsAddress) Validator // get a particular validator by consensus address TotalPower(Context) Dec // total power of the validator set // slash the validator and delegators of the validator, specifying offence height, offence power, and slash fraction - Slash(Context, crypto.PubKey, int64, int64, Dec) - Jail(Context, crypto.PubKey) // jail a validator - Unjail(Context, crypto.PubKey) // unjail a validator + Slash(Context, ConsAddress, int64, int64, Dec) + Jail(Context, ConsAddress) // jail a validator + Unjail(Context, ConsAddress) // unjail a validator // Delegation allows for getting a particular delegation for a given validator // and delegator outside the scope of the staking module. @@ -87,7 +88,7 @@ type ValidatorSet interface { type Delegation interface { GetDelegator() AccAddress // delegator AccAddress for the bond GetValidator() ValAddress // validator operator address - GetBondShares() Dec // amount of validator's shares + GetShares() Dec // amount of validator's shares held in this delegation } // properties for the set of all delegations for a particular diff --git a/x/distribution/keeper.go b/x/distribution/keeper.go deleted file mode 100644 index 937a4674cf..0000000000 --- a/x/distribution/keeper.go +++ /dev/null @@ -1,91 +0,0 @@ -package stake - -//// keeper of the staking store -//type Keeper struct { -//storeKey sdk.StoreKey -//cdc *codec.Codec -//bankKeeper bank.Keeper - -//// codespace -//codespace sdk.CodespaceType -//} - -//func NewKeeper(cdc *codec.Codec, key sdk.StoreKey, ck bank.Keeper, codespace sdk.CodespaceType) Keeper { -//keeper := Keeper{ -//storeKey: key, -//cdc: cdc, -//bankKeeper: ck, -//codespace: codespace, -//} -//return keeper -//} - -////_________________________________________________________________________ - -//// cummulative power of the non-absent prevotes -//func (k Keeper) GetTotalPrecommitVotingPower(ctx sdk.Context) sdk.Dec { -//store := ctx.KVStore(k.storeKey) - -//// get absent prevote indexes -//absents := ctx.AbsentValidators() - -//TotalPower := sdk.ZeroDec() -//i := int32(0) -//iterator := store.SubspaceIterator(ValidatorsBondedKey) -//for ; iterator.Valid(); iterator.Next() { - -//skip := false -//for j, absentIndex := range absents { -//if absentIndex > i { -//break -//} - -//// if non-voting validator found, skip adding its power -//if absentIndex == i { -//absents = append(absents[:j], absents[j+1:]...) // won't need again -//skip = true -//break -//} -//} -//if skip { -//continue -//} - -//bz := iterator.Value() -//var validator Validator -//k.cdc.MustUnmarshalBinary(bz, &validator) -//TotalPower = TotalPower.Add(validator.Power) -//i++ -//} -//iterator.Close() -//return TotalPower -//} - -////_______________________________________________________________________ - -//// XXX TODO trim functionality - -//// retrieve all the power changes which occur after a height -//func (k Keeper) GetPowerChangesAfterHeight(ctx sdk.Context, earliestHeight int64) (pcs []PowerChange) { -//store := ctx.KVStore(k.storeKey) - -//iterator := store.SubspaceIterator(PowerChangeKey) //smallest to largest -//for ; iterator.Valid(); iterator.Next() { -//pcBytes := iterator.Value() -//var pc PowerChange -//k.cdc.MustUnmarshalBinary(pcBytes, &pc) -//if pc.Height < earliestHeight { -//break -//} -//pcs = append(pcs, pc) -//} -//iterator.Close() -//return -//} - -//// set a power change -//func (k Keeper) setPowerChange(ctx sdk.Context, pc PowerChange) { -//store := ctx.KVStore(k.storeKey) -//b := k.cdc.MustMarshalBinary(pc) -//store.Set(GetPowerChangeKey(pc.Height), b) -//} diff --git a/x/distribution/keeper_test.go b/x/distribution/keeper_test.go deleted file mode 100644 index 8902680604..0000000000 --- a/x/distribution/keeper_test.go +++ /dev/null @@ -1,31 +0,0 @@ -package stake - -//// test if is a gotValidator from the last update -//func TestGetTotalPrecommitVotingPower(t *testing.T) { -//ctx, _, keeper := createTestInput(t, false, 0) - -//amts := []int64{10000, 1000, 100, 10, 1} -//var candidatesIn [5]Candidate -//for i, amt := range amts { -//candidatesIn[i] = NewCandidate(addrVals[i], pks[i], Description{}) -//candidatesIn[i].BondedShares = sdk.NewDec(amt) -//candidatesIn[i].DelegatorShares = sdk.NewDec(amt) -//keeper.setCandidate(ctx, candidatesIn[i]) -//} - -//// test that an empty gotValidator set doesn't have any gotValidators -//gotValidators := keeper.GetValidators(ctx) -//require.Equal(t, 5, len(gotValidators)) - -//totPow := keeper.GetTotalPrecommitVotingPower(ctx) -//exp := sdk.NewDec(11111) -//require.True(t, exp.Equal(totPow), "exp %v, got %v", exp, totPow) - -//// set absent gotValidators to be the 1st and 3rd record sorted by pubKey address -//ctx = ctx.WithAbsentValidators([]int32{1, 3}) -//totPow = keeper.GetTotalPrecommitVotingPower(ctx) - -//// XXX verify that this order should infact exclude these two records -//exp = sdk.NewDec(11100) -//require.True(t, exp.Equal(totPow), "exp %v, got %v", exp, totPow) -//} diff --git a/x/distribution/movement.go b/x/distribution/movement.go deleted file mode 100644 index 399a25a681..0000000000 --- a/x/distribution/movement.go +++ /dev/null @@ -1,72 +0,0 @@ -package stake - -import ( - sdk "github.com/cosmos/cosmos-sdk/types" -) - -// burn burn burn -func BurnFeeHandler(ctx sdk.Context, _ sdk.Tx, collectedFees sdk.Coins) {} - -//// Handle fee distribution to the validators and delegators -//func (k Keeper) FeeHandler(ctx sdk.Context, collectedFees sdk.Coins) { -//pool := k.GetPool(ctx) -//params := k.GetParams(ctx) - -//// XXX determine -//candidate := NewCandidate(addrs[0], pks[0], Description{}) - -//// calculate the proposer reward -//precommitPower := k.GetTotalPrecommitVotingPower(ctx) -//toProposer := coinsMulRat(collectedFees, (sdk.NewDec(1, 100).Add(sdk.NewDec(4, 100).Mul(precommitPower).Quo(pool.BondedShares)))) -//candidate.ProposerRewardPool = candidate.ProposerRewardPool.Plus(toProposer) - -//toReservePool := coinsMulRat(collectedFees, params.ReservePoolFee) -//pool.FeeReservePool = pool.FeeReservePool.Plus(toReservePool) - -//distributedReward := (collectedFees.Minus(toProposer)).Minus(toReservePool) -//pool.FeePool = pool.FeePool.Plus(distributedReward) -//pool.FeeSumReceived = pool.FeeSumReceived.Plus(distributedReward) -//pool.FeeRecent = distributedReward - -//// lastly update the FeeRecent term -//pool.FeeRecent = collectedFees - -//k.setPool(ctx, pool) -//} - -//func coinsMulRat(coins sdk.Coins, rat sdk.Dec) sdk.Coins { -//var res sdk.Coins -//for _, coin := range coins { -//coinMulAmt := rat.Mul(sdk.NewDec(coin.Amount)).Evaluate() -//coinMul := sdk.Coins{{coin.Denom, coinMulAmt}} -//res = res.Plus(coinMul) -//} -//return res -//} - -////____________________________________________________________________________- - -//// calculate adjustment changes for a candidate at a height -//func CalculateAdjustmentChange(candidate Candidate, pool Pool, denoms []string, height int64) (Candidate, Pool) { - -//heightRat := sdk.NewDec(height) -//lastHeightRat := sdk.NewDec(height - 1) -//candidateFeeCount := candidate.BondedShares.Mul(heightRat) -//poolFeeCount := pool.BondedShares.Mul(heightRat) - -//for i, denom := range denoms { -//poolFeeSumReceived := sdk.NewDec(pool.FeeSumReceived.AmountOf(denom)) -//poolFeeRecent := sdk.NewDec(pool.FeeRecent.AmountOf(denom)) -//// calculate simple and projected pools -//simplePool := candidateFeeCount.Quo(poolFeeCount).Mul(poolFeeSumReceived) -//calc1 := candidate.PrevBondedShares.Mul(lastHeightRat).Quo(pool.PrevBondedShares.Mul(lastHeightRat)).Mul(poolFeeRecent) -//calc2 := candidate.BondedShares.Quo(pool.BondedShares).Mul(poolFeeRecent) -//projectedPool := calc1.Add(calc2) - -//AdjustmentChange := simplePool.Sub(projectedPool) -//candidate.FeeAdjustments[i] = candidate.FeeAdjustments[i].Add(AdjustmentChange) -//pool.FeeAdjustments[i] = pool.FeeAdjustments[i].Add(AdjustmentChange) -//} - -//return candidate, pool -//} diff --git a/x/distribution/types.go b/x/distribution/types.go deleted file mode 100644 index 2234104717..0000000000 --- a/x/distribution/types.go +++ /dev/null @@ -1,107 +0,0 @@ -package stake - -//// GenesisState - all staking state that must be provided at genesis -//type GenesisState struct { -//Pool Pool `json:"pool"` -//Params Params `json:"params"` -//} - -//func NewGenesisState(pool Pool, params Params, candidates []Candidate, bonds []Delegation) GenesisState { -//return GenesisState{ -//Pool: pool, -//Params: params, -//} -//} - -//// get raw genesis raw message for testing -//func DefaultGenesisState() GenesisState { -//return GenesisState{ -//Pool: initialPool(), -//Params: defaultParams(), -//} -//} - -//// fee information for a validator -//type Validator struct { -//Adjustments []sdk.Dec `json:"fee_adjustments"` // XXX Adjustment factors for lazy fee accounting, couples with Params.BondDenoms -//PrevBondedShares sdk.Dec `json:"prev_bonded_shares"` // total shares of a global hold pools -//} - -////_________________________________________________________________________ - -//// Params defines the high level settings for staking -//type Params struct { -//FeeDenoms []string `json:"fee_denoms"` // accepted fee denoms -//ReservePoolFee sdk.Dec `json:"reserve_pool_fee"` // percent of fees which go to reserve pool -//} - -//func (p Params) equal(p2 Params) bool { -//return p.BondDenom == p2.BondDenom && -//p.ReservePoolFee.Equal(p2.ReservePoolFee) -//} - -//func defaultParams() Params { -//return Params{ -//FeeDenoms: []string{"steak"}, -//ReservePoolFee: sdk.NewDec(5, 100), -//} -//} - -////_________________________________________________________________________ - -//// Pool - dynamic parameters of the current state -//type Pool struct { -//FeeReservePool sdk.Coins `json:"fee_reserve_pool"` // XXX reserve pool of collected fees for use by governance -//FeePool sdk.Coins `json:"fee_pool"` // XXX fee pool for all the fee shares which have already been distributed -//FeeSumReceived sdk.Coins `json:"fee_sum_received"` // XXX sum of all fees received, post reserve pool `json:"fee_sum_received"` -//FeeRecent sdk.Coins `json:"fee_recent"` // XXX most recent fee collected -//FeeAdjustments []sdk.Dec `json:"fee_adjustments"` // XXX Adjustment factors for lazy fee accounting, couples with Params.BondDenoms -//PrevBondedShares sdk.Dec `json:"prev_bonded_shares"` // XXX last recorded bonded shares -//} - -//func (p Pool) equal(p2 Pool) bool { -//return p.FeeReservePool.IsEqual(p2.FeeReservePool) && -//p.FeePool.IsEqual(p2.FeePool) && -//p.FeeSumReceived.IsEqual(p2.FeeSumReceived) && -//p.FeeRecent.IsEqual(p2.FeeRecent) && -//sdk.DecsEqual(p.FeeAdjustments, p2.FeeAdjustments) && -//p.PrevBondedShares.Equal(p2.PrevBondedShares) -//} - -//// initial pool for testing -//func initialPool() Pool { -//return Pool{ -//FeeReservePool: sdk.Coins(nil), -//FeePool: sdk.Coins(nil), -//FeeSumReceived: sdk.Coins(nil), -//FeeRecent: sdk.Coins(nil), -//FeeAdjustments: []sdk.Dec{sdk.ZeroDec()}, -//PrevBondedShares: sdk.ZeroDec(), -//} -//} - -////_________________________________________________________________________ - -//// Used in calculation of fee shares, added to a queue for each block where a power change occures -//type PowerChange struct { -//Height int64 `json:"height"` // block height at change -//Power sdk.Dec `json:"power"` // total power at change -//PrevPower sdk.Dec `json:"prev_power"` // total power at previous height-1 -//FeesIn sdk.Coins `json:"fees_in"` // fees in at block height -//PrevFeePool sdk.Coins `json:"prev_fee_pool"` // total fees in at previous block height -//} - -////_________________________________________________________________________ -//// KEY MANAGEMENT - -//var ( -//// Keys for store prefixes -//PowerChangeKey = []byte{0x09} // prefix for power change object -//) - -//// get the key for the accumulated update validators -//func GetPowerChangeKey(height int64) []byte { -//heightBytes := make([]byte, binary.MaxVarintLen64) -//binary.BigEndian.PutUint64(heightBytes, ^uint64(height)) // invert height (older validators first) -//return append(PowerChangeKey, heightBytes...) -//} diff --git a/x/gov/handler.go b/x/gov/handler.go index e8cdf49aa5..00eaf37440 100644 --- a/x/gov/handler.go +++ b/x/gov/handler.go @@ -155,7 +155,7 @@ func EndBlocker(ctx sdk.Context, keeper Keeper) (resTags sdk.Tags) { for _, valAddr := range nonVotingVals { val := keeper.ds.GetValidatorSet().Validator(ctx, valAddr) keeper.ds.GetValidatorSet().Slash(ctx, - val.GetPubKey(), + val.GetConsAddr(), ctx.BlockHeight(), val.GetPower().RoundInt64(), keeper.GetTallyingProcedure(ctx).GovernancePenalty) diff --git a/x/gov/tally.go b/x/gov/tally.go index a756eaf926..55fd81da24 100644 --- a/x/gov/tally.go +++ b/x/gov/tally.go @@ -53,10 +53,10 @@ func tally(ctx sdk.Context, keeper Keeper, proposal Proposal) (passes bool, tall valAddrStr := delegation.GetValidator().String() if val, ok := currValidators[valAddrStr]; ok { - val.Minus = val.Minus.Add(delegation.GetBondShares()) + val.Minus = val.Minus.Add(delegation.GetShares()) currValidators[valAddrStr] = val - delegatorShare := delegation.GetBondShares().Quo(val.DelegatorShares) + delegatorShare := delegation.GetShares().Quo(val.DelegatorShares) votingPower := val.Power.Mul(delegatorShare) results[vote.Option] = results[vote.Option].Add(votingPower) diff --git a/x/gov/tally_test.go b/x/gov/tally_test.go index 71983bf593..d8e3e290ca 100644 --- a/x/gov/tally_test.go +++ b/x/gov/tally_test.go @@ -444,7 +444,7 @@ func TestTallyJailedValidator(t *testing.T) { val2, found := sk.GetValidator(ctx, sdk.ValAddress(addrs[1])) require.True(t, found) - sk.Jail(ctx, val2.ConsPubKey) + sk.Jail(ctx, sdk.ConsAddress(val2.ConsPubKey.Address())) proposal := keeper.NewTextProposal(ctx, "Test", "description", ProposalTypeText) proposalID := proposal.GetProposalID() diff --git a/x/slashing/genesis.go b/x/slashing/genesis.go index 6e2809bfcb..43ae6b0d0a 100644 --- a/x/slashing/genesis.go +++ b/x/slashing/genesis.go @@ -8,7 +8,7 @@ import ( // InitGenesis initializes the keeper's address to pubkey map. func InitGenesis(ctx sdk.Context, keeper Keeper, data types.GenesisState) { for _, validator := range data.Validators { - keeper.addPubkey(ctx, validator.GetPubKey()) + keeper.addPubkey(ctx, validator.GetConsPubKey()) } return } diff --git a/x/slashing/handler.go b/x/slashing/handler.go index c43ed6be61..740166d2af 100644 --- a/x/slashing/handler.go +++ b/x/slashing/handler.go @@ -34,9 +34,9 @@ func handleMsgUnjail(ctx sdk.Context, msg MsgUnjail, k Keeper) sdk.Result { return ErrValidatorNotJailed(k.codespace).Result() } - addr := sdk.ConsAddress(validator.GetPubKey().Address()) + consAddr := sdk.ConsAddress(validator.GetConsPubKey().Address()) - info, found := k.getValidatorSigningInfo(ctx, addr) + info, found := k.getValidatorSigningInfo(ctx, consAddr) if !found { return ErrNoValidatorForAddress(k.codespace).Result() } @@ -49,9 +49,9 @@ func handleMsgUnjail(ctx sdk.Context, msg MsgUnjail, k Keeper) sdk.Result { // update the starting height so the validator can't be immediately jailed // again info.StartHeight = ctx.BlockHeight() - k.setValidatorSigningInfo(ctx, addr, info) + k.setValidatorSigningInfo(ctx, consAddr, info) - k.validatorSet.Unjail(ctx, validator.GetPubKey()) + k.validatorSet.Unjail(ctx, consAddr) tags := sdk.NewTags("action", []byte("unjail"), "validator", []byte(msg.ValidatorAddr.String())) diff --git a/x/slashing/hooks.go b/x/slashing/hooks.go index f5f3cc48c3..92c4cf85f8 100644 --- a/x/slashing/hooks.go +++ b/x/slashing/hooks.go @@ -22,25 +22,26 @@ func (k Keeper) onValidatorBeginUnbonding(ctx sdk.Context, address sdk.ConsAddre k.addOrUpdateValidatorSlashingPeriod(ctx, slashingPeriod) } -// Wrapper struct for sdk.ValidatorHooks -type ValidatorHooks struct { +//_________________________________________________________________________________________ + +// Wrapper struct +type Hooks struct { k Keeper } -// Assert implementation -var _ sdk.ValidatorHooks = ValidatorHooks{} +var _ sdk.ValidatorHooks = Hooks{} -// Return a sdk.ValidatorHooks interface over the wrapper struct -func (k Keeper) ValidatorHooks() sdk.ValidatorHooks { - return ValidatorHooks{k} +// Return the wrapper struct +func (k Keeper) ValidatorHooks() Hooks { + return Hooks{k} } // Implements sdk.ValidatorHooks -func (v ValidatorHooks) OnValidatorBonded(ctx sdk.Context, address sdk.ConsAddress) { - v.k.onValidatorBonded(ctx, address) +func (h Hooks) OnValidatorBonded(ctx sdk.Context, address sdk.ConsAddress) { + h.k.onValidatorBonded(ctx, address) } // Implements sdk.ValidatorHooks -func (v ValidatorHooks) OnValidatorBeginUnbonding(ctx sdk.Context, address sdk.ConsAddress) { - v.k.onValidatorBeginUnbonding(ctx, address) +func (h Hooks) OnValidatorBeginUnbonding(ctx sdk.Context, address sdk.ConsAddress) { + h.k.onValidatorBeginUnbonding(ctx, address) } diff --git a/x/slashing/keeper.go b/x/slashing/keeper.go index 5a008ccec5..e639182e18 100644 --- a/x/slashing/keeper.go +++ b/x/slashing/keeper.go @@ -40,10 +40,10 @@ func (k Keeper) handleDoubleSign(ctx sdk.Context, addr crypto.Address, infractio logger := ctx.Logger().With("module", "x/slashing") time := ctx.BlockHeader().Time age := time.Sub(timestamp) - address := sdk.ConsAddress(addr) + consAddr := sdk.ConsAddress(addr) pubkey, err := k.getPubkey(ctx, addr) if err != nil { - panic(fmt.Sprintf("Validator address %v not found", addr)) + panic(fmt.Sprintf("Validator consensus-address %v not found", consAddr)) } // Double sign too old @@ -59,37 +59,38 @@ func (k Keeper) handleDoubleSign(ctx sdk.Context, addr crypto.Address, infractio // Cap the amount slashed to the penalty for the worst infraction // within the slashing period when this infraction was committed fraction := k.SlashFractionDoubleSign(ctx) - revisedFraction := k.capBySlashingPeriod(ctx, address, fraction, infractionHeight) + revisedFraction := k.capBySlashingPeriod(ctx, consAddr, fraction, infractionHeight) logger.Info(fmt.Sprintf("Fraction slashed capped by slashing period from %v to %v", fraction, revisedFraction)) // Slash validator - k.validatorSet.Slash(ctx, pubkey, infractionHeight, power, revisedFraction) + k.validatorSet.Slash(ctx, consAddr, infractionHeight, power, revisedFraction) // Jail validator - k.validatorSet.Jail(ctx, pubkey) + k.validatorSet.Jail(ctx, consAddr) // Set validator jail duration - signInfo, found := k.getValidatorSigningInfo(ctx, address) + signInfo, found := k.getValidatorSigningInfo(ctx, consAddr) if !found { - panic(fmt.Sprintf("Expected signing info for validator %s but not found", address)) + panic(fmt.Sprintf("Expected signing info for validator %s but not found", consAddr)) } signInfo.JailedUntil = time.Add(k.DoubleSignUnbondDuration(ctx)) - k.setValidatorSigningInfo(ctx, address, signInfo) + k.setValidatorSigningInfo(ctx, consAddr, signInfo) } // handle a validator signature, must be called once per validator per block +// TODO refactor to take in a consensus address, additionally should maybe just take in the pubkey too // nolint gocyclo func (k Keeper) handleValidatorSignature(ctx sdk.Context, addr crypto.Address, power int64, signed bool) { logger := ctx.Logger().With("module", "x/slashing") height := ctx.BlockHeight() - address := sdk.ConsAddress(addr) + consAddr := sdk.ConsAddress(addr) pubkey, err := k.getPubkey(ctx, addr) if err != nil { - panic(fmt.Sprintf("Validator address %v not found", addr)) + panic(fmt.Sprintf("Validator consensus-address %v not found", consAddr)) } // Local index, so counts blocks validator *should* have signed // Will use the 0-value default signing info if not present, except for start height - signInfo, found := k.getValidatorSigningInfo(ctx, address) + signInfo, found := k.getValidatorSigningInfo(ctx, consAddr) if !found { // If this validator has never been seen before, construct a new SigningInfo with the correct start height signInfo = NewValidatorSigningInfo(height, 0, time.Unix(0, 0), 0) @@ -100,16 +101,16 @@ func (k Keeper) handleValidatorSignature(ctx sdk.Context, addr crypto.Address, p // Update signed block bit array & counter // This counter just tracks the sum of the bit array // That way we avoid needing to read/write the whole array each time - previous := k.getValidatorSigningBitArray(ctx, address, index) + previous := k.getValidatorSigningBitArray(ctx, consAddr, index) if previous == signed { // Array value at this index has not changed, no need to update counter } else if previous && !signed { // Array value has changed from signed to unsigned, decrement counter - k.setValidatorSigningBitArray(ctx, address, index, false) + k.setValidatorSigningBitArray(ctx, consAddr, index, false) signInfo.SignedBlocksCounter-- } else if !previous && signed { // Array value has changed from unsigned to signed, increment counter - k.setValidatorSigningBitArray(ctx, address, index, true) + k.setValidatorSigningBitArray(ctx, consAddr, index, true) signInfo.SignedBlocksCounter++ } @@ -118,13 +119,13 @@ func (k Keeper) handleValidatorSignature(ctx sdk.Context, addr crypto.Address, p } minHeight := signInfo.StartHeight + k.SignedBlocksWindow(ctx) if height > minHeight && signInfo.SignedBlocksCounter < k.MinSignedPerWindow(ctx) { - validator := k.validatorSet.ValidatorByPubKey(ctx, pubkey) + validator := k.validatorSet.ValidatorByConsAddr(ctx, consAddr) if validator != nil && !validator.GetJailed() { // Downtime confirmed: slash and jail the validator logger.Info(fmt.Sprintf("Validator %s past min height of %d and below signed blocks threshold of %d", pubkey.Address(), minHeight, k.MinSignedPerWindow(ctx))) - k.validatorSet.Slash(ctx, pubkey, height, power, k.SlashFractionDowntime(ctx)) - k.validatorSet.Jail(ctx, pubkey) + k.validatorSet.Slash(ctx, consAddr, height, power, k.SlashFractionDowntime(ctx)) + k.validatorSet.Jail(ctx, consAddr) signInfo.JailedUntil = ctx.BlockHeader().Time.Add(k.DowntimeUnbondDuration(ctx)) } else { // Validator was (a) not found or (b) already jailed, don't slash @@ -134,7 +135,7 @@ func (k Keeper) handleValidatorSignature(ctx sdk.Context, addr crypto.Address, p } // Set the updated signing info - k.setValidatorSigningInfo(ctx, address, signInfo) + k.setValidatorSigningInfo(ctx, consAddr, signInfo) } // AddValidators adds the validators to the keepers validator addr to pubkey mapping. diff --git a/x/slashing/keeper_test.go b/x/slashing/keeper_test.go index ff50a594ea..d4b1af3d51 100644 --- a/x/slashing/keeper_test.go +++ b/x/slashing/keeper_test.go @@ -18,8 +18,11 @@ func init() { defaultDoubleSignUnbondDuration = 60 * 60 } +// ______________________________________________________________ + // Test that a validator is slashed correctly // when we discover evidence of infraction +// TODO fix this test to not be using the same pubkey/address for signing and operating, it's confusing func TestHandleDoubleSign(t *testing.T) { // initial setup @@ -43,7 +46,7 @@ func TestHandleDoubleSign(t *testing.T) { // should be jailed require.True(t, sk.Validator(ctx, addr).GetJailed()) // unjail to measure power - sk.Unjail(ctx, val) + sk.Unjail(ctx, sdk.ConsAddress(addr)) // TODO distinguish cons address // power should be reduced require.Equal( t, sdk.NewDecFromInt(amt).Mul(sdk.NewDec(19).Quo(sdk.NewDec(20))), @@ -61,14 +64,16 @@ func TestHandleDoubleSign(t *testing.T) { // Test that the amount a validator is slashed for multiple double signs // is correctly capped by the slashing period in which they were committed +// TODO properly distinguish between consensus and operator address is variable names func TestSlashingPeriodCap(t *testing.T) { // initial setup ctx, ck, sk, _, keeper := createTestInput(t) sk = sk.WithValidatorHooks(keeper.ValidatorHooks()) amtInt := int64(100) - addr, val, amt := addrs[0], pks[0], sdk.NewInt(amtInt) - got := stake.NewHandler(sk)(ctx, newTestMsgCreateValidator(addr, val, amt)) + addr, amt := addrs[0], sdk.NewInt(amtInt) + valConsPubKey, valConsAddr := pks[0], sdk.ConsAddress(pks[0].Address()) + got := stake.NewHandler(sk)(ctx, newTestMsgCreateValidator(addr, valConsPubKey, amt)) require.True(t, got.IsOK()) validatorUpdates := stake.EndBlocker(ctx, sk) keeper.AddValidators(ctx, validatorUpdates) @@ -76,39 +81,39 @@ func TestSlashingPeriodCap(t *testing.T) { require.True(t, sdk.NewDecFromInt(amt).Equal(sk.Validator(ctx, addr).GetPower())) // handle a signature to set signing info - keeper.handleValidatorSignature(ctx, val.Address(), amtInt, true) + keeper.handleValidatorSignature(ctx, valConsPubKey.Address(), amtInt, true) // double sign less than max age - keeper.handleDoubleSign(ctx, val.Address(), 0, time.Unix(0, 0), amtInt) + keeper.handleDoubleSign(ctx, valConsPubKey.Address(), 0, time.Unix(0, 0), amtInt) // should be jailed require.True(t, sk.Validator(ctx, addr).GetJailed()) // update block height ctx = ctx.WithBlockHeight(int64(1)) // unjail to measure power - sk.Unjail(ctx, val) + sk.Unjail(ctx, valConsAddr) // power should be reduced expectedPower := sdk.NewDecFromInt(amt).Mul(sdk.NewDec(19).Quo(sdk.NewDec(20))) require.Equal(t, expectedPower, sk.Validator(ctx, addr).GetPower()) // double sign again, same slashing period - keeper.handleDoubleSign(ctx, val.Address(), 0, time.Unix(0, 0), amtInt) + keeper.handleDoubleSign(ctx, valConsPubKey.Address(), 0, time.Unix(0, 0), amtInt) // should be jailed require.True(t, sk.Validator(ctx, addr).GetJailed()) // update block height ctx = ctx.WithBlockHeight(int64(2)) // unjail to measure power - sk.Unjail(ctx, val) + sk.Unjail(ctx, valConsAddr) // power should be equal, no more should have been slashed expectedPower = sdk.NewDecFromInt(amt).Mul(sdk.NewDec(19).Quo(sdk.NewDec(20))) require.Equal(t, expectedPower, sk.Validator(ctx, addr).GetPower()) // double sign again, new slashing period - keeper.handleDoubleSign(ctx, val.Address(), 2, time.Unix(0, 0), amtInt) + keeper.handleDoubleSign(ctx, valConsPubKey.Address(), 2, time.Unix(0, 0), amtInt) // should be jailed require.True(t, sk.Validator(ctx, addr).GetJailed()) // unjail to measure power - sk.Unjail(ctx, val) + sk.Unjail(ctx, valConsAddr) // power should be reduced expectedPower = sdk.NewDecFromInt(amt).Mul(sdk.NewDec(18).Quo(sdk.NewDec(20))) require.Equal(t, expectedPower, sk.Validator(ctx, addr).GetPower()) @@ -162,7 +167,7 @@ func TestHandleAbsentValidator(t *testing.T) { require.Equal(t, keeper.SignedBlocksWindow(ctx)-keeper.MinSignedPerWindow(ctx), info.SignedBlocksCounter) // validator should be bonded still - validator, _ := sk.GetValidatorByPubKey(ctx, val) + validator, _ := sk.GetValidatorByConsAddr(ctx, sdk.GetConsAddress(val)) require.Equal(t, sdk.Bonded, validator.GetStatus()) pool := sk.GetPool(ctx) require.Equal(t, amtInt, pool.BondedTokens.RoundInt64()) @@ -176,7 +181,7 @@ func TestHandleAbsentValidator(t *testing.T) { require.Equal(t, keeper.SignedBlocksWindow(ctx)-keeper.MinSignedPerWindow(ctx)-1, info.SignedBlocksCounter) // validator should have been jailed - validator, _ = sk.GetValidatorByPubKey(ctx, val) + validator, _ = sk.GetValidatorByConsAddr(ctx, sdk.GetConsAddress(val)) require.Equal(t, sdk.Unbonding, validator.GetStatus()) // unrevocation should fail prior to jail expiration @@ -189,7 +194,7 @@ func TestHandleAbsentValidator(t *testing.T) { require.True(t, got.IsOK()) // validator should be rebonded now - validator, _ = sk.GetValidatorByPubKey(ctx, val) + validator, _ = sk.GetValidatorByConsAddr(ctx, sdk.GetConsAddress(val)) require.Equal(t, sdk.Bonded, validator.GetStatus()) // validator should have been slashed @@ -207,7 +212,7 @@ func TestHandleAbsentValidator(t *testing.T) { height++ ctx = ctx.WithBlockHeight(height) keeper.handleValidatorSignature(ctx, val.Address(), amtInt, false) - validator, _ = sk.GetValidatorByPubKey(ctx, val) + validator, _ = sk.GetValidatorByConsAddr(ctx, sdk.GetConsAddress(val)) require.Equal(t, sdk.Bonded, validator.GetStatus()) // 500 signed blocks @@ -223,7 +228,7 @@ func TestHandleAbsentValidator(t *testing.T) { ctx = ctx.WithBlockHeight(height) keeper.handleValidatorSignature(ctx, val.Address(), amtInt, false) } - validator, _ = sk.GetValidatorByPubKey(ctx, val) + validator, _ = sk.GetValidatorByConsAddr(ctx, sdk.GetConsAddress(val)) require.Equal(t, sdk.Unbonding, validator.GetStatus()) } @@ -258,7 +263,7 @@ func TestHandleNewValidator(t *testing.T) { require.Equal(t, time.Unix(0, 0).UTC(), info.JailedUntil) // validator should be bonded still, should not have been jailed or slashed - validator, _ := sk.GetValidatorByPubKey(ctx, val) + validator, _ := sk.GetValidatorByConsAddr(ctx, sdk.GetConsAddress(val)) require.Equal(t, sdk.Bonded, validator.GetStatus()) pool := sk.GetPool(ctx) require.Equal(t, int64(100), pool.BondedTokens.RoundInt64()) @@ -292,7 +297,7 @@ func TestHandleAlreadyJailed(t *testing.T) { } // validator should have been jailed and slashed - validator, _ := sk.GetValidatorByPubKey(ctx, val) + validator, _ := sk.GetValidatorByConsAddr(ctx, sdk.GetConsAddress(val)) require.Equal(t, sdk.Unbonding, validator.GetStatus()) // validator should have been slashed @@ -303,7 +308,7 @@ func TestHandleAlreadyJailed(t *testing.T) { keeper.handleValidatorSignature(ctx, val.Address(), amtInt, false) // validator should not have been slashed twice - validator, _ = sk.GetValidatorByPubKey(ctx, val) + validator, _ = sk.GetValidatorByConsAddr(ctx, sdk.GetConsAddress(val)) require.Equal(t, amtInt-1, validator.GetTokens().RoundInt64()) } diff --git a/x/slashing/tick_test.go b/x/slashing/tick_test.go index 81003a968a..8225c96347 100644 --- a/x/slashing/tick_test.go +++ b/x/slashing/tick_test.go @@ -78,7 +78,7 @@ func TestBeginBlocker(t *testing.T) { } // validator should be jailed - validator, found := sk.GetValidatorByPubKey(ctx, pk) + validator, found := sk.GetValidatorByConsAddr(ctx, sdk.GetConsAddress(pk)) require.True(t, found) require.Equal(t, sdk.Unbonding, validator.GetStatus()) } diff --git a/x/stake/client/cli/flags.go b/x/stake/client/cli/flags.go index 1228bec2a8..bec76298f3 100644 --- a/x/stake/client/cli/flags.go +++ b/x/stake/client/cli/flags.go @@ -58,8 +58,8 @@ func init() { fsDescriptionEdit.String(FlagIdentity, types.DoNotModifyDesc, "optional identity signature (ex. UPort or Keybase)") fsDescriptionEdit.String(FlagWebsite, types.DoNotModifyDesc, "optional website") fsDescriptionEdit.String(FlagDetails, types.DoNotModifyDesc, "optional details") - fsValidator.String(FlagAddressValidator, "", "hex address of the validator") - fsDelegator.String(FlagAddressDelegator, "", "hex address of the delegator") - fsRedelegation.String(FlagAddressValidatorSrc, "", "hex address of the source validator") - fsRedelegation.String(FlagAddressValidatorDst, "", "hex address of the destination validator") + fsValidator.String(FlagAddressValidator, "", "bech address of the validator") + fsDelegator.String(FlagAddressDelegator, "", "bech address of the delegator") + fsRedelegation.String(FlagAddressValidatorSrc, "", "bech address of the source validator") + fsRedelegation.String(FlagAddressValidatorDst, "", "bech address of the destination validator") } diff --git a/x/stake/genesis.go b/x/stake/genesis.go index 7a004bccd2..58b7ed1b4f 100644 --- a/x/stake/genesis.go +++ b/x/stake/genesis.go @@ -32,7 +32,7 @@ func InitGenesis(ctx sdk.Context, keeper Keeper, data types.GenesisState) (res [ } // Manually set indexes for the first time - keeper.SetValidatorByPubKeyIndex(ctx, validator) + keeper.SetValidatorByConsAddr(ctx, validator) keeper.SetValidatorByPowerIndex(ctx, validator, data.Pool) if validator.Status == sdk.Bonded { @@ -75,7 +75,7 @@ func WriteGenesis(ctx sdk.Context, keeper Keeper) types.GenesisState { func WriteValidators(ctx sdk.Context, keeper Keeper) (vals []tmtypes.GenesisValidator) { keeper.IterateValidatorsBonded(ctx, func(_ int64, validator sdk.Validator) (stop bool) { vals = append(vals, tmtypes.GenesisValidator{ - PubKey: validator.GetPubKey(), + PubKey: validator.GetConsPubKey(), Power: validator.GetPower().RoundInt64(), Name: validator.GetMoniker(), }) diff --git a/x/stake/handler.go b/x/stake/handler.go index aaf372af2a..0524b2c117 100644 --- a/x/stake/handler.go +++ b/x/stake/handler.go @@ -68,7 +68,7 @@ func handleMsgCreateValidator(ctx sdk.Context, msg types.MsgCreateValidator, k k return ErrValidatorOwnerExists(k.Codespace()).Result() } - _, found = k.GetValidatorByPubKey(ctx, msg.PubKey) + _, found = k.GetValidatorByConsAddr(ctx, sdk.GetConsAddress(msg.PubKey)) if found { return ErrValidatorPubKeyExists(k.Codespace()).Result() } @@ -89,7 +89,7 @@ func handleMsgCreateValidator(ctx sdk.Context, msg types.MsgCreateValidator, k k } k.SetValidator(ctx, validator) - k.SetValidatorByPubKeyIndex(ctx, validator) + k.SetValidatorByConsAddr(ctx, validator) // move coins from the msg.Address account to a (self-delegation) delegator account // the validator account and global shares are updated within here diff --git a/x/stake/handler_test.go b/x/stake/handler_test.go index 845844fe28..5780bf5431 100644 --- a/x/stake/handler_test.go +++ b/x/stake/handler_test.go @@ -84,8 +84,9 @@ func TestValidatorByPowerIndex(t *testing.T) { require.True(t, got.IsOK(), "expected create-validator to be ok, got %v", got) // slash and jail the first validator - keeper.Slash(ctx, keep.PKs[0], 0, initBond, sdk.NewDecWithPrec(5, 1)) - keeper.Jail(ctx, keep.PKs[0]) + consAddr0 := sdk.ConsAddress(keep.PKs[0].Address()) + keeper.Slash(ctx, consAddr0, 0, initBond, sdk.NewDecWithPrec(5, 1)) + keeper.Jail(ctx, consAddr0) validator, found = keeper.GetValidator(ctx, validatorAddr) require.True(t, found) require.Equal(t, sdk.Unbonding, validator.Status) // ensure is unbonding @@ -201,11 +202,12 @@ func TestLegacyValidatorDelegations(t *testing.T) { setInstantUnbondPeriod(keeper, ctx) bondAmount := int64(10) - valAddr, valPubKey := sdk.ValAddress(keep.Addrs[0]), keep.PKs[0] + valAddr := sdk.ValAddress(keep.Addrs[0]) + valConsPubKey, valConsAddr := keep.PKs[0], sdk.ConsAddress(keep.PKs[0].Address()) delAddr := keep.Addrs[1] // create validator - msgCreateVal := newTestMsgCreateValidator(valAddr, valPubKey, bondAmount) + msgCreateVal := newTestMsgCreateValidator(valAddr, valConsPubKey, bondAmount) got := handleMsgCreateValidator(ctx, msgCreateVal, keeper) require.True(t, got.IsOK(), "expected create validator msg to be ok, got %v", got) @@ -267,7 +269,7 @@ func TestLegacyValidatorDelegations(t *testing.T) { require.Equal(t, bondAmount*2, validator.Tokens.RoundInt64()) // unjail the validator now that is has non-zero self-delegated shares - keeper.Unjail(ctx, valPubKey) + keeper.Unjail(ctx, valConsAddr) // verify the validator can now accept delegations msgDelegate = newTestMsgDelegate(delAddr, valAddr, bondAmount) @@ -914,6 +916,7 @@ func TestCliffValidator(t *testing.T) { func TestBondUnbondRedelegateSlashTwice(t *testing.T) { ctx, _, keeper := keep.CreateTestInput(t, false, 1000) valA, valB, del := sdk.ValAddress(keep.Addrs[0]), sdk.ValAddress(keep.Addrs[1]), keep.Addrs[2] + consAddr0 := sdk.ConsAddress(keep.PKs[0].Address()) msgCreateValidator := newTestMsgCreateValidator(valA, keep.PKs[0], 10) got := handleMsgCreateValidator(ctx, msgCreateValidator, keeper) @@ -947,7 +950,7 @@ func TestBondUnbondRedelegateSlashTwice(t *testing.T) { require.Equal(t, sdk.NewDec(6), delegation.Shares) // slash the validator by half - keeper.Slash(ctx, keep.PKs[0], 0, 20, sdk.NewDecWithPrec(5, 1)) + keeper.Slash(ctx, consAddr0, 0, 20, sdk.NewDecWithPrec(5, 1)) // unbonding delegation should have been slashed by half unbonding, found := keeper.GetUnbondingDelegation(ctx, del, valA) @@ -971,7 +974,7 @@ func TestBondUnbondRedelegateSlashTwice(t *testing.T) { // slash the validator for an infraction committed after the unbonding and redelegation begin ctx = ctx.WithBlockHeight(3) - keeper.Slash(ctx, keep.PKs[0], 2, 10, sdk.NewDecWithPrec(5, 1)) + keeper.Slash(ctx, consAddr0, 2, 10, sdk.NewDecWithPrec(5, 1)) // unbonding delegation should be unchanged unbonding, found = keeper.GetUnbondingDelegation(ctx, del, valA) diff --git a/x/stake/keeper/delegation.go b/x/stake/keeper/delegation.go index ef09a307e1..6efa4c8ed3 100644 --- a/x/stake/keeper/delegation.go +++ b/x/stake/keeper/delegation.go @@ -66,6 +66,7 @@ func (k Keeper) SetDelegation(ctx sdk.Context, delegation types.Delegation) { // remove a delegation from store func (k Keeper) RemoveDelegation(ctx sdk.Context, delegation types.Delegation) { + store := ctx.KVStore(k.storeKey) store.Delete(GetDelegationKey(delegation.DelegatorAddr, delegation.ValidatorAddr)) } diff --git a/x/stake/keeper/keeper.go b/x/stake/keeper/keeper.go index a3754f7504..0f700f9ab8 100644 --- a/x/stake/keeper/keeper.go +++ b/x/stake/keeper/keeper.go @@ -10,11 +10,11 @@ import ( // keeper of the stake store type Keeper struct { - storeKey sdk.StoreKey - storeTKey sdk.StoreKey - cdc *codec.Codec - bankKeeper bank.Keeper - validatorHooks sdk.ValidatorHooks + storeKey sdk.StoreKey + storeTKey sdk.StoreKey + cdc *codec.Codec + bankKeeper bank.Keeper + hooks sdk.ValidatorHooks // codespace codespace sdk.CodespaceType @@ -22,22 +22,22 @@ type Keeper struct { func NewKeeper(cdc *codec.Codec, key, tkey sdk.StoreKey, ck bank.Keeper, codespace sdk.CodespaceType) Keeper { keeper := Keeper{ - storeKey: key, - storeTKey: tkey, - cdc: cdc, - bankKeeper: ck, - validatorHooks: nil, - codespace: codespace, + storeKey: key, + storeTKey: tkey, + cdc: cdc, + bankKeeper: ck, + hooks: nil, + codespace: codespace, } return keeper } // Set the validator hooks -func (k Keeper) WithValidatorHooks(v sdk.ValidatorHooks) Keeper { - if k.validatorHooks != nil { +func (k Keeper) WithValidatorHooks(sh sdk.ValidatorHooks) Keeper { + if k.hooks != nil { panic("cannot set validator hooks twice") } - k.validatorHooks = v + k.hooks = sh return k } diff --git a/x/stake/keeper/key.go b/x/stake/keeper/key.go index c445e25524..91e6a69708 100644 --- a/x/stake/keeper/key.go +++ b/x/stake/keeper/key.go @@ -3,8 +3,6 @@ package keeper import ( "encoding/binary" - "github.com/tendermint/tendermint/crypto" - sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/x/stake/types" ) @@ -17,7 +15,7 @@ var ( ParamKey = []byte{0x00} // key for parameters relating to staking PoolKey = []byte{0x01} // key for the staking pools ValidatorsKey = []byte{0x02} // prefix for each key to a validator - ValidatorsByPubKeyIndexKey = []byte{0x03} // prefix for each key to a validator index, by pubkey + ValidatorsByConsAddrKey = []byte{0x03} // prefix for each key to a validator index, by pubkey ValidatorsBondedIndexKey = []byte{0x04} // prefix for each key to a validator index, for bonded validators ValidatorsByPowerIndexKey = []byte{0x05} // prefix for each key to a validator index, sorted by power ValidatorCliffIndexKey = []byte{0x06} // key for the validator index of the cliff validator @@ -44,8 +42,8 @@ func GetValidatorKey(operatorAddr sdk.ValAddress) []byte { // gets the key for the validator with pubkey // VALUE: validator operator address ([]byte) -func GetValidatorByPubKeyIndexKey(pubkey crypto.PubKey) []byte { - return append(ValidatorsByPubKeyIndexKey, pubkey.Bytes()...) +func GetValidatorByConsAddrKey(addr sdk.ConsAddress) []byte { + return append(ValidatorsByConsAddrKey, addr.Bytes()...) } // gets the key for the current validator group diff --git a/x/stake/keeper/sdk_types.go b/x/stake/keeper/sdk_types.go index e4c7a1c193..d702e845da 100644 --- a/x/stake/keeper/sdk_types.go +++ b/x/stake/keeper/sdk_types.go @@ -3,8 +3,6 @@ package keeper import ( "fmt" - "github.com/tendermint/tendermint/crypto" - sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/x/stake/types" ) @@ -60,8 +58,8 @@ func (k Keeper) Validator(ctx sdk.Context, address sdk.ValAddress) sdk.Validator } // get the sdk.validator for a particular pubkey -func (k Keeper) ValidatorByPubKey(ctx sdk.Context, pubkey crypto.PubKey) sdk.Validator { - val, found := k.GetValidatorByPubKey(ctx, pubkey) +func (k Keeper) ValidatorByConsAddr(ctx sdk.Context, addr sdk.ConsAddress) sdk.Validator { + val, found := k.GetValidatorByConsAddr(ctx, addr) if !found { return nil } @@ -95,15 +93,16 @@ func (k Keeper) Delegation(ctx sdk.Context, addrDel sdk.AccAddress, addrVal sdk. return bond } -// iterate through the active validator set and perform the provided function -func (k Keeper) IterateDelegations(ctx sdk.Context, delAddr sdk.AccAddress, fn func(index int64, delegation sdk.Delegation) (stop bool)) { +// iterate through all of the delegations from a delegator +func (k Keeper) IterateDelegations(ctx sdk.Context, delAddr sdk.AccAddress, + fn func(index int64, del sdk.Delegation) (stop bool)) { + store := ctx.KVStore(k.storeKey) - key := GetDelegationsKey(delAddr) - iterator := sdk.KVStorePrefixIterator(store, key) - i := int64(0) - for ; iterator.Valid(); iterator.Next() { - delegation := types.MustUnmarshalDelegation(k.cdc, iterator.Key(), iterator.Value()) - stop := fn(i, delegation) // XXX is this safe will the fields be able to get written to? + delegatorPrefixKey := GetDelegationsKey(delAddr) + iterator := sdk.KVStorePrefixIterator(store, delegatorPrefixKey) //smallest to largest + for i := int64(0); iterator.Valid(); iterator.Next() { + del := types.MustUnmarshalDelegation(k.cdc, iterator.Key(), iterator.Value()) + stop := fn(i, del) if stop { break } diff --git a/x/stake/keeper/slash.go b/x/stake/keeper/slash.go index 52dbd21c7e..29adec06a7 100644 --- a/x/stake/keeper/slash.go +++ b/x/stake/keeper/slash.go @@ -5,7 +5,6 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" types "github.com/cosmos/cosmos-sdk/x/stake/types" - "github.com/tendermint/tendermint/crypto" ) // Slash a validator for an infraction committed at a known height @@ -24,7 +23,7 @@ import ( // not at a height in the future // // nolint: gocyclo -func (k Keeper) Slash(ctx sdk.Context, pubkey crypto.PubKey, infractionHeight int64, power int64, slashFactor sdk.Dec) { +func (k Keeper) Slash(ctx sdk.Context, consAddr sdk.ConsAddress, infractionHeight int64, power int64, slashFactor sdk.Dec) { logger := ctx.Logger().With("module", "x/stake") if slashFactor.LT(sdk.ZeroDec()) { @@ -36,7 +35,7 @@ func (k Keeper) Slash(ctx sdk.Context, pubkey crypto.PubKey, infractionHeight in // ref https://github.com/cosmos/cosmos-sdk/issues/1348 // ref https://github.com/cosmos/cosmos-sdk/issues/1471 - validator, found := k.GetValidatorByPubKey(ctx, pubkey) + validator, found := k.GetValidatorByConsAddr(ctx, consAddr) if !found { // If not found, the validator must have been overslashed and removed - so we don't need to do anything // NOTE: Correctness dependent on invariant that unbonding delegations / redelegations must also have been completely @@ -44,7 +43,7 @@ func (k Keeper) Slash(ctx sdk.Context, pubkey crypto.PubKey, infractionHeight in // Log the slash attempt for future reference (maybe we should tag it too) logger.Error(fmt.Sprintf( "WARNING: Ignored attempt to slash a nonexistent validator with address %s, we recommend you investigate immediately", - pubkey.Address())) + consAddr)) return } @@ -125,36 +124,28 @@ func (k Keeper) Slash(ctx sdk.Context, pubkey crypto.PubKey, infractionHeight in } // jail a validator -func (k Keeper) Jail(ctx sdk.Context, pubkey crypto.PubKey) { - k.setJailed(ctx, pubkey, true) - validatorAddr, err := sdk.ValAddressFromHex(pubkey.Address().String()) - if err != nil { - panic(err.Error()) - } +func (k Keeper) Jail(ctx sdk.Context, consAddr sdk.ConsAddress) { + k.setJailed(ctx, consAddr, true) logger := ctx.Logger().With("module", "x/stake") - logger.Info(fmt.Sprintf("validator %s jailed", validatorAddr)) + logger.Info(fmt.Sprintf("validator %s jailed", consAddr)) // TODO Return event(s), blocked on https://github.com/tendermint/tendermint/pull/1803 return } // unjail a validator -func (k Keeper) Unjail(ctx sdk.Context, pubkey crypto.PubKey) { - k.setJailed(ctx, pubkey, false) - validatorAddr, err := sdk.ValAddressFromHex(pubkey.Address().String()) - if err != nil { - panic(err.Error()) - } +func (k Keeper) Unjail(ctx sdk.Context, consAddr sdk.ConsAddress) { + k.setJailed(ctx, consAddr, false) logger := ctx.Logger().With("module", "x/stake") - logger.Info(fmt.Sprintf("validator %s unjailed", validatorAddr)) + logger.Info(fmt.Sprintf("validator %s unjailed", consAddr)) // TODO Return event(s), blocked on https://github.com/tendermint/tendermint/pull/1803 return } // set the jailed flag on a validator -func (k Keeper) setJailed(ctx sdk.Context, pubkey crypto.PubKey, isJailed bool) { - validator, found := k.GetValidatorByPubKey(ctx, pubkey) +func (k Keeper) setJailed(ctx sdk.Context, consAddr sdk.ConsAddress, isJailed bool) { + validator, found := k.GetValidatorByConsAddr(ctx, consAddr) if !found { - panic(fmt.Errorf("validator with pubkey %s not found, cannot set jailed to %v", pubkey, isJailed)) + panic(fmt.Errorf("validator with consensus-Address %s not found, cannot set jailed to %v", consAddr, isJailed)) } validator.Jailed = isJailed k.UpdateValidator(ctx, validator) // update validator, possibly unbonding or bonding it diff --git a/x/stake/keeper/slash_test.go b/x/stake/keeper/slash_test.go index 65bac2d808..0a6cccac2a 100644 --- a/x/stake/keeper/slash_test.go +++ b/x/stake/keeper/slash_test.go @@ -14,6 +14,7 @@ import ( // TODO integrate with test_common.go helper (CreateTestInput) // setup helper function - creates two validators func setupHelper(t *testing.T, amt int64) (sdk.Context, Keeper, types.Params) { + // setup ctx, _, keeper := CreateTestInput(t, false, amt) params := keeper.GetParams(ctx) @@ -27,7 +28,7 @@ func setupHelper(t *testing.T, amt int64) (sdk.Context, Keeper, types.Params) { validator, pool, _ = validator.AddTokensFromDel(pool, sdk.NewInt(amt)) keeper.SetPool(ctx, pool) validator = keeper.UpdateValidator(ctx, validator) - keeper.SetValidatorByPubKeyIndex(ctx, validator) + keeper.SetValidatorByConsAddr(ctx, validator) } pool = keeper.GetPool(ctx) @@ -42,7 +43,7 @@ func TestRevocation(t *testing.T) { // setup ctx, keeper, _ := setupHelper(t, 10) addr := addrVals[0] - pk := PKs[0] + consAddr := sdk.ConsAddress(PKs[0].Address()) // initial state val, found := keeper.GetValidator(ctx, addr) @@ -50,13 +51,13 @@ func TestRevocation(t *testing.T) { require.False(t, val.GetJailed()) // test jail - keeper.Jail(ctx, pk) + keeper.Jail(ctx, consAddr) val, found = keeper.GetValidator(ctx, addr) require.True(t, found) require.True(t, val.GetJailed()) // test unjail - keeper.Unjail(ctx, pk) + keeper.Unjail(ctx, consAddr) val, found = keeper.GetValidator(ctx, addr) require.True(t, found) require.False(t, val.GetJailed()) @@ -179,24 +180,24 @@ func TestSlashRedelegation(t *testing.T) { // tests Slash at a future height (must panic) func TestSlashAtFutureHeight(t *testing.T) { ctx, keeper, _ := setupHelper(t, 10) - pk := PKs[0] + consAddr := sdk.ConsAddress(PKs[0].Address()) fraction := sdk.NewDecWithPrec(5, 1) - require.Panics(t, func() { keeper.Slash(ctx, pk, 1, 10, fraction) }) + require.Panics(t, func() { keeper.Slash(ctx, consAddr, 1, 10, fraction) }) } // tests Slash at the current height func TestSlashValidatorAtCurrentHeight(t *testing.T) { ctx, keeper, _ := setupHelper(t, 10) - pk := PKs[0] + consAddr := sdk.ConsAddress(PKs[0].Address()) fraction := sdk.NewDecWithPrec(5, 1) oldPool := keeper.GetPool(ctx) - validator, found := keeper.GetValidatorByPubKey(ctx, pk) + validator, found := keeper.GetValidatorByConsAddr(ctx, consAddr) require.True(t, found) - keeper.Slash(ctx, pk, ctx.BlockHeight(), 10, fraction) + keeper.Slash(ctx, consAddr, ctx.BlockHeight(), 10, fraction) // read updated state - validator, found = keeper.GetValidatorByPubKey(ctx, pk) + validator, found = keeper.GetValidatorByConsAddr(ctx, consAddr) require.True(t, found) newPool := keeper.GetPool(ctx) @@ -209,7 +210,7 @@ func TestSlashValidatorAtCurrentHeight(t *testing.T) { // tests Slash at a previous height with an unbonding delegation func TestSlashWithUnbondingDelegation(t *testing.T) { ctx, keeper, params := setupHelper(t, 10) - pk := PKs[0] + consAddr := sdk.ConsAddress(PKs[0].Address()) fraction := sdk.NewDecWithPrec(5, 1) // set an unbonding delegation @@ -227,9 +228,9 @@ func TestSlashWithUnbondingDelegation(t *testing.T) { // slash validator for the first time ctx = ctx.WithBlockHeight(12) oldPool := keeper.GetPool(ctx) - validator, found := keeper.GetValidatorByPubKey(ctx, pk) + validator, found := keeper.GetValidatorByConsAddr(ctx, consAddr) require.True(t, found) - keeper.Slash(ctx, pk, 10, 10, fraction) + keeper.Slash(ctx, consAddr, 10, 10, fraction) // read updating unbonding delegation ubd, found = keeper.GetUnbondingDelegation(ctx, addrDels[0], addrVals[0]) @@ -241,7 +242,7 @@ func TestSlashWithUnbondingDelegation(t *testing.T) { // bonded tokens burned require.Equal(t, int64(3), oldPool.BondedTokens.Sub(newPool.BondedTokens).RoundInt64()) // read updated validator - validator, found = keeper.GetValidatorByPubKey(ctx, pk) + validator, found = keeper.GetValidatorByConsAddr(ctx, consAddr) require.True(t, found) // power decreased by 3 - 6 stake originally bonded at the time of infraction // was still bonded at the time of discovery and was slashed by half, 4 stake @@ -251,7 +252,7 @@ func TestSlashWithUnbondingDelegation(t *testing.T) { // slash validator again ctx = ctx.WithBlockHeight(13) - keeper.Slash(ctx, pk, 9, 10, fraction) + keeper.Slash(ctx, consAddr, 9, 10, fraction) ubd, found = keeper.GetUnbondingDelegation(ctx, addrDels[0], addrVals[0]) require.True(t, found) // balance decreased again @@ -261,7 +262,7 @@ func TestSlashWithUnbondingDelegation(t *testing.T) { // bonded tokens burned again require.Equal(t, int64(6), oldPool.BondedTokens.Sub(newPool.BondedTokens).RoundInt64()) // read updated validator - validator, found = keeper.GetValidatorByPubKey(ctx, pk) + validator, found = keeper.GetValidatorByConsAddr(ctx, consAddr) require.True(t, found) // power decreased by 3 again require.Equal(t, sdk.NewDec(4), validator.GetPower()) @@ -271,7 +272,7 @@ func TestSlashWithUnbondingDelegation(t *testing.T) { // on the unbonding delegation, but it will slash stake bonded since the infraction // this may not be the desirable behaviour, ref https://github.com/cosmos/cosmos-sdk/issues/1440 ctx = ctx.WithBlockHeight(13) - keeper.Slash(ctx, pk, 9, 10, fraction) + keeper.Slash(ctx, consAddr, 9, 10, fraction) ubd, found = keeper.GetUnbondingDelegation(ctx, addrDels[0], addrVals[0]) require.True(t, found) // balance unchanged @@ -281,7 +282,7 @@ func TestSlashWithUnbondingDelegation(t *testing.T) { // bonded tokens burned again require.Equal(t, int64(9), oldPool.BondedTokens.Sub(newPool.BondedTokens).RoundInt64()) // read updated validator - validator, found = keeper.GetValidatorByPubKey(ctx, pk) + validator, found = keeper.GetValidatorByConsAddr(ctx, consAddr) require.True(t, found) // power decreased by 3 again require.Equal(t, sdk.NewDec(1), validator.GetPower()) @@ -291,7 +292,7 @@ func TestSlashWithUnbondingDelegation(t *testing.T) { // on the unbonding delegation, but it will slash stake bonded since the infraction // this may not be the desirable behaviour, ref https://github.com/cosmos/cosmos-sdk/issues/1440 ctx = ctx.WithBlockHeight(13) - keeper.Slash(ctx, pk, 9, 10, fraction) + keeper.Slash(ctx, consAddr, 9, 10, fraction) ubd, found = keeper.GetUnbondingDelegation(ctx, addrDels[0], addrVals[0]) require.True(t, found) // balance unchanged @@ -303,14 +304,14 @@ func TestSlashWithUnbondingDelegation(t *testing.T) { // read updated validator // power decreased by 1 again, validator is out of stake // ergo validator should have been removed from the store - _, found = keeper.GetValidatorByPubKey(ctx, pk) + _, found = keeper.GetValidatorByConsAddr(ctx, consAddr) require.False(t, found) } // tests Slash at a previous height with a redelegation func TestSlashWithRedelegation(t *testing.T) { ctx, keeper, params := setupHelper(t, 10) - pk := PKs[0] + consAddr := sdk.ConsAddress(PKs[0].Address()) fraction := sdk.NewDecWithPrec(5, 1) // set a redelegation @@ -343,9 +344,9 @@ func TestSlashWithRedelegation(t *testing.T) { // slash validator ctx = ctx.WithBlockHeight(12) oldPool := keeper.GetPool(ctx) - validator, found := keeper.GetValidatorByPubKey(ctx, pk) + validator, found := keeper.GetValidatorByConsAddr(ctx, consAddr) require.True(t, found) - keeper.Slash(ctx, pk, 10, 10, fraction) + keeper.Slash(ctx, consAddr, 10, 10, fraction) // read updating redelegation rd, found = keeper.GetRedelegation(ctx, addrDels[0], addrVals[0], addrVals[1]) @@ -357,7 +358,7 @@ func TestSlashWithRedelegation(t *testing.T) { // bonded tokens burned require.Equal(t, int64(5), oldPool.BondedTokens.Sub(newPool.BondedTokens).RoundInt64()) // read updated validator - validator, found = keeper.GetValidatorByPubKey(ctx, pk) + validator, found = keeper.GetValidatorByConsAddr(ctx, consAddr) require.True(t, found) // power decreased by 2 - 4 stake originally bonded at the time of infraction // was still bonded at the time of discovery and was slashed by half, 4 stake @@ -367,9 +368,9 @@ func TestSlashWithRedelegation(t *testing.T) { // slash the validator again ctx = ctx.WithBlockHeight(12) - validator, found = keeper.GetValidatorByPubKey(ctx, pk) + validator, found = keeper.GetValidatorByConsAddr(ctx, consAddr) require.True(t, found) - require.NotPanics(t, func() { keeper.Slash(ctx, pk, 10, 10, sdk.OneDec()) }) + require.NotPanics(t, func() { keeper.Slash(ctx, consAddr, 10, 10, sdk.OneDec()) }) // read updating redelegation rd, found = keeper.GetRedelegation(ctx, addrDels[0], addrVals[0], addrVals[1]) @@ -381,16 +382,16 @@ func TestSlashWithRedelegation(t *testing.T) { // seven bonded tokens burned require.Equal(t, int64(12), oldPool.BondedTokens.Sub(newPool.BondedTokens).RoundInt64()) // read updated validator - validator, found = keeper.GetValidatorByPubKey(ctx, pk) + validator, found = keeper.GetValidatorByConsAddr(ctx, consAddr) require.True(t, found) // power decreased by 4 require.Equal(t, sdk.NewDec(4), validator.GetPower()) // slash the validator again, by 100% ctx = ctx.WithBlockHeight(12) - validator, found = keeper.GetValidatorByPubKey(ctx, pk) + validator, found = keeper.GetValidatorByConsAddr(ctx, consAddr) require.True(t, found) - keeper.Slash(ctx, pk, 10, 10, sdk.OneDec()) + keeper.Slash(ctx, consAddr, 10, 10, sdk.OneDec()) // read updating redelegation rd, found = keeper.GetRedelegation(ctx, addrDels[0], addrVals[0], addrVals[1]) @@ -403,16 +404,16 @@ func TestSlashWithRedelegation(t *testing.T) { require.Equal(t, int64(16), oldPool.BondedTokens.Sub(newPool.BondedTokens).RoundInt64()) // read updated validator // validator decreased to zero power, should have been removed from the store - _, found = keeper.GetValidatorByPubKey(ctx, pk) + _, found = keeper.GetValidatorByConsAddr(ctx, consAddr) require.False(t, found) // slash the validator again, by 100% // no stake remains to be slashed ctx = ctx.WithBlockHeight(12) // validator no longer in the store - _, found = keeper.GetValidatorByPubKey(ctx, pk) + _, found = keeper.GetValidatorByConsAddr(ctx, consAddr) require.False(t, found) - keeper.Slash(ctx, pk, 10, 10, sdk.OneDec()) + keeper.Slash(ctx, consAddr, 10, 10, sdk.OneDec()) // read updating redelegation rd, found = keeper.GetRedelegation(ctx, addrDels[0], addrVals[0], addrVals[1]) @@ -425,7 +426,7 @@ func TestSlashWithRedelegation(t *testing.T) { require.Equal(t, int64(16), oldPool.BondedTokens.Sub(newPool.BondedTokens).RoundInt64()) // read updated validator // power still zero, still not in the store - _, found = keeper.GetValidatorByPubKey(ctx, pk) + _, found = keeper.GetValidatorByConsAddr(ctx, consAddr) require.False(t, found) } @@ -472,9 +473,10 @@ func TestSlashBoth(t *testing.T) { // slash validator ctx = ctx.WithBlockHeight(12) oldPool := keeper.GetPool(ctx) - validator, found := keeper.GetValidatorByPubKey(ctx, PKs[0]) + validator, found := keeper.GetValidatorByConsAddr(ctx, sdk.GetConsAddress(PKs[0])) require.True(t, found) - keeper.Slash(ctx, PKs[0], 10, 10, fraction) + consAddr0 := sdk.ConsAddress(PKs[0].Address()) + keeper.Slash(ctx, consAddr0, 10, 10, fraction) // read updating redelegation rdA, found = keeper.GetRedelegation(ctx, addrDels[0], addrVals[0], addrVals[1]) @@ -488,7 +490,7 @@ func TestSlashBoth(t *testing.T) { // bonded tokens burned require.Equal(t, int64(3), oldPool.BondedTokens.Sub(newPool.BondedTokens).RoundInt64()) // read updated validator - validator, found = keeper.GetValidatorByPubKey(ctx, PKs[0]) + validator, found = keeper.GetValidatorByConsAddr(ctx, sdk.GetConsAddress(PKs[0])) require.True(t, found) // power not decreased, all stake was bonded since require.Equal(t, sdk.NewDec(10), validator.GetPower()) diff --git a/x/stake/keeper/validator.go b/x/stake/keeper/validator.go index de4c4a187c..64d2ad021c 100644 --- a/x/stake/keeper/validator.go +++ b/x/stake/keeper/validator.go @@ -6,7 +6,6 @@ import ( "fmt" abci "github.com/tendermint/tendermint/abci/types" - "github.com/tendermint/tendermint/crypto" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/x/stake/types" @@ -58,14 +57,14 @@ func (k Keeper) GetValidator(ctx sdk.Context, addr sdk.ValAddress) (validator ty return validator, true } -// get a single validator by pubkey -func (k Keeper) GetValidatorByPubKey(ctx sdk.Context, pubkey crypto.PubKey) (validator types.Validator, found bool) { +// get a single validator by consensus address +func (k Keeper) GetValidatorByConsAddr(ctx sdk.Context, consAddr sdk.ConsAddress) (validator types.Validator, found bool) { store := ctx.KVStore(k.storeKey) - addr := store.Get(GetValidatorByPubKeyIndexKey(pubkey)) - if addr == nil { + opAddr := store.Get(GetValidatorByConsAddrKey(consAddr)) + if opAddr == nil { return validator, false } - return k.GetValidator(ctx, addr) + return k.GetValidator(ctx, opAddr) } // set the main record holding validator details @@ -76,9 +75,10 @@ func (k Keeper) SetValidator(ctx sdk.Context, validator types.Validator) { } // validator index -func (k Keeper) SetValidatorByPubKeyIndex(ctx sdk.Context, validator types.Validator) { +func (k Keeper) SetValidatorByConsAddr(ctx sdk.Context, validator types.Validator) { store := ctx.KVStore(k.storeKey) - store.Set(GetValidatorByPubKeyIndexKey(validator.ConsPubKey), validator.OperatorAddr) + consAddr := sdk.ConsAddress(validator.ConsPubKey.Address()) + store.Set(GetValidatorByConsAddrKey(consAddr), validator.OperatorAddr) } // validator index @@ -622,8 +622,8 @@ func (k Keeper) beginUnbondingValidator(ctx sdk.Context, validator types.Validat store.Delete(GetValidatorsBondedIndexKey(validator.OperatorAddr)) // call the unbond hook if present - if k.validatorHooks != nil { - k.validatorHooks.OnValidatorBeginUnbonding(ctx, validator.ConsAddress()) + if k.hooks != nil { + k.hooks.OnValidatorBeginUnbonding(ctx, validator.ConsAddress()) } // return updated validator @@ -657,8 +657,8 @@ func (k Keeper) bondValidator(ctx sdk.Context, validator types.Validator) types. tstore.Set(GetTendermintUpdatesTKey(validator.OperatorAddr), bzABCI) // call the bond hook if present - if k.validatorHooks != nil { - k.validatorHooks.OnValidatorBonded(ctx, validator.ConsAddress()) + if k.hooks != nil { + k.hooks.OnValidatorBonded(ctx, validator.ConsAddress()) } // return updated validator @@ -678,7 +678,7 @@ func (k Keeper) RemoveValidator(ctx sdk.Context, address sdk.ValAddress) { store := ctx.KVStore(k.storeKey) pool := k.GetPool(ctx) store.Delete(GetValidatorKey(address)) - store.Delete(GetValidatorByPubKeyIndexKey(validator.ConsPubKey)) + store.Delete(GetValidatorByConsAddrKey(sdk.ConsAddress(validator.ConsPubKey.Address()))) store.Delete(GetValidatorsByPowerIndexKey(validator, pool)) // delete from the current and power weighted validator groups if the validator diff --git a/x/stake/keeper/validator_test.go b/x/stake/keeper/validator_test.go index 1a5cc3fee2..73a391acc7 100644 --- a/x/stake/keeper/validator_test.go +++ b/x/stake/keeper/validator_test.go @@ -262,12 +262,13 @@ func TestSlashToZeroPowerRemoved(t *testing.T) { require.Equal(t, sdk.Unbonded, validator.Status) require.Equal(t, int64(100), validator.Tokens.RoundInt64()) keeper.SetPool(ctx, pool) - keeper.SetValidatorByPubKeyIndex(ctx, validator) + keeper.SetValidatorByConsAddr(ctx, validator) validator = keeper.UpdateValidator(ctx, validator) require.Equal(t, int64(100), validator.Tokens.RoundInt64(), "\nvalidator %v\npool %v", validator, pool) // slash the validator by 100% - keeper.Slash(ctx, PKs[0], 0, 100, sdk.OneDec()) + consAddr0 := sdk.ConsAddress(PKs[0].Address()) + keeper.Slash(ctx, consAddr0, 0, 100, sdk.OneDec()) // validator should have been deleted _, found := keeper.GetValidator(ctx, addrVals[0]) require.False(t, found) @@ -306,10 +307,19 @@ func TestValidatorBasics(t *testing.T) { // set and retrieve a record validators[0] = keeper.UpdateValidator(ctx, validators[0]) + keeper.SetValidatorByConsAddr(ctx, validators[0]) resVal, found := keeper.GetValidator(ctx, addrVals[0]) require.True(t, found) assert.True(ValEq(t, validators[0], resVal)) + // retrieve from consensus + resVal, found = keeper.GetValidatorByConsAddr(ctx, sdk.ConsAddress(PKs[0].Address())) + require.True(t, found) + assert.True(ValEq(t, validators[0], resVal)) + resVal, found = keeper.GetValidatorByConsAddr(ctx, sdk.GetConsAddress(PKs[0])) + require.True(t, found) + assert.True(ValEq(t, validators[0], resVal)) + resVals = keeper.GetValidatorsBonded(ctx) require.Equal(t, 1, len(resVals)) assert.True(ValEq(t, validators[0], resVals[0])) diff --git a/x/stake/simulation/invariants.go b/x/stake/simulation/invariants.go index 2ab0717042..cdd80a8c41 100644 --- a/x/stake/simulation/invariants.go +++ b/x/stake/simulation/invariants.go @@ -80,7 +80,7 @@ func PositivePowerInvariant(k stake.Keeper) simulation.Invariant { var err error k.IterateValidatorsBonded(ctx, func(_ int64, validator sdk.Validator) bool { if !validator.GetPower().GT(sdk.ZeroDec()) { - err = fmt.Errorf("validator with non-positive power stored. (pubkey %v)", validator.GetPubKey()) + err = fmt.Errorf("validator with non-positive power stored. (pubkey %v)", validator.GetConsPubKey()) return true } return false diff --git a/x/stake/stake.go b/x/stake/stake.go index b421a5d25a..7e60b3113a 100644 --- a/x/stake/stake.go +++ b/x/stake/stake.go @@ -36,7 +36,7 @@ var ( NewKeeper = keeper.NewKeeper GetValidatorKey = keeper.GetValidatorKey - GetValidatorByPubKeyIndexKey = keeper.GetValidatorByPubKeyIndexKey + GetValidatorByConsAddrKey = keeper.GetValidatorByConsAddrKey GetValidatorsBondedIndexKey = keeper.GetValidatorsBondedIndexKey GetValidatorsByPowerIndexKey = keeper.GetValidatorsByPowerIndexKey GetTendermintUpdatesTKey = keeper.GetTendermintUpdatesTKey @@ -45,7 +45,7 @@ var ( ParamKey = keeper.ParamKey PoolKey = keeper.PoolKey ValidatorsKey = keeper.ValidatorsKey - ValidatorsByPubKeyIndexKey = keeper.ValidatorsByPubKeyIndexKey + ValidatorsByConsAddrKey = keeper.ValidatorsByConsAddrKey ValidatorsBondedIndexKey = keeper.ValidatorsBondedIndexKey ValidatorsByPowerIndexKey = keeper.ValidatorsByPowerIndexKey ValidatorCliffIndexKey = keeper.ValidatorCliffIndexKey diff --git a/x/stake/types/codec.go b/x/stake/types/codec.go index aa4f64f8a2..4921cdf8e2 100644 --- a/x/stake/types/codec.go +++ b/x/stake/types/codec.go @@ -22,6 +22,5 @@ func init() { cdc := codec.New() RegisterCodec(cdc) codec.RegisterCrypto(cdc) - MsgCdc = cdc - //MsgCdc = cdc.Seal() //TODO use when upgraded to go-amino 0.9.10 + MsgCdc = cdc.Seal() } diff --git a/x/stake/types/delegation.go b/x/stake/types/delegation.go index 38a94c3695..57ac70a571 100644 --- a/x/stake/types/delegation.go +++ b/x/stake/types/delegation.go @@ -89,7 +89,7 @@ var _ sdk.Delegation = Delegation{} // nolint - for sdk.Delegation func (d Delegation) GetDelegator() sdk.AccAddress { return d.DelegatorAddr } func (d Delegation) GetValidator() sdk.ValAddress { return d.ValidatorAddr } -func (d Delegation) GetBondShares() sdk.Dec { return d.Shares } +func (d Delegation) GetShares() sdk.Dec { return d.Shares } // HumanReadableString returns a human readable string representation of a // Delegation. An error is returned if the Delegation's delegator or validator diff --git a/x/stake/types/validator.go b/x/stake/types/validator.go index f124ab5959..051ffa9e51 100644 --- a/x/stake/types/validator.go +++ b/x/stake/types/validator.go @@ -450,12 +450,14 @@ func (v Validator) IsUnbonded(ctx sdk.Context) bool { var _ sdk.Validator = Validator{} // nolint - for sdk.Validator -func (v Validator) GetJailed() bool { return v.Jailed } -func (v Validator) GetMoniker() string { return v.Description.Moniker } -func (v Validator) GetStatus() sdk.BondStatus { return v.Status } -func (v Validator) GetOperator() sdk.ValAddress { return v.OperatorAddr } -func (v Validator) GetPubKey() crypto.PubKey { return v.ConsPubKey } -func (v Validator) GetPower() sdk.Dec { return v.BondedTokens() } -func (v Validator) GetTokens() sdk.Dec { return v.Tokens } -func (v Validator) GetDelegatorShares() sdk.Dec { return v.DelegatorShares } -func (v Validator) GetBondHeight() int64 { return v.BondHeight } +func (v Validator) GetJailed() bool { return v.Jailed } +func (v Validator) GetMoniker() string { return v.Description.Moniker } +func (v Validator) GetStatus() sdk.BondStatus { return v.Status } +func (v Validator) GetOperator() sdk.ValAddress { return v.OperatorAddr } +func (v Validator) GetConsPubKey() crypto.PubKey { return v.ConsPubKey } +func (v Validator) GetConsAddr() sdk.ConsAddress { return sdk.ConsAddress(v.ConsPubKey.Address()) } +func (v Validator) GetPower() sdk.Dec { return v.BondedTokens() } +func (v Validator) GetTokens() sdk.Dec { return v.Tokens } +func (v Validator) GetCommission() sdk.Dec { return v.Commission.Rate } +func (v Validator) GetDelegatorShares() sdk.Dec { return v.DelegatorShares } +func (v Validator) GetBondHeight() int64 { return v.BondHeight } From de30281afa9229afd1e88b5140deee41b22437bd Mon Sep 17 00:00:00 2001 From: Rigel Date: Tue, 25 Sep 2018 00:18:18 -0400 Subject: [PATCH 07/26] Distr-PR-2 Truncate Decimal Functionality (#2379) --- PENDING.md | 1 + types/decimal.go | 26 ++++++++++++++++++++++++++ types/decimal_test.go | 26 ++++++++++++++++++++++++++ 3 files changed, 53 insertions(+) diff --git a/PENDING.md b/PENDING.md index 344e3628d6..f0e87eb728 100644 --- a/PENDING.md +++ b/PENDING.md @@ -129,6 +129,7 @@ IMPROVEMENTS * [simulation] Logs get written to file if large, and also get printed on panics \#2285 * [gaiad] \#1992 Add optional flag to `gaiad testnet` to make config directory of daemon (default `gaiad`) and cli (default `gaiacli`) configurable * [x/stake] Add stake `Queriers` for Gaia-lite endpoints. This increases the staking endpoints performance by reusing the staking `keeper` logic for queries. [#2249](https://github.com/cosmos/cosmos-sdk/pull/2149) + * [types/decimal] \#2378 - Added truncate functionality to decimal * Tendermint diff --git a/types/decimal.go b/types/decimal.go index 564d6322c0..13a494ba0c 100644 --- a/types/decimal.go +++ b/types/decimal.go @@ -325,6 +325,32 @@ func (d Dec) RoundInt() Int { //___________________________________________________________________________________ +// similar to chopPrecisionAndRound, but always rounds down +func chopPrecisionAndTruncate(d *big.Int) *big.Int { + return d.Quo(d, precisionReuse) +} + +func chopPrecisionAndTruncateNonMutative(d *big.Int) *big.Int { + tmp := new(big.Int).Set(d) + return chopPrecisionAndTruncate(tmp) +} + +// TruncateInt64 truncates the decimals from the number and returns an int64 +func (d Dec) TruncateInt64() int64 { + chopped := chopPrecisionAndTruncateNonMutative(d.Int) + if !chopped.IsInt64() { + panic("Int64() out of bound") + } + return chopped.Int64() +} + +// TruncateInt truncates the decimals from the number and returns an Int +func (d Dec) TruncateInt() Int { + return NewIntFromBigInt(chopPrecisionAndTruncateNonMutative(d.Int)) +} + +//___________________________________________________________________________________ + // reuse nil values var ( nilAmino string diff --git a/types/decimal_test.go b/types/decimal_test.go index 779460c253..161215467f 100644 --- a/types/decimal_test.go +++ b/types/decimal_test.go @@ -202,6 +202,32 @@ func TestBankerRoundChop(t *testing.T) { } } +func TestTruncate(t *testing.T) { + tests := []struct { + d1 Dec + exp int64 + }{ + {mustNewDecFromStr(t, "0"), 0}, + {mustNewDecFromStr(t, "0.25"), 0}, + {mustNewDecFromStr(t, "0.75"), 0}, + {mustNewDecFromStr(t, "1"), 1}, + {mustNewDecFromStr(t, "1.5"), 1}, + {mustNewDecFromStr(t, "7.5"), 7}, + {mustNewDecFromStr(t, "7.6"), 7}, + {mustNewDecFromStr(t, "7.4"), 7}, + {mustNewDecFromStr(t, "100.1"), 100}, + {mustNewDecFromStr(t, "1000.1"), 1000}, + } + + for tcIndex, tc := range tests { + resNeg := tc.d1.Neg().TruncateInt64() + require.Equal(t, -1*tc.exp, resNeg, "negative tc %d", tcIndex) + + resPos := tc.d1.TruncateInt64() + require.Equal(t, tc.exp, resPos, "positive tc %d", tcIndex) + } +} + func TestToLeftPadded(t *testing.T) { tests := []struct { dec Dec From 611e2873750600347d7d411a433fbdbc2741d96f Mon Sep 17 00:00:00 2001 From: Alexander Bezobchuk Date: Tue, 25 Sep 2018 17:45:04 +0000 Subject: [PATCH 08/26] Merge PR #2401: Remove remnants of gocyclo --- client/keys/add.go | 1 - tools/Makefile | 15 --------------- types/errors.go | 1 - x/auth/ante.go | 1 - x/bank/client/rest/sendtx.go | 1 - x/gov/client/cli/tx.go | 1 - x/gov/client/rest/rest.go | 3 --- x/gov/keeper.go | 1 - x/ibc/client/cli/relay.go | 1 - x/ibc/client/rest/transfer.go | 1 - x/slashing/client/rest/tx.go | 1 - x/slashing/keeper.go | 1 - x/stake/client/rest/query.go | 1 - x/stake/client/rest/tx.go | 1 - x/stake/keeper/slash.go | 2 -- x/stake/keeper/validator.go | 4 ---- x/stake/types/inflation_test.go | 1 - 17 files changed, 37 deletions(-) diff --git a/client/keys/add.go b/client/keys/add.go index c421728196..be2825a55d 100644 --- a/client/keys/add.go +++ b/client/keys/add.go @@ -46,7 +46,6 @@ phrase, otherwise, a new key will be generated.`, return cmd } -// nolint: gocyclo // TODO remove the above when addressing #1446 func runAddCmd(cmd *cobra.Command, args []string) error { var kb keys.Keybase diff --git a/tools/Makefile b/tools/Makefile index 87544107c5..c24e886ffb 100644 --- a/tools/Makefile +++ b/tools/Makefile @@ -12,7 +12,6 @@ INEFFASSIGN = github.com/gordonklaus/ineffassign MISSPELL = github.com/client9/misspell/cmd/misspell ERRCHECK = github.com/kisielk/errcheck UNPARAM = mvdan.cc/unparam -GOCYCLO = github.com/alecthomas/gocyclo DEP_CHECK := $(shell command -v dep 2> /dev/null) GOLINT_CHECK := $(shell command -v golint 2> /dev/null) @@ -22,7 +21,6 @@ INEFFASSIGN_CHECK := $(shell command -v ineffassign 2> /dev/null) MISSPELL_CHECK := $(shell command -v misspell 2> /dev/null) ERRCHECK_CHECK := $(shell command -v errcheck 2> /dev/null) UNPARAM_CHECK := $(shell command -v unparam 2> /dev/null) -# GOCYCLO_CHECK := $(shell command -v gocyclo 2> /dev/null) check_tools: ifndef DEP_CHECK @@ -68,11 +66,6 @@ ifndef UNPARAM_CHECK else @echo "Found unparam in path." endif -ifndef GOCYCLO_CHECK - @echo "No gocyclo in path. Install with 'make get_tools'." -else - @echo "Found gocyclo in path." -endif get_tools: ifdef DEP_CHECK @@ -126,12 +119,6 @@ else @echo "Installing unparam" go get -v $(UNPARAM) endif -# ifdef GOCYCLO_CHECK -# @echo "gocyclo is already installed. Run 'make update_tools' to update." -# else -# @echo "Installing gocyclo" -# go get -v $(GOCYCLO) -# endif update_tools: @echo "Updating dep" @@ -153,8 +140,6 @@ update_dev_tools: go get -u -v $(ERRCHECK) @echo "Updating unparam" go get -u -v $(UNPARAM) - # @echo "Updating goyclo" - # go get -u -v $(GOCYCLO) # To avoid unintended conflicts with file names, always add to .PHONY # unless there is a reason not to. diff --git a/types/errors.go b/types/errors.go index 1d4900d3c2..46bf748f48 100644 --- a/types/errors.go +++ b/types/errors.go @@ -73,7 +73,6 @@ func unknownCodeMsg(code CodeType) string { } // NOTE: Don't stringer this, we'll put better messages in later. -// nolint: gocyclo func CodeToDefaultMsg(code CodeType) string { switch code { case CodeInternal: diff --git a/x/auth/ante.go b/x/auth/ante.go index 5773e6b1fe..f423c9465c 100644 --- a/x/auth/ante.go +++ b/x/auth/ante.go @@ -23,7 +23,6 @@ const ( // NewAnteHandler returns an AnteHandler that checks // and increments sequence numbers, checks signatures & account numbers, // and deducts fees from the first signer. -// nolint: gocyclo func NewAnteHandler(am AccountMapper, fck FeeCollectionKeeper) sdk.AnteHandler { return func( diff --git a/x/bank/client/rest/sendtx.go b/x/bank/client/rest/sendtx.go index 242614d118..2a1e51f0de 100644 --- a/x/bank/client/rest/sendtx.go +++ b/x/bank/client/rest/sendtx.go @@ -43,7 +43,6 @@ func init() { } // SendRequestHandlerFn - http request handler to send coins to a address -// nolint: gocyclo func SendRequestHandlerFn(cdc *codec.Codec, kb keys.Keybase, cliCtx context.CLIContext) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { // collect data diff --git a/x/gov/client/cli/tx.go b/x/gov/client/cli/tx.go index 77364665a1..1bd01b58ab 100644 --- a/x/gov/client/cli/tx.go +++ b/x/gov/client/cli/tx.go @@ -285,7 +285,6 @@ func GetCmdQueryProposal(queryRoute string, cdc *codec.Codec) *cobra.Command { return cmd } -// nolint: gocyclo // GetCmdQueryProposals implements a query proposals command. func GetCmdQueryProposals(queryRoute string, cdc *codec.Codec) *cobra.Command { cmd := &cobra.Command{ diff --git a/x/gov/client/rest/rest.go b/x/gov/client/rest/rest.go index 059c0bb1cf..3f62691c74 100644 --- a/x/gov/client/rest/rest.go +++ b/x/gov/client/rest/rest.go @@ -334,7 +334,6 @@ func queryVoteHandlerFn(cdc *codec.Codec) http.HandlerFunc { } } -// nolint: gocyclo // todo: Split this functionality into helper functions to remove the above func queryVotesOnProposalHandlerFn(cdc *codec.Codec) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { @@ -373,7 +372,6 @@ func queryVotesOnProposalHandlerFn(cdc *codec.Codec) http.HandlerFunc { } } -// nolint: gocyclo // todo: Split this functionality into helper functions to remove the above func queryProposalsWithParameterFn(cdc *codec.Codec) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { @@ -439,7 +437,6 @@ func queryProposalsWithParameterFn(cdc *codec.Codec) http.HandlerFunc { } } -// nolint: gocyclo // todo: Split this functionality into helper functions to remove the above func queryTallyOnProposalHandlerFn(cdc *codec.Codec) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { diff --git a/x/gov/keeper.go b/x/gov/keeper.go index 2a462cfb42..80e5a205cd 100644 --- a/x/gov/keeper.go +++ b/x/gov/keeper.go @@ -106,7 +106,6 @@ func (keeper Keeper) DeleteProposal(ctx sdk.Context, proposal Proposal) { store.Delete(KeyProposal(proposal.GetProposalID())) } -// nolint: gocyclo // Get Proposal from store by ProposalID func (keeper Keeper) GetProposalsFiltered(ctx sdk.Context, voterAddr sdk.AccAddress, depositerAddr sdk.AccAddress, status ProposalStatus, numLatest int64) []Proposal { diff --git a/x/ibc/client/cli/relay.go b/x/ibc/client/cli/relay.go index a2b626b2c6..a57fac7054 100644 --- a/x/ibc/client/cli/relay.go +++ b/x/ibc/client/cli/relay.go @@ -91,7 +91,6 @@ func (c relayCommander) runIBCRelay(cmd *cobra.Command, args []string) { } // This is nolinted as someone is in the process of refactoring this to remove the goto -// nolint: gocyclo func (c relayCommander) loop(fromChainID, fromChainNode, toChainID, toChainNode string) { cliCtx := context.NewCLIContext() diff --git a/x/ibc/client/rest/transfer.go b/x/ibc/client/rest/transfer.go index e44ea240be..d151f8aa58 100644 --- a/x/ibc/client/rest/transfer.go +++ b/x/ibc/client/rest/transfer.go @@ -35,7 +35,6 @@ type transferBody struct { // TransferRequestHandler - http request handler to transfer coins to a address // on a different chain via IBC -// nolint: gocyclo func TransferRequestHandlerFn(cdc *codec.Codec, kb keys.Keybase, cliCtx context.CLIContext) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { vars := mux.Vars(r) diff --git a/x/slashing/client/rest/tx.go b/x/slashing/client/rest/tx.go index 45b1b6471a..94537c2eb3 100644 --- a/x/slashing/client/rest/tx.go +++ b/x/slashing/client/rest/tx.go @@ -38,7 +38,6 @@ type UnjailBody struct { ValidatorAddr string `json:"validator_addr"` } -// nolint: gocyclo func unjailRequestHandlerFn(cdc *codec.Codec, kb keys.Keybase, cliCtx context.CLIContext) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { var m UnjailBody diff --git a/x/slashing/keeper.go b/x/slashing/keeper.go index e639182e18..6a5fa30148 100644 --- a/x/slashing/keeper.go +++ b/x/slashing/keeper.go @@ -79,7 +79,6 @@ func (k Keeper) handleDoubleSign(ctx sdk.Context, addr crypto.Address, infractio // handle a validator signature, must be called once per validator per block // TODO refactor to take in a consensus address, additionally should maybe just take in the pubkey too -// nolint gocyclo func (k Keeper) handleValidatorSignature(ctx sdk.Context, addr crypto.Address, power int64, signed bool) { logger := ctx.Logger().With("module", "x/slashing") height := ctx.BlockHeight() diff --git a/x/stake/client/rest/query.go b/x/stake/client/rest/query.go index 8d55bed8cf..a8fbfecf4d 100644 --- a/x/stake/client/rest/query.go +++ b/x/stake/client/rest/query.go @@ -120,7 +120,6 @@ func delegatorHandlerFn(cliCtx context.CLIContext, cdc *codec.Codec) http.Handle } } -// nolint gocyclo // HTTP request handler to query all staking txs (msgs) from a delegator func delegatorTxsHandlerFn(cliCtx context.CLIContext, cdc *codec.Codec) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { diff --git a/x/stake/client/rest/tx.go b/x/stake/client/rest/tx.go index b716843602..324e0c914f 100644 --- a/x/stake/client/rest/tx.go +++ b/x/stake/client/rest/tx.go @@ -69,7 +69,6 @@ type EditDelegationsBody struct { CompleteRedelegates []msgCompleteRedelegateInput `json:"complete_redelegates"` } -// nolint: gocyclo // TODO: Split this up into several smaller functions, and remove the above nolint // TODO: use sdk.ValAddress instead of sdk.AccAddress for validators in messages func delegationsRequestHandlerFn(cdc *codec.Codec, kb keys.Keybase, cliCtx context.CLIContext) http.HandlerFunc { diff --git a/x/stake/keeper/slash.go b/x/stake/keeper/slash.go index 29adec06a7..486dc32869 100644 --- a/x/stake/keeper/slash.go +++ b/x/stake/keeper/slash.go @@ -21,8 +21,6 @@ import ( // CONTRACT: // Infraction committed at the current height or at a past height, // not at a height in the future -// -// nolint: gocyclo func (k Keeper) Slash(ctx sdk.Context, consAddr sdk.ConsAddress, infractionHeight int64, power int64, slashFactor sdk.Dec) { logger := ctx.Logger().With("module", "x/stake") diff --git a/x/stake/keeper/validator.go b/x/stake/keeper/validator.go index 64d2ad021c..9d8fb4362a 100644 --- a/x/stake/keeper/validator.go +++ b/x/stake/keeper/validator.go @@ -232,7 +232,6 @@ func (k Keeper) GetValidTendermintUpdates(ctx sdk.Context) (updates []abci.Valid // It may kick out validators if a new validator is entering the bonded validator // group. // -// nolint: gocyclo // TODO: Remove above nolint, function needs to be simplified! func (k Keeper) UpdateValidator(ctx sdk.Context, validator types.Validator) types.Validator { tstore := ctx.TransientStore(k.storeTKey) @@ -422,9 +421,6 @@ func (k Keeper) updateValidatorPower(ctx sdk.Context, oldFound bool, oldValidato // updated in store with the ValidatorsBondedIndexKey. This store is used to // determine if a validator is a validator without needing to iterate over all // validators. -// -// nolint: gocyclo -// TODO: Remove the above golint func (k Keeper) UpdateBondedValidators( ctx sdk.Context, affectedValidator types.Validator) ( updatedVal types.Validator, updated bool) { diff --git a/x/stake/types/inflation_test.go b/x/stake/types/inflation_test.go index fd181af3c9..159ecb4c4e 100644 --- a/x/stake/types/inflation_test.go +++ b/x/stake/types/inflation_test.go @@ -107,7 +107,6 @@ func updateProvisions(t *testing.T, pool Pool, params Params, hr int) (sdk.Dec, } // Checks that The inflation will correctly increase or decrease after an update to the pool -// nolint: gocyclo func checkInflation(t *testing.T, pool Pool, previousInflation, updatedInflation sdk.Dec, msg string) { inflationChange := updatedInflation.Sub(previousInflation) From a04d5cf26dd7178626c7be3baf987d6a83c3610f Mon Sep 17 00:00:00 2001 From: Sunny Aggarwal Date: Tue, 25 Sep 2018 11:18:05 -0700 Subject: [PATCH 09/26] Merge PR #2072: WIP Bank Denom Metadata Spec * Bank Denom Metadata Spec * moved to WIP filename * val comment --- docs/spec/bank/WIP_keeper.md | 25 +++++++++++++++++++++++++ docs/spec/bank/state.md | 2 -- 2 files changed, 25 insertions(+), 2 deletions(-) create mode 100644 docs/spec/bank/WIP_keeper.md delete mode 100644 docs/spec/bank/state.md diff --git a/docs/spec/bank/WIP_keeper.md b/docs/spec/bank/WIP_keeper.md new file mode 100644 index 0000000000..9667fc5dd0 --- /dev/null +++ b/docs/spec/bank/WIP_keeper.md @@ -0,0 +1,25 @@ +WORK IN PROGRESS +See PR comments here https://github.com/cosmos/cosmos-sdk/pull/2072 + +# Keeper + +## Denom Metadata + +The BankKeeper contains a store that stores the metadata of different token denoms. Denoms are referred to by their name, same as the `denom` field in sdk.Coin. The different attributes of a denom are stored in the denom metadata store under the key `[denom name]:[attribute name]`. The default attributes in the store are explained below. However, this can be extended by the developer or through SoftwareUpgrade proposals. + +### Decimals `int8` + +- `Base Unit` = The common standard for the default "standard" size of a token. Examples: 1 Bitcoin or 1 Ether. +- `Smallest Unit` = The smallest possible denomination of a token. A fraction of the base unit. Examples: 1 satoshi or 1 wei. + +All amounts throughout the SDK are denominated in the smallest unit of a token, so that all amounts can be expressed as integers. However, UIs typically want to display token values in the base unit, so the Decimals metadata field standardizes the number of digits that come after the decimal place in the base unit. + +`1 [Base Unit] = 10^(N) [Smallest Unit]` + +### TotalSupply `sdk.Integer` + +The TotalSupply of a denom is the total amount of a token that exists (known to the chain) across all accounts and modules. It is denominated in the `smallest unit` of a denom. It can be changed by the Keeper functions `MintCoins` and `BurnCoins`. `AddCoins` and `SubtractCoins` are used when adding or subtracting coins for an account, but not removing them from total supply (for example, when moving the coins to the control of the staking module). + +### Aliases `[]string` + +Aliases is an array of strings that are "alternative names" for a token. As an example, while the Ether's denom name might be `ether`, a possible alias could be `ETH`. This field can be useful for UIs and clients. It is intended that this field can be modified by a governance mechanism. diff --git a/docs/spec/bank/state.md b/docs/spec/bank/state.md deleted file mode 100644 index 8c2c47fd4e..0000000000 --- a/docs/spec/bank/state.md +++ /dev/null @@ -1,2 +0,0 @@ -- SDK related specifications (ie. how multistore, signatures, etc. work). -- Basecoin (SendTx) From 15e848e43c430547081dade39ccbf7583965fe92 Mon Sep 17 00:00:00 2001 From: Sunny Aggarwal Date: Tue, 25 Sep 2018 11:45:20 -0700 Subject: [PATCH 10/26] Merge PR #2395: Remove governance slashing --- PENDING.md | 1 + docs/spec/governance/state.md | 11 ++----- x/gov/endblocker_test.go | 60 ----------------------------------- x/gov/handler.go | 14 +------- x/gov/queryable.go | 2 +- x/gov/tally.go | 17 ++++------ x/gov/tally_test.go | 29 ++++++++--------- 7 files changed, 25 insertions(+), 109 deletions(-) diff --git a/PENDING.md b/PENDING.md index f4f816b264..1b9238796c 100644 --- a/PENDING.md +++ b/PENDING.md @@ -36,6 +36,7 @@ BREAKING CHANGES * `cosmosvaladdr` / `cosmosvalpub` => `cosmosvaloper` / `cosmosvaloperpub` * [x/stake] [#1013] TendermintUpdates now uses transient store * [x/gov] [#2195] Governance uses BFT Time + * [x/gov] \#2256 Removed slashing for governance non-voting validators * SDK * [core] [\#1807](https://github.com/cosmos/cosmos-sdk/issues/1807) Switch from use of rational to decimal diff --git a/docs/spec/governance/state.md b/docs/spec/governance/state.md index 0d2927d7a5..5b577cec39 100644 --- a/docs/spec/governance/state.md +++ b/docs/spec/governance/state.md @@ -28,7 +28,6 @@ type TallyingProcedure struct { Threshold sdk.Dec // Minimum propotion of Yes votes for proposal to pass. Initial value: 0.5 Veto sdk.Dec // Minimum proportion of Veto votes to Total votes ratio for proposal to be vetoed. Initial value: 1/3 GovernancePenalty sdk.Dec // Penalty if validator does not vote - GracePeriod time.Time // If validator entered validator set in this period of blocks before vote ended, governance penalty does not apply } ``` @@ -192,14 +191,10 @@ And the pseudocode for the `ProposalProcessingQueue`: tallyingProcedure = load(GlobalParams, 'TallyingProcedure') - // Slash validators that did not vote, or update tally if they voted + // Update tally if validator voted they voted for each validator in validators - if (validator.bondTime < CurrentTime - tallyingProcedure.GracePeriod) - // only slash if validator entered validator set before grace period - if (!tmpValMap(validator).HasVoted) - slash validator by tallyingProcedure.GovernancePenalty - else - proposal.updateTally(tmpValMap(validator).Vote, (validator.TotalShares - tmpValMap(validator).Minus)) + if tmpValMap(validator).HasVoted + proposal.updateTally(tmpValMap(validator).Vote, (validator.TotalShares - tmpValMap(validator).Minus)) diff --git a/x/gov/endblocker_test.go b/x/gov/endblocker_test.go index 4cbd1d7588..27eff15f64 100644 --- a/x/gov/endblocker_test.go +++ b/x/gov/endblocker_test.go @@ -7,7 +7,6 @@ import ( "github.com/stretchr/testify/require" sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/cosmos/cosmos-sdk/x/stake" abci "github.com/tendermint/tendermint/abci/types" ) @@ -194,62 +193,3 @@ func TestTickPassedVotingPeriod(t *testing.T) { require.Equal(t, StatusRejected, keeper.GetProposal(ctx, proposalID).GetStatus()) require.True(t, keeper.GetProposal(ctx, proposalID).GetTallyResult().Equals(EmptyTallyResult())) } - -func TestSlashing(t *testing.T) { - mapp, keeper, sk, addrs, _, _ := getMockApp(t, 10) - SortAddresses(addrs) - mapp.BeginBlock(abci.RequestBeginBlock{}) - ctx := mapp.BaseApp.NewContext(false, abci.Header{}) - govHandler := NewHandler(keeper) - stakeHandler := stake.NewHandler(sk) - - valAddrs := make([]sdk.ValAddress, len(addrs[:3])) - for i, addr := range addrs[:3] { - valAddrs[i] = sdk.ValAddress(addr) - } - - createValidators(t, stakeHandler, ctx, valAddrs, []int64{25, 6, 7}) - - initTotalPower := keeper.ds.GetValidatorSet().TotalPower(ctx) - val0Initial := keeper.ds.GetValidatorSet().Validator(ctx, sdk.ValAddress(addrs[0])).GetPower().Quo(initTotalPower) - val1Initial := keeper.ds.GetValidatorSet().Validator(ctx, sdk.ValAddress(addrs[1])).GetPower().Quo(initTotalPower) - val2Initial := keeper.ds.GetValidatorSet().Validator(ctx, sdk.ValAddress(addrs[2])).GetPower().Quo(initTotalPower) - - newProposalMsg := NewMsgSubmitProposal("Test", "test", ProposalTypeText, addrs[0], sdk.Coins{sdk.NewInt64Coin("steak", 15)}) - - res := govHandler(ctx, newProposalMsg) - require.True(t, res.IsOK()) - var proposalID int64 - keeper.cdc.UnmarshalBinaryBare(res.Data, &proposalID) - - newHeader := ctx.BlockHeader() - newHeader.Time = ctx.BlockHeader().Time.Add(time.Duration(1) * time.Second) - ctx = ctx.WithBlockHeader(newHeader) - - require.Equal(t, StatusVotingPeriod, keeper.GetProposal(ctx, proposalID).GetStatus()) - - newVoteMsg := NewMsgVote(addrs[0], proposalID, OptionYes) - res = govHandler(ctx, newVoteMsg) - require.True(t, res.IsOK()) - - EndBlocker(ctx, keeper) - - newHeader = ctx.BlockHeader() - newHeader.Time = ctx.BlockHeader().Time.Add(keeper.GetDepositProcedure(ctx).MaxDepositPeriod).Add(keeper.GetDepositProcedure(ctx).MaxDepositPeriod) - ctx = ctx.WithBlockHeader(newHeader) - - require.Equal(t, StatusVotingPeriod, keeper.GetProposal(ctx, proposalID).GetStatus()) - - EndBlocker(ctx, keeper) - - require.False(t, keeper.GetProposal(ctx, proposalID).GetTallyResult().Equals(EmptyTallyResult())) - - endTotalPower := keeper.ds.GetValidatorSet().TotalPower(ctx) - val0End := keeper.ds.GetValidatorSet().Validator(ctx, sdk.ValAddress(addrs[0])).GetPower().Quo(endTotalPower) - val1End := keeper.ds.GetValidatorSet().Validator(ctx, sdk.ValAddress(addrs[1])).GetPower().Quo(endTotalPower) - val2End := keeper.ds.GetValidatorSet().Validator(ctx, sdk.ValAddress(addrs[2])).GetPower().Quo(endTotalPower) - - require.True(t, val0End.GTE(val0Initial)) - require.True(t, val1End.LT(val1Initial)) - require.True(t, val2End.LT(val2Initial)) -} diff --git a/x/gov/handler.go b/x/gov/handler.go index 00eaf37440..3f8cd312b6 100644 --- a/x/gov/handler.go +++ b/x/gov/handler.go @@ -134,7 +134,7 @@ func EndBlocker(ctx sdk.Context, keeper Keeper) (resTags sdk.Tags) { continue } - passes, tallyResults, nonVotingVals := tally(ctx, keeper, activeProposal) + passes, tallyResults := tally(ctx, keeper, activeProposal) proposalIDBytes := keeper.cdc.MustMarshalBinaryBare(activeProposal.GetProposalID()) var action []byte if passes { @@ -152,18 +152,6 @@ func EndBlocker(ctx sdk.Context, keeper Keeper) (resTags sdk.Tags) { logger.Info(fmt.Sprintf("proposal %d (%s) tallied; passed: %v", activeProposal.GetProposalID(), activeProposal.GetTitle(), passes)) - for _, valAddr := range nonVotingVals { - val := keeper.ds.GetValidatorSet().Validator(ctx, valAddr) - keeper.ds.GetValidatorSet().Slash(ctx, - val.GetConsAddr(), - ctx.BlockHeight(), - val.GetPower().RoundInt64(), - keeper.GetTallyingProcedure(ctx).GovernancePenalty) - - logger.Info(fmt.Sprintf("validator %s failed to vote on proposal %d; slashing", - val.GetOperator(), activeProposal.GetProposalID())) - } - resTags.AppendTag(tags.Action, action) resTags.AppendTag(tags.ProposalID, proposalIDBytes) } diff --git a/x/gov/queryable.go b/x/gov/queryable.go index 44380fe91a..606f73a9ca 100644 --- a/x/gov/queryable.go +++ b/x/gov/queryable.go @@ -220,7 +220,7 @@ func queryTally(ctx sdk.Context, path []string, req abci.RequestQuery, keeper Ke } else if proposal.GetStatus() == StatusPassed || proposal.GetStatus() == StatusRejected { tallyResult = proposal.GetTallyResult() } else { - _, tallyResult, _ = tally(ctx, keeper, proposal) + _, tallyResult = tally(ctx, keeper, proposal) } bz, err2 := codec.MarshalJSONIndent(keeper.cdc, tallyResult) diff --git a/x/gov/tally.go b/x/gov/tally.go index 55fd81da24..c5751258a0 100644 --- a/x/gov/tally.go +++ b/x/gov/tally.go @@ -13,7 +13,7 @@ type validatorGovInfo struct { Vote VoteOption // Vote of the validator } -func tally(ctx sdk.Context, keeper Keeper, proposal Proposal) (passes bool, tallyResults TallyResult, nonVoting []sdk.ValAddress) { +func tally(ctx sdk.Context, keeper Keeper, proposal Proposal) (passes bool, tallyResults TallyResult) { results := make(map[VoteOption]sdk.Dec) results[OptionYes] = sdk.ZeroDec() results[OptionAbstain] = sdk.ZeroDec() @@ -70,12 +70,9 @@ func tally(ctx sdk.Context, keeper Keeper, proposal Proposal) (passes bool, tall keeper.deleteVote(ctx, vote.ProposalID, vote.Voter) } - // iterate over the validators again to tally their voting power and see - // who didn't vote - nonVoting = []sdk.ValAddress{} + // iterate over the validators again to tally their voting power for _, val := range currValidators { if val.Vote == OptionEmpty { - nonVoting = append(nonVoting, val.Address) continue } @@ -98,19 +95,17 @@ func tally(ctx sdk.Context, keeper Keeper, proposal Proposal) (passes bool, tall // If no one votes, proposal fails if totalVotingPower.Sub(results[OptionAbstain]).Equal(sdk.ZeroDec()) { - return false, tallyResults, nonVoting + return false, tallyResults } // If more than 1/3 of voters veto, proposal fails if results[OptionNoWithVeto].Quo(totalVotingPower).GT(tallyingProcedure.Veto) { - return false, tallyResults, nonVoting + return false, tallyResults } // If more than 1/2 of non-abstaining voters vote Yes, proposal passes if results[OptionYes].Quo(totalVotingPower.Sub(results[OptionAbstain])).GT(tallyingProcedure.Threshold) { - return true, tallyResults, nonVoting + return true, tallyResults } // If more than 1/2 of non-abstaining voters vote No, proposal fails - SortValAddresses(nonVoting) - - return false, tallyResults, nonVoting + return false, tallyResults } diff --git a/x/gov/tally_test.go b/x/gov/tally_test.go index d8e3e290ca..28fe0cb599 100644 --- a/x/gov/tally_test.go +++ b/x/gov/tally_test.go @@ -51,7 +51,7 @@ func TestTallyNoOneVotes(t *testing.T) { proposal.SetStatus(StatusVotingPeriod) keeper.SetProposal(ctx, proposal) - passes, tallyResults, _ := tally(ctx, keeper, keeper.GetProposal(ctx, proposalID)) + passes, tallyResults := tally(ctx, keeper, keeper.GetProposal(ctx, proposalID)) require.False(t, passes) require.True(t, tallyResults.Equals(EmptyTallyResult())) @@ -80,7 +80,7 @@ func TestTallyOnlyValidatorsAllYes(t *testing.T) { err = keeper.AddVote(ctx, proposalID, addrs[1], OptionYes) require.Nil(t, err) - passes, tallyResults, _ := tally(ctx, keeper, keeper.GetProposal(ctx, proposalID)) + passes, tallyResults := tally(ctx, keeper, keeper.GetProposal(ctx, proposalID)) require.True(t, passes) require.False(t, tallyResults.Equals(EmptyTallyResult())) @@ -109,7 +109,7 @@ func TestTallyOnlyValidators51No(t *testing.T) { err = keeper.AddVote(ctx, proposalID, addrs[1], OptionNo) require.Nil(t, err) - passes, _, _ := tally(ctx, keeper, keeper.GetProposal(ctx, proposalID)) + passes, _ := tally(ctx, keeper, keeper.GetProposal(ctx, proposalID)) require.False(t, passes) } @@ -139,7 +139,7 @@ func TestTallyOnlyValidators51Yes(t *testing.T) { err = keeper.AddVote(ctx, proposalID, addrs[2], OptionNo) require.Nil(t, err) - passes, tallyResults, _ := tally(ctx, keeper, keeper.GetProposal(ctx, proposalID)) + passes, tallyResults := tally(ctx, keeper, keeper.GetProposal(ctx, proposalID)) require.True(t, passes) require.False(t, tallyResults.Equals(EmptyTallyResult())) @@ -170,7 +170,7 @@ func TestTallyOnlyValidatorsVetoed(t *testing.T) { err = keeper.AddVote(ctx, proposalID, addrs[2], OptionNoWithVeto) require.Nil(t, err) - passes, tallyResults, _ := tally(ctx, keeper, keeper.GetProposal(ctx, proposalID)) + passes, tallyResults := tally(ctx, keeper, keeper.GetProposal(ctx, proposalID)) require.False(t, passes) require.False(t, tallyResults.Equals(EmptyTallyResult())) @@ -201,7 +201,7 @@ func TestTallyOnlyValidatorsAbstainPasses(t *testing.T) { err = keeper.AddVote(ctx, proposalID, addrs[2], OptionYes) require.Nil(t, err) - passes, tallyResults, _ := tally(ctx, keeper, keeper.GetProposal(ctx, proposalID)) + passes, tallyResults := tally(ctx, keeper, keeper.GetProposal(ctx, proposalID)) require.True(t, passes) require.False(t, tallyResults.Equals(EmptyTallyResult())) @@ -232,7 +232,7 @@ func TestTallyOnlyValidatorsAbstainFails(t *testing.T) { err = keeper.AddVote(ctx, proposalID, addrs[2], OptionNo) require.Nil(t, err) - passes, tallyResults, _ := tally(ctx, keeper, keeper.GetProposal(ctx, proposalID)) + passes, tallyResults := tally(ctx, keeper, keeper.GetProposal(ctx, proposalID)) require.False(t, passes) require.False(t, tallyResults.Equals(EmptyTallyResult())) @@ -261,11 +261,9 @@ func TestTallyOnlyValidatorsNonVoter(t *testing.T) { err = keeper.AddVote(ctx, proposalID, addrs[2], OptionNo) require.Nil(t, err) - passes, tallyResults, nonVoting := tally(ctx, keeper, keeper.GetProposal(ctx, proposalID)) + passes, tallyResults := tally(ctx, keeper, keeper.GetProposal(ctx, proposalID)) require.False(t, passes) - require.Equal(t, 1, len(nonVoting)) - require.Equal(t, sdk.ValAddress(addrs[0]), nonVoting[0]) require.False(t, tallyResults.Equals(EmptyTallyResult())) } @@ -299,7 +297,7 @@ func TestTallyDelgatorOverride(t *testing.T) { err = keeper.AddVote(ctx, proposalID, addrs[3], OptionNo) require.Nil(t, err) - passes, tallyResults, _ := tally(ctx, keeper, keeper.GetProposal(ctx, proposalID)) + passes, tallyResults := tally(ctx, keeper, keeper.GetProposal(ctx, proposalID)) require.False(t, passes) require.False(t, tallyResults.Equals(EmptyTallyResult())) @@ -333,10 +331,9 @@ func TestTallyDelgatorInherit(t *testing.T) { err = keeper.AddVote(ctx, proposalID, addrs[2], OptionYes) require.Nil(t, err) - passes, tallyResults, nonVoting := tally(ctx, keeper, keeper.GetProposal(ctx, proposalID)) + passes, tallyResults := tally(ctx, keeper, keeper.GetProposal(ctx, proposalID)) require.True(t, passes) - require.Equal(t, 0, len(nonVoting)) require.False(t, tallyResults.Equals(EmptyTallyResult())) } @@ -372,7 +369,7 @@ func TestTallyDelgatorMultipleOverride(t *testing.T) { err = keeper.AddVote(ctx, proposalID, addrs[3], OptionNo) require.Nil(t, err) - passes, tallyResults, _ := tally(ctx, keeper, keeper.GetProposal(ctx, proposalID)) + passes, tallyResults := tally(ctx, keeper, keeper.GetProposal(ctx, proposalID)) require.False(t, passes) require.False(t, tallyResults.Equals(EmptyTallyResult())) @@ -417,7 +414,7 @@ func TestTallyDelgatorMultipleInherit(t *testing.T) { err = keeper.AddVote(ctx, proposalID, addrs[2], OptionNo) require.Nil(t, err) - passes, tallyResults, _ := tally(ctx, keeper, keeper.GetProposal(ctx, proposalID)) + passes, tallyResults := tally(ctx, keeper, keeper.GetProposal(ctx, proposalID)) require.False(t, passes) require.False(t, tallyResults.Equals(EmptyTallyResult())) @@ -458,7 +455,7 @@ func TestTallyJailedValidator(t *testing.T) { err = keeper.AddVote(ctx, proposalID, addrs[2], OptionNo) require.Nil(t, err) - passes, tallyResults, _ := tally(ctx, keeper, keeper.GetProposal(ctx, proposalID)) + passes, tallyResults := tally(ctx, keeper, keeper.GetProposal(ctx, proposalID)) require.True(t, passes) require.False(t, tallyResults.Equals(EmptyTallyResult())) From 04487ca5db67b548f9656c49ffefb85d969b40bb Mon Sep 17 00:00:00 2001 From: Alexander Bezobchuk Date: Tue, 25 Sep 2018 18:47:28 +0000 Subject: [PATCH 11/26] Merge PR #2402: Skip LCD test TestVersion when no env var --- client/lcd/lcd_test.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/client/lcd/lcd_test.go b/client/lcd/lcd_test.go index a45d914771..30fa314c53 100644 --- a/client/lcd/lcd_test.go +++ b/client/lcd/lcd_test.go @@ -120,6 +120,11 @@ func TestKeys(t *testing.T) { } func TestVersion(t *testing.T) { + // skip the test if the VERSION environment variable has not been set + if version.Version == "" { + t.SkipNow() + } + cleanup, _, port := InitializeTestLCD(t, 1, []sdk.AccAddress{}) defer cleanup() From 24413a395d64666d63e58b1783e925e399dc48d9 Mon Sep 17 00:00:00 2001 From: Matthew Slipper Date: Tue, 25 Sep 2018 13:48:38 -0700 Subject: [PATCH 12/26] Merge PR #2073: Allow --from to be a name or an address * Allow --from to be a name or an address Closes #1735. * Post-rebase fixes * Updates from code review * Updates from code review * Updates from code review * Fix merge artifacts * Fix merge conflicts * Fix integration tests * Add back GetFromName() check broken during merge * Code review updates * Fix failing test * Updates from code review --- PENDING.md | 2 + client/context/context.go | 106 +++++++++++++++++++++++++----------- client/context/query.go | 22 ++------ client/flags.go | 2 +- client/utils/utils.go | 23 ++++++-- crypto/keys/keybase.go | 39 +++++++++++-- crypto/keys/keybase_test.go | 19 ++++++- crypto/keys/types.go | 17 +++++- types/address_test.go | 2 +- x/ibc/client/cli/relay.go | 13 ++++- 10 files changed, 178 insertions(+), 67 deletions(-) diff --git a/PENDING.md b/PENDING.md index 1b9238796c..95dcc7c47d 100644 --- a/PENDING.md +++ b/PENDING.md @@ -18,6 +18,8 @@ BREAKING CHANGES utilize a validator's operator address must now use the new Bech32 prefix, `cosmosvaloper`. * [cli] [\#2190](https://github.com/cosmos/cosmos-sdk/issues/2190) `gaiacli init --gen-txs` is now `gaiacli init --with-txs` to reduce confusion + * [cli] \#2073 --from can now be either an address or a key name + * Gaia * Make the transient store key use a distinct store key. [#2013](https://github.com/cosmos/cosmos-sdk/pull/2013) diff --git a/client/context/context.go b/client/context/context.go index 0497761c67..31cfab705a 100644 --- a/client/context/context.go +++ b/client/context/context.go @@ -16,6 +16,9 @@ import ( tmliteProxy "github.com/tendermint/tendermint/lite/proxy" rpcclient "github.com/tendermint/tendermint/rpc/client" "os" + "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/client/keys" + cskeys "github.com/cosmos/cosmos-sdk/crypto/keys" ) const ctxAccStoreName = "acc" @@ -23,22 +26,24 @@ const ctxAccStoreName = "acc" // CLIContext implements a typical CLI context created in SDK modules for // transaction handling and queries. type CLIContext struct { - Codec *codec.Codec - AccDecoder auth.AccountDecoder - Client rpcclient.Client - Logger io.Writer - Height int64 - NodeURI string - FromAddressName string - AccountStore string - TrustNode bool - UseLedger bool - Async bool - JSON bool - PrintResponse bool - Certifier tmlite.Certifier - DryRun bool - GenerateOnly bool + Codec *codec.Codec + AccDecoder auth.AccountDecoder + Client rpcclient.Client + Logger io.Writer + Height int64 + NodeURI string + From string + AccountStore string + TrustNode bool + UseLedger bool + Async bool + JSON bool + PrintResponse bool + Certifier tmlite.Certifier + DryRun bool + GenerateOnly bool + fromAddress types.AccAddress + fromName string } // NewCLIContext returns a new initialized CLIContext with parameters from the @@ -51,20 +56,25 @@ func NewCLIContext() CLIContext { rpc = rpcclient.NewHTTP(nodeURI, "/websocket") } + from := viper.GetString(client.FlagFrom) + fromAddress, fromName := fromFields(from) + return CLIContext{ - Client: rpc, - NodeURI: nodeURI, - AccountStore: ctxAccStoreName, - FromAddressName: viper.GetString(client.FlagFrom), - Height: viper.GetInt64(client.FlagHeight), - TrustNode: viper.GetBool(client.FlagTrustNode), - UseLedger: viper.GetBool(client.FlagUseLedger), - Async: viper.GetBool(client.FlagAsync), - JSON: viper.GetBool(client.FlagJson), - PrintResponse: viper.GetBool(client.FlagPrintResponse), - Certifier: createCertifier(), - DryRun: viper.GetBool(client.FlagDryRun), - GenerateOnly: viper.GetBool(client.FlagGenerateOnly), + Client: rpc, + NodeURI: nodeURI, + AccountStore: ctxAccStoreName, + From: viper.GetString(client.FlagFrom), + Height: viper.GetInt64(client.FlagHeight), + TrustNode: viper.GetBool(client.FlagTrustNode), + UseLedger: viper.GetBool(client.FlagUseLedger), + Async: viper.GetBool(client.FlagAsync), + JSON: viper.GetBool(client.FlagJson), + PrintResponse: viper.GetBool(client.FlagPrintResponse), + Certifier: createCertifier(), + DryRun: viper.GetBool(client.FlagDryRun), + GenerateOnly: viper.GetBool(client.FlagGenerateOnly), + fromAddress: fromAddress, + fromName: fromName, } } @@ -108,6 +118,37 @@ func createCertifier() tmlite.Certifier { return certifier } +func fromFields(from string) (fromAddr types.AccAddress, fromName string) { + if from == "" { + return nil, "" + } + + keybase, err := keys.GetKeyBase() + if err != nil { + fmt.Println("no keybase found") + os.Exit(1) + } + + var info cskeys.Info + if addr, err := types.AccAddressFromBech32(from); err == nil { + info, err = keybase.GetByAddress(addr) + if err != nil { + fmt.Printf("could not find key %s\n", from) + os.Exit(1) + } + } else { + info, err = keybase.Get(from) + if err != nil { + fmt.Printf("could not find key %s\n", from) + os.Exit(1) + } + } + + fromAddr = info.GetAddress() + fromName = info.GetName() + return +} + // WithCodec returns a copy of the context with an updated codec. func (ctx CLIContext) WithCodec(cdc *codec.Codec) CLIContext { ctx.Codec = cdc @@ -133,10 +174,9 @@ func (ctx CLIContext) WithAccountStore(accountStore string) CLIContext { return ctx } -// WithFromAddressName returns a copy of the context with an updated from -// address. -func (ctx CLIContext) WithFromAddressName(addrName string) CLIContext { - ctx.FromAddressName = addrName +// WithFrom returns a copy of the context with an updated from address or name. +func (ctx CLIContext) WithFrom(from string) CLIContext { + ctx.From = from return ctx } diff --git a/client/context/query.go b/client/context/query.go index 9eb70fd8cc..35402fbfbe 100644 --- a/client/context/query.go +++ b/client/context/query.go @@ -4,7 +4,6 @@ import ( "fmt" "io" - "github.com/cosmos/cosmos-sdk/client/keys" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/x/auth" @@ -84,22 +83,13 @@ func (ctx CLIContext) GetAccount(address []byte) (auth.Account, error) { } // GetFromAddress returns the from address from the context's name. -func (ctx CLIContext) GetFromAddress() (from sdk.AccAddress, err error) { - if ctx.FromAddressName == "" { - return nil, errors.Errorf("must provide a from address name") - } +func (ctx CLIContext) GetFromAddress() (sdk.AccAddress, error) { + return ctx.fromAddress, nil +} - keybase, err := keys.GetKeyBase() - if err != nil { - return nil, err - } - - info, err := keybase.Get(ctx.FromAddressName) - if err != nil { - return nil, errors.Errorf("no key for: %s", ctx.FromAddressName) - } - - return sdk.AccAddress(info.GetPubKey().Address()), nil +// GetFromName returns the key name for the current context. +func (ctx CLIContext) GetFromName() (string, error) { + return ctx.fromName, nil } // GetAccountNumber returns the next account number for the given account diff --git a/client/flags.go b/client/flags.go index 55b29b53ca..d0d783d3a7 100644 --- a/client/flags.go +++ b/client/flags.go @@ -58,7 +58,7 @@ func GetCommands(cmds ...*cobra.Command) []*cobra.Command { // PostCommands adds common flags for commands to post tx func PostCommands(cmds ...*cobra.Command) []*cobra.Command { for _, c := range cmds { - c.Flags().String(FlagFrom, "", "Name of private key with which to sign") + c.Flags().String(FlagFrom, "", "Name or address of private key with which to sign") c.Flags().Int64(FlagAccountNumber, 0, "AccountNumber number to sign the tx") c.Flags().Int64(FlagSequence, 0, "Sequence number to sign the tx") c.Flags().String(FlagMemo, "", "Memo to send along with transaction") diff --git a/client/utils/utils.go b/client/utils/utils.go index 81fd138595..76ee91585e 100644 --- a/client/utils/utils.go +++ b/client/utils/utils.go @@ -8,9 +8,9 @@ import ( "github.com/cosmos/cosmos-sdk/client/context" "github.com/cosmos/cosmos-sdk/client/keys" sdk "github.com/cosmos/cosmos-sdk/types" - auth "github.com/cosmos/cosmos-sdk/x/auth" + "github.com/cosmos/cosmos-sdk/x/auth" authtxb "github.com/cosmos/cosmos-sdk/x/auth/client/txbuilder" - amino "github.com/tendermint/go-amino" + "github.com/tendermint/go-amino" "github.com/tendermint/tendermint/libs/common" ) @@ -25,8 +25,13 @@ func SendTx(txBldr authtxb.TxBuilder, cliCtx context.CLIContext, msgs []sdk.Msg) return err } + name, err := cliCtx.GetFromName() + if err != nil { + return err + } + if txBldr.SimulateGas || cliCtx.DryRun { - txBldr, err = EnrichCtxWithGas(txBldr, cliCtx, cliCtx.FromAddressName, msgs) + txBldr, err = EnrichCtxWithGas(txBldr, cliCtx, name, msgs) if err != nil { return err } @@ -36,13 +41,13 @@ func SendTx(txBldr authtxb.TxBuilder, cliCtx context.CLIContext, msgs []sdk.Msg) return nil } - passphrase, err := keys.GetPassphrase(cliCtx.FromAddressName) + passphrase, err := keys.GetPassphrase(name) if err != nil { return err } // build and sign the transaction - txBytes, err := txBldr.BuildAndSign(cliCtx.FromAddressName, passphrase, msgs) + txBytes, err := txBldr.BuildAndSign(name, passphrase, msgs) if err != nil { return err } @@ -196,7 +201,13 @@ func buildUnsignedStdTx(txBldr authtxb.TxBuilder, cliCtx context.CLIContext, msg return } if txBldr.SimulateGas { - txBldr, err = EnrichCtxWithGas(txBldr, cliCtx, cliCtx.FromAddressName, msgs) + var name string + name, err = cliCtx.GetFromName() + if err != nil { + return + } + + txBldr, err = EnrichCtxWithGas(txBldr, cliCtx, name, msgs) if err != nil { return } diff --git a/crypto/keys/keybase.go b/crypto/keys/keybase.go index 0bf7ad9903..c9bfd08129 100644 --- a/crypto/keys/keybase.go +++ b/crypto/keys/keybase.go @@ -14,6 +14,7 @@ import ( "github.com/tendermint/tendermint/crypto/encoding/amino" "github.com/tendermint/tendermint/crypto/secp256k1" dbm "github.com/tendermint/tendermint/libs/db" + "github.com/cosmos/cosmos-sdk/types" ) var _ Keybase = dbKeybase{} @@ -41,6 +42,8 @@ const ( French // Italian is currently not supported. Italian + addressSuffix = "address" + infoSuffix = "info" ) var ( @@ -179,11 +182,16 @@ func (kb dbKeybase) List() ([]Info, error) { iter := kb.db.Iterator(nil, nil) defer iter.Close() for ; iter.Valid(); iter.Next() { - info, err := readInfo(iter.Value()) - if err != nil { - return nil, err + key := string(iter.Key()) + + // need to include only keys in storage that have an info suffix + if strings.HasSuffix(key, infoSuffix) { + info, err := readInfo(iter.Value()) + if err != nil { + return nil, err + } + res = append(res, info) } - res = append(res, info) } return res, nil } @@ -197,6 +205,15 @@ func (kb dbKeybase) Get(name string) (Info, error) { return readInfo(bs) } +func (kb dbKeybase) GetByAddress(address types.AccAddress) (Info, error) { + ik := kb.db.Get(addrKey(address)) + if len(ik) == 0 { + return nil, fmt.Errorf("key with address %s not found", address) + } + bs := kb.db.Get(ik) + return readInfo(bs) +} + // Sign signs the msg with the named key. // It returns an error if the key doesn't exist or the decryption fails. func (kb dbKeybase) Sign(name, passphrase string, msg []byte) (sig []byte, pub tmcrypto.PubKey, err error) { @@ -347,6 +364,7 @@ func (kb dbKeybase) Delete(name, passphrase string) error { if err != nil { return err } + kb.db.DeleteSync(addrKey(linfo.GetAddress())) kb.db.DeleteSync(infoKey(name)) return nil case ledgerInfo: @@ -354,9 +372,11 @@ func (kb dbKeybase) Delete(name, passphrase string) error { if passphrase != "yes" { return fmt.Errorf("enter 'yes' exactly to delete the key - this cannot be undone") } + kb.db.DeleteSync(addrKey(info.GetAddress())) kb.db.DeleteSync(infoKey(name)) return nil } + return nil } @@ -413,9 +433,16 @@ func (kb dbKeybase) writeOfflineKey(pub tmcrypto.PubKey, name string) Info { func (kb dbKeybase) writeInfo(info Info, name string) { // write the info by key - kb.db.SetSync(infoKey(name), writeInfo(info)) + key := infoKey(name) + kb.db.SetSync(key, writeInfo(info)) + // store a pointer to the infokey by address for fast lookup + kb.db.SetSync(addrKey(info.GetAddress()), key) +} + +func addrKey(address types.AccAddress) []byte { + return []byte(fmt.Sprintf("%s.%s", address.String(), addressSuffix)) } func infoKey(name string) []byte { - return []byte(fmt.Sprintf("%s.info", name)) + return []byte(fmt.Sprintf("%s.%s", name, infoSuffix)) } diff --git a/crypto/keys/keybase_test.go b/crypto/keys/keybase_test.go index 652a36bcb0..2a602461a1 100644 --- a/crypto/keys/keybase_test.go +++ b/crypto/keys/keybase_test.go @@ -11,6 +11,7 @@ import ( "github.com/tendermint/tendermint/crypto/ed25519" dbm "github.com/tendermint/tendermint/libs/db" + "github.com/cosmos/cosmos-sdk/types" ) func init() { @@ -20,8 +21,9 @@ func init() { // TestKeyManagement makes sure we can manipulate these keys well func TestKeyManagement(t *testing.T) { // make the storage with reasonable defaults + db := dbm.NewMemDB() cstore := New( - dbm.NewMemDB(), + db, ) algo := Secp256k1 @@ -51,6 +53,12 @@ func TestKeyManagement(t *testing.T) { require.NoError(t, err) _, err = cstore.Get(n3) require.NotNil(t, err) + _, err = cstore.GetByAddress(accAddr(i2)) + require.NoError(t, err) + addr, err := types.AccAddressFromBech32("cosmos1yq8lgssgxlx9smjhes6ryjasmqmd3ts2559g0t") + require.NoError(t, err) + _, err = cstore.GetByAddress(addr) + require.NotNil(t, err) // list shows them in order keyS, err := cstore.List() @@ -92,6 +100,11 @@ func TestKeyManagement(t *testing.T) { keyS, err = cstore.List() require.NoError(t, err) require.Equal(t, 1, len(keyS)) + + // addr cache gets nuked + err = cstore.Delete(n2, p2) + require.NoError(t, err) + require.False(t, db.Has(addrKey(i2.GetAddress()))) } // TestSignVerify does some detailed checks on how we sign and validate @@ -387,3 +400,7 @@ func ExampleNew() { // Carl // signed by Bob } + +func accAddr(info Info) types.AccAddress { + return (types.AccAddress)(info.GetPubKey().Address()) +} \ No newline at end of file diff --git a/crypto/keys/types.go b/crypto/keys/types.go index a686264897..c5e97d5fba 100644 --- a/crypto/keys/types.go +++ b/crypto/keys/types.go @@ -5,14 +5,15 @@ import ( "github.com/tendermint/tendermint/crypto" "github.com/cosmos/cosmos-sdk/crypto/keys/hd" + "github.com/cosmos/cosmos-sdk/types" ) // Keybase exposes operations on a generic keystore type Keybase interface { - // CRUD on the keystore List() ([]Info, error) Get(name string) (Info, error) + GetByAddress(address types.AccAddress) (Info, error) Delete(name, passphrase string) error // Sign some bytes, looking up the private key to use @@ -73,6 +74,8 @@ type Info interface { GetName() string // Public key GetPubKey() crypto.PubKey + // Address + GetAddress() types.AccAddress } var _ Info = &localInfo{} @@ -106,6 +109,10 @@ func (i localInfo) GetPubKey() crypto.PubKey { return i.PubKey } +func (i localInfo) GetAddress() types.AccAddress { + return i.PubKey.Address().Bytes() +} + // ledgerInfo is the public information about a Ledger key type ledgerInfo struct { Name string `json:"name"` @@ -133,6 +140,10 @@ func (i ledgerInfo) GetPubKey() crypto.PubKey { return i.PubKey } +func (i ledgerInfo) GetAddress() types.AccAddress { + return i.PubKey.Address().Bytes() +} + // offlineInfo is the public information about an offline key type offlineInfo struct { Name string `json:"name"` @@ -158,6 +169,10 @@ func (i offlineInfo) GetPubKey() crypto.PubKey { return i.PubKey } +func (i offlineInfo) GetAddress() types.AccAddress { + return i.PubKey.Address().Bytes() +} + // encoding info func writeInfo(i Info) []byte { return cdc.MustMarshalBinary(i) diff --git a/types/address_test.go b/types/address_test.go index e2ec36876c..6c6c78d6ec 100644 --- a/types/address_test.go +++ b/types/address_test.go @@ -10,7 +10,7 @@ import ( "github.com/tendermint/tendermint/crypto/ed25519" "github.com/cosmos/cosmos-sdk/types" -) + ) var invalidStrs = []string{ "", diff --git a/x/ibc/client/cli/relay.go b/x/ibc/client/cli/relay.go index a57fac7054..ab7168aca3 100644 --- a/x/ibc/client/cli/relay.go +++ b/x/ibc/client/cli/relay.go @@ -94,7 +94,11 @@ func (c relayCommander) runIBCRelay(cmd *cobra.Command, args []string) { func (c relayCommander) loop(fromChainID, fromChainNode, toChainID, toChainNode string) { cliCtx := context.NewCLIContext() - passphrase, err := keys.ReadPassphraseFromStdin(cliCtx.FromAddressName) + name, err := cliCtx.GetFromName() + if err != nil { + panic(err) + } + passphrase, err := keys.ReadPassphraseFromStdin(name) if err != nil { panic(err) } @@ -201,7 +205,12 @@ func (c relayCommander) refine(bz []byte, sequence int64, passphrase string) []b txBldr := authtxb.NewTxBuilderFromCLI().WithSequence(sequence).WithCodec(c.cdc) cliCtx := context.NewCLIContext() - res, err := txBldr.BuildAndSign(cliCtx.FromAddressName, passphrase, []sdk.Msg{msg}) + name, err := cliCtx.GetFromName() + if err != nil { + panic(err) + } + + res, err := txBldr.BuildAndSign(name, passphrase, []sdk.Msg{msg}) if err != nil { panic(err) } From 2fb3493ff57c32d13c3c20c6270d8dc694e03b54 Mon Sep 17 00:00:00 2001 From: Matthew Slipper Date: Tue, 25 Sep 2018 14:36:42 -0700 Subject: [PATCH 13/26] Merge PR #2223: Gaia CLI Config Command * Allow a gaia-cli config file to be created Closes #1613. Closes #1275. Closes #1956. * Add homedir to Gopkg.toml * Updates from code review * Post-rebase fixes * Update test * Code review refactor * Fixes from code review * Fix import * Fix broken test * Fixes from rebase * Fix formatting --- Gopkg.lock | 10 ++ Gopkg.toml | 4 + PENDING.md | 1 + client/config.go | 131 +++++++++++++++++++++++++++ client/flags.go | 9 ++ client/input.go | 18 ++++ client/lcd/root.go | 3 + client/rpc/block.go | 3 + client/rpc/root.go | 4 + client/rpc/status.go | 2 + client/rpc/validators.go | 4 + client/tx/query.go | 8 +- client/tx/search.go | 5 +- cmd/gaia/cli_test/cli_test.go | 43 +++++++++ cmd/gaia/cmd/gaiacli/main.go | 32 ++++++- types/utils.go | 25 ++++- x/auth/client/txbuilder/txbuilder.go | 2 +- x/auth/client/txbuilder/utils.go | 25 ----- 18 files changed, 298 insertions(+), 31 deletions(-) create mode 100644 client/config.go delete mode 100644 x/auth/client/txbuilder/utils.go diff --git a/Gopkg.lock b/Gopkg.lock index d64538d36b..00eafa4ade 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -229,6 +229,14 @@ revision = "c12348ce28de40eed0136aa2b644d0ee0650e56c" version = "v1.0.1" +[[projects]] + digest = "1:78bbb1ba5b7c3f2ed0ea1eab57bdd3859aec7e177811563edc41198a760b06af" + name = "github.com/mitchellh/go-homedir" + packages = ["."] + pruneopts = "UT" + revision = "ae18d6b8b3205b561c79e8e5f69bff09736185f4" + version = "v1.0.0" + [[projects]] digest = "1:645110e089152bd0f4a011a2648fbb0e4df5977be73ca605781157ac297f50c4" name = "github.com/mitchellh/mapstructure" @@ -630,6 +638,8 @@ "github.com/golang/protobuf/proto", "github.com/gorilla/mux", "github.com/mattn/go-isatty", + "github.com/mitchellh/go-homedir", + "github.com/pelletier/go-toml", "github.com/pkg/errors", "github.com/spf13/cobra", "github.com/spf13/pflag", diff --git a/Gopkg.toml b/Gopkg.toml index 2b5928a897..3080e3cd35 100644 --- a/Gopkg.toml +++ b/Gopkg.toml @@ -70,3 +70,7 @@ [prune] go-tests = true unused-packages = true + +[[constraint]] + name = "github.com/mitchellh/go-homedir" + version = "1.0.0" diff --git a/PENDING.md b/PENDING.md index 95dcc7c47d..a4abb336bd 100644 --- a/PENDING.md +++ b/PENDING.md @@ -81,6 +81,7 @@ FEATURES * [\#966](https://github.com/cosmos/cosmos-sdk/issues/966) Add --generate-only flag to build an unsigned transaction and write it to STDOUT. * [\#1953](https://github.com/cosmos/cosmos-sdk/issues/1953) New `sign` command to sign transactions generated with the --generate-only flag. * [\#1954](https://github.com/cosmos/cosmos-sdk/issues/1954) New `broadcast` command to broadcast transactions generated offline and signed with the `sign` command. + * [cli] \#2220 Add `gaiacli config` feature to interactively create CLI config files to reduce the number of required flags * [stake][cli] [\#1672](https://github.com/cosmos/cosmos-sdk/issues/1672) Introduced new commission flags for validator commands `create-validator` and `edit-validator`. diff --git a/client/config.go b/client/config.go new file mode 100644 index 0000000000..fcb2523753 --- /dev/null +++ b/client/config.go @@ -0,0 +1,131 @@ +package client + +import ( + "github.com/spf13/cobra" + "github.com/mitchellh/go-homedir" + "bufio" + "path" + "os" + "io/ioutil" + "github.com/pelletier/go-toml" + "fmt" + "github.com/cosmos/cosmos-sdk/types" +) + +type cliConfig struct { + Home string `toml:"home"` + ChainID string `toml:"chain_id"` + TrustNode bool `toml:"trust_node"` + Encoding string `toml:"encoding"` + Output string `toml:"output"` + Node string `toml:"node"` + Trace bool `toml:"trace"` +} + +// ConfigCmd returns a CLI command to interactively create a +// Gaia CLI config file. +func ConfigCmd() *cobra.Command { + cfg := &cobra.Command{ + Use: "config", + Short: "Interactively creates a Gaia CLI config file", + RunE: runConfigCmd, + } + + return cfg +} + +func runConfigCmd(cmd *cobra.Command, args [] string) error { + home, err := homedir.Dir() + if err != nil { + return err + } + + stdin := BufferStdin() + gaiaCLIHome, err := handleGaiaCLIHome(home, stdin) + if err != nil { + return err + } + node, err := handleNode(stdin) + if err != nil { + return err + } + trustNode, err := handleTrustNode(stdin) + if err != nil { + return err + } + + encoding := "btc" + output := "text" + var chainID string + chainID, err = types.DefaultChainID() + if err != nil { + fmt.Println("Couldn't populate ChainID, so using an empty one.") + } + + cfg := &cliConfig{ + Home: gaiaCLIHome, + ChainID: chainID, + TrustNode: trustNode, + Encoding: encoding, + Output: output, + Node: node, + Trace: false, + } + + return createGaiaCLIConfig(cfg) +} + +func handleGaiaCLIHome(dir string, stdin *bufio.Reader) (string, error) { + dirName := ".gaiacli" + home, err := GetString(fmt.Sprintf("Where is your gaiacli home directory? (Default: ~/%s)", dirName), stdin) + if err != nil { + return "", err + } + + if home == "" { + home = path.Join(dir, dirName) + } + + return home, nil +} + +func handleNode(stdin *bufio.Reader) (string, error) { + defaultNode := "tcp://localhost:26657" + node, err := GetString(fmt.Sprintf("Where is your validator node running? (Default: %s)", defaultNode), stdin) + if err != nil { + return "", err + } + + if node == "" { + node = defaultNode + } + + return node, nil +} + +func handleTrustNode(stdin *bufio.Reader) (bool, error) { + return GetConfirmation("Do you trust this node?", stdin) +} + +func createGaiaCLIConfig(cfg *cliConfig) error { + cfgPath := path.Join(cfg.Home, "config") + err := os.MkdirAll(cfgPath, os.ModePerm) + if err != nil { + return err + } + + data, err := toml.Marshal(*cfg) + if err != nil { + return err + } + + cfgFile := path.Join(cfgPath, "config.toml") + if info, err := os.Stat(cfgFile); err == nil && !info.IsDir() { + err = os.Rename(cfgFile, path.Join(cfgPath, "config.toml-old")) + if err != nil { + return err + } + } + + return ioutil.WriteFile(cfgFile, data, os.ModePerm) +} diff --git a/client/flags.go b/client/flags.go index d0d783d3a7..ff93549370 100644 --- a/client/flags.go +++ b/client/flags.go @@ -5,6 +5,7 @@ import ( "strconv" "github.com/spf13/cobra" + "github.com/spf13/viper" ) // nolint @@ -51,6 +52,10 @@ func GetCommands(cmds ...*cobra.Command) []*cobra.Command { c.Flags().String(FlagChainID, "", "Chain ID of tendermint node") c.Flags().String(FlagNode, "tcp://localhost:26657", ": to tendermint rpc interface for this chain") c.Flags().Int64(FlagHeight, 0, "block height to query, omit to get most recent provable block") + viper.BindPFlag(FlagTrustNode, c.Flags().Lookup(FlagTrustNode)) + viper.BindPFlag(FlagUseLedger, c.Flags().Lookup(FlagUseLedger)) + viper.BindPFlag(FlagChainID, c.Flags().Lookup(FlagChainID)) + viper.BindPFlag(FlagNode, c.Flags().Lookup(FlagNode)) } return cmds } @@ -76,6 +81,10 @@ func PostCommands(cmds ...*cobra.Command) []*cobra.Command { // --gas can accept integers and "simulate" c.Flags().Var(&GasFlagVar, "gas", fmt.Sprintf( "gas limit to set per-transaction; set to %q to calculate required gas automatically (default %d)", GasFlagSimulate, DefaultGasLimit)) + viper.BindPFlag(FlagTrustNode, c.Flags().Lookup(FlagTrustNode)) + viper.BindPFlag(FlagUseLedger, c.Flags().Lookup(FlagUseLedger)) + viper.BindPFlag(FlagChainID, c.Flags().Lookup(FlagChainID)) + viper.BindPFlag(FlagNode, c.Flags().Lookup(FlagNode)) } return cmds } diff --git a/client/input.go b/client/input.go index b10f65ce62..a456f1b92a 100644 --- a/client/input.go +++ b/client/input.go @@ -100,6 +100,19 @@ func GetConfirmation(prompt string, buf *bufio.Reader) (bool, error) { } } +// GetString simply returns the trimmed string output of a given reader. +func GetString(prompt string, buf *bufio.Reader) (string, error) { + if inputIsTty() && prompt != "" { + PrintPrefixed(prompt) + } + + out, err := readLineFromBuf(buf) + if err != nil { + return "", err + } + return strings.TrimSpace(out), nil +} + // inputIsTty returns true iff we have an interactive prompt, // where we can disable echo and request to repeat the password. // If false, we can optimize for piped input from another command @@ -117,3 +130,8 @@ func readLineFromBuf(buf *bufio.Reader) (string, error) { } return strings.TrimSpace(pass), nil } + +// PrintPrefixed prints a string with > prefixed for use in prompts. +func PrintPrefixed(msg string) { + fmt.Printf("> %s\n", msg) +} diff --git a/client/lcd/root.go b/client/lcd/root.go index 36fbd42d0b..84d3e806c9 100644 --- a/client/lcd/root.go +++ b/client/lcd/root.go @@ -121,6 +121,9 @@ func ServeCommand(cdc *codec.Codec) *cobra.Command { cmd.Flags().String(client.FlagNode, "tcp://localhost:26657", "Address of the node to connect to") cmd.Flags().Int(flagMaxOpenConnections, 1000, "The number of maximum open connections") cmd.Flags().Bool(client.FlagTrustNode, false, "Trust connected full node (don't verify proofs for responses)") + viper.BindPFlag(client.FlagTrustNode, cmd.Flags().Lookup(client.FlagTrustNode)) + viper.BindPFlag(client.FlagChainID, cmd.Flags().Lookup(client.FlagChainID)) + viper.BindPFlag(client.FlagNode, cmd.Flags().Lookup(client.FlagNode)) return cmd } diff --git a/client/rpc/block.go b/client/rpc/block.go index 3824bc3e5c..44def2d323 100644 --- a/client/rpc/block.go +++ b/client/rpc/block.go @@ -10,6 +10,7 @@ import ( "github.com/gorilla/mux" "github.com/spf13/cobra" + "github.com/spf13/viper" tmliteProxy "github.com/tendermint/tendermint/lite/proxy" ) @@ -22,7 +23,9 @@ func BlockCommand() *cobra.Command { RunE: printBlock, } cmd.Flags().StringP(client.FlagNode, "n", "tcp://localhost:26657", "Node to connect to") + viper.BindPFlag(client.FlagNode, cmd.Flags().Lookup(client.FlagNode)) cmd.Flags().Bool(client.FlagTrustNode, false, "Trust connected full node (don't verify proofs for responses)") + viper.BindPFlag(client.FlagTrustNode, cmd.Flags().Lookup(client.FlagTrustNode)) cmd.Flags().String(client.FlagChainID, "", "Chain ID of Tendermint node") return cmd } diff --git a/client/rpc/root.go b/client/rpc/root.go index a8171a293a..74c5a69a2e 100644 --- a/client/rpc/root.go +++ b/client/rpc/root.go @@ -7,6 +7,7 @@ import ( "github.com/cosmos/cosmos-sdk/client" "github.com/cosmos/cosmos-sdk/client/context" + "github.com/spf13/viper" ) const ( @@ -40,6 +41,9 @@ func initClientCommand() *cobra.Command { cmd.Flags().String(flagGenesis, "", "Genesis file to verify header validity") cmd.Flags().String(flagCommit, "", "File with trusted and signed header") cmd.Flags().String(flagValHash, "", "Hash of trusted validator set (hex-encoded)") + viper.BindPFlag(client.FlagChainID, cmd.Flags().Lookup(client.FlagChainID)) + viper.BindPFlag(client.FlagNode, cmd.Flags().Lookup(client.FlagNode)) + return cmd } diff --git a/client/rpc/status.go b/client/rpc/status.go index ea090d32f9..6c42701754 100644 --- a/client/rpc/status.go +++ b/client/rpc/status.go @@ -10,6 +10,7 @@ import ( "github.com/cosmos/cosmos-sdk/client" "github.com/cosmos/cosmos-sdk/client/context" ctypes "github.com/tendermint/tendermint/rpc/core/types" + "github.com/spf13/viper" ) func statusCommand() *cobra.Command { @@ -20,6 +21,7 @@ func statusCommand() *cobra.Command { } cmd.Flags().StringP(client.FlagNode, "n", "tcp://localhost:26657", "Node to connect to") + viper.BindPFlag(client.FlagNode, cmd.Flags().Lookup(client.FlagNode)) return cmd } diff --git a/client/rpc/validators.go b/client/rpc/validators.go index 0583dc0545..d8572d5444 100644 --- a/client/rpc/validators.go +++ b/client/rpc/validators.go @@ -13,6 +13,7 @@ import ( "github.com/cosmos/cosmos-sdk/client/context" sdk "github.com/cosmos/cosmos-sdk/types" tmtypes "github.com/tendermint/tendermint/types" + "github.com/spf13/viper" ) // TODO these next two functions feel kinda hacky based on their placement @@ -26,8 +27,11 @@ func ValidatorCommand() *cobra.Command { RunE: printValidators, } cmd.Flags().StringP(client.FlagNode, "n", "tcp://localhost:26657", "Node to connect to") + viper.BindPFlag(client.FlagNode, cmd.Flags().Lookup(client.FlagNode)) cmd.Flags().Bool(client.FlagTrustNode, false, "Trust connected full node (don't verify proofs for responses)") + viper.BindPFlag(client.FlagTrustNode, cmd.Flags().Lookup(client.FlagTrustNode)) cmd.Flags().String(client.FlagChainID, "", "Chain ID of Tendermint node") + viper.BindPFlag(client.FlagChainID, cmd.Flags().Lookup(client.FlagChainID)) return cmd } diff --git a/client/tx/query.go b/client/tx/query.go index 732c11b659..f1d13b608a 100644 --- a/client/tx/query.go +++ b/client/tx/query.go @@ -3,8 +3,8 @@ package tx import ( "encoding/hex" "fmt" - "github.com/tendermint/tendermint/libs/common" "net/http" + "github.com/tendermint/tendermint/libs/common" "github.com/gorilla/mux" "github.com/spf13/cobra" @@ -16,6 +16,7 @@ import ( "github.com/cosmos/cosmos-sdk/codec" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/x/auth" + "github.com/spf13/viper" ) // QueryTxCmd implements the default command for a tx query. @@ -41,8 +42,11 @@ func QueryTxCmd(cdc *codec.Codec) *cobra.Command { } cmd.Flags().StringP(client.FlagNode, "n", "tcp://localhost:26657", "Node to connect to") - cmd.Flags().Bool(client.FlagTrustNode, false, "Trust connected full node (don't verify proofs for responses)") + viper.BindPFlag(client.FlagNode, cmd.Flags().Lookup(client.FlagNode)) cmd.Flags().String(client.FlagChainID, "", "Chain ID of Tendermint node") + viper.BindPFlag(client.FlagChainID, cmd.Flags().Lookup(client.FlagChainID)) + cmd.Flags().Bool(client.FlagTrustNode, false, "Trust connected full node (don't verify proofs for responses)") + viper.BindPFlag(client.FlagTrustNode, cmd.Flags().Lookup(client.FlagTrustNode)) return cmd } diff --git a/client/tx/search.go b/client/tx/search.go index 5bc8726db1..8d83c44b3e 100644 --- a/client/tx/search.go +++ b/client/tx/search.go @@ -62,8 +62,11 @@ $ gaiacli tendermint txs --tag test1,test2 --any } cmd.Flags().StringP(client.FlagNode, "n", "tcp://localhost:26657", "Node to connect to") - cmd.Flags().Bool(client.FlagTrustNode, false, "Trust connected full node (don't verify proofs for responses)") + viper.BindPFlag(client.FlagNode, cmd.Flags().Lookup(client.FlagNode)) cmd.Flags().String(client.FlagChainID, "", "Chain ID of Tendermint node") + viper.BindPFlag(client.FlagChainID, cmd.Flags().Lookup(client.FlagChainID)) + cmd.Flags().Bool(client.FlagTrustNode, false, "Trust connected full node (don't verify proofs for responses)") + viper.BindPFlag(client.FlagTrustNode, cmd.Flags().Lookup(client.FlagTrustNode)) cmd.Flags().StringSlice(flagTags, nil, "Comma-separated list of tags that must match") cmd.Flags().Bool(flagAny, false, "Return transactions that match ANY tag, rather than ALL") return cmd diff --git a/cmd/gaia/cli_test/cli_test.go b/cmd/gaia/cli_test/cli_test.go index 3c847dc90e..8e31c664bc 100644 --- a/cmd/gaia/cli_test/cli_test.go +++ b/cmd/gaia/cli_test/cli_test.go @@ -8,6 +8,7 @@ import ( "io/ioutil" "os" "testing" + "path" "github.com/stretchr/testify/require" @@ -515,6 +516,48 @@ func TestGaiaCLISendGenerateSignAndBroadcast(t *testing.T) { require.Equal(t, int64(40), fooAcc.GetCoins().AmountOf("steak").Int64()) } +func TestGaiaCLIConfig(t *testing.T) { + require.NoError(t, os.RemoveAll(gaiacliHome)) + require.NoError(t, os.RemoveAll(gaiadHome)) + servAddr, port, err := server.FreeTCPAddr() + require.NoError(t, err) + node := fmt.Sprintf("%s:%s", servAddr, port) + chainID := executeInit(t, fmt.Sprintf("gaiad init -o --name=foo --home=%s --home-client=%s", gaiadHome, gaiacliHome)) + executeWrite(t, fmt.Sprintf("gaiacli --home=%s config", gaiadHome), gaiacliHome, node, "y") + config, err := ioutil.ReadFile(path.Join(gaiacliHome, "config", "config.toml")) + require.NoError(t, err) + expectedConfig := fmt.Sprintf(`chain_id = "%s" +encoding = "btc" +home = "%s" +node = "%s" +output = "text" +trace = false +trust_node = true +`, chainID, gaiacliHome, node) + require.Equal(t, expectedConfig, string(config)) + // ensure a backup gets created + executeWrite(t, "gaiacli config", gaiacliHome, node, "y", "y") + configBackup, err := ioutil.ReadFile(path.Join(gaiacliHome, "config", "config.toml-old")) + require.NoError(t, err) + require.Equal(t, expectedConfig, string(configBackup)) + + require.NoError(t, os.RemoveAll(gaiadHome)) + executeWrite(t, "gaiacli config", gaiacliHome, node, "y") + + // ensure it works without an initialized gaiad state + expectedConfig = fmt.Sprintf(`chain_id = "" +encoding = "btc" +home = "%s" +node = "%s" +output = "text" +trace = false +trust_node = true +`, gaiacliHome, node) + config, err = ioutil.ReadFile(path.Join(gaiacliHome, "config", "config.toml")) + require.NoError(t, err) + require.Equal(t, expectedConfig, string(config)) +} + //___________________________________________________________________________________ // helper methods diff --git a/cmd/gaia/cmd/gaiacli/main.go b/cmd/gaia/cmd/gaiacli/main.go index c61b30056f..fee43dc02b 100644 --- a/cmd/gaia/cmd/gaiacli/main.go +++ b/cmd/gaia/cmd/gaiacli/main.go @@ -18,6 +18,9 @@ import ( stakecmd "github.com/cosmos/cosmos-sdk/x/stake/client/cli" "github.com/cosmos/cosmos-sdk/cmd/gaia/app" + "path" + "os" + "github.com/spf13/viper" ) // rootCmd is the entry point for this binary @@ -35,6 +38,7 @@ func main() { // TODO: setup keybase, viper object, etc. to be passed into // the below functions and eliminate global vars, like we do // with the cdc + rootCmd.AddCommand(client.ConfigCmd()) // add standard rpc commands rpc.AddCommands(rootCmd) @@ -131,9 +135,35 @@ func main() { // prepare and add flags executor := cli.PrepareMainCmd(rootCmd, "GA", app.DefaultCLIHome) - err := executor.Execute() + err := initConfig(rootCmd) + if err != nil { + panic(err) + } + + err = executor.Execute() if err != nil { // handle with #870 panic(err) } } + +func initConfig(cmd *cobra.Command) error { + home, err := cmd.PersistentFlags().GetString(cli.HomeFlag) + if err != nil { + return err + } + + cfgFile := path.Join(home, "config", "config.toml") + if _, err := os.Stat(cfgFile); err == nil { + viper.SetConfigFile(cfgFile) + + if err := viper.ReadInConfig(); err != nil { + return err + } + } + + if err := viper.BindPFlag(cli.EncodingFlag, cmd.PersistentFlags().Lookup(cli.EncodingFlag)); err != nil { + return err + } + return viper.BindPFlag(cli.OutputFlag, cmd.PersistentFlags().Lookup(cli.OutputFlag)) +} diff --git a/types/utils.go b/types/utils.go index 2e027676a0..95fc779d73 100644 --- a/types/utils.go +++ b/types/utils.go @@ -1,6 +1,10 @@ package types -import "encoding/json" +import ( + "encoding/json" + tcmd "github.com/tendermint/tendermint/cmd/tendermint/commands" + tmtypes "github.com/tendermint/tendermint/types" +) // SortedJSON takes any JSON and returns it sorted by keys. Also, all white-spaces // are removed. @@ -29,3 +33,22 @@ func MustSortJSON(toSortJSON []byte) []byte { } return js } + +// DefaultChainID returns the chain ID from the genesis file if present. An +// error is returned if the file cannot be read or parsed. +// +// TODO: This should be removed and the chainID should always be provided by +// the end user. +func DefaultChainID() (string, error) { + cfg, err := tcmd.ParseConfig() + if err != nil { + return "", err + } + + doc, err := tmtypes.GenesisDocFromFile(cfg.GenesisFile()) + if err != nil { + return "", err + } + + return doc.ChainID, nil +} \ No newline at end of file diff --git a/x/auth/client/txbuilder/txbuilder.go b/x/auth/client/txbuilder/txbuilder.go index 030ddb72a4..642893b52f 100644 --- a/x/auth/client/txbuilder/txbuilder.go +++ b/x/auth/client/txbuilder/txbuilder.go @@ -30,7 +30,7 @@ func NewTxBuilderFromCLI() TxBuilder { // if chain ID is not specified manually, read default chain ID chainID := viper.GetString(client.FlagChainID) if chainID == "" { - defaultChainID, err := defaultChainID() + defaultChainID, err := sdk.DefaultChainID() if err != nil { chainID = defaultChainID } diff --git a/x/auth/client/txbuilder/utils.go b/x/auth/client/txbuilder/utils.go deleted file mode 100644 index 22cc8220bb..0000000000 --- a/x/auth/client/txbuilder/utils.go +++ /dev/null @@ -1,25 +0,0 @@ -package context - -import ( - tcmd "github.com/tendermint/tendermint/cmd/tendermint/commands" - tmtypes "github.com/tendermint/tendermint/types" -) - -// defaultChainID returns the chain ID from the genesis file if present. An -// error is returned if the file cannot be read or parsed. -// -// TODO: This should be removed and the chainID should always be provided by -// the end user. -func defaultChainID() (string, error) { - cfg, err := tcmd.ParseConfig() - if err != nil { - return "", err - } - - doc, err := tmtypes.GenesisDocFromFile(cfg.GenesisFile()) - if err != nil { - return "", err - } - - return doc.ChainID, nil -} From b54801b4b395deb5d71534dc21895a61d8599caa Mon Sep 17 00:00:00 2001 From: Rigel Date: Wed, 26 Sep 2018 02:11:57 -0400 Subject: [PATCH 14/26] [RETRY] Distr-PR-3 More staking hooks (#2404) * update commission hook for new commission work * comment update --- PENDING.md | 1 + cmd/gaia/app/app.go | 2 +- docs/spec/staking/hooks.md | 19 +++++++++++++ types/stake.go | 25 +++++++++++++---- x/slashing/hooks.go | 12 ++++++-- x/slashing/keeper_test.go | 6 ++-- x/stake/handler.go | 7 +++++ x/stake/keeper/delegation.go | 5 +++- x/stake/keeper/hooks.go | 54 ++++++++++++++++++++++++++++++++++++ x/stake/keeper/keeper.go | 4 +-- x/stake/keeper/validator.go | 17 ++++-------- 11 files changed, 125 insertions(+), 27 deletions(-) create mode 100644 docs/spec/staking/hooks.md create mode 100644 x/stake/keeper/hooks.go diff --git a/PENDING.md b/PENDING.md index a4abb336bd..c432ec3e07 100644 --- a/PENDING.md +++ b/PENDING.md @@ -56,6 +56,7 @@ BREAKING CHANGES * [types] \#2343 Make sdk.Msg have a names field, to facilitate automatic tagging. * [baseapp] \#2366 Automatically add action tags to all messages * [x/staking] \#2244 staking now holds a consensus-address-index instead of a consensus-pubkey-index + * [x/staking] \#2236 more distribution hooks for distribution * Tendermint diff --git a/cmd/gaia/app/app.go b/cmd/gaia/app/app.go index 353825121a..31b912a9de 100644 --- a/cmd/gaia/app/app.go +++ b/cmd/gaia/app/app.go @@ -91,7 +91,7 @@ func NewGaiaApp(logger log.Logger, db dbm.DB, traceStore io.Writer, baseAppOptio app.paramsKeeper = params.NewKeeper(app.cdc, app.keyParams) app.stakeKeeper = stake.NewKeeper(app.cdc, app.keyStake, app.tkeyStake, app.bankKeeper, app.RegisterCodespace(stake.DefaultCodespace)) app.slashingKeeper = slashing.NewKeeper(app.cdc, app.keySlashing, app.stakeKeeper, app.paramsKeeper.Getter(), app.RegisterCodespace(slashing.DefaultCodespace)) - app.stakeKeeper = app.stakeKeeper.WithValidatorHooks(app.slashingKeeper.ValidatorHooks()) + app.stakeKeeper = app.stakeKeeper.WithHooks(app.slashingKeeper.Hooks()) app.govKeeper = gov.NewKeeper(app.cdc, app.keyGov, app.paramsKeeper.Setter(), app.bankKeeper, app.stakeKeeper, app.RegisterCodespace(gov.DefaultCodespace)) app.feeCollectionKeeper = auth.NewFeeCollectionKeeper(app.cdc, app.keyFeeCollection) diff --git a/docs/spec/staking/hooks.md b/docs/spec/staking/hooks.md new file mode 100644 index 0000000000..bcc496e3d6 --- /dev/null +++ b/docs/spec/staking/hooks.md @@ -0,0 +1,19 @@ +## Receiver Hooks + +The staking module allow for the following hooks to be registered with staking events: + +``` golang +// event hooks for staking validator object +type StakingHooks interface { + OnValidatorCreated(ctx Context, address ValAddress) // called when a validator is created + OnValidatorCommissionChange(ctx Context, address ValAddress) // called when a validator's commission is modified + OnValidatorRemoved(ctx Context, address ValAddress) // called when a validator is deleted + + OnValidatorBonded(ctx Context, address ConsAddress) // called when a validator is bonded + OnValidatorBeginUnbonding(ctx Context, address ConsAddress) // called when a validator begins unbonding + + OnDelegationCreated(ctx Context, delAddr AccAddress, valAddr ValAddress) // called when a delegation is created + OnDelegationSharesModified(ctx Context, delAddr AccAddress, valAddr ValAddress) // called when a delegation's shares are modified + OnDelegationRemoved(ctx Context, delAddr AccAddress, valAddr ValAddress) // called when a delegation is removed +} +``` diff --git a/types/stake.go b/types/stake.go index e794ea7349..2f92639f33 100644 --- a/types/stake.go +++ b/types/stake.go @@ -101,12 +101,25 @@ type DelegationSet interface { fn func(index int64, delegation Delegation) (stop bool)) } -// validator event hooks -// These can be utilized to communicate between a staking keeper -// and another keeper which must take particular actions when -// validators are bonded and unbonded. The second keeper must implement -// this interface, which then the staking keeper can call. -type ValidatorHooks interface { +//_______________________________________________________________________________ +// Event Hooks +// These can be utilized to communicate between a staking keeper and another +// keeper which must take particular actions when validators/delegators change +// state. The second keeper must implement this interface, which then the +// staking keeper can call. + +// TODO refactor event hooks out to the receiver modules + +// event hooks for staking validator object +type StakingHooks interface { + OnValidatorCreated(ctx Context, address ValAddress) // Must be called when a validator is created + OnValidatorCommissionChange(ctx Context, address ValAddress) // Must be called when a validator's commission is modified + OnValidatorRemoved(ctx Context, address ValAddress) // Must be called when a validator is deleted + OnValidatorBonded(ctx Context, address ConsAddress) // Must be called when a validator is bonded OnValidatorBeginUnbonding(ctx Context, address ConsAddress) // Must be called when a validator begins unbonding + + OnDelegationCreated(ctx Context, delAddr AccAddress, valAddr ValAddress) // Must be called when a delegation is created + OnDelegationSharesModified(ctx Context, delAddr AccAddress, valAddr ValAddress) // Must be called when a delegation's shares are modified + OnDelegationRemoved(ctx Context, delAddr AccAddress, valAddr ValAddress) // Must be called when a delegation is removed } diff --git a/x/slashing/hooks.go b/x/slashing/hooks.go index 92c4cf85f8..701a6b2cde 100644 --- a/x/slashing/hooks.go +++ b/x/slashing/hooks.go @@ -29,10 +29,10 @@ type Hooks struct { k Keeper } -var _ sdk.ValidatorHooks = Hooks{} +var _ sdk.StakingHooks = Hooks{} // Return the wrapper struct -func (k Keeper) ValidatorHooks() Hooks { +func (k Keeper) Hooks() Hooks { return Hooks{k} } @@ -45,3 +45,11 @@ func (h Hooks) OnValidatorBonded(ctx sdk.Context, address sdk.ConsAddress) { func (h Hooks) OnValidatorBeginUnbonding(ctx sdk.Context, address sdk.ConsAddress) { h.k.onValidatorBeginUnbonding(ctx, address) } + +// nolint - unused hooks +func (h Hooks) OnValidatorCreated(_ sdk.Context, _ sdk.ValAddress) {} +func (h Hooks) OnValidatorCommissionChange(_ sdk.Context, _ sdk.ValAddress) {} +func (h Hooks) OnValidatorRemoved(_ sdk.Context, _ sdk.ValAddress) {} +func (h Hooks) OnDelegationCreated(_ sdk.Context, _ sdk.AccAddress, _ sdk.ValAddress) {} +func (h Hooks) OnDelegationSharesModified(_ sdk.Context, _ sdk.AccAddress, _ sdk.ValAddress) {} +func (h Hooks) OnDelegationRemoved(_ sdk.Context, _ sdk.AccAddress, _ sdk.ValAddress) {} diff --git a/x/slashing/keeper_test.go b/x/slashing/keeper_test.go index d4b1af3d51..6845f35ce2 100644 --- a/x/slashing/keeper_test.go +++ b/x/slashing/keeper_test.go @@ -27,7 +27,7 @@ func TestHandleDoubleSign(t *testing.T) { // initial setup ctx, ck, sk, _, keeper := createTestInput(t) - sk = sk.WithValidatorHooks(keeper.ValidatorHooks()) + sk = sk.WithHooks(keeper.Hooks()) amtInt := int64(100) addr, val, amt := addrs[0], pks[0], sdk.NewInt(amtInt) got := stake.NewHandler(sk)(ctx, newTestMsgCreateValidator(addr, val, amt)) @@ -69,7 +69,7 @@ func TestSlashingPeriodCap(t *testing.T) { // initial setup ctx, ck, sk, _, keeper := createTestInput(t) - sk = sk.WithValidatorHooks(keeper.ValidatorHooks()) + sk = sk.WithHooks(keeper.Hooks()) amtInt := int64(100) addr, amt := addrs[0], sdk.NewInt(amtInt) valConsPubKey, valConsAddr := pks[0], sdk.ConsAddress(pks[0].Address()) @@ -125,7 +125,7 @@ func TestHandleAbsentValidator(t *testing.T) { // initial setup ctx, ck, sk, _, keeper := createTestInput(t) - sk = sk.WithValidatorHooks(keeper.ValidatorHooks()) + sk = sk.WithHooks(keeper.Hooks()) amtInt := int64(100) addr, val, amt := addrs[0], pks[0], sdk.NewInt(amtInt) sh := stake.NewHandler(sk) diff --git a/x/stake/handler.go b/x/stake/handler.go index 0524b2c117..8b53065ccf 100644 --- a/x/stake/handler.go +++ b/x/stake/handler.go @@ -98,6 +98,10 @@ func handleMsgCreateValidator(ctx sdk.Context, msg types.MsgCreateValidator, k k return err.Result() } + k.OnValidatorCreated(ctx, validator.OperatorAddr) + accAddr := sdk.AccAddress(validator.OperatorAddr) + k.OnDelegationCreated(ctx, accAddr, validator.OperatorAddr) + tags := sdk.NewTags( tags.Action, tags.ActionCreateValidator, tags.DstValidator, []byte(msg.ValidatorAddr.String()), @@ -166,6 +170,9 @@ func handleMsgDelegate(ctx sdk.Context, msg types.MsgDelegate, k keeper.Keeper) return err.Result() } + // call the hook if present + k.OnDelegationCreated(ctx, msg.DelegatorAddr, validator.OperatorAddr) + tags := sdk.NewTags( tags.Action, tags.ActionDelegate, tags.Delegator, []byte(msg.DelegatorAddr.String()), diff --git a/x/stake/keeper/delegation.go b/x/stake/keeper/delegation.go index 6efa4c8ed3..cc46646a73 100644 --- a/x/stake/keeper/delegation.go +++ b/x/stake/keeper/delegation.go @@ -66,7 +66,7 @@ func (k Keeper) SetDelegation(ctx sdk.Context, delegation types.Delegation) { // remove a delegation from store func (k Keeper) RemoveDelegation(ctx sdk.Context, delegation types.Delegation) { - + k.OnDelegationRemoved(ctx, delegation.DelegatorAddr, delegation.ValidatorAddr) store := ctx.KVStore(k.storeKey) store.Delete(GetDelegationKey(delegation.DelegatorAddr, delegation.ValidatorAddr)) } @@ -283,6 +283,8 @@ func (k Keeper) Delegate(ctx sdk.Context, delAddr sdk.AccAddress, bondAmt sdk.Co func (k Keeper) unbond(ctx sdk.Context, delAddr sdk.AccAddress, valAddr sdk.ValAddress, shares sdk.Dec) (amount sdk.Dec, err sdk.Error) { + k.OnDelegationSharesModified(ctx, delAddr, valAddr) + // check if delegation has any shares in it unbond delegation, found := k.GetDelegation(ctx, delAddr, valAddr) if !found { @@ -334,6 +336,7 @@ func (k Keeper) unbond(ctx sdk.Context, delAddr sdk.AccAddress, valAddr sdk.ValA k.RemoveValidator(ctx, validator.OperatorAddr) } + k.OnDelegationSharesModified(ctx, delegation.DelegatorAddr, validator.OperatorAddr) return amount, nil } diff --git a/x/stake/keeper/hooks.go b/x/stake/keeper/hooks.go new file mode 100644 index 0000000000..81bf5594ba --- /dev/null +++ b/x/stake/keeper/hooks.go @@ -0,0 +1,54 @@ +//nolint +package keeper + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" +) + +// Expose the hooks if present +func (k Keeper) OnValidatorCreated(ctx sdk.Context, address sdk.ValAddress) { + if k.hooks != nil { + k.hooks.OnValidatorCreated(ctx, address) + } +} +func (k Keeper) OnValidatorCommissionChange(ctx sdk.Context, address sdk.ValAddress) { + if k.hooks != nil { + k.hooks.OnValidatorCommissionChange(ctx, address) + } +} + +func (k Keeper) OnValidatorRemoved(ctx sdk.Context, address sdk.ValAddress) { + if k.hooks != nil { + k.hooks.OnValidatorRemoved(ctx, address) + } +} + +func (k Keeper) OnValidatorBonded(ctx sdk.Context, address sdk.ConsAddress) { + if k.hooks != nil { + k.hooks.OnValidatorBonded(ctx, address) + } +} + +func (k Keeper) OnValidatorBeginUnbonding(ctx sdk.Context, address sdk.ConsAddress) { + if k.hooks != nil { + k.hooks.OnValidatorBeginUnbonding(ctx, address) + } +} + +func (k Keeper) OnDelegationCreated(ctx sdk.Context, delAddr sdk.AccAddress, valAddr sdk.ValAddress) { + if k.hooks != nil { + k.hooks.OnDelegationCreated(ctx, delAddr, valAddr) + } +} + +func (k Keeper) OnDelegationSharesModified(ctx sdk.Context, delAddr sdk.AccAddress, valAddr sdk.ValAddress) { + if k.hooks != nil { + k.hooks.OnDelegationSharesModified(ctx, delAddr, valAddr) + } +} + +func (k Keeper) OnDelegationRemoved(ctx sdk.Context, delAddr sdk.AccAddress, valAddr sdk.ValAddress) { + if k.hooks != nil { + k.hooks.OnDelegationRemoved(ctx, delAddr, valAddr) + } +} diff --git a/x/stake/keeper/keeper.go b/x/stake/keeper/keeper.go index 0f700f9ab8..82170a4aed 100644 --- a/x/stake/keeper/keeper.go +++ b/x/stake/keeper/keeper.go @@ -14,7 +14,7 @@ type Keeper struct { storeTKey sdk.StoreKey cdc *codec.Codec bankKeeper bank.Keeper - hooks sdk.ValidatorHooks + hooks sdk.StakingHooks // codespace codespace sdk.CodespaceType @@ -33,7 +33,7 @@ func NewKeeper(cdc *codec.Codec, key, tkey sdk.StoreKey, ck bank.Keeper, codespa } // Set the validator hooks -func (k Keeper) WithValidatorHooks(sh sdk.ValidatorHooks) Keeper { +func (k Keeper) WithHooks(sh sdk.StakingHooks) Keeper { if k.hooks != nil { panic("cannot set validator hooks twice") } diff --git a/x/stake/keeper/validator.go b/x/stake/keeper/validator.go index 9d8fb4362a..84a82dba5f 100644 --- a/x/stake/keeper/validator.go +++ b/x/stake/keeper/validator.go @@ -617,12 +617,7 @@ func (k Keeper) beginUnbondingValidator(ctx sdk.Context, validator types.Validat // also remove from the Bonded types.Validators Store store.Delete(GetValidatorsBondedIndexKey(validator.OperatorAddr)) - // call the unbond hook if present - if k.hooks != nil { - k.hooks.OnValidatorBeginUnbonding(ctx, validator.ConsAddress()) - } - - // return updated validator + k.OnValidatorBeginUnbonding(ctx, validator.ConsAddress()) return validator } @@ -652,18 +647,15 @@ func (k Keeper) bondValidator(ctx sdk.Context, validator types.Validator) types. tstore := ctx.TransientStore(k.storeTKey) tstore.Set(GetTendermintUpdatesTKey(validator.OperatorAddr), bzABCI) - // call the bond hook if present - if k.hooks != nil { - k.hooks.OnValidatorBonded(ctx, validator.ConsAddress()) - } - - // return updated validator + k.OnValidatorBonded(ctx, validator.ConsAddress()) return validator } // remove the validator record and associated indexes func (k Keeper) RemoveValidator(ctx sdk.Context, address sdk.ValAddress) { + k.OnValidatorRemoved(ctx, address) + // first retrieve the old validator record validator, found := k.GetValidator(ctx, address) if !found { @@ -703,6 +695,7 @@ func (k Keeper) UpdateValidatorCommission(ctx sdk.Context, validator types.Valid validator.Commission.UpdateTime = blockTime k.SetValidator(ctx, validator) + k.OnValidatorCommissionChange(ctx, validator.OperatorAddr) return nil } From e11c52e873b1025d95b9e99c78275f65a4fa8dc7 Mon Sep 17 00:00:00 2001 From: Dev Ojha Date: Wed, 26 Sep 2018 03:13:40 -0700 Subject: [PATCH 15/26] Merge PR #2408: decimal: Add a method to multiply a decimal by an Int This is for usage within the slash function, to minimize operations required to slash each unbonded and redelegating account. --- PENDING.md | 3 ++- types/decimal.go | 10 ++++++++++ types/decimal_test.go | 17 +++++++++++++++++ x/stake/keeper/slash.go | 4 ++-- 4 files changed, 31 insertions(+), 3 deletions(-) diff --git a/PENDING.md b/PENDING.md index c432ec3e07..aa328d9698 100644 --- a/PENDING.md +++ b/PENDING.md @@ -44,7 +44,8 @@ BREAKING CHANGES * [core] [\#1807](https://github.com/cosmos/cosmos-sdk/issues/1807) Switch from use of rational to decimal * [types] [\#1901](https://github.com/cosmos/cosmos-sdk/issues/1901) Validator interface's GetOwner() renamed to GetOperator() * [x/slashing] [#2122](https://github.com/cosmos/cosmos-sdk/pull/2122) - Implement slashing period - * [types] [\#2119](https://github.com/cosmos/cosmos-sdk/issues/2119) Parsed error messages and ABCI log errors to make them more human readable. + * [types] [\#2119](https://github.com/cosmos/cosmos-sdk/issues/2119) Parsed error messages and ABCI log errors to make them more human readable. + * [types] \#2407 MulInt method added to big decimal in order to improve efficiency of slashing * [simulation] Rename TestAndRunTx to Operation [#2153](https://github.com/cosmos/cosmos-sdk/pull/2153) * [simulation] Remove log and testing.TB from Operation and Invariants, in favor of using errors \#2282 * [simulation] Remove usage of keys and addrs in the types, in favor of simulation.Account \#2384 diff --git a/types/decimal.go b/types/decimal.go index 13a494ba0c..13a8a26c15 100644 --- a/types/decimal.go +++ b/types/decimal.go @@ -215,6 +215,16 @@ func (d Dec) Mul(d2 Dec) Dec { return Dec{chopped} } +// multiplication +func (d Dec) MulInt(i Int) Dec { + mul := new(big.Int).Mul(d.Int, i.i) + + if mul.BitLen() > 255+DecimalPrecisionBits { + panic("Int overflow") + } + return Dec{mul} +} + // quotient func (d Dec) Quo(d2 Dec) Dec { diff --git a/types/decimal_test.go b/types/decimal_test.go index 161215467f..a6ec0740e8 100644 --- a/types/decimal_test.go +++ b/types/decimal_test.go @@ -325,3 +325,20 @@ func TestStringOverflow(t *testing.T) { dec3.String(), ) } + +func TestDecMulInt(t *testing.T) { + tests := []struct { + sdkDec Dec + sdkInt Int + want Dec + }{ + {NewDec(10), NewInt(2), NewDec(20)}, + {NewDec(1000000), NewInt(100), NewDec(100000000)}, + {NewDecWithPrec(1, 1), NewInt(10), NewDec(1)}, + {NewDecWithPrec(1, 5), NewInt(20), NewDecWithPrec(2, 4)}, + } + for i, tc := range tests { + got := tc.sdkDec.MulInt(tc.sdkInt) + require.Equal(t, tc.want, got, "Incorrect result on test case %d", i) + } +} diff --git a/x/stake/keeper/slash.go b/x/stake/keeper/slash.go index 486dc32869..7bfe4eb254 100644 --- a/x/stake/keeper/slash.go +++ b/x/stake/keeper/slash.go @@ -172,7 +172,7 @@ func (k Keeper) slashUnbondingDelegation(ctx sdk.Context, unbondingDelegation ty } // Calculate slash amount proportional to stake contributing to infraction - slashAmount = sdk.NewDecFromInt(unbondingDelegation.InitialBalance.Amount).Mul(slashFactor) + slashAmount = slashFactor.MulInt(unbondingDelegation.InitialBalance.Amount) // Don't slash more tokens than held // Possible since the unbonding delegation may already @@ -218,7 +218,7 @@ func (k Keeper) slashRedelegation(ctx sdk.Context, validator types.Validator, re } // Calculate slash amount proportional to stake contributing to infraction - slashAmount = sdk.NewDecFromInt(redelegation.InitialBalance.Amount).Mul(slashFactor) + slashAmount = slashFactor.MulInt(redelegation.InitialBalance.Amount) // Don't slash more tokens than held // Possible since the redelegation may already From 9e27e4bac549dbea21d77f7070c07edd97b63b69 Mon Sep 17 00:00:00 2001 From: Dev Ojha Date: Wed, 26 Sep 2018 06:15:37 -0700 Subject: [PATCH 16/26] Merge PR #2386: types/coins: Add benchmarks for coins addition --- types/coin_test.go | 53 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 53 insertions(+) diff --git a/types/coin_test.go b/types/coin_test.go index 3ff0bffe57..ba7038010a 100644 --- a/types/coin_test.go +++ b/types/coin_test.go @@ -1,6 +1,7 @@ package types import ( + "fmt" "testing" "github.com/stretchr/testify/assert" @@ -423,3 +424,55 @@ func TestAmountOf(t *testing.T) { assert.Equal(t, NewInt(tc.amountOfTREE), tc.coins.AmountOf("TREE")) } } + +func BenchmarkCoinsAdditionIntersect(b *testing.B) { + benchmarkingFunc := func(numCoinsA int, numCoinsB int) func(b *testing.B) { + return func(b *testing.B) { + coinsA := Coins(make([]Coin, numCoinsA)) + coinsB := Coins(make([]Coin, numCoinsB)) + for i := 0; i < numCoinsA; i++ { + coinsA[i] = NewCoin("COINZ_"+string(i), NewInt(int64(i))) + } + for i := 0; i < numCoinsB; i++ { + coinsB[i] = NewCoin("COINZ_"+string(i), NewInt(int64(i))) + } + b.ResetTimer() + for i := 0; i < b.N; i++ { + coinsA.Plus(coinsB) + } + } + } + + benchmarkSizes := [][]int{[]int{1, 1}, []int{5, 5}, []int{5, 20}, []int{1, 1000}, []int{2, 1000}} + for i := 0; i < len(benchmarkSizes); i++ { + sizeA := benchmarkSizes[i][0] + sizeB := benchmarkSizes[i][1] + b.Run(fmt.Sprintf("sizes: A_%d, B_%d", sizeA, sizeB), benchmarkingFunc(sizeA, sizeB)) + } +} + +func BenchmarkCoinsAdditionNoIntersect(b *testing.B) { + benchmarkingFunc := func(numCoinsA int, numCoinsB int) func(b *testing.B) { + return func(b *testing.B) { + coinsA := Coins(make([]Coin, numCoinsA)) + coinsB := Coins(make([]Coin, numCoinsB)) + for i := 0; i < numCoinsA; i++ { + coinsA[i] = NewCoin("COINZ_"+string(numCoinsB+i), NewInt(int64(i))) + } + for i := 0; i < numCoinsB; i++ { + coinsB[i] = NewCoin("COINZ_"+string(i), NewInt(int64(i))) + } + b.ResetTimer() + for i := 0; i < b.N; i++ { + coinsA.Plus(coinsB) + } + } + } + + benchmarkSizes := [][]int{[]int{1, 1}, []int{5, 5}, []int{5, 20}, []int{1, 1000}, []int{2, 1000}, []int{1000, 2}} + for i := 0; i < len(benchmarkSizes); i++ { + sizeA := benchmarkSizes[i][0] + sizeB := benchmarkSizes[i][1] + b.Run(fmt.Sprintf("sizes: A_%d, B_%d", sizeA, sizeB), benchmarkingFunc(sizeA, sizeB)) + } +} From b1583dd8c5e757465abee9e6da67dac477ce4758 Mon Sep 17 00:00:00 2001 From: Dev Ojha Date: Wed, 26 Sep 2018 06:16:50 -0700 Subject: [PATCH 17/26] Merge PR #2383: baseapp: Move query tests to their own file --- baseapp/baseapp_test.go | 89 -------------------------------------- baseapp/query_test.go | 96 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 96 insertions(+), 89 deletions(-) create mode 100644 baseapp/query_test.go diff --git a/baseapp/baseapp_test.go b/baseapp/baseapp_test.go index f6042bd086..0137a3f7d7 100644 --- a/baseapp/baseapp_test.go +++ b/baseapp/baseapp_test.go @@ -821,92 +821,3 @@ func TestTxGasLimits(t *testing.T) { } } } - -//------------------------------------------------------------------------------------------- -// Queries - -// Test that we can only query from the latest committed state. -func TestQuery(t *testing.T) { - key, value := []byte("hello"), []byte("goodbye") - anteOpt := func(bapp *BaseApp) { - bapp.SetAnteHandler(func(ctx sdk.Context, tx sdk.Tx, simulate bool) (newCtx sdk.Context, res sdk.Result, abort bool) { - store := ctx.KVStore(capKey1) - store.Set(key, value) - return - }) - } - - routerOpt := func(bapp *BaseApp) { - bapp.Router().AddRoute(typeMsgCounter, func(ctx sdk.Context, msg sdk.Msg) sdk.Result { - store := ctx.KVStore(capKey1) - store.Set(key, value) - return sdk.Result{} - }) - } - - app := setupBaseApp(t, anteOpt, routerOpt) - - app.InitChain(abci.RequestInitChain{}) - - // NOTE: "/store/key1" tells us KVStore - // and the final "/key" says to use the data as the - // key in the given KVStore ... - query := abci.RequestQuery{ - Path: "/store/key1/key", - Data: key, - } - tx := newTxCounter(0, 0) - - // query is empty before we do anything - res := app.Query(query) - require.Equal(t, 0, len(res.Value)) - - // query is still empty after a CheckTx - resTx := app.Check(tx) - require.True(t, resTx.IsOK(), fmt.Sprintf("%v", resTx)) - res = app.Query(query) - require.Equal(t, 0, len(res.Value)) - - // query is still empty after a DeliverTx before we commit - app.BeginBlock(abci.RequestBeginBlock{}) - resTx = app.Deliver(tx) - require.True(t, resTx.IsOK(), fmt.Sprintf("%v", resTx)) - res = app.Query(query) - require.Equal(t, 0, len(res.Value)) - - // query returns correct value after Commit - app.Commit() - res = app.Query(query) - require.Equal(t, value, res.Value) -} - -// Test p2p filter queries -func TestP2PQuery(t *testing.T) { - addrPeerFilterOpt := func(bapp *BaseApp) { - bapp.SetAddrPeerFilter(func(addrport string) abci.ResponseQuery { - require.Equal(t, "1.1.1.1:8000", addrport) - return abci.ResponseQuery{Code: uint32(3)} - }) - } - - pubkeyPeerFilterOpt := func(bapp *BaseApp) { - bapp.SetPubKeyPeerFilter(func(pubkey string) abci.ResponseQuery { - require.Equal(t, "testpubkey", pubkey) - return abci.ResponseQuery{Code: uint32(4)} - }) - } - - app := setupBaseApp(t, addrPeerFilterOpt, pubkeyPeerFilterOpt) - - addrQuery := abci.RequestQuery{ - Path: "/p2p/filter/addr/1.1.1.1:8000", - } - res := app.Query(addrQuery) - require.Equal(t, uint32(3), res.Code) - - pubkeyQuery := abci.RequestQuery{ - Path: "/p2p/filter/pubkey/testpubkey", - } - res = app.Query(pubkeyQuery) - require.Equal(t, uint32(4), res.Code) -} diff --git a/baseapp/query_test.go b/baseapp/query_test.go new file mode 100644 index 0000000000..579874e217 --- /dev/null +++ b/baseapp/query_test.go @@ -0,0 +1,96 @@ +package baseapp + +import ( + "fmt" + "testing" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/stretchr/testify/require" + abci "github.com/tendermint/tendermint/abci/types" +) + +// Test that we can only query from the latest committed state. +func TestQuery(t *testing.T) { + key, value := []byte("hello"), []byte("goodbye") + anteOpt := func(bapp *BaseApp) { + bapp.SetAnteHandler(func(ctx sdk.Context, tx sdk.Tx, simulate bool) (newCtx sdk.Context, res sdk.Result, abort bool) { + store := ctx.KVStore(capKey1) + store.Set(key, value) + return + }) + } + + routerOpt := func(bapp *BaseApp) { + bapp.Router().AddRoute(typeMsgCounter, func(ctx sdk.Context, msg sdk.Msg) sdk.Result { + store := ctx.KVStore(capKey1) + store.Set(key, value) + return sdk.Result{} + }) + } + + app := setupBaseApp(t, anteOpt, routerOpt) + + app.InitChain(abci.RequestInitChain{}) + + // NOTE: "/store/key1" tells us KVStore + // and the final "/key" says to use the data as the + // key in the given KVStore ... + query := abci.RequestQuery{ + Path: "/store/key1/key", + Data: key, + } + tx := newTxCounter(0, 0) + + // query is empty before we do anything + res := app.Query(query) + require.Equal(t, 0, len(res.Value)) + + // query is still empty after a CheckTx + resTx := app.Check(tx) + require.True(t, resTx.IsOK(), fmt.Sprintf("%v", resTx)) + res = app.Query(query) + require.Equal(t, 0, len(res.Value)) + + // query is still empty after a DeliverTx before we commit + app.BeginBlock(abci.RequestBeginBlock{}) + resTx = app.Deliver(tx) + require.True(t, resTx.IsOK(), fmt.Sprintf("%v", resTx)) + res = app.Query(query) + require.Equal(t, 0, len(res.Value)) + + // query returns correct value after Commit + app.Commit() + res = app.Query(query) + require.Equal(t, value, res.Value) +} + +// Test p2p filter queries +func TestP2PQuery(t *testing.T) { + addrPeerFilterOpt := func(bapp *BaseApp) { + bapp.SetAddrPeerFilter(func(addrport string) abci.ResponseQuery { + require.Equal(t, "1.1.1.1:8000", addrport) + return abci.ResponseQuery{Code: uint32(3)} + }) + } + + pubkeyPeerFilterOpt := func(bapp *BaseApp) { + bapp.SetPubKeyPeerFilter(func(pubkey string) abci.ResponseQuery { + require.Equal(t, "testpubkey", pubkey) + return abci.ResponseQuery{Code: uint32(4)} + }) + } + + app := setupBaseApp(t, addrPeerFilterOpt, pubkeyPeerFilterOpt) + + addrQuery := abci.RequestQuery{ + Path: "/p2p/filter/addr/1.1.1.1:8000", + } + res := app.Query(addrQuery) + require.Equal(t, uint32(3), res.Code) + + pubkeyQuery := abci.RequestQuery{ + Path: "/p2p/filter/pubkey/testpubkey", + } + res = app.Query(pubkeyQuery) + require.Equal(t, uint32(4), res.Code) +} From 145e06b85cc52bf61c7438dd2c0a8b2b1f668eb3 Mon Sep 17 00:00:00 2001 From: Dev Ojha Date: Wed, 26 Sep 2018 06:17:46 -0700 Subject: [PATCH 18/26] Merge PR #2382: baseapp: Move code from setters.go to options.go --- baseapp/options.go | 85 ++++++++++++++++++++++++++++++++++++++++++++++ baseapp/setters.go | 80 ------------------------------------------- 2 files changed, 85 insertions(+), 80 deletions(-) delete mode 100644 baseapp/setters.go diff --git a/baseapp/options.go b/baseapp/options.go index 048a17d588..a6460248df 100644 --- a/baseapp/options.go +++ b/baseapp/options.go @@ -1,9 +1,12 @@ +// nolint: golint package baseapp import ( "fmt" + "github.com/cosmos/cosmos-sdk/store" sdk "github.com/cosmos/cosmos-sdk/types" + dbm "github.com/tendermint/tendermint/libs/db" ) // File for storing in-package BaseApp optional functions, @@ -35,3 +38,85 @@ func SetMinimumFees(minFees string) func(*BaseApp) { } return func(bap *BaseApp) { bap.SetMinimumFees(fees) } } + +func (app *BaseApp) SetName(name string) { + if app.sealed { + panic("SetName() on sealed BaseApp") + } + app.name = name +} + +func (app *BaseApp) SetDB(db dbm.DB) { + if app.sealed { + panic("SetDB() on sealed BaseApp") + } + app.db = db +} + +func (app *BaseApp) SetCMS(cms store.CommitMultiStore) { + if app.sealed { + panic("SetEndBlocker() on sealed BaseApp") + } + app.cms = cms +} + +func (app *BaseApp) SetInitChainer(initChainer sdk.InitChainer) { + if app.sealed { + panic("SetInitChainer() on sealed BaseApp") + } + app.initChainer = initChainer +} + +func (app *BaseApp) SetBeginBlocker(beginBlocker sdk.BeginBlocker) { + if app.sealed { + panic("SetBeginBlocker() on sealed BaseApp") + } + app.beginBlocker = beginBlocker +} + +func (app *BaseApp) SetEndBlocker(endBlocker sdk.EndBlocker) { + if app.sealed { + panic("SetEndBlocker() on sealed BaseApp") + } + app.endBlocker = endBlocker +} + +func (app *BaseApp) SetAnteHandler(ah sdk.AnteHandler) { + if app.sealed { + panic("SetAnteHandler() on sealed BaseApp") + } + app.anteHandler = ah +} + +func (app *BaseApp) SetAddrPeerFilter(pf sdk.PeerFilter) { + if app.sealed { + panic("SetAddrPeerFilter() on sealed BaseApp") + } + app.addrPeerFilter = pf +} + +func (app *BaseApp) SetPubKeyPeerFilter(pf sdk.PeerFilter) { + if app.sealed { + panic("SetPubKeyPeerFilter() on sealed BaseApp") + } + app.pubkeyPeerFilter = pf +} + +func (app *BaseApp) Router() Router { + if app.sealed { + panic("Router() on sealed BaseApp") + } + return app.router +} + +func (app *BaseApp) QueryRouter() QueryRouter { + return app.queryRouter +} + +func (app *BaseApp) Seal() { app.sealed = true } +func (app *BaseApp) IsSealed() bool { return app.sealed } +func (app *BaseApp) enforceSeal() { + if !app.sealed { + panic("enforceSeal() on BaseApp but not sealed") + } +} diff --git a/baseapp/setters.go b/baseapp/setters.go deleted file mode 100644 index 28782c96de..0000000000 --- a/baseapp/setters.go +++ /dev/null @@ -1,80 +0,0 @@ -package baseapp - -import ( - dbm "github.com/tendermint/tendermint/libs/db" - - "github.com/cosmos/cosmos-sdk/store" - sdk "github.com/cosmos/cosmos-sdk/types" -) - -// nolint - Setter functions -func (app *BaseApp) SetName(name string) { - if app.sealed { - panic("SetName() on sealed BaseApp") - } - app.name = name -} -func (app *BaseApp) SetDB(db dbm.DB) { - if app.sealed { - panic("SetDB() on sealed BaseApp") - } - app.db = db -} -func (app *BaseApp) SetCMS(cms store.CommitMultiStore) { - if app.sealed { - panic("SetEndBlocker() on sealed BaseApp") - } - app.cms = cms -} -func (app *BaseApp) SetInitChainer(initChainer sdk.InitChainer) { - if app.sealed { - panic("SetInitChainer() on sealed BaseApp") - } - app.initChainer = initChainer -} -func (app *BaseApp) SetBeginBlocker(beginBlocker sdk.BeginBlocker) { - if app.sealed { - panic("SetBeginBlocker() on sealed BaseApp") - } - app.beginBlocker = beginBlocker -} -func (app *BaseApp) SetEndBlocker(endBlocker sdk.EndBlocker) { - if app.sealed { - panic("SetEndBlocker() on sealed BaseApp") - } - app.endBlocker = endBlocker -} -func (app *BaseApp) SetAnteHandler(ah sdk.AnteHandler) { - if app.sealed { - panic("SetAnteHandler() on sealed BaseApp") - } - app.anteHandler = ah -} -func (app *BaseApp) SetAddrPeerFilter(pf sdk.PeerFilter) { - if app.sealed { - panic("SetAddrPeerFilter() on sealed BaseApp") - } - app.addrPeerFilter = pf -} -func (app *BaseApp) SetPubKeyPeerFilter(pf sdk.PeerFilter) { - if app.sealed { - panic("SetPubKeyPeerFilter() on sealed BaseApp") - } - app.pubkeyPeerFilter = pf -} -func (app *BaseApp) Router() Router { - if app.sealed { - panic("Router() on sealed BaseApp") - } - return app.router -} -func (app *BaseApp) QueryRouter() QueryRouter { - return app.queryRouter -} -func (app *BaseApp) Seal() { app.sealed = true } -func (app *BaseApp) IsSealed() bool { return app.sealed } -func (app *BaseApp) enforceSeal() { - if !app.sealed { - panic("enforceSeal() on BaseApp but not sealed") - } -} From 91cac96feac96bbdd4e6d91449151b5f1efa90d3 Mon Sep 17 00:00:00 2001 From: Alexander Bezobchuk Date: Wed, 26 Sep 2018 13:29:39 +0000 Subject: [PATCH 19/26] Merge PR #2391: LCD Cleanup and DRY Refactor --- client/context/broadcast.go | 158 ++++++++++++++ client/context/query.go | 161 ++------------- client/lcd/lcd_test.go | 80 ++++---- client/tx/broadcast.go | 2 +- client/utils/rest.go | 194 +++++++++++++++++- client/utils/utils.go | 24 ++- examples/democoin/x/cool/client/cli/tx.go | 4 +- examples/democoin/x/pow/client/cli/tx.go | 2 +- .../x/simplestake/client/cli/commands.go | 4 +- x/bank/client/cli/broadcast.go | 5 +- x/bank/client/cli/sendtx.go | 4 +- x/bank/client/rest/sendtx.go | 107 ++-------- x/bank/client/util.go | 4 +- x/gov/client/cli/tx.go | 6 +- x/gov/client/rest/rest.go | 45 ++-- x/gov/client/rest/util.go | 141 ------------- x/ibc/client/cli/ibctx.go | 2 +- x/ibc/client/rest/transfer.go | 99 ++------- x/slashing/client/cli/tx.go | 2 +- x/slashing/client/rest/tx.go | 93 ++------- x/stake/client/cli/tx.go | 14 +- x/stake/client/rest/tx.go | 134 ++++++------ 22 files changed, 596 insertions(+), 689 deletions(-) create mode 100644 client/context/broadcast.go delete mode 100644 x/gov/client/rest/util.go diff --git a/client/context/broadcast.go b/client/context/broadcast.go new file mode 100644 index 0000000000..4e295777c0 --- /dev/null +++ b/client/context/broadcast.go @@ -0,0 +1,158 @@ +package context + +import ( + "fmt" + "io" + + "github.com/pkg/errors" + + abci "github.com/tendermint/tendermint/abci/types" + ctypes "github.com/tendermint/tendermint/rpc/core/types" +) + +// TODO: This should get deleted eventually, and perhaps +// ctypes.ResultBroadcastTx be stripped of unused fields, and +// ctypes.ResultBroadcastTxCommit returned for tendermint RPC BroadcastTxSync. +// +// The motivation is that we want a unified type to return, and the better +// option is the one that can hold CheckTx/DeliverTx responses optionally. +func resultBroadcastTxToCommit(res *ctypes.ResultBroadcastTx) *ctypes.ResultBroadcastTxCommit { + return &ctypes.ResultBroadcastTxCommit{ + Hash: res.Hash, + // NOTE: other fields are unused for async. + } +} + +// BroadcastTx broadcasts a transactions either synchronously or asynchronously +// based on the context parameters. The result of the broadcast is parsed into +// an intermediate structure which is logged if the context has a logger +// defined. +func (ctx CLIContext) BroadcastTx(txBytes []byte) (*ctypes.ResultBroadcastTxCommit, error) { + if ctx.Async { + res, err := ctx.broadcastTxAsync(txBytes) + if err != nil { + return nil, err + } + + resCommit := resultBroadcastTxToCommit(res) + return resCommit, err + } + + return ctx.broadcastTxCommit(txBytes) +} + +// BroadcastTxAndAwaitCommit broadcasts transaction bytes to a Tendermint node +// and waits for a commit. +func (ctx CLIContext) BroadcastTxAndAwaitCommit(tx []byte) (*ctypes.ResultBroadcastTxCommit, error) { + node, err := ctx.GetNode() + if err != nil { + return nil, err + } + + res, err := node.BroadcastTxCommit(tx) + if err != nil { + return res, err + } + + if !res.CheckTx.IsOK() { + return res, errors.Errorf("checkTx failed: (%d) %s", + res.CheckTx.Code, + res.CheckTx.Log) + } + + if !res.DeliverTx.IsOK() { + return res, errors.Errorf("deliverTx failed: (%d) %s", + res.DeliverTx.Code, + res.DeliverTx.Log) + } + + return res, err +} + +// BroadcastTxAsync broadcasts transaction bytes to a Tendermint node +// asynchronously. +func (ctx CLIContext) BroadcastTxAsync(tx []byte) (*ctypes.ResultBroadcastTx, error) { + node, err := ctx.GetNode() + if err != nil { + return nil, err + } + + res, err := node.BroadcastTxAsync(tx) + if err != nil { + return res, err + } + + return res, err +} + +func (ctx CLIContext) broadcastTxAsync(txBytes []byte) (*ctypes.ResultBroadcastTx, error) { + res, err := ctx.BroadcastTxAsync(txBytes) + if err != nil { + return res, err + } + + if ctx.Logger != nil { + if ctx.JSON { + type toJSON struct { + TxHash string + } + + resJSON := toJSON{res.Hash.String()} + bz, err := ctx.Codec.MarshalJSON(resJSON) + if err != nil { + return res, err + } + + ctx.Logger.Write(bz) + io.WriteString(ctx.Logger, "\n") + } else { + io.WriteString(ctx.Logger, fmt.Sprintf("async tx sent (tx hash: %s)\n", res.Hash)) + } + } + + return res, nil +} + +func (ctx CLIContext) broadcastTxCommit(txBytes []byte) (*ctypes.ResultBroadcastTxCommit, error) { + res, err := ctx.BroadcastTxAndAwaitCommit(txBytes) + if err != nil { + return res, err + } + + if ctx.JSON { + // Since JSON is intended for automated scripts, always include response in + // JSON mode. + type toJSON struct { + Height int64 + TxHash string + Response abci.ResponseDeliverTx + } + + if ctx.Logger != nil { + resJSON := toJSON{res.Height, res.Hash.String(), res.DeliverTx} + bz, err := ctx.Codec.MarshalJSON(resJSON) + if err != nil { + return res, err + } + + ctx.Logger.Write(bz) + io.WriteString(ctx.Logger, "\n") + } + + return res, nil + } + + if ctx.Logger != nil { + resStr := fmt.Sprintf("Committed at block %d (tx hash: %s)\n", res.Height, res.Hash.String()) + + if ctx.PrintResponse { + resStr = fmt.Sprintf("Committed at block %d (tx hash: %s, response: %+v)\n", + res.Height, res.Hash.String(), res.DeliverTx, + ) + } + + io.WriteString(ctx.Logger, resStr) + } + + return res, nil +} diff --git a/client/context/query.go b/client/context/query.go index 35402fbfbe..d9bef3f5d1 100644 --- a/client/context/query.go +++ b/client/context/query.go @@ -2,7 +2,6 @@ package context import ( "fmt" - "io" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/x/auth" @@ -19,7 +18,6 @@ import ( tmliteErr "github.com/tendermint/tendermint/lite/errors" tmliteProxy "github.com/tendermint/tendermint/lite/proxy" rpcclient "github.com/tendermint/tendermint/rpc/client" - ctypes "github.com/tendermint/tendermint/rpc/core/types" ) // GetNode returns an RPC client. If the context's client is not defined, an @@ -114,49 +112,6 @@ func (ctx CLIContext) GetAccountSequence(address []byte) (int64, error) { return account.GetSequence(), nil } -// BroadcastTx broadcasts transaction bytes to a Tendermint node. -func (ctx CLIContext) BroadcastTx(tx []byte) (*ctypes.ResultBroadcastTxCommit, error) { - node, err := ctx.GetNode() - if err != nil { - return nil, err - } - - res, err := node.BroadcastTxCommit(tx) - if err != nil { - return res, err - } - - if !res.CheckTx.IsOK() { - return res, errors.Errorf("checkTx failed: (%d) %s", - res.CheckTx.Code, - res.CheckTx.Log) - } - - if !res.DeliverTx.IsOK() { - return res, errors.Errorf("deliverTx failed: (%d) %s", - res.DeliverTx.Code, - res.DeliverTx.Log) - } - - return res, err -} - -// BroadcastTxAsync broadcasts transaction bytes to a Tendermint node -// asynchronously. -func (ctx CLIContext) BroadcastTxAsync(tx []byte) (*ctypes.ResultBroadcastTx, error) { - node, err := ctx.GetNode() - if err != nil { - return nil, err - } - - res, err := node.BroadcastTxAsync(tx) - if err != nil { - return res, err - } - - return res, err -} - // EnsureAccountExists ensures that an account exists for a given context. An // error is returned if it does not. func (ctx CLIContext) EnsureAccountExists() error { @@ -193,92 +148,6 @@ func (ctx CLIContext) EnsureAccountExistsFromAddr(addr sdk.AccAddress) error { return nil } -// EnsureBroadcastTx broadcasts a transactions either synchronously or -// asynchronously based on the context parameters. The result of the broadcast -// is parsed into an intermediate structure which is logged if the context has -// a logger defined. -func (ctx CLIContext) EnsureBroadcastTx(txBytes []byte) error { - if ctx.Async { - return ctx.ensureBroadcastTxAsync(txBytes) - } - - return ctx.ensureBroadcastTx(txBytes) -} - -func (ctx CLIContext) ensureBroadcastTxAsync(txBytes []byte) error { - res, err := ctx.BroadcastTxAsync(txBytes) - if err != nil { - return err - } - - if ctx.JSON { - type toJSON struct { - TxHash string - } - - if ctx.Logger != nil { - resJSON := toJSON{res.Hash.String()} - bz, err := ctx.Codec.MarshalJSON(resJSON) - if err != nil { - return err - } - - ctx.Logger.Write(bz) - io.WriteString(ctx.Logger, "\n") - } - } else { - if ctx.Logger != nil { - io.WriteString(ctx.Logger, fmt.Sprintf("Async tx sent (tx hash: %s)\n", res.Hash)) - } - } - - return nil -} - -func (ctx CLIContext) ensureBroadcastTx(txBytes []byte) error { - res, err := ctx.BroadcastTx(txBytes) - if err != nil { - return err - } - - if ctx.JSON { - // since JSON is intended for automated scripts, always include - // response in JSON mode. - type toJSON struct { - Height int64 - TxHash string - Response abci.ResponseDeliverTx - } - - if ctx.Logger != nil { - resJSON := toJSON{res.Height, res.Hash.String(), res.DeliverTx} - bz, err := ctx.Codec.MarshalJSON(resJSON) - if err != nil { - return err - } - - ctx.Logger.Write(bz) - io.WriteString(ctx.Logger, "\n") - } - - return nil - } - - if ctx.Logger != nil { - resStr := fmt.Sprintf("Committed at block %d (tx hash: %s)\n", res.Height, res.Hash.String()) - - if ctx.PrintResponse { - resStr = fmt.Sprintf("Committed at block %d (tx hash: %s, response: %+v)\n", - res.Height, res.Hash.String(), res.DeliverTx, - ) - } - - io.WriteString(ctx.Logger, resStr) - } - - return nil -} - // query performs a query from a Tendermint node with the provided store name // and path. func (ctx CLIContext) query(path string, key cmn.HexBytes) (res []byte, err error) { @@ -302,7 +171,7 @@ func (ctx CLIContext) query(path string, key cmn.HexBytes) (res []byte, err erro return res, errors.Errorf("query failed: (%d) %s", resp.Code, resp.Log) } - // Data from trusted node or subspace query doesn't need verification. + // data from trusted node or subspace query doesn't need verification if ctx.TrustNode || !isQueryStoreWithProof(path) { return resp.Value, nil } @@ -315,26 +184,26 @@ func (ctx CLIContext) query(path string, key cmn.HexBytes) (res []byte, err erro return resp.Value, nil } -// Certify verifies the consensus proof at given height +// Certify verifies the consensus proof at given height. func (ctx CLIContext) Certify(height int64) (lite.Commit, error) { check, err := tmliteProxy.GetCertifiedCommit(height, ctx.Client, ctx.Certifier) - if tmliteErr.IsCommitNotFoundErr(err) { + switch { + case tmliteErr.IsCommitNotFoundErr(err): return lite.Commit{}, ErrVerifyCommit(height) - } else if err != nil { + case err != nil: return lite.Commit{}, err } + return check, nil } -// verifyProof perform response proof verification -// nolint: unparam -func (ctx CLIContext) verifyProof(path string, resp abci.ResponseQuery) error { - +// verifyProof perform response proof verification. +func (ctx CLIContext) verifyProof(_ string, resp abci.ResponseQuery) error { if ctx.Certifier == nil { - return fmt.Errorf("missing valid certifier to verify data from untrusted node") + return fmt.Errorf("missing valid certifier to verify data from distrusted node") } - // AppHash for height H is in header H+1 + // the AppHash for height H is in header H+1 commit, err := ctx.Certify(resp.Height + 1) if err != nil { return err @@ -342,14 +211,16 @@ func (ctx CLIContext) verifyProof(path string, resp abci.ResponseQuery) error { var multiStoreProof store.MultiStoreProof cdc := codec.New() + err = cdc.UnmarshalBinary(resp.Proof, &multiStoreProof) if err != nil { return errors.Wrap(err, "failed to unmarshalBinary rangeProof") } - // Verify the substore commit hash against trusted appHash + // verify the substore commit hash against trusted appHash substoreCommitHash, err := store.VerifyMultiStoreCommitInfo( - multiStoreProof.StoreName, multiStoreProof.StoreInfos, commit.Header.AppHash) + multiStoreProof.StoreName, multiStoreProof.StoreInfos, commit.Header.AppHash, + ) if err != nil { return errors.Wrap(err, "failed in verifying the proof against appHash") } @@ -370,11 +241,12 @@ func (ctx CLIContext) queryStore(key cmn.HexBytes, storeName, endPath string) ([ } // isQueryStoreWithProof expects a format like /// -// queryType can be app or store +// queryType can be app or store. func isQueryStoreWithProof(path string) bool { if !strings.HasPrefix(path, "/") { return false } + paths := strings.SplitN(path[1:], "/", 3) if len(paths) != 3 { return false @@ -383,5 +255,6 @@ func isQueryStoreWithProof(path string) bool { if store.RequireProof("/" + paths[2]) { return true } + return false } diff --git a/client/lcd/lcd_test.go b/client/lcd/lcd_test.go index 30fa314c53..1da8edcc80 100644 --- a/client/lcd/lcd_test.go +++ b/client/lcd/lcd_test.go @@ -838,14 +838,16 @@ func doSendWithGas(t *testing.T, port, seed, name, password string, addr sdk.Acc `, gasAdjustment) } jsonStr := []byte(fmt.Sprintf(`{ - %v%v - "name":"%s", - "password":"%s", - "account_number":"%d", - "sequence":"%d", "amount":[%s], - "chain_id":"%s" - }`, gasStr, gasAdjustmentStr, name, password, accnum, sequence, coinbz, chainID)) + "base_req": { + %v%v + "name": "%s", + "password": "%s", + "chain_id": "%s", + "account_number":"%d", + "sequence":"%d" + } + }`, coinbz, gasStr, gasAdjustmentStr, name, password, chainID, accnum, sequence)) res, body = Request(t, port, "POST", fmt.Sprintf("/accounts/%s/send%v", receiveAddr, queryStr), jsonStr) return @@ -877,18 +879,20 @@ func doIBCTransfer(t *testing.T, port, seed, name, password string, addr sdk.Acc // send jsonStr := []byte(fmt.Sprintf(`{ - "name":"%s", - "password": "%s", - "account_number":"%d", - "sequence": "%d", - "src_chain_id": "%s", "amount":[ { "denom": "%s", "amount": "1" } - ] - }`, name, password, accnum, sequence, chainID, "steak")) + ], + "base_req": { + "name": "%s", + "password": "%s", + "chain_id": "%s", + "account_number":"%d", + "sequence":"%d" + } + }`, "steak", name, password, chainID, accnum, sequence)) res, body := Request(t, port, "POST", fmt.Sprintf("/ibc/testchain/%s/send", receiveAddr), jsonStr) require.Equal(t, http.StatusOK, res.StatusCode, body) @@ -997,11 +1001,6 @@ func doDelegate(t *testing.T, port, seed, name, password string, chainID := viper.GetString(client.FlagChainID) jsonStr := []byte(fmt.Sprintf(`{ - "name": "%s", - "password": "%s", - "account_number": "%d", - "sequence": "%d", - "chain_id": "%s", "delegations": [ { "delegator_addr": "%s", @@ -1012,8 +1011,15 @@ func doDelegate(t *testing.T, port, seed, name, password string, "begin_unbondings": [], "complete_unbondings": [], "begin_redelegates": [], - "complete_redelegates": [] - }`, name, password, accnum, sequence, chainID, delAddr, valAddr, "steak", amount)) + "complete_redelegates": [], + "base_req": { + "name": "%s", + "password": "%s", + "chain_id": "%s", + "account_number":"%d", + "sequence":"%d" + } + }`, delAddr, valAddr, "steak", amount, name, password, chainID, accnum, sequence)) res, body := Request(t, port, "POST", fmt.Sprintf("/stake/delegators/%s/delegations", delAddr), jsonStr) require.Equal(t, http.StatusOK, res.StatusCode, body) @@ -1034,11 +1040,6 @@ func doBeginUnbonding(t *testing.T, port, seed, name, password string, chainID := viper.GetString(client.FlagChainID) jsonStr := []byte(fmt.Sprintf(`{ - "name": "%s", - "password": "%s", - "account_number": "%d", - "sequence": "%d", - "chain_id": "%s", "delegations": [], "begin_unbondings": [ { @@ -1049,8 +1050,15 @@ func doBeginUnbonding(t *testing.T, port, seed, name, password string, ], "complete_unbondings": [], "begin_redelegates": [], - "complete_redelegates": [] - }`, name, password, accnum, sequence, chainID, delAddr, valAddr, amount)) + "complete_redelegates": [], + "base_req": { + "name": "%s", + "password": "%s", + "chain_id": "%s", + "account_number":"%d", + "sequence":"%d" + } + }`, delAddr, valAddr, amount, name, password, chainID, accnum, sequence)) res, body := Request(t, port, "POST", fmt.Sprintf("/stake/delegators/%s/delegations", delAddr), jsonStr) require.Equal(t, http.StatusOK, res.StatusCode, body) @@ -1072,11 +1080,6 @@ func doBeginRedelegation(t *testing.T, port, seed, name, password string, chainID := viper.GetString(client.FlagChainID) jsonStr := []byte(fmt.Sprintf(`{ - "name": "%s", - "password": "%s", - "account_number": "%d", - "sequence": "%d", - "chain_id": "%s", "delegations": [], "begin_unbondings": [], "complete_unbondings": [], @@ -1088,8 +1091,15 @@ func doBeginRedelegation(t *testing.T, port, seed, name, password string, "shares": "30" } ], - "complete_redelegates": [] - }`, name, password, accnum, sequence, chainID, delAddr, valSrcAddr, valDstAddr)) + "complete_redelegates": [], + "base_req": { + "name": "%s", + "password": "%s", + "chain_id": "%s", + "account_number":"%d", + "sequence":"%d" + } + }`, delAddr, valSrcAddr, valDstAddr, name, password, chainID, accnum, sequence)) res, body := Request(t, port, "POST", fmt.Sprintf("/stake/delegators/%s/delegations", delAddr), jsonStr) require.Equal(t, http.StatusOK, res.StatusCode, body) diff --git a/client/tx/broadcast.go b/client/tx/broadcast.go index 89ad48f43f..a494318255 100644 --- a/client/tx/broadcast.go +++ b/client/tx/broadcast.go @@ -25,7 +25,7 @@ func BroadcastTxRequestHandlerFn(cliCtx context.CLIContext) http.HandlerFunc { return } - res, err := cliCtx.BroadcastTx([]byte(m.TxBytes)) + res, err := cliCtx.BroadcastTxAndAwaitCommit([]byte(m.TxBytes)) if err != nil { w.WriteHeader(500) w.Write([]byte(err.Error())) diff --git a/client/utils/rest.go b/client/utils/rest.go index e0fae753db..5e3deb71df 100644 --- a/client/utils/rest.go +++ b/client/utils/rest.go @@ -2,10 +2,15 @@ package utils import ( "fmt" + "io/ioutil" "net/http" "net/url" "strconv" + "strings" + client "github.com/cosmos/cosmos-sdk/client" + "github.com/cosmos/cosmos-sdk/client/context" + "github.com/cosmos/cosmos-sdk/codec" sdk "github.com/cosmos/cosmos-sdk/types" auth "github.com/cosmos/cosmos-sdk/x/auth" authtxb "github.com/cosmos/cosmos-sdk/x/auth/client/txbuilder" @@ -16,6 +21,9 @@ const ( queryArgGenerateOnly = "generate_only" ) +//---------------------------------------- +// Basic HTTP utilities + // WriteErrorResponse prepares and writes a HTTP error // given a status code and an error message. func WriteErrorResponse(w http.ResponseWriter, status int, msg string) { @@ -30,24 +38,45 @@ func WriteSimulationResponse(w http.ResponseWriter, gas int64) { w.Write([]byte(fmt.Sprintf(`{"gas_estimate":%v}`, gas))) } -// HasDryRunArg returns true if the request's URL query contains -// the dry run argument and its value is set to "true". -func HasDryRunArg(r *http.Request) bool { return urlQueryHasArg(r.URL, queryArgDryRun) } +// HasDryRunArg returns true if the request's URL query contains the dry run +// argument and its value is set to "true". +func HasDryRunArg(r *http.Request) bool { + return urlQueryHasArg(r.URL, queryArgDryRun) +} -// HasGenerateOnlyArg returns whether a URL's query "generate-only" parameter is set to "true". -func HasGenerateOnlyArg(r *http.Request) bool { return urlQueryHasArg(r.URL, queryArgGenerateOnly) } +// HasGenerateOnlyArg returns whether a URL's query "generate-only" parameter +// is set to "true". +func HasGenerateOnlyArg(r *http.Request) bool { + return urlQueryHasArg(r.URL, queryArgGenerateOnly) +} -// ParseFloat64OrReturnBadRequest converts s to a float64 value. It returns a default -// value if the string is empty. Write +// ParseInt64OrReturnBadRequest converts s to a int64 value. +func ParseInt64OrReturnBadRequest(w http.ResponseWriter, s string) (n int64, ok bool) { + var err error + + n, err = strconv.ParseInt(s, 10, 64) + if err != nil { + err := fmt.Errorf("'%s' is not a valid int64", s) + WriteErrorResponse(w, http.StatusBadRequest, err.Error()) + return n, false + } + + return n, true +} + +// ParseFloat64OrReturnBadRequest converts s to a float64 value. It returns a +// default value, defaultIfEmpty, if the string is empty. func ParseFloat64OrReturnBadRequest(w http.ResponseWriter, s string, defaultIfEmpty float64) (n float64, ok bool) { if len(s) == 0 { return defaultIfEmpty, true } + n, err := strconv.ParseFloat(s, 64) if err != nil { WriteErrorResponse(w, http.StatusBadRequest, err.Error()) return n, false } + return n, true } @@ -58,13 +87,164 @@ func WriteGenerateStdTxResponse(w http.ResponseWriter, txBldr authtxb.TxBuilder, WriteErrorResponse(w, http.StatusBadRequest, err.Error()) return } + output, err := txBldr.Codec.MarshalJSON(auth.NewStdTx(stdMsg.Msgs, stdMsg.Fee, nil, stdMsg.Memo)) if err != nil { WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) return } + w.Write(output) return } func urlQueryHasArg(url *url.URL, arg string) bool { return url.Query().Get(arg) == "true" } + +//---------------------------------------- +// Building / Sending utilities + +// BaseReq defines a structure that can be embedded in other request structures +// that all share common "base" fields. +type BaseReq struct { + Name string `json:"name"` + Password string `json:"password"` + ChainID string `json:"chain_id"` + AccountNumber int64 `json:"account_number"` + Sequence int64 `json:"sequence"` + Gas string `json:"gas"` + GasAdjustment string `json:"gas_adjustment"` +} + +// Sanitize performs basic sanitization on a BaseReq object. +func (br BaseReq) Sanitize() BaseReq { + return BaseReq{ + Name: strings.TrimSpace(br.Name), + Password: strings.TrimSpace(br.Password), + ChainID: strings.TrimSpace(br.ChainID), + Gas: strings.TrimSpace(br.Gas), + GasAdjustment: strings.TrimSpace(br.GasAdjustment), + AccountNumber: br.AccountNumber, + Sequence: br.Sequence, + } +} + +/* +ReadRESTReq is a simple convenience wrapper that reads the body and +unmarshals to the req interface. + + Usage: + type SomeReq struct { + BaseReq `json:"base_req"` + CustomField string `json:"custom_field"` + } + + req := new(SomeReq) + err := ReadRESTReq(w, r, cdc, req) +*/ +func ReadRESTReq(w http.ResponseWriter, r *http.Request, cdc *codec.Codec, req interface{}) error { + body, err := ioutil.ReadAll(r.Body) + if err != nil { + WriteErrorResponse(w, http.StatusBadRequest, err.Error()) + return err + } + + err = cdc.UnmarshalJSON(body, req) + if err != nil { + WriteErrorResponse(w, http.StatusBadRequest, err.Error()) + return err + } + + return nil +} + +// ValidateBasic performs basic validation of a BaseReq. If custom validation +// logic is needed, the implementing request handler should perform those +// checks manually. +func (br BaseReq) ValidateBasic(w http.ResponseWriter) bool { + switch { + case len(br.Name) == 0: + WriteErrorResponse(w, http.StatusUnauthorized, "name required but not specified") + return false + + case len(br.Password) == 0: + WriteErrorResponse(w, http.StatusUnauthorized, "password required but not specified") + return false + + case len(br.ChainID) == 0: + WriteErrorResponse(w, http.StatusUnauthorized, "chainID required but not specified") + return false + } + + return true +} + +// CompleteAndBroadcastTxREST implements a utility function that facilitates +// sending a series of messages in a signed transaction given a TxBuilder and a +// QueryContext. It ensures that the account exists, has a proper number and +// sequence set. In addition, it builds and signs a transaction with the +// supplied messages. Finally, it broadcasts the signed transaction to a node. +// +// NOTE: Also see CompleteAndBroadcastTxCli. +// NOTE: Also see x/stake/client/rest/tx.go delegationsRequestHandlerFn. +func CompleteAndBroadcastTxREST(w http.ResponseWriter, r *http.Request, cliCtx context.CLIContext, baseReq BaseReq, msgs []sdk.Msg, cdc *codec.Codec) { + simulateGas, gas, err := client.ReadGasFlag(baseReq.Gas) + if err != nil { + WriteErrorResponse(w, http.StatusBadRequest, err.Error()) + return + } + + adjustment, ok := ParseFloat64OrReturnBadRequest(w, baseReq.GasAdjustment, client.DefaultGasAdjustment) + if !ok { + return + } + + txBldr := authtxb.TxBuilder{ + Codec: cdc, + Gas: gas, + GasAdjustment: adjustment, + SimulateGas: simulateGas, + ChainID: baseReq.ChainID, + AccountNumber: baseReq.AccountNumber, + Sequence: baseReq.Sequence, + } + + if HasDryRunArg(r) || txBldr.SimulateGas { + newBldr, err := EnrichCtxWithGas(txBldr, cliCtx, baseReq.Name, msgs) + if err != nil { + WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) + return + } + + if HasDryRunArg(r) { + WriteSimulationResponse(w, newBldr.Gas) + return + } + + txBldr = newBldr + } + + if HasGenerateOnlyArg(r) { + WriteGenerateStdTxResponse(w, txBldr, msgs) + return + } + + txBytes, err := txBldr.BuildAndSign(baseReq.Name, baseReq.Password, msgs) + if err != nil { + WriteErrorResponse(w, http.StatusUnauthorized, err.Error()) + return + } + + res, err := cliCtx.BroadcastTx(txBytes) + if err != nil { + WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) + return + } + + output, err := codec.MarshalJSONIndent(cdc, res) + if err != nil { + WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) + return + } + + w.Write(output) +} diff --git a/client/utils/utils.go b/client/utils/utils.go index 76ee91585e..9e72c552d1 100644 --- a/client/utils/utils.go +++ b/client/utils/utils.go @@ -14,13 +14,16 @@ import ( "github.com/tendermint/tendermint/libs/common" ) -// SendTx implements a auxiliary handler that facilitates sending a series of -// messages in a signed transaction given a TxBuilder and a QueryContext. It -// ensures that the account exists, has a proper number and sequence set. In -// addition, it builds and signs a transaction with the supplied messages. -// Finally, it broadcasts the signed transaction to a node. -func SendTx(txBldr authtxb.TxBuilder, cliCtx context.CLIContext, msgs []sdk.Msg) error { - txBldr, err := prepareTxContext(txBldr, cliCtx) +// CompleteAndBroadcastTxCli implements a utility function that +// facilitates sending a series of messages in a signed +// transaction given a TxBuilder and a QueryContext. It ensures +// that the account exists, has a proper number and sequence +// set. In addition, it builds and signs a transaction with the +// supplied messages. Finally, it broadcasts the signed +// transaction to a node. +// NOTE: Also see CompleteAndBroadcastTxREST. +func CompleteAndBroadcastTxCli(txBldr authtxb.TxBuilder, cliCtx context.CLIContext, msgs []sdk.Msg) error { + txBldr, err := prepareTxBuilder(txBldr, cliCtx) if err != nil { return err } @@ -52,7 +55,8 @@ func SendTx(txBldr authtxb.TxBuilder, cliCtx context.CLIContext, msgs []sdk.Msg) return err } // broadcast to a Tendermint node - return cliCtx.EnsureBroadcastTx(txBytes) + _, err = cliCtx.BroadcastTx(txBytes) + return err } // EnrichCtxWithGas calculates the gas estimate that would be consumed by the @@ -161,7 +165,7 @@ func parseQueryResponse(cdc *amino.Codec, rawRes []byte) (int64, error) { return simulationResult.GasUsed, nil } -func prepareTxContext(txBldr authtxb.TxBuilder, cliCtx context.CLIContext) (authtxb.TxBuilder, error) { +func prepareTxBuilder(txBldr authtxb.TxBuilder, cliCtx context.CLIContext) (authtxb.TxBuilder, error) { if err := cliCtx.EnsureAccountExists(); err != nil { return txBldr, err } @@ -196,7 +200,7 @@ func prepareTxContext(txBldr authtxb.TxBuilder, cliCtx context.CLIContext) (auth // buildUnsignedStdTx builds a StdTx as per the parameters passed in the // contexts. Gas is automatically estimated if gas wanted is set to 0. func buildUnsignedStdTx(txBldr authtxb.TxBuilder, cliCtx context.CLIContext, msgs []sdk.Msg) (stdTx auth.StdTx, err error) { - txBldr, err = prepareTxContext(txBldr, cliCtx) + txBldr, err = prepareTxBuilder(txBldr, cliCtx) if err != nil { return } diff --git a/examples/democoin/x/cool/client/cli/tx.go b/examples/democoin/x/cool/client/cli/tx.go index 2f78bba460..db6394e88a 100644 --- a/examples/democoin/x/cool/client/cli/tx.go +++ b/examples/democoin/x/cool/client/cli/tx.go @@ -34,7 +34,7 @@ func QuizTxCmd(cdc *codec.Codec) *cobra.Command { msg := cool.NewMsgQuiz(from, args[0]) - return utils.SendTx(txBldr, cliCtx, []sdk.Msg{msg}) + return utils.CompleteAndBroadcastTxCli(txBldr, cliCtx, []sdk.Msg{msg}) }, } } @@ -59,7 +59,7 @@ func SetTrendTxCmd(cdc *codec.Codec) *cobra.Command { msg := cool.NewMsgSetTrend(from, args[0]) - return utils.SendTx(txBldr, cliCtx, []sdk.Msg{msg}) + return utils.CompleteAndBroadcastTxCli(txBldr, cliCtx, []sdk.Msg{msg}) }, } } diff --git a/examples/democoin/x/pow/client/cli/tx.go b/examples/democoin/x/pow/client/cli/tx.go index f1d708c897..4f5dacb1a6 100644 --- a/examples/democoin/x/pow/client/cli/tx.go +++ b/examples/democoin/x/pow/client/cli/tx.go @@ -53,7 +53,7 @@ func MineCmd(cdc *codec.Codec) *cobra.Command { // Build and sign the transaction, then broadcast to a Tendermint // node. - return utils.SendTx(txBldr, cliCtx, []sdk.Msg{msg}) + return utils.CompleteAndBroadcastTxCli(txBldr, cliCtx, []sdk.Msg{msg}) }, } } diff --git a/examples/democoin/x/simplestake/client/cli/commands.go b/examples/democoin/x/simplestake/client/cli/commands.go index d09bf64f37..5d2790e70d 100644 --- a/examples/democoin/x/simplestake/client/cli/commands.go +++ b/examples/democoin/x/simplestake/client/cli/commands.go @@ -68,7 +68,7 @@ func BondTxCmd(cdc *codec.Codec) *cobra.Command { // Build and sign the transaction, then broadcast to a Tendermint // node. - return utils.SendTx(txBldr, cliCtx, []sdk.Msg{msg}) + return utils.CompleteAndBroadcastTxCli(txBldr, cliCtx, []sdk.Msg{msg}) }, } @@ -98,7 +98,7 @@ func UnbondTxCmd(cdc *codec.Codec) *cobra.Command { // Build and sign the transaction, then broadcast to a Tendermint // node. - return utils.SendTx(txBldr, cliCtx, []sdk.Msg{msg}) + return utils.CompleteAndBroadcastTxCli(txBldr, cliCtx, []sdk.Msg{msg}) }, } diff --git a/x/bank/client/cli/broadcast.go b/x/bank/client/cli/broadcast.go index cef88246c4..bf3d529433 100644 --- a/x/bank/client/cli/broadcast.go +++ b/x/bank/client/cli/broadcast.go @@ -29,9 +29,12 @@ in place of an input filename, the command reads from standard input.`, if err != nil { return } - return cliCtx.EnsureBroadcastTx(txBytes) + + _, err = cliCtx.BroadcastTx(txBytes) + return err }, } + return cmd } diff --git a/x/bank/client/cli/sendtx.go b/x/bank/client/cli/sendtx.go index 31fe182991..befe6bf00c 100644 --- a/x/bank/client/cli/sendtx.go +++ b/x/bank/client/cli/sendtx.go @@ -67,12 +67,12 @@ func SendTxCmd(cdc *codec.Codec) *cobra.Command { } // build and sign the transaction, then broadcast to Tendermint - msg := client.BuildMsg(from, to, coins) + msg := client.CreateMsg(from, to, coins) if cliCtx.GenerateOnly { return utils.PrintUnsignedStdTx(txBldr, cliCtx, []sdk.Msg{msg}) } - return utils.SendTx(txBldr, cliCtx, []sdk.Msg{msg}) + return utils.CompleteAndBroadcastTxCli(txBldr, cliCtx, []sdk.Msg{msg}) }, } diff --git a/x/bank/client/rest/sendtx.go b/x/bank/client/rest/sendtx.go index 2a1e51f0de..e186db773a 100644 --- a/x/bank/client/rest/sendtx.go +++ b/x/bank/client/rest/sendtx.go @@ -1,16 +1,13 @@ package rest import ( - "io/ioutil" "net/http" - cliclient "github.com/cosmos/cosmos-sdk/client" "github.com/cosmos/cosmos-sdk/client/context" "github.com/cosmos/cosmos-sdk/client/utils" "github.com/cosmos/cosmos-sdk/codec" "github.com/cosmos/cosmos-sdk/crypto/keys" sdk "github.com/cosmos/cosmos-sdk/types" - authtxb "github.com/cosmos/cosmos-sdk/x/auth/client/txbuilder" "github.com/cosmos/cosmos-sdk/x/bank" "github.com/cosmos/cosmos-sdk/x/bank/client" @@ -23,17 +20,9 @@ func RegisterRoutes(cliCtx context.CLIContext, r *mux.Router, cdc *codec.Codec, r.HandleFunc("/tx/broadcast", BroadcastTxRequestHandlerFn(cdc, cliCtx)).Methods("POST") } -type sendBody struct { - // fees is not used currently - // Fees sdk.Coin `json="fees"` - Amount sdk.Coins `json:"amount"` - LocalAccountName string `json:"name"` - Password string `json:"password"` - ChainID string `json:"chain_id"` - AccountNumber int64 `json:"account_number"` - Sequence int64 `json:"sequence"` - Gas string `json:"gas"` - GasAdjustment string `json:"gas_adjustment"` +type sendReq struct { + BaseReq utils.BaseReq `json:"base_req"` + Amount sdk.Coins `json:"amount"` } var msgCdc = codec.New() @@ -42,100 +31,36 @@ func init() { bank.RegisterCodec(msgCdc) } -// SendRequestHandlerFn - http request handler to send coins to a address +// SendRequestHandlerFn - http request handler to send coins to a address. func SendRequestHandlerFn(cdc *codec.Codec, kb keys.Keybase, cliCtx context.CLIContext) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { - // collect data vars := mux.Vars(r) - bech32addr := vars["address"] + bech32Addr := vars["address"] - to, err := sdk.AccAddressFromBech32(bech32addr) + to, err := sdk.AccAddressFromBech32(bech32Addr) if err != nil { utils.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) return } - var m sendBody - body, err := ioutil.ReadAll(r.Body) + var req sendReq + err = utils.ReadRESTReq(w, r, cdc, &req) if err != nil { - utils.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) - return - } - err = msgCdc.UnmarshalJSON(body, &m) - if err != nil { - utils.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) return } - info, err := kb.Get(m.LocalAccountName) + baseReq := req.BaseReq.Sanitize() + if !baseReq.ValidateBasic(w) { + return + } + + info, err := kb.Get(baseReq.Name) if err != nil { utils.WriteErrorResponse(w, http.StatusUnauthorized, err.Error()) return } - // build message - msg := client.BuildMsg(sdk.AccAddress(info.GetPubKey().Address()), to, m.Amount) - if err != nil { // XXX rechecking same error ? - utils.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) - return - } - - simulateGas, gas, err := cliclient.ReadGasFlag(m.Gas) - if err != nil { - utils.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) - return - } - - adjustment, ok := utils.ParseFloat64OrReturnBadRequest(w, m.GasAdjustment, cliclient.DefaultGasAdjustment) - if !ok { - return - } - txBldr := authtxb.TxBuilder{ - Codec: cdc, - Gas: gas, - GasAdjustment: adjustment, - SimulateGas: simulateGas, - ChainID: m.ChainID, - AccountNumber: m.AccountNumber, - Sequence: m.Sequence, - } - - if utils.HasDryRunArg(r) || txBldr.SimulateGas { - newBldr, err := utils.EnrichCtxWithGas(txBldr, cliCtx, m.LocalAccountName, []sdk.Msg{msg}) - if err != nil { - utils.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) - return - } - if utils.HasDryRunArg(r) { - utils.WriteSimulationResponse(w, newBldr.Gas) - return - } - txBldr = newBldr - } - - if utils.HasGenerateOnlyArg(r) { - utils.WriteGenerateStdTxResponse(w, txBldr, []sdk.Msg{msg}) - return - } - - txBytes, err := txBldr.BuildAndSign(m.LocalAccountName, m.Password, []sdk.Msg{msg}) - if err != nil { - utils.WriteErrorResponse(w, http.StatusUnauthorized, err.Error()) - return - } - - res, err := cliCtx.BroadcastTx(txBytes) - if err != nil { - utils.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) - return - } - - output, err := codec.MarshalJSONIndent(cdc, res) - if err != nil { - utils.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) - return - } - - w.Write(output) + msg := client.CreateMsg(sdk.AccAddress(info.GetPubKey().Address()), to, req.Amount) + utils.CompleteAndBroadcastTxREST(w, r, cliCtx, baseReq, []sdk.Msg{msg}, cdc) } } diff --git a/x/bank/client/util.go b/x/bank/client/util.go index c733517a84..b647f00d3f 100644 --- a/x/bank/client/util.go +++ b/x/bank/client/util.go @@ -5,8 +5,8 @@ import ( bank "github.com/cosmos/cosmos-sdk/x/bank" ) -// build the sendTx msg -func BuildMsg(from sdk.AccAddress, to sdk.AccAddress, coins sdk.Coins) sdk.Msg { +// create the sendTx msg +func CreateMsg(from sdk.AccAddress, to sdk.AccAddress, coins sdk.Coins) sdk.Msg { input := bank.NewInput(from, coins) output := bank.NewOutput(to, coins) msg := bank.NewMsgSend([]bank.Input{input}, []bank.Output{output}) diff --git a/x/gov/client/cli/tx.go b/x/gov/client/cli/tx.go index 1bd01b58ab..cb897f43af 100644 --- a/x/gov/client/cli/tx.go +++ b/x/gov/client/cli/tx.go @@ -111,7 +111,7 @@ $ gaiacli gov submit-proposal --title="Test Proposal" --description="My awesome // Build and sign the transaction, then broadcast to Tendermint // proposalID must be returned, and it is a part of response. cliCtx.PrintResponse = true - return utils.SendTx(txBldr, cliCtx, []sdk.Msg{msg}) + return utils.CompleteAndBroadcastTxCli(txBldr, cliCtx, []sdk.Msg{msg}) }, } @@ -191,7 +191,7 @@ func GetCmdDeposit(cdc *codec.Codec) *cobra.Command { // Build and sign the transaction, then broadcast to a Tendermint // node. - return utils.SendTx(txBldr, cliCtx, []sdk.Msg{msg}) + return utils.CompleteAndBroadcastTxCli(txBldr, cliCtx, []sdk.Msg{msg}) }, } @@ -242,7 +242,7 @@ func GetCmdVote(cdc *codec.Codec) *cobra.Command { // Build and sign the transaction, then broadcast to a Tendermint // node. - return utils.SendTx(txBldr, cliCtx, []sdk.Msg{msg}) + return utils.CompleteAndBroadcastTxCli(txBldr, cliCtx, []sdk.Msg{msg}) }, } diff --git a/x/gov/client/rest/rest.go b/x/gov/client/rest/rest.go index 3f62691c74..f66331ad5a 100644 --- a/x/gov/client/rest/rest.go +++ b/x/gov/client/rest/rest.go @@ -41,7 +41,7 @@ func RegisterRoutes(cliCtx context.CLIContext, r *mux.Router, cdc *codec.Codec) } type postProposalReq struct { - BaseReq baseReq `json:"base_req"` + BaseReq utils.BaseReq `json:"base_req"` Title string `json:"title"` // Title of the proposal Description string `json:"description"` // Description of the proposal ProposalType gov.ProposalKind `json:"proposal_type"` // Type of proposal. Initial set {PlainTextProposal, SoftwareUpgradeProposal} @@ -50,13 +50,13 @@ type postProposalReq struct { } type depositReq struct { - BaseReq baseReq `json:"base_req"` + BaseReq utils.BaseReq `json:"base_req"` Depositer sdk.AccAddress `json:"depositer"` // Address of the depositer Amount sdk.Coins `json:"amount"` // Coins to add to the proposal's deposit } type voteReq struct { - BaseReq baseReq `json:"base_req"` + BaseReq utils.BaseReq `json:"base_req"` Voter sdk.AccAddress `json:"voter"` // address of the voter Option gov.VoteOption `json:"option"` // option from OptionSet chosen by the voter } @@ -64,12 +64,13 @@ type voteReq struct { func postProposalHandlerFn(cdc *codec.Codec, cliCtx context.CLIContext) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { var req postProposalReq - err := buildReq(w, r, cdc, &req) + err := utils.ReadRESTReq(w, r, cdc, &req) if err != nil { return } - if !req.BaseReq.baseReqValidate(w) { + baseReq := req.BaseReq.Sanitize() + if !baseReq.ValidateBasic(w) { return } @@ -81,7 +82,7 @@ func postProposalHandlerFn(cdc *codec.Codec, cliCtx context.CLIContext) http.Han return } - signAndBuild(w, r, cliCtx, req.BaseReq, msg, cdc) + utils.CompleteAndBroadcastTxREST(w, r, cliCtx, baseReq, []sdk.Msg{msg}, cdc) } } @@ -96,17 +97,19 @@ func depositHandlerFn(cdc *codec.Codec, cliCtx context.CLIContext) http.HandlerF return } - proposalID, ok := parseInt64OrReturnBadRequest(strProposalID, w) + proposalID, ok := utils.ParseInt64OrReturnBadRequest(w, strProposalID) if !ok { return } var req depositReq - err := buildReq(w, r, cdc, &req) + err := utils.ReadRESTReq(w, r, cdc, &req) if err != nil { return } - if !req.BaseReq.baseReqValidate(w) { + + baseReq := req.BaseReq.Sanitize() + if !baseReq.ValidateBasic(w) { return } @@ -118,7 +121,7 @@ func depositHandlerFn(cdc *codec.Codec, cliCtx context.CLIContext) http.HandlerF return } - signAndBuild(w, r, cliCtx, req.BaseReq, msg, cdc) + utils.CompleteAndBroadcastTxREST(w, r, cliCtx, baseReq, []sdk.Msg{msg}, cdc) } } @@ -133,17 +136,19 @@ func voteHandlerFn(cdc *codec.Codec, cliCtx context.CLIContext) http.HandlerFunc return } - proposalID, ok := parseInt64OrReturnBadRequest(strProposalID, w) + proposalID, ok := utils.ParseInt64OrReturnBadRequest(w, strProposalID) if !ok { return } var req voteReq - err := buildReq(w, r, cdc, &req) + err := utils.ReadRESTReq(w, r, cdc, &req) if err != nil { return } - if !req.BaseReq.baseReqValidate(w) { + + baseReq := req.BaseReq.Sanitize() + if !baseReq.ValidateBasic(w) { return } @@ -155,7 +160,7 @@ func voteHandlerFn(cdc *codec.Codec, cliCtx context.CLIContext) http.HandlerFunc return } - signAndBuild(w, r, cliCtx, req.BaseReq, msg, cdc) + utils.CompleteAndBroadcastTxREST(w, r, cliCtx, baseReq, []sdk.Msg{msg}, cdc) } } @@ -170,7 +175,7 @@ func queryProposalHandlerFn(cdc *codec.Codec) http.HandlerFunc { return } - proposalID, ok := parseInt64OrReturnBadRequest(strProposalID, w) + proposalID, ok := utils.ParseInt64OrReturnBadRequest(w, strProposalID) if !ok { return } @@ -209,7 +214,7 @@ func queryDepositHandlerFn(cdc *codec.Codec) http.HandlerFunc { return } - proposalID, ok := parseInt64OrReturnBadRequest(strProposalID, w) + proposalID, ok := utils.ParseInt64OrReturnBadRequest(w, strProposalID) if !ok { return } @@ -276,7 +281,7 @@ func queryVoteHandlerFn(cdc *codec.Codec) http.HandlerFunc { return } - proposalID, ok := parseInt64OrReturnBadRequest(strProposalID, w) + proposalID, ok := utils.ParseInt64OrReturnBadRequest(w, strProposalID) if !ok { return } @@ -346,7 +351,7 @@ func queryVotesOnProposalHandlerFn(cdc *codec.Codec) http.HandlerFunc { return } - proposalID, ok := parseInt64OrReturnBadRequest(strProposalID, w) + proposalID, ok := utils.ParseInt64OrReturnBadRequest(w, strProposalID) if !ok { return } @@ -412,7 +417,7 @@ func queryProposalsWithParameterFn(cdc *codec.Codec) http.HandlerFunc { params.ProposalStatus = proposalStatus } if len(strNumLatest) != 0 { - numLatest, ok := parseInt64OrReturnBadRequest(strNumLatest, w) + numLatest, ok := utils.ParseInt64OrReturnBadRequest(w, strNumLatest) if !ok { return } @@ -451,7 +456,7 @@ func queryTallyOnProposalHandlerFn(cdc *codec.Codec) http.HandlerFunc { return } - proposalID, ok := parseInt64OrReturnBadRequest(strProposalID, w) + proposalID, ok := utils.ParseInt64OrReturnBadRequest(w, strProposalID) if !ok { return } diff --git a/x/gov/client/rest/util.go b/x/gov/client/rest/util.go deleted file mode 100644 index 0bc1a8804e..0000000000 --- a/x/gov/client/rest/util.go +++ /dev/null @@ -1,141 +0,0 @@ -package rest - -import ( - "fmt" - "io/ioutil" - "net/http" - "strconv" - - "github.com/cosmos/cosmos-sdk/client" - "github.com/cosmos/cosmos-sdk/client/context" - "github.com/cosmos/cosmos-sdk/client/utils" - "github.com/cosmos/cosmos-sdk/codec" - sdk "github.com/cosmos/cosmos-sdk/types" - authtxb "github.com/cosmos/cosmos-sdk/x/auth/client/txbuilder" -) - -type baseReq struct { - Name string `json:"name"` - Password string `json:"password"` - ChainID string `json:"chain_id"` - AccountNumber int64 `json:"account_number"` - Sequence int64 `json:"sequence"` - Gas string `json:"gas"` - GasAdjustment string `json:"gas_adjustment"` -} - -func buildReq(w http.ResponseWriter, r *http.Request, cdc *codec.Codec, req interface{}) error { - body, err := ioutil.ReadAll(r.Body) - if err != nil { - utils.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) - return err - } - err = cdc.UnmarshalJSON(body, req) - if err != nil { - utils.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) - return err - } - return nil -} - -func (req baseReq) baseReqValidate(w http.ResponseWriter) bool { - if len(req.Name) == 0 { - utils.WriteErrorResponse(w, http.StatusUnauthorized, "Name required but not specified") - return false - } - - if len(req.Password) == 0 { - utils.WriteErrorResponse(w, http.StatusUnauthorized, "Password required but not specified") - return false - } - - if len(req.ChainID) == 0 { - utils.WriteErrorResponse(w, http.StatusUnauthorized, "ChainID required but not specified") - return false - } - - if req.AccountNumber < 0 { - utils.WriteErrorResponse(w, http.StatusUnauthorized, "Account Number required but not specified") - return false - } - - if req.Sequence < 0 { - utils.WriteErrorResponse(w, http.StatusUnauthorized, "Sequence required but not specified") - return false - } - return true -} - -// TODO: Build this function out into a more generic base-request -// (probably should live in client/lcd). -func signAndBuild(w http.ResponseWriter, r *http.Request, cliCtx context.CLIContext, baseReq baseReq, msg sdk.Msg, cdc *codec.Codec) { - simulateGas, gas, err := client.ReadGasFlag(baseReq.Gas) - if err != nil { - utils.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) - return - } - - adjustment, ok := utils.ParseFloat64OrReturnBadRequest(w, baseReq.GasAdjustment, client.DefaultGasAdjustment) - if !ok { - return - } - txBldr := authtxb.TxBuilder{ - Codec: cdc, - Gas: gas, - GasAdjustment: adjustment, - SimulateGas: simulateGas, - ChainID: baseReq.ChainID, - AccountNumber: baseReq.AccountNumber, - Sequence: baseReq.Sequence, - } - - if utils.HasDryRunArg(r) || txBldr.SimulateGas { - newBldr, err := utils.EnrichCtxWithGas(txBldr, cliCtx, baseReq.Name, []sdk.Msg{msg}) - if err != nil { - utils.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) - return - } - if utils.HasDryRunArg(r) { - utils.WriteSimulationResponse(w, newBldr.Gas) - return - } - txBldr = newBldr - } - - if utils.HasGenerateOnlyArg(r) { - utils.WriteGenerateStdTxResponse(w, txBldr, []sdk.Msg{msg}) - return - } - - txBytes, err := txBldr.BuildAndSign(baseReq.Name, baseReq.Password, []sdk.Msg{msg}) - if err != nil { - utils.WriteErrorResponse(w, http.StatusUnauthorized, err.Error()) - return - } - - res, err := cliCtx.BroadcastTx(txBytes) - if err != nil { - utils.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) - return - } - - output, err := codec.MarshalJSONIndent(cdc, res) - if err != nil { - utils.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) - return - } - - w.Write(output) -} - -func parseInt64OrReturnBadRequest(s string, w http.ResponseWriter) (n int64, ok bool) { - var err error - n, err = strconv.ParseInt(s, 10, 64) - if err != nil { - w.WriteHeader(http.StatusBadRequest) - err := fmt.Errorf("'%s' is not a valid int64", s) - w.Write([]byte(err.Error())) - return 0, false - } - return n, true -} diff --git a/x/ibc/client/cli/ibctx.go b/x/ibc/client/cli/ibctx.go index d34d6687b8..f58b178793 100644 --- a/x/ibc/client/cli/ibctx.go +++ b/x/ibc/client/cli/ibctx.go @@ -47,7 +47,7 @@ func IBCTransferCmd(cdc *codec.Codec) *cobra.Command { return utils.PrintUnsignedStdTx(txBldr, cliCtx, []sdk.Msg{msg}) } - return utils.SendTx(txBldr, cliCtx, []sdk.Msg{msg}) + return utils.CompleteAndBroadcastTxCli(txBldr, cliCtx, []sdk.Msg{msg}) }, } diff --git a/x/ibc/client/rest/transfer.go b/x/ibc/client/rest/transfer.go index d151f8aa58..19c9219711 100644 --- a/x/ibc/client/rest/transfer.go +++ b/x/ibc/client/rest/transfer.go @@ -1,16 +1,13 @@ package rest import ( - "io/ioutil" "net/http" - "github.com/cosmos/cosmos-sdk/client" "github.com/cosmos/cosmos-sdk/client/context" "github.com/cosmos/cosmos-sdk/client/utils" "github.com/cosmos/cosmos-sdk/codec" "github.com/cosmos/cosmos-sdk/crypto/keys" sdk "github.com/cosmos/cosmos-sdk/types" - authtxb "github.com/cosmos/cosmos-sdk/x/auth/client/txbuilder" "github.com/cosmos/cosmos-sdk/x/ibc" "github.com/gorilla/mux" @@ -21,110 +18,48 @@ func RegisterRoutes(cliCtx context.CLIContext, r *mux.Router, cdc *codec.Codec, r.HandleFunc("/ibc/{destchain}/{address}/send", TransferRequestHandlerFn(cdc, kb, cliCtx)).Methods("POST") } -type transferBody struct { - // Fees sdk.Coin `json="fees"` - Amount sdk.Coins `json:"amount"` - LocalAccountName string `json:"name"` - Password string `json:"password"` - SrcChainID string `json:"src_chain_id"` - AccountNumber int64 `json:"account_number"` - Sequence int64 `json:"sequence"` - Gas string `json:"gas"` - GasAdjustment string `json:"gas_adjustment"` +type transferReq struct { + BaseReq utils.BaseReq `json:"base_req"` + Amount sdk.Coins `json:"amount"` } // TransferRequestHandler - http request handler to transfer coins to a address -// on a different chain via IBC +// on a different chain via IBC. func TransferRequestHandlerFn(cdc *codec.Codec, kb keys.Keybase, cliCtx context.CLIContext) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { vars := mux.Vars(r) destChainID := vars["destchain"] - bech32addr := vars["address"] + bech32Addr := vars["address"] - to, err := sdk.AccAddressFromBech32(bech32addr) + to, err := sdk.AccAddressFromBech32(bech32Addr) if err != nil { utils.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) return } - var m transferBody - body, err := ioutil.ReadAll(r.Body) + var req transferReq + err = utils.ReadRESTReq(w, r, cdc, &req) if err != nil { - utils.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) return } - err = cdc.UnmarshalJSON(body, &m) - if err != nil { - utils.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) + baseReq := req.BaseReq.Sanitize() + if !baseReq.ValidateBasic(w) { return } - info, err := kb.Get(m.LocalAccountName) + info, err := kb.Get(baseReq.Name) if err != nil { utils.WriteErrorResponse(w, http.StatusUnauthorized, err.Error()) return } - // build message - packet := ibc.NewIBCPacket(sdk.AccAddress(info.GetPubKey().Address()), to, m.Amount, m.SrcChainID, destChainID) - msg := ibc.IBCTransferMsg{packet} + packet := ibc.NewIBCPacket( + sdk.AccAddress(info.GetPubKey().Address()), to, + req.Amount, baseReq.ChainID, destChainID, + ) + msg := ibc.IBCTransferMsg{IBCPacket: packet} - simulateGas, gas, err := client.ReadGasFlag(m.Gas) - if err != nil { - utils.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) - return - } - adjustment, ok := utils.ParseFloat64OrReturnBadRequest(w, m.GasAdjustment, client.DefaultGasAdjustment) - if !ok { - return - } - txBldr := authtxb.TxBuilder{ - Codec: cdc, - Gas: gas, - GasAdjustment: adjustment, - SimulateGas: simulateGas, - ChainID: m.SrcChainID, - AccountNumber: m.AccountNumber, - Sequence: m.Sequence, - } - - if utils.HasDryRunArg(r) || txBldr.SimulateGas { - newCtx, err := utils.EnrichCtxWithGas(txBldr, cliCtx, m.LocalAccountName, []sdk.Msg{msg}) - if err != nil { - utils.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) - return - } - if utils.HasDryRunArg(r) { - utils.WriteSimulationResponse(w, txBldr.Gas) - return - } - txBldr = newCtx - } - - if utils.HasGenerateOnlyArg(r) { - utils.WriteGenerateStdTxResponse(w, txBldr, []sdk.Msg{msg}) - return - } - - txBytes, err := txBldr.BuildAndSign(m.LocalAccountName, m.Password, []sdk.Msg{msg}) - if err != nil { - utils.WriteErrorResponse(w, http.StatusUnauthorized, err.Error()) - return - } - - res, err := cliCtx.BroadcastTx(txBytes) - if err != nil { - utils.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) - return - } - - output, err := cdc.MarshalJSON(res) - if err != nil { - utils.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) - return - } - - w.Write(output) + utils.CompleteAndBroadcastTxREST(w, r, cliCtx, baseReq, []sdk.Msg{msg}, cdc) } } diff --git a/x/slashing/client/cli/tx.go b/x/slashing/client/cli/tx.go index 9d7e46578c..ad3646ea17 100644 --- a/x/slashing/client/cli/tx.go +++ b/x/slashing/client/cli/tx.go @@ -36,7 +36,7 @@ func GetCmdUnjail(cdc *codec.Codec) *cobra.Command { if cliCtx.GenerateOnly { return utils.PrintUnsignedStdTx(txBldr, cliCtx, []sdk.Msg{msg}) } - return utils.SendTx(txBldr, cliCtx, []sdk.Msg{msg}) + return utils.CompleteAndBroadcastTxCli(txBldr, cliCtx, []sdk.Msg{msg}) }, } diff --git a/x/slashing/client/rest/tx.go b/x/slashing/client/rest/tx.go index 94537c2eb3..972d4351fa 100644 --- a/x/slashing/client/rest/tx.go +++ b/x/slashing/client/rest/tx.go @@ -2,18 +2,14 @@ package rest import ( "bytes" - "encoding/json" "fmt" - "io/ioutil" "net/http" - "github.com/cosmos/cosmos-sdk/client" "github.com/cosmos/cosmos-sdk/client/context" "github.com/cosmos/cosmos-sdk/client/utils" "github.com/cosmos/cosmos-sdk/codec" "github.com/cosmos/cosmos-sdk/crypto/keys" sdk "github.com/cosmos/cosmos-sdk/types" - authtxb "github.com/cosmos/cosmos-sdk/x/auth/client/txbuilder" "github.com/cosmos/cosmos-sdk/x/slashing" "github.com/gorilla/mux" @@ -27,98 +23,45 @@ func registerTxRoutes(cliCtx context.CLIContext, r *mux.Router, cdc *codec.Codec } // Unjail TX body -type UnjailBody struct { - LocalAccountName string `json:"name"` - Password string `json:"password"` - ChainID string `json:"chain_id"` - AccountNumber int64 `json:"account_number"` - Sequence int64 `json:"sequence"` - Gas int64 `json:"gas"` - GasAdjustment string `json:"gas_adjustment"` - ValidatorAddr string `json:"validator_addr"` +type UnjailReq struct { + BaseReq utils.BaseReq `json:"base_req"` + ValidatorAddr string `json:"validator_addr"` } func unjailRequestHandlerFn(cdc *codec.Codec, kb keys.Keybase, cliCtx context.CLIContext) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { - var m UnjailBody - body, err := ioutil.ReadAll(r.Body) + var req UnjailReq + err := utils.ReadRESTReq(w, r, cdc, &req) if err != nil { - utils.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) - return - } - err = json.Unmarshal(body, &m) - if err != nil { - utils.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) return } - info, err := kb.Get(m.LocalAccountName) + baseReq := req.BaseReq.Sanitize() + if !baseReq.ValidateBasic(w) { + return + } + + info, err := kb.Get(baseReq.Name) if err != nil { utils.WriteErrorResponse(w, http.StatusUnauthorized, err.Error()) return } - valAddr, err := sdk.ValAddressFromBech32(m.ValidatorAddr) + valAddr, err := sdk.ValAddressFromBech32(req.ValidatorAddr) if err != nil { - utils.WriteErrorResponse(w, http.StatusInternalServerError, fmt.Sprintf("Couldn't decode validator. Error: %s", err.Error())) + utils.WriteErrorResponse( + w, http.StatusInternalServerError, + fmt.Sprintf("failed to decode validator; error: %s", err.Error()), + ) return } if !bytes.Equal(info.GetPubKey().Address(), valAddr) { - utils.WriteErrorResponse(w, http.StatusUnauthorized, "Must use own validator address") + utils.WriteErrorResponse(w, http.StatusUnauthorized, "must use own validator address") return } - adjustment, ok := utils.ParseFloat64OrReturnBadRequest(w, m.GasAdjustment, client.DefaultGasAdjustment) - if !ok { - return - } - txBldr := authtxb.TxBuilder{ - Codec: cdc, - ChainID: m.ChainID, - AccountNumber: m.AccountNumber, - Sequence: m.Sequence, - Gas: m.Gas, - GasAdjustment: adjustment, - } - msg := slashing.NewMsgUnjail(valAddr) - if utils.HasDryRunArg(r) || m.Gas == 0 { - newCtx, err := utils.EnrichCtxWithGas(txBldr, cliCtx, m.LocalAccountName, []sdk.Msg{msg}) - if err != nil { - utils.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) - return - } - if utils.HasDryRunArg(r) { - utils.WriteSimulationResponse(w, txBldr.Gas) - return - } - txBldr = newCtx - } - - if utils.HasGenerateOnlyArg(r) { - utils.WriteGenerateStdTxResponse(w, txBldr, []sdk.Msg{msg}) - return - } - - txBytes, err := txBldr.BuildAndSign(m.LocalAccountName, m.Password, []sdk.Msg{msg}) - if err != nil { - utils.WriteErrorResponse(w, http.StatusUnauthorized, "Must use own validator address") - return - } - - res, err := cliCtx.BroadcastTx(txBytes) - if err != nil { - utils.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) - return - } - - output, err := json.MarshalIndent(res, "", " ") - if err != nil { - utils.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) - return - } - - w.Write(output) + utils.CompleteAndBroadcastTxREST(w, r, cliCtx, baseReq, []sdk.Msg{msg}, cdc) } } diff --git a/x/stake/client/cli/tx.go b/x/stake/client/cli/tx.go index 74f4c6d6e7..c68c12a5d4 100644 --- a/x/stake/client/cli/tx.go +++ b/x/stake/client/cli/tx.go @@ -94,7 +94,7 @@ func GetCmdCreateValidator(cdc *codec.Codec) *cobra.Command { } // build and sign the transaction, then broadcast to Tendermint - return utils.SendTx(txBldr, cliCtx, []sdk.Msg{msg}) + return utils.CompleteAndBroadcastTxCli(txBldr, cliCtx, []sdk.Msg{msg}) }, } @@ -150,7 +150,7 @@ func GetCmdEditValidator(cdc *codec.Codec) *cobra.Command { } // build and sign the transaction, then broadcast to Tendermint - return utils.SendTx(txBldr, cliCtx, []sdk.Msg{msg}) + return utils.CompleteAndBroadcastTxCli(txBldr, cliCtx, []sdk.Msg{msg}) }, } @@ -193,7 +193,7 @@ func GetCmdDelegate(cdc *codec.Codec) *cobra.Command { return utils.PrintUnsignedStdTx(txBldr, cliCtx, []sdk.Msg{msg}) } // build and sign the transaction, then broadcast to Tendermint - return utils.SendTx(txBldr, cliCtx, []sdk.Msg{msg}) + return utils.CompleteAndBroadcastTxCli(txBldr, cliCtx, []sdk.Msg{msg}) }, } @@ -265,7 +265,7 @@ func GetCmdBeginRedelegate(storeName string, cdc *codec.Codec) *cobra.Command { return utils.PrintUnsignedStdTx(txBldr, cliCtx, []sdk.Msg{msg}) } // build and sign the transaction, then broadcast to Tendermint - return utils.SendTx(txBldr, cliCtx, []sdk.Msg{msg}) + return utils.CompleteAndBroadcastTxCli(txBldr, cliCtx, []sdk.Msg{msg}) }, } @@ -308,7 +308,7 @@ func GetCmdCompleteRedelegate(cdc *codec.Codec) *cobra.Command { return utils.PrintUnsignedStdTx(txBldr, cliCtx, []sdk.Msg{msg}) } // build and sign the transaction, then broadcast to Tendermint - return utils.SendTx(txBldr, cliCtx, []sdk.Msg{msg}) + return utils.CompleteAndBroadcastTxCli(txBldr, cliCtx, []sdk.Msg{msg}) }, } @@ -372,7 +372,7 @@ func GetCmdBeginUnbonding(storeName string, cdc *codec.Codec) *cobra.Command { return utils.PrintUnsignedStdTx(txBldr, cliCtx, []sdk.Msg{msg}) } // build and sign the transaction, then broadcast to Tendermint - return utils.SendTx(txBldr, cliCtx, []sdk.Msg{msg}) + return utils.CompleteAndBroadcastTxCli(txBldr, cliCtx, []sdk.Msg{msg}) }, } @@ -410,7 +410,7 @@ func GetCmdCompleteUnbonding(cdc *codec.Codec) *cobra.Command { return utils.PrintUnsignedStdTx(txBldr, cliCtx, []sdk.Msg{msg}) } // build and sign the transaction, then broadcast to Tendermint - return utils.SendTx(txBldr, cliCtx, []sdk.Msg{msg}) + return utils.CompleteAndBroadcastTxCli(txBldr, cliCtx, []sdk.Msg{msg}) }, } diff --git a/x/stake/client/rest/tx.go b/x/stake/client/rest/tx.go index 324e0c914f..ee24322281 100644 --- a/x/stake/client/rest/tx.go +++ b/x/stake/client/rest/tx.go @@ -27,53 +27,55 @@ func registerTxRoutes(cliCtx context.CLIContext, r *mux.Router, cdc *codec.Codec ).Methods("POST") } -type msgDelegationsInput struct { - DelegatorAddr string `json:"delegator_addr"` // in bech32 - ValidatorAddr string `json:"validator_addr"` // in bech32 - Delegation sdk.Coin `json:"delegation"` -} -type msgBeginRedelegateInput struct { - DelegatorAddr string `json:"delegator_addr"` // in bech32 - ValidatorSrcAddr string `json:"validator_src_addr"` // in bech32 - ValidatorDstAddr string `json:"validator_dst_addr"` // in bech32 - SharesAmount string `json:"shares"` -} -type msgCompleteRedelegateInput struct { - DelegatorAddr string `json:"delegator_addr"` // in bech32 - ValidatorSrcAddr string `json:"validator_src_addr"` // in bech32 - ValidatorDstAddr string `json:"validator_dst_addr"` // in bech32 -} -type msgBeginUnbondingInput struct { - DelegatorAddr string `json:"delegator_addr"` // in bech32 - ValidatorAddr string `json:"validator_addr"` // in bech32 - SharesAmount string `json:"shares"` -} -type msgCompleteUnbondingInput struct { - DelegatorAddr string `json:"delegator_addr"` // in bech32 - ValidatorAddr string `json:"validator_addr"` // in bech32 -} +type ( + msgDelegationsInput struct { + DelegatorAddr string `json:"delegator_addr"` // in bech32 + ValidatorAddr string `json:"validator_addr"` // in bech32 + Delegation sdk.Coin `json:"delegation"` + } -// the request body for edit delegations -type EditDelegationsBody struct { - LocalAccountName string `json:"name"` - Password string `json:"password"` - ChainID string `json:"chain_id"` - AccountNumber int64 `json:"account_number"` - Sequence int64 `json:"sequence"` - Gas string `json:"gas"` - GasAdjustment string `json:"gas_adjustment"` - Delegations []msgDelegationsInput `json:"delegations"` - BeginUnbondings []msgBeginUnbondingInput `json:"begin_unbondings"` - CompleteUnbondings []msgCompleteUnbondingInput `json:"complete_unbondings"` - BeginRedelegates []msgBeginRedelegateInput `json:"begin_redelegates"` - CompleteRedelegates []msgCompleteRedelegateInput `json:"complete_redelegates"` -} + msgBeginRedelegateInput struct { + DelegatorAddr string `json:"delegator_addr"` // in bech32 + ValidatorSrcAddr string `json:"validator_src_addr"` // in bech32 + ValidatorDstAddr string `json:"validator_dst_addr"` // in bech32 + SharesAmount string `json:"shares"` + } + + msgCompleteRedelegateInput struct { + DelegatorAddr string `json:"delegator_addr"` // in bech32 + ValidatorSrcAddr string `json:"validator_src_addr"` // in bech32 + ValidatorDstAddr string `json:"validator_dst_addr"` // in bech32 + } + + msgBeginUnbondingInput struct { + DelegatorAddr string `json:"delegator_addr"` // in bech32 + ValidatorAddr string `json:"validator_addr"` // in bech32 + SharesAmount string `json:"shares"` + } + + msgCompleteUnbondingInput struct { + DelegatorAddr string `json:"delegator_addr"` // in bech32 + ValidatorAddr string `json:"validator_addr"` // in bech32 + } + + // the request body for edit delegations + EditDelegationsReq struct { + BaseReq utils.BaseReq `json:"base_req"` + Delegations []msgDelegationsInput `json:"delegations"` + BeginUnbondings []msgBeginUnbondingInput `json:"begin_unbondings"` + CompleteUnbondings []msgCompleteUnbondingInput `json:"complete_unbondings"` + BeginRedelegates []msgBeginRedelegateInput `json:"begin_redelegates"` + CompleteRedelegates []msgCompleteRedelegateInput `json:"complete_redelegates"` + } +) // TODO: Split this up into several smaller functions, and remove the above nolint // TODO: use sdk.ValAddress instead of sdk.AccAddress for validators in messages +// TODO: Seriously consider how to refactor...do we need to make it multiple txs? +// If not, we can just use CompleteAndBroadcastTxREST. func delegationsRequestHandlerFn(cdc *codec.Codec, kb keys.Keybase, cliCtx context.CLIContext) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { - var m EditDelegationsBody + var req EditDelegationsReq body, err := ioutil.ReadAll(r.Body) if err != nil { @@ -82,14 +84,19 @@ func delegationsRequestHandlerFn(cdc *codec.Codec, kb keys.Keybase, cliCtx conte return } - err = cdc.UnmarshalJSON(body, &m) + err = cdc.UnmarshalJSON(body, &req) if err != nil { w.WriteHeader(http.StatusBadRequest) w.Write([]byte(err.Error())) return } - info, err := kb.Get(m.LocalAccountName) + baseReq := req.BaseReq.Sanitize() + if !baseReq.ValidateBasic(w) { + return + } + + info, err := kb.Get(baseReq.Name) if err != nil { w.WriteHeader(http.StatusUnauthorized) w.Write([]byte(err.Error())) @@ -97,14 +104,14 @@ func delegationsRequestHandlerFn(cdc *codec.Codec, kb keys.Keybase, cliCtx conte } // build messages - messages := make([]sdk.Msg, len(m.Delegations)+ - len(m.BeginRedelegates)+ - len(m.CompleteRedelegates)+ - len(m.BeginUnbondings)+ - len(m.CompleteUnbondings)) + messages := make([]sdk.Msg, len(req.Delegations)+ + len(req.BeginRedelegates)+ + len(req.CompleteRedelegates)+ + len(req.BeginUnbondings)+ + len(req.CompleteUnbondings)) i := 0 - for _, msg := range m.Delegations { + for _, msg := range req.Delegations { delAddr, err := sdk.AccAddressFromBech32(msg.DelegatorAddr) if err != nil { utils.WriteErrorResponse(w, http.StatusInternalServerError, fmt.Sprintf("Couldn't decode delegator. Error: %s", err.Error())) @@ -131,7 +138,7 @@ func delegationsRequestHandlerFn(cdc *codec.Codec, kb keys.Keybase, cliCtx conte i++ } - for _, msg := range m.BeginRedelegates { + for _, msg := range req.BeginRedelegates { delAddr, err := sdk.AccAddressFromBech32(msg.DelegatorAddr) if err != nil { utils.WriteErrorResponse(w, http.StatusInternalServerError, fmt.Sprintf("Couldn't decode validator. Error: %s", err.Error())) @@ -170,7 +177,7 @@ func delegationsRequestHandlerFn(cdc *codec.Codec, kb keys.Keybase, cliCtx conte i++ } - for _, msg := range m.CompleteRedelegates { + for _, msg := range req.CompleteRedelegates { delAddr, err := sdk.AccAddressFromBech32(msg.DelegatorAddr) if err != nil { utils.WriteErrorResponse(w, http.StatusInternalServerError, fmt.Sprintf("Couldn't decode delegator. Error: %s", err.Error())) @@ -203,7 +210,7 @@ func delegationsRequestHandlerFn(cdc *codec.Codec, kb keys.Keybase, cliCtx conte i++ } - for _, msg := range m.BeginUnbondings { + for _, msg := range req.BeginUnbondings { delAddr, err := sdk.AccAddressFromBech32(msg.DelegatorAddr) if err != nil { utils.WriteErrorResponse(w, http.StatusInternalServerError, fmt.Sprintf("Couldn't decode delegator. Error: %s", err.Error())) @@ -236,7 +243,7 @@ func delegationsRequestHandlerFn(cdc *codec.Codec, kb keys.Keybase, cliCtx conte i++ } - for _, msg := range m.CompleteUnbondings { + for _, msg := range req.CompleteUnbondings { delAddr, err := sdk.AccAddressFromBech32(msg.DelegatorAddr) if err != nil { utils.WriteErrorResponse(w, http.StatusInternalServerError, fmt.Sprintf("Couldn't decode delegator. Error: %s", err.Error())) @@ -262,41 +269,46 @@ func delegationsRequestHandlerFn(cdc *codec.Codec, kb keys.Keybase, cliCtx conte i++ } - simulateGas, gas, err := client.ReadGasFlag(m.Gas) + simulateGas, gas, err := client.ReadGasFlag(baseReq.Gas) if err != nil { utils.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) return } - adjustment, ok := utils.ParseFloat64OrReturnBadRequest(w, m.GasAdjustment, client.DefaultGasAdjustment) + + adjustment, ok := utils.ParseFloat64OrReturnBadRequest(w, baseReq.GasAdjustment, client.DefaultGasAdjustment) if !ok { return } + txBldr := authtxb.TxBuilder{ Codec: cdc, Gas: gas, GasAdjustment: adjustment, SimulateGas: simulateGas, - ChainID: m.ChainID, + ChainID: baseReq.ChainID, } // sign messages signedTxs := make([][]byte, len(messages[:])) for i, msg := range messages { // increment sequence for each message - txBldr = txBldr.WithAccountNumber(m.AccountNumber) - txBldr = txBldr.WithSequence(m.Sequence) - m.Sequence++ + txBldr = txBldr.WithAccountNumber(baseReq.AccountNumber) + txBldr = txBldr.WithSequence(baseReq.Sequence) + + baseReq.Sequence++ if utils.HasDryRunArg(r) || txBldr.SimulateGas { - newBldr, err := utils.EnrichCtxWithGas(txBldr, cliCtx, m.LocalAccountName, []sdk.Msg{msg}) + newBldr, err := utils.EnrichCtxWithGas(txBldr, cliCtx, baseReq.Name, []sdk.Msg{msg}) if err != nil { utils.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) return } + if utils.HasDryRunArg(r) { utils.WriteSimulationResponse(w, newBldr.Gas) return } + txBldr = newBldr } @@ -305,7 +317,7 @@ func delegationsRequestHandlerFn(cdc *codec.Codec, kb keys.Keybase, cliCtx conte return } - txBytes, err := txBldr.BuildAndSign(m.LocalAccountName, m.Password, []sdk.Msg{msg}) + txBytes, err := txBldr.BuildAndSign(baseReq.Name, baseReq.Password, []sdk.Msg{msg}) if err != nil { utils.WriteErrorResponse(w, http.StatusUnauthorized, err.Error()) return From e6a8b2278e84d0ff2bc0362e1462d1292184ab7e Mon Sep 17 00:00:00 2001 From: Dev Ojha Date: Wed, 26 Sep 2018 06:39:05 -0700 Subject: [PATCH 20/26] Merge PR #2377: auth: Move stdsignmsg to txbuilder, add tests --- PENDING.md | 1 + x/auth/client/txbuilder/stdsignmsg.go | 23 +++++++ x/auth/client/txbuilder/txbuilder.go | 14 ++-- x/auth/client/txbuilder/txbuilder_test.go | 81 +++++++++++++++++++++++ x/auth/stdtx.go | 17 ----- x/auth/stdtx_test.go | 41 +++++++----- 6 files changed, 138 insertions(+), 39 deletions(-) create mode 100644 x/auth/client/txbuilder/stdsignmsg.go create mode 100644 x/auth/client/txbuilder/txbuilder_test.go diff --git a/PENDING.md b/PENDING.md index aa328d9698..bf9fc1d4a1 100644 --- a/PENDING.md +++ b/PENDING.md @@ -56,6 +56,7 @@ BREAKING CHANGES * [codec] \#2324 All referrences to wire have been renamed to codec. Additionally, wire.NewCodec is now codec.New(). * [types] \#2343 Make sdk.Msg have a names field, to facilitate automatic tagging. * [baseapp] \#2366 Automatically add action tags to all messages + * [x/auth] \#2377 auth.StdSignMsg -> txbuilder.StdSignMsg * [x/staking] \#2244 staking now holds a consensus-address-index instead of a consensus-pubkey-index * [x/staking] \#2236 more distribution hooks for distribution diff --git a/x/auth/client/txbuilder/stdsignmsg.go b/x/auth/client/txbuilder/stdsignmsg.go new file mode 100644 index 0000000000..050a370fff --- /dev/null +++ b/x/auth/client/txbuilder/stdsignmsg.go @@ -0,0 +1,23 @@ +package context + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/auth" +) + +// StdSignMsg is a convenience structure for passing along +// a Msg with the other requirements for a StdSignDoc before +// it is signed. For use in the CLI. +type StdSignMsg struct { + ChainID string `json:"chain_id"` + AccountNumber int64 `json:"account_number"` + Sequence int64 `json:"sequence"` + Fee auth.StdFee `json:"fee"` + Msgs []sdk.Msg `json:"msgs"` + Memo string `json:"memo"` +} + +// get message bytes +func (msg StdSignMsg) Bytes() []byte { + return auth.StdSignBytes(msg.ChainID, msg.AccountNumber, msg.Sequence, msg.Fee, msg.Msgs, msg.Memo) +} diff --git a/x/auth/client/txbuilder/txbuilder.go b/x/auth/client/txbuilder/txbuilder.go index 642893b52f..b488ef359f 100644 --- a/x/auth/client/txbuilder/txbuilder.go +++ b/x/auth/client/txbuilder/txbuilder.go @@ -92,23 +92,23 @@ func (bldr TxBuilder) WithAccountNumber(accnum int64) TxBuilder { // Build builds a single message to be signed from a TxBuilder given a set of // messages. It returns an error if a fee is supplied but cannot be parsed. -func (bldr TxBuilder) Build(msgs []sdk.Msg) (auth.StdSignMsg, error) { +func (bldr TxBuilder) Build(msgs []sdk.Msg) (StdSignMsg, error) { chainID := bldr.ChainID if chainID == "" { - return auth.StdSignMsg{}, errors.Errorf("chain ID required but not specified") + return StdSignMsg{}, errors.Errorf("chain ID required but not specified") } fee := sdk.Coin{} if bldr.Fee != "" { parsedFee, err := sdk.ParseCoin(bldr.Fee) if err != nil { - return auth.StdSignMsg{}, err + return StdSignMsg{}, err } fee = parsedFee } - return auth.StdSignMsg{ + return StdSignMsg{ ChainID: bldr.ChainID, AccountNumber: bldr.AccountNumber, Sequence: bldr.Sequence, @@ -120,7 +120,7 @@ func (bldr TxBuilder) Build(msgs []sdk.Msg) (auth.StdSignMsg, error) { // Sign signs a transaction given a name, passphrase, and a single message to // signed. An error is returned if signing fails. -func (bldr TxBuilder) Sign(name, passphrase string, msg auth.StdSignMsg) ([]byte, error) { +func (bldr TxBuilder) Sign(name, passphrase string, msg StdSignMsg) ([]byte, error) { sig, err := MakeSignature(name, passphrase, msg) if err != nil { return nil, err @@ -172,7 +172,7 @@ func (bldr TxBuilder) BuildWithPubKey(name string, msgs []sdk.Msg) ([]byte, erro // SignStdTx appends a signature to a StdTx and returns a copy of a it. If append // is false, it replaces the signatures already attached with the new signature. func (bldr TxBuilder) SignStdTx(name, passphrase string, stdTx auth.StdTx, appendSig bool) (signedStdTx auth.StdTx, err error) { - stdSignature, err := MakeSignature(name, passphrase, auth.StdSignMsg{ + stdSignature, err := MakeSignature(name, passphrase, StdSignMsg{ ChainID: bldr.ChainID, AccountNumber: bldr.AccountNumber, Sequence: bldr.Sequence, @@ -195,7 +195,7 @@ func (bldr TxBuilder) SignStdTx(name, passphrase string, stdTx auth.StdTx, appen } // MakeSignature builds a StdSignature given key name, passphrase, and a StdSignMsg. -func MakeSignature(name, passphrase string, msg auth.StdSignMsg) (sig auth.StdSignature, err error) { +func MakeSignature(name, passphrase string, msg StdSignMsg) (sig auth.StdSignature, err error) { keybase, err := keys.GetKeyBase() if err != nil { return diff --git a/x/auth/client/txbuilder/txbuilder_test.go b/x/auth/client/txbuilder/txbuilder_test.go new file mode 100644 index 0000000000..996d9b8b1b --- /dev/null +++ b/x/auth/client/txbuilder/txbuilder_test.go @@ -0,0 +1,81 @@ +package context + +import ( + "reflect" + "testing" + + "github.com/stretchr/testify/require" + + "github.com/cosmos/cosmos-sdk/codec" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/auth" + "github.com/tendermint/tendermint/crypto/ed25519" +) + +var ( + priv = ed25519.GenPrivKey() + addr = sdk.AccAddress(priv.PubKey().Address()) +) + +func TestTxBuilderBuild(t *testing.T) { + type fields struct { + Codec *codec.Codec + AccountNumber int64 + Sequence int64 + Gas int64 + GasAdjustment float64 + SimulateGas bool + ChainID string + Memo string + Fee string + } + defaultMsg := []sdk.Msg{sdk.NewTestMsg(addr)} + tests := []struct { + fields fields + msgs []sdk.Msg + want StdSignMsg + wantErr bool + }{ + { + fields{ + Codec: codec.New(), + AccountNumber: 1, + Sequence: 1, + Gas: 100, + GasAdjustment: 1.1, + SimulateGas: false, + ChainID: "test-chain", + Memo: "hello", + Fee: "1steak", + }, + defaultMsg, + StdSignMsg{ + ChainID: "test-chain", + AccountNumber: 1, + Sequence: 1, + Memo: "hello", + Msgs: defaultMsg, + Fee: auth.NewStdFee(100, sdk.NewCoin("steak", sdk.NewInt(1))), + }, + false, + }, + } + for i, tc := range tests { + bldr := TxBuilder{ + Codec: tc.fields.Codec, + AccountNumber: tc.fields.AccountNumber, + Sequence: tc.fields.Sequence, + Gas: tc.fields.Gas, + GasAdjustment: tc.fields.GasAdjustment, + SimulateGas: tc.fields.SimulateGas, + ChainID: tc.fields.ChainID, + Memo: tc.fields.Memo, + Fee: tc.fields.Fee, + } + got, err := bldr.Build(tc.msgs) + require.Equal(t, tc.wantErr, (err != nil), "TxBuilder.Build() error = %v, wantErr %v, tc %d", err, tc.wantErr, i) + if !reflect.DeepEqual(got, tc.want) { + t.Errorf("TxBuilder.Build() = %v, want %v", got, tc.want) + } + } +} diff --git a/x/auth/stdtx.go b/x/auth/stdtx.go index e38dc0c7ee..0c0a133ed2 100644 --- a/x/auth/stdtx.go +++ b/x/auth/stdtx.go @@ -139,23 +139,6 @@ func StdSignBytes(chainID string, accnum int64, sequence int64, fee StdFee, msgs return sdk.MustSortJSON(bz) } -// StdSignMsg is a convenience structure for passing along -// a Msg with the other requirements for a StdSignDoc before -// it is signed. For use in the CLI. -type StdSignMsg struct { - ChainID string `json:"chain_id"` - AccountNumber int64 `json:"account_number"` - Sequence int64 `json:"sequence"` - Fee StdFee `json:"fee"` - Msgs []sdk.Msg `json:"msgs"` - Memo string `json:"memo"` -} - -// get message bytes -func (msg StdSignMsg) Bytes() []byte { - return StdSignBytes(msg.ChainID, msg.AccountNumber, msg.Sequence, msg.Fee, msg.Msgs, msg.Memo) -} - // Standard Signature type StdSignature struct { crypto.PubKey `json:"pub_key"` // optional diff --git a/x/auth/stdtx_test.go b/x/auth/stdtx_test.go index f6e3f8fc21..8d9172a923 100644 --- a/x/auth/stdtx_test.go +++ b/x/auth/stdtx_test.go @@ -4,15 +4,17 @@ import ( "fmt" "testing" + sdk "github.com/cosmos/cosmos-sdk/types" "github.com/stretchr/testify/require" "github.com/tendermint/tendermint/crypto/ed25519" +) - sdk "github.com/cosmos/cosmos-sdk/types" +var ( + priv = ed25519.GenPrivKey() + addr = sdk.AccAddress(priv.PubKey().Address()) ) func TestStdTx(t *testing.T) { - priv := ed25519.GenPrivKey() - addr := sdk.AccAddress(priv.PubKey().Address()) msgs := []sdk.Msg{sdk.NewTestMsg(addr)} fee := newStdFee() sigs := []StdSignature{} @@ -26,17 +28,26 @@ func TestStdTx(t *testing.T) { } func TestStdSignBytes(t *testing.T) { - priv := ed25519.GenPrivKey() - addr := sdk.AccAddress(priv.PubKey().Address()) - msgs := []sdk.Msg{sdk.NewTestMsg(addr)} - fee := newStdFee() - signMsg := StdSignMsg{ - "1234", - 3, - 6, - fee, - msgs, - "memo", + type args struct { + chainID string + accnum int64 + sequence int64 + fee StdFee + msgs []sdk.Msg + memo string + } + defaultFee := newStdFee() + tests := []struct { + args args + want string + }{ + { + args{"1234", 3, 6, defaultFee, []sdk.Msg{sdk.NewTestMsg(addr)}, "memo"}, + fmt.Sprintf("{\"account_number\":\"3\",\"chain_id\":\"1234\",\"fee\":{\"amount\":[{\"amount\":\"150\",\"denom\":\"atom\"}],\"gas\":\"5000\"},\"memo\":\"memo\",\"msgs\":[[\"%s\"]],\"sequence\":\"6\"}", addr), + }, + } + for i, tc := range tests { + got := string(StdSignBytes(tc.args.chainID, tc.args.accnum, tc.args.sequence, tc.args.fee, tc.args.msgs, tc.args.memo)) + require.Equal(t, tc.want, got, "Got unexpected result on test case i: %d", i) } - require.Equal(t, fmt.Sprintf("{\"account_number\":\"3\",\"chain_id\":\"1234\",\"fee\":{\"amount\":[{\"amount\":\"150\",\"denom\":\"atom\"}],\"gas\":\"5000\"},\"memo\":\"memo\",\"msgs\":[[\"%s\"]],\"sequence\":\"6\"}", addr), string(signMsg.Bytes())) } From a2caefc8ae6a45ea1c71a1af09408086f48fe871 Mon Sep 17 00:00:00 2001 From: Alessio Treglia Date: Wed, 26 Sep 2018 15:00:49 +0100 Subject: [PATCH 21/26] Merge PR #2390: CLI subcommands reorganization --- PENDING.md | 3 +- client/rpc/validators.go | 4 +- client/tx/sign.go | 48 ----------- cmd/gaia/cli_test/cli_test.go | 128 ++++++++++++++--------------- cmd/gaia/cmd/gaiacli/main.go | 121 +++++++++++++-------------- docs/clients/ledger.md | 2 +- docs/clients/service-providers.md | 6 +- docs/sdk/clients.md | 56 ++++++------- docs/validators/validator-setup.md | 16 ++-- x/auth/client/cli/sign.go | 2 +- x/gov/client/cli/tx.go | 32 ++++---- 11 files changed, 180 insertions(+), 238 deletions(-) delete mode 100644 client/tx/sign.go diff --git a/PENDING.md b/PENDING.md index bf9fc1d4a1..8e6c9e94e9 100644 --- a/PENDING.md +++ b/PENDING.md @@ -19,7 +19,7 @@ BREAKING CHANGES `cosmosvaloper`. * [cli] [\#2190](https://github.com/cosmos/cosmos-sdk/issues/2190) `gaiacli init --gen-txs` is now `gaiacli init --with-txs` to reduce confusion * [cli] \#2073 --from can now be either an address or a key name - + * [cli] [\#1184](https://github.com/cosmos/cosmos-sdk/issues/1184) Subcommands reorganisation, see [\#2390](https://github.com/cosmos/cosmos-sdk/pull/2390) for a comprehensive list of changes. * Gaia * Make the transient store key use a distinct store key. [#2013](https://github.com/cosmos/cosmos-sdk/pull/2013) @@ -138,6 +138,7 @@ IMPROVEMENTS * [gaiad] \#1992 Add optional flag to `gaiad testnet` to make config directory of daemon (default `gaiad`) and cli (default `gaiacli`) configurable * [x/stake] Add stake `Queriers` for Gaia-lite endpoints. This increases the staking endpoints performance by reusing the staking `keeper` logic for queries. [#2249](https://github.com/cosmos/cosmos-sdk/pull/2149) * [types/decimal] \#2378 - Added truncate functionality to decimal + * [client] [\#1184](https://github.com/cosmos/cosmos-sdk/issues/1184) Remove unused `client/tx/sign.go`. * Tendermint diff --git a/client/rpc/validators.go b/client/rpc/validators.go index d8572d5444..4802e785b4 100644 --- a/client/rpc/validators.go +++ b/client/rpc/validators.go @@ -1,6 +1,7 @@ package rpc import ( + "bytes" "fmt" "net/http" "strconv" @@ -8,7 +9,6 @@ import ( "github.com/gorilla/mux" "github.com/spf13/cobra" - "bytes" "github.com/cosmos/cosmos-sdk/client" "github.com/cosmos/cosmos-sdk/client/context" sdk "github.com/cosmos/cosmos-sdk/types" @@ -21,7 +21,7 @@ import ( //ValidatorCommand returns the validator set for a given height func ValidatorCommand() *cobra.Command { cmd := &cobra.Command{ - Use: "validator-set [height]", + Use: "tendermint-validator-set [height]", Short: "Get the full tendermint validator set at given height", Args: cobra.MaximumNArgs(1), RunE: printValidators, diff --git a/client/tx/sign.go b/client/tx/sign.go deleted file mode 100644 index 786c7fa0bd..0000000000 --- a/client/tx/sign.go +++ /dev/null @@ -1,48 +0,0 @@ -package tx - -import ( - "encoding/json" - "net/http" - - keybase "github.com/cosmos/cosmos-sdk/client/keys" - keys "github.com/cosmos/cosmos-sdk/crypto/keys" -) - -// REST request body for signed txs -// TODO does this need to be exposed? -type SignTxBody struct { - Name string `json:"name"` - Password string `json:"password"` - TxBytes string `json:"tx"` -} - -// sign transaction REST Handler -func SignTxRequstHandler(w http.ResponseWriter, r *http.Request) { - var kb keys.Keybase - var m SignTxBody - - decoder := json.NewDecoder(r.Body) - err := decoder.Decode(&m) - if err != nil { - w.WriteHeader(400) - w.Write([]byte(err.Error())) - return - } - - kb, err = keybase.GetKeyBase() - if err != nil { - w.WriteHeader(500) - w.Write([]byte(err.Error())) - return - } - - //TODO check if account exists - sig, _, err := kb.Sign(m.Name, m.Password, []byte(m.TxBytes)) - if err != nil { - w.WriteHeader(403) - w.Write([]byte(err.Error())) - return - } - - w.Write(sig) -} diff --git a/cmd/gaia/cli_test/cli_test.go b/cmd/gaia/cli_test/cli_test.go index 8e31c664bc..5b204f4df7 100644 --- a/cmd/gaia/cli_test/cli_test.go +++ b/cmd/gaia/cli_test/cli_test.go @@ -51,11 +51,11 @@ func TestGaiaCLIMinimumFees(t *testing.T) { fooAddr, _ := executeGetAddrPK(t, fmt.Sprintf("gaiacli keys show foo --output=json --home=%s", gaiacliHome)) barAddr, _ := executeGetAddrPK(t, fmt.Sprintf("gaiacli keys show bar --output=json --home=%s", gaiacliHome)) - fooAcc := executeGetAccount(t, fmt.Sprintf("gaiacli account %s %v", fooAddr, flags)) + fooAcc := executeGetAccount(t, fmt.Sprintf("gaiacli query account %s %v", fooAddr, flags)) require.Equal(t, int64(50), fooAcc.GetCoins().AmountOf("steak").Int64()) success := executeWrite(t, fmt.Sprintf( - "gaiacli send %v --amount=10steak --to=%s --from=foo", flags, barAddr), app.DefaultKeyPass) + "gaiacli tx send %v --amount=10steak --to=%s --from=foo", flags, barAddr), app.DefaultKeyPass) require.False(t, success) tests.WaitForNextNBlocksTM(2, port) @@ -75,30 +75,30 @@ func TestGaiaCLIFeesDeduction(t *testing.T) { fooAddr, _ := executeGetAddrPK(t, fmt.Sprintf("gaiacli keys show foo --output=json --home=%s", gaiacliHome)) barAddr, _ := executeGetAddrPK(t, fmt.Sprintf("gaiacli keys show bar --output=json --home=%s", gaiacliHome)) - fooAcc := executeGetAccount(t, fmt.Sprintf("gaiacli account %s %v", fooAddr, flags)) + fooAcc := executeGetAccount(t, fmt.Sprintf("gaiacli query account %s %v", fooAddr, flags)) require.Equal(t, int64(1000), fooAcc.GetCoins().AmountOf("fooToken").Int64()) // test simulation success := executeWrite(t, fmt.Sprintf( - "gaiacli send %v --amount=1000fooToken --to=%s --from=foo --fee=1fooToken --dry-run", flags, barAddr), app.DefaultKeyPass) + "gaiacli tx send %v --amount=1000fooToken --to=%s --from=foo --fee=1fooToken --dry-run", flags, barAddr), app.DefaultKeyPass) require.True(t, success) tests.WaitForNextNBlocksTM(2, port) // ensure state didn't change - fooAcc = executeGetAccount(t, fmt.Sprintf("gaiacli account %s %v", fooAddr, flags)) + fooAcc = executeGetAccount(t, fmt.Sprintf("gaiacli query account %s %v", fooAddr, flags)) require.Equal(t, int64(1000), fooAcc.GetCoins().AmountOf("fooToken").Int64()) // insufficient funds (coins + fees) success = executeWrite(t, fmt.Sprintf( - "gaiacli send %v --amount=1000fooToken --to=%s --from=foo --fee=1fooToken", flags, barAddr), app.DefaultKeyPass) + "gaiacli tx send %v --amount=1000fooToken --to=%s --from=foo --fee=1fooToken", flags, barAddr), app.DefaultKeyPass) require.False(t, success) tests.WaitForNextNBlocksTM(2, port) // ensure state didn't change - fooAcc = executeGetAccount(t, fmt.Sprintf("gaiacli account %s %v", fooAddr, flags)) + fooAcc = executeGetAccount(t, fmt.Sprintf("gaiacli query account %s %v", fooAddr, flags)) require.Equal(t, int64(1000), fooAcc.GetCoins().AmountOf("fooToken").Int64()) // test success (transfer = coins + fees) success = executeWrite(t, fmt.Sprintf( - "gaiacli send %v --fee=300fooToken --amount=500fooToken --to=%s --from=foo", flags, barAddr), app.DefaultKeyPass) + "gaiacli tx send %v --fee=300fooToken --amount=500fooToken --to=%s --from=foo", flags, barAddr), app.DefaultKeyPass) require.True(t, success) tests.WaitForNextNBlocksTM(2, port) } @@ -117,40 +117,40 @@ func TestGaiaCLISend(t *testing.T) { fooAddr, _ := executeGetAddrPK(t, fmt.Sprintf("gaiacli keys show foo --output=json --home=%s", gaiacliHome)) barAddr, _ := executeGetAddrPK(t, fmt.Sprintf("gaiacli keys show bar --output=json --home=%s", gaiacliHome)) - fooAcc := executeGetAccount(t, fmt.Sprintf("gaiacli account %s %v", fooAddr, flags)) + fooAcc := executeGetAccount(t, fmt.Sprintf("gaiacli query account %s %v", fooAddr, flags)) require.Equal(t, int64(50), fooAcc.GetCoins().AmountOf("steak").Int64()) - executeWrite(t, fmt.Sprintf("gaiacli send %v --amount=10steak --to=%s --from=foo", flags, barAddr), app.DefaultKeyPass) + executeWrite(t, fmt.Sprintf("gaiacli tx send %v --amount=10steak --to=%s --from=foo", flags, barAddr), app.DefaultKeyPass) tests.WaitForNextNBlocksTM(2, port) - barAcc := executeGetAccount(t, fmt.Sprintf("gaiacli account %s %v", barAddr, flags)) + barAcc := executeGetAccount(t, fmt.Sprintf("gaiacli query account %s %v", barAddr, flags)) require.Equal(t, int64(10), barAcc.GetCoins().AmountOf("steak").Int64()) - fooAcc = executeGetAccount(t, fmt.Sprintf("gaiacli account %s %v", fooAddr, flags)) + fooAcc = executeGetAccount(t, fmt.Sprintf("gaiacli query account %s %v", fooAddr, flags)) require.Equal(t, int64(40), fooAcc.GetCoins().AmountOf("steak").Int64()) // Test --dry-run - success := executeWrite(t, fmt.Sprintf("gaiacli send %v --amount=10steak --to=%s --from=foo --dry-run", flags, barAddr), app.DefaultKeyPass) + success := executeWrite(t, fmt.Sprintf("gaiacli tx send %v --amount=10steak --to=%s --from=foo --dry-run", flags, barAddr), app.DefaultKeyPass) require.True(t, success) // Check state didn't change - fooAcc = executeGetAccount(t, fmt.Sprintf("gaiacli account %s %v", fooAddr, flags)) + fooAcc = executeGetAccount(t, fmt.Sprintf("gaiacli query account %s %v", fooAddr, flags)) require.Equal(t, int64(40), fooAcc.GetCoins().AmountOf("steak").Int64()) // test autosequencing - executeWrite(t, fmt.Sprintf("gaiacli send %v --amount=10steak --to=%s --from=foo", flags, barAddr), app.DefaultKeyPass) + executeWrite(t, fmt.Sprintf("gaiacli tx send %v --amount=10steak --to=%s --from=foo", flags, barAddr), app.DefaultKeyPass) tests.WaitForNextNBlocksTM(2, port) - barAcc = executeGetAccount(t, fmt.Sprintf("gaiacli account %s %v", barAddr, flags)) + barAcc = executeGetAccount(t, fmt.Sprintf("gaiacli query account %s %v", barAddr, flags)) require.Equal(t, int64(20), barAcc.GetCoins().AmountOf("steak").Int64()) - fooAcc = executeGetAccount(t, fmt.Sprintf("gaiacli account %s %v", fooAddr, flags)) + fooAcc = executeGetAccount(t, fmt.Sprintf("gaiacli query account %s %v", fooAddr, flags)) require.Equal(t, int64(30), fooAcc.GetCoins().AmountOf("steak").Int64()) // test memo - executeWrite(t, fmt.Sprintf("gaiacli send %v --amount=10steak --to=%s --from=foo --memo 'testmemo'", flags, barAddr), app.DefaultKeyPass) + executeWrite(t, fmt.Sprintf("gaiacli tx send %v --amount=10steak --to=%s --from=foo --memo 'testmemo'", flags, barAddr), app.DefaultKeyPass) tests.WaitForNextNBlocksTM(2, port) - barAcc = executeGetAccount(t, fmt.Sprintf("gaiacli account %s %v", barAddr, flags)) + barAcc = executeGetAccount(t, fmt.Sprintf("gaiacli query account %s %v", barAddr, flags)) require.Equal(t, int64(30), barAcc.GetCoins().AmountOf("steak").Int64()) - fooAcc = executeGetAccount(t, fmt.Sprintf("gaiacli account %s %v", fooAddr, flags)) + fooAcc = executeGetAccount(t, fmt.Sprintf("gaiacli query account %s %v", fooAddr, flags)) require.Equal(t, int64(20), fooAcc.GetCoins().AmountOf("steak").Int64()) } @@ -168,27 +168,27 @@ func TestGaiaCLIGasAuto(t *testing.T) { fooAddr, _ := executeGetAddrPK(t, fmt.Sprintf("gaiacli keys show foo --output=json --home=%s", gaiacliHome)) barAddr, _ := executeGetAddrPK(t, fmt.Sprintf("gaiacli keys show bar --output=json --home=%s", gaiacliHome)) - fooAcc := executeGetAccount(t, fmt.Sprintf("gaiacli account %s %v", fooAddr, flags)) + fooAcc := executeGetAccount(t, fmt.Sprintf("gaiacli query account %s %v", fooAddr, flags)) require.Equal(t, int64(50), fooAcc.GetCoins().AmountOf("steak").Int64()) // Test failure with auto gas disabled and very little gas set by hand - success := executeWrite(t, fmt.Sprintf("gaiacli send %v --gas=10 --amount=10steak --to=%s --from=foo", flags, barAddr), app.DefaultKeyPass) + success := executeWrite(t, fmt.Sprintf("gaiacli tx send %v --gas=10 --amount=10steak --to=%s --from=foo", flags, barAddr), app.DefaultKeyPass) require.False(t, success) tests.WaitForNextNBlocksTM(2, port) // Check state didn't change - fooAcc = executeGetAccount(t, fmt.Sprintf("gaiacli account %s %v", fooAddr, flags)) + fooAcc = executeGetAccount(t, fmt.Sprintf("gaiacli query account %s %v", fooAddr, flags)) require.Equal(t, int64(50), fooAcc.GetCoins().AmountOf("steak").Int64()) // Test failure with negative gas - success = executeWrite(t, fmt.Sprintf("gaiacli send %v --gas=-100 --amount=10steak --to=%s --from=foo", flags, barAddr), app.DefaultKeyPass) + success = executeWrite(t, fmt.Sprintf("gaiacli tx send %v --gas=-100 --amount=10steak --to=%s --from=foo", flags, barAddr), app.DefaultKeyPass) require.False(t, success) // Test failure with 0 gas - success = executeWrite(t, fmt.Sprintf("gaiacli send %v --gas=0 --amount=10steak --to=%s --from=foo", flags, barAddr), app.DefaultKeyPass) + success = executeWrite(t, fmt.Sprintf("gaiacli tx send %v --gas=0 --amount=10steak --to=%s --from=foo", flags, barAddr), app.DefaultKeyPass) require.False(t, success) // Enable auto gas - success, stdout, _ := executeWriteRetStdStreams(t, fmt.Sprintf("gaiacli send %v --json --gas=simulate --amount=10steak --to=%s --from=foo", flags, barAddr), app.DefaultKeyPass) + success, stdout, _ := executeWriteRetStdStreams(t, fmt.Sprintf("gaiacli tx send %v --json --gas=simulate --amount=10steak --to=%s --from=foo", flags, barAddr), app.DefaultKeyPass) require.True(t, success) // check that gas wanted == gas used cdc := app.MakeCodec() @@ -201,7 +201,7 @@ func TestGaiaCLIGasAuto(t *testing.T) { require.Equal(t, jsonOutput.Response.GasWanted, jsonOutput.Response.GasUsed) tests.WaitForNextNBlocksTM(2, port) // Check state has changed accordingly - fooAcc = executeGetAccount(t, fmt.Sprintf("gaiacli account %s %v", fooAddr, flags)) + fooAcc = executeGetAccount(t, fmt.Sprintf("gaiacli query account %s %v", fooAddr, flags)) require.Equal(t, int64(40), fooAcc.GetCoins().AmountOf("steak").Int64()) } @@ -220,12 +220,12 @@ func TestGaiaCLICreateValidator(t *testing.T) { barAddr, barPubKey := executeGetAddrPK(t, fmt.Sprintf("gaiacli keys show bar --output=json --home=%s", gaiacliHome)) barCeshPubKey := sdk.MustBech32ifyConsPub(barPubKey) - executeWrite(t, fmt.Sprintf("gaiacli send %v --amount=10steak --to=%s --from=foo", flags, barAddr), app.DefaultKeyPass) + executeWrite(t, fmt.Sprintf("gaiacli tx send %v --amount=10steak --to=%s --from=foo", flags, barAddr), app.DefaultKeyPass) tests.WaitForNextNBlocksTM(2, port) - barAcc := executeGetAccount(t, fmt.Sprintf("gaiacli account %s %v", barAddr, flags)) + barAcc := executeGetAccount(t, fmt.Sprintf("gaiacli query account %s %v", barAddr, flags)) require.Equal(t, int64(10), barAcc.GetCoins().AmountOf("steak").Int64()) - fooAcc := executeGetAccount(t, fmt.Sprintf("gaiacli account %s %v", fooAddr, flags)) + fooAcc := executeGetAccount(t, fmt.Sprintf("gaiacli query account %s %v", fooAddr, flags)) require.Equal(t, int64(40), fooAcc.GetCoins().AmountOf("steak").Int64()) defaultParams := stake.DefaultParams() @@ -234,7 +234,7 @@ func TestGaiaCLICreateValidator(t *testing.T) { initialPool = initialPool.ProcessProvisions(defaultParams) // provisions are added to the pool every hour // create validator - cvStr := fmt.Sprintf("gaiacli stake create-validator %v", flags) + cvStr := fmt.Sprintf("gaiacli tx create-validator %v", flags) cvStr += fmt.Sprintf(" --from=%s", "bar") cvStr += fmt.Sprintf(" --pubkey=%s", barCeshPubKey) cvStr += fmt.Sprintf(" --amount=%v", "2steak") @@ -262,15 +262,15 @@ func TestGaiaCLICreateValidator(t *testing.T) { executeWrite(t, cvStr, app.DefaultKeyPass) tests.WaitForNextNBlocksTM(2, port) - barAcc = executeGetAccount(t, fmt.Sprintf("gaiacli account %s %v", barAddr, flags)) + barAcc = executeGetAccount(t, fmt.Sprintf("gaiacli query account %s %v", barAddr, flags)) require.Equal(t, int64(8), barAcc.GetCoins().AmountOf("steak").Int64(), "%v", barAcc) - validator := executeGetValidator(t, fmt.Sprintf("gaiacli stake validator %s --output=json %v", sdk.ValAddress(barAddr), flags)) + validator := executeGetValidator(t, fmt.Sprintf("gaiacli query validator %s --output=json %v", sdk.ValAddress(barAddr), flags)) require.Equal(t, validator.OperatorAddr, sdk.ValAddress(barAddr)) require.True(sdk.DecEq(t, sdk.NewDec(2), validator.Tokens)) // unbond a single share - unbondStr := fmt.Sprintf("gaiacli stake unbond begin %v", flags) + unbondStr := fmt.Sprintf("gaiacli tx unbond begin %v", flags) unbondStr += fmt.Sprintf(" --from=%s", "bar") unbondStr += fmt.Sprintf(" --validator=%s", sdk.ValAddress(barAddr)) unbondStr += fmt.Sprintf(" --shares-amount=%v", "1") @@ -280,16 +280,16 @@ func TestGaiaCLICreateValidator(t *testing.T) { tests.WaitForNextNBlocksTM(2, port) /* // this won't be what we expect because we've only started unbonding, haven't completed - barAcc = executeGetAccount(t, fmt.Sprintf("gaiacli account %v %v", barCech, flags)) + barAcc = executeGetAccount(t, fmt.Sprintf("gaiacli query account %v %v", barCech, flags)) require.Equal(t, int64(9), barAcc.GetCoins().AmountOf("steak").Int64(), "%v", barAcc) */ - validator = executeGetValidator(t, fmt.Sprintf("gaiacli stake validator %s --output=json %v", sdk.ValAddress(barAddr), flags)) + validator = executeGetValidator(t, fmt.Sprintf("gaiacli query validator %s --output=json %v", sdk.ValAddress(barAddr), flags)) require.Equal(t, "1.0000000000", validator.Tokens.String()) - params := executeGetParams(t, fmt.Sprintf("gaiacli stake parameters --output=json %v", flags)) + params := executeGetParams(t, fmt.Sprintf("gaiacli query parameters --output=json %v", flags)) require.True(t, defaultParams.Equal(params)) - pool := executeGetPool(t, fmt.Sprintf("gaiacli stake pool --output=json %v", flags)) + pool := executeGetPool(t, fmt.Sprintf("gaiacli query pool --output=json %v", flags)) require.Equal(t, initialPool.DateLastCommissionReset, pool.DateLastCommissionReset) require.Equal(t, initialPool.PrevBondedShares, pool.PrevBondedShares) require.Equal(t, initialPool.BondedTokens, pool.BondedTokens) @@ -308,14 +308,14 @@ func TestGaiaCLISubmitProposal(t *testing.T) { fooAddr, _ := executeGetAddrPK(t, fmt.Sprintf("gaiacli keys show foo --output=json --home=%s", gaiacliHome)) - fooAcc := executeGetAccount(t, fmt.Sprintf("gaiacli account %s %v", fooAddr, flags)) + fooAcc := executeGetAccount(t, fmt.Sprintf("gaiacli query account %s %v", fooAddr, flags)) require.Equal(t, int64(50), fooAcc.GetCoins().AmountOf("steak").Int64()) - proposalsQuery := tests.ExecuteT(t, fmt.Sprintf("gaiacli gov query-proposals %v", flags), "") + proposalsQuery := tests.ExecuteT(t, fmt.Sprintf("gaiacli query proposals %v", flags), "") require.Equal(t, "No matching proposals found", proposalsQuery) // submit a test proposal - spStr := fmt.Sprintf("gaiacli gov submit-proposal %v", flags) + spStr := fmt.Sprintf("gaiacli tx submit-proposal %v", flags) spStr += fmt.Sprintf(" --from=%s", "foo") spStr += fmt.Sprintf(" --deposit=%s", "5steak") spStr += fmt.Sprintf(" --type=%s", "Text") @@ -339,17 +339,17 @@ func TestGaiaCLISubmitProposal(t *testing.T) { executeWrite(t, spStr, app.DefaultKeyPass) tests.WaitForNextNBlocksTM(2, port) - fooAcc = executeGetAccount(t, fmt.Sprintf("gaiacli account %s %v", fooAddr, flags)) + fooAcc = executeGetAccount(t, fmt.Sprintf("gaiacli query account %s %v", fooAddr, flags)) require.Equal(t, int64(45), fooAcc.GetCoins().AmountOf("steak").Int64()) - proposal1 := executeGetProposal(t, fmt.Sprintf("gaiacli gov query-proposal --proposal-id=1 --output=json %v", flags)) + proposal1 := executeGetProposal(t, fmt.Sprintf("gaiacli query proposal --proposal-id=1 --output=json %v", flags)) require.Equal(t, int64(1), proposal1.GetProposalID()) require.Equal(t, gov.StatusDepositPeriod, proposal1.GetStatus()) - proposalsQuery = tests.ExecuteT(t, fmt.Sprintf("gaiacli gov query-proposals %v", flags), "") + proposalsQuery = tests.ExecuteT(t, fmt.Sprintf("gaiacli query proposals %v", flags), "") require.Equal(t, " 1 - Test", proposalsQuery) - depositStr := fmt.Sprintf("gaiacli gov deposit %v", flags) + depositStr := fmt.Sprintf("gaiacli tx deposit %v", flags) depositStr += fmt.Sprintf(" --from=%s", "foo") depositStr += fmt.Sprintf(" --deposit=%s", "10steak") depositStr += fmt.Sprintf(" --proposal-id=%s", "1") @@ -367,13 +367,13 @@ func TestGaiaCLISubmitProposal(t *testing.T) { executeWrite(t, depositStr, app.DefaultKeyPass) tests.WaitForNextNBlocksTM(2, port) - fooAcc = executeGetAccount(t, fmt.Sprintf("gaiacli account %s %v", fooAddr, flags)) + fooAcc = executeGetAccount(t, fmt.Sprintf("gaiacli query account %s %v", fooAddr, flags)) require.Equal(t, int64(35), fooAcc.GetCoins().AmountOf("steak").Int64()) - proposal1 = executeGetProposal(t, fmt.Sprintf("gaiacli gov query-proposal --proposal-id=1 --output=json %v", flags)) + proposal1 = executeGetProposal(t, fmt.Sprintf("gaiacli query proposal --proposal-id=1 --output=json %v", flags)) require.Equal(t, int64(1), proposal1.GetProposalID()) require.Equal(t, gov.StatusVotingPeriod, proposal1.GetStatus()) - voteStr := fmt.Sprintf("gaiacli gov vote %v", flags) + voteStr := fmt.Sprintf("gaiacli tx vote %v", flags) voteStr += fmt.Sprintf(" --from=%s", "foo") voteStr += fmt.Sprintf(" --proposal-id=%s", "1") voteStr += fmt.Sprintf(" --option=%s", "Yes") @@ -391,23 +391,23 @@ func TestGaiaCLISubmitProposal(t *testing.T) { executeWrite(t, voteStr, app.DefaultKeyPass) tests.WaitForNextNBlocksTM(2, port) - vote := executeGetVote(t, fmt.Sprintf("gaiacli gov query-vote --proposal-id=1 --voter=%s --output=json %v", fooAddr, flags)) + vote := executeGetVote(t, fmt.Sprintf("gaiacli query vote --proposal-id=1 --voter=%s --output=json %v", fooAddr, flags)) require.Equal(t, int64(1), vote.ProposalID) require.Equal(t, gov.OptionYes, vote.Option) - votes := executeGetVotes(t, fmt.Sprintf("gaiacli gov query-votes --proposal-id=1 --output=json %v", flags)) + votes := executeGetVotes(t, fmt.Sprintf("gaiacli query votes --proposal-id=1 --output=json %v", flags)) require.Len(t, votes, 1) require.Equal(t, int64(1), votes[0].ProposalID) require.Equal(t, gov.OptionYes, votes[0].Option) - proposalsQuery = tests.ExecuteT(t, fmt.Sprintf("gaiacli gov query-proposals --status=DepositPeriod %v", flags), "") + proposalsQuery = tests.ExecuteT(t, fmt.Sprintf("gaiacli query proposals --status=DepositPeriod %v", flags), "") require.Equal(t, "No matching proposals found", proposalsQuery) - proposalsQuery = tests.ExecuteT(t, fmt.Sprintf("gaiacli gov query-proposals --status=VotingPeriod %v", flags), "") + proposalsQuery = tests.ExecuteT(t, fmt.Sprintf("gaiacli query proposals --status=VotingPeriod %v", flags), "") require.Equal(t, " 1 - Test", proposalsQuery) // submit a second test proposal - spStr = fmt.Sprintf("gaiacli gov submit-proposal %v", flags) + spStr = fmt.Sprintf("gaiacli tx submit-proposal %v", flags) spStr += fmt.Sprintf(" --from=%s", "foo") spStr += fmt.Sprintf(" --deposit=%s", "5steak") spStr += fmt.Sprintf(" --type=%s", "Text") @@ -417,7 +417,7 @@ func TestGaiaCLISubmitProposal(t *testing.T) { executeWrite(t, spStr, app.DefaultKeyPass) tests.WaitForNextNBlocksTM(2, port) - proposalsQuery = tests.ExecuteT(t, fmt.Sprintf("gaiacli gov query-proposals --latest=1 %v", flags), "") + proposalsQuery = tests.ExecuteT(t, fmt.Sprintf("gaiacli query proposals --latest=1 %v", flags), "") require.Equal(t, " 2 - Apples", proposalsQuery) } @@ -437,7 +437,7 @@ func TestGaiaCLISendGenerateSignAndBroadcast(t *testing.T) { // Test generate sendTx with default gas success, stdout, stderr := executeWriteRetStdStreams(t, fmt.Sprintf( - "gaiacli send %v --amount=10steak --to=%s --from=foo --generate-only", + "gaiacli tx send %v --amount=10steak --to=%s --from=foo --generate-only", flags, barAddr), []string{}...) require.True(t, success) require.Empty(t, stderr) @@ -448,7 +448,7 @@ func TestGaiaCLISendGenerateSignAndBroadcast(t *testing.T) { // Test generate sendTx with --gas=$amount success, stdout, stderr = executeWriteRetStdStreams(t, fmt.Sprintf( - "gaiacli send %v --amount=10steak --to=%s --from=foo --gas=100 --generate-only", + "gaiacli tx send %v --amount=10steak --to=%s --from=foo --gas=100 --generate-only", flags, barAddr), []string{}...) require.True(t, success) require.Empty(t, stderr) @@ -459,7 +459,7 @@ func TestGaiaCLISendGenerateSignAndBroadcast(t *testing.T) { // Test generate sendTx, estimate gas success, stdout, stderr = executeWriteRetStdStreams(t, fmt.Sprintf( - "gaiacli send %v --amount=10steak --to=%s --from=foo --gas=simulate --generate-only", + "gaiacli tx send %v --amount=10steak --to=%s --from=foo --gas=simulate --generate-only", flags, barAddr), []string{}...) require.True(t, success) require.NotEmpty(t, stderr) @@ -473,13 +473,13 @@ func TestGaiaCLISendGenerateSignAndBroadcast(t *testing.T) { // Test sign --print-sigs success, stdout, _ = executeWriteRetStdStreams(t, fmt.Sprintf( - "gaiacli sign %v --print-sigs %v", flags, unsignedTxFile.Name())) + "gaiacli tx sign %v --print-sigs %v", flags, unsignedTxFile.Name())) require.True(t, success) require.Equal(t, fmt.Sprintf("Signers:\n 0: %v\n\nSignatures:\n", fooAddr.String()), stdout) // Test sign success, stdout, _ = executeWriteRetStdStreams(t, fmt.Sprintf( - "gaiacli sign %v --name=foo %v", flags, unsignedTxFile.Name()), app.DefaultKeyPass) + "gaiacli tx sign %v --name=foo %v", flags, unsignedTxFile.Name()), app.DefaultKeyPass) require.True(t, success) msg = unmarshalStdTx(t, stdout) require.Equal(t, len(msg.Msgs), 1) @@ -492,15 +492,15 @@ func TestGaiaCLISendGenerateSignAndBroadcast(t *testing.T) { // Test sign --print-signatures success, stdout, _ = executeWriteRetStdStreams(t, fmt.Sprintf( - "gaiacli sign %v --print-sigs %v", flags, signedTxFile.Name())) + "gaiacli tx sign %v --print-sigs %v", flags, signedTxFile.Name())) require.True(t, success) require.Equal(t, fmt.Sprintf("Signers:\n 0: %v\n\nSignatures:\n 0: %v\n", fooAddr.String(), fooAddr.String()), stdout) // Test broadcast - fooAcc := executeGetAccount(t, fmt.Sprintf("gaiacli account %s %v", fooAddr, flags)) + fooAcc := executeGetAccount(t, fmt.Sprintf("gaiacli query account %s %v", fooAddr, flags)) require.Equal(t, int64(50), fooAcc.GetCoins().AmountOf("steak").Int64()) - success, stdout, _ = executeWriteRetStdStreams(t, fmt.Sprintf("gaiacli broadcast %v --json %v", flags, signedTxFile.Name())) + success, stdout, _ = executeWriteRetStdStreams(t, fmt.Sprintf("gaiacli tx broadcast %v --json %v", flags, signedTxFile.Name())) require.True(t, success) var result struct { Response abci.ResponseDeliverTx @@ -510,9 +510,9 @@ func TestGaiaCLISendGenerateSignAndBroadcast(t *testing.T) { require.Equal(t, msg.Fee.Gas, result.Response.GasWanted) tests.WaitForNextNBlocksTM(2, port) - barAcc := executeGetAccount(t, fmt.Sprintf("gaiacli account %s %v", barAddr, flags)) + barAcc := executeGetAccount(t, fmt.Sprintf("gaiacli query account %s %v", barAddr, flags)) require.Equal(t, int64(10), barAcc.GetCoins().AmountOf("steak").Int64()) - fooAcc = executeGetAccount(t, fmt.Sprintf("gaiacli account %s %v", fooAddr, flags)) + fooAcc = executeGetAccount(t, fmt.Sprintf("gaiacli query account %s %v", fooAddr, flags)) require.Equal(t, int64(40), fooAcc.GetCoins().AmountOf("steak").Int64()) } diff --git a/cmd/gaia/cmd/gaiacli/main.go b/cmd/gaia/cmd/gaiacli/main.go index fee43dc02b..2f66caadd6 100644 --- a/cmd/gaia/cmd/gaiacli/main.go +++ b/cmd/gaia/cmd/gaiacli/main.go @@ -23,6 +23,13 @@ import ( "github.com/spf13/viper" ) +const ( + storeAcc = "acc" + storeGov = "gov" + storeSlashing = "slashing" + storeStake = "stake" +) + // rootCmd is the entry point for this binary var ( rootCmd = &cobra.Command{ @@ -43,89 +50,71 @@ func main() { // add standard rpc commands rpc.AddCommands(rootCmd) - //Add state commands - tendermintCmd := &cobra.Command{ - Use: "tendermint", - Short: "Tendermint state querying subcommands", + //Add query commands + queryCmd := &cobra.Command{ + Use: "query", + Aliases: []string{"q"}, + Short: "Querying subcommands", } - tendermintCmd.AddCommand( + queryCmd.AddCommand( rpc.BlockCommand(), rpc.ValidatorCommand(), ) - tx.AddCommands(tendermintCmd, cdc) + tx.AddCommands(queryCmd, cdc) + queryCmd.AddCommand(client.LineBreak) + queryCmd.AddCommand(client.GetCommands( + authcmd.GetAccountCmd(storeAcc, cdc, authcmd.GetAccountDecoder(cdc)), + stakecmd.GetCmdQueryDelegation(storeStake, cdc), + stakecmd.GetCmdQueryDelegations(storeStake, cdc), + stakecmd.GetCmdQueryParams(storeStake, cdc), + stakecmd.GetCmdQueryPool(storeStake, cdc), + govcmd.GetCmdQueryProposal(storeGov, cdc), + govcmd.GetCmdQueryProposals(storeGov, cdc), + stakecmd.GetCmdQueryRedelegation(storeStake, cdc), + stakecmd.GetCmdQueryRedelegations(storeStake, cdc), + slashingcmd.GetCmdQuerySigningInfo(storeSlashing, cdc), + stakecmd.GetCmdQueryUnbondingDelegation(storeStake, cdc), + stakecmd.GetCmdQueryUnbondingDelegations(storeStake, cdc), + stakecmd.GetCmdQueryValidator(storeStake, cdc), + stakecmd.GetCmdQueryValidators(storeStake, cdc), + govcmd.GetCmdQueryVote(storeGov, cdc), + govcmd.GetCmdQueryVotes(storeGov, cdc), + )...) - rootCmd.AddCommand( - tendermintCmd, - lcd.ServeCommand(cdc), - client.LineBreak, - ) - - //Add stake commands - stakeCmd := &cobra.Command{ - Use: "stake", - Short: "Stake and validation subcommands", + //Add query commands + txCmd := &cobra.Command{ + Use: "tx", + Short: "Transactions subcommands", } - stakeCmd.AddCommand( - client.GetCommands( - stakecmd.GetCmdQueryValidator("stake", cdc), - stakecmd.GetCmdQueryValidators("stake", cdc), - stakecmd.GetCmdQueryDelegation("stake", cdc), - stakecmd.GetCmdQueryDelegations("stake", cdc), - stakecmd.GetCmdQueryParams("stake", cdc), - stakecmd.GetCmdQueryPool("stake", cdc), - stakecmd.GetCmdQueryUnbondingDelegation("stake", cdc), - stakecmd.GetCmdQueryUnbondingDelegations("stake", cdc), - stakecmd.GetCmdQueryRedelegation("stake", cdc), - stakecmd.GetCmdQueryRedelegations("stake", cdc), - slashingcmd.GetCmdQuerySigningInfo("slashing", cdc), + + //Add auth and bank commands + txCmd.AddCommand( + client.PostCommands( + bankcmd.GetBroadcastCommand(cdc), + authcmd.GetSignCommand(cdc, authcmd.GetAccountDecoder(cdc)), )...) - stakeCmd.AddCommand( + txCmd.AddCommand(client.LineBreak) + + txCmd.AddCommand( client.PostCommands( stakecmd.GetCmdCreateValidator(cdc), stakecmd.GetCmdEditValidator(cdc), stakecmd.GetCmdDelegate(cdc), - stakecmd.GetCmdUnbond("stake", cdc), - stakecmd.GetCmdRedelegate("stake", cdc), - slashingcmd.GetCmdUnjail(cdc), - )...) - rootCmd.AddCommand( - stakeCmd, - ) - - //Add stake commands - govCmd := &cobra.Command{ - Use: "gov", - Short: "Governance and voting subcommands", - } - govCmd.AddCommand( - client.GetCommands( - govcmd.GetCmdQueryProposal("gov", cdc), - govcmd.GetCmdQueryVote("gov", cdc), - govcmd.GetCmdQueryVotes("gov", cdc), - govcmd.GetCmdQueryProposals("gov", cdc), - )...) - govCmd.AddCommand( - client.PostCommands( - govcmd.GetCmdSubmitProposal(cdc), govcmd.GetCmdDeposit(cdc), + stakecmd.GetCmdRedelegate(storeStake, cdc), + bankcmd.SendTxCmd(cdc), + govcmd.GetCmdSubmitProposal(cdc), + stakecmd.GetCmdUnbond(storeStake, cdc), + slashingcmd.GetCmdUnjail(cdc), govcmd.GetCmdVote(cdc), )...) rootCmd.AddCommand( - govCmd, + queryCmd, + txCmd, + lcd.ServeCommand(cdc), + client.LineBreak, ) - //Add auth and bank commands - rootCmd.AddCommand( - client.GetCommands( - authcmd.GetAccountCmd("acc", cdc, authcmd.GetAccountDecoder(cdc)), - authcmd.GetSignCommand(cdc, authcmd.GetAccountDecoder(cdc)), - )...) - rootCmd.AddCommand( - client.PostCommands( - bankcmd.SendTxCmd(cdc), - bankcmd.GetBroadcastCommand(cdc), - )...) - // add proxy, version and key info rootCmd.AddCommand( keys.Commands(), diff --git a/docs/clients/ledger.md b/docs/clients/ledger.md index 26b0215b3d..e4bf9ac15b 100644 --- a/docs/clients/ledger.md +++ b/docs/clients/ledger.md @@ -23,7 +23,7 @@ NAME: TYPE: ADDRESS: PUBKEY: This key will only be accessible while the Ledger is plugged in and unlocked. To send some coins with this key, run the following: ```bash -$ gaiacli send --from {{ .Key.Name }} --to {{ .Destination.AccAddr }} --chain-id=gaia-7000 +$ gaiacli tx send --from {{ .Key.Name }} --to {{ .Destination.AccAddr }} --chain-id=gaia-7000 ``` You will be asked to review and confirm the transaction on the Ledger. Once you do this you should see the result in the console! Now you can use your Ledger to manage your Atoms and Stake! diff --git a/docs/clients/service-providers.md b/docs/clients/service-providers.md index d7f69fb10e..79fe2a667d 100644 --- a/docs/clients/service-providers.md +++ b/docs/clients/service-providers.md @@ -62,7 +62,7 @@ gaiacli account Here is the command to send coins via the CLI: ```bash -gaiacli send --amount=10faucetToken --chain-id= --name= --to= +gaiacli tx send --amount=10faucetToken --chain-id= --name= --to= ``` Flags: @@ -88,7 +88,7 @@ The Rest Server acts as an intermediary between the front-end and the full-node. To start the Rest server: ```bash -gaiacli advanced rest-server --trust-node=false --node= +gaiacli rest-server --trust-node=false --node= ``` Flags: @@ -108,4 +108,4 @@ The Rest API documents all the available endpoints that you can use to interract The API is divided into ICS standards for each category of endpoints. For example, the [ICS20](https://github.com/cosmos/cosmos-sdk/blob/develop/docs/light/api.md#ics20---tokenapi) describes the API to interact with tokens. -To give more flexibility to implementers, we have separated the different steps that are involved in the process of sending transactions. You will be able to generate unsigned transactions (example with [coin transfer](https://github.com/cosmos/cosmos-sdk/blob/develop/docs/light/api.md#post-banktransfers)), [sign](https://github.com/cosmos/cosmos-sdk/blob/develop/docs/light/api.md#post-authtxsign) and [broadcast](https://github.com/cosmos/cosmos-sdk/blob/develop/docs/light/api.md#post-authtxbroadcast) them with different API endpoints. This allows service providers to use their own signing mechanism for instance. \ No newline at end of file +To give more flexibility to implementers, we have separated the different steps that are involved in the process of sending transactions. You will be able to generate unsigned transactions (example with [coin transfer](https://github.com/cosmos/cosmos-sdk/blob/develop/docs/light/api.md#post-banktransfers)), [sign](https://github.com/cosmos/cosmos-sdk/blob/develop/docs/light/api.md#post-authtxsign) and [broadcast](https://github.com/cosmos/cosmos-sdk/blob/develop/docs/light/api.md#post-authtxbroadcast) them with different API endpoints. This allows service providers to use their own signing mechanism for instance. diff --git a/docs/sdk/clients.md b/docs/sdk/clients.md index 5b7d3ca866..5e5a9c7ac3 100644 --- a/docs/sdk/clients.md +++ b/docs/sdk/clients.md @@ -98,7 +98,7 @@ When you query an account balance with zero tokens, you will get this error: `No The following command could be used to send coins from one account to another: ```bash -gaiacli send \ +gaiacli tx send \ --amount=10faucetToken \ --chain-id= \ --name= \ @@ -131,7 +131,7 @@ gaiacli account --block= You can simulate a transaction without actually broadcasting it by appending the `--dry-run` flag to the command line: ```bash -gaiacli send \ +gaiacli tx send \ --amount=10faucetToken \ --chain-id= \ --name= \ @@ -142,7 +142,7 @@ gaiacli send \ Furthermore, you can build a transaction and print its JSON format to STDOUT by appending `--generate-only` to the list of the command line arguments: ```bash -gaiacli send \ +gaiacli tx send \ --amount=10faucetToken \ --chain-id= \ --name= \ @@ -153,7 +153,7 @@ gaiacli send \ You can now sign the transaction file generated through the `--generate-only` flag by providing your key to the following command: ```bash -gaiacli sign \ +gaiacli tx sign \ --chain-id= \ --name= unsignedSendTx.json > signedSendTx.json @@ -162,7 +162,7 @@ gaiacli sign \ You can broadcast the signed transaction to a node by providing the JSON file to the following command: ``` -gaiacli broadcast --node= signedSendTx.json +gaiacli tx broadcast --node= signedSendTx.json ``` ### Staking @@ -180,13 +180,13 @@ On the upcoming mainnet, you can delegate `atom` to a validator. These [delegato You can query the list of all validators of a specific chain: ```bash -gaiacli stake validators +gaiacli query validators ``` If you want to get the information of a single validator you can check it with: ```bash -gaiacli stake validator +gaiacli query validator ``` #### Bond Tokens @@ -194,7 +194,7 @@ gaiacli stake validator On the testnet, we delegate `steak` instead of `atom`. Here's how you can bond tokens to a testnet validator (*i.e.* delegate): ```bash -gaiacli stake delegate \ +gaiacli tx delegate \ --amount=10steak \ --validator=$(gaiad tendermint show-validator) \ --name= \ @@ -212,7 +212,7 @@ Don't use more `steak` thank you have! You can always get more by using the [Fau Once submitted a delegation to a validator, you can see it's information by using the following command: ```bash -gaiacli stake delegation \ +gaiacli query delegation \ --address-delegator= \ --validator= ``` @@ -220,7 +220,7 @@ gaiacli stake delegation \ Or if you want to check all your current delegations with disctinct validators: ```bash -gaiacli stake delegations +gaiacli query delegations ``` You can also get previous delegation(s) status by adding the `--height` flag. @@ -230,17 +230,17 @@ You can also get previous delegation(s) status by adding the `--height` flag. If for any reason the validator misbehaves, or you just want to unbond a certain amount of tokens, use this following command. You can unbond a specific `shares-amount` (eg:`12.1`\) or a `shares-percent` (eg:`25`) with the corresponding flags. ```bash -gaiacli stake unbond begin \ +gaiacli tx unbond begin \ --validator= \ --shares-percent=100 \ --from= \ --chain-id= ``` -Later you must complete the unbonding process by using the `gaiacli stake unbond complete` command: +Later you must complete the unbonding process by using the `gaiacli tx unbond complete` command: ```bash -gaiacli stake unbond complete \ +gaiacli tx unbond complete \ --validator= \ --from= \ --chain-id= @@ -251,7 +251,7 @@ gaiacli stake unbond complete \ Once you begin an unbonding-delegation, you can see it's information by using the following command: ```bash -gaiacli stake unbonding-delegation \ +gaiacli query unbonding-delegation \ --address-delegator= \ --validator= \ ``` @@ -259,7 +259,7 @@ gaiacli stake unbonding-delegation \ Or if you want to check all your current unbonding-delegations with disctinct validators: ```bash -gaiacli stake unbonding-delegations +gaiacli query unbonding-delegations ``` You can also get previous unbonding-delegation(s) status by adding the `--height` flag. @@ -269,7 +269,7 @@ You can also get previous unbonding-delegation(s) status by adding the `--height A redelegation is a type delegation that allows you to bond illiquid tokens from one validator to another: ```bash -gaiacli stake redelegate begin \ +gaiacli tx redelegate begin \ --address-validator-source= \ --address-validator-dest= \ --shares-percent=50 \ @@ -279,10 +279,10 @@ gaiacli stake redelegate begin \ Here you can also redelegate a specific `shares-amount` or a `shares-percent` with the corresponding flags. -Later you must complete the redelegation process by using the `gaiacli stake redelegate complete` command: +Later you must complete the redelegation process by using the `gaiacli tx redelegate complete` command: ```bash -gaiacli stake unbond complete \ +gaiacli tx unbond complete \ --validator= \ --from= \ --chain-id= @@ -293,7 +293,7 @@ gaiacli stake unbond complete \ Once you begin an redelegation, you can see it's information by using the following command: ```bash -gaiacli stake redelegation \ +gaiacli query redelegation \ --address-delegator= \ --address-validator-source= \ --address-validator-dest= \ @@ -302,7 +302,7 @@ gaiacli stake redelegation \ Or if you want to check all your current unbonding-delegations with disctinct validators: ```bash -gaiacli stake redelegations +gaiacli query redelegations ``` You can also get previous redelegation(s) status by adding the `--height` flag. @@ -331,7 +331,7 @@ In order to create a governance proposal, you must submit an initial deposit alo - `type`: Type of proposal. Must be of value _Text_ (types _SoftwareUpgrade_ and _ParameterChange_ not supported yet). ```bash -gaiacli gov submit-proposal \ +gaiacli tx submit-proposal \ --title= \ --description=<description> \ --type=<Text/ParameterChange/SoftwareUpgrade> \ @@ -346,14 +346,14 @@ gaiacli gov submit-proposal \ Once created, you can now query information of the proposal: ```bash -gaiacli gov query-proposal \ +gaiacli query proposal \ --proposal-id=<proposal_id> ``` Or query all available proposals: ```bash -gaiacli gov query-proposals +gaiacli query proposals ``` You can also query proposals filtered by `voter` or `depositer` by using the corresponding flags. @@ -363,7 +363,7 @@ You can also query proposals filtered by `voter` or `depositer` by using the cor In order for a proposal to be broadcasted to the network, the amount deposited must be above a `minDeposit` value (default: `10 steak`). If the proposal you previously created didn't meet this requirement, you can still increase the total amount deposited to activate it. Once the minimum deposit is reached, the proposal enters voting period: ```bash -gaiacli gov deposit \ +gaiacli tx deposit \ --proposal-id=<proposal_id> \ --depositer=<account_cosmos> \ --deposit=<200steak> \ @@ -378,7 +378,7 @@ gaiacli gov deposit \ After a proposal's deposit reaches the `MinDeposit` value, the voting period opens. Bonded `Atom` holders can then cast vote on it: ```bash -gaiacli gov vote \ +gaiacli tx vote \ --proposal-id=<proposal_id> \ --voter=<account_cosmos> \ --option=<Yes/No/NoWithVeto/Abstain> \ @@ -391,7 +391,7 @@ gaiacli gov vote \ Check the vote with the option you just submitted: ```bash -gaiacli gov query-vote \ +gaiacli query vote \ --proposal-id=<proposal_id> \ --voter=<account_cosmos> ``` @@ -401,7 +401,7 @@ gaiacli gov query-vote \ You can get the current parameters that define high level settings for staking: ``` -gaiacli stake parameters +gaiacli query parameters ``` With the above command you will get the values for: @@ -420,7 +420,7 @@ All this values can be updated though a `governance` process by submitting a par A staking `Pool` defines the dynamic parameters of the current state. You can query them with the following command: ``` -gaiacli stake pool +gaiacli query pool ``` With the `pool` command you will get the values for: diff --git a/docs/validators/validator-setup.md b/docs/validators/validator-setup.md index dbcc84b3d5..43a3b06728 100644 --- a/docs/validators/validator-setup.md +++ b/docs/validators/validator-setup.md @@ -22,14 +22,14 @@ Your `cosmosvalconspub` can be used to create a new validator by staking tokens. gaiad tendermint show-validator ``` -Next, craft your `gaiacli stake create-validator` command: +Next, craft your `gaiacli tx create-validator` command: ::: warning Note Don't use more `steak` thank you have! You can always get more by using the [Faucet](https://faucetcosmos.network/)! ::: ```bash -gaiacli stake create-validator \ +gaiacli tx create-validator \ --amount=5steak \ --pubkey=$(gaiad tendermint show-validator) \ --address-validator=<account_cosmosval> @@ -52,7 +52,7 @@ You can edit your validator's public description. This info is to identify your The `--identity` can be used as to verify identity with systems like Keybase or UPort. When using with Keybase `--identity` should be populated with a 16-digit string that is generated with a [keybase.io](https://keybase.io) account. It's a cryptographically secure method of verifying your identity across multiple online networks. The Keybase API allows us to retrieve your Keybase avatar. This is how you can add a logo to your validator profile. ```bash -gaiacli stake edit-validator +gaiacli tx edit-validator --validator=<account_cosmos> --moniker="choose a moniker" \ --website="https://cosmos.network" \ @@ -75,7 +75,7 @@ __Note__: The `commission-rate` value must adhere to the following invariants: View the validator's information with this command: ```bash -gaiacli stake validator <account_cosmos> +gaiacli query validator <account_cosmos> ``` ### Track Validator Signing Information @@ -83,7 +83,7 @@ gaiacli stake validator <account_cosmos> In order to keep track of a validator's signatures in the past you can do so by using the `signing-info` command: ```bash -gaiacli stake signing-information <validator-pubkey>\ +gaiacli query signing-information <validator-pubkey>\ --chain-id=<chain_id> ``` @@ -92,7 +92,7 @@ gaiacli stake signing-information <validator-pubkey>\ When a validator is "jailed" for downtime, you must submit an `Unjail` transaction in order to be able to get block proposer rewards again (depends on the zone fee distribution). ```bash -gaiacli stake unjail \ +gaiacli tx unjail \ --from=<key_name> \ --chain-id=<chain_id> --validator=<account_cosmosval> \ @@ -104,7 +104,7 @@ gaiacli stake unjail \ Your validator is active if the following command returns anything: ```bash -gaiacli tendermint validator-set | grep "$(gaiad tendermint show-validator)" +gaiacli query tendermint-validator-set | grep "$(gaiad tendermint show-validator)" ``` You should also be able to see your validator on the [Explorer](https://explorecosmos.network/validators). You are looking for the `bech32` encoded `address` in the `~/.gaiad/config/priv_validator.json` file. @@ -128,7 +128,7 @@ gaiad start Wait for your full node to catch up to the latest block. Next, run the following command. Note that `<cosmos>` is the address of your validator account, and `<name>` is the name of the validator account. You can find this info by running `gaiacli keys list`. ```bash -gaiacli stake unjail <cosmos> --chain-id=<chain_id> --name=<name> +gaiacli tx unjail <cosmos> --chain-id=<chain_id> --name=<name> ``` ::: danger Warning diff --git a/x/auth/client/cli/sign.go b/x/auth/client/cli/sign.go index d8ffae813e..31648e3de0 100644 --- a/x/auth/client/cli/sign.go +++ b/x/auth/client/cli/sign.go @@ -25,7 +25,7 @@ const ( func GetSignCommand(codec *amino.Codec, decoder auth.AccountDecoder) *cobra.Command { cmd := &cobra.Command{ Use: "sign <file>", - Short: "Sign transactions", + Short: "Sign transactions generated offline", Long: `Sign transactions created with the --generate-only flag. Read a transaction from <file>, sign it, and print its JSON encoding.`, RunE: makeSignCmd(codec, decoder), diff --git a/x/gov/client/cli/tx.go b/x/gov/client/cli/tx.go index cb897f43af..2a759c218f 100644 --- a/x/gov/client/cli/tx.go +++ b/x/gov/client/cli/tx.go @@ -159,7 +159,7 @@ func parseSubmitProposalFlags() (*proposal, error) { func GetCmdDeposit(cdc *codec.Codec) *cobra.Command { cmd := &cobra.Command{ Use: "deposit", - Short: "deposit tokens for activing proposal", + Short: "Deposit tokens for activing proposal", RunE: func(cmd *cobra.Command, args []string) error { txBldr := authtxb.NewTxBuilderFromCLI().WithCodec(cdc) cliCtx := context.NewCLIContext(). @@ -205,7 +205,7 @@ func GetCmdDeposit(cdc *codec.Codec) *cobra.Command { func GetCmdVote(cdc *codec.Codec) *cobra.Command { cmd := &cobra.Command{ Use: "vote", - Short: "vote for an active proposal, options: Yes/No/NoWithVeto/Abstain", + Short: "Vote for an active proposal, options: Yes/No/NoWithVeto/Abstain", RunE: func(cmd *cobra.Command, args []string) error { txBldr := authtxb.NewTxBuilderFromCLI().WithCodec(cdc) cliCtx := context.NewCLIContext(). @@ -255,8 +255,8 @@ func GetCmdVote(cdc *codec.Codec) *cobra.Command { // GetCmdQueryProposal implements the query proposal command. func GetCmdQueryProposal(queryRoute string, cdc *codec.Codec) *cobra.Command { cmd := &cobra.Command{ - Use: "query-proposal", - Short: "query proposal details", + Use: "proposal", + Short: "Query details of a single proposal", RunE: func(cmd *cobra.Command, args []string) error { cliCtx := context.NewCLIContext().WithCodec(cdc) proposalID := viper.GetInt64(flagProposalID) @@ -288,8 +288,8 @@ func GetCmdQueryProposal(queryRoute string, cdc *codec.Codec) *cobra.Command { // GetCmdQueryProposals implements a query proposals command. func GetCmdQueryProposals(queryRoute string, cdc *codec.Codec) *cobra.Command { cmd := &cobra.Command{ - Use: "query-proposals", - Short: "query proposals with optional filters", + Use: "proposals", + Short: "Query proposals with optional filters", RunE: func(cmd *cobra.Command, args []string) error { bechDepositerAddr := viper.GetString(flagDepositer) bechVoterAddr := viper.GetString(flagVoter) @@ -367,8 +367,8 @@ func GetCmdQueryProposals(queryRoute string, cdc *codec.Codec) *cobra.Command { // GetCmdQueryVote implements the query proposal vote command. func GetCmdQueryVote(queryRoute string, cdc *codec.Codec) *cobra.Command { cmd := &cobra.Command{ - Use: "query-vote", - Short: "query vote", + Use: "vote", + Short: "Query details of a single vote", RunE: func(cmd *cobra.Command, args []string) error { cliCtx := context.NewCLIContext().WithCodec(cdc) proposalID := viper.GetInt64(flagProposalID) @@ -406,8 +406,8 @@ func GetCmdQueryVote(queryRoute string, cdc *codec.Codec) *cobra.Command { // GetCmdQueryVotes implements the command to query for proposal votes. func GetCmdQueryVotes(queryRoute string, cdc *codec.Codec) *cobra.Command { cmd := &cobra.Command{ - Use: "query-votes", - Short: "query votes on a proposal", + Use: "votes", + Short: "Query votes on a proposal", RunE: func(cmd *cobra.Command, args []string) error { cliCtx := context.NewCLIContext().WithCodec(cdc) proposalID := viper.GetInt64(flagProposalID) @@ -439,8 +439,8 @@ func GetCmdQueryVotes(queryRoute string, cdc *codec.Codec) *cobra.Command { // GetCmdQueryDeposit implements the query proposal deposit command. func GetCmdQueryDeposit(queryRoute string, cdc *codec.Codec) *cobra.Command { cmd := &cobra.Command{ - Use: "query-deposit", - Short: "query deposit", + Use: "deposit", + Short: "Query details of a deposit", RunE: func(cmd *cobra.Command, args []string) error { cliCtx := context.NewCLIContext().WithCodec(cdc) proposalID := viper.GetInt64(flagProposalID) @@ -478,8 +478,8 @@ func GetCmdQueryDeposit(queryRoute string, cdc *codec.Codec) *cobra.Command { // GetCmdQueryDeposits implements the command to query for proposal deposits. func GetCmdQueryDeposits(queryRoute string, cdc *codec.Codec) *cobra.Command { cmd := &cobra.Command{ - Use: "query-deposits", - Short: "query deposits on a proposal", + Use: "deposits", + Short: "Query deposits on a proposal", RunE: func(cmd *cobra.Command, args []string) error { cliCtx := context.NewCLIContext().WithCodec(cdc) proposalID := viper.GetInt64(flagProposalID) @@ -510,8 +510,8 @@ func GetCmdQueryDeposits(queryRoute string, cdc *codec.Codec) *cobra.Command { // GetCmdQueryDeposits implements the command to query for proposal deposits. func GetCmdQueryTally(queryRoute string, cdc *codec.Codec) *cobra.Command { cmd := &cobra.Command{ - Use: "query-tally", - Short: "get the tally of a proposal vote", + Use: "tally", + Short: "Get the tally of a proposal vote", RunE: func(cmd *cobra.Command, args []string) error { cliCtx := context.NewCLIContext().WithCodec(cdc) proposalID := viper.GetInt64(flagProposalID) From 8ca8acf6387c753cc95a8670642e3cb4e20f0095 Mon Sep 17 00:00:00 2001 From: Alexander Bezobchuk <alexanderbez@users.noreply.github.com> Date: Wed, 26 Sep 2018 14:02:56 +0000 Subject: [PATCH 22/26] Merge PR #2357: Refactor Iterator Gas Consumption --- PENDING.md | 3 + store/gaskvstore.go | 143 +++++++++++++++++++++++---------------- store/gaskvstore_test.go | 4 +- types/gas.go | 38 +++++++---- 4 files changed, 117 insertions(+), 71 deletions(-) diff --git a/PENDING.md b/PENDING.md index 8e6c9e94e9..2fc72dd73e 100644 --- a/PENDING.md +++ b/PENDING.md @@ -137,6 +137,9 @@ IMPROVEMENTS * [simulation] Logs get written to file if large, and also get printed on panics \#2285 * [gaiad] \#1992 Add optional flag to `gaiad testnet` to make config directory of daemon (default `gaiad`) and cli (default `gaiacli`) configurable * [x/stake] Add stake `Queriers` for Gaia-lite endpoints. This increases the staking endpoints performance by reusing the staking `keeper` logic for queries. [#2249](https://github.com/cosmos/cosmos-sdk/pull/2149) + * [store] [\#2017](https://github.com/cosmos/cosmos-sdk/issues/2017) Refactor + gas iterator gas consumption to only consume gas for iterator creation and `Next` + calls which includes dynamic consumption of value length. * [types/decimal] \#2378 - Added truncate functionality to decimal * [client] [\#1184](https://github.com/cosmos/cosmos-sdk/issues/1184) Remove unused `client/tx/sign.go`. diff --git a/store/gaskvstore.go b/store/gaskvstore.go index 53ac340bd7..2ce6415e66 100644 --- a/store/gaskvstore.go +++ b/store/gaskvstore.go @@ -8,13 +8,15 @@ import ( var _ KVStore = &gasKVStore{} -// gasKVStore applies gas tracking to an underlying kvstore +// gasKVStore applies gas tracking to an underlying KVStore. It implements the +// KVStore interface. type gasKVStore struct { gasMeter sdk.GasMeter gasConfig sdk.GasConfig parent sdk.KVStore } +// NewGasKVStore returns a reference to a new GasKVStore. // nolint func NewGasKVStore(gasMeter sdk.GasMeter, gasConfig sdk.GasConfig, parent sdk.KVStore) *gasKVStore { kvs := &gasKVStore{ @@ -26,83 +28,96 @@ func NewGasKVStore(gasMeter sdk.GasMeter, gasConfig sdk.GasConfig, parent sdk.KV } // Implements Store. -func (gi *gasKVStore) GetStoreType() sdk.StoreType { - return gi.parent.GetStoreType() +func (gs *gasKVStore) GetStoreType() sdk.StoreType { + return gs.parent.GetStoreType() } // Implements KVStore. -func (gi *gasKVStore) Get(key []byte) (value []byte) { - gi.gasMeter.ConsumeGas(gi.gasConfig.ReadCostFlat, "ReadFlat") - value = gi.parent.Get(key) +func (gs *gasKVStore) Get(key []byte) (value []byte) { + gs.gasMeter.ConsumeGas(gs.gasConfig.ReadCostFlat, sdk.GasReadCostFlatDesc) + value = gs.parent.Get(key) + // TODO overflow-safe math? - gi.gasMeter.ConsumeGas(gi.gasConfig.ReadCostPerByte*sdk.Gas(len(value)), "ReadPerByte") + gs.gasMeter.ConsumeGas(gs.gasConfig.ReadCostPerByte*sdk.Gas(len(value)), sdk.GasReadPerByteDesc) return value } // Implements KVStore. -func (gi *gasKVStore) Set(key []byte, value []byte) { - gi.gasMeter.ConsumeGas(gi.gasConfig.WriteCostFlat, "WriteFlat") +func (gs *gasKVStore) Set(key []byte, value []byte) { + gs.gasMeter.ConsumeGas(gs.gasConfig.WriteCostFlat, sdk.GasWriteCostFlatDesc) // TODO overflow-safe math? - gi.gasMeter.ConsumeGas(gi.gasConfig.WriteCostPerByte*sdk.Gas(len(value)), "WritePerByte") - gi.parent.Set(key, value) + gs.gasMeter.ConsumeGas(gs.gasConfig.WriteCostPerByte*sdk.Gas(len(value)), sdk.GasWritePerByteDesc) + gs.parent.Set(key, value) } // Implements KVStore. -func (gi *gasKVStore) Has(key []byte) bool { - gi.gasMeter.ConsumeGas(gi.gasConfig.HasCost, "Has") - return gi.parent.Has(key) +func (gs *gasKVStore) Has(key []byte) bool { + gs.gasMeter.ConsumeGas(gs.gasConfig.HasCost, sdk.GasHasDesc) + return gs.parent.Has(key) } // Implements KVStore. -func (gi *gasKVStore) Delete(key []byte) { - // No gas costs for deletion - gi.parent.Delete(key) +func (gs *gasKVStore) Delete(key []byte) { + // charge gas to prevent certain attack vectors even though space is being freed + gs.gasMeter.ConsumeGas(gs.gasConfig.DeleteCost, sdk.GasDeleteDesc) + gs.parent.Delete(key) } // Implements KVStore -func (gi *gasKVStore) Prefix(prefix []byte) KVStore { +func (gs *gasKVStore) Prefix(prefix []byte) KVStore { // Keep gasstore layer at the top return &gasKVStore{ - gasMeter: gi.gasMeter, - gasConfig: gi.gasConfig, - parent: prefixStore{gi.parent, prefix}, + gasMeter: gs.gasMeter, + gasConfig: gs.gasConfig, + parent: prefixStore{gs.parent, prefix}, } } // Implements KVStore -func (gi *gasKVStore) Gas(meter GasMeter, config GasConfig) KVStore { - return NewGasKVStore(meter, config, gi) +func (gs *gasKVStore) Gas(meter GasMeter, config GasConfig) KVStore { + return NewGasKVStore(meter, config, gs) +} + +// Iterator implements the KVStore interface. It returns an iterator which +// incurs a flat gas cost for seeking to the first key/value pair and a variable +// gas cost based on the current value's length if the iterator is valid. +func (gs *gasKVStore) Iterator(start, end []byte) sdk.Iterator { + return gs.iterator(start, end, true) +} + +// ReverseIterator implements the KVStore interface. It returns a reverse +// iterator which incurs a flat gas cost for seeking to the first key/value pair +// and a variable gas cost based on the current value's length if the iterator +// is valid. +func (gs *gasKVStore) ReverseIterator(start, end []byte) sdk.Iterator { + return gs.iterator(start, end, false) } // Implements KVStore. -func (gi *gasKVStore) Iterator(start, end []byte) sdk.Iterator { - return gi.iterator(start, end, true) -} - -// Implements KVStore. -func (gi *gasKVStore) ReverseIterator(start, end []byte) sdk.Iterator { - return gi.iterator(start, end, false) -} - -// Implements KVStore. -func (gi *gasKVStore) CacheWrap() sdk.CacheWrap { +func (gs *gasKVStore) CacheWrap() sdk.CacheWrap { panic("cannot CacheWrap a GasKVStore") } // CacheWrapWithTrace implements the KVStore interface. -func (gi *gasKVStore) CacheWrapWithTrace(_ io.Writer, _ TraceContext) CacheWrap { +func (gs *gasKVStore) CacheWrapWithTrace(_ io.Writer, _ TraceContext) CacheWrap { panic("cannot CacheWrapWithTrace a GasKVStore") } -func (gi *gasKVStore) iterator(start, end []byte, ascending bool) sdk.Iterator { +func (gs *gasKVStore) iterator(start, end []byte, ascending bool) sdk.Iterator { var parent sdk.Iterator if ascending { - parent = gi.parent.Iterator(start, end) + parent = gs.parent.Iterator(start, end) } else { - parent = gi.parent.ReverseIterator(start, end) + parent = gs.parent.ReverseIterator(start, end) } - return newGasIterator(gi.gasMeter, gi.gasConfig, parent) + + gi := newGasIterator(gs.gasMeter, gs.gasConfig, parent) + if gi.Valid() { + gi.(*gasIterator).consumeSeekGas() + } + + return gi } type gasIterator struct { @@ -120,36 +135,50 @@ func newGasIterator(gasMeter sdk.GasMeter, gasConfig sdk.GasConfig, parent sdk.I } // Implements Iterator. -func (g *gasIterator) Domain() (start []byte, end []byte) { - return g.parent.Domain() +func (gi *gasIterator) Domain() (start []byte, end []byte) { + return gi.parent.Domain() } // Implements Iterator. -func (g *gasIterator) Valid() bool { - return g.parent.Valid() +func (gi *gasIterator) Valid() bool { + return gi.parent.Valid() } -// Implements Iterator. -func (g *gasIterator) Next() { - g.parent.Next() +// Next implements the Iterator interface. It seeks to the next key/value pair +// in the iterator. It incurs a flat gas cost for seeking and a variable gas +// cost based on the current value's length if the iterator is valid. +func (gi *gasIterator) Next() { + if gi.Valid() { + gi.consumeSeekGas() + } + + gi.parent.Next() } -// Implements Iterator. -func (g *gasIterator) Key() (key []byte) { - g.gasMeter.ConsumeGas(g.gasConfig.KeyCostFlat, "KeyFlat") - key = g.parent.Key() +// Key implements the Iterator interface. It returns the current key and it does +// not incur any gas cost. +func (gi *gasIterator) Key() (key []byte) { + key = gi.parent.Key() return key } -// Implements Iterator. -func (g *gasIterator) Value() (value []byte) { - value = g.parent.Value() - g.gasMeter.ConsumeGas(g.gasConfig.ValueCostFlat, "ValueFlat") - g.gasMeter.ConsumeGas(g.gasConfig.ValueCostPerByte*sdk.Gas(len(value)), "ValuePerByte") +// Value implements the Iterator interface. It returns the current value and it +// does not incur any gas cost. +func (gi *gasIterator) Value() (value []byte) { + value = gi.parent.Value() return value } // Implements Iterator. -func (g *gasIterator) Close() { - g.parent.Close() +func (gi *gasIterator) Close() { + gi.parent.Close() +} + +// consumeSeekGas consumes a flat gas cost for seeking and a variable gas cost +// based on the current value's length. +func (gi *gasIterator) consumeSeekGas() { + value := gi.Value() + + gi.gasMeter.ConsumeGas(gi.gasConfig.ValueCostPerByte*sdk.Gas(len(value)), sdk.GasValuePerByteDesc) + gi.gasMeter.ConsumeGas(gi.gasConfig.IterNextCostFlat, sdk.GasIterNextCostFlatDesc) } diff --git a/store/gaskvstore_test.go b/store/gaskvstore_test.go index b2c23c9553..eb3ae7dd4f 100644 --- a/store/gaskvstore_test.go +++ b/store/gaskvstore_test.go @@ -25,7 +25,7 @@ func TestGasKVStoreBasic(t *testing.T) { require.Equal(t, valFmt(1), st.Get(keyFmt(1))) st.Delete(keyFmt(1)) require.Empty(t, st.Get(keyFmt(1)), "Expected `key1` to be empty") - require.Equal(t, meter.GasConsumed(), sdk.Gas(183)) + require.Equal(t, meter.GasConsumed(), sdk.Gas(193)) } func TestGasKVStoreIterator(t *testing.T) { @@ -49,7 +49,7 @@ func TestGasKVStoreIterator(t *testing.T) { iterator.Next() require.False(t, iterator.Valid()) require.Panics(t, iterator.Next) - require.Equal(t, meter.GasConsumed(), sdk.Gas(356)) + require.Equal(t, meter.GasConsumed(), sdk.Gas(384)) } func TestGasKVStoreOutOfGasSet(t *testing.T) { diff --git a/types/gas.go b/types/gas.go index e8486c8136..cce4975c30 100644 --- a/types/gas.go +++ b/types/gas.go @@ -1,9 +1,26 @@ package types +// Gas consumption descriptors. +const ( + GasIterNextCostFlatDesc = "IterNextFlat" + GasValuePerByteDesc = "ValuePerByte" + GasWritePerByteDesc = "WritePerByte" + GasReadPerByteDesc = "ReadPerByte" + GasWriteCostFlatDesc = "WriteFlat" + GasReadCostFlatDesc = "ReadFlat" + GasHasDesc = "Has" + GasDeleteDesc = "Delete" +) + +var ( + cachedDefaultGasConfig = DefaultGasConfig() + cachedTransientGasConfig = TransientGasConfig() +) + // Gas measured by the SDK type Gas = int64 -// Error thrown when out of gas +// ErrorOutOfGas defines an error thrown when an action results in out of gas. type ErrorOutOfGas struct { Descriptor string } @@ -19,6 +36,7 @@ type basicGasMeter struct { consumed Gas } +// NewGasMeter returns a reference to a new basicGasMeter. func NewGasMeter(limit Gas) GasMeter { return &basicGasMeter{ limit: limit, @@ -41,6 +59,7 @@ type infiniteGasMeter struct { consumed Gas } +// NewInfiniteGasMeter returns a reference to a new infiniteGasMeter. func NewInfiniteGasMeter() GasMeter { return &infiniteGasMeter{ consumed: 0, @@ -58,35 +77,30 @@ func (g *infiniteGasMeter) ConsumeGas(amount Gas, descriptor string) { // GasConfig defines gas cost for each operation on KVStores type GasConfig struct { HasCost Gas + DeleteCost Gas ReadCostFlat Gas ReadCostPerByte Gas WriteCostFlat Gas WriteCostPerByte Gas - KeyCostFlat Gas - ValueCostFlat Gas ValueCostPerByte Gas + IterNextCostFlat Gas } -var ( - cachedDefaultGasConfig = DefaultGasConfig() - cachedTransientGasConfig = TransientGasConfig() -) - -// Default gas config for KVStores +// DefaultGasConfig returns a default gas config for KVStores. func DefaultGasConfig() GasConfig { return GasConfig{ HasCost: 10, + DeleteCost: 10, ReadCostFlat: 10, ReadCostPerByte: 1, WriteCostFlat: 10, WriteCostPerByte: 10, - KeyCostFlat: 5, - ValueCostFlat: 10, ValueCostPerByte: 1, + IterNextCostFlat: 15, } } -// Default gas config for TransientStores +// TransientGasConfig returns a default gas config for TransientStores. func TransientGasConfig() GasConfig { // TODO: define gasconfig for transient stores return DefaultGasConfig() From 91ee6b0d99d9bb1c5324a69bffbaedbd96e06602 Mon Sep 17 00:00:00 2001 From: Christopher Goes <cwgoes@pluranimity.org> Date: Wed, 26 Sep 2018 23:04:42 +0800 Subject: [PATCH 23/26] Merge PR #2313: Multi-seed parallel simulation --- Makefile | 6 +++--- PENDING.md | 1 + scripts/multisim.sh | 48 +++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 52 insertions(+), 3 deletions(-) create mode 100755 scripts/multisim.sh diff --git a/Makefile b/Makefile index 60c213871c..49f15b375f 100644 --- a/Makefile +++ b/Makefile @@ -159,9 +159,9 @@ test_sim_gaia_fast: @echo "Running quick Gaia simulation. This may take several minutes..." @go test ./cmd/gaia/app -run TestFullGaiaSimulation -SimulationEnabled=true -SimulationNumBlocks=400 -SimulationBlockSize=200 -SimulationCommit=true -v -timeout 24h -test_sim_gaia_slow: - @echo "Running full Gaia simulation. This may take a while!" - @go test ./cmd/gaia/app -run TestFullGaiaSimulation -SimulationEnabled=true -SimulationNumBlocks=1000 -SimulationVerbose=true -SimulationCommit=true -v -timeout 24h +test_sim_gaia_full: + @echo "Running full multi-seed Gaia simulation. This may take awhile!" + @sh scripts/multisim.sh SIM_NUM_BLOCKS ?= 210 SIM_BLOCK_SIZE ?= 200 diff --git a/PENDING.md b/PENDING.md index 2fc72dd73e..844b7c7d84 100644 --- a/PENDING.md +++ b/PENDING.md @@ -90,6 +90,7 @@ FEATURES * Gaia * [cli] #2170 added ability to show the node's address via `gaiad tendermint show-address` + * [simulation] #2313 Reworked `make test_sim_gaia_slow` to `make test_sim_gaia_full`, now simulates from multiple starting seeds in parallel * [cli] [\#1921] (https://github.com/cosmos/cosmos-sdk/issues/1921) * New configuration file `gaiad.toml` is now created to host Gaia-specific configuration. * New --minimum_fees/minimum_fees flag/config option to set a minimum fee. diff --git a/scripts/multisim.sh b/scripts/multisim.sh new file mode 100755 index 0000000000..be59b0f1a8 --- /dev/null +++ b/scripts/multisim.sh @@ -0,0 +1,48 @@ +#!/bin/bash + +seeds=(1 2 4 7 9 20 32 123 4728 37827 981928 87821 891823782 989182 89182391) + +echo "Running multi-seed simulation with seeds: ${seeds[@]}" +echo "Edit scripts/multisim.sh to add new seeds. Keeping parameters in the file makes failures easy to reproduce." +echo "This script will kill all sub-simulations on SIGINT/SIGTERM/EXIT (i.e. Ctrl-C)." + +trap 'kill $(jobs -pr)' SIGINT SIGTERM EXIT + +tmpdir=$(mktemp -d) +echo "Using temporary log directory: $tmpdir" + +sim() { + seed=$1 + echo "Running full Gaia simulation with seed $seed. This may take awhile!" + file="$tmpdir/gaia-simulation-seed-$seed-date-$(date -Iseconds -u).stdout" + echo "Writing stdout to $file..." + go test ./cmd/gaia/app -run TestFullGaiaSimulation -SimulationEnabled=true -SimulationNumBlocks=1000 \ + -SimulationVerbose=true -SimulationCommit=true -SimulationSeed=$seed -v -timeout 24h > $file +} + +i=0 +pids=() +for seed in ${seeds[@]}; do + sim $seed & + pids[${i}]=$! + i=$(($i+1)) + sleep 0.1 # start in order, nicer logs +done + +echo "Simulation processes spawned, waiting for completion..." + +code=0 + +i=0 +for pid in ${pids[*]}; do + wait $pid + last=$? + if [ $last -ne 0 ]; then + seed=${seeds[${i}]} + echo "Simulation with seed $seed failed!" + code=1 + fi + i=$(($i+1)) +done + +exit $code From cb86efa05407145c4687730ba6725e491d724ad7 Mon Sep 17 00:00:00 2001 From: Dev Ojha <ValarDragon@users.noreply.github.com> Date: Wed, 26 Sep 2018 11:34:01 -0700 Subject: [PATCH 24/26] Merge PR #2376: auth: Don't recalculate mempool fees for every msg signer, misc. cleanup This PR begins improving the godocs for the auth module, and begins cleaning up the Ante handler. Additionally we previously calculated if the fee was sufficient for the tx on every single signer. This is now refactored to be more efficient, and have a better logical flow. No changelog entry as this is new to this release. --- CHANGELOG.md | 2 + PENDING.md | 1 + cmd/gaia/app/benchmarks/txsize_test.go | 2 +- docs/sdk/core/app3.md | 5 +- x/auth/account.go | 18 ++- x/auth/ante.go | 199 ++++++++++++++----------- x/auth/stdtx.go | 9 +- x/auth/stdtx_test.go | 2 +- 8 files changed, 135 insertions(+), 103 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 479f5913a8..bb5d1e639a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -109,6 +109,8 @@ IMPROVEMENTS - [baseapp] Allow any alphanumeric character in route - [tools] Remove `rm -rf vendor/` from `make get_vendor_deps` - [x/auth] Recover ErrorOutOfGas panic in order to set sdk.Result attributes correctly + - [x/auth] \#2376 No longer runs any signature in a multi-msg, if any account/sequence number is wrong. + - [x/auth] \#2376 No longer charge gas for subtracting fees - [x/bank] Unit tests are now table-driven - [tests] Add tests to example apps in docs - [tests] Fixes ansible scripts to work with AWS too diff --git a/PENDING.md b/PENDING.md index 844b7c7d84..fafb495fd9 100644 --- a/PENDING.md +++ b/PENDING.md @@ -100,6 +100,7 @@ FEATURES * [simulation] [\#1924](https://github.com/cosmos/cosmos-sdk/issues/1924) allow operations to specify future operations * [simulation] [\#1924](https://github.com/cosmos/cosmos-sdk/issues/1924) Add benchmarking capabilities, with makefile commands "test_sim_gaia_benchmark, test_sim_gaia_profile" * [simulation] [\#2349](https://github.com/cosmos/cosmos-sdk/issues/2349) Add time-based future scheduled operations to simulator + * [x/auth] \#2376 Remove FeePayer() from StdTx * [x/stake] [\#1672](https://github.com/cosmos/cosmos-sdk/issues/1672) Implement basis for the validator commission model. diff --git a/cmd/gaia/app/benchmarks/txsize_test.go b/cmd/gaia/app/benchmarks/txsize_test.go index 83e51c0416..186ad87e58 100644 --- a/cmd/gaia/app/benchmarks/txsize_test.go +++ b/cmd/gaia/app/benchmarks/txsize_test.go @@ -26,7 +26,7 @@ func ExampleTxSendSize() { Outputs: []bank.Output{bank.NewOutput(addr2, coins)}, } sig, _ := priv1.Sign(msg1.GetSignBytes()) - sigs := []auth.StdSignature{auth.StdSignature{nil, sig, 0, 0}} + sigs := []auth.StdSignature{{nil, sig, 0, 0}} tx := auth.NewStdTx([]sdk.Msg{msg1}, auth.NewStdFee(0, coins...), sigs, "") fmt.Println(len(cdc.MustMarshalBinaryBare([]sdk.Msg{msg1}))) fmt.Println(len(cdc.MustMarshalBinaryBare(tx))) diff --git a/docs/sdk/core/app3.md b/docs/sdk/core/app3.md index b84313adc5..595a3694fa 100644 --- a/docs/sdk/core/app3.md +++ b/docs/sdk/core/app3.md @@ -133,7 +133,7 @@ Now that we have a native model for accounts, it's time to introduce the native ```go // StdTx is a standard way to wrap a Msg with Fee and Signatures. -// NOTE: the first signature is the FeePayer (Signatures must not be nil). +// NOTE: the first signature is the fee payer (Signatures must not be nil). type StdTx struct { Msgs []sdk.Msg `json:"msg"` Fee StdFee `json:"fee"` @@ -259,8 +259,7 @@ the same message could be executed over and over again. The PubKey is required for signature verification, but it is only required in the StdSignature once. From that point on, it will be stored in the account. -The fee is paid by the first address returned by `msg.GetSigners()` for the first `Msg`, -as provided by the `FeePayer(tx Tx) sdk.AccAddress` function. +The fee is paid by the first address returned by `msg.GetSigners()` for the first `Msg`. ## CoinKeeper diff --git a/x/auth/account.go b/x/auth/account.go index ceadd39510..4a55a48ea3 100644 --- a/x/auth/account.go +++ b/x/auth/account.go @@ -8,8 +8,12 @@ import ( "github.com/tendermint/tendermint/crypto" ) -// Account is a standard account using a sequence number for replay protection -// and a pubkey for authentication. +// Account is an interface used to store coins at a given address within state. +// It presumes a notion of sequence numbers for replay protection, +// a notion of account numbers for replay protection for previously pruned accounts, +// and a pubkey for authentication purposes. +// +// Many complex conditions can be used in the concrete struct which implements Account. type Account interface { GetAddress() sdk.AccAddress SetAddress(sdk.AccAddress) error // errors if already set. @@ -35,9 +39,11 @@ type AccountDecoder func(accountBytes []byte) (Account, error) var _ Account = (*BaseAccount)(nil) -// BaseAccount - base account structure. -// Extend this by embedding this in your AppAccount. -// See the examples/basecoin/types/account.go for an example. +// BaseAccount - a base account structure. +// This can be extended by embedding within in your AppAccount. +// There are examples of this in: examples/basecoin/types/account.go. +// However one doesn't have to use BaseAccount as long as your struct +// implements Account. type BaseAccount struct { Address sdk.AccAddress `json:"address"` Coins sdk.Coins `json:"coins"` @@ -118,7 +124,7 @@ func (acc *BaseAccount) SetSequence(seq int64) error { //---------------------------------------- // Wire -// Most users shouldn't use this, but this comes handy for tests. +// Most users shouldn't use this, but this comes in handy for tests. func RegisterBaseAccount(cdc *codec.Codec) { cdc.RegisterInterface((*Account)(nil), nil) cdc.RegisterConcrete(&BaseAccount{}, "cosmos-sdk/BaseAccount", nil) diff --git a/x/auth/ante.go b/x/auth/ante.go index f423c9465c..68f6f65408 100644 --- a/x/auth/ante.go +++ b/x/auth/ante.go @@ -12,19 +12,18 @@ import ( ) const ( - deductFeesCost sdk.Gas = 10 - memoCostPerByte sdk.Gas = 1 - ed25519VerifyCost = 59 - secp256k1VerifyCost = 100 - maxMemoCharacters = 100 - feeDeductionGasFactor = 0.001 + memoCostPerByte sdk.Gas = 1 + ed25519VerifyCost = 59 + secp256k1VerifyCost = 100 + maxMemoCharacters = 100 + // how much gas = 1 atom + gasPerUnitCost = 1000 ) // NewAnteHandler returns an AnteHandler that checks // and increments sequence numbers, checks signatures & account numbers, // and deducts fees from the first signer. func NewAnteHandler(am AccountMapper, fck FeeCollectionKeeper) sdk.AnteHandler { - return func( ctx sdk.Context, tx sdk.Tx, simulate bool, ) (newCtx sdk.Context, res sdk.Result, abort bool) { @@ -35,13 +34,17 @@ func NewAnteHandler(am AccountMapper, fck FeeCollectionKeeper) sdk.AnteHandler { return ctx, sdk.ErrInternal("tx must be StdTx").Result(), true } - // set the gas meter - if simulate { - newCtx = ctx.WithGasMeter(sdk.NewInfiniteGasMeter()) - } else { - newCtx = ctx.WithGasMeter(sdk.NewGasMeter(stdTx.Fee.Gas)) + // Ensure that the provided fees meet a minimum threshold for the validator, if this is a CheckTx. + // This is only for local mempool purposes, and thus is only ran on check tx. + if ctx.IsCheckTx() && !simulate { + res := ensureSufficientMempoolFees(ctx, stdTx) + if !res.IsOK() { + return newCtx, res, true + } } + newCtx = setGasMeter(simulate, ctx, stdTx) + // AnteHandlers must have their own defer/recover in order // for the BaseApp to know how much gas was used! // This is because the GasMeter is created in the AnteHandler, @@ -65,64 +68,49 @@ func NewAnteHandler(am AccountMapper, fck FeeCollectionKeeper) sdk.AnteHandler { if err != nil { return newCtx, err.Result(), true } - - sigs := stdTx.GetSignatures() // When simulating, this would just be a 0-length slice. - signerAddrs := stdTx.GetSigners() - msgs := tx.GetMsgs() - // charge gas for the memo newCtx.GasMeter().ConsumeGas(memoCostPerByte*sdk.Gas(len(stdTx.GetMemo())), "memo") - // Get the sign bytes (requires all account & sequence numbers and the fee) - sequences := make([]int64, len(sigs)) - accNums := make([]int64, len(sigs)) - for i := 0; i < len(sigs); i++ { - sequences[i] = sigs[i].Sequence - accNums[i] = sigs[i].AccountNumber + // stdSigs contains the sequence number, account number, and signatures + stdSigs := stdTx.GetSignatures() // When simulating, this would just be a 0-length slice. + signerAddrs := stdTx.GetSigners() + + // create the list of all sign bytes + signBytesList := getSignBytesList(newCtx.ChainID(), stdTx, stdSigs) + signerAccs, res := getSignerAccs(newCtx, am, signerAddrs) + if !res.IsOK() { + return newCtx, res, true + } + res = validateAccNumAndSequence(signerAccs, stdSigs) + if !res.IsOK() { + return newCtx, res, true } - fee := stdTx.Fee - // Check sig and nonce and collect signer accounts. - var signerAccs = make([]Account, len(signerAddrs)) - for i := 0; i < len(sigs); i++ { - signerAddr, sig := signerAddrs[i], sigs[i] + // first sig pays the fees + if !stdTx.Fee.Amount.IsZero() { + // signerAccs[0] is the fee payer + signerAccs[0], res = deductFees(signerAccs[0], stdTx.Fee) + if !res.IsOK() { + return newCtx, res, true + } + fck.addCollectedFees(newCtx, stdTx.Fee.Amount) + } + for i := 0; i < len(stdSigs); i++ { // check signature, return account with incremented nonce - signBytes := StdSignBytes(newCtx.ChainID(), accNums[i], sequences[i], fee, msgs, stdTx.GetMemo()) - signerAcc, res := processSig(newCtx, am, signerAddr, sig, signBytes, simulate) + signerAccs[i], res = processSig(newCtx, signerAccs[i], stdSigs[i], signBytesList[i], simulate) if !res.IsOK() { return newCtx, res, true } - requiredFees := adjustFeesByGas(ctx.MinimumFees(), fee.Gas) - // fees must be greater than the minimum set by the validator adjusted by gas - if ctx.IsCheckTx() && !simulate && !ctx.MinimumFees().IsZero() && fee.Amount.IsLT(requiredFees) { - // validators reject any tx from the mempool with less than the minimum fee per gas * gas factor - return newCtx, sdk.ErrInsufficientFee(fmt.Sprintf( - "insufficient fee, got: %q required: %q", fee.Amount, requiredFees)).Result(), true - } - - // first sig pays the fees - // Can this function be moved outside of the loop? - if i == 0 && !fee.Amount.IsZero() { - newCtx.GasMeter().ConsumeGas(deductFeesCost, "deductFees") - signerAcc, res = deductFees(signerAcc, fee) - if !res.IsOK() { - return newCtx, res, true - } - fck.addCollectedFees(newCtx, fee.Amount) - } - // Save the account. - am.SetAccount(newCtx, signerAcc) - signerAccs[i] = signerAcc + am.SetAccount(newCtx, signerAccs[i]) } // cache the signer accounts in the context newCtx = WithSigners(newCtx, signerAccs) // TODO: tx tags (?) - return newCtx, sdk.Result{GasWanted: stdTx.Fee.Gas}, false // continue... } } @@ -150,42 +138,45 @@ func validateBasic(tx StdTx) (err sdk.Error) { return nil } +func getSignerAccs(ctx sdk.Context, am AccountMapper, addrs []sdk.AccAddress) (accs []Account, res sdk.Result) { + accs = make([]Account, len(addrs)) + for i := 0; i < len(accs); i++ { + accs[i] = am.GetAccount(ctx, addrs[i]) + if accs[i] == nil { + return nil, sdk.ErrUnknownAddress(addrs[i].String()).Result() + } + } + return +} + +func validateAccNumAndSequence(accs []Account, sigs []StdSignature) sdk.Result { + for i := 0; i < len(accs); i++ { + accnum := accs[i].GetAccountNumber() + seq := accs[i].GetSequence() + // Check account number. + if accnum != sigs[i].AccountNumber { + return sdk.ErrInvalidSequence( + fmt.Sprintf("Invalid account number. Got %d, expected %d", sigs[i].AccountNumber, accnum)).Result() + } + + // Check sequence number. + if seq != sigs[i].Sequence { + return sdk.ErrInvalidSequence( + fmt.Sprintf("Invalid sequence. Got %d, expected %d", sigs[i].Sequence, seq)).Result() + } + } + return sdk.Result{} +} + // verify the signature and increment the sequence. // if the account doesn't have a pubkey, set it. -func processSig( - ctx sdk.Context, am AccountMapper, - addr sdk.AccAddress, sig StdSignature, signBytes []byte, simulate bool) ( - acc Account, res sdk.Result) { - // Get the account. - acc = am.GetAccount(ctx, addr) - if acc == nil { - return nil, sdk.ErrUnknownAddress(addr.String()).Result() - } - - accnum := acc.GetAccountNumber() - seq := acc.GetSequence() - - // Check account number. - if accnum != sig.AccountNumber { - return nil, sdk.ErrInvalidSequence( - fmt.Sprintf("Invalid account number. Got %d, expected %d", sig.AccountNumber, accnum)).Result() - } - - // Check sequence number. - if seq != sig.Sequence { - return nil, sdk.ErrInvalidSequence( - fmt.Sprintf("Invalid sequence. Got %d, expected %d", sig.Sequence, seq)).Result() - } - err := acc.SetSequence(seq + 1) - if err != nil { - // Handle w/ #870 - panic(err) - } +func processSig(ctx sdk.Context, + acc Account, sig StdSignature, signBytes []byte, simulate bool) (updatedAcc Account, res sdk.Result) { pubKey, res := processPubKey(acc, sig, simulate) if !res.IsOK() { return nil, res } - err = acc.SetPubKey(pubKey) + err := acc.SetPubKey(pubKey) if err != nil { return nil, sdk.ErrInternal("setting PubKey on signer's account").Result() } @@ -195,7 +186,14 @@ func processSig( return nil, sdk.ErrUnauthorized("signature verification failed").Result() } - return + // increment the sequence number + err = acc.SetSequence(acc.GetSequence() + 1) + if err != nil { + // Handle w/ #870 + panic(err) + } + + return acc, res } var dummySecp256k1Pubkey secp256k1.PubKeySecp256k1 @@ -244,8 +242,9 @@ func consumeSignatureVerificationGas(meter sdk.GasMeter, pubkey crypto.PubKey) { } func adjustFeesByGas(fees sdk.Coins, gas int64) sdk.Coins { - gasCost := int64(float64(gas) * feeDeductionGasFactor) + gasCost := gas / gasPerUnitCost gasFees := make(sdk.Coins, len(fees)) + // TODO: Make this not price all coins in the same way for i := 0; i < len(fees); i++ { gasFees[i] = sdk.NewInt64Coin(fees[i].Denom, gasCost) } @@ -272,5 +271,37 @@ func deductFees(acc Account, fee StdFee) (Account, sdk.Result) { return acc, sdk.Result{} } +func ensureSufficientMempoolFees(ctx sdk.Context, stdTx StdTx) sdk.Result { + // currently we use a very primitive gas pricing model with a constant gasPrice. + // adjustFeesByGas handles calculating the amount of fees required based on the provided gas. + // TODO: Make the gasPrice not a constant, and account for tx size. + requiredFees := adjustFeesByGas(ctx.MinimumFees(), stdTx.Fee.Gas) + + if !ctx.MinimumFees().IsZero() && stdTx.Fee.Amount.IsLT(requiredFees) { + // validators reject any tx from the mempool with less than the minimum fee per gas * gas factor + return sdk.ErrInsufficientFee(fmt.Sprintf( + "insufficient fee, got: %q required: %q", stdTx.Fee.Amount, requiredFees)).Result() + } + return sdk.Result{} +} + +func setGasMeter(simulate bool, ctx sdk.Context, stdTx StdTx) sdk.Context { + // set the gas meter + if simulate { + return ctx.WithGasMeter(sdk.NewInfiniteGasMeter()) + } + return ctx.WithGasMeter(sdk.NewGasMeter(stdTx.Fee.Gas)) +} + +func getSignBytesList(chainID string, stdTx StdTx, stdSigs []StdSignature) (signatureBytesList [][]byte) { + signatureBytesList = make([][]byte, len(stdSigs)) + for i := 0; i < len(stdSigs); i++ { + signatureBytesList[i] = StdSignBytes(chainID, + stdSigs[i].AccountNumber, stdSigs[i].Sequence, + stdTx.Fee, stdTx.Msgs, stdTx.Memo) + } + return +} + // BurnFeeHandler burns all fees (decreasing total supply) func BurnFeeHandler(_ sdk.Context, _ sdk.Tx, _ sdk.Coins) {} diff --git a/x/auth/stdtx.go b/x/auth/stdtx.go index 0c0a133ed2..ca6eb7b422 100644 --- a/x/auth/stdtx.go +++ b/x/auth/stdtx.go @@ -11,7 +11,7 @@ import ( var _ sdk.Tx = (*StdTx)(nil) // StdTx is a standard way to wrap a Msg with Fee and Signatures. -// NOTE: the first signature is the FeePayer (Signatures must not be nil). +// NOTE: the first signature is the fee payer (Signatures must not be nil). type StdTx struct { Msgs []sdk.Msg `json:"msg"` Fee StdFee `json:"fee"` @@ -63,13 +63,6 @@ func (tx StdTx) GetMemo() string { return tx.Memo } // .Empty(). func (tx StdTx) GetSignatures() []StdSignature { return tx.Signatures } -// FeePayer returns the address responsible for paying the fees -// for the transactions. It's the first address returned by msg.GetSigners(). -// If GetSigners() is empty, this panics. -func FeePayer(tx sdk.Tx) sdk.AccAddress { - return tx.GetMsgs()[0].GetSigners()[0] -} - //__________________________________________________________ // StdFee includes the amount of coins paid in fees and the maximum diff --git a/x/auth/stdtx_test.go b/x/auth/stdtx_test.go index 8d9172a923..26bd792455 100644 --- a/x/auth/stdtx_test.go +++ b/x/auth/stdtx_test.go @@ -23,7 +23,7 @@ func TestStdTx(t *testing.T) { require.Equal(t, msgs, tx.GetMsgs()) require.Equal(t, sigs, tx.GetSignatures()) - feePayer := FeePayer(tx) + feePayer := tx.GetSigners()[0] require.Equal(t, addr, feePayer) } From 7d5bc459fa77aa84041fd7b9375874282aa6341f Mon Sep 17 00:00:00 2001 From: Dev Ojha <ValarDragon@users.noreply.github.com> Date: Wed, 26 Sep 2018 14:04:07 -0700 Subject: [PATCH 25/26] Merge PR #2413: Remove reflection from handlers --- docs/sdk/sdk-by-examples/simple-governance/module-handler.md | 2 +- examples/democoin/x/cool/handler.go | 3 +-- examples/democoin/x/pow/handler.go | 4 +--- x/bank/handler.go | 4 +--- x/ibc/handler.go | 4 +--- 5 files changed, 5 insertions(+), 12 deletions(-) diff --git a/docs/sdk/sdk-by-examples/simple-governance/module-handler.md b/docs/sdk/sdk-by-examples/simple-governance/module-handler.md index fc142e0fa7..a879c31741 100644 --- a/docs/sdk/sdk-by-examples/simple-governance/module-handler.md +++ b/docs/sdk/sdk-by-examples/simple-governance/module-handler.md @@ -17,7 +17,7 @@ func NewHandler(k Keeper) sdk.Handler { case VoteMsg: return handleVoteMsg(ctx, k, msg) default: - errMsg := "Unrecognized gov Msg type: " + reflect.TypeOf(msg).Name() + errMsg := "Unrecognized gov Msg type: " + msg.Name() return sdk.ErrUnknownRequest(errMsg).Result() } } diff --git a/examples/democoin/x/cool/handler.go b/examples/democoin/x/cool/handler.go index 33aa9bef43..a4c6ce7be6 100644 --- a/examples/democoin/x/cool/handler.go +++ b/examples/democoin/x/cool/handler.go @@ -2,7 +2,6 @@ package cool import ( "fmt" - "reflect" sdk "github.com/cosmos/cosmos-sdk/types" ) @@ -26,7 +25,7 @@ func NewHandler(k Keeper) sdk.Handler { case MsgQuiz: return handleMsgQuiz(ctx, k, msg) default: - errMsg := fmt.Sprintf("Unrecognized cool Msg type: %v", reflect.TypeOf(msg).Name()) + errMsg := fmt.Sprintf("Unrecognized cool Msg type: %v", msg.Name()) return sdk.ErrUnknownRequest(errMsg).Result() } } diff --git a/examples/democoin/x/pow/handler.go b/examples/democoin/x/pow/handler.go index e4fa39d1da..246246e96e 100644 --- a/examples/democoin/x/pow/handler.go +++ b/examples/democoin/x/pow/handler.go @@ -1,8 +1,6 @@ package pow import ( - "reflect" - sdk "github.com/cosmos/cosmos-sdk/types" ) @@ -12,7 +10,7 @@ func (pk Keeper) Handler(ctx sdk.Context, msg sdk.Msg) sdk.Result { case MsgMine: return handleMsgMine(ctx, pk, msg) default: - errMsg := "Unrecognized pow Msg type: " + reflect.TypeOf(msg).Name() + errMsg := "Unrecognized pow Msg type: " + msg.Name() return sdk.ErrUnknownRequest(errMsg).Result() } } diff --git a/x/bank/handler.go b/x/bank/handler.go index ec56d05b42..cd60ac8351 100644 --- a/x/bank/handler.go +++ b/x/bank/handler.go @@ -1,8 +1,6 @@ package bank import ( - "reflect" - sdk "github.com/cosmos/cosmos-sdk/types" ) @@ -15,7 +13,7 @@ func NewHandler(k Keeper) sdk.Handler { case MsgIssue: return handleMsgIssue(ctx, k, msg) default: - errMsg := "Unrecognized bank Msg type: " + reflect.TypeOf(msg).Name() + errMsg := "Unrecognized bank Msg type: %s" + msg.Name() return sdk.ErrUnknownRequest(errMsg).Result() } } diff --git a/x/ibc/handler.go b/x/ibc/handler.go index 1f334166bf..4a9eebaebc 100644 --- a/x/ibc/handler.go +++ b/x/ibc/handler.go @@ -1,8 +1,6 @@ package ibc import ( - "reflect" - sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/x/bank" ) @@ -15,7 +13,7 @@ func NewHandler(ibcm Mapper, ck bank.Keeper) sdk.Handler { case IBCReceiveMsg: return handleIBCReceiveMsg(ctx, ibcm, ck, msg) default: - errMsg := "Unrecognized IBC Msg type: " + reflect.TypeOf(msg).Name() + errMsg := "Unrecognized IBC Msg type: " + msg.Name() return sdk.ErrUnknownRequest(errMsg).Result() } } From e7134e8742f6fea87f0a540230b1fe1862bca42d Mon Sep 17 00:00:00 2001 From: Zach <zach.ramsay@gmail.com> Date: Thu, 27 Sep 2018 04:52:24 -0400 Subject: [PATCH 26/26] Merge PR #2414: docs: add version --- docs/DOCS_README.md | 3 ++- docs/README.md | 4 ++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/docs/DOCS_README.md b/docs/DOCS_README.md index 30cb5d9bcb..38fa30c4f8 100644 --- a/docs/DOCS_README.md +++ b/docs/DOCS_README.md @@ -20,7 +20,8 @@ a private website repository has make targets consumed by a standard Jenkins tas ## README The [README.md](./README.md) is also the landing page for the documentation -on the website. +on the website. During the Jenkins build, the current commit is added to the bottom +of the README. ## Config.js diff --git a/docs/README.md b/docs/README.md index e721ad1a1b..48b8c69d5c 100644 --- a/docs/README.md +++ b/docs/README.md @@ -12,3 +12,7 @@ Cosmos can interoperate with multiple other applications and cryptocurrencies. B See [this file](./DOCS_README.md) for details of the build process and considerations when making changes. + +## Version + +This documentation is built from the following commit: