From 86f07d271db98b17f8f5a0aebba7195d2385cae7 Mon Sep 17 00:00:00 2001 From: rigelrozanski Date: Fri, 24 Aug 2018 21:47:31 -0400 Subject: [PATCH 01/52] unbonding validators, delegtion effects --- x/stake/keeper/delegation.go | 47 ++++++++++++++++++++++++++- x/stake/keeper/slash.go | 37 ++++++++++++++++----- x/stake/keeper/validator.go | 62 +++++++++++++++++++++--------------- x/stake/types/validator.go | 45 ++++++++++++-------------- 4 files changed, 132 insertions(+), 59 deletions(-) diff --git a/x/stake/keeper/delegation.go b/x/stake/keeper/delegation.go index 2bad79c20f..107665a9b5 100644 --- a/x/stake/keeper/delegation.go +++ b/x/stake/keeper/delegation.go @@ -2,6 +2,7 @@ package keeper import ( "bytes" + "time" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/x/stake/types" @@ -311,6 +312,32 @@ func (k Keeper) unbond(ctx sdk.Context, delegatorAddr, validatorAddr sdk.AccAddr //______________________________________________________________________________________________________ +// get info for begin functions: MinTime and CreationHeight +func (k Keeper) getBeginInfo(ctx sdk.Context, params types.Params, validatorSrcAddr sdk.AccAddress) ( + minTime time.Time, height int64, completeNow bool) { + + validator, found := k.GetValidator(ctx, validatorSrcAddr) + switch { + case !found || validator.Status == sdk.Bonded: + + // longest wait - just unbonding period from now + minTime = ctx.BlockHeader().Time.Add(params.UnbondingTime) + height = ctx.BlockHeader().Height + return minTime, height, false + + case validator.Status == sdk.Unbonding: + minTime = validator.UnbondingMinTime + height = validator.UnbondingHeight + return minTime, height, false + + case validator.Status == sdk.Unbonded: + return minTime, height, true + + default: + panic("unknown validator status") + } +} + // complete unbonding an unbonding record func (k Keeper) BeginUnbonding(ctx sdk.Context, delegatorAddr, validatorAddr sdk.AccAddress, sharesAmount sdk.Dec) sdk.Error { @@ -327,12 +354,22 @@ func (k Keeper) BeginUnbonding(ctx sdk.Context, delegatorAddr, validatorAddr sdk // create the unbonding delegation params := k.GetParams(ctx) - minTime := ctx.BlockHeader().Time.Add(params.UnbondingTime) + minTime, height, completeNow := k.getBeginInfo(ctx, params, validatorAddr) balance := sdk.Coin{params.BondDenom, returnAmount.RoundInt()} + // no need to create the ubd object just complete now + if completeNow { + _, _, err := k.coinKeeper.AddCoins(ctx, delegatorAddr, sdk.Coins{balance}) + if err != nil { + return err + } + return nil + } + ubd := types.UnbondingDelegation{ DelegatorAddr: delegatorAddr, ValidatorAddr: validatorAddr, + CreationHeight: height, MinTime: minTime, Balance: balance, InitialBalance: balance, @@ -390,11 +427,19 @@ func (k Keeper) BeginRedelegation(ctx sdk.Context, delegatorAddr, validatorSrcAd // create the unbonding delegation minTime := ctx.BlockHeader().Time.Add(params.UnbondingTime) + height := ctx.BlockHeader().Height + + minTime, height, completeNow := k.getBeginInfo(ctx, params, validatorSrcAddr) + + if completeNow { // no need to create the redelegation object + return nil + } red := types.Redelegation{ DelegatorAddr: delegatorAddr, ValidatorSrcAddr: validatorSrcAddr, ValidatorDstAddr: validatorDstAddr, + CreationHeight: height, MinTime: minTime, SharesDst: sharesCreated, SharesSrc: sharesAmount, diff --git a/x/stake/keeper/slash.go b/x/stake/keeper/slash.go index aa6fe974d8..b936e585d0 100644 --- a/x/stake/keeper/slash.go +++ b/x/stake/keeper/slash.go @@ -43,6 +43,26 @@ func (k Keeper) Slash(ctx sdk.Context, pubkey crypto.PubKey, infractionHeight in pubkey.Address())) return } + + // do not slash if unbonded + unbonded := false + if validator.Status == sdk.Unbonded { + unbonded = true + } else if validator.Status == sdk.Unbonding { + ctxTime := ctx.BlockHeader().Time + if ctxTime.After(validator.UnbondingMinTime) { + + // TODO should we also just update the + // validator status to unbonded here? + unbonded = true + } + } + if unbonded { + logger.Info(fmt.Sprintf( + "failed attempt to slash an unbonded validator")) + return + } + operatorAddress := validator.GetOperator() // Track remaining slash amount for the validator @@ -91,17 +111,16 @@ func (k Keeper) Slash(ctx sdk.Context, pubkey crypto.PubKey, infractionHeight in // Cannot decrease balance below zero tokensToBurn := sdk.MinDec(remainingSlashAmount, validator.Tokens) - // Get the current pool + // burn validator's tokens pool := k.GetPool(ctx) - // remove tokens from the validator validator, pool = validator.RemoveTokens(pool, tokensToBurn) - // burn tokens pool.LooseTokens = pool.LooseTokens.Sub(tokensToBurn) - // update the pool k.SetPool(ctx, pool) + // update the validator, possibly kicking it out validator = k.UpdateValidator(ctx, validator) - // remove validator if it has been reduced to zero shares + + // remove validator if it has no more tokens if validator.Tokens.IsZero() { k.RemoveValidator(ctx, validator.Operator) } @@ -134,12 +153,12 @@ func (k Keeper) Unjail(ctx sdk.Context, pubkey crypto.PubKey) { } // set the jailed flag on a validator -func (k Keeper) setJailed(ctx sdk.Context, pubkey crypto.PubKey, jailed bool) { +func (k Keeper) setJailed(ctx sdk.Context, pubkey crypto.PubKey, isJailed bool) { validator, found := k.GetValidatorByPubKey(ctx, pubkey) if !found { - panic(fmt.Errorf("Validator with pubkey %s not found, cannot set jailed to %v", pubkey, jailed)) + panic(fmt.Errorf("Validator with pubkey %s not found, cannot set jailed to %v", pubkey, isJailed)) } - validator.Jailed = jailed + validator.Jailed = isJailed k.UpdateValidator(ctx, validator) // update validator, possibly unbonding or bonding it return } @@ -179,6 +198,7 @@ func (k Keeper) slashUnbondingDelegation(ctx sdk.Context, unbondingDelegation ty unbondingDelegation.Balance.Amount = unbondingDelegation.Balance.Amount.Sub(unbondingSlashAmount) k.SetUnbondingDelegation(ctx, unbondingDelegation) pool := k.GetPool(ctx) + // Burn loose tokens // Ref https://github.com/cosmos/cosmos-sdk/pull/1278#discussion_r198657760 pool.LooseTokens = pool.LooseTokens.Sub(slashAmount) @@ -239,6 +259,7 @@ func (k Keeper) slashRedelegation(ctx sdk.Context, validator types.Validator, re if err != nil { panic(fmt.Errorf("error unbonding delegator: %v", err)) } + // Burn loose tokens pool := k.GetPool(ctx) pool.LooseTokens = pool.LooseTokens.Sub(tokensToBurn) diff --git a/x/stake/keeper/validator.go b/x/stake/keeper/validator.go index e933a6d236..72567576be 100644 --- a/x/stake/keeper/validator.go +++ b/x/stake/keeper/validator.go @@ -212,6 +212,7 @@ func (k Keeper) UpdateValidator(ctx sdk.Context, validator types.Validator) type cliffPower := k.GetCliffValidatorPower(ctx) switch { + // if the validator is already bonded and the power is increasing, we need // perform the following: // a) update Tendermint @@ -240,10 +241,11 @@ func (k Keeper) UpdateValidator(ctx sdk.Context, validator types.Validator) type bytes.Compare(valPower, cliffPower) == -1: //(valPower < cliffPower // skip to completion - // default case - validator was either: + default: + // default case - validator was either: // a) not-bonded and now has power-rank greater than cliff validator // b) bonded and now has decreased in power - default: + // update the validator set for this validator updatedVal, updated := k.UpdateBondedValidators(ctx, validator) if updated { @@ -307,10 +309,13 @@ func (k Keeper) updateCliffValidator(ctx sdk.Context, affectedVal types.Validato newCliffValRank := GetValidatorsByPowerIndexKey(newCliffVal, pool) if bytes.Equal(affectedVal.Operator, newCliffVal.Operator) { + // The affected validator remains the cliff validator, however, since // the store does not contain the new power, update the new power rank. store.Set(ValidatorPowerCliffKey, affectedValRank) + } else if bytes.Compare(affectedValRank, newCliffValRank) > 0 { + // The affected validator no longer remains the cliff validator as it's // power is greater than the new cliff validator. k.setCliffValidator(ctx, newCliffVal, pool) @@ -321,7 +326,7 @@ func (k Keeper) updateCliffValidator(ctx sdk.Context, affectedVal types.Validato func (k Keeper) updateForJailing(ctx sdk.Context, oldFound bool, oldValidator, newValidator types.Validator) types.Validator { if newValidator.Jailed && oldFound && oldValidator.Status == sdk.Bonded { - newValidator = k.unbondValidator(ctx, newValidator) + newValidator = k.beginUnbondingValidator(ctx, newValidator) // need to also clear the cliff validator spot because the jail has // opened up a new spot which will be filled when @@ -416,20 +421,20 @@ func (k Keeper) UpdateBondedValidators( } } - // increment bondedValidatorsCount / get the validator to bond - if !validator.Jailed { - if validator.Status != sdk.Bonded { - validatorToBond = validator - if newValidatorBonded { - panic("already decided to bond a validator, can't bond another!") - } - newValidatorBonded = true - } - } else { - // TODO: document why we must break here. + // if we've reached jailed validators no further bonded validators exist + if validator.Jailed { break } + // increment bondedValidatorsCount / get the validator to bond + if validator.Status != sdk.Bonded { + validatorToBond = validator + if newValidatorBonded { + panic("already decided to bond a validator, can't bond another!") + } + newValidatorBonded = true + } + // increment the total number of bonded validators and potentially mark // the validator to bond if validator.Status != sdk.Bonded { @@ -464,13 +469,15 @@ func (k Keeper) UpdateBondedValidators( } if bytes.Equal(validatorToBond.Operator, affectedValidator.Operator) { - // unbond the old cliff validator iff the affected validator was - // newly bonded and has greater power - k.unbondValidator(ctx, oldCliffVal) + + // begin unbonding the old cliff validator iff the affected + // validator was newly bonded and has greater power + k.beginUnbondingValidator(ctx, oldCliffVal) } else { - // otherwise unbond the affected validator, which must have been - // kicked out - affectedValidator = k.unbondValidator(ctx, affectedValidator) + + // otherwise begin unbonding the affected validator, which must + // have been kicked out + affectedValidator = k.beginUnbondingValidator(ctx, affectedValidator) } } @@ -563,25 +570,30 @@ func kickOutValidators(k Keeper, ctx sdk.Context, toKickOut map[string]byte) { if !found { panic(fmt.Sprintf("validator record not found for address: %v\n", ownerAddr)) } - k.unbondValidator(ctx, validator) + k.beginUnbondingValidator(ctx, validator) } } // perform all the store operations for when a validator status becomes unbonded -func (k Keeper) unbondValidator(ctx sdk.Context, validator types.Validator) types.Validator { +func (k Keeper) beginUnbondingValidator(ctx sdk.Context, validator types.Validator) types.Validator { store := ctx.KVStore(k.storeKey) pool := k.GetPool(ctx) + params := k.GetParams(ctx) // sanity check - if validator.Status == sdk.Unbonded { - panic(fmt.Sprintf("should not already be unbonded, validator: %v\n", validator)) + if validator.Status == sdk.Unbonded || + validator.Status == sdk.Unbonding { + panic(fmt.Sprintf("should not already be unbonded or unbonding, validator: %v\n", validator)) } // set the status - validator, pool = validator.UpdateStatus(pool, sdk.Unbonded) + validator, pool = validator.UpdateStatus(pool, sdk.Unbonding) k.SetPool(ctx, pool) + validator.UnbondingMinTime = ctx.BlockHeader().Time.Add(params.UnbondingTime) + validator.UnbondingHeight = ctx.BlockHeader().Height + // save the now unbonded validator record k.SetValidator(ctx, validator) diff --git a/x/stake/types/validator.go b/x/stake/types/validator.go index f8404b5963..db9c8afd5d 100644 --- a/x/stake/types/validator.go +++ b/x/stake/types/validator.go @@ -3,6 +3,7 @@ package types import ( "bytes" "fmt" + "time" abci "github.com/tendermint/tendermint/abci/types" "github.com/tendermint/tendermint/crypto" @@ -31,15 +32,14 @@ type Validator struct { Description Description `json:"description"` // description terms for the validator BondHeight int64 `json:"bond_height"` // earliest height as a bonded validator BondIntraTxCounter int16 `json:"bond_intra_tx_counter"` // block-local tx index of validator change - ProposerRewardPool sdk.Coins `json:"proposer_reward_pool"` // XXX reward pool collected from being the proposer + + 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) - - // fee related - LastBondedTokens sdk.Dec `json:"prev_bonded_tokens"` // Previous bonded tokens held } // NewValidator - initialize a new validator @@ -54,12 +54,12 @@ func NewValidator(operator sdk.AccAddress, pubKey crypto.PubKey, description Des Description: description, BondHeight: int64(0), BondIntraTxCounter: int16(0), - ProposerRewardPool: sdk.Coins{}, + UnbondingHeight: int64(0), + UnbondingMinTime: time.Unix(0, 0), Commission: sdk.ZeroDec(), CommissionMax: sdk.ZeroDec(), CommissionChangeRate: sdk.ZeroDec(), CommissionChangeToday: sdk.ZeroDec(), - LastBondedTokens: sdk.ZeroDec(), } } @@ -73,12 +73,12 @@ type validatorValue struct { Description Description BondHeight int64 BondIntraTxCounter int16 - ProposerRewardPool sdk.Coins + UnbondingHeight int64 + UnbondingMinTime time.Time Commission sdk.Dec CommissionMax sdk.Dec CommissionChangeRate sdk.Dec CommissionChangeToday sdk.Dec - LastBondedTokens sdk.Dec } // return the redelegation without fields contained within the key for the store @@ -92,12 +92,12 @@ func MustMarshalValidator(cdc *wire.Codec, validator Validator) []byte { Description: validator.Description, BondHeight: validator.BondHeight, BondIntraTxCounter: validator.BondIntraTxCounter, - ProposerRewardPool: validator.ProposerRewardPool, + UnbondingHeight: validator.UnbondingHeight, + UnbondingMinTime: validator.UnbondingMinTime, Commission: validator.Commission, CommissionMax: validator.CommissionMax, CommissionChangeRate: validator.CommissionChangeRate, CommissionChangeToday: validator.CommissionChangeToday, - LastBondedTokens: validator.LastBondedTokens, } return cdc.MustMarshalBinary(val) } @@ -108,7 +108,6 @@ func MustUnmarshalValidator(cdc *wire.Codec, operatorAddr, value []byte) Validat if err != nil { panic(err) } - return validator } @@ -134,12 +133,12 @@ func UnmarshalValidator(cdc *wire.Codec, operatorAddr, value []byte) (validator Description: storeValue.Description, BondHeight: storeValue.BondHeight, BondIntraTxCounter: storeValue.BondIntraTxCounter, - ProposerRewardPool: storeValue.ProposerRewardPool, + UnbondingHeight: storeValue.UnbondingHeight, + UnbondingMinTime: storeValue.UnbondingMinTime, Commission: storeValue.Commission, CommissionMax: storeValue.CommissionMax, CommissionChangeRate: storeValue.CommissionChangeRate, CommissionChangeToday: storeValue.CommissionChangeToday, - LastBondedTokens: storeValue.LastBondedTokens, }, nil } @@ -161,12 +160,12 @@ func (v Validator) HumanReadableString() (string, error) { resp += fmt.Sprintf("Delegator Shares: %s\n", v.DelegatorShares.String()) resp += fmt.Sprintf("Description: %s\n", v.Description) resp += fmt.Sprintf("Bond Height: %d\n", v.BondHeight) - resp += fmt.Sprintf("Proposer Reward Pool: %s\n", v.ProposerRewardPool.String()) + resp += fmt.Sprintf("Unbonding Height: %d\n", v.UnbondingHeight) + resp += fmt.Sprintf("Minimum Unbonding Time: %d\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("Previous Bonded Tokens: %s\n", v.LastBondedTokens.String()) return resp, nil } @@ -186,15 +185,14 @@ type BechValidator struct { Description Description `json:"description"` // description terms for the validator BondHeight int64 `json:"bond_height"` // earliest height as a bonded validator BondIntraTxCounter int16 `json:"bond_intra_tx_counter"` // block-local tx index of validator change - ProposerRewardPool sdk.Coins `json:"proposer_reward_pool"` // XXX reward pool collected from being the proposer + + 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) - - // fee related - LastBondedTokens sdk.Dec `json:"prev_bonded_shares"` // last bonded token amount } // get the bech validator from the the regular validator @@ -216,14 +214,13 @@ func (v Validator) Bech32Validator() (BechValidator, error) { Description: v.Description, BondHeight: v.BondHeight, BondIntraTxCounter: v.BondIntraTxCounter, - ProposerRewardPool: v.ProposerRewardPool, + UnbondingHeight: v.UnbondingHeight, + UnbondingMinTime: v.UnbondingMinTime, Commission: v.Commission, CommissionMax: v.CommissionMax, CommissionChangeRate: v.CommissionChangeRate, CommissionChangeToday: v.CommissionChangeToday, - - LastBondedTokens: v.LastBondedTokens, }, nil } @@ -238,12 +235,10 @@ func (v Validator) Equal(c2 Validator) bool { v.Tokens.Equal(c2.Tokens) && v.DelegatorShares.Equal(c2.DelegatorShares) && v.Description == c2.Description && - v.ProposerRewardPool.IsEqual(c2.ProposerRewardPool) && v.Commission.Equal(c2.Commission) && v.CommissionMax.Equal(c2.CommissionMax) && v.CommissionChangeRate.Equal(c2.CommissionChangeRate) && - v.CommissionChangeToday.Equal(c2.CommissionChangeToday) && - v.LastBondedTokens.Equal(c2.LastBondedTokens) + v.CommissionChangeToday.Equal(c2.CommissionChangeToday) } // constant used in flags to indicate that description field should not be updated From ec42e6650156b4f768e138231df5971a1d6b284e Mon Sep 17 00:00:00 2001 From: rigelrozanski Date: Mon, 27 Aug 2018 00:20:40 -0400 Subject: [PATCH 02/52] fix old tests --- x/slashing/keeper_test.go | 6 +++--- x/slashing/tick_test.go | 2 +- x/stake/handler_test.go | 4 ++-- x/stake/keeper/validator_test.go | 14 ++++++++------ x/stake/types/validator.go | 2 +- 5 files changed, 15 insertions(+), 13 deletions(-) diff --git a/x/slashing/keeper_test.go b/x/slashing/keeper_test.go index 808c82014e..6e9765ab2b 100644 --- a/x/slashing/keeper_test.go +++ b/x/slashing/keeper_test.go @@ -114,7 +114,7 @@ func TestHandleAbsentValidator(t *testing.T) { // validator should have been jailed validator, _ = sk.GetValidatorByPubKey(ctx, val) - require.Equal(t, sdk.Unbonded, validator.GetStatus()) + require.Equal(t, sdk.Unbonding, validator.GetStatus()) // unrevocation should fail prior to jail expiration got = slh(ctx, NewMsgUnjail(addr)) @@ -161,7 +161,7 @@ func TestHandleAbsentValidator(t *testing.T) { keeper.handleValidatorSignature(ctx, val.Address(), amtInt, false) } validator, _ = sk.GetValidatorByPubKey(ctx, val) - require.Equal(t, sdk.Unbonded, validator.GetStatus()) + require.Equal(t, sdk.Unbonding, validator.GetStatus()) } // Test a new validator entering the validator set @@ -230,7 +230,7 @@ func TestHandleAlreadyJailed(t *testing.T) { // validator should have been jailed and slashed validator, _ := sk.GetValidatorByPubKey(ctx, val) - require.Equal(t, sdk.Unbonded, validator.GetStatus()) + require.Equal(t, sdk.Unbonding, validator.GetStatus()) // validator should have been slashed require.Equal(t, int64(amtInt-1), validator.GetTokens().RoundInt64()) diff --git a/x/slashing/tick_test.go b/x/slashing/tick_test.go index 78d2695494..9a8eaf4ffc 100644 --- a/x/slashing/tick_test.go +++ b/x/slashing/tick_test.go @@ -80,5 +80,5 @@ func TestBeginBlocker(t *testing.T) { // validator should be jailed validator, found := sk.GetValidatorByPubKey(ctx, pk) require.True(t, found) - require.Equal(t, sdk.Unbonded, validator.GetStatus()) + require.Equal(t, sdk.Unbonding, validator.GetStatus()) } diff --git a/x/stake/handler_test.go b/x/stake/handler_test.go index 6ab54e48c2..f0fce9dcb4 100644 --- a/x/stake/handler_test.go +++ b/x/stake/handler_test.go @@ -85,8 +85,8 @@ func TestValidatorByPowerIndex(t *testing.T) { keeper.Jail(ctx, keep.PKs[0]) validator, found = keeper.GetValidator(ctx, validatorAddr) require.True(t, found) - require.Equal(t, sdk.Unbonded, validator.Status) // ensure is unbonded - require.Equal(t, int64(500000), validator.Tokens.RoundInt64()) // ensure is unbonded + require.Equal(t, sdk.Unbonding, validator.Status) // ensure is unbonding + require.Equal(t, int64(500000), validator.Tokens.RoundInt64()) // ensure tokens slashed // the old power record should have been deleted as the power changed require.False(t, keep.ValidatorByPowerIndexExists(ctx, keeper, power)) diff --git a/x/stake/keeper/validator_test.go b/x/stake/keeper/validator_test.go index 54149707e8..dee8223660 100644 --- a/x/stake/keeper/validator_test.go +++ b/x/stake/keeper/validator_test.go @@ -137,7 +137,7 @@ func TestUpdateBondedValidatorsDecreaseCliff(t *testing.T) { expectedValStatus := map[int]sdk.BondStatus{ 9: sdk.Bonded, 8: sdk.Bonded, 7: sdk.Bonded, 5: sdk.Bonded, 4: sdk.Bonded, - 0: sdk.Unbonded, 1: sdk.Unbonded, 2: sdk.Unbonded, 3: sdk.Unbonded, 6: sdk.Unbonded, + 0: sdk.Unbonding, 1: sdk.Unbonding, 2: sdk.Unbonding, 3: sdk.Unbonding, 6: sdk.Unbonding, } // require all the validators have their respective statuses @@ -145,9 +145,11 @@ func TestUpdateBondedValidatorsDecreaseCliff(t *testing.T) { valAddr := validators[valIdx].Operator val, _ := keeper.GetValidator(ctx, valAddr) - require.Equal( - t, val.GetStatus(), status, - fmt.Sprintf("expected validator to have status: %s", sdk.BondStatusToString(status))) + assert.Equal( + t, status, val.GetStatus(), + fmt.Sprintf("expected validator at index %v to have status: %s", + valIdx, + sdk.BondStatusToString(status))) } } @@ -610,8 +612,8 @@ func TestFullValidatorSetPowerChange(t *testing.T) { validators[i], found = keeper.GetValidator(ctx, validators[i].Operator) require.True(t, found) } - assert.Equal(t, sdk.Unbonded, validators[0].Status) - assert.Equal(t, sdk.Unbonded, validators[1].Status) + assert.Equal(t, sdk.Unbonding, validators[0].Status) + assert.Equal(t, sdk.Unbonding, validators[1].Status) assert.Equal(t, sdk.Bonded, validators[2].Status) assert.Equal(t, sdk.Bonded, validators[3].Status) assert.Equal(t, sdk.Unbonded, validators[4].Status) diff --git a/x/stake/types/validator.go b/x/stake/types/validator.go index db9c8afd5d..c8868a56ca 100644 --- a/x/stake/types/validator.go +++ b/x/stake/types/validator.go @@ -161,7 +161,7 @@ func (v Validator) HumanReadableString() (string, error) { 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: %d\n", v.UnbondingMinTime) + 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()) From 527320c9fc2308aacd029b1e368d439de3a64804 Mon Sep 17 00:00:00 2001 From: rigelrozanski Date: Mon, 27 Aug 2018 00:38:31 -0400 Subject: [PATCH 03/52] dummy tests --- x/stake/keeper/delegation_test.go | 16 ++++++++++++++ x/stake/keeper/slash_test.go | 26 +++++++++++++++++++---- x/stake/simulation/invariants.go | 1 + x/stake/simulation/msgs.go | 35 ++++++++++++++++++++++++------- 4 files changed, 67 insertions(+), 11 deletions(-) diff --git a/x/stake/keeper/delegation_test.go b/x/stake/keeper/delegation_test.go index 4333a74942..32594654af 100644 --- a/x/stake/keeper/delegation_test.go +++ b/x/stake/keeper/delegation_test.go @@ -180,6 +180,14 @@ func TestUnbondDelegation(t *testing.T) { require.Equal(t, int64(4), pool.BondedTokens.RoundInt64()) } +func TestUndelegateFromUnbondingValidator(t *testing.T) { + require.Fail(t) +} + +func TestUndelegateFromUnbondedValidator(t *testing.T) { + require.Fail(t) +} + // Make sure that that the retrieving the delegations doesn't affect the state func TestGetRedelegationsFromValidator(t *testing.T) { ctx, _, keeper := CreateTestInput(t, false, 0) @@ -259,3 +267,11 @@ func TestRedelegation(t *testing.T) { _, found = keeper.GetRedelegation(ctx, addrDels[0], addrVals[0], addrVals[1]) require.False(t, found) } + +func TestRedelegationFromUnbondingValidator(t *testing.T) { + require.Fail(t) +} + +func TestRedelegationFromUnbondedValidator(t *testing.T) { + require.Fail(t) +} diff --git a/x/stake/keeper/slash_test.go b/x/stake/keeper/slash_test.go index 156f0602e9..5eb7c5e35e 100644 --- a/x/stake/keeper/slash_test.go +++ b/x/stake/keeper/slash_test.go @@ -11,8 +11,8 @@ import ( abci "github.com/tendermint/tendermint/abci/types" ) -// setup helper function -// creates two validators +// 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) @@ -34,8 +34,11 @@ func setupHelper(t *testing.T, amt int64) (sdk.Context, Keeper, types.Params) { return ctx, keeper, params } +//_________________________________________________________________________________ + // tests Jail, Unjail func TestRevocation(t *testing.T) { + // setup ctx, keeper, _ := setupHelper(t, 10) addr := addrVals[0] @@ -57,7 +60,6 @@ func TestRevocation(t *testing.T) { val, found = keeper.GetValidator(ctx, addr) require.True(t, found) require.False(t, val.GetJailed()) - } // tests slashUnbondingDelegation @@ -95,8 +97,10 @@ func TestSlashUnbondingDelegation(t *testing.T) { require.Equal(t, int64(5), slashAmount.RoundInt64()) ubd, found := keeper.GetUnbondingDelegation(ctx, addrDels[0], addrVals[0]) require.True(t, found) + // initialbalance unchanged require.Equal(t, sdk.NewInt64Coin(params.BondDenom, 10), ubd.InitialBalance) + // balance decreased require.Equal(t, sdk.NewInt64Coin(params.BondDenom, 5), ubd.Balance) newPool := keeper.GetPool(ctx) @@ -155,14 +159,18 @@ func TestSlashRedelegation(t *testing.T) { require.Equal(t, int64(5), slashAmount.RoundInt64()) rd, found = keeper.GetRedelegation(ctx, addrDels[0], addrVals[0], addrVals[1]) require.True(t, found) + // initialbalance unchanged require.Equal(t, sdk.NewInt64Coin(params.BondDenom, 10), rd.InitialBalance) + // balance decreased require.Equal(t, sdk.NewInt64Coin(params.BondDenom, 5), rd.Balance) + // shares decreased del, found = keeper.GetDelegation(ctx, addrDels[0], addrVals[1]) require.True(t, found) require.Equal(t, int64(5), del.Shares.RoundInt64()) + // pool bonded tokens decreased newPool := keeper.GetPool(ctx) require.Equal(t, int64(5), oldPool.BondedTokens.Sub(newPool.BondedTokens).RoundInt64()) @@ -177,7 +185,7 @@ func TestSlashAtFutureHeight(t *testing.T) { } // tests Slash at the current height -func TestSlashAtCurrentHeight(t *testing.T) { +func TestSlashValidatorAtCurrentHeight(t *testing.T) { ctx, keeper, _ := setupHelper(t, 10) pk := PKs[0] fraction := sdk.NewDecWithPrec(5, 1) @@ -198,6 +206,16 @@ func TestSlashAtCurrentHeight(t *testing.T) { require.Equal(t, sdk.NewDec(5).RoundInt64(), oldPool.BondedTokens.Sub(newPool.BondedTokens).RoundInt64()) } +// slash an unbonding validator +func TestSlashUnbondingValidator(t *testing.T) { + require.Fail(t) +} + +// fail an attept to slash an unbonded validator +func TestSlashUnbondedValidator(t *testing.T) { + require.Fail(t) +} + // tests Slash at a previous height with an unbonding delegation func TestSlashWithUnbondingDelegation(t *testing.T) { ctx, keeper, params := setupHelper(t, 10) diff --git a/x/stake/simulation/invariants.go b/x/stake/simulation/invariants.go index 1e961d05de..859bb05915 100644 --- a/x/stake/simulation/invariants.go +++ b/x/stake/simulation/invariants.go @@ -45,6 +45,7 @@ func SupplyInvariants(ck bank.Keeper, k stake.Keeper, am auth.AccountMapper) sim case sdk.Bonded: bonded = bonded.Add(validator.GetPower()) case sdk.Unbonding: + loose = loose.Add(validator.GetTokens().RoundInt()) case sdk.Unbonded: loose = loose.Add(validator.GetTokens().RoundInt()) } diff --git a/x/stake/simulation/msgs.go b/x/stake/simulation/msgs.go index 17f9808931..f2b37769a0 100644 --- a/x/stake/simulation/msgs.go +++ b/x/stake/simulation/msgs.go @@ -19,7 +19,10 @@ import ( // SimulateMsgCreateValidator func SimulateMsgCreateValidator(m auth.AccountMapper, k stake.Keeper) simulation.TestAndRunTx { - return func(t *testing.T, r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, keys []crypto.PrivKey, log string, event func(string)) (action string, err sdk.Error) { + + return func(t *testing.T, r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, + keys []crypto.PrivKey, log string, event func(string)) (action string, err sdk.Error) { + denom := k.GetParams(ctx).BondDenom description := stake.Description{ Moniker: simulation.RandStringOfLength(r, 10), @@ -56,7 +59,10 @@ func SimulateMsgCreateValidator(m auth.AccountMapper, k stake.Keeper) simulation // SimulateMsgEditValidator func SimulateMsgEditValidator(k stake.Keeper) simulation.TestAndRunTx { - return func(t *testing.T, r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, keys []crypto.PrivKey, log string, event func(string)) (action string, err sdk.Error) { + + return func(t *testing.T, r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, + keys []crypto.PrivKey, log string, event func(string)) (action string, err sdk.Error) { + description := stake.Description{ Moniker: simulation.RandStringOfLength(r, 10), Identity: simulation.RandStringOfLength(r, 10), @@ -84,7 +90,10 @@ func SimulateMsgEditValidator(k stake.Keeper) simulation.TestAndRunTx { // SimulateMsgDelegate func SimulateMsgDelegate(m auth.AccountMapper, k stake.Keeper) simulation.TestAndRunTx { - return func(t *testing.T, r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, keys []crypto.PrivKey, log string, event func(string)) (action string, err sdk.Error) { + + return func(t *testing.T, r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, + keys []crypto.PrivKey, log string, event func(string)) (action string, err sdk.Error) { + denom := k.GetParams(ctx).BondDenom validatorKey := simulation.RandomKey(r, keys) validatorAddress := sdk.AccAddress(validatorKey.PubKey().Address()) @@ -116,7 +125,10 @@ func SimulateMsgDelegate(m auth.AccountMapper, k stake.Keeper) simulation.TestAn // SimulateMsgBeginUnbonding func SimulateMsgBeginUnbonding(m auth.AccountMapper, k stake.Keeper) simulation.TestAndRunTx { - return func(t *testing.T, r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, keys []crypto.PrivKey, log string, event func(string)) (action string, err sdk.Error) { + + return func(t *testing.T, r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, + keys []crypto.PrivKey, log string, event func(string)) (action string, err sdk.Error) { + denom := k.GetParams(ctx).BondDenom validatorKey := simulation.RandomKey(r, keys) validatorAddress := sdk.AccAddress(validatorKey.PubKey().Address()) @@ -148,7 +160,10 @@ func SimulateMsgBeginUnbonding(m auth.AccountMapper, k stake.Keeper) simulation. // SimulateMsgCompleteUnbonding func SimulateMsgCompleteUnbonding(k stake.Keeper) simulation.TestAndRunTx { - return func(t *testing.T, r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, keys []crypto.PrivKey, log string, event func(string)) (action string, err sdk.Error) { + + return func(t *testing.T, r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, + keys []crypto.PrivKey, log string, event func(string)) (action string, err sdk.Error) { + validatorKey := simulation.RandomKey(r, keys) validatorAddress := sdk.AccAddress(validatorKey.PubKey().Address()) delegatorKey := simulation.RandomKey(r, keys) @@ -171,7 +186,10 @@ func SimulateMsgCompleteUnbonding(k stake.Keeper) simulation.TestAndRunTx { // SimulateMsgBeginRedelegate func SimulateMsgBeginRedelegate(m auth.AccountMapper, k stake.Keeper) simulation.TestAndRunTx { - return func(t *testing.T, r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, keys []crypto.PrivKey, log string, event func(string)) (action string, err sdk.Error) { + + return func(t *testing.T, r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, + keys []crypto.PrivKey, log string, event func(string)) (action string, err sdk.Error) { + denom := k.GetParams(ctx).BondDenom sourceValidatorKey := simulation.RandomKey(r, keys) sourceValidatorAddress := sdk.AccAddress(sourceValidatorKey.PubKey().Address()) @@ -207,7 +225,10 @@ func SimulateMsgBeginRedelegate(m auth.AccountMapper, k stake.Keeper) simulation // SimulateMsgCompleteRedelegate func SimulateMsgCompleteRedelegate(k stake.Keeper) simulation.TestAndRunTx { - return func(t *testing.T, r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, keys []crypto.PrivKey, log string, event func(string)) (action string, err sdk.Error) { + + return func(t *testing.T, r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, + keys []crypto.PrivKey, log string, event func(string)) (action string, err sdk.Error) { + validatorSrcKey := simulation.RandomKey(r, keys) validatorSrcAddress := sdk.AccAddress(validatorSrcKey.PubKey().Address()) validatorDstKey := simulation.RandomKey(r, keys) From f2d47f9e9187e99b5e87d78145848205593add0b Mon Sep 17 00:00:00 2001 From: rigelrozanski Date: Mon, 27 Aug 2018 14:46:38 -0400 Subject: [PATCH 04/52] slash contract, cwgoes comment --- x/stake/keeper/slash.go | 17 +++++------------ 1 file changed, 5 insertions(+), 12 deletions(-) diff --git a/x/stake/keeper/slash.go b/x/stake/keeper/slash.go index b936e585d0..c3c79714ee 100644 --- a/x/stake/keeper/slash.go +++ b/x/stake/keeper/slash.go @@ -18,6 +18,8 @@ import ( // Infraction committed equal to or less than an unbonding period in the past, // so all unbonding delegations and redelegations from that height are stored // CONTRACT: +// Slash will not slash unbonded validators (for the above reason) +// CONTRACT: // Infraction committed at the current height or at a past height, // not at a height in the future func (k Keeper) Slash(ctx sdk.Context, pubkey crypto.PubKey, infractionHeight int64, power int64, slashFactor sdk.Dec) { @@ -44,24 +46,15 @@ func (k Keeper) Slash(ctx sdk.Context, pubkey crypto.PubKey, infractionHeight in return } - // do not slash if unbonded - unbonded := false + // slashing should not be slashing unbonded if validator.Status == sdk.Unbonded { - unbonded = true + panic(fmt.Sprintf("should not be slashing unbonded validator: %v", validator)) } else if validator.Status == sdk.Unbonding { ctxTime := ctx.BlockHeader().Time if ctxTime.After(validator.UnbondingMinTime) { - - // TODO should we also just update the - // validator status to unbonded here? - unbonded = true + panic(fmt.Sprintf("should not be slashing unbonded validator: %v", validator)) } } - if unbonded { - logger.Info(fmt.Sprintf( - "failed attempt to slash an unbonded validator")) - return - } operatorAddress := validator.GetOperator() From d1a2fed2bcec1f14829a6afb4e4ab6aff2d1d161 Mon Sep 17 00:00:00 2001 From: rigelrozanski Date: Wed, 29 Aug 2018 02:28:27 -0400 Subject: [PATCH 05/52] tests working --- x/stake/keeper/delegation_test.go | 188 ++++++++++++++++++++++++++++-- x/stake/keeper/slash_test.go | 10 -- 2 files changed, 181 insertions(+), 17 deletions(-) diff --git a/x/stake/keeper/delegation_test.go b/x/stake/keeper/delegation_test.go index df324beab4..add16fb52c 100644 --- a/x/stake/keeper/delegation_test.go +++ b/x/stake/keeper/delegation_test.go @@ -7,6 +7,7 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/x/stake/types" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -162,9 +163,7 @@ func TestUnbondDelegation(t *testing.T) { } keeper.SetDelegation(ctx, delegation) - var err error - var amount sdk.Dec - amount, err = keeper.unbond(ctx, addrDels[0], addrVals[0], sdk.NewDec(6)) + amount, err := keeper.unbond(ctx, addrDels[0], addrVals[0], sdk.NewDec(6)) require.NoError(t, err) require.Equal(t, int64(6), amount.RoundInt64()) // shares to be added to an unbonding delegation / redelegation @@ -180,12 +179,187 @@ func TestUnbondDelegation(t *testing.T) { require.Equal(t, int64(4), pool.BondedTokens.RoundInt64()) } +// test removing all self delegation from a validator which should +// shift it from the bonded to unbonded state +func TestUndelegateSelfDelegation(t *testing.T) { + + ctx, _, keeper := CreateTestInput(t, false, 0) + pool := keeper.GetPool(ctx) + pool.LooseTokens = sdk.NewDec(20) + + //create a validator with a self-delegation + validator := types.NewValidator(addrVals[0], PKs[0], types.Description{}) + validator, pool, issuedShares := validator.AddTokensFromDel(pool, sdk.NewInt(10)) + require.Equal(t, int64(10), issuedShares.RoundInt64()) + keeper.SetPool(ctx, pool) + validator = keeper.UpdateValidator(ctx, validator) + pool = keeper.GetPool(ctx) + selfDelegation := types.Delegation{ + DelegatorAddr: addrVals[0], + ValidatorAddr: addrVals[0], + Shares: issuedShares, + } + keeper.SetDelegation(ctx, selfDelegation) + + // create a second delegation to this validator + validator, pool, issuedShares = validator.AddTokensFromDel(pool, sdk.NewInt(10)) + require.Equal(t, int64(10), issuedShares.RoundInt64()) + keeper.SetPool(ctx, pool) + validator = keeper.UpdateValidator(ctx, validator) + pool = keeper.GetPool(ctx) + delegation := types.Delegation{ + DelegatorAddr: addrDels[0], + ValidatorAddr: addrVals[0], + Shares: issuedShares, + } + keeper.SetDelegation(ctx, delegation) + + err := keeper.BeginUnbonding(ctx, addrVals[0], addrVals[0], sdk.NewDec(10)) + require.NoError(t, err) + + validator, found := keeper.GetValidator(ctx, addrVals[0]) + require.True(t, found) + pool = keeper.GetPool(ctx) + + require.Equal(t, int64(10), validator.Tokens.RoundInt64()) + require.Equal(t, sdk.Unbonding, validator.Status) +} + func TestUndelegateFromUnbondingValidator(t *testing.T) { - require.Fail(t) + ctx, _, keeper := CreateTestInput(t, false, 0) + pool := keeper.GetPool(ctx) + pool.LooseTokens = sdk.NewDec(20) + + //create a validator with a self-delegation + validator := types.NewValidator(addrVals[0], PKs[0], types.Description{}) + + validator, pool, issuedShares := validator.AddTokensFromDel(pool, sdk.NewInt(10)) + require.Equal(t, int64(10), issuedShares.RoundInt64()) + keeper.SetPool(ctx, pool) + validator = keeper.UpdateValidator(ctx, validator) + pool = keeper.GetPool(ctx) + selfDelegation := types.Delegation{ + DelegatorAddr: addrVals[0], + ValidatorAddr: addrVals[0], + Shares: issuedShares, + } + keeper.SetDelegation(ctx, selfDelegation) + + // create a second delegation to this validator + validator, pool, issuedShares = validator.AddTokensFromDel(pool, sdk.NewInt(10)) + require.Equal(t, int64(10), issuedShares.RoundInt64()) + keeper.SetPool(ctx, pool) + validator = keeper.UpdateValidator(ctx, validator) + pool = keeper.GetPool(ctx) + delegation := types.Delegation{ + DelegatorAddr: addrDels[0], + ValidatorAddr: addrVals[0], + Shares: issuedShares, + } + keeper.SetDelegation(ctx, delegation) + + header := ctx.BlockHeader() + blockHeight := int64(10) + header.Height = blockHeight + blockTime := time.Unix(333, 0) + header.Time = blockTime + ctx = ctx.WithBlockHeader(header) + + // unbond the all self-delegation to put validator in unbonding state + err := keeper.BeginUnbonding(ctx, addrVals[0], addrVals[0], sdk.NewDec(10)) + require.NoError(t, err) + + validator, found := keeper.GetValidator(ctx, addrVals[0]) + require.True(t, found) + require.Equal(t, blockHeight, validator.UnbondingHeight) + params := keeper.GetParams(ctx) + require.True(t, blockTime.Add(params.UnbondingTime).Equal(validator.UnbondingMinTime)) + + //change the context + header = ctx.BlockHeader() + blockHeight2 := int64(20) + header.Height = blockHeight2 + blockTime2 := time.Unix(444, 0) + header.Time = blockTime2 + ctx = ctx.WithBlockHeader(header) + + // unbond some of the other delegation's shares + err = keeper.BeginUnbonding(ctx, addrDels[0], addrVals[0], sdk.NewDec(6)) + require.NoError(t, err) + + // retrieve the unbonding delegation + ubd, found := keeper.GetUnbondingDelegation(ctx, addrDels[0], addrVals[0]) + require.True(t, found) + require.True(t, ubd.Balance.IsEqual(sdk.NewInt64Coin(params.BondDenom, 6))) + assert.Equal(t, blockHeight, ubd.CreationHeight) + assert.True(t, blockTime.Add(params.UnbondingTime).Equal(ubd.MinTime)) } func TestUndelegateFromUnbondedValidator(t *testing.T) { - require.Fail(t) + ctx, _, keeper := CreateTestInput(t, false, 0) + pool := keeper.GetPool(ctx) + pool.LooseTokens = sdk.NewDec(20) + + //create a validator with a self-delegation + validator := types.NewValidator(addrVals[0], PKs[0], types.Description{}) + + validator, pool, issuedShares := validator.AddTokensFromDel(pool, sdk.NewInt(10)) + require.Equal(t, int64(10), issuedShares.RoundInt64()) + keeper.SetPool(ctx, pool) + validator = keeper.UpdateValidator(ctx, validator) + pool = keeper.GetPool(ctx) + selfDelegation := types.Delegation{ + DelegatorAddr: addrVals[0], + ValidatorAddr: addrVals[0], + Shares: issuedShares, + } + keeper.SetDelegation(ctx, selfDelegation) + + // create a second delegation to this validator + validator, pool, issuedShares = validator.AddTokensFromDel(pool, sdk.NewInt(10)) + require.Equal(t, int64(10), issuedShares.RoundInt64()) + keeper.SetPool(ctx, pool) + validator = keeper.UpdateValidator(ctx, validator) + pool = keeper.GetPool(ctx) + delegation := types.Delegation{ + DelegatorAddr: addrDels[0], + ValidatorAddr: addrVals[0], + Shares: issuedShares, + } + keeper.SetDelegation(ctx, delegation) + + header := ctx.BlockHeader() + blockHeight := int64(10) + header.Height = blockHeight + blockTime := time.Unix(333, 0) + header.Time = blockTime + ctx = ctx.WithBlockHeader(header) + + // unbond the all self-delegation to put validator in unbonding state + err := keeper.BeginUnbonding(ctx, addrVals[0], addrVals[0], sdk.NewDec(10)) + require.NoError(t, err) + + validator, found := keeper.GetValidator(ctx, addrVals[0]) + require.True(t, found) + require.Equal(t, blockHeight, validator.UnbondingHeight) + params := keeper.GetParams(ctx) + require.True(t, blockTime.Add(params.UnbondingTime).Equal(validator.UnbondingMinTime)) + + // change the context to one which makes the validator considered unbonded + header = ctx.BlockHeader() + blockHeight2 := int64(20) + header.Height = blockHeight2 + blockTime2 := time.Unix(444, 0).Add(params.UnbondingTime) + header.Time = blockTime2 + ctx = ctx.WithBlockHeader(header) + + // unbond some of the other delegation's shares + err = keeper.BeginUnbonding(ctx, addrDels[0], addrVals[0], sdk.NewDec(6)) + require.NoError(t, err) + + // no ubd should have been found, coins should have been returned direcly to account + _, found = keeper.GetUnbondingDelegation(ctx, addrDels[0], addrVals[0]) + require.False(t, found) } // Make sure that that the retrieving the delegations doesn't affect the state @@ -269,9 +443,9 @@ func TestRedelegation(t *testing.T) { } func TestRedelegationFromUnbondingValidator(t *testing.T) { - require.Fail(t) + require.Fail(t, "") } func TestRedelegationFromUnbondedValidator(t *testing.T) { - require.Fail(t) + require.Fail(t, "") } diff --git a/x/stake/keeper/slash_test.go b/x/stake/keeper/slash_test.go index c53d4e1ec2..65bac2d808 100644 --- a/x/stake/keeper/slash_test.go +++ b/x/stake/keeper/slash_test.go @@ -206,16 +206,6 @@ func TestSlashValidatorAtCurrentHeight(t *testing.T) { require.Equal(t, sdk.NewDec(5).RoundInt64(), oldPool.BondedTokens.Sub(newPool.BondedTokens).RoundInt64()) } -// slash an unbonding validator -func TestSlashUnbondingValidator(t *testing.T) { - require.Fail(t) -} - -// fail an attept to slash an unbonded validator -func TestSlashUnbondedValidator(t *testing.T) { - require.Fail(t) -} - // tests Slash at a previous height with an unbonding delegation func TestSlashWithUnbondingDelegation(t *testing.T) { ctx, keeper, params := setupHelper(t, 10) From 52b249b0cdf6267541d6b7ccd7ab726336f8c363 Mon Sep 17 00:00:00 2001 From: rigelrozanski Date: Wed, 29 Aug 2018 18:44:40 -0400 Subject: [PATCH 06/52] complete tests --- x/stake/keeper/delegation.go | 39 +++++- x/stake/keeper/delegation_test.go | 205 +++++++++++++++++++++++++++++- x/stake/keeper/slash.go | 9 +- x/stake/types/validator.go | 13 ++ 4 files changed, 248 insertions(+), 18 deletions(-) diff --git a/x/stake/keeper/delegation.go b/x/stake/keeper/delegation.go index 88260cb7d5..7f66cd5550 100644 --- a/x/stake/keeper/delegation.go +++ b/x/stake/keeper/delegation.go @@ -2,6 +2,7 @@ package keeper import ( "bytes" + "fmt" "time" sdk "github.com/cosmos/cosmos-sdk/types" @@ -256,44 +257,56 @@ func (k Keeper) Delegate(ctx sdk.Context, delegatorAddr sdk.AccAddress, bondAmt // unbond the the delegation return func (k Keeper) unbond(ctx sdk.Context, delegatorAddr, validatorAddr sdk.AccAddress, shares sdk.Dec) (amount sdk.Dec, err sdk.Error) { + fmt.Println("wackydebugoutput unbond 0") // check if delegation has any shares in it unbond delegation, found := k.GetDelegation(ctx, delegatorAddr, validatorAddr) if !found { + fmt.Println("wackydebugoutput unbond 1") err = types.ErrNoDelegatorForAddress(k.Codespace()) return } + fmt.Println("wackydebugoutput unbond 2") // retrieve the amount to remove if delegation.Shares.LT(shares) { + fmt.Println("wackydebugoutput unbond 3") err = types.ErrNotEnoughDelegationShares(k.Codespace(), delegation.Shares.String()) return } + fmt.Println("wackydebugoutput unbond 4") // get validator validator, found := k.GetValidator(ctx, validatorAddr) if !found { + fmt.Println("wackydebugoutput unbond 5") err = types.ErrNoValidatorFound(k.Codespace()) return } + fmt.Println("wackydebugoutput unbond 6") // subtract shares from delegator delegation.Shares = delegation.Shares.Sub(shares) // remove the delegation if delegation.Shares.IsZero() { + fmt.Println("wackydebugoutput unbond 7") // if the delegation is the operator of the validator then // trigger a jail validator if bytes.Equal(delegation.DelegatorAddr, validator.Operator) && validator.Jailed == false { + fmt.Println("wackydebugoutput unbond 8") validator.Jailed = true } + fmt.Println("wackydebugoutput unbond 9") k.RemoveDelegation(ctx, delegation) } else { + fmt.Println("wackydebugoutput unbond 10") // Update height delegation.Height = ctx.BlockHeight() k.SetDelegation(ctx, delegation) } + fmt.Println("wackydebugoutput unbond 11") // remove the coins from the validator pool := k.GetPool(ctx) @@ -303,9 +316,12 @@ func (k Keeper) unbond(ctx sdk.Context, delegatorAddr, validatorAddr sdk.AccAddr // update then remove validator if necessary validator = k.UpdateValidator(ctx, validator) + fmt.Printf("debug validator: %v\n", validator) if validator.DelegatorShares.IsZero() { + fmt.Println("wackydebugoutput unbond 12") k.RemoveValidator(ctx, validator.Operator) } + fmt.Println("wackydebugoutput unbond 13") return } @@ -315,24 +331,25 @@ func (k Keeper) unbond(ctx sdk.Context, delegatorAddr, validatorAddr sdk.AccAddr // get info for begin functions: MinTime and CreationHeight func (k Keeper) getBeginInfo(ctx sdk.Context, params types.Params, validatorSrcAddr sdk.AccAddress) ( minTime time.Time, height int64, completeNow bool) { + fmt.Println("wackydebugoutput getBeginInfo 0") validator, found := k.GetValidator(ctx, validatorSrcAddr) switch { case !found || validator.Status == sdk.Bonded: - // longest wait - just unbonding period from now + // the longest wait - just unbonding period from now minTime = ctx.BlockHeader().Time.Add(params.UnbondingTime) height = ctx.BlockHeader().Height return minTime, height, false + case validator.IsUnbonded(ctx): + return minTime, height, true + case validator.Status == sdk.Unbonding: minTime = validator.UnbondingMinTime height = validator.UnbondingHeight return minTime, height, false - case validator.Status == sdk.Unbonded: - return minTime, height, true - default: panic("unknown validator status") } @@ -403,27 +420,38 @@ func (k Keeper) CompleteUnbonding(ctx sdk.Context, delegatorAddr, validatorAddr // complete unbonding an unbonding record func (k Keeper) BeginRedelegation(ctx sdk.Context, delegatorAddr, validatorSrcAddr, validatorDstAddr sdk.AccAddress, sharesAmount sdk.Dec) sdk.Error { + fmt.Println("wackydebugoutput BeginRedelegation 0") // check if this is a transitive redelegation if k.HasReceivingRedelegation(ctx, delegatorAddr, validatorSrcAddr) { + fmt.Println("wackydebugoutput BeginRedelegation 1") return types.ErrTransitiveRedelegation(k.Codespace()) } + fmt.Println("wackydebugoutput BeginRedelegation 2") returnAmount, err := k.unbond(ctx, delegatorAddr, validatorSrcAddr, sharesAmount) if err != nil { + fmt.Println("wackydebugoutput BeginRedelegation 3") return err } + fmt.Println("wackydebugoutput BeginRedelegation 4") params := k.GetParams(ctx) returnCoin := sdk.Coin{params.BondDenom, returnAmount.RoundInt()} + fmt.Printf("debug returnCoin: %v\n", returnCoin) + fmt.Println("wackydebugoutput BeginRedelegation 5") dstValidator, found := k.GetValidator(ctx, validatorDstAddr) if !found { + fmt.Println("wackydebugoutput BeginRedelegation 6") return types.ErrBadRedelegationDst(k.Codespace()) } + fmt.Println("wackydebugoutput BeginRedelegation 7") sharesCreated, err := k.Delegate(ctx, delegatorAddr, returnCoin, dstValidator, false) if err != nil { + fmt.Println("wackydebugoutput BeginRedelegation 8") return err } + fmt.Println("wackydebugoutput BeginRedelegation 9") // create the unbonding delegation minTime := ctx.BlockHeader().Time.Add(params.UnbondingTime) @@ -432,8 +460,10 @@ func (k Keeper) BeginRedelegation(ctx sdk.Context, delegatorAddr, validatorSrcAd minTime, height, completeNow := k.getBeginInfo(ctx, params, validatorSrcAddr) if completeNow { // no need to create the redelegation object + fmt.Println("wackydebugoutput BeginRedelegation 10") return nil } + fmt.Println("wackydebugoutput BeginRedelegation 11") red := types.Redelegation{ DelegatorAddr: delegatorAddr, @@ -446,6 +476,7 @@ func (k Keeper) BeginRedelegation(ctx sdk.Context, delegatorAddr, validatorSrcAd Balance: returnCoin, InitialBalance: returnCoin, } + fmt.Println("wackydebugoutput BeginRedelegation 13") k.SetRedelegation(ctx, red) return nil } diff --git a/x/stake/keeper/delegation_test.go b/x/stake/keeper/delegation_test.go index add16fb52c..e64bfa38ba 100644 --- a/x/stake/keeper/delegation_test.go +++ b/x/stake/keeper/delegation_test.go @@ -219,8 +219,6 @@ func TestUndelegateSelfDelegation(t *testing.T) { validator, found := keeper.GetValidator(ctx, addrVals[0]) require.True(t, found) - pool = keeper.GetPool(ctx) - require.Equal(t, int64(10), validator.Tokens.RoundInt64()) require.Equal(t, sdk.Unbonding, validator.Status) } @@ -358,8 +356,8 @@ func TestUndelegateFromUnbondedValidator(t *testing.T) { require.NoError(t, err) // no ubd should have been found, coins should have been returned direcly to account - _, found = keeper.GetUnbondingDelegation(ctx, addrDels[0], addrVals[0]) - require.False(t, found) + ubd, found := keeper.GetUnbondingDelegation(ctx, addrDels[0], addrVals[0]) + require.False(t, found, "%v", ubd) } // Make sure that that the retrieving the delegations doesn't affect the state @@ -442,10 +440,203 @@ func TestRedelegation(t *testing.T) { require.False(t, found) } -func TestRedelegationFromUnbondingValidator(t *testing.T) { - require.Fail(t, "") +func TestRedelegateSelfDelegation(t *testing.T) { + + ctx, _, keeper := CreateTestInput(t, false, 0) + pool := keeper.GetPool(ctx) + pool.LooseTokens = sdk.NewDec(30) + + //create a validator with a self-delegation + validator := types.NewValidator(addrVals[0], PKs[0], types.Description{}) + validator, pool, issuedShares := validator.AddTokensFromDel(pool, sdk.NewInt(10)) + require.Equal(t, int64(10), issuedShares.RoundInt64()) + keeper.SetPool(ctx, pool) + validator = keeper.UpdateValidator(ctx, validator) + pool = keeper.GetPool(ctx) + selfDelegation := types.Delegation{ + DelegatorAddr: addrVals[0], + ValidatorAddr: addrVals[0], + Shares: issuedShares, + } + keeper.SetDelegation(ctx, selfDelegation) + + // create a second validator + validator2 := types.NewValidator(addrVals[1], PKs[1], types.Description{}) + validator2, pool, issuedShares = validator2.AddTokensFromDel(pool, sdk.NewInt(10)) + require.Equal(t, int64(10), issuedShares.RoundInt64()) + keeper.SetPool(ctx, pool) + validator2 = keeper.UpdateValidator(ctx, validator2) + + // create a second delegation to this validator + validator, pool, issuedShares = validator.AddTokensFromDel(pool, sdk.NewInt(10)) + require.Equal(t, int64(10), issuedShares.RoundInt64()) + keeper.SetPool(ctx, pool) + validator = keeper.UpdateValidator(ctx, validator) + pool = keeper.GetPool(ctx) + delegation := types.Delegation{ + DelegatorAddr: addrDels[0], + ValidatorAddr: addrVals[0], + Shares: issuedShares, + } + keeper.SetDelegation(ctx, delegation) + + err := keeper.BeginRedelegation(ctx, addrVals[0], addrVals[0], addrVals[1], sdk.NewDec(10)) + require.NoError(t, err) + + validator, found := keeper.GetValidator(ctx, addrVals[0]) + require.True(t, found) + require.Equal(t, int64(10), validator.Tokens.RoundInt64()) + require.Equal(t, sdk.Unbonding, validator.Status) } -func TestRedelegationFromUnbondedValidator(t *testing.T) { +func TestRedelegateFromUnbondingValidator(t *testing.T) { + ctx, _, keeper := CreateTestInput(t, false, 0) + pool := keeper.GetPool(ctx) + pool.LooseTokens = sdk.NewDec(30) + + //create a validator with a self-delegation + validator := types.NewValidator(addrVals[0], PKs[0], types.Description{}) + + validator, pool, issuedShares := validator.AddTokensFromDel(pool, sdk.NewInt(10)) + require.Equal(t, int64(10), issuedShares.RoundInt64()) + keeper.SetPool(ctx, pool) + validator = keeper.UpdateValidator(ctx, validator) + pool = keeper.GetPool(ctx) + selfDelegation := types.Delegation{ + DelegatorAddr: addrVals[0], + ValidatorAddr: addrVals[0], + Shares: issuedShares, + } + keeper.SetDelegation(ctx, selfDelegation) + + // create a second delegation to this validator + validator, pool, issuedShares = validator.AddTokensFromDel(pool, sdk.NewInt(10)) + require.Equal(t, int64(10), issuedShares.RoundInt64()) + keeper.SetPool(ctx, pool) + validator = keeper.UpdateValidator(ctx, validator) + pool = keeper.GetPool(ctx) + delegation := types.Delegation{ + DelegatorAddr: addrDels[0], + ValidatorAddr: addrVals[0], + Shares: issuedShares, + } + keeper.SetDelegation(ctx, delegation) + + // create a second validator + validator2 := types.NewValidator(addrVals[1], PKs[1], types.Description{}) + validator2, pool, issuedShares = validator2.AddTokensFromDel(pool, sdk.NewInt(10)) + require.Equal(t, int64(10), issuedShares.RoundInt64()) + keeper.SetPool(ctx, pool) + validator2 = keeper.UpdateValidator(ctx, validator2) + + header := ctx.BlockHeader() + blockHeight := int64(10) + header.Height = blockHeight + blockTime := time.Unix(333, 0) + header.Time = blockTime + ctx = ctx.WithBlockHeader(header) + + // unbond the all self-delegation to put validator in unbonding state + err := keeper.BeginUnbonding(ctx, addrVals[0], addrVals[0], sdk.NewDec(10)) + require.NoError(t, err) + + validator, found := keeper.GetValidator(ctx, addrVals[0]) + require.True(t, found) + require.Equal(t, blockHeight, validator.UnbondingHeight) + params := keeper.GetParams(ctx) + require.True(t, blockTime.Add(params.UnbondingTime).Equal(validator.UnbondingMinTime)) + + //change the context + header = ctx.BlockHeader() + blockHeight2 := int64(20) + header.Height = blockHeight2 + blockTime2 := time.Unix(444, 0) + header.Time = blockTime2 + ctx = ctx.WithBlockHeader(header) + + // unbond some of the other delegation's shares + err = keeper.BeginRedelegation(ctx, addrDels[0], addrVals[0], addrVals[1], sdk.NewDec(6)) + require.NoError(t, err) + + // retrieve the unbonding delegation + ubd, found := keeper.GetRedelegation(ctx, addrDels[0], addrVals[0], addrVals[1]) + require.True(t, found) + require.True(t, ubd.Balance.IsEqual(sdk.NewInt64Coin(params.BondDenom, 6))) + assert.Equal(t, blockHeight, ubd.CreationHeight) + assert.True(t, blockTime.Add(params.UnbondingTime).Equal(ubd.MinTime)) +} + +func TestRedelegateFromUnbondedValidator(t *testing.T) { + ctx, _, keeper := CreateTestInput(t, false, 0) + pool := keeper.GetPool(ctx) + pool.LooseTokens = sdk.NewDec(30) + + //create a validator with a self-delegation + validator := types.NewValidator(addrVals[0], PKs[0], types.Description{}) + + validator, pool, issuedShares := validator.AddTokensFromDel(pool, sdk.NewInt(10)) + require.Equal(t, int64(10), issuedShares.RoundInt64()) + keeper.SetPool(ctx, pool) + validator = keeper.UpdateValidator(ctx, validator) + pool = keeper.GetPool(ctx) + selfDelegation := types.Delegation{ + DelegatorAddr: addrVals[0], + ValidatorAddr: addrVals[0], + Shares: issuedShares, + } + keeper.SetDelegation(ctx, selfDelegation) + + // create a second delegation to this validator + validator, pool, issuedShares = validator.AddTokensFromDel(pool, sdk.NewInt(10)) + require.Equal(t, int64(10), issuedShares.RoundInt64()) + keeper.SetPool(ctx, pool) + validator = keeper.UpdateValidator(ctx, validator) + pool = keeper.GetPool(ctx) + delegation := types.Delegation{ + DelegatorAddr: addrDels[0], + ValidatorAddr: addrVals[0], + Shares: issuedShares, + } + keeper.SetDelegation(ctx, delegation) + + // create a second validator + validator2 := types.NewValidator(addrVals[1], PKs[1], types.Description{}) + validator2, pool, issuedShares = validator2.AddTokensFromDel(pool, sdk.NewInt(10)) + require.Equal(t, int64(10), issuedShares.RoundInt64()) + keeper.SetPool(ctx, pool) + validator2 = keeper.UpdateValidator(ctx, validator2) + + header := ctx.BlockHeader() + blockHeight := int64(10) + header.Height = blockHeight + blockTime := time.Unix(333, 0) + header.Time = blockTime + ctx = ctx.WithBlockHeader(header) + + // unbond the all self-delegation to put validator in unbonding state + err := keeper.BeginUnbonding(ctx, addrVals[0], addrVals[0], sdk.NewDec(10)) + require.NoError(t, err) + + validator, found := keeper.GetValidator(ctx, addrVals[0]) + require.True(t, found) + require.Equal(t, blockHeight, validator.UnbondingHeight) + params := keeper.GetParams(ctx) + require.True(t, blockTime.Add(params.UnbondingTime).Equal(validator.UnbondingMinTime)) + + // change the context to one which makes the validator considered unbonded + header = ctx.BlockHeader() + blockHeight2 := int64(20) + header.Height = blockHeight2 + blockTime2 := time.Unix(444, 0).Add(params.UnbondingTime) + header.Time = blockTime2 + ctx = ctx.WithBlockHeader(header) + + // unbond some of the other delegation's shares + err = keeper.BeginRedelegation(ctx, addrDels[0], addrVals[0], addrVals[1], sdk.NewDec(6)) + require.NoError(t, err) + + // no ubd should have been found, coins should have been returned direcly to account + ubd, found := keeper.GetRedelegation(ctx, addrDels[0], addrVals[0], addrVals[1]) + require.False(t, found, "%v", ubd) require.Fail(t, "") } diff --git a/x/stake/keeper/slash.go b/x/stake/keeper/slash.go index c3c79714ee..be3572d7c3 100644 --- a/x/stake/keeper/slash.go +++ b/x/stake/keeper/slash.go @@ -46,14 +46,9 @@ func (k Keeper) Slash(ctx sdk.Context, pubkey crypto.PubKey, infractionHeight in return } - // slashing should not be slashing unbonded - if validator.Status == sdk.Unbonded { + // should not be slashing unbonded + if validator.IsUnbonded(ctx) { panic(fmt.Sprintf("should not be slashing unbonded validator: %v", validator)) - } else if validator.Status == sdk.Unbonding { - ctxTime := ctx.BlockHeader().Time - if ctxTime.After(validator.UnbondingMinTime) { - panic(fmt.Sprintf("should not be slashing unbonded validator: %v", validator)) - } } operatorAddress := validator.GetOperator() diff --git a/x/stake/types/validator.go b/x/stake/types/validator.go index a912b26dc7..13b00f9094 100644 --- a/x/stake/types/validator.go +++ b/x/stake/types/validator.go @@ -418,6 +418,19 @@ func (v Validator) BondedTokens() sdk.Dec { return sdk.ZeroDec() } +// Returns if the validator should be considered unbonded +func (v Validator) IsUnbonded(ctx sdk.Context) bool { + if v.Status == sdk.Unbonded { + return true + } else if v.Status == sdk.Unbonding { + ctxTime := ctx.BlockHeader().Time + if ctxTime.After(v.UnbondingMinTime) { + return true + } + } + return false +} + //______________________________________________________________________ // ensure fulfills the sdk validator types From 2a1515f00a5fe06b74c4e8c005ccb4b5826ca1dc Mon Sep 17 00:00:00 2001 From: rigelrozanski Date: Wed, 29 Aug 2018 18:48:42 -0400 Subject: [PATCH 07/52] changelog --- PENDING.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/PENDING.md b/PENDING.md index dd18be3c0b..5c7d04d75a 100644 --- a/PENDING.md +++ b/PENDING.md @@ -19,6 +19,8 @@ BREAKING CHANGES * [x/stake] \#1901 Validator type's Owner field renamed to Operator; Validator's GetOwner() renamed accordingly to comply with the SDK's Validator interface. * [docs] [#2001](https://github.com/cosmos/cosmos-sdk/pull/2001) Update slashing spec for slashing period * [x/stake, x/slashing] [#1305](https://github.com/cosmos/cosmos-sdk/issues/1305) - Rename "revoked" to "jailed" + * [x/stake] [#1676] Revoked and jailed validators put into the unbonding state + * [x/stake] [#1877] Redelegations/unbonding-delegation from unbonding validator have reduced time * SDK * [core] \#1807 Switch from use of rational to decimal From 6fd8299b1a000a63171a6de8cc828f05288840b3 Mon Sep 17 00:00:00 2001 From: rigelrozanski Date: Wed, 29 Aug 2018 18:54:02 -0400 Subject: [PATCH 08/52] ... --- x/stake/keeper/delegation.go | 31 ------------------------------- x/stake/keeper/delegation_test.go | 1 - 2 files changed, 32 deletions(-) diff --git a/x/stake/keeper/delegation.go b/x/stake/keeper/delegation.go index 7f66cd5550..cfe21c03f7 100644 --- a/x/stake/keeper/delegation.go +++ b/x/stake/keeper/delegation.go @@ -2,7 +2,6 @@ package keeper import ( "bytes" - "fmt" "time" sdk "github.com/cosmos/cosmos-sdk/types" @@ -257,56 +256,44 @@ func (k Keeper) Delegate(ctx sdk.Context, delegatorAddr sdk.AccAddress, bondAmt // unbond the the delegation return func (k Keeper) unbond(ctx sdk.Context, delegatorAddr, validatorAddr sdk.AccAddress, shares sdk.Dec) (amount sdk.Dec, err sdk.Error) { - fmt.Println("wackydebugoutput unbond 0") // check if delegation has any shares in it unbond delegation, found := k.GetDelegation(ctx, delegatorAddr, validatorAddr) if !found { - fmt.Println("wackydebugoutput unbond 1") err = types.ErrNoDelegatorForAddress(k.Codespace()) return } - fmt.Println("wackydebugoutput unbond 2") // retrieve the amount to remove if delegation.Shares.LT(shares) { - fmt.Println("wackydebugoutput unbond 3") err = types.ErrNotEnoughDelegationShares(k.Codespace(), delegation.Shares.String()) return } - fmt.Println("wackydebugoutput unbond 4") // get validator validator, found := k.GetValidator(ctx, validatorAddr) if !found { - fmt.Println("wackydebugoutput unbond 5") err = types.ErrNoValidatorFound(k.Codespace()) return } - fmt.Println("wackydebugoutput unbond 6") // subtract shares from delegator delegation.Shares = delegation.Shares.Sub(shares) // remove the delegation if delegation.Shares.IsZero() { - fmt.Println("wackydebugoutput unbond 7") // if the delegation is the operator of the validator then // trigger a jail validator if bytes.Equal(delegation.DelegatorAddr, validator.Operator) && validator.Jailed == false { - fmt.Println("wackydebugoutput unbond 8") validator.Jailed = true } - fmt.Println("wackydebugoutput unbond 9") k.RemoveDelegation(ctx, delegation) } else { - fmt.Println("wackydebugoutput unbond 10") // Update height delegation.Height = ctx.BlockHeight() k.SetDelegation(ctx, delegation) } - fmt.Println("wackydebugoutput unbond 11") // remove the coins from the validator pool := k.GetPool(ctx) @@ -316,12 +303,9 @@ func (k Keeper) unbond(ctx sdk.Context, delegatorAddr, validatorAddr sdk.AccAddr // update then remove validator if necessary validator = k.UpdateValidator(ctx, validator) - fmt.Printf("debug validator: %v\n", validator) if validator.DelegatorShares.IsZero() { - fmt.Println("wackydebugoutput unbond 12") k.RemoveValidator(ctx, validator.Operator) } - fmt.Println("wackydebugoutput unbond 13") return } @@ -331,7 +315,6 @@ func (k Keeper) unbond(ctx sdk.Context, delegatorAddr, validatorAddr sdk.AccAddr // get info for begin functions: MinTime and CreationHeight func (k Keeper) getBeginInfo(ctx sdk.Context, params types.Params, validatorSrcAddr sdk.AccAddress) ( minTime time.Time, height int64, completeNow bool) { - fmt.Println("wackydebugoutput getBeginInfo 0") validator, found := k.GetValidator(ctx, validatorSrcAddr) switch { @@ -420,38 +403,27 @@ func (k Keeper) CompleteUnbonding(ctx sdk.Context, delegatorAddr, validatorAddr // complete unbonding an unbonding record func (k Keeper) BeginRedelegation(ctx sdk.Context, delegatorAddr, validatorSrcAddr, validatorDstAddr sdk.AccAddress, sharesAmount sdk.Dec) sdk.Error { - fmt.Println("wackydebugoutput BeginRedelegation 0") // check if this is a transitive redelegation if k.HasReceivingRedelegation(ctx, delegatorAddr, validatorSrcAddr) { - fmt.Println("wackydebugoutput BeginRedelegation 1") return types.ErrTransitiveRedelegation(k.Codespace()) } - fmt.Println("wackydebugoutput BeginRedelegation 2") returnAmount, err := k.unbond(ctx, delegatorAddr, validatorSrcAddr, sharesAmount) if err != nil { - fmt.Println("wackydebugoutput BeginRedelegation 3") return err } - fmt.Println("wackydebugoutput BeginRedelegation 4") params := k.GetParams(ctx) returnCoin := sdk.Coin{params.BondDenom, returnAmount.RoundInt()} - fmt.Printf("debug returnCoin: %v\n", returnCoin) - fmt.Println("wackydebugoutput BeginRedelegation 5") dstValidator, found := k.GetValidator(ctx, validatorDstAddr) if !found { - fmt.Println("wackydebugoutput BeginRedelegation 6") return types.ErrBadRedelegationDst(k.Codespace()) } - fmt.Println("wackydebugoutput BeginRedelegation 7") sharesCreated, err := k.Delegate(ctx, delegatorAddr, returnCoin, dstValidator, false) if err != nil { - fmt.Println("wackydebugoutput BeginRedelegation 8") return err } - fmt.Println("wackydebugoutput BeginRedelegation 9") // create the unbonding delegation minTime := ctx.BlockHeader().Time.Add(params.UnbondingTime) @@ -460,10 +432,8 @@ func (k Keeper) BeginRedelegation(ctx sdk.Context, delegatorAddr, validatorSrcAd minTime, height, completeNow := k.getBeginInfo(ctx, params, validatorSrcAddr) if completeNow { // no need to create the redelegation object - fmt.Println("wackydebugoutput BeginRedelegation 10") return nil } - fmt.Println("wackydebugoutput BeginRedelegation 11") red := types.Redelegation{ DelegatorAddr: delegatorAddr, @@ -476,7 +446,6 @@ func (k Keeper) BeginRedelegation(ctx sdk.Context, delegatorAddr, validatorSrcAd Balance: returnCoin, InitialBalance: returnCoin, } - fmt.Println("wackydebugoutput BeginRedelegation 13") k.SetRedelegation(ctx, red) return nil } diff --git a/x/stake/keeper/delegation_test.go b/x/stake/keeper/delegation_test.go index e64bfa38ba..d0fd736d93 100644 --- a/x/stake/keeper/delegation_test.go +++ b/x/stake/keeper/delegation_test.go @@ -638,5 +638,4 @@ func TestRedelegateFromUnbondedValidator(t *testing.T) { // no ubd should have been found, coins should have been returned direcly to account ubd, found := keeper.GetRedelegation(ctx, addrDels[0], addrVals[0], addrVals[1]) require.False(t, found, "%v", ubd) - require.Fail(t, "") } From 8d02c3eba09cce9c88a5c710cfda059189708c4b Mon Sep 17 00:00:00 2001 From: rigelrozanski Date: Wed, 29 Aug 2018 20:24:35 -0400 Subject: [PATCH 09/52] fix golint --- x/stake/keeper/delegation.go | 3 --- x/stake/keeper/slash.go | 2 ++ 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/x/stake/keeper/delegation.go b/x/stake/keeper/delegation.go index cfe21c03f7..d19c9bf82e 100644 --- a/x/stake/keeper/delegation.go +++ b/x/stake/keeper/delegation.go @@ -426,9 +426,6 @@ func (k Keeper) BeginRedelegation(ctx sdk.Context, delegatorAddr, validatorSrcAd } // create the unbonding delegation - minTime := ctx.BlockHeader().Time.Add(params.UnbondingTime) - height := ctx.BlockHeader().Height - minTime, height, completeNow := k.getBeginInfo(ctx, params, validatorSrcAddr) if completeNow { // no need to create the redelegation object diff --git a/x/stake/keeper/slash.go b/x/stake/keeper/slash.go index be3572d7c3..0658afb862 100644 --- a/x/stake/keeper/slash.go +++ b/x/stake/keeper/slash.go @@ -22,6 +22,8 @@ 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, pubkey crypto.PubKey, infractionHeight int64, power int64, slashFactor sdk.Dec) { logger := ctx.Logger().With("module", "x/stake") From 46bbada4eea507426a9909491279ceb5564522a5 Mon Sep 17 00:00:00 2001 From: ValarDragon Date: Wed, 29 Aug 2018 23:02:15 -0700 Subject: [PATCH 10/52] simulation: Add benchmarking --- PENDING.md | 1 + cmd/gaia/app/sim_test.go | 21 +++ x/bank/simulation/msgs.go | 18 +-- x/gov/simulation/msgs.go | 50 +++--- x/mock/simulation/random_simulate_blocks.go | 166 +++++++++++++++----- x/mock/simulation/types.go | 2 +- x/slashing/simulation/msgs.go | 8 +- x/stake/simulation/msgs.go | 44 ++++-- 8 files changed, 220 insertions(+), 90 deletions(-) diff --git a/PENDING.md b/PENDING.md index f45541ccdc..2e862ead6a 100644 --- a/PENDING.md +++ b/PENDING.md @@ -45,6 +45,7 @@ FEATURES * SDK * [querier] added custom querier functionality, so ABCI query requests can be handled by keepers * [simulation] \#1924 allow operations to specify future operations + * [simulation] Add benchmarking capabilities * Tendermint diff --git a/cmd/gaia/app/sim_test.go b/cmd/gaia/app/sim_test.go index 0ca936bdbf..2bad6aba23 100644 --- a/cmd/gaia/app/sim_test.go +++ b/cmd/gaia/app/sim_test.go @@ -112,6 +112,27 @@ func invariants(app *GaiaApp) []simulation.Invariant { } } +// Profile with: +// /usr/local/go/bin/go test -benchmem -run=^$ github.com/cosmos/cosmos-sdk/cmd/gaia/app -bench ^BenchmarkFullGaiaSimulation$ -cpuprofile cpu.out +func BenchmarkFullGaiaSimulation(b *testing.B) { + // Setup Gaia application + var logger log.Logger + logger = log.NewNopLogger() + db := dbm.NewMemDB() + app := NewGaiaApp(logger, db, nil) + + // Run randomized simulation + // TODO parameterize numbers, save for a later PR + simulation.BenchmarkSimulationFromSeed( + b, app.BaseApp, appStateFn, seed, + testAndRunTxs(app), + []simulation.RandSetup{}, + 10, + 100, + false, + ) +} + func TestFullGaiaSimulation(t *testing.T) { if !enabled { t.Skip("Skipping Gaia simulation") diff --git a/x/bank/simulation/msgs.go b/x/bank/simulation/msgs.go index e3f3ab77b8..c53916a613 100644 --- a/x/bank/simulation/msgs.go +++ b/x/bank/simulation/msgs.go @@ -21,7 +21,7 @@ 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(t *testing.T, r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, keys []crypto.PrivKey, log string, event func(string)) (action string, fOps []simulation.FutureOperation, err sdk.Error) { + return func(tb testing.TB, r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, keys []crypto.PrivKey, log string, event func(string)) (action string, fOps []simulation.FutureOperation, err sdk.Error) { fromKey := simulation.RandomKey(r, keys) fromAddr := sdk.AccAddress(fromKey.PubKey().Address()) toKey := simulation.RandomKey(r, keys) @@ -58,7 +58,7 @@ func SimulateSingleInputMsgSend(mapper auth.AccountMapper) simulation.Operation Inputs: []bank.Input{bank.NewInput(fromAddr, coins)}, Outputs: []bank.Output{bank.NewOutput(toAddr, coins)}, } - sendAndVerifyMsgSend(t, app, mapper, msg, ctx, log, []crypto.PrivKey{fromKey}) + sendAndVerifyMsgSend(tb, app, mapper, msg, ctx, log, []crypto.PrivKey{fromKey}) event("bank/sendAndVerifyMsgSend/ok") return action, nil, nil @@ -66,7 +66,7 @@ func SimulateSingleInputMsgSend(mapper auth.AccountMapper) simulation.Operation } // Sends and verifies the transition of a msg send. This fails if there are repeated inputs or outputs -func sendAndVerifyMsgSend(t *testing.T, app *baseapp.BaseApp, mapper auth.AccountMapper, msg bank.MsgSend, ctx sdk.Context, log string, privkeys []crypto.PrivKey) { +func sendAndVerifyMsgSend(tb testing.TB, app *baseapp.BaseApp, mapper auth.AccountMapper, msg bank.MsgSend, ctx sdk.Context, log string, privkeys []crypto.PrivKey) { initialInputAddrCoins := make([]sdk.Coins, len(msg.Inputs)) initialOutputAddrCoins := make([]sdk.Coins, len(msg.Outputs)) AccountNumbers := make([]int64, len(msg.Inputs)) @@ -91,12 +91,12 @@ func sendAndVerifyMsgSend(t *testing.T, app *baseapp.BaseApp, mapper auth.Accoun // TODO: Do this in a more 'canonical' way fmt.Println(res) fmt.Println(log) - t.FailNow() + tb.FailNow() } for i := 0; i < len(msg.Inputs); i++ { terminalInputCoins := mapper.GetAccount(ctx, msg.Inputs[i].Address).GetCoins() - require.Equal(t, + require.Equal(tb, initialInputAddrCoins[i].Minus(msg.Inputs[i].Coins), terminalInputCoins, fmt.Sprintf("Input #%d had an incorrect amount of coins\n%s", i, log), @@ -104,11 +104,9 @@ func sendAndVerifyMsgSend(t *testing.T, app *baseapp.BaseApp, mapper auth.Accoun } for i := 0; i < len(msg.Outputs); i++ { terminalOutputCoins := mapper.GetAccount(ctx, msg.Outputs[i].Address).GetCoins() - require.Equal(t, - initialOutputAddrCoins[i].Plus(msg.Outputs[i].Coins), - terminalOutputCoins, - fmt.Sprintf("Output #%d had an incorrect amount of coins\n%s", i, log), - ) + if !terminalOutputCoins.IsEqual(initialOutputAddrCoins[i].Plus(msg.Outputs[i].Coins)) { + tb.Fatalf("Output #%d had an incorrect amount of coins\n%s", i, log) + } } } diff --git a/x/gov/simulation/msgs.go b/x/gov/simulation/msgs.go index 0b1530138f..eca8accae0 100644 --- a/x/gov/simulation/msgs.go +++ b/x/gov/simulation/msgs.go @@ -5,8 +5,6 @@ import ( "math/rand" "testing" - "github.com/stretchr/testify/require" - "github.com/tendermint/tendermint/crypto" "github.com/cosmos/cosmos-sdk/baseapp" @@ -23,24 +21,15 @@ const ( // SimulateMsgSubmitProposal simulates a msg Submit Proposal // Note: Currently doesn't ensure that the proposal txt is in JSON form func SimulateMsgSubmitProposal(k gov.Keeper, sk stake.Keeper) simulation.Operation { - return func(t *testing.T, r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, keys []crypto.PrivKey, log string, event func(string)) (action string, fOps []simulation.FutureOperation, err sdk.Error) { - key := simulation.RandomKey(r, keys) - addr := sdk.AccAddress(key.PubKey().Address()) - deposit := randomDeposit(r) - msg := gov.NewMsgSubmitProposal( - simulation.RandStringOfLength(r, 5), - simulation.RandStringOfLength(r, 5), - gov.ProposalTypeText, - addr, - deposit, - ) - require.Nil(t, msg.ValidateBasic(), "expected msg to pass ValidateBasic: %s", msg.GetSignBytes()) + handler := gov.NewHandler(k) + return func(tb testing.TB, r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, keys []crypto.PrivKey, log string, event func(string)) (action string, fOps []simulation.FutureOperation, err sdk.Error) { + msg := simulationCreateMsgSubmitProposal(tb, r, keys, log) ctx, write := ctx.CacheContext() - result := gov.NewHandler(k)(ctx, msg) + result := handler(ctx, msg) if result.IsOK() { // Update pool to keep invariants pool := sk.GetPool(ctx) - pool.LooseTokens = pool.LooseTokens.Sub(sdk.NewDecFromInt(deposit.AmountOf(denom))) + pool.LooseTokens = pool.LooseTokens.Sub(sdk.NewDecFromInt(msg.InitialDeposit.AmountOf(denom))) sk.SetPool(ctx, pool) write() } @@ -50,9 +39,26 @@ func SimulateMsgSubmitProposal(k gov.Keeper, sk stake.Keeper) simulation.Operati } } +func simulationCreateMsgSubmitProposal(tb testing.TB, r *rand.Rand, keys []crypto.PrivKey, log string) gov.MsgSubmitProposal { + key := simulation.RandomKey(r, keys) + addr := sdk.AccAddress(key.PubKey().Address()) + deposit := randomDeposit(r) + msg := gov.NewMsgSubmitProposal( + simulation.RandStringOfLength(r, 5), + simulation.RandStringOfLength(r, 5), + gov.ProposalTypeText, + addr, + deposit, + ) + if msg.ValidateBasic() != nil { + tb.Fatalf("expected msg to pass ValidateBasic: %s, log %s", msg.GetSignBytes(), log) + } + return msg +} + // SimulateMsgDeposit func SimulateMsgDeposit(k gov.Keeper, sk stake.Keeper) simulation.Operation { - return func(t *testing.T, r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, keys []crypto.PrivKey, log string, event func(string)) (action string, fOp []simulation.FutureOperation, err sdk.Error) { + return func(tb testing.TB, r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, keys []crypto.PrivKey, log string, event func(string)) (action string, fOp []simulation.FutureOperation, err sdk.Error) { key := simulation.RandomKey(r, keys) addr := sdk.AccAddress(key.PubKey().Address()) proposalID, ok := randomProposalID(r, k, ctx) @@ -61,7 +67,9 @@ func SimulateMsgDeposit(k gov.Keeper, sk stake.Keeper) simulation.Operation { } deposit := randomDeposit(r) msg := gov.NewMsgDeposit(addr, proposalID, deposit) - require.Nil(t, msg.ValidateBasic(), "expected msg to pass ValidateBasic: %s", msg.GetSignBytes()) + if msg.ValidateBasic() != nil { + tb.Fatalf("expected msg to pass ValidateBasic: %s, log %s", msg.GetSignBytes(), log) + } ctx, write := ctx.CacheContext() result := gov.NewHandler(k)(ctx, msg) if result.IsOK() { @@ -79,7 +87,7 @@ func SimulateMsgDeposit(k gov.Keeper, sk stake.Keeper) simulation.Operation { // SimulateMsgVote func SimulateMsgVote(k gov.Keeper, sk stake.Keeper) simulation.Operation { - return func(t *testing.T, r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, keys []crypto.PrivKey, log string, event func(string)) (action string, fOp []simulation.FutureOperation, err sdk.Error) { + return func(tb testing.TB, r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, keys []crypto.PrivKey, log string, event func(string)) (action string, fOp []simulation.FutureOperation, err sdk.Error) { key := simulation.RandomKey(r, keys) addr := sdk.AccAddress(key.PubKey().Address()) proposalID, ok := randomProposalID(r, k, ctx) @@ -88,7 +96,9 @@ func SimulateMsgVote(k gov.Keeper, sk stake.Keeper) simulation.Operation { } option := randomVotingOption(r) msg := gov.NewMsgVote(addr, proposalID, option) - require.Nil(t, msg.ValidateBasic(), "expected msg to pass ValidateBasic: %s", msg.GetSignBytes()) + if msg.ValidateBasic() != nil { + tb.Fatalf("expected msg to pass ValidateBasic: %s, log %s", msg.GetSignBytes(), log) + } ctx, write := ctx.CacheContext() result := gov.NewHandler(k)(ctx, msg) if result.IsOK() { diff --git a/x/mock/simulation/random_simulate_blocks.go b/x/mock/simulation/random_simulate_blocks.go index 995013ef8f..aa4df613dd 100644 --- a/x/mock/simulation/random_simulate_blocks.go +++ b/x/mock/simulation/random_simulate_blocks.go @@ -5,6 +5,7 @@ import ( "fmt" "math" "math/rand" + "os" "sort" "testing" "time" @@ -28,6 +29,26 @@ func Simulate( 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)}) + 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) + } + + return +} + +func randTimestamp(r *rand.Rand) time.Time { + unixTime := r.Int63n(int64(math.Pow(2, 40))) + return time.Unix(unixTime, 0) +} + // SimulateFromSeed tests an application by running the provided // operations, testing the provided invariants, but using the provided seed. func SimulateFromSeed( @@ -36,11 +57,7 @@ func SimulateFromSeed( ) { log := fmt.Sprintf("Starting SimulateFromSeed with randomness created with seed %d", int(seed)) r := rand.New(rand.NewSource(seed)) - - unixTime := r.Int63n(int64(math.Pow(2, 40))) - - // Set the timestamp for simulation - timestamp := time.Unix(unixTime, 0) + timestamp := randTimestamp(r) log = fmt.Sprintf("%s\nStarting the simulation from time %v, unixtime %v", log, timestamp.UTC().Format(time.UnixDate), timestamp.Unix()) fmt.Printf("%s\n", log) timeDiff := maxTimePerBlock - minTimePerBlock @@ -54,30 +71,18 @@ func SimulateFromSeed( events[what]++ } - res := app.InitChain(abci.RequestInitChain{AppStateBytes: appStateFn(r, keys, accs)}) - 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) - } + validators := initChain(r, keys, accs, setups, app, appStateFn) header := abci.Header{Height: 0, Time: timestamp} opCount := 0 request := abci.RequestBeginBlock{Header: header} - var pastTimes []time.Time + var lastHeaderTime time.Time // These are operations which have been queued by previous operations operationQueue := make(map[int][]Operation) for i := 0; i < numBlocks; i++ { - - // Log the header time for future lookup - pastTimes = append(pastTimes, header.Time) - // Run the BeginBlock handler app.BeginBlock(request) @@ -88,16 +93,8 @@ func SimulateFromSeed( ctx := app.NewContext(false, header) - var thisBlockSize int - load := r.Float64() - switch { - case load < 0.33: - thisBlockSize = 0 - case load < 0.66: - thisBlockSize = r.Intn(blockSize * 2) - default: - thisBlockSize = r.Intn(blockSize * 4) - } + thisBlockSize := getBlockSize(r, blockSize) + // Run queued operations. Ignores blocksize if blocksize is too small log, numQueuedOpsRan := runQueuedOperations(operationQueue, int(header.Height), t, r, app, ctx, keys, log, event) opCount += numQueuedOpsRan @@ -119,6 +116,7 @@ func SimulateFromSeed( res := app.EndBlock(abci.RequestEndBlock{}) header.Height++ + lastHeaderTime = header.Time header.Time = header.Time.Add(time.Duration(minTimePerBlock) * time.Second).Add(time.Duration(int64(r.Intn(int(timeDiff)))) * time.Second) log += "\nEndBlock" @@ -131,7 +129,7 @@ func SimulateFromSeed( } // Generate a random RequestBeginBlock with the current validator set for the next block - request = RandomRequestBeginBlock(t, r, validators, livenessTransitionMatrix, evidenceFraction, pastTimes, event, header, log) + request = RandomRequestBeginBlock(r, validators, livenessTransitionMatrix, evidenceFraction, lastHeaderTime, event, header, log) // Update the validator set validators = updateValidators(t, r, validators, res.ValidatorUpdates, event) @@ -141,6 +139,84 @@ func SimulateFromSeed( DisplayEvents(events) } +func getBlockSize(r *rand.Rand, blockSize int) int { + load := r.Float64() + switch { + case load < 0.33: + return 0 + case load < 0.66: + return r.Intn(blockSize * 2) + default: + return r.Intn(blockSize * 4) + } +} + +// Simulate from seed, benchmarks +func BenchmarkSimulationFromSeed(b *testing.B, app *baseapp.BaseApp, appStateFn func(r *rand.Rand, keys []crypto.PrivKey, accs []sdk.AccAddress) json.RawMessage, + seed int64, ops []Operation, setups []RandSetup, numBlocks int, blockSize int, commit bool) { + r := rand.New(rand.NewSource(seed)) + timestamp := randTimestamp(r) + timeDiff := maxTimePerBlock - minTimePerBlock + keys, accs := mock.GeneratePrivKeyAddressPairsFromRand(r, numKeys) + + // Setup event stats + events := make(map[string]uint) + event := func(what string) { + events[what]++ + } + + validators := initChain(r, keys, accs, setups, app, appStateFn) + + header := abci.Header{Height: 0, Time: timestamp} + opCount := 0 + + request := abci.RequestBeginBlock{Header: header} + + var lastHeaderTime time.Time + // These are operations which have been queued by previous operations + operationQueue := make(map[int][]Operation) + b.ResetTimer() + + for i := 0; i < numBlocks; i++ { + + // Run the BeginBlock handler + app.BeginBlock(request) + + ctx := app.NewContext(false, header) + + thisBlockSize := getBlockSize(r, blockSize) + + // Run queued operations. Ignores blocksize if blocksize is too small + log, numQueuedOpsRan := runQueuedOperations(operationQueue, int(header.Height), b, r, app, ctx, keys, "", event) + opCount += numQueuedOpsRan + thisBlockSize -= numQueuedOpsRan + for j := 0; j < thisBlockSize; j++ { + _, futureOps, err := ops[r.Intn(len(ops))](b, r, app, ctx, keys, "", event) + queueOperations(operationQueue, futureOps) + if err != nil { + b.Fatalf("error on operation %d, %v", opCount, err) + } + opCount++ + } + + res := app.EndBlock(abci.RequestEndBlock{}) + header.Height++ + lastHeaderTime = header.Time + header.Time = header.Time.Add(time.Duration(minTimePerBlock) * time.Second).Add(time.Duration(int64(r.Intn(int(timeDiff)))) * time.Second) + if commit { + app.Commit() + } + + // Generate a random RequestBeginBlock with the current validator set for the next block + request = RandomRequestBeginBlock(r, validators, livenessTransitionMatrix, evidenceFraction, lastHeaderTime, event, header, log) + + // Update the validator set + validators = updateValidators(b, r, validators, res.ValidatorUpdates, event) + } + DisplayEvents(events) + fmt.Printf("Benchmark simulation ran %d operations\n", opCount) +} + // adds all future operations into the operation queue. func queueOperations(queuedOperations map[int][]Operation, futureOperations []FutureOperation) { if futureOperations == nil { @@ -155,7 +231,7 @@ func queueOperations(queuedOperations map[int][]Operation, futureOperations []Fu } } -func runQueuedOperations(queueOperations map[int][]Operation, height int, t *testing.T, r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, +func runQueuedOperations(queueOperations map[int][]Operation, height int, tb testing.TB, r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, privKeys []crypto.PrivKey, log string, event func(string)) (updatedLog string, numOpsRan int) { updatedLog = log if queuedOps, ok := queueOperations[height]; ok { @@ -164,9 +240,12 @@ func runQueuedOperations(queueOperations map[int][]Operation, height int, t *tes // 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](t, r, app, ctx, privKeys, updatedLog, event) - updatedLog += "\n" + logUpdate - require.Nil(t, err, updatedLog) + logUpdate, _, err := queuedOps[i](tb, r, app, ctx, privKeys, updatedLog, event) + updatedLog = fmt.Sprintf("%s\n%s", updatedLog, logUpdate) + if err != nil { + fmt.Fprint(os.Stderr, updatedLog) + tb.FailNow() + } } delete(queueOperations, height) return updatedLog, numOps @@ -186,8 +265,8 @@ func getKeys(validators map[string]mockValidator) []string { } // RandomRequestBeginBlock generates a list of signing validators according to the provided list of validators, signing fraction, and evidence fraction -func RandomRequestBeginBlock(t *testing.T, r *rand.Rand, validators map[string]mockValidator, livenessTransitions TransitionMatrix, evidenceFraction float64, - pastTimes []time.Time, event func(string), header abci.Header, log string) abci.RequestBeginBlock { +func RandomRequestBeginBlock(r *rand.Rand, validators map[string]mockValidator, livenessTransitions TransitionMatrix, evidenceFraction float64, + lastHeaderTime time.Time, event func(string), header abci.Header, log string) abci.RequestBeginBlock { if len(validators) == 0 { return abci.RequestBeginBlock{Header: header} } @@ -219,13 +298,14 @@ func RandomRequestBeginBlock(t *testing.T, r *rand.Rand, validators map[string]m } i++ } + // TODO: Determine capacity before allocation evidence := make([]abci.Evidence, 0) for r.Float64() < evidenceFraction { height := header.Height time := header.Time if r.Float64() < pastEvidenceFraction { height = int64(r.Intn(int(header.Height))) - time = pastTimes[height] + time = lastHeaderTime } validator := signingValidators[r.Intn(len(signingValidators))].Validator var currentTotalVotingPower int64 @@ -258,11 +338,19 @@ func AssertAllInvariants(t *testing.T, app *baseapp.BaseApp, tests []Invariant, } // updateValidators mimicks Tendermint's update logic -func updateValidators(t *testing.T, r *rand.Rand, current map[string]mockValidator, updates []abci.Validator, event func(string)) map[string]mockValidator { +func updateValidators(tb testing.TB, r *rand.Rand, current map[string]mockValidator, updates []abci.Validator, event func(string)) map[string]mockValidator { for _, update := range updates { switch { case update.Power == 0: - require.NotNil(t, current[string(update.PubKey.Data)], "tried to delete a nonexistent validator") + // // TEMPORARY DEBUG CODE TO PROVE THAT THE OLD METHOD WAS BROKEN + // // (i.e. didn't catch in the event of problem) + // if val, ok := tb.(*testing.T); ok { + // require.NotNil(val, current[string(update.PubKey.Data)]) + // } + // // CORRECT CHECK + // if _, ok := current[string(update.PubKey.Data)]; !ok { + // tb.Fatalf("tried to delete a nonexistent validator") + // } event("endblock/validatorupdates/kicked") delete(current, string(update.PubKey.Data)) default: diff --git a/x/mock/simulation/types.go b/x/mock/simulation/types.go index 2516e07ae5..2f91a4f263 100644 --- a/x/mock/simulation/types.go +++ b/x/mock/simulation/types.go @@ -23,7 +23,7 @@ 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( - t *testing.T, r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, + tb testing.TB, r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, privKeys []crypto.PrivKey, log string, event func(string), ) (action string, futureOperations []FutureOperation, err sdk.Error) diff --git a/x/slashing/simulation/msgs.go b/x/slashing/simulation/msgs.go index e4900889e3..7b5490e423 100644 --- a/x/slashing/simulation/msgs.go +++ b/x/slashing/simulation/msgs.go @@ -5,8 +5,6 @@ import ( "math/rand" "testing" - "github.com/stretchr/testify/require" - "github.com/tendermint/tendermint/crypto" "github.com/cosmos/cosmos-sdk/baseapp" @@ -17,11 +15,13 @@ import ( // SimulateMsgUnjail func SimulateMsgUnjail(k slashing.Keeper) simulation.Operation { - return func(t *testing.T, r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, keys []crypto.PrivKey, log string, event func(string)) (action string, fOp []simulation.FutureOperation, err sdk.Error) { + return func(tb testing.TB, r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, keys []crypto.PrivKey, log string, event func(string)) (action string, fOp []simulation.FutureOperation, err sdk.Error) { key := simulation.RandomKey(r, keys) address := sdk.AccAddress(key.PubKey().Address()) msg := slashing.NewMsgUnjail(address) - require.Nil(t, msg.ValidateBasic(), "expected msg to pass ValidateBasic: %s", msg.GetSignBytes()) + if msg.ValidateBasic() != nil { + tb.Fatalf("expected msg to pass ValidateBasic: %s, log %s", msg.GetSignBytes(), log) + } ctx, write := ctx.CacheContext() result := slashing.NewHandler(k)(ctx, msg) if result.IsOK() { diff --git a/x/stake/simulation/msgs.go b/x/stake/simulation/msgs.go index 4280e70c13..bc1063494d 100644 --- a/x/stake/simulation/msgs.go +++ b/x/stake/simulation/msgs.go @@ -5,8 +5,6 @@ import ( "math/rand" "testing" - "github.com/stretchr/testify/require" - "github.com/cosmos/cosmos-sdk/baseapp" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/x/auth" @@ -19,7 +17,7 @@ import ( // SimulateMsgCreateValidator func SimulateMsgCreateValidator(m auth.AccountMapper, k stake.Keeper) simulation.Operation { - return func(t *testing.T, r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, keys []crypto.PrivKey, log string, event func(string)) (action string, fOp []simulation.FutureOperation, err sdk.Error) { + return func(tb testing.TB, r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, keys []crypto.PrivKey, log string, event func(string)) (action string, fOp []simulation.FutureOperation, err sdk.Error) { denom := k.GetParams(ctx).BondDenom description := stake.Description{ Moniker: simulation.RandStringOfLength(r, 10), @@ -41,7 +39,9 @@ func SimulateMsgCreateValidator(m auth.AccountMapper, k stake.Keeper) simulation PubKey: pubkey, Delegation: sdk.NewCoin(denom, amount), } - require.Nil(t, msg.ValidateBasic(), "expected msg to pass ValidateBasic: %s", msg.GetSignBytes()) + if msg.ValidateBasic() != nil { + tb.Fatalf("expected msg to pass ValidateBasic: %s, log %s", msg.GetSignBytes(), log) + } ctx, write := ctx.CacheContext() result := stake.NewHandler(k)(ctx, msg) if result.IsOK() { @@ -56,7 +56,7 @@ func SimulateMsgCreateValidator(m auth.AccountMapper, k stake.Keeper) simulation // SimulateMsgEditValidator func SimulateMsgEditValidator(k stake.Keeper) simulation.Operation { - return func(t *testing.T, r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, keys []crypto.PrivKey, log string, event func(string)) (action string, fOp []simulation.FutureOperation, err sdk.Error) { + return func(tb testing.TB, r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, keys []crypto.PrivKey, log string, event func(string)) (action string, fOp []simulation.FutureOperation, err sdk.Error) { description := stake.Description{ Moniker: simulation.RandStringOfLength(r, 10), Identity: simulation.RandStringOfLength(r, 10), @@ -70,7 +70,9 @@ func SimulateMsgEditValidator(k stake.Keeper) simulation.Operation { Description: description, ValidatorAddr: address, } - require.Nil(t, msg.ValidateBasic(), "expected msg to pass ValidateBasic: %s", msg.GetSignBytes()) + if msg.ValidateBasic() != nil { + tb.Fatalf("expected msg to pass ValidateBasic: %s, log %s", msg.GetSignBytes(), log) + } ctx, write := ctx.CacheContext() result := stake.NewHandler(k)(ctx, msg) if result.IsOK() { @@ -84,7 +86,7 @@ func SimulateMsgEditValidator(k stake.Keeper) simulation.Operation { // SimulateMsgDelegate func SimulateMsgDelegate(m auth.AccountMapper, k stake.Keeper) simulation.Operation { - return func(t *testing.T, r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, keys []crypto.PrivKey, log string, event func(string)) (action string, fOp []simulation.FutureOperation, err sdk.Error) { + return func(tb testing.TB, r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, keys []crypto.PrivKey, log string, event func(string)) (action string, fOp []simulation.FutureOperation, err sdk.Error) { denom := k.GetParams(ctx).BondDenom validatorKey := simulation.RandomKey(r, keys) validatorAddress := sdk.AccAddress(validatorKey.PubKey().Address()) @@ -102,7 +104,9 @@ func SimulateMsgDelegate(m auth.AccountMapper, k stake.Keeper) simulation.Operat ValidatorAddr: validatorAddress, Delegation: sdk.NewCoin(denom, amount), } - require.Nil(t, msg.ValidateBasic(), "expected msg to pass ValidateBasic: %s", msg.GetSignBytes()) + if msg.ValidateBasic() != nil { + tb.Fatalf("expected msg to pass ValidateBasic: %s, log %s", msg.GetSignBytes(), log) + } ctx, write := ctx.CacheContext() result := stake.NewHandler(k)(ctx, msg) if result.IsOK() { @@ -116,7 +120,7 @@ func SimulateMsgDelegate(m auth.AccountMapper, k stake.Keeper) simulation.Operat // SimulateMsgBeginUnbonding func SimulateMsgBeginUnbonding(m auth.AccountMapper, k stake.Keeper) simulation.Operation { - return func(t *testing.T, r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, keys []crypto.PrivKey, log string, event func(string)) (action string, fOp []simulation.FutureOperation, err sdk.Error) { + return func(tb testing.TB, r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, keys []crypto.PrivKey, log string, event func(string)) (action string, fOp []simulation.FutureOperation, err sdk.Error) { denom := k.GetParams(ctx).BondDenom validatorKey := simulation.RandomKey(r, keys) validatorAddress := sdk.AccAddress(validatorKey.PubKey().Address()) @@ -134,7 +138,9 @@ func SimulateMsgBeginUnbonding(m auth.AccountMapper, k stake.Keeper) simulation. ValidatorAddr: validatorAddress, SharesAmount: sdk.NewDecFromInt(amount), } - require.Nil(t, msg.ValidateBasic(), "expected msg to pass ValidateBasic: %s", msg.GetSignBytes()) + if msg.ValidateBasic() != nil { + tb.Fatalf("expected msg to pass ValidateBasic: %s, log %s", msg.GetSignBytes(), log) + } ctx, write := ctx.CacheContext() result := stake.NewHandler(k)(ctx, msg) if result.IsOK() { @@ -148,7 +154,7 @@ func SimulateMsgBeginUnbonding(m auth.AccountMapper, k stake.Keeper) simulation. // SimulateMsgCompleteUnbonding func SimulateMsgCompleteUnbonding(k stake.Keeper) simulation.Operation { - return func(t *testing.T, r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, keys []crypto.PrivKey, log string, event func(string)) (action string, fOp []simulation.FutureOperation, err sdk.Error) { + return func(tb testing.TB, r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, keys []crypto.PrivKey, log string, event func(string)) (action string, fOp []simulation.FutureOperation, err sdk.Error) { validatorKey := simulation.RandomKey(r, keys) validatorAddress := sdk.AccAddress(validatorKey.PubKey().Address()) delegatorKey := simulation.RandomKey(r, keys) @@ -157,7 +163,9 @@ func SimulateMsgCompleteUnbonding(k stake.Keeper) simulation.Operation { DelegatorAddr: delegatorAddress, ValidatorAddr: validatorAddress, } - require.Nil(t, msg.ValidateBasic(), "expected msg to pass ValidateBasic: %s", msg.GetSignBytes()) + if msg.ValidateBasic() != nil { + tb.Fatalf("expected msg to pass ValidateBasic: %s, log %s", msg.GetSignBytes(), log) + } ctx, write := ctx.CacheContext() result := stake.NewHandler(k)(ctx, msg) if result.IsOK() { @@ -171,7 +179,7 @@ func SimulateMsgCompleteUnbonding(k stake.Keeper) simulation.Operation { // SimulateMsgBeginRedelegate func SimulateMsgBeginRedelegate(m auth.AccountMapper, k stake.Keeper) simulation.Operation { - return func(t *testing.T, r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, keys []crypto.PrivKey, log string, event func(string)) (action string, fOp []simulation.FutureOperation, err sdk.Error) { + return func(tb testing.TB, r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, keys []crypto.PrivKey, log string, event func(string)) (action string, fOp []simulation.FutureOperation, err sdk.Error) { denom := k.GetParams(ctx).BondDenom sourceValidatorKey := simulation.RandomKey(r, keys) sourceValidatorAddress := sdk.AccAddress(sourceValidatorKey.PubKey().Address()) @@ -193,7 +201,9 @@ func SimulateMsgBeginRedelegate(m auth.AccountMapper, k stake.Keeper) simulation ValidatorDstAddr: destValidatorAddress, SharesAmount: sdk.NewDecFromInt(amount), } - require.Nil(t, msg.ValidateBasic(), "expected msg to pass ValidateBasic: %s", msg.GetSignBytes()) + if msg.ValidateBasic() != nil { + tb.Fatalf("expected msg to pass ValidateBasic: %s, log %s", msg.GetSignBytes(), log) + } ctx, write := ctx.CacheContext() result := stake.NewHandler(k)(ctx, msg) if result.IsOK() { @@ -207,7 +217,7 @@ func SimulateMsgBeginRedelegate(m auth.AccountMapper, k stake.Keeper) simulation // SimulateMsgCompleteRedelegate func SimulateMsgCompleteRedelegate(k stake.Keeper) simulation.Operation { - return func(t *testing.T, r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, keys []crypto.PrivKey, log string, event func(string)) (action string, fOp []simulation.FutureOperation, err sdk.Error) { + return func(tb testing.TB, r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, keys []crypto.PrivKey, log string, event func(string)) (action string, fOp []simulation.FutureOperation, err sdk.Error) { validatorSrcKey := simulation.RandomKey(r, keys) validatorSrcAddress := sdk.AccAddress(validatorSrcKey.PubKey().Address()) validatorDstKey := simulation.RandomKey(r, keys) @@ -219,7 +229,9 @@ func SimulateMsgCompleteRedelegate(k stake.Keeper) simulation.Operation { ValidatorSrcAddr: validatorSrcAddress, ValidatorDstAddr: validatorDstAddress, } - require.Nil(t, msg.ValidateBasic(), "expected msg to pass ValidateBasic: %s", msg.GetSignBytes()) + if msg.ValidateBasic() != nil { + tb.Fatalf("expected msg to pass ValidateBasic: %s, log %s", msg.GetSignBytes(), log) + } ctx, write := ctx.CacheContext() result := stake.NewHandler(k)(ctx, msg) if result.IsOK() { From 31ca2e058b9d666ac3ea60ad5b399f3f9da28da2 Mon Sep 17 00:00:00 2001 From: ValarDragon Date: Wed, 29 Aug 2018 23:11:14 -0700 Subject: [PATCH 11/52] Update PENDING --- PENDING.md | 2 +- x/mock/simulation/random_simulate_blocks.go | 10 ++-------- 2 files changed, 3 insertions(+), 9 deletions(-) diff --git a/PENDING.md b/PENDING.md index 2e862ead6a..53c7a6b343 100644 --- a/PENDING.md +++ b/PENDING.md @@ -45,7 +45,7 @@ FEATURES * SDK * [querier] added custom querier functionality, so ABCI query requests can be handled by keepers * [simulation] \#1924 allow operations to specify future operations - * [simulation] Add benchmarking capabilities + * [simulation] \#1924 Add benchmarking capabilities * Tendermint diff --git a/x/mock/simulation/random_simulate_blocks.go b/x/mock/simulation/random_simulate_blocks.go index aa4df613dd..ea0078853e 100644 --- a/x/mock/simulation/random_simulate_blocks.go +++ b/x/mock/simulation/random_simulate_blocks.go @@ -85,14 +85,12 @@ func SimulateFromSeed( for i := 0; i < numBlocks; i++ { // Run the BeginBlock handler app.BeginBlock(request) - log += "\nBeginBlock" // Make sure invariants hold at beginning of block AssertAllInvariants(t, app, invariants, log) ctx := app.NewContext(false, header) - thisBlockSize := getBlockSize(r, blockSize) // Run queued operations. Ignores blocksize if blocksize is too small @@ -102,9 +100,10 @@ func SimulateFromSeed( for j := 0; j < thisBlockSize; j++ { logUpdate, futureOps, err := ops[r.Intn(len(ops))](t, r, app, ctx, keys, log, event) log += "\n" + logUpdate + require.Nil(t, err, log) + queueOperations(operationQueue, futureOps) - require.Nil(t, err, log) if onOperation { AssertAllInvariants(t, app, invariants, log) } @@ -118,12 +117,10 @@ func SimulateFromSeed( header.Height++ lastHeaderTime = header.Time header.Time = header.Time.Add(time.Duration(minTimePerBlock) * time.Second).Add(time.Duration(int64(r.Intn(int(timeDiff)))) * time.Second) - log += "\nEndBlock" // Make sure invariants hold at end of block AssertAllInvariants(t, app, invariants, log) - if commit { app.Commit() } @@ -178,12 +175,9 @@ func BenchmarkSimulationFromSeed(b *testing.B, app *baseapp.BaseApp, appStateFn b.ResetTimer() for i := 0; i < numBlocks; i++ { - // Run the BeginBlock handler app.BeginBlock(request) - ctx := app.NewContext(false, header) - thisBlockSize := getBlockSize(r, blockSize) // Run queued operations. Ignores blocksize if blocksize is too small From 703c643fc08487b954ccd750d63d2cd405be61a4 Mon Sep 17 00:00:00 2001 From: HaoyangLiu Date: Thu, 30 Aug 2018 12:50:41 +0800 Subject: [PATCH 12/52] IRISHUB-238: Add multiply store proof build and verification --- client/context/client_manager.go | 46 ++++++++++ client/context/client_manager_test.go | 16 ++++ client/context/context.go | 15 ++++ client/context/query.go | 79 ++++++++++++++++++ client/lcd/root.go | 14 ++++ store/multistoreproof.go | 116 ++++++++++++++++++++++++++ store/multistoreproof_test.go | 96 +++++++++++++++++++++ store/rootmultistore.go | 19 +++++ 8 files changed, 401 insertions(+) create mode 100644 client/context/client_manager.go create mode 100644 client/context/client_manager_test.go create mode 100644 store/multistoreproof.go create mode 100644 store/multistoreproof_test.go diff --git a/client/context/client_manager.go b/client/context/client_manager.go new file mode 100644 index 0000000000..8fffb9d651 --- /dev/null +++ b/client/context/client_manager.go @@ -0,0 +1,46 @@ +package context + +import ( + "github.com/pkg/errors" + rpcclient "github.com/tendermint/tendermint/rpc/client" + "strings" + "sync" +) + +// ClientManager is a manager of a set of rpc clients to full nodes. +// This manager can do load balancing upon these rpc clients. +type ClientManager struct { + clients []rpcclient.Client + currentIndex int + mutex sync.Mutex +} + +// NewClientManager create a new ClientManager +func NewClientManager(nodeURIs string) (*ClientManager, error) { + if nodeURIs != "" { + nodeURLArray := strings.Split(nodeURIs, ",") + var clients []rpcclient.Client + for _, url := range nodeURLArray { + client := rpcclient.NewHTTP(url, "/websocket") + clients = append(clients, client) + } + mgr := &ClientManager{ + currentIndex: 0, + clients: clients, + } + return mgr, nil + } + return nil, errors.New("missing node URIs") +} + +func (mgr *ClientManager) getClient() rpcclient.Client { + mgr.mutex.Lock() + defer mgr.mutex.Unlock() + + client := mgr.clients[mgr.currentIndex] + mgr.currentIndex++ + if mgr.currentIndex >= len(mgr.clients) { + mgr.currentIndex = 0 + } + return client +} diff --git a/client/context/client_manager_test.go b/client/context/client_manager_test.go new file mode 100644 index 0000000000..c060e11cea --- /dev/null +++ b/client/context/client_manager_test.go @@ -0,0 +1,16 @@ +package context + +import ( + "github.com/stretchr/testify/assert" + "testing" +) + +func TestClientManager(t *testing.T) { + nodeURIs := "10.10.10.10:26657,20.20.20.20:26657,30.30.30.30:26657" + clientMgr, err := NewClientManager(nodeURIs) + assert.Empty(t, err) + endpoint := clientMgr.getClient() + assert.NotEqual(t, endpoint, clientMgr.getClient()) + clientMgr.getClient() + assert.Equal(t, endpoint, clientMgr.getClient()) +} \ No newline at end of file diff --git a/client/context/context.go b/client/context/context.go index 743c923552..b57e336657 100644 --- a/client/context/context.go +++ b/client/context/context.go @@ -10,6 +10,7 @@ import ( "github.com/spf13/viper" rpcclient "github.com/tendermint/tendermint/rpc/client" + tendermintLite "github.com/tendermint/tendermint/lite" ) const ctxAccStoreName = "acc" @@ -32,6 +33,8 @@ type CLIContext struct { Async bool JSON bool PrintResponse bool + Certifier tendermintLite.Certifier + ClientManager *ClientManager } // NewCLIContext returns a new initialized CLIContext with parameters from the @@ -117,3 +120,15 @@ func (ctx CLIContext) WithUseLedger(useLedger bool) CLIContext { ctx.UseLedger = useLedger return ctx } + +// WithCertifier - return a copy of the context with an updated Certifier +func (ctx CLIContext) WithCertifier(certifier tendermintLite.Certifier) CLIContext { + ctx.Certifier = certifier + return ctx +} + +// WithClientManager - return a copy of the context with an updated ClientManager +func (ctx CLIContext) WithClientManager(clientManager *ClientManager) CLIContext { + ctx.ClientManager = clientManager + return ctx +} diff --git a/client/context/query.go b/client/context/query.go index e526c0abbc..72defb0d4a 100644 --- a/client/context/query.go +++ b/client/context/query.go @@ -13,6 +13,11 @@ import ( cmn "github.com/tendermint/tendermint/libs/common" rpcclient "github.com/tendermint/tendermint/rpc/client" ctypes "github.com/tendermint/tendermint/rpc/core/types" + "github.com/cosmos/cosmos-sdk/store" + "github.com/cosmos/cosmos-sdk/wire" + "strings" + tendermintLiteProxy "github.com/tendermint/tendermint/lite/proxy" + abci "github.com/tendermint/tendermint/abci/types" ) // GetNode returns an RPC client. If the context's client is not defined, an @@ -304,12 +309,86 @@ 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 + if ctx.TrustNode || !isQueryStoreWithProof(path) { + return resp.Value, nil + } + + err = ctx.verifyProof(path, resp) + if err != nil { + return nil, err + } + return resp.Value, nil } +// verifyProof perform response proof verification +func (ctx CLIContext) verifyProof(path string, resp abci.ResponseQuery) error { + + // TODO: Later we consider to return error for missing valid certifier to verify data from untrusted node + if ctx.Certifier == nil { + if ctx.Logger != nil { + io.WriteString(ctx.Logger, fmt.Sprintf("Missing valid certifier to verify data from untrusted node\n")) + } + return nil + } + + node, err := ctx.GetNode() + if err != nil { + return err + } + + // TODO: need improvement + // If the the node http client connect to a full node which can't produce or receive new blocks, + // then here the code will wait for a while and return error if time is out. + // AppHash for height H is in header H+1 + commit, err := tendermintLiteProxy.GetCertifiedCommit(resp.Height+1, node, ctx.Certifier) + if err != nil { + return err + } + + var multiStoreProof store.MultiStoreProof + cdc := wire.NewCodec() + err = cdc.UnmarshalBinary(resp.Proof, &multiStoreProof) + if err != nil { + return errors.Wrap(err, "failed to unmarshalBinary rangeProof") + } + + // Validate the substore commit hash against trusted appHash + substoreCommitHash, err := store.VerifyMultiStoreCommitInfo(multiStoreProof.StoreName, + multiStoreProof.CommitIDList, commit.Header.AppHash) + if err != nil { + return errors.Wrap(err, "failed in verifying the proof against appHash") + } + err = store.VerifyRangeProof(resp.Key, resp.Value, substoreCommitHash, &multiStoreProof.RangeProof) + if err != nil { + return errors.Wrap(err, "failed in the range proof verification") + } + return nil +} + // queryStore performs a query from a Tendermint node with the provided a store // name and path. func (ctx CLIContext) queryStore(key cmn.HexBytes, storeName, endPath string) ([]byte, error) { path := fmt.Sprintf("/store/%s/%s", storeName, endPath) return ctx.query(path, key) } + +// isQueryStoreWithProof expects a format like /// +// queryType can be app or store +// if subpath equals to "/store" or "/key", then return true +func isQueryStoreWithProof(path string) (bool) { + if !strings.HasPrefix(path, "/") { + return false + } + paths := strings.SplitN(path[1:], "/", 3) + if len(paths) != 3 { + return false + } + // Currently, only when query subpath is "/store" or "/key", will proof be included in response. + // If there are some changes about proof building in iavlstore.go, we must change code here to keep consistency with iavlstore.go + if paths[2] == "store" || paths[2] == "key" { + return true + } + return false +} diff --git a/client/lcd/root.go b/client/lcd/root.go index bfa62f1cf0..71c9a51144 100644 --- a/client/lcd/root.go +++ b/client/lcd/root.go @@ -22,6 +22,9 @@ import ( cmn "github.com/tendermint/tendermint/libs/common" "github.com/tendermint/tendermint/libs/log" tmserver "github.com/tendermint/tendermint/rpc/lib/server" + tendermintLiteProxy "github.com/tendermint/tendermint/lite/proxy" + "github.com/tendermint/tendermint/libs/cli" + tendermintLite "github.com/tendermint/tendermint/lite" ) // ServeCommand will generate a long-running rest server @@ -80,6 +83,17 @@ func createHandler(cdc *wire.Codec) http.Handler { cliCtx := context.NewCLIContext().WithCodec(cdc).WithLogger(os.Stdout) + chainID := viper.GetString(client.FlagChainID) + home := viper.GetString(cli.HomeFlag) + nodeURI := viper.GetString(client.FlagNode) + var certifier tendermintLite.Certifier + if chainID != "" && home != "" && nodeURI != ""{ + certifier, err = tendermintLiteProxy.GetCertifier(chainID, home, nodeURI) + if err != nil { + panic(err) + } + cliCtx = cliCtx.WithCertifier(certifier) + } // TODO: make more functional? aka r = keys.RegisterRoutes(r) r.HandleFunc("/version", CLIVersionRequestHandler).Methods("GET") r.HandleFunc("/node_version", NodeVersionRequestHandler(cliCtx)).Methods("GET") diff --git a/store/multistoreproof.go b/store/multistoreproof.go new file mode 100644 index 0000000000..e98a198e93 --- /dev/null +++ b/store/multistoreproof.go @@ -0,0 +1,116 @@ +package store + +import ( + "bytes" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/pkg/errors" + "github.com/tendermint/iavl" + cmn "github.com/tendermint/tendermint/libs/common" +) + +// commitID of substores, such as acc store, gov store +type SubstoreCommitID struct { + Name string `json:"name"` + Version int64 `json:"version"` + CommitHash cmn.HexBytes `json:"commit_hash"` +} + +// proof of store which have multi substores +type MultiStoreProof struct { + CommitIDList []SubstoreCommitID `json:"commit_id_list"` + StoreName string `json:"store_name"` + RangeProof iavl.RangeProof `json:"range_proof"` +} + +// build MultiStoreProof based on iavl proof and storeInfos +func BuildMultiStoreProof(iavlProof []byte, storeName string, storeInfos []storeInfo) ([]byte, error) { + var rangeProof iavl.RangeProof + err := cdc.UnmarshalBinary(iavlProof, &rangeProof) + if err != nil { + return nil, err + } + + var multiStoreProof MultiStoreProof + for _, storeInfo := range storeInfos { + + commitID := SubstoreCommitID{ + Name: storeInfo.Name, + Version: storeInfo.Core.CommitID.Version, + CommitHash: storeInfo.Core.CommitID.Hash, + } + multiStoreProof.CommitIDList = append(multiStoreProof.CommitIDList, commitID) + } + multiStoreProof.StoreName = storeName + multiStoreProof.RangeProof = rangeProof + + proof, err := cdc.MarshalBinary(multiStoreProof) + if err != nil { + return nil, err + } + + return proof, nil +} + +// verify multiStoreCommitInfo against appHash +func VerifyMultiStoreCommitInfo(storeName string, multiStoreCommitInfo []SubstoreCommitID, appHash []byte) ([]byte, error) { + var substoreCommitHash []byte + var storeInfos []storeInfo + var height int64 + for _, multiStoreCommitID := range multiStoreCommitInfo { + + if multiStoreCommitID.Name == storeName { + substoreCommitHash = multiStoreCommitID.CommitHash + height = multiStoreCommitID.Version + } + storeInfo := storeInfo{ + Name: multiStoreCommitID.Name, + Core: storeCore{ + CommitID: sdk.CommitID{ + Version: multiStoreCommitID.Version, + Hash: multiStoreCommitID.CommitHash, + }, + }, + } + + storeInfos = append(storeInfos, storeInfo) + } + if len(substoreCommitHash) == 0 { + return nil, cmn.NewError("failed to get substore root commit hash by store name") + } + + ci := commitInfo{ + Version: height, + StoreInfos: storeInfos, + } + + if !bytes.Equal(appHash, ci.Hash()) { + return nil, cmn.NewError("the merkle root of multiStoreCommitInfo doesn't equal to appHash") + } + return substoreCommitHash, nil +} + +// verify iavl proof +func VerifyRangeProof(key, value []byte, substoreCommitHash []byte, rangeProof *iavl.RangeProof) error { + + // Validate the proof to ensure data integrity. + err := rangeProof.Verify(substoreCommitHash) + if err != nil { + return errors.Wrap(err, "proof root hash doesn't equal to substore commit root hash") + } + + if len(value) != 0 { + // Validate existence proof + err = rangeProof.VerifyItem(key, value) + if err != nil { + return errors.Wrap(err, "failed in existence verification") + } + } else { + // Validate absence proof + err = rangeProof.VerifyAbsence(key) + if err != nil { + return errors.Wrap(err, "failed in absence verification") + } + } + + return nil +} \ No newline at end of file diff --git a/store/multistoreproof_test.go b/store/multistoreproof_test.go new file mode 100644 index 0000000000..b1667c7265 --- /dev/null +++ b/store/multistoreproof_test.go @@ -0,0 +1,96 @@ +package store + +import ( + "encoding/hex" + "github.com/stretchr/testify/assert" + "github.com/tendermint/iavl" + cmn "github.com/tendermint/tendermint/libs/common" + "testing" +) + +func TestVerifyMultiStoreCommitInfo(t *testing.T) { + appHash, _ := hex.DecodeString("ebf3c1fb724d3458023c8fefef7b33add2fc1e84") + + substoreRootHash, _ := hex.DecodeString("ea5d468431015c2cd6295e9a0bb1fc0e49033828") + storeName := "acc" + + var multiStoreCommitInfo []SubstoreCommitID + + gocRootHash, _ := hex.DecodeString("62c171bb022e47d1f745608ff749e676dbd25f78") + multiStoreCommitInfo = append(multiStoreCommitInfo, SubstoreCommitID{ + Name: "gov", + Version: 689, + CommitHash: gocRootHash, + }) + + multiStoreCommitInfo = append(multiStoreCommitInfo, SubstoreCommitID{ + Name: "main", + Version: 689, + CommitHash: nil, + }) + + accRootHash, _ := hex.DecodeString("ea5d468431015c2cd6295e9a0bb1fc0e49033828") + multiStoreCommitInfo = append(multiStoreCommitInfo, SubstoreCommitID{ + Name: "acc", + Version: 689, + CommitHash: accRootHash, + }) + + multiStoreCommitInfo = append(multiStoreCommitInfo, SubstoreCommitID{ + Name: "ibc", + Version: 689, + CommitHash: nil, + }) + + stakeRootHash, _ := hex.DecodeString("987d1d27b8771d93aa3691262f661d2c85af7ca4") + multiStoreCommitInfo = append(multiStoreCommitInfo, SubstoreCommitID{ + Name: "stake", + Version: 689, + CommitHash: stakeRootHash, + }) + + slashingRootHash, _ := hex.DecodeString("388ee6e5b11f367069beb1eefd553491afe9d73e") + multiStoreCommitInfo = append(multiStoreCommitInfo, SubstoreCommitID{ + Name: "slashing", + Version: 689, + CommitHash: slashingRootHash, + }) + + commitHash, err := VerifyMultiStoreCommitInfo(storeName, multiStoreCommitInfo, appHash) + assert.Nil(t, err) + assert.Equal(t, commitHash, substoreRootHash) + + appHash, _ = hex.DecodeString("29de216bf5e2531c688de36caaf024cd3bb09ee3") + + _, err = VerifyMultiStoreCommitInfo(storeName, multiStoreCommitInfo, appHash) + assert.Error(t, err, "appHash doesn't match to the merkle root of multiStoreCommitInfo") +} + +func TestVerifyRangeProof(t *testing.T) { + tree := iavl.NewTree(nil, 0) + + rand := cmn.NewRand() + rand.Seed(0) // for determinism + for _, ikey := range []byte{0x11, 0x32, 0x50, 0x72, 0x99} { + key := []byte{ikey} + tree.Set(key, []byte(rand.Str(8))) + } + + root := tree.Hash() + + key := []byte{0x32} + val, proof, err := tree.GetWithProof(key) + assert.Nil(t, err) + assert.NotEmpty(t, val) + assert.NotEmpty(t, proof) + err = VerifyRangeProof(key, val, root, proof) + assert.Nil(t, err) + + key = []byte{0x40} + val, proof, err = tree.GetWithProof(key) + assert.Nil(t, err) + assert.Empty(t, val) + assert.NotEmpty(t, proof) + err = VerifyRangeProof(key, val, root, proof) + assert.Nil(t, err) +} \ No newline at end of file diff --git a/store/rootmultistore.go b/store/rootmultistore.go index 04f8e44e69..2a9a9373a4 100644 --- a/store/rootmultistore.go +++ b/store/rootmultistore.go @@ -291,6 +291,25 @@ func (rs *rootMultiStore) Query(req abci.RequestQuery) abci.ResponseQuery { // trim the path and make the query req.Path = subpath res := queryable.Query(req) + + + // Currently, only when query subpath is "/store" or "/key", will proof be included in response. + // If there are some changes about proof building in iavlstore.go, we must change code here to keep consistency with iavlstore.go + if !req.Prove || subpath != "/store" && subpath != "/key" { + return res + } + + //Load commit info from db + commitInfo, errMsg := getCommitInfo(rs.db,res.Height) + if errMsg != nil { + return sdk.ErrInternal(errMsg.Error()).QueryResult() + } + + res.Proof, errMsg = BuildMultiStoreProof(res.Proof, storeName, commitInfo.StoreInfos) + if errMsg != nil { + return sdk.ErrInternal(errMsg.Error()).QueryResult() + } + return res } From 5473771105b5db36c093a6a6de649240cd40e3bb Mon Sep 17 00:00:00 2001 From: HaoyangLiu Date: Thu, 30 Aug 2018 15:08:10 +0800 Subject: [PATCH 13/52] IRISHUB-238 :add trust option for rest-server command --- client/lcd/root.go | 1 + 1 file changed, 1 insertion(+) diff --git a/client/lcd/root.go b/client/lcd/root.go index 71c9a51144..75cf3632ea 100644 --- a/client/lcd/root.go +++ b/client/lcd/root.go @@ -69,6 +69,7 @@ func ServeCommand(cdc *wire.Codec) *cobra.Command { cmd.Flags().String(client.FlagChainID, "", "The chain ID to connect to") 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, "Whether trust connected full node") return cmd } From 03d2f7331b418d68f0b3e4f5448754e4c27771f5 Mon Sep 17 00:00:00 2001 From: ValarDragon Date: Thu, 30 Aug 2018 00:13:31 -0700 Subject: [PATCH 14/52] Remove code duplication --- cmd/gaia/app/sim_test.go | 3 +- x/mock/simulation/random_simulate_blocks.go | 125 +++++++------------- x/stake/simulation/msgs.go | 21 ++-- 3 files changed, 61 insertions(+), 88 deletions(-) diff --git a/cmd/gaia/app/sim_test.go b/cmd/gaia/app/sim_test.go index 2bad6aba23..1fd07b3302 100644 --- a/cmd/gaia/app/sim_test.go +++ b/cmd/gaia/app/sim_test.go @@ -123,10 +123,11 @@ func BenchmarkFullGaiaSimulation(b *testing.B) { // Run randomized simulation // TODO parameterize numbers, save for a later PR - simulation.BenchmarkSimulationFromSeed( + simulation.SimulateFromSeed( b, app.BaseApp, appStateFn, seed, testAndRunTxs(app), []simulation.RandSetup{}, + invariants(app), // these shouldn't get ran 10, 100, false, diff --git a/x/mock/simulation/random_simulate_blocks.go b/x/mock/simulation/random_simulate_blocks.go index ea0078853e..6376d53dcb 100644 --- a/x/mock/simulation/random_simulate_blocks.go +++ b/x/mock/simulation/random_simulate_blocks.go @@ -17,7 +17,6 @@ import ( "github.com/cosmos/cosmos-sdk/baseapp" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/x/mock" - "github.com/stretchr/testify/require" ) // Simulate tests application by sending random messages. @@ -52,13 +51,22 @@ 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( - t *testing.T, app *baseapp.BaseApp, appStateFn func(r *rand.Rand, keys []crypto.PrivKey, accs []sdk.AccAddress) json.RawMessage, seed int64, ops []Operation, setups []RandSetup, + tb testing.TB, app *baseapp.BaseApp, appStateFn func(r *rand.Rand, keys []crypto.PrivKey, accs []sdk.AccAddress) json.RawMessage, seed int64, ops []Operation, setups []RandSetup, invariants []Invariant, numBlocks int, blockSize int, commit bool, ) { + var t *testing.T + var b *testing.B + testingmode := false + if _t, ok := tb.(*testing.T); ok { + t = _t + testingmode = true + } else { + b = tb.(*testing.B) + } log := fmt.Sprintf("Starting SimulateFromSeed with randomness created with seed %d", int(seed)) r := rand.New(rand.NewSource(seed)) timestamp := randTimestamp(r) - log = fmt.Sprintf("%s\nStarting the simulation from time %v, unixtime %v", log, timestamp.UTC().Format(time.UnixDate), timestamp.Unix()) + log = updateLog(testingmode, log, "Starting the simulation from time %v, unixtime %v", timestamp.UTC().Format(time.UnixDate), timestamp.Unix()) fmt.Printf("%s\n", log) timeDiff := maxTimePerBlock - minTimePerBlock @@ -67,7 +75,7 @@ func SimulateFromSeed( // Setup event stats events := make(map[string]uint) event := func(what string) { - log += "\nevent - " + what + log = updateLog(testingmode, log, "event - "+what) events[what]++ } @@ -82,32 +90,40 @@ func SimulateFromSeed( // These are operations which have been queued by previous operations operationQueue := make(map[int][]Operation) + if !testingmode { + b.ResetTimer() + } + for i := 0; i < numBlocks; i++ { // Run the BeginBlock handler app.BeginBlock(request) - log += "\nBeginBlock" + log = updateLog(testingmode, log, "BeginBlock") - // Make sure invariants hold at beginning of block - AssertAllInvariants(t, app, invariants, log) + if testingmode { + // Make sure invariants hold at beginning of block + AssertAllInvariants(t, app, invariants, log) + } ctx := app.NewContext(false, header) thisBlockSize := getBlockSize(r, blockSize) // Run queued operations. Ignores blocksize if blocksize is too small - log, numQueuedOpsRan := runQueuedOperations(operationQueue, int(header.Height), t, r, app, ctx, keys, log, event) + log, numQueuedOpsRan := runQueuedOperations(operationQueue, int(header.Height), tb, r, app, ctx, keys, log, event) opCount += numQueuedOpsRan thisBlockSize -= numQueuedOpsRan for j := 0; j < thisBlockSize; j++ { - logUpdate, futureOps, err := ops[r.Intn(len(ops))](t, r, app, ctx, keys, log, event) - log += "\n" + logUpdate - require.Nil(t, err, log) + logUpdate, futureOps, err := ops[r.Intn(len(ops))](tb, r, app, ctx, keys, log, event) + log = updateLog(testingmode, log, logUpdate) + if err != nil { + tb.Fatalf("error on operation %d, %v", opCount, err) + } queueOperations(operationQueue, futureOps) - if onOperation { + if onOperation && testingmode { AssertAllInvariants(t, app, invariants, log) } - if opCount%200 == 0 { + if testingmode && opCount%200 == 0 { fmt.Printf("\rSimulating... block %d/%d, operation %d.", header.Height, numBlocks, opCount) } opCount++ @@ -119,8 +135,10 @@ func SimulateFromSeed( header.Time = header.Time.Add(time.Duration(minTimePerBlock) * time.Second).Add(time.Duration(int64(r.Intn(int(timeDiff)))) * time.Second) log += "\nEndBlock" - // Make sure invariants hold at end of block - AssertAllInvariants(t, app, invariants, log) + if testingmode { + // Make sure invariants hold at end of block + AssertAllInvariants(t, app, invariants, log) + } if commit { app.Commit() } @@ -129,13 +147,23 @@ func SimulateFromSeed( request = RandomRequestBeginBlock(r, validators, livenessTransitionMatrix, evidenceFraction, lastHeaderTime, event, header, log) // Update the validator set - validators = updateValidators(t, r, validators, res.ValidatorUpdates, event) + validators = updateValidators(tb, r, validators, res.ValidatorUpdates, event) } - fmt.Printf("\nSimulation complete. Final height (blocks): %d, final time (seconds): %v\n", header.Height, header.Time) + if testingmode { + fmt.Printf("\nSimulation complete. Final height (blocks): %d, final time (seconds): %v\n", header.Height, header.Time) + } DisplayEvents(events) } +func updateLog(testingmode bool, log string, update string, args ...interface{}) (updatedLog string) { + if testingmode == true { + update = fmt.Sprintf(update, args) + return fmt.Sprintf("%s\n%s", log, update) + } + return "" +} + func getBlockSize(r *rand.Rand, blockSize int) int { load := r.Float64() switch { @@ -148,69 +176,6 @@ func getBlockSize(r *rand.Rand, blockSize int) int { } } -// Simulate from seed, benchmarks -func BenchmarkSimulationFromSeed(b *testing.B, app *baseapp.BaseApp, appStateFn func(r *rand.Rand, keys []crypto.PrivKey, accs []sdk.AccAddress) json.RawMessage, - seed int64, ops []Operation, setups []RandSetup, numBlocks int, blockSize int, commit bool) { - r := rand.New(rand.NewSource(seed)) - timestamp := randTimestamp(r) - timeDiff := maxTimePerBlock - minTimePerBlock - keys, accs := mock.GeneratePrivKeyAddressPairsFromRand(r, numKeys) - - // Setup event stats - events := make(map[string]uint) - event := func(what string) { - events[what]++ - } - - validators := initChain(r, keys, accs, setups, app, appStateFn) - - header := abci.Header{Height: 0, Time: timestamp} - opCount := 0 - - request := abci.RequestBeginBlock{Header: header} - - var lastHeaderTime time.Time - // These are operations which have been queued by previous operations - operationQueue := make(map[int][]Operation) - b.ResetTimer() - - for i := 0; i < numBlocks; i++ { - // Run the BeginBlock handler - app.BeginBlock(request) - ctx := app.NewContext(false, header) - thisBlockSize := getBlockSize(r, blockSize) - - // Run queued operations. Ignores blocksize if blocksize is too small - log, numQueuedOpsRan := runQueuedOperations(operationQueue, int(header.Height), b, r, app, ctx, keys, "", event) - opCount += numQueuedOpsRan - thisBlockSize -= numQueuedOpsRan - for j := 0; j < thisBlockSize; j++ { - _, futureOps, err := ops[r.Intn(len(ops))](b, r, app, ctx, keys, "", event) - queueOperations(operationQueue, futureOps) - if err != nil { - b.Fatalf("error on operation %d, %v", opCount, err) - } - opCount++ - } - - res := app.EndBlock(abci.RequestEndBlock{}) - header.Height++ - lastHeaderTime = header.Time - header.Time = header.Time.Add(time.Duration(minTimePerBlock) * time.Second).Add(time.Duration(int64(r.Intn(int(timeDiff)))) * time.Second) - if commit { - app.Commit() - } - - // Generate a random RequestBeginBlock with the current validator set for the next block - request = RandomRequestBeginBlock(r, validators, livenessTransitionMatrix, evidenceFraction, lastHeaderTime, event, header, log) - - // Update the validator set - validators = updateValidators(b, r, validators, res.ValidatorUpdates, event) - } - DisplayEvents(events) - fmt.Printf("Benchmark simulation ran %d operations\n", opCount) -} - // adds all future operations into the operation queue. func queueOperations(queuedOperations map[int][]Operation, futureOperations []FutureOperation) { if futureOperations == nil { diff --git a/x/stake/simulation/msgs.go b/x/stake/simulation/msgs.go index bc1063494d..033487254f 100644 --- a/x/stake/simulation/msgs.go +++ b/x/stake/simulation/msgs.go @@ -17,6 +17,7 @@ import ( // SimulateMsgCreateValidator func SimulateMsgCreateValidator(m auth.AccountMapper, k stake.Keeper) simulation.Operation { + handler := stake.NewHandler(k) return func(tb testing.TB, r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, keys []crypto.PrivKey, log string, event func(string)) (action string, fOp []simulation.FutureOperation, err sdk.Error) { denom := k.GetParams(ctx).BondDenom description := stake.Description{ @@ -43,7 +44,7 @@ func SimulateMsgCreateValidator(m auth.AccountMapper, k stake.Keeper) simulation tb.Fatalf("expected msg to pass ValidateBasic: %s, log %s", msg.GetSignBytes(), log) } ctx, write := ctx.CacheContext() - result := stake.NewHandler(k)(ctx, msg) + result := handler(ctx, msg) if result.IsOK() { write() } @@ -56,6 +57,7 @@ func SimulateMsgCreateValidator(m auth.AccountMapper, k stake.Keeper) simulation // SimulateMsgEditValidator func SimulateMsgEditValidator(k stake.Keeper) simulation.Operation { + handler := stake.NewHandler(k) return func(tb testing.TB, r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, keys []crypto.PrivKey, log string, event func(string)) (action string, fOp []simulation.FutureOperation, err sdk.Error) { description := stake.Description{ Moniker: simulation.RandStringOfLength(r, 10), @@ -74,7 +76,7 @@ func SimulateMsgEditValidator(k stake.Keeper) simulation.Operation { tb.Fatalf("expected msg to pass ValidateBasic: %s, log %s", msg.GetSignBytes(), log) } ctx, write := ctx.CacheContext() - result := stake.NewHandler(k)(ctx, msg) + result := handler(ctx, msg) if result.IsOK() { write() } @@ -86,6 +88,7 @@ func SimulateMsgEditValidator(k stake.Keeper) simulation.Operation { // SimulateMsgDelegate func SimulateMsgDelegate(m auth.AccountMapper, k stake.Keeper) simulation.Operation { + handler := stake.NewHandler(k) return func(tb testing.TB, r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, keys []crypto.PrivKey, log string, event func(string)) (action string, fOp []simulation.FutureOperation, err sdk.Error) { denom := k.GetParams(ctx).BondDenom validatorKey := simulation.RandomKey(r, keys) @@ -108,7 +111,7 @@ func SimulateMsgDelegate(m auth.AccountMapper, k stake.Keeper) simulation.Operat tb.Fatalf("expected msg to pass ValidateBasic: %s, log %s", msg.GetSignBytes(), log) } ctx, write := ctx.CacheContext() - result := stake.NewHandler(k)(ctx, msg) + result := handler(ctx, msg) if result.IsOK() { write() } @@ -120,6 +123,7 @@ 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(tb testing.TB, r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, keys []crypto.PrivKey, log string, event func(string)) (action string, fOp []simulation.FutureOperation, err sdk.Error) { denom := k.GetParams(ctx).BondDenom validatorKey := simulation.RandomKey(r, keys) @@ -142,7 +146,7 @@ func SimulateMsgBeginUnbonding(m auth.AccountMapper, k stake.Keeper) simulation. tb.Fatalf("expected msg to pass ValidateBasic: %s, log %s", msg.GetSignBytes(), log) } ctx, write := ctx.CacheContext() - result := stake.NewHandler(k)(ctx, msg) + result := handler(ctx, msg) if result.IsOK() { write() } @@ -154,6 +158,7 @@ func SimulateMsgBeginUnbonding(m auth.AccountMapper, k stake.Keeper) simulation. // SimulateMsgCompleteUnbonding func SimulateMsgCompleteUnbonding(k stake.Keeper) simulation.Operation { + handler := stake.NewHandler(k) return func(tb testing.TB, r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, keys []crypto.PrivKey, log string, event func(string)) (action string, fOp []simulation.FutureOperation, err sdk.Error) { validatorKey := simulation.RandomKey(r, keys) validatorAddress := sdk.AccAddress(validatorKey.PubKey().Address()) @@ -167,7 +172,7 @@ func SimulateMsgCompleteUnbonding(k stake.Keeper) simulation.Operation { tb.Fatalf("expected msg to pass ValidateBasic: %s, log %s", msg.GetSignBytes(), log) } ctx, write := ctx.CacheContext() - result := stake.NewHandler(k)(ctx, msg) + result := handler(ctx, msg) if result.IsOK() { write() } @@ -179,6 +184,7 @@ func SimulateMsgCompleteUnbonding(k stake.Keeper) simulation.Operation { // SimulateMsgBeginRedelegate func SimulateMsgBeginRedelegate(m auth.AccountMapper, k stake.Keeper) simulation.Operation { + handler := stake.NewHandler(k) return func(tb testing.TB, r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, keys []crypto.PrivKey, log string, event func(string)) (action string, fOp []simulation.FutureOperation, err sdk.Error) { denom := k.GetParams(ctx).BondDenom sourceValidatorKey := simulation.RandomKey(r, keys) @@ -205,7 +211,7 @@ func SimulateMsgBeginRedelegate(m auth.AccountMapper, k stake.Keeper) simulation tb.Fatalf("expected msg to pass ValidateBasic: %s, log %s", msg.GetSignBytes(), log) } ctx, write := ctx.CacheContext() - result := stake.NewHandler(k)(ctx, msg) + result := handler(ctx, msg) if result.IsOK() { write() } @@ -217,6 +223,7 @@ func SimulateMsgBeginRedelegate(m auth.AccountMapper, k stake.Keeper) simulation // SimulateMsgCompleteRedelegate func SimulateMsgCompleteRedelegate(k stake.Keeper) simulation.Operation { + handler := stake.NewHandler(k) return func(tb testing.TB, r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, keys []crypto.PrivKey, log string, event func(string)) (action string, fOp []simulation.FutureOperation, err sdk.Error) { validatorSrcKey := simulation.RandomKey(r, keys) validatorSrcAddress := sdk.AccAddress(validatorSrcKey.PubKey().Address()) @@ -233,7 +240,7 @@ func SimulateMsgCompleteRedelegate(k stake.Keeper) simulation.Operation { tb.Fatalf("expected msg to pass ValidateBasic: %s, log %s", msg.GetSignBytes(), log) } ctx, write := ctx.CacheContext() - result := stake.NewHandler(k)(ctx, msg) + result := handler(ctx, msg) if result.IsOK() { write() } From b3d08bcb239198709191895ee15c86a09434ed08 Mon Sep 17 00:00:00 2001 From: ValarDragon Date: Thu, 30 Aug 2018 00:35:02 -0700 Subject: [PATCH 15/52] minor cleanup --- x/mock/simulation/random_simulate_blocks.go | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/x/mock/simulation/random_simulate_blocks.go b/x/mock/simulation/random_simulate_blocks.go index 6376d53dcb..e609d3c5f9 100644 --- a/x/mock/simulation/random_simulate_blocks.go +++ b/x/mock/simulation/random_simulate_blocks.go @@ -75,7 +75,7 @@ func SimulateFromSeed( // Setup event stats events := make(map[string]uint) event := func(what string) { - log = updateLog(testingmode, log, "event - "+what) + log = updateLog(testingmode, log, "event - %s", what) events[what]++ } @@ -119,12 +119,13 @@ func SimulateFromSeed( } queueOperations(operationQueue, futureOps) - - if onOperation && testingmode { - AssertAllInvariants(t, app, invariants, log) - } - if testingmode && opCount%200 == 0 { - fmt.Printf("\rSimulating... block %d/%d, operation %d.", header.Height, numBlocks, opCount) + if testingmode { + if onOperation { + AssertAllInvariants(t, app, invariants, log) + } + if opCount%200 == 0 { + fmt.Printf("\rSimulating... block %d/%d, operation %d.", header.Height, numBlocks, opCount) + } } opCount++ } @@ -133,7 +134,7 @@ func SimulateFromSeed( header.Height++ lastHeaderTime = header.Time header.Time = header.Time.Add(time.Duration(minTimePerBlock) * time.Second).Add(time.Duration(int64(r.Intn(int(timeDiff)))) * time.Second) - log += "\nEndBlock" + log = updateLog(testingmode, log, "EndBlock") if testingmode { // Make sure invariants hold at end of block @@ -152,13 +153,15 @@ func SimulateFromSeed( if testingmode { fmt.Printf("\nSimulation complete. Final height (blocks): %d, final time (seconds): %v\n", header.Height, header.Time) + } else { + fmt.Printf("%d operations ran\n", opCount) } DisplayEvents(events) } func updateLog(testingmode bool, log string, update string, args ...interface{}) (updatedLog string) { if testingmode == true { - update = fmt.Sprintf(update, args) + update = fmt.Sprintf(update, args...) return fmt.Sprintf("%s\n%s", log, update) } return "" From b878edc388788542b55398c1923b742e63c4ecc2 Mon Sep 17 00:00:00 2001 From: HaoyangLiu Date: Thu, 30 Aug 2018 15:52:17 +0800 Subject: [PATCH 16/52] IRISHUB-238: fix failures in test_lint and test_cover --- client/context/client_manager_test.go | 2 +- client/context/context.go | 2 +- client/context/query.go | 20 ++++++++++---------- client/lcd/test_helpers.go | 2 ++ store/multistoreproof.go | 2 +- store/multistoreproof_test.go | 2 +- store/rootmultistore.go | 3 +-- 7 files changed, 17 insertions(+), 16 deletions(-) diff --git a/client/context/client_manager_test.go b/client/context/client_manager_test.go index c060e11cea..1960f74ced 100644 --- a/client/context/client_manager_test.go +++ b/client/context/client_manager_test.go @@ -13,4 +13,4 @@ func TestClientManager(t *testing.T) { assert.NotEqual(t, endpoint, clientMgr.getClient()) clientMgr.getClient() assert.Equal(t, endpoint, clientMgr.getClient()) -} \ No newline at end of file +} diff --git a/client/context/context.go b/client/context/context.go index b57e336657..28506756cf 100644 --- a/client/context/context.go +++ b/client/context/context.go @@ -9,8 +9,8 @@ import ( "github.com/spf13/viper" - rpcclient "github.com/tendermint/tendermint/rpc/client" tendermintLite "github.com/tendermint/tendermint/lite" + rpcclient "github.com/tendermint/tendermint/rpc/client" ) const ctxAccStoreName = "acc" diff --git a/client/context/query.go b/client/context/query.go index 72defb0d4a..aac0c90306 100644 --- a/client/context/query.go +++ b/client/context/query.go @@ -10,14 +10,14 @@ import ( "github.com/pkg/errors" - cmn "github.com/tendermint/tendermint/libs/common" - rpcclient "github.com/tendermint/tendermint/rpc/client" - ctypes "github.com/tendermint/tendermint/rpc/core/types" "github.com/cosmos/cosmos-sdk/store" "github.com/cosmos/cosmos-sdk/wire" - "strings" - tendermintLiteProxy "github.com/tendermint/tendermint/lite/proxy" abci "github.com/tendermint/tendermint/abci/types" + cmn "github.com/tendermint/tendermint/libs/common" + tendermintLiteProxy "github.com/tendermint/tendermint/lite/proxy" + rpcclient "github.com/tendermint/tendermint/rpc/client" + ctypes "github.com/tendermint/tendermint/rpc/core/types" + "strings" ) // GetNode returns an RPC client. If the context's client is not defined, an @@ -351,18 +351,18 @@ func (ctx CLIContext) verifyProof(path string, resp abci.ResponseQuery) error { cdc := wire.NewCodec() err = cdc.UnmarshalBinary(resp.Proof, &multiStoreProof) if err != nil { - return errors.Wrap(err, "failed to unmarshalBinary rangeProof") + return errors.Wrap(err, "failed to unmarshalBinary rangeProof") } // Validate the substore commit hash against trusted appHash - substoreCommitHash, err := store.VerifyMultiStoreCommitInfo(multiStoreProof.StoreName, + substoreCommitHash, err := store.VerifyMultiStoreCommitInfo(multiStoreProof.StoreName, multiStoreProof.CommitIDList, commit.Header.AppHash) if err != nil { - return errors.Wrap(err, "failed in verifying the proof against appHash") + return errors.Wrap(err, "failed in verifying the proof against appHash") } err = store.VerifyRangeProof(resp.Key, resp.Value, substoreCommitHash, &multiStoreProof.RangeProof) if err != nil { - return errors.Wrap(err, "failed in the range proof verification") + return errors.Wrap(err, "failed in the range proof verification") } return nil } @@ -377,7 +377,7 @@ func (ctx CLIContext) queryStore(key cmn.HexBytes, storeName, endPath string) ([ // isQueryStoreWithProof expects a format like /// // queryType can be app or store // if subpath equals to "/store" or "/key", then return true -func isQueryStoreWithProof(path string) (bool) { +func isQueryStoreWithProof(path string) bool { if !strings.HasPrefix(path, "/") { return false } diff --git a/client/lcd/test_helpers.go b/client/lcd/test_helpers.go index 818eae1e86..8a344976a1 100644 --- a/client/lcd/test_helpers.go +++ b/client/lcd/test_helpers.go @@ -37,6 +37,7 @@ import ( "github.com/tendermint/tendermint/proxy" tmrpc "github.com/tendermint/tendermint/rpc/lib/server" tmtypes "github.com/tendermint/tendermint/types" + "time" ) // makePathname creates a unique pathname for each test. It will panic if it @@ -190,6 +191,7 @@ func InitializeTestLCD(t *testing.T, nValidators int, initAddrs []sdk.AccAddress node, err := startTM(config, logger, genDoc, privVal, app) require.NoError(t, err) + time.Sleep(3 * time.Second) lcd, err := startLCD(logger, listenAddr, cdc) require.NoError(t, err) diff --git a/store/multistoreproof.go b/store/multistoreproof.go index e98a198e93..5ca214bc7d 100644 --- a/store/multistoreproof.go +++ b/store/multistoreproof.go @@ -113,4 +113,4 @@ func VerifyRangeProof(key, value []byte, substoreCommitHash []byte, rangeProof * } return nil -} \ No newline at end of file +} diff --git a/store/multistoreproof_test.go b/store/multistoreproof_test.go index b1667c7265..1538915677 100644 --- a/store/multistoreproof_test.go +++ b/store/multistoreproof_test.go @@ -93,4 +93,4 @@ func TestVerifyRangeProof(t *testing.T) { assert.NotEmpty(t, proof) err = VerifyRangeProof(key, val, root, proof) assert.Nil(t, err) -} \ No newline at end of file +} diff --git a/store/rootmultistore.go b/store/rootmultistore.go index 2a9a9373a4..c5ce52b0ba 100644 --- a/store/rootmultistore.go +++ b/store/rootmultistore.go @@ -292,7 +292,6 @@ func (rs *rootMultiStore) Query(req abci.RequestQuery) abci.ResponseQuery { req.Path = subpath res := queryable.Query(req) - // Currently, only when query subpath is "/store" or "/key", will proof be included in response. // If there are some changes about proof building in iavlstore.go, we must change code here to keep consistency with iavlstore.go if !req.Prove || subpath != "/store" && subpath != "/key" { @@ -300,7 +299,7 @@ func (rs *rootMultiStore) Query(req abci.RequestQuery) abci.ResponseQuery { } //Load commit info from db - commitInfo, errMsg := getCommitInfo(rs.db,res.Height) + commitInfo, errMsg := getCommitInfo(rs.db, res.Height) if errMsg != nil { return sdk.ErrInternal(errMsg.Error()).QueryResult() } From 3f83aca5992ea483ad42dc4431dfdbf6af4540c7 Mon Sep 17 00:00:00 2001 From: HaoyangLiu Date: Thu, 30 Aug 2018 16:24:56 +0800 Subject: [PATCH 17/52] IRISHUB-238: fix test_lint failures in client/lcd/root.go and server/ --- client/lcd/root.go | 8 ++++---- server/export_test.go | 16 ++++++++-------- server/mock/app.go | 2 +- 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/client/lcd/root.go b/client/lcd/root.go index 75cf3632ea..f05fb88081 100644 --- a/client/lcd/root.go +++ b/client/lcd/root.go @@ -19,12 +19,12 @@ import ( "github.com/gorilla/mux" "github.com/spf13/cobra" "github.com/spf13/viper" + "github.com/tendermint/tendermint/libs/cli" cmn "github.com/tendermint/tendermint/libs/common" "github.com/tendermint/tendermint/libs/log" - tmserver "github.com/tendermint/tendermint/rpc/lib/server" - tendermintLiteProxy "github.com/tendermint/tendermint/lite/proxy" - "github.com/tendermint/tendermint/libs/cli" tendermintLite "github.com/tendermint/tendermint/lite" + tendermintLiteProxy "github.com/tendermint/tendermint/lite/proxy" + tmserver "github.com/tendermint/tendermint/rpc/lib/server" ) // ServeCommand will generate a long-running rest server @@ -88,7 +88,7 @@ func createHandler(cdc *wire.Codec) http.Handler { home := viper.GetString(cli.HomeFlag) nodeURI := viper.GetString(client.FlagNode) var certifier tendermintLite.Certifier - if chainID != "" && home != "" && nodeURI != ""{ + if chainID != "" && home != "" && nodeURI != "" { certifier, err = tendermintLiteProxy.GetCertifier(chainID, home, nodeURI) if err != nil { panic(err) diff --git a/server/export_test.go b/server/export_test.go index 358f72cf60..488c55bbf6 100644 --- a/server/export_test.go +++ b/server/export_test.go @@ -1,16 +1,16 @@ package server import ( - "testing" - "github.com/stretchr/testify/require" - "github.com/cosmos/cosmos-sdk/wire" - "github.com/tendermint/tendermint/libs/log" - tcmd "github.com/tendermint/tendermint/cmd/tendermint/commands" - "os" "bytes" + "github.com/cosmos/cosmos-sdk/server/mock" + "github.com/cosmos/cosmos-sdk/wire" + "github.com/stretchr/testify/require" + tcmd "github.com/tendermint/tendermint/cmd/tendermint/commands" + "github.com/tendermint/tendermint/libs/log" "io" - "github.com/cosmos/cosmos-sdk/server/mock" - ) + "os" + "testing" +) func TestEmptyState(t *testing.T) { defer setupViper(t)() diff --git a/server/mock/app.go b/server/mock/app.go index eb2dfc3cc3..3c6ad3ec27 100644 --- a/server/mock/app.go +++ b/server/mock/app.go @@ -129,7 +129,7 @@ func AppGenStateEmpty(_ *wire.Codec, _ []json.RawMessage) (appState json.RawMess // Return a validator, not much else func AppGenTx(_ *wire.Codec, pk crypto.PubKey, genTxConfig gc.GenTx) ( - appGenTx, cliPrint json.RawMessage, validator tmtypes.GenesisValidator, err error) { + appGenTx, cliPrint json.RawMessage, validator tmtypes.GenesisValidator, err error) { validator = tmtypes.GenesisValidator{ PubKey: pk, From 5e85a5cdcdba8d9d7287e3950ae439d6b9ffd515 Mon Sep 17 00:00:00 2001 From: HaoyangLiu Date: Thu, 30 Aug 2018 17:14:10 +0800 Subject: [PATCH 18/52] IRISHUB-238: delete client manager --- client/context/client_manager.go | 46 --------------------------- client/context/client_manager_test.go | 16 ---------- client/context/context.go | 7 ---- 3 files changed, 69 deletions(-) delete mode 100644 client/context/client_manager.go delete mode 100644 client/context/client_manager_test.go diff --git a/client/context/client_manager.go b/client/context/client_manager.go deleted file mode 100644 index 8fffb9d651..0000000000 --- a/client/context/client_manager.go +++ /dev/null @@ -1,46 +0,0 @@ -package context - -import ( - "github.com/pkg/errors" - rpcclient "github.com/tendermint/tendermint/rpc/client" - "strings" - "sync" -) - -// ClientManager is a manager of a set of rpc clients to full nodes. -// This manager can do load balancing upon these rpc clients. -type ClientManager struct { - clients []rpcclient.Client - currentIndex int - mutex sync.Mutex -} - -// NewClientManager create a new ClientManager -func NewClientManager(nodeURIs string) (*ClientManager, error) { - if nodeURIs != "" { - nodeURLArray := strings.Split(nodeURIs, ",") - var clients []rpcclient.Client - for _, url := range nodeURLArray { - client := rpcclient.NewHTTP(url, "/websocket") - clients = append(clients, client) - } - mgr := &ClientManager{ - currentIndex: 0, - clients: clients, - } - return mgr, nil - } - return nil, errors.New("missing node URIs") -} - -func (mgr *ClientManager) getClient() rpcclient.Client { - mgr.mutex.Lock() - defer mgr.mutex.Unlock() - - client := mgr.clients[mgr.currentIndex] - mgr.currentIndex++ - if mgr.currentIndex >= len(mgr.clients) { - mgr.currentIndex = 0 - } - return client -} diff --git a/client/context/client_manager_test.go b/client/context/client_manager_test.go deleted file mode 100644 index 1960f74ced..0000000000 --- a/client/context/client_manager_test.go +++ /dev/null @@ -1,16 +0,0 @@ -package context - -import ( - "github.com/stretchr/testify/assert" - "testing" -) - -func TestClientManager(t *testing.T) { - nodeURIs := "10.10.10.10:26657,20.20.20.20:26657,30.30.30.30:26657" - clientMgr, err := NewClientManager(nodeURIs) - assert.Empty(t, err) - endpoint := clientMgr.getClient() - assert.NotEqual(t, endpoint, clientMgr.getClient()) - clientMgr.getClient() - assert.Equal(t, endpoint, clientMgr.getClient()) -} diff --git a/client/context/context.go b/client/context/context.go index 28506756cf..ffdd0f7bc0 100644 --- a/client/context/context.go +++ b/client/context/context.go @@ -34,7 +34,6 @@ type CLIContext struct { JSON bool PrintResponse bool Certifier tendermintLite.Certifier - ClientManager *ClientManager } // NewCLIContext returns a new initialized CLIContext with parameters from the @@ -126,9 +125,3 @@ func (ctx CLIContext) WithCertifier(certifier tendermintLite.Certifier) CLIConte ctx.Certifier = certifier return ctx } - -// WithClientManager - return a copy of the context with an updated ClientManager -func (ctx CLIContext) WithClientManager(clientManager *ClientManager) CLIContext { - ctx.ClientManager = clientManager - return ctx -} From d1a5808a7518f1244a1716877580c4df9c5ceb12 Mon Sep 17 00:00:00 2001 From: ValarDragon Date: Thu, 30 Aug 2018 09:32:15 -0700 Subject: [PATCH 19/52] Address Anton's comment --- x/mock/simulation/random_simulate_blocks.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/x/mock/simulation/random_simulate_blocks.go b/x/mock/simulation/random_simulate_blocks.go index e609d3c5f9..e0ab9ba586 100644 --- a/x/mock/simulation/random_simulate_blocks.go +++ b/x/mock/simulation/random_simulate_blocks.go @@ -115,7 +115,7 @@ func SimulateFromSeed( logUpdate, futureOps, err := ops[r.Intn(len(ops))](tb, r, app, ctx, keys, log, event) log = updateLog(testingmode, log, logUpdate) if err != nil { - tb.Fatalf("error on operation %d, %v", opCount, err) + tb.Fatalf("error on operation %d, %v, log %s", opCount, err, log) } queueOperations(operationQueue, futureOps) @@ -160,7 +160,7 @@ func SimulateFromSeed( } func updateLog(testingmode bool, log string, update string, args ...interface{}) (updatedLog string) { - if testingmode == true { + if testingmode { update = fmt.Sprintf(update, args...) return fmt.Sprintf("%s\n%s", log, update) } From 4be69077fe262524527a1e265d4c1032877946ff Mon Sep 17 00:00:00 2001 From: ValarDragon Date: Thu, 30 Aug 2018 11:43:56 -0700 Subject: [PATCH 20/52] Fix cyclomatic complexity, add ability to use GoLevelDB --- cmd/gaia/app/sim_test.go | 19 ++++-- server/export_test.go | 16 ++--- server/mock/app.go | 2 +- x/mock/simulation/random_simulate_blocks.go | 76 ++++++++++++--------- 4 files changed, 66 insertions(+), 47 deletions(-) diff --git a/cmd/gaia/app/sim_test.go b/cmd/gaia/app/sim_test.go index 1fd07b3302..6568f124b0 100644 --- a/cmd/gaia/app/sim_test.go +++ b/cmd/gaia/app/sim_test.go @@ -4,6 +4,7 @@ import ( "encoding/json" "flag" "math/rand" + "os" "testing" "github.com/stretchr/testify/require" @@ -23,11 +24,12 @@ import ( ) var ( - seed int64 - numBlocks int - blockSize int - enabled bool - verbose bool + seed int64 + numBlocks int + blockSize int + enabled bool + verbose bool + usegoleveldb bool ) func init() { @@ -36,6 +38,7 @@ func init() { flag.IntVar(&blockSize, "SimulationBlockSize", 200, "Operations per block") flag.BoolVar(&enabled, "SimulationEnabled", false, "Enable the simulation") flag.BoolVar(&verbose, "SimulationVerbose", false, "Verbose log output") + flag.BoolVar(&usegoleveldb, "SimulationGoLevelDB", false, "Use GoLevelDB instead of memdb") } func appStateFn(r *rand.Rand, keys []crypto.PrivKey, accs []sdk.AccAddress) json.RawMessage { @@ -118,7 +121,11 @@ func BenchmarkFullGaiaSimulation(b *testing.B) { // Setup Gaia application var logger log.Logger logger = log.NewNopLogger() - db := dbm.NewMemDB() + var db dbm.DB + db = dbm.NewMemDB() + if usegoleveldb { + db, _ = dbm.NewGoLevelDB("Simulation", os.TempDir()) + } app := NewGaiaApp(logger, db, nil) // Run randomized simulation diff --git a/server/export_test.go b/server/export_test.go index 358f72cf60..488c55bbf6 100644 --- a/server/export_test.go +++ b/server/export_test.go @@ -1,16 +1,16 @@ package server import ( - "testing" - "github.com/stretchr/testify/require" - "github.com/cosmos/cosmos-sdk/wire" - "github.com/tendermint/tendermint/libs/log" - tcmd "github.com/tendermint/tendermint/cmd/tendermint/commands" - "os" "bytes" + "github.com/cosmos/cosmos-sdk/server/mock" + "github.com/cosmos/cosmos-sdk/wire" + "github.com/stretchr/testify/require" + tcmd "github.com/tendermint/tendermint/cmd/tendermint/commands" + "github.com/tendermint/tendermint/libs/log" "io" - "github.com/cosmos/cosmos-sdk/server/mock" - ) + "os" + "testing" +) func TestEmptyState(t *testing.T) { defer setupViper(t)() diff --git a/server/mock/app.go b/server/mock/app.go index eb2dfc3cc3..3c6ad3ec27 100644 --- a/server/mock/app.go +++ b/server/mock/app.go @@ -129,7 +129,7 @@ func AppGenStateEmpty(_ *wire.Codec, _ []json.RawMessage) (appState json.RawMess // Return a validator, not much else func AppGenTx(_ *wire.Codec, pk crypto.PubKey, genTxConfig gc.GenTx) ( - appGenTx, cliPrint json.RawMessage, validator tmtypes.GenesisValidator, err error) { + appGenTx, cliPrint json.RawMessage, validator tmtypes.GenesisValidator, err error) { validator = tmtypes.GenesisValidator{ PubKey: pk, diff --git a/x/mock/simulation/random_simulate_blocks.go b/x/mock/simulation/random_simulate_blocks.go index e0ab9ba586..42a616331d 100644 --- a/x/mock/simulation/random_simulate_blocks.go +++ b/x/mock/simulation/random_simulate_blocks.go @@ -54,15 +54,7 @@ func SimulateFromSeed( tb testing.TB, app *baseapp.BaseApp, appStateFn func(r *rand.Rand, keys []crypto.PrivKey, accs []sdk.AccAddress) json.RawMessage, seed int64, ops []Operation, setups []RandSetup, invariants []Invariant, numBlocks int, blockSize int, commit bool, ) { - var t *testing.T - var b *testing.B - testingmode := false - if _t, ok := tb.(*testing.T); ok { - t = _t - testingmode = true - } else { - b = tb.(*testing.B) - } + testingmode, t, b := getTestingMode(tb) log := fmt.Sprintf("Starting SimulateFromSeed with randomness created with seed %d", int(seed)) r := rand.New(rand.NewSource(seed)) timestamp := randTimestamp(r) @@ -93,6 +85,7 @@ func SimulateFromSeed( if !testingmode { b.ResetTimer() } + blockSimulator := createBlockSimulator(testingmode, tb, t, event, invariants, ops, operationQueue, numBlocks) for i := 0; i < numBlocks; i++ { // Run the BeginBlock handler @@ -111,24 +104,8 @@ func SimulateFromSeed( log, numQueuedOpsRan := runQueuedOperations(operationQueue, int(header.Height), tb, r, app, ctx, keys, log, event) opCount += numQueuedOpsRan thisBlockSize -= numQueuedOpsRan - for j := 0; j < thisBlockSize; j++ { - logUpdate, futureOps, err := ops[r.Intn(len(ops))](tb, r, app, ctx, keys, log, event) - log = updateLog(testingmode, log, logUpdate) - if err != nil { - tb.Fatalf("error on operation %d, %v, log %s", opCount, err, log) - } - - queueOperations(operationQueue, futureOps) - if testingmode { - if onOperation { - AssertAllInvariants(t, app, invariants, log) - } - if opCount%200 == 0 { - fmt.Printf("\rSimulating... block %d/%d, operation %d.", header.Height, numBlocks, opCount) - } - } - opCount++ - } + log, operations := blockSimulator(thisBlockSize, r, app, ctx, keys, log, header) + opCount += operations res := app.EndBlock(abci.RequestEndBlock{}) header.Height++ @@ -151,14 +128,49 @@ func SimulateFromSeed( validators = updateValidators(tb, r, validators, res.ValidatorUpdates, event) } - if testingmode { - fmt.Printf("\nSimulation complete. Final height (blocks): %d, final time (seconds): %v\n", header.Height, header.Time) - } else { - fmt.Printf("%d operations ran\n", opCount) - } + fmt.Printf("\nSimulation complete. Final height (blocks): %d, final time (seconds), : %v, operations ran %d\n", header.Height, header.Time, opCount) DisplayEvents(events) } +// 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 []Operation, operationQueue map[int][]Operation, totalNumBlocks int) func( + blocksize int, r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, privKeys []crypto.PrivKey, log string, header abci.Header) (updatedLog string, opCount int) { + return func(blocksize int, r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, + keys []crypto.PrivKey, log string, header abci.Header) (updatedLog string, opCount int) { + for j := 0; j < blocksize; j++ { + logUpdate, futureOps, err := ops[r.Intn(len(ops))](tb, r, app, ctx, keys, log, event) + log = updateLog(testingmode, log, logUpdate) + if err != nil { + tb.Fatalf("error on operation %d within block %d, %v, log %s", header.Height, opCount, err, log) + } + + queueOperations(operationQueue, futureOps) + if testingmode { + if onOperation { + AssertAllInvariants(t, app, invariants, log) + } + if opCount%50 == 0 { + fmt.Printf("\rSimulating... block %d/%d, operation %d/%d.", header.Height, totalNumBlocks, opCount, blocksize) + } + } + opCount++ + } + return log, opCount + } +} + +func getTestingMode(tb testing.TB) (testingmode bool, t *testing.T, b *testing.B) { + testingmode = false + if _t, ok := tb.(*testing.T); ok { + t = _t + testingmode = true + } else { + b = tb.(*testing.B) + } + return +} + func updateLog(testingmode bool, log string, update string, args ...interface{}) (updatedLog string) { if testingmode { update = fmt.Sprintf(update, args...) From a69725d40d5ceaf2b27c0f5fba8e4e3c47029703 Mon Sep 17 00:00:00 2001 From: ValarDragon Date: Thu, 30 Aug 2018 11:47:32 -0700 Subject: [PATCH 21/52] Add method to benchmark with commits --- cmd/gaia/app/sim_test.go | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/cmd/gaia/app/sim_test.go b/cmd/gaia/app/sim_test.go index 6568f124b0..c7452ae533 100644 --- a/cmd/gaia/app/sim_test.go +++ b/cmd/gaia/app/sim_test.go @@ -30,6 +30,7 @@ var ( enabled bool verbose bool usegoleveldb bool + commit bool ) func init() { @@ -39,6 +40,7 @@ func init() { flag.BoolVar(&enabled, "SimulationEnabled", false, "Enable the simulation") flag.BoolVar(&verbose, "SimulationVerbose", false, "Verbose log output") flag.BoolVar(&usegoleveldb, "SimulationGoLevelDB", false, "Use GoLevelDB instead of memdb") + flag.BoolVar(&commit, "SimulationCommit", false, "Have the simulation commit") } func appStateFn(r *rand.Rand, keys []crypto.PrivKey, accs []sdk.AccAddress) json.RawMessage { @@ -137,7 +139,7 @@ func BenchmarkFullGaiaSimulation(b *testing.B) { invariants(app), // these shouldn't get ran 10, 100, - false, + commit, ) } @@ -165,7 +167,7 @@ func TestFullGaiaSimulation(t *testing.T) { invariants(app), numBlocks, blockSize, - false, + commit, ) } From d900c583ffa3f35cd881d71270309f0b8943711d Mon Sep 17 00:00:00 2001 From: rigelrozanski Date: Thu, 30 Aug 2018 21:16:23 -0400 Subject: [PATCH 22/52] melekes comment --- x/stake/types/validator.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/x/stake/types/validator.go b/x/stake/types/validator.go index 13b00f9094..5a325ec73c 100644 --- a/x/stake/types/validator.go +++ b/x/stake/types/validator.go @@ -420,9 +420,10 @@ func (v Validator) BondedTokens() sdk.Dec { // Returns if the validator should be considered unbonded func (v Validator) IsUnbonded(ctx sdk.Context) bool { - if v.Status == sdk.Unbonded { + switch v.Status { + case sdk.Unbonded: return true - } else if v.Status == sdk.Unbonding { + case sdk.Unbonding: ctxTime := ctx.BlockHeader().Time if ctxTime.After(v.UnbondingMinTime) { return true From ab76fd964a8af03c41ed7160bc2399b8d811502e Mon Sep 17 00:00:00 2001 From: HaoyangLiu Date: Fri, 31 Aug 2018 10:03:48 +0800 Subject: [PATCH 23/52] IRISHUB-238: remove todo, refactor comment and refactor multistoreproof --- client/context/context.go | 6 +-- client/context/query.go | 21 +++------- store/multistoreproof.go | 78 ++++++++++++++--------------------- store/multistoreproof_test.go | 78 +++++++++++++++++++++++------------ store/rootmultistore.go | 7 +--- 5 files changed, 92 insertions(+), 98 deletions(-) diff --git a/client/context/context.go b/client/context/context.go index ffdd0f7bc0..d28cc34f87 100644 --- a/client/context/context.go +++ b/client/context/context.go @@ -9,7 +9,7 @@ import ( "github.com/spf13/viper" - tendermintLite "github.com/tendermint/tendermint/lite" + tmlite "github.com/tendermint/tendermint/lite" rpcclient "github.com/tendermint/tendermint/rpc/client" ) @@ -33,7 +33,7 @@ type CLIContext struct { Async bool JSON bool PrintResponse bool - Certifier tendermintLite.Certifier + Certifier tmlite.Certifier } // NewCLIContext returns a new initialized CLIContext with parameters from the @@ -121,7 +121,7 @@ func (ctx CLIContext) WithUseLedger(useLedger bool) CLIContext { } // WithCertifier - return a copy of the context with an updated Certifier -func (ctx CLIContext) WithCertifier(certifier tendermintLite.Certifier) CLIContext { +func (ctx CLIContext) WithCertifier(certifier tmlite.Certifier) CLIContext { ctx.Certifier = certifier return ctx } diff --git a/client/context/query.go b/client/context/query.go index aac0c90306..47390d7872 100644 --- a/client/context/query.go +++ b/client/context/query.go @@ -14,7 +14,7 @@ import ( "github.com/cosmos/cosmos-sdk/wire" abci "github.com/tendermint/tendermint/abci/types" cmn "github.com/tendermint/tendermint/libs/common" - tendermintLiteProxy "github.com/tendermint/tendermint/lite/proxy" + tmliteProxy "github.com/tendermint/tendermint/lite/proxy" rpcclient "github.com/tendermint/tendermint/rpc/client" ctypes "github.com/tendermint/tendermint/rpc/core/types" "strings" @@ -325,12 +325,8 @@ func (ctx CLIContext) query(path string, key cmn.HexBytes) (res []byte, err erro // verifyProof perform response proof verification func (ctx CLIContext) verifyProof(path string, resp abci.ResponseQuery) error { - // TODO: Later we consider to return error for missing valid certifier to verify data from untrusted node if ctx.Certifier == nil { - if ctx.Logger != nil { - io.WriteString(ctx.Logger, fmt.Sprintf("Missing valid certifier to verify data from untrusted node\n")) - } - return nil + return fmt.Errorf("missing valid certifier to verify data from untrusted node") } node, err := ctx.GetNode() @@ -338,11 +334,8 @@ func (ctx CLIContext) verifyProof(path string, resp abci.ResponseQuery) error { return err } - // TODO: need improvement - // If the the node http client connect to a full node which can't produce or receive new blocks, - // then here the code will wait for a while and return error if time is out. // AppHash for height H is in header H+1 - commit, err := tendermintLiteProxy.GetCertifiedCommit(resp.Height+1, node, ctx.Certifier) + commit, err := tmliteProxy.GetCertifiedCommit(resp.Height+1, node, ctx.Certifier) if err != nil { return err } @@ -356,7 +349,7 @@ func (ctx CLIContext) verifyProof(path string, resp abci.ResponseQuery) error { // Validate the substore commit hash against trusted appHash substoreCommitHash, err := store.VerifyMultiStoreCommitInfo(multiStoreProof.StoreName, - multiStoreProof.CommitIDList, commit.Header.AppHash) + multiStoreProof.StoreInfos, commit.Header.AppHash) if err != nil { return errors.Wrap(err, "failed in verifying the proof against appHash") } @@ -376,7 +369,6 @@ func (ctx CLIContext) queryStore(key cmn.HexBytes, storeName, endPath string) ([ // isQueryStoreWithProof expects a format like /// // queryType can be app or store -// if subpath equals to "/store" or "/key", then return true func isQueryStoreWithProof(path string) bool { if !strings.HasPrefix(path, "/") { return false @@ -385,9 +377,8 @@ func isQueryStoreWithProof(path string) bool { if len(paths) != 3 { return false } - // Currently, only when query subpath is "/store" or "/key", will proof be included in response. - // If there are some changes about proof building in iavlstore.go, we must change code here to keep consistency with iavlstore.go - if paths[2] == "store" || paths[2] == "key" { + + if store.RequireProof(paths[2]) { return true } return false diff --git a/store/multistoreproof.go b/store/multistoreproof.go index 5ca214bc7d..62cc30ddfc 100644 --- a/store/multistoreproof.go +++ b/store/multistoreproof.go @@ -2,48 +2,33 @@ package store import ( "bytes" - sdk "github.com/cosmos/cosmos-sdk/types" "github.com/pkg/errors" "github.com/tendermint/iavl" cmn "github.com/tendermint/tendermint/libs/common" ) -// commitID of substores, such as acc store, gov store -type SubstoreCommitID struct { - Name string `json:"name"` - Version int64 `json:"version"` - CommitHash cmn.HexBytes `json:"commit_hash"` -} - -// proof of store which have multi substores +// MultiStoreProof defines a collection of store proofs in a multi-store type MultiStoreProof struct { - CommitIDList []SubstoreCommitID `json:"commit_id_list"` - StoreName string `json:"store_name"` - RangeProof iavl.RangeProof `json:"range_proof"` + StoreInfos []storeInfo + StoreName string + RangeProof iavl.RangeProof } -// build MultiStoreProof based on iavl proof and storeInfos -func BuildMultiStoreProof(iavlProof []byte, storeName string, storeInfos []storeInfo) ([]byte, error) { +// buildMultiStoreProof build MultiStoreProof based on iavl proof and storeInfos +func buildMultiStoreProof(iavlProof []byte, storeName string, storeInfos []storeInfo) ([]byte, error) { var rangeProof iavl.RangeProof err := cdc.UnmarshalBinary(iavlProof, &rangeProof) if err != nil { return nil, err } - var multiStoreProof MultiStoreProof - for _, storeInfo := range storeInfos { - - commitID := SubstoreCommitID{ - Name: storeInfo.Name, - Version: storeInfo.Core.CommitID.Version, - CommitHash: storeInfo.Core.CommitID.Hash, - } - multiStoreProof.CommitIDList = append(multiStoreProof.CommitIDList, commitID) + msp := MultiStoreProof{ + StoreInfos: storeInfos, + StoreName: storeName, + RangeProof: rangeProof, } - multiStoreProof.StoreName = storeName - multiStoreProof.RangeProof = rangeProof - proof, err := cdc.MarshalBinary(multiStoreProof) + proof, err := cdc.MarshalBinary(msp) if err != nil { return nil, err } @@ -51,28 +36,15 @@ func BuildMultiStoreProof(iavlProof []byte, storeName string, storeInfos []store return proof, nil } -// verify multiStoreCommitInfo against appHash -func VerifyMultiStoreCommitInfo(storeName string, multiStoreCommitInfo []SubstoreCommitID, appHash []byte) ([]byte, error) { +// VerifyMultiStoreCommitInfo verify multiStoreCommitInfo against appHash +func VerifyMultiStoreCommitInfo(storeName string, storeInfos []storeInfo, appHash []byte) ([]byte, error) { var substoreCommitHash []byte - var storeInfos []storeInfo var height int64 - for _, multiStoreCommitID := range multiStoreCommitInfo { - - if multiStoreCommitID.Name == storeName { - substoreCommitHash = multiStoreCommitID.CommitHash - height = multiStoreCommitID.Version + for _, storeInfo := range storeInfos { + if storeInfo.Name == storeName { + substoreCommitHash = storeInfo.Core.CommitID.Hash + height = storeInfo.Core.CommitID.Version } - storeInfo := storeInfo{ - Name: multiStoreCommitID.Name, - Core: storeCore{ - CommitID: sdk.CommitID{ - Version: multiStoreCommitID.Version, - Hash: multiStoreCommitID.CommitHash, - }, - }, - } - - storeInfos = append(storeInfos, storeInfo) } if len(substoreCommitHash) == 0 { return nil, cmn.NewError("failed to get substore root commit hash by store name") @@ -89,7 +61,7 @@ func VerifyMultiStoreCommitInfo(storeName string, multiStoreCommitInfo []Substor return substoreCommitHash, nil } -// verify iavl proof +// VerifyRangeProof verify iavl RangeProof func VerifyRangeProof(key, value []byte, substoreCommitHash []byte, rangeProof *iavl.RangeProof) error { // Validate the proof to ensure data integrity. @@ -99,13 +71,13 @@ func VerifyRangeProof(key, value []byte, substoreCommitHash []byte, rangeProof * } if len(value) != 0 { - // Validate existence proof + // Verify existence proof err = rangeProof.VerifyItem(key, value) if err != nil { return errors.Wrap(err, "failed in existence verification") } } else { - // Validate absence proof + // Verify absence proof err = rangeProof.VerifyAbsence(key) if err != nil { return errors.Wrap(err, "failed in absence verification") @@ -114,3 +86,13 @@ func VerifyRangeProof(key, value []byte, substoreCommitHash []byte, rangeProof * return nil } + +// RequireProof return whether proof is require for the subpath +func RequireProof(subpath string) bool { + // Currently, only when query subpath is "/store" or "/key", will proof be included in response. + // If there are some changes about proof building in iavlstore.go, we must change code here to keep consistency with iavlstore.go:212 + if subpath == "/store" || subpath == "/key" { + return true + } + return false +} diff --git a/store/multistoreproof_test.go b/store/multistoreproof_test.go index 1538915677..b4e8a84b16 100644 --- a/store/multistoreproof_test.go +++ b/store/multistoreproof_test.go @@ -14,55 +14,79 @@ func TestVerifyMultiStoreCommitInfo(t *testing.T) { substoreRootHash, _ := hex.DecodeString("ea5d468431015c2cd6295e9a0bb1fc0e49033828") storeName := "acc" - var multiStoreCommitInfo []SubstoreCommitID + var storeInfos []storeInfo gocRootHash, _ := hex.DecodeString("62c171bb022e47d1f745608ff749e676dbd25f78") - multiStoreCommitInfo = append(multiStoreCommitInfo, SubstoreCommitID{ - Name: "gov", - Version: 689, - CommitHash: gocRootHash, + storeInfos = append(storeInfos, storeInfo{ + Name: "gov", + Core: storeCore{ + CommitID: CommitID{ + Version: 689, + Hash: gocRootHash, + }, + }, }) - multiStoreCommitInfo = append(multiStoreCommitInfo, SubstoreCommitID{ - Name: "main", - Version: 689, - CommitHash: nil, + storeInfos = append(storeInfos, storeInfo{ + Name: "main", + Core: storeCore{ + CommitID: CommitID{ + Version: 689, + Hash: nil, + }, + }, }) accRootHash, _ := hex.DecodeString("ea5d468431015c2cd6295e9a0bb1fc0e49033828") - multiStoreCommitInfo = append(multiStoreCommitInfo, SubstoreCommitID{ - Name: "acc", - Version: 689, - CommitHash: accRootHash, + storeInfos = append(storeInfos, storeInfo{ + Name: "acc", + Core: storeCore{ + CommitID: CommitID{ + Version: 689, + Hash: accRootHash, + }, + }, }) - multiStoreCommitInfo = append(multiStoreCommitInfo, SubstoreCommitID{ - Name: "ibc", - Version: 689, - CommitHash: nil, + storeInfos = append(storeInfos, storeInfo{ + Name: "ibc", + Core: storeCore{ + CommitID: CommitID{ + Version: 689, + Hash: nil, + }, + }, }) stakeRootHash, _ := hex.DecodeString("987d1d27b8771d93aa3691262f661d2c85af7ca4") - multiStoreCommitInfo = append(multiStoreCommitInfo, SubstoreCommitID{ - Name: "stake", - Version: 689, - CommitHash: stakeRootHash, + storeInfos = append(storeInfos, storeInfo{ + Name: "stake", + Core: storeCore{ + CommitID: CommitID{ + Version: 689, + Hash: stakeRootHash, + }, + }, }) slashingRootHash, _ := hex.DecodeString("388ee6e5b11f367069beb1eefd553491afe9d73e") - multiStoreCommitInfo = append(multiStoreCommitInfo, SubstoreCommitID{ - Name: "slashing", - Version: 689, - CommitHash: slashingRootHash, + storeInfos = append(storeInfos, storeInfo{ + Name: "slashing", + Core: storeCore{ + CommitID: CommitID{ + Version: 689, + Hash: slashingRootHash, + }, + }, }) - commitHash, err := VerifyMultiStoreCommitInfo(storeName, multiStoreCommitInfo, appHash) + commitHash, err := VerifyMultiStoreCommitInfo(storeName, storeInfos, appHash) assert.Nil(t, err) assert.Equal(t, commitHash, substoreRootHash) appHash, _ = hex.DecodeString("29de216bf5e2531c688de36caaf024cd3bb09ee3") - _, err = VerifyMultiStoreCommitInfo(storeName, multiStoreCommitInfo, appHash) + _, err = VerifyMultiStoreCommitInfo(storeName, storeInfos, appHash) assert.Error(t, err, "appHash doesn't match to the merkle root of multiStoreCommitInfo") } diff --git a/store/rootmultistore.go b/store/rootmultistore.go index c5ce52b0ba..5f6b082832 100644 --- a/store/rootmultistore.go +++ b/store/rootmultistore.go @@ -292,19 +292,16 @@ func (rs *rootMultiStore) Query(req abci.RequestQuery) abci.ResponseQuery { req.Path = subpath res := queryable.Query(req) - // Currently, only when query subpath is "/store" or "/key", will proof be included in response. - // If there are some changes about proof building in iavlstore.go, we must change code here to keep consistency with iavlstore.go - if !req.Prove || subpath != "/store" && subpath != "/key" { + if !req.Prove || !RequireProof(subpath) { return res } - //Load commit info from db commitInfo, errMsg := getCommitInfo(rs.db, res.Height) if errMsg != nil { return sdk.ErrInternal(errMsg.Error()).QueryResult() } - res.Proof, errMsg = BuildMultiStoreProof(res.Proof, storeName, commitInfo.StoreInfos) + res.Proof, errMsg = buildMultiStoreProof(res.Proof, storeName, commitInfo.StoreInfos) if errMsg != nil { return sdk.ErrInternal(errMsg.Error()).QueryResult() } From 9de7650b990f2186084448804789447782b3aff7 Mon Sep 17 00:00:00 2001 From: HaoyangLiu Date: Fri, 31 Aug 2018 10:15:37 +0800 Subject: [PATCH 24/52] IRISHUB-238: fix a bug in judge whether proof is required and refactor comment --- client/context/query.go | 2 +- store/multistoreproof.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/client/context/query.go b/client/context/query.go index 47390d7872..31861a972e 100644 --- a/client/context/query.go +++ b/client/context/query.go @@ -378,7 +378,7 @@ func isQueryStoreWithProof(path string) bool { return false } - if store.RequireProof(paths[2]) { + if store.RequireProof("/" + paths[2]) { return true } return false diff --git a/store/multistoreproof.go b/store/multistoreproof.go index 62cc30ddfc..05eb49730a 100644 --- a/store/multistoreproof.go +++ b/store/multistoreproof.go @@ -64,7 +64,7 @@ func VerifyMultiStoreCommitInfo(storeName string, storeInfos []storeInfo, appHas // VerifyRangeProof verify iavl RangeProof func VerifyRangeProof(key, value []byte, substoreCommitHash []byte, rangeProof *iavl.RangeProof) error { - // Validate the proof to ensure data integrity. + // Verify the proof to ensure data integrity. err := rangeProof.Verify(substoreCommitHash) if err != nil { return errors.Wrap(err, "proof root hash doesn't equal to substore commit root hash") From 6d2fb8edefe61f31ea47234d2a5b5164fe51ac9f Mon Sep 17 00:00:00 2001 From: HaoyangLiu Date: Fri, 31 Aug 2018 11:29:55 +0800 Subject: [PATCH 25/52] IRISHUB-238: fix test_cli failure, move certifier creation from lcd/root.go to NewCLIContext --- client/context/context.go | 23 ++++++++++++++++++++++- client/flags.go | 1 + client/lcd/root.go | 22 ++++------------------ 3 files changed, 27 insertions(+), 19 deletions(-) diff --git a/client/context/context.go b/client/context/context.go index d28cc34f87..2d9c9b90c9 100644 --- a/client/context/context.go +++ b/client/context/context.go @@ -9,8 +9,12 @@ import ( "github.com/spf13/viper" + "github.com/tendermint/tendermint/libs/cli" + tendermintLite "github.com/tendermint/tendermint/lite" tmlite "github.com/tendermint/tendermint/lite" + tendermintLiteProxy "github.com/tendermint/tendermint/lite/proxy" rpcclient "github.com/tendermint/tendermint/rpc/client" + "fmt" ) const ctxAccStoreName = "acc" @@ -46,6 +50,22 @@ func NewCLIContext() CLIContext { rpc = rpcclient.NewHTTP(nodeURI, "/websocket") } + trustNode := viper.GetBool(client.FlagTrustNode) + var certifier tendermintLite.Certifier + if !trustNode { + chainID := viper.GetString(client.FlagChainID) + home := viper.GetString(cli.HomeFlag) + if chainID != "" && home != "" && nodeURI != "" { + var err error + certifier, err = tendermintLiteProxy.GetCertifier(chainID, home, nodeURI) + if err != nil { + panic(err) + } + } else { + panic(fmt.Errorf("can't create certifier for distrust mode, values from these options may be empty: --chain-id, --home or --node")) + } + } + return CLIContext{ Client: rpc, NodeURI: nodeURI, @@ -54,11 +74,12 @@ func NewCLIContext() CLIContext { Height: viper.GetInt64(client.FlagHeight), Gas: viper.GetInt64(client.FlagGas), GasAdjustment: viper.GetFloat64(client.FlagGasAdjustment), - TrustNode: viper.GetBool(client.FlagTrustNode), + TrustNode: trustNode, UseLedger: viper.GetBool(client.FlagUseLedger), Async: viper.GetBool(client.FlagAsync), JSON: viper.GetBool(client.FlagJson), PrintResponse: viper.GetBool(client.FlagPrintResponse), + Certifier: certifier, } } diff --git a/client/flags.go b/client/flags.go index 81e0670678..12bddb7d96 100644 --- a/client/flags.go +++ b/client/flags.go @@ -58,6 +58,7 @@ func PostCommands(cmds ...*cobra.Command) []*cobra.Command { c.Flags().Bool(FlagAsync, false, "broadcast transactions asynchronously") c.Flags().Bool(FlagJson, false, "return output in json format") c.Flags().Bool(FlagPrintResponse, true, "return tx response (only works with async = false)") + c.Flags().Bool(FlagTrustNode, true, "Don't verify proofs for query responses") } return cmds } diff --git a/client/lcd/root.go b/client/lcd/root.go index f05fb88081..f18ee5dfd4 100644 --- a/client/lcd/root.go +++ b/client/lcd/root.go @@ -4,11 +4,11 @@ import ( "net/http" "os" - client "github.com/cosmos/cosmos-sdk/client" + "github.com/cosmos/cosmos-sdk/client" "github.com/cosmos/cosmos-sdk/client/context" - keys "github.com/cosmos/cosmos-sdk/client/keys" - rpc "github.com/cosmos/cosmos-sdk/client/rpc" - tx "github.com/cosmos/cosmos-sdk/client/tx" + "github.com/cosmos/cosmos-sdk/client/keys" + "github.com/cosmos/cosmos-sdk/client/rpc" + "github.com/cosmos/cosmos-sdk/client/tx" "github.com/cosmos/cosmos-sdk/wire" auth "github.com/cosmos/cosmos-sdk/x/auth/client/rest" bank "github.com/cosmos/cosmos-sdk/x/bank/client/rest" @@ -19,11 +19,8 @@ import ( "github.com/gorilla/mux" "github.com/spf13/cobra" "github.com/spf13/viper" - "github.com/tendermint/tendermint/libs/cli" cmn "github.com/tendermint/tendermint/libs/common" "github.com/tendermint/tendermint/libs/log" - tendermintLite "github.com/tendermint/tendermint/lite" - tendermintLiteProxy "github.com/tendermint/tendermint/lite/proxy" tmserver "github.com/tendermint/tendermint/rpc/lib/server" ) @@ -84,17 +81,6 @@ func createHandler(cdc *wire.Codec) http.Handler { cliCtx := context.NewCLIContext().WithCodec(cdc).WithLogger(os.Stdout) - chainID := viper.GetString(client.FlagChainID) - home := viper.GetString(cli.HomeFlag) - nodeURI := viper.GetString(client.FlagNode) - var certifier tendermintLite.Certifier - if chainID != "" && home != "" && nodeURI != "" { - certifier, err = tendermintLiteProxy.GetCertifier(chainID, home, nodeURI) - if err != nil { - panic(err) - } - cliCtx = cliCtx.WithCertifier(certifier) - } // TODO: make more functional? aka r = keys.RegisterRoutes(r) r.HandleFunc("/version", CLIVersionRequestHandler).Methods("GET") r.HandleFunc("/node_version", NodeVersionRequestHandler(cliCtx)).Methods("GET") From b977baec73ea1cf51dc8d439e49a186fb6e8625b Mon Sep 17 00:00:00 2001 From: HaoyangLiu Date: Fri, 31 Aug 2018 13:31:24 +0800 Subject: [PATCH 26/52] IRISHUB-238: move certifier creation to a function --- client/context/context.go | 57 ++++++++++++++++++++++++--------------- 1 file changed, 35 insertions(+), 22 deletions(-) diff --git a/client/context/context.go b/client/context/context.go index 2d9c9b90c9..da37795f08 100644 --- a/client/context/context.go +++ b/client/context/context.go @@ -2,7 +2,8 @@ package context import ( "io" - + "bytes" + "fmt" "github.com/cosmos/cosmos-sdk/client" "github.com/cosmos/cosmos-sdk/wire" "github.com/cosmos/cosmos-sdk/x/auth" @@ -10,11 +11,9 @@ import ( "github.com/spf13/viper" "github.com/tendermint/tendermint/libs/cli" - tendermintLite "github.com/tendermint/tendermint/lite" tmlite "github.com/tendermint/tendermint/lite" - tendermintLiteProxy "github.com/tendermint/tendermint/lite/proxy" + tmliteProxy "github.com/tendermint/tendermint/lite/proxy" rpcclient "github.com/tendermint/tendermint/rpc/client" - "fmt" ) const ctxAccStoreName = "acc" @@ -50,22 +49,6 @@ func NewCLIContext() CLIContext { rpc = rpcclient.NewHTTP(nodeURI, "/websocket") } - trustNode := viper.GetBool(client.FlagTrustNode) - var certifier tendermintLite.Certifier - if !trustNode { - chainID := viper.GetString(client.FlagChainID) - home := viper.GetString(cli.HomeFlag) - if chainID != "" && home != "" && nodeURI != "" { - var err error - certifier, err = tendermintLiteProxy.GetCertifier(chainID, home, nodeURI) - if err != nil { - panic(err) - } - } else { - panic(fmt.Errorf("can't create certifier for distrust mode, values from these options may be empty: --chain-id, --home or --node")) - } - } - return CLIContext{ Client: rpc, NodeURI: nodeURI, @@ -74,15 +57,45 @@ func NewCLIContext() CLIContext { Height: viper.GetInt64(client.FlagHeight), Gas: viper.GetInt64(client.FlagGas), GasAdjustment: viper.GetFloat64(client.FlagGasAdjustment), - TrustNode: trustNode, + 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: certifier, + Certifier: createCertifier(), } } +func createCertifier() tmlite.Certifier { + trustNode := viper.GetBool(client.FlagTrustNode) + if !trustNode { + chainID := viper.GetString(client.FlagChainID) + home := viper.GetString(cli.HomeFlag) + nodeURI := viper.GetString(client.FlagNode) + + var errMsg bytes.Buffer + if chainID == "" { + errMsg.WriteString("chain-id ") + } + if home == "" { + errMsg.WriteString("home ") + } + if nodeURI == "" { + errMsg.WriteString("node ") + } + // errMsg is not empty + if errMsg.Len() != 0 { + panic(fmt.Errorf("can't create certifier for distrust mode, empty values from these options: %s", errMsg.String())) + } + certifier, err := tmliteProxy.GetCertifier(chainID, home, nodeURI) + if err != nil { + panic(err) + } + return certifier + } + return nil +} + // WithCodec returns a copy of the context with an updated codec. func (ctx CLIContext) WithCodec(cdc *wire.Codec) CLIContext { ctx.Codec = cdc From de8ac6e2a982ceccfaa72061048e813304d05702 Mon Sep 17 00:00:00 2001 From: HaoyangLiu Date: Fri, 31 Aug 2018 14:06:53 +0800 Subject: [PATCH 27/52] IRISHUB-238: missing change a comment --- client/context/query.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/context/query.go b/client/context/query.go index 31861a972e..4c1cad8777 100644 --- a/client/context/query.go +++ b/client/context/query.go @@ -347,7 +347,7 @@ func (ctx CLIContext) verifyProof(path string, resp abci.ResponseQuery) error { return errors.Wrap(err, "failed to unmarshalBinary rangeProof") } - // Validate 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) if err != nil { From e64c6da6f223019a5db6fbcb51c8d51745e42e44 Mon Sep 17 00:00:00 2001 From: ValarDragon Date: Thu, 30 Aug 2018 23:42:12 -0700 Subject: [PATCH 28/52] cleanup goleveldb dirs --- cmd/gaia/app/sim_test.go | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/cmd/gaia/app/sim_test.go b/cmd/gaia/app/sim_test.go index c7452ae533..38dd352bde 100644 --- a/cmd/gaia/app/sim_test.go +++ b/cmd/gaia/app/sim_test.go @@ -124,10 +124,14 @@ func BenchmarkFullGaiaSimulation(b *testing.B) { var logger log.Logger logger = log.NewNopLogger() var db dbm.DB - db = dbm.NewMemDB() if usegoleveldb { - db, _ = dbm.NewGoLevelDB("Simulation", os.TempDir()) + dir := os.TempDir() + db, _ = dbm.NewGoLevelDB("Simulation", dir) + defer os.RemoveAll(dir) + } else { + db = dbm.NewMemDB() } + defer db.Close() app := NewGaiaApp(logger, db, nil) // Run randomized simulation From 959582476bd32b90d4375cd147638f11b86a0b9f Mon Sep 17 00:00:00 2001 From: HaoyangLiu Date: Fri, 31 Aug 2018 15:21:35 +0800 Subject: [PATCH 29/52] IRISHUB-238: fix test_lint failure --- Gopkg.lock | 45 +++++++++++++++++++++++---------------------- 1 file changed, 23 insertions(+), 22 deletions(-) diff --git a/Gopkg.lock b/Gopkg.lock index fa6fafb99d..5e40c17e6c 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -34,7 +34,7 @@ [[projects]] branch = "master" - digest = "1:6aabc1566d6351115d561d038da82a4c19b46c3b6e17f4a0a2fa60260663dc79" + digest = "1:2c00f064ba355903866cbfbf3f7f4c0fe64af6638cc7d1b8bdcf3181bc67f1d8" name = "github.com/btcsuite/btcd" packages = ["btcec"] pruneopts = "UT" @@ -71,7 +71,7 @@ version = "v1.4.7" [[projects]] - digest = "1:fa30c0652956e159cdb97dcb2ef8b8db63ed668c02a5c3a40961c8f0641252fe" + digest = "1:fdf5169073fb0ad6dc12a70c249145e30f4058647bea25f0abd48b6d9f228a11" name = "github.com/go-kit/kit" packages = [ "log", @@ -103,7 +103,7 @@ version = "v1.7.0" [[projects]] - digest = "1:212285efb97b9ec2e20550d81f0446cb7897e57cbdfd7301b1363ab113d8be45" + digest = "1:35621fe20f140f05a0c4ef662c26c0ab4ee50bca78aa30fe87d33120bd28165e" name = "github.com/gogo/protobuf" packages = [ "gogoproto", @@ -118,7 +118,7 @@ version = "v1.1.1" [[projects]] - digest = "1:cb22af0ed7c72d495d8be1106233ee553898950f15fd3f5404406d44c2e86888" + digest = "1:17fe264ee908afc795734e8c4e63db2accabaf57326dbf21763a7d6b86096260" name = "github.com/golang/protobuf" packages = [ "proto", @@ -165,13 +165,12 @@ [[projects]] branch = "master" - digest = "1:ac64f01acc5eeea9dde40e326de6b6471e501392ec06524c3b51033aa50789bc" + digest = "1:12247a2e99a060cc692f6680e5272c8adf0b8f572e6bce0d7095e624c958a240" name = "github.com/hashicorp/hcl" packages = [ ".", "hcl/ast", "hcl/parser", - "hcl/printer", "hcl/scanner", "hcl/strconv", "hcl/token", @@ -263,7 +262,7 @@ version = "v1.0.0" [[projects]] - digest = "1:98225904b7abff96c052b669b25788f18225a36673fba022fb93514bb9a2a64e" + digest = "1:c1a04665f9613e082e1209cf288bf64f4068dcd6c87a64bf1c4ff006ad422ba0" name = "github.com/prometheus/client_golang" packages = [ "prometheus", @@ -274,7 +273,7 @@ [[projects]] branch = "master" - digest = "1:0f37e09b3e92aaeda5991581311f8dbf38944b36a3edec61cc2d1991f527554a" + digest = "1:2d5cd61daa5565187e1d96bae64dbbc6080dacf741448e9629c64fd93203b0d4" name = "github.com/prometheus/client_model" packages = ["go"] pruneopts = "UT" @@ -282,7 +281,7 @@ [[projects]] branch = "master" - digest = "1:dad2e5a2153ee7a6c9ab8fc13673a16ee4fb64434a7da980965a3741b0c981a3" + digest = "1:63b68062b8968092eb86bedc4e68894bd096ea6b24920faca8b9dcf451f54bb5" name = "github.com/prometheus/common" packages = [ "expfmt", @@ -294,7 +293,7 @@ [[projects]] branch = "master" - digest = "1:a37c98f4b7a66bb5c539c0539f0915a74ef1c8e0b3b6f45735289d94cae92bfd" + digest = "1:8c49953a1414305f2ff5465147ee576dd705487c35b15918fcd4efdc0cb7a290" name = "github.com/prometheus/procfs" packages = [ ".", @@ -313,7 +312,7 @@ revision = "e2704e165165ec55d062f5919b4b29494e9fa790" [[projects]] - digest = "1:37ace7f35375adec11634126944bdc45a673415e2fcc07382d03b75ec76ea94c" + digest = "1:bd1ae00087d17c5a748660b8e89e1043e1e5479d0fea743352cda2f8dd8c4f84" name = "github.com/spf13/afero" packages = [ ".", @@ -332,7 +331,7 @@ version = "v1.2.0" [[projects]] - digest = "1:627ab2f549a6a55c44f46fa24a4307f4d0da81bfc7934ed0473bf38b24051d26" + digest = "1:7ffc0983035bc7e297da3688d9fe19d60a420e9c38bef23f845c53788ed6a05e" name = "github.com/spf13/cobra" packages = ["."] pruneopts = "UT" @@ -364,7 +363,7 @@ version = "v1.0.0" [[projects]] - digest = "1:73697231b93fb74a73ebd8384b68b9a60c57ea6b13c56d2425414566a72c8e6d" + digest = "1:7e8d267900c7fa7f35129a2a37596e38ed0f11ca746d6d9ba727980ee138f9f6" name = "github.com/stretchr/testify" packages = [ "assert", @@ -376,7 +375,7 @@ [[projects]] branch = "master" - digest = "1:442d2ffa75ffae302ce8800bf4144696b92bef02917923ea132ce2d39efe7d65" + digest = "1:f2ffd421680b0a3f7887501b3c6974bcf19217ecd301d0e2c9b681940ec363d5" name = "github.com/syndtr/goleveldb" packages = [ "leveldb", @@ -397,7 +396,7 @@ [[projects]] branch = "master" - digest = "1:203b409c21115233a576f99e8f13d8e07ad82b25500491f7e1cca12588fb3232" + digest = "1:087aaa7920e5d0bf79586feb57ce01c35c830396ab4392798112e8aae8c47722" name = "github.com/tendermint/ed25519" packages = [ ".", @@ -424,7 +423,7 @@ version = "v0.9.2" [[projects]] - digest = "1:963f6c04345ce36f900c1d6367200eebc3cc2db6ee632ff865ea8dcf64b748a0" + digest = "1:4f15e95fe3888cc75dd34f407d6394cbc7fd3ff24920851b92b295f6a8b556e6" name = "github.com/tendermint/tendermint" packages = [ "abci/client", @@ -491,7 +490,7 @@ version = "v0.23.1-rc0" [[projects]] - digest = "1:ad879bb8c71020a3f92f0c61f414d93eae1d5dc2f37023b6abaa3cc84b00165e" + digest = "1:bf6d9a827ea3cad964c2f863302e4f6823170d0b5ed16f72cf1184a7c615067e" name = "github.com/tendermint/tmlibs" packages = ["cli"] pruneopts = "UT" @@ -507,7 +506,7 @@ [[projects]] branch = "master" - digest = "1:2a3ce1f08dcae8bac666deb6e4c88b5d7170c510da38fd746231144cac351704" + digest = "1:27507554c6d4f060d8d700c31c624a43d3a92baa634e178ddc044bdf7d13b44a" name = "golang.org/x/crypto" packages = [ "blowfish", @@ -529,7 +528,7 @@ revision = "614d502a4dac94afa3a6ce146bd1736da82514c6" [[projects]] - digest = "1:04dda8391c3e2397daf254ac68003f30141c069b228d06baec8324a5f81dc1e9" + digest = "1:d36f55a999540d29b6ea3c2ea29d71c76b1d9853fdcd3e5c5cb4836f2ba118f1" name = "golang.org/x/net" packages = [ "context", @@ -546,7 +545,7 @@ [[projects]] branch = "master" - digest = "1:c8baf78f0ac6eb27c645e264fe5e8a74d5a50db188ab41a7ff3b275c112e0735" + digest = "1:86171d21d59449dcf7cee0b7d2da83dff989dab9b9b69bfe0a3d59c3c1ca6081" name = "golang.org/x/sys" packages = [ "cpu", @@ -556,7 +555,7 @@ revision = "11551d06cbcc94edc80a0facaccbda56473c19c1" [[projects]] - digest = "1:7509ba4347d1f8de6ae9be8818b0cd1abc3deeffe28aeaf4be6d4b6b5178d9ca" + digest = "1:a2ab62866c75542dd18d2b069fec854577a20211d7c0ea6ae746072a1dccdd18" name = "golang.org/x/text" packages = [ "collate", @@ -587,7 +586,7 @@ revision = "c66870c02cf823ceb633bcd05be3c7cda29976f4" [[projects]] - digest = "1:4515e3030c440845b046354fd5d57671238428b820deebce2e9dabb5cd3c51ac" + digest = "1:2dab32a43451e320e49608ff4542fdfc653c95dcc35d0065ec9c6c3dd540ed74" name = "google.golang.org/grpc" packages = [ ".", @@ -664,6 +663,8 @@ "github.com/tendermint/tendermint/libs/common", "github.com/tendermint/tendermint/libs/db", "github.com/tendermint/tendermint/libs/log", + "github.com/tendermint/tendermint/lite", + "github.com/tendermint/tendermint/lite/proxy", "github.com/tendermint/tendermint/node", "github.com/tendermint/tendermint/p2p", "github.com/tendermint/tendermint/privval", From cb3e729582b6b7410d56f06077a24d9e9c545d2c Mon Sep 17 00:00:00 2001 From: HaoyangLiu Date: Fri, 31 Aug 2018 15:54:15 +0800 Subject: [PATCH 30/52] IRISHUB-238: fix test_lint failure in context.go --- client/context/context.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/context/context.go b/client/context/context.go index da37795f08..f05cf69f98 100644 --- a/client/context/context.go +++ b/client/context/context.go @@ -1,12 +1,12 @@ package context import ( - "io" "bytes" "fmt" "github.com/cosmos/cosmos-sdk/client" "github.com/cosmos/cosmos-sdk/wire" "github.com/cosmos/cosmos-sdk/x/auth" + "io" "github.com/spf13/viper" From 67857d704bda708583bd95b94dce25f28c081121 Mon Sep 17 00:00:00 2001 From: HaoyangLiu Date: Fri, 31 Aug 2018 23:20:06 +0800 Subject: [PATCH 31/52] IRISHUB-238: change wait 3 second to wait tendermint new block. use MustUnmarshalBinary instead of UnmarshalBinary --- client/context/context.go | 50 +++++++++++++++++++------------------- client/lcd/test_helpers.go | 3 +-- store/multistoreproof.go | 21 ++++++---------- store/rootmultistore.go | 5 +--- tests/util.go | 12 +++++++++ 5 files changed, 46 insertions(+), 45 deletions(-) diff --git a/client/context/context.go b/client/context/context.go index f05cf69f98..4122cbab30 100644 --- a/client/context/context.go +++ b/client/context/context.go @@ -68,32 +68,32 @@ func NewCLIContext() CLIContext { func createCertifier() tmlite.Certifier { trustNode := viper.GetBool(client.FlagTrustNode) - if !trustNode { - chainID := viper.GetString(client.FlagChainID) - home := viper.GetString(cli.HomeFlag) - nodeURI := viper.GetString(client.FlagNode) - - var errMsg bytes.Buffer - if chainID == "" { - errMsg.WriteString("chain-id ") - } - if home == "" { - errMsg.WriteString("home ") - } - if nodeURI == "" { - errMsg.WriteString("node ") - } - // errMsg is not empty - if errMsg.Len() != 0 { - panic(fmt.Errorf("can't create certifier for distrust mode, empty values from these options: %s", errMsg.String())) - } - certifier, err := tmliteProxy.GetCertifier(chainID, home, nodeURI) - if err != nil { - panic(err) - } - return certifier + if trustNode { + return nil } - return nil + chainID := viper.GetString(client.FlagChainID) + home := viper.GetString(cli.HomeFlag) + nodeURI := viper.GetString(client.FlagNode) + + var errMsg bytes.Buffer + if chainID == "" { + errMsg.WriteString("chain-id ") + } + if home == "" { + errMsg.WriteString("home ") + } + if nodeURI == "" { + errMsg.WriteString("node ") + } + // errMsg is not empty + if errMsg.Len() != 0 { + panic(fmt.Errorf("can't create certifier for distrust mode, empty values from these options: %s", errMsg.String())) + } + certifier, err := tmliteProxy.GetCertifier(chainID, home, nodeURI) + if err != nil { + panic(err) + } + return certifier } // WithCodec returns a copy of the context with an updated codec. diff --git a/client/lcd/test_helpers.go b/client/lcd/test_helpers.go index 8a344976a1..7d9a46403c 100644 --- a/client/lcd/test_helpers.go +++ b/client/lcd/test_helpers.go @@ -37,7 +37,6 @@ import ( "github.com/tendermint/tendermint/proxy" tmrpc "github.com/tendermint/tendermint/rpc/lib/server" tmtypes "github.com/tendermint/tendermint/types" - "time" ) // makePathname creates a unique pathname for each test. It will panic if it @@ -191,7 +190,7 @@ func InitializeTestLCD(t *testing.T, nValidators int, initAddrs []sdk.AccAddress node, err := startTM(config, logger, genDoc, privVal, app) require.NoError(t, err) - time.Sleep(3 * time.Second) + tests.WaitForNextHeightTM(tests.ExtractPortFromAddress(config.RPC.ListenAddress)) lcd, err := startLCD(logger, listenAddr, cdc) require.NoError(t, err) diff --git a/store/multistoreproof.go b/store/multistoreproof.go index 05eb49730a..e25f1cc1f2 100644 --- a/store/multistoreproof.go +++ b/store/multistoreproof.go @@ -15,12 +15,9 @@ type MultiStoreProof struct { } // buildMultiStoreProof build MultiStoreProof based on iavl proof and storeInfos -func buildMultiStoreProof(iavlProof []byte, storeName string, storeInfos []storeInfo) ([]byte, error) { +func buildMultiStoreProof(iavlProof []byte, storeName string, storeInfos []storeInfo) []byte { var rangeProof iavl.RangeProof - err := cdc.UnmarshalBinary(iavlProof, &rangeProof) - if err != nil { - return nil, err - } + cdc.MustUnmarshalBinary(iavlProof, &rangeProof) msp := MultiStoreProof{ StoreInfos: storeInfos, @@ -28,12 +25,8 @@ func buildMultiStoreProof(iavlProof []byte, storeName string, storeInfos []store RangeProof: rangeProof, } - proof, err := cdc.MarshalBinary(msp) - if err != nil { - return nil, err - } - - return proof, nil + proof := cdc.MustMarshalBinary(msp) + return proof } // VerifyMultiStoreCommitInfo verify multiStoreCommitInfo against appHash @@ -64,20 +57,20 @@ func VerifyMultiStoreCommitInfo(storeName string, storeInfos []storeInfo, appHas // VerifyRangeProof verify iavl RangeProof func VerifyRangeProof(key, value []byte, substoreCommitHash []byte, rangeProof *iavl.RangeProof) error { - // Verify the proof to ensure data integrity. + // verify the proof to ensure data integrity. err := rangeProof.Verify(substoreCommitHash) if err != nil { return errors.Wrap(err, "proof root hash doesn't equal to substore commit root hash") } if len(value) != 0 { - // Verify existence proof + // verify existence proof err = rangeProof.VerifyItem(key, value) if err != nil { return errors.Wrap(err, "failed in existence verification") } } else { - // Verify absence proof + // verify absence proof err = rangeProof.VerifyAbsence(key) if err != nil { return errors.Wrap(err, "failed in absence verification") diff --git a/store/rootmultistore.go b/store/rootmultistore.go index 5f6b082832..d9cf8a29a2 100644 --- a/store/rootmultistore.go +++ b/store/rootmultistore.go @@ -301,10 +301,7 @@ func (rs *rootMultiStore) Query(req abci.RequestQuery) abci.ResponseQuery { return sdk.ErrInternal(errMsg.Error()).QueryResult() } - res.Proof, errMsg = buildMultiStoreProof(res.Proof, storeName, commitInfo.StoreInfos) - if errMsg != nil { - return sdk.ErrInternal(errMsg.Error()).QueryResult() - } + res.Proof = buildMultiStoreProof(res.Proof, storeName, commitInfo.StoreInfos) return res } diff --git a/tests/util.go b/tests/util.go index 1138bc95e9..48649eaf76 100644 --- a/tests/util.go +++ b/tests/util.go @@ -10,6 +10,7 @@ import ( tmclient "github.com/tendermint/tendermint/rpc/client" ctypes "github.com/tendermint/tendermint/rpc/core/types" rpcclient "github.com/tendermint/tendermint/rpc/lib/client" + "strings" ) // Wait for the next tendermint block from the Tendermint RPC @@ -185,6 +186,17 @@ func WaitForRPC(laddr string) { } } +// ExtractPortFromAddress extract port from listenAddress +// The listenAddress must be some strings like tcp://0.0.0.0:12345 +func ExtractPortFromAddress(listenAddress string) string { + stringList := strings.Split(listenAddress, ":") + length := len(stringList) + if length != 3 { + panic(fmt.Errorf("expected listen address: tcp://0.0.0.0:12345, got %s", listenAddress)) + } + return stringList[2] +} + var cdc = amino.NewCodec() func init() { From 599923fb99e3b36d2c11ac35ba89f61a3396e0fd Mon Sep 17 00:00:00 2001 From: Alessio Treglia Date: Fri, 31 Aug 2018 19:04:11 +0200 Subject: [PATCH 32/52] Introduce simulate mode Add a simulate only flag '--dry-run' to both CLI tx commands and RESTful endpoints to trigger the simulation of unsigned transactions. * Turning --dry-run on causes the --gas flag to be ignored. The simulation will return the estimate of the gas required to actually run the transaction. * Adjustment is no longer required. It now defaults to 1.0. * In some test cases accounts retrieved from the state do not come with a PubKey. In such cases, a fake secp256k1 key is generated and gas consumption calculated accordingly. Closes: #2110 --- PENDING.md | 2 + client/context/context.go | 2 + client/flags.go | 6 +- client/lcd/lcd_test.go | 19 +++-- client/utils/rest.go | 18 +++++ client/utils/utils.go | 101 +++++++++++++++------------ cmd/gaia/cli_test/cli_test.go | 17 ++++- docs/sdk/clients.md | 15 +++- examples/democoin/x/cool/app_test.go | 16 ++--- examples/democoin/x/pow/app_test.go | 6 +- x/auth/ante.go | 86 +++++++++++++++-------- x/auth/ante_test.go | 66 ++++++++++++++++- x/auth/client/context/context.go | 29 ++++++++ x/bank/app_test.go | 77 ++++++++++---------- x/bank/client/rest/sendtx.go | 9 ++- x/gov/client/rest/rest.go | 6 +- x/gov/client/rest/util.go | 10 ++- x/ibc/app_test.go | 8 +-- x/ibc/client/rest/transfer.go | 9 ++- x/mock/app_test.go | 6 +- x/mock/test_utils.go | 4 +- x/slashing/app_test.go | 4 +- x/slashing/client/rest/tx.go | 9 ++- x/stake/app_test.go | 10 +-- x/stake/client/rest/tx.go | 8 ++- 25 files changed, 377 insertions(+), 166 deletions(-) diff --git a/PENDING.md b/PENDING.md index 832f25e8f4..c0f250e06a 100644 --- a/PENDING.md +++ b/PENDING.md @@ -40,6 +40,7 @@ FEATURES * Gaia REST API (`gaiacli advanced rest-server`) * [lcd] Endpoints to query staking pool and params + * [lcd] \#2110 Add support for `simulate=true` requests query argument to endpoints that send txs to run simulations of transactions * Gaia CLI (`gaiacli`) * [cli] Cmds to query staking pool and params @@ -48,6 +49,7 @@ FEATURES provide desired Bech32 prefix encoding * [cli] \#2047 Setting the --gas flag value to 0 triggers a simulation of the tx before the actual execution. The gas estimate obtained via the simulation will be used as gas limit in the actual execution. * [cli] \#2047 The --gas-adjustment flag can be used to adjust the estimate obtained via the simulation triggered by --gas=0. + * [cli] \#2110 Add --dry-run flag to perform a simulation of a transaction without broadcasting it. The --gas flag is ignored as gas would be automatically estimated. * Gaia * [cli] #2170 added ability to show the node's address via `gaiad tendermint show-address` diff --git a/client/context/context.go b/client/context/context.go index 4122cbab30..64d2375d09 100644 --- a/client/context/context.go +++ b/client/context/context.go @@ -37,6 +37,7 @@ type CLIContext struct { JSON bool PrintResponse bool Certifier tmlite.Certifier + DryRun bool } // NewCLIContext returns a new initialized CLIContext with parameters from the @@ -63,6 +64,7 @@ func NewCLIContext() CLIContext { JSON: viper.GetBool(client.FlagJson), PrintResponse: viper.GetBool(client.FlagPrintResponse), Certifier: createCertifier(), + DryRun: viper.GetBool(client.FlagDryRun), } } diff --git a/client/flags.go b/client/flags.go index 12bddb7d96..3bb4d7bc33 100644 --- a/client/flags.go +++ b/client/flags.go @@ -5,7 +5,7 @@ import "github.com/spf13/cobra" // nolint const ( DefaultGasLimit = 200000 - DefaultGasAdjustment = 1.2 + DefaultGasAdjustment = 1.0 FlagUseLedger = "ledger" FlagChainID = "chain-id" @@ -23,6 +23,7 @@ const ( FlagAsync = "async" FlagJson = "json" FlagPrintResponse = "print-response" + FlagDryRun = "dry-run" ) // LineBreak can be included in a command list to provide a blank line @@ -54,11 +55,12 @@ func PostCommands(cmds ...*cobra.Command) []*cobra.Command { c.Flags().String(FlagNode, "tcp://localhost:26657", ": to tendermint rpc interface for this chain") c.Flags().Bool(FlagUseLedger, false, "Use a connected Ledger device") c.Flags().Int64(FlagGas, DefaultGasLimit, "gas limit to set per-transaction; set to 0 to calculate required gas automatically") - c.Flags().Float64(FlagGasAdjustment, DefaultGasAdjustment, "adjustment factor to be multiplied against the estimate returned by the tx simulation") + c.Flags().Float64(FlagGasAdjustment, DefaultGasAdjustment, "adjustment factor to be multiplied against the estimate returned by the tx simulation; if the gas limit is set manually this flag is ignored ") c.Flags().Bool(FlagAsync, false, "broadcast transactions asynchronously") c.Flags().Bool(FlagJson, false, "return output in json format") c.Flags().Bool(FlagPrintResponse, true, "return tx response (only works with async = false)") c.Flags().Bool(FlagTrustNode, true, "Don't verify proofs for query responses") + c.Flags().Bool(FlagDryRun, false, "ignore the --gas flag and perform a simulation of a transaction, but don't broadcast it") } return cmds } diff --git a/client/lcd/lcd_test.go b/client/lcd/lcd_test.go index d68ba278c1..5a0694976b 100644 --- a/client/lcd/lcd_test.go +++ b/client/lcd/lcd_test.go @@ -2,6 +2,7 @@ package lcd import ( "encoding/hex" + "encoding/json" "fmt" "net/http" "regexp" @@ -265,11 +266,17 @@ func TestCoinSend(t *testing.T) { require.Equal(t, int64(1), mycoins.Amount.Int64()) // test failure with too little gas - res, body, _ = doSendWithGas(t, port, seed, name, password, addr, 100) + res, body, _ = doSendWithGas(t, port, seed, name, password, addr, 100, "") require.Equal(t, http.StatusInternalServerError, res.StatusCode, body) - // test success with just enough gas - res, body, _ = doSendWithGas(t, port, seed, name, password, addr, 3000) + // run simulation and test success with estimated gas + res, body, _ = doSendWithGas(t, port, seed, name, password, addr, 0, "?simulate=true") + require.Equal(t, http.StatusOK, res.StatusCode, body) + var responseBody struct { + GasEstimate int64 `json:"gas_estimate"` + } + require.Nil(t, json.Unmarshal([]byte(body), &responseBody)) + res, body, _ = doSendWithGas(t, port, seed, name, password, addr, responseBody.GasEstimate, "") require.Equal(t, http.StatusOK, res.StatusCode, body) } @@ -720,7 +727,7 @@ func getAccount(t *testing.T, port string, addr sdk.AccAddress) auth.Account { return acc } -func doSendWithGas(t *testing.T, port, seed, name, password string, addr sdk.AccAddress, gas int64) (res *http.Response, body string, receiveAddr sdk.AccAddress) { +func doSendWithGas(t *testing.T, port, seed, name, password string, addr sdk.AccAddress, gas int64, queryStr string) (res *http.Response, body string, receiveAddr sdk.AccAddress) { // create receive address kb := client.MockKeyBase() @@ -754,12 +761,12 @@ func doSendWithGas(t *testing.T, port, seed, name, password string, addr sdk.Acc "chain_id":"%s" }`, gasStr, name, password, accnum, sequence, coinbz, chainID)) - res, body = Request(t, port, "POST", fmt.Sprintf("/accounts/%s/send", receiveAddr), jsonStr) + res, body = Request(t, port, "POST", fmt.Sprintf("/accounts/%s/send%v", receiveAddr, queryStr), jsonStr) return } func doSend(t *testing.T, port, seed, name, password string, addr sdk.AccAddress) (receiveAddr sdk.AccAddress, resultTx ctypes.ResultBroadcastTxCommit) { - res, body, receiveAddr := doSendWithGas(t, port, seed, name, password, addr, 0) + res, body, receiveAddr := doSendWithGas(t, port, seed, name, password, addr, 0, "") require.Equal(t, http.StatusOK, res.StatusCode, body) err := cdc.UnmarshalJSON([]byte(body), &resultTx) diff --git a/client/utils/rest.go b/client/utils/rest.go index 0e26403124..4bbc10b328 100644 --- a/client/utils/rest.go +++ b/client/utils/rest.go @@ -1,12 +1,30 @@ package utils import ( + "fmt" "net/http" ) +const ( + queryArgDryRun = "simulate" +) + // WriteErrorResponse prepares and writes a HTTP error // given a status code and an error message. func WriteErrorResponse(w *http.ResponseWriter, status int, msg string) { (*w).WriteHeader(status) (*w).Write([]byte(msg)) } + +// WriteGasEstimateResponse prepares and writes an HTTP +// response for transactions simulations. +func WriteSimulationResponse(w *http.ResponseWriter, gas int64) { + (*w).WriteHeader(http.StatusOK) + (*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 r.URL.Query().Get(queryArgDryRun) == "true" +} diff --git a/client/utils/utils.go b/client/utils/utils.go index fb5d619887..acf90efaa0 100644 --- a/client/utils/utils.go +++ b/client/utils/utils.go @@ -15,7 +15,7 @@ import ( // DefaultGasAdjustment is applied to gas estimates to avoid tx // execution failures due to state changes that might // occur between the tx simulation and the actual run. -const DefaultGasAdjustment = 1.2 +const DefaultGasAdjustment = 1.0 // SendTx implements a auxiliary handler that facilitates sending a series of // messages in a signed transaction given a TxContext and a QueryContext. It @@ -23,35 +23,20 @@ const DefaultGasAdjustment = 1.2 // addition, it builds and signs a transaction with the supplied messages. // Finally, it broadcasts the signed transaction to a node. func SendTx(txCtx authctx.TxContext, cliCtx context.CLIContext, msgs []sdk.Msg) error { - if err := cliCtx.EnsureAccountExists(); err != nil { - return err - } - - from, err := cliCtx.GetFromAddress() + txCtx, err := prepareTxContext(txCtx, cliCtx) if err != nil { return err } - - // TODO: (ref #1903) Allow for user supplied account number without - // automatically doing a manual lookup. - if txCtx.AccountNumber == 0 { - accNum, err := cliCtx.GetAccountNumber(from) + autogas := cliCtx.DryRun || (cliCtx.Gas == 0) + if autogas { + txCtx, err = EnrichCtxWithGas(txCtx, cliCtx, cliCtx.FromAddressName, msgs) if err != nil { return err } - - txCtx = txCtx.WithAccountNumber(accNum) + fmt.Fprintf(os.Stdout, "estimated gas = %v\n", txCtx.Gas) } - - // TODO: (ref #1903) Allow for user supplied account sequence without - // automatically doing a manual lookup. - if txCtx.Sequence == 0 { - accSeq, err := cliCtx.GetAccountSequence(from) - if err != nil { - return err - } - - txCtx = txCtx.WithSequence(accSeq) + if cliCtx.DryRun { + return nil } passphrase, err := keys.GetPassphrase(cliCtx.FromAddressName) @@ -59,13 +44,6 @@ func SendTx(txCtx authctx.TxContext, cliCtx context.CLIContext, msgs []sdk.Msg) return err } - if cliCtx.Gas == 0 { - txCtx, err = EnrichCtxWithGas(txCtx, cliCtx, cliCtx.FromAddressName, passphrase, msgs) - if err != nil { - return err - } - } - // build and sign the transaction txBytes, err := txCtx.BuildAndSign(cliCtx.FromAddressName, passphrase, msgs) if err != nil { @@ -75,24 +53,24 @@ func SendTx(txCtx authctx.TxContext, cliCtx context.CLIContext, msgs []sdk.Msg) return cliCtx.EnsureBroadcastTx(txBytes) } -// EnrichCtxWithGas calculates the gas estimate that would be consumed by the -// transaction and set the transaction's respective value accordingly. -func EnrichCtxWithGas(txCtx authctx.TxContext, cliCtx context.CLIContext, name, passphrase string, msgs []sdk.Msg) (authctx.TxContext, error) { - txBytes, err := BuildAndSignTxWithZeroGas(txCtx, name, passphrase, msgs) +// SimulateMsgs simulates the transaction and returns the gas estimate and the adjusted value. +func SimulateMsgs(txCtx authctx.TxContext, cliCtx context.CLIContext, name string, msgs []sdk.Msg, gas int64) (estimated, adjusted int64, err error) { + txBytes, err := txCtx.WithGas(gas).BuildWithPubKey(name, msgs) if err != nil { - return txCtx, err + return } - estimate, adjusted, err := CalculateGas(cliCtx.Query, cliCtx.Codec, txBytes, cliCtx.GasAdjustment) - if err != nil { - return txCtx, err - } - fmt.Fprintf(os.Stderr, "gas: [estimated = %v] [adjusted = %v]\n", estimate, adjusted) - return txCtx.WithGas(adjusted), nil + estimated, adjusted, err = CalculateGas(cliCtx.Query, cliCtx.Codec, txBytes, cliCtx.GasAdjustment) + return } -// BuildAndSignTxWithZeroGas builds transactions with GasWanted set to 0. -func BuildAndSignTxWithZeroGas(txCtx authctx.TxContext, name, passphrase string, msgs []sdk.Msg) ([]byte, error) { - return txCtx.WithGas(0).BuildAndSign(name, passphrase, msgs) +// EnrichCtxWithGas calculates the gas estimate that would be consumed by the +// transaction and set the transaction's respective value accordingly. +func EnrichCtxWithGas(txCtx authctx.TxContext, cliCtx context.CLIContext, name string, msgs []sdk.Msg) (authctx.TxContext, error) { + _, adjusted, err := SimulateMsgs(txCtx, cliCtx, name, msgs, 0) + if err != nil { + return txCtx, err + } + return txCtx.WithGas(adjusted), nil } // CalculateGas simulates the execution of a transaction and returns @@ -109,13 +87,12 @@ func CalculateGas(queryFunc func(string, common.HexBytes) ([]byte, error), cdc * return } adjusted = adjustGasEstimate(estimate, adjustment) - fmt.Fprintf(os.Stderr, "gas: [estimated = %v] [adjusted = %v]\n", estimate, adjusted) return } func adjustGasEstimate(estimate int64, adjustment float64) int64 { if adjustment == 0 { - return int64(DefaultGasAdjustment * float64(estimate)) + adjustment = DefaultGasAdjustment } return int64(adjustment * float64(estimate)) } @@ -127,3 +104,35 @@ func parseQueryResponse(cdc *amino.Codec, rawRes []byte) (int64, error) { } return simulationResult.GasUsed, nil } + +func prepareTxContext(txCtx authctx.TxContext, cliCtx context.CLIContext) (authctx.TxContext, error) { + if err := cliCtx.EnsureAccountExists(); err != nil { + return txCtx, err + } + + from, err := cliCtx.GetFromAddress() + if err != nil { + return txCtx, err + } + + // TODO: (ref #1903) Allow for user supplied account number without + // automatically doing a manual lookup. + if txCtx.AccountNumber == 0 { + accNum, err := cliCtx.GetAccountNumber(from) + if err != nil { + return txCtx, err + } + txCtx = txCtx.WithAccountNumber(accNum) + } + + // TODO: (ref #1903) Allow for user supplied account sequence without + // automatically doing a manual lookup. + if txCtx.Sequence == 0 { + accSeq, err := cliCtx.GetAccountSequence(from) + if err != nil { + return txCtx, err + } + txCtx = txCtx.WithSequence(accSeq) + } + return txCtx, nil +} diff --git a/cmd/gaia/cli_test/cli_test.go b/cmd/gaia/cli_test/cli_test.go index b91b5a45d6..696c0fc6ab 100644 --- a/cmd/gaia/cli_test/cli_test.go +++ b/cmd/gaia/cli_test/cli_test.go @@ -58,6 +58,13 @@ func TestGaiaCLISend(t *testing.T) { fooAcc = executeGetAccount(t, fmt.Sprintf("gaiacli 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) + require.True(t, success) + // Check state didn't change + fooAcc = executeGetAccount(t, fmt.Sprintf("gaiacli 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) tests.WaitForNextNBlocksTM(2, port) @@ -148,6 +155,10 @@ func TestGaiaCLICreateValidator(t *testing.T) { initialPool.BondedTokens = initialPool.BondedTokens.Add(sdk.NewDec(1)) + // Test --dry-run + success := executeWrite(t, cvStr+" --dry-run", app.DefaultKeyPass) + require.True(t, success) + executeWrite(t, cvStr, app.DefaultKeyPass) tests.WaitForNextNBlocksTM(2, port) @@ -164,7 +175,7 @@ func TestGaiaCLICreateValidator(t *testing.T) { unbondStr += fmt.Sprintf(" --validator=%s", sdk.ValAddress(barAddr)) unbondStr += fmt.Sprintf(" --shares-amount=%v", "1") - success := executeWrite(t, unbondStr, app.DefaultKeyPass) + success = executeWrite(t, unbondStr, app.DefaultKeyPass) require.True(t, success) tests.WaitForNextNBlocksTM(2, port) @@ -211,6 +222,10 @@ func TestGaiaCLISubmitProposal(t *testing.T) { spStr += fmt.Sprintf(" --title=%s", "Test") spStr += fmt.Sprintf(" --description=%s", "test") + // Test --dry-run + success := executeWrite(t, spStr+" --dry-run", app.DefaultKeyPass) + require.True(t, success) + executeWrite(t, spStr, app.DefaultKeyPass) tests.WaitForNextNBlocksTM(2, port) diff --git a/docs/sdk/clients.md b/docs/sdk/clients.md index 7a23ba28ac..62d79bbacd 100644 --- a/docs/sdk/clients.md +++ b/docs/sdk/clients.md @@ -95,6 +95,8 @@ When you query an account balance with zero tokens, you will get this error: `No ### Send Tokens +The following command could be used to send coins from one account to another: + ```bash gaiacli send \ --amount=10faucetToken \ @@ -110,7 +112,7 @@ The `--amount` flag accepts the format `--amount=`. ::: tip Note You may want to cap the maximum gas that can be consumed by the transaction via the `--gas` flag. If set to 0, the gas limit will be automatically estimated. -Gas estimate might be inaccurate as state changes could occur in between the end of the simulation and the actual execution of a transaction, thus an adjustment is applied on top of the original estimate in order to ensure the transaction is broadcasted successfully. The adjustment can be controlled via the `--gas-adjustment` flag, whose default value is 1.2. +Gas estimate might be inaccurate as state changes could occur in between the end of the simulation and the actual execution of a transaction, thus an adjustment is applied on top of the original estimate in order to ensure the transaction is broadcasted successfully. The adjustment can be controlled via the `--gas-adjustment` flag, whose default value is 1.0. ::: Now, view the updated balances of the origin and destination accounts: @@ -126,6 +128,17 @@ You can also check your balance at a given block by using the `--block` flag: 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 \ + --amount=10faucetToken \ + --chain-id= \ + --name= \ + --to= \ + --dry-run +``` + ### Staking #### Set up a Validator diff --git a/examples/democoin/x/cool/app_test.go b/examples/democoin/x/cool/app_test.go index 71b4202bc3..c0453a58ec 100644 --- a/examples/democoin/x/cool/app_test.go +++ b/examples/democoin/x/cool/app_test.go @@ -88,17 +88,17 @@ func TestMsgQuiz(t *testing.T) { require.Equal(t, acc1, res1) // Set the trend, submit a really cool quiz and check for reward - mock.SignCheckDeliver(t, mapp.BaseApp, []sdk.Msg{setTrendMsg1}, []int64{0}, []int64{0}, true, priv1) - mock.SignCheckDeliver(t, mapp.BaseApp, []sdk.Msg{quizMsg1}, []int64{0}, []int64{1}, true, priv1) + mock.SignCheckDeliver(t, mapp.BaseApp, []sdk.Msg{setTrendMsg1}, []int64{0}, []int64{0}, true, true, priv1) + mock.SignCheckDeliver(t, mapp.BaseApp, []sdk.Msg{quizMsg1}, []int64{0}, []int64{1}, true, true, priv1) mock.CheckBalance(t, mapp, addr1, sdk.Coins{{"icecold", sdk.NewInt(69)}}) - mock.SignCheckDeliver(t, mapp.BaseApp, []sdk.Msg{quizMsg2}, []int64{0}, []int64{2}, false, priv1) // result without reward + mock.SignCheckDeliver(t, mapp.BaseApp, []sdk.Msg{quizMsg2}, []int64{0}, []int64{2}, false, false, priv1) // result without reward mock.CheckBalance(t, mapp, addr1, sdk.Coins{{"icecold", sdk.NewInt(69)}}) - mock.SignCheckDeliver(t, mapp.BaseApp, []sdk.Msg{quizMsg1}, []int64{0}, []int64{3}, true, priv1) + mock.SignCheckDeliver(t, mapp.BaseApp, []sdk.Msg{quizMsg1}, []int64{0}, []int64{3}, true, true, priv1) mock.CheckBalance(t, mapp, addr1, sdk.Coins{{"icecold", sdk.NewInt(138)}}) - mock.SignCheckDeliver(t, mapp.BaseApp, []sdk.Msg{setTrendMsg2}, []int64{0}, []int64{4}, true, priv1) // reset the trend - mock.SignCheckDeliver(t, mapp.BaseApp, []sdk.Msg{quizMsg1}, []int64{0}, []int64{5}, false, priv1) // the same answer will nolonger do! + mock.SignCheckDeliver(t, mapp.BaseApp, []sdk.Msg{setTrendMsg2}, []int64{0}, []int64{4}, true, true, priv1) // reset the trend + mock.SignCheckDeliver(t, mapp.BaseApp, []sdk.Msg{quizMsg1}, []int64{0}, []int64{5}, false, false, priv1) // the same answer will nolonger do! mock.CheckBalance(t, mapp, addr1, sdk.Coins{{"icecold", sdk.NewInt(138)}}) - mock.SignCheckDeliver(t, mapp.BaseApp, []sdk.Msg{quizMsg2}, []int64{0}, []int64{6}, true, priv1) // earlier answer now relevant again + mock.SignCheckDeliver(t, mapp.BaseApp, []sdk.Msg{quizMsg2}, []int64{0}, []int64{6}, true, true, priv1) // earlier answer now relevant again mock.CheckBalance(t, mapp, addr1, sdk.Coins{{"badvibesonly", sdk.NewInt(69)}, {"icecold", sdk.NewInt(138)}}) - mock.SignCheckDeliver(t, mapp.BaseApp, []sdk.Msg{setTrendMsg3}, []int64{0}, []int64{7}, false, priv1) // expect to fail to set the trend to something which is not cool + mock.SignCheckDeliver(t, mapp.BaseApp, []sdk.Msg{setTrendMsg3}, []int64{0}, []int64{7}, false, false, priv1) // expect to fail to set the trend to something which is not cool } diff --git a/examples/democoin/x/pow/app_test.go b/examples/democoin/x/pow/app_test.go index dc53d1d998..76f062e3b0 100644 --- a/examples/democoin/x/pow/app_test.go +++ b/examples/democoin/x/pow/app_test.go @@ -74,13 +74,13 @@ func TestMsgMine(t *testing.T) { // Mine and check for reward mineMsg1 := GenerateMsgMine(addr1, 1, 2) - mock.SignCheckDeliver(t, mapp.BaseApp, []sdk.Msg{mineMsg1}, []int64{0}, []int64{0}, true, priv1) + mock.SignCheckDeliver(t, mapp.BaseApp, []sdk.Msg{mineMsg1}, []int64{0}, []int64{0}, true, true, priv1) mock.CheckBalance(t, mapp, addr1, sdk.Coins{{"pow", sdk.NewInt(1)}}) // Mine again and check for reward mineMsg2 := GenerateMsgMine(addr1, 2, 3) - mock.SignCheckDeliver(t, mapp.BaseApp, []sdk.Msg{mineMsg2}, []int64{0}, []int64{1}, true, priv1) + mock.SignCheckDeliver(t, mapp.BaseApp, []sdk.Msg{mineMsg2}, []int64{0}, []int64{1}, true, true, priv1) mock.CheckBalance(t, mapp, addr1, sdk.Coins{{"pow", sdk.NewInt(2)}}) // Mine again - should be invalid - mock.SignCheckDeliver(t, mapp.BaseApp, []sdk.Msg{mineMsg2}, []int64{0}, []int64{1}, false, priv1) + mock.SignCheckDeliver(t, mapp.BaseApp, []sdk.Msg{mineMsg2}, []int64{0}, []int64{1}, false, false, priv1) mock.CheckBalance(t, mapp, addr1, sdk.Coins{{"pow", sdk.NewInt(2)}}) } diff --git a/x/auth/ante.go b/x/auth/ante.go index c0a129be3e..94899a8726 100644 --- a/x/auth/ante.go +++ b/x/auth/ante.go @@ -65,9 +65,12 @@ func NewAnteHandler(am AccountMapper, fck FeeCollectionKeeper) sdk.AnteHandler { return newCtx, err.Result(), true } - sigs := stdTx.GetSignatures() + sigs := stdTx.GetSignatures() // When simulating, this would just be a 0-length slice. signerAddrs := stdTx.GetSigners() msgs := tx.GetMsgs() + if simulate { + sigs = make([]StdSignature, len(signerAddrs)) + } // charge gas for the memo newCtx.GasMeter().ConsumeGas(memoCostPerByte*sdk.Gas(len(stdTx.GetMemo())), "memo") @@ -88,10 +91,7 @@ func NewAnteHandler(am AccountMapper, fck FeeCollectionKeeper) sdk.AnteHandler { // 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, - ) + signerAcc, res := processSig(newCtx, am, signerAddr, sig, signBytes, simulate) if !res.IsOK() { return newCtx, res, true } @@ -149,58 +149,82 @@ func validateBasic(tx StdTx) (err sdk.Error) { // if the account doesn't have a pubkey, set it. func processSig( ctx sdk.Context, am AccountMapper, - addr sdk.AccAddress, sig StdSignature, signBytes []byte) ( + 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() } - // Check account number. accnum := acc.GetAccountNumber() - if accnum != sig.AccountNumber { - return nil, sdk.ErrInvalidSequence( - fmt.Sprintf("Invalid account number. Got %d, expected %d", sig.AccountNumber, accnum)).Result() - } - - // Check and increment sequence number. seq := acc.GetSequence() - if seq != sig.Sequence { - return nil, sdk.ErrInvalidSequence( - fmt.Sprintf("Invalid sequence. Got %d, expected %d", sig.Sequence, seq)).Result() + + // Perform checks that wouldn't pass successfully in simulation, i.e. sig + // would be empty as simulated transactions come with no signatures whatsoever. + if !simulate { + // 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() + } } + // Increment sequence number err := acc.SetSequence(seq + 1) if err != nil { // Handle w/ #870 panic(err) } + // If pubkey is not known for account, + // set it from the StdSignature. + pubKey, res := processPubKey(acc, sig, simulate) + if !res.IsOK() { + return nil, res + } + err = acc.SetPubKey(pubKey) + if err != nil { + return nil, sdk.ErrInternal("setting PubKey on signer's account").Result() + } + + consumeSignatureVerificationGas(ctx.GasMeter(), pubKey) + if !simulate && !pubKey.VerifyBytes(signBytes, sig.Signature) { + return nil, sdk.ErrUnauthorized("signature verification failed").Result() + } + + return +} + +func processPubKey(acc Account, sig StdSignature, simulate bool) (crypto.PubKey, sdk.Result) { // If pubkey is not known for account, // set it from the StdSignature. pubKey := acc.GetPubKey() + if simulate { + // In simulate mode the transaction comes with no signatures, thus + // if the account's pubkey is nil, both signature verification + // and gasKVStore.Set() shall consume the largest amount, i.e. + // it takes more gas to verifiy secp256k1 keys than ed25519 ones. + if pubKey == nil { + return secp256k1.GenPrivKey().PubKey(), sdk.Result{} + } + return pubKey, sdk.Result{} + } if pubKey == nil { pubKey = sig.PubKey if pubKey == nil { return nil, sdk.ErrInvalidPubKey("PubKey not found").Result() } - if !bytes.Equal(pubKey.Address(), addr) { + if !bytes.Equal(pubKey.Address(), acc.GetAddress()) { return nil, sdk.ErrInvalidPubKey( - fmt.Sprintf("PubKey does not match Signer address %v", addr)).Result() - } - err = acc.SetPubKey(pubKey) - if err != nil { - return nil, sdk.ErrInternal("setting PubKey on signer's account").Result() + fmt.Sprintf("PubKey does not match Signer address %v", acc.GetAddress())).Result() } } - - // Check sig. - consumeSignatureVerificationGas(ctx.GasMeter(), pubKey) - if !pubKey.VerifyBytes(signBytes, sig.Signature) { - return nil, sdk.ErrUnauthorized("signature verification failed").Result() - } - - return + return pubKey, sdk.Result{} } func consumeSignatureVerificationGas(meter sdk.GasMeter, pubkey crypto.PubKey) { diff --git a/x/auth/ante_test.go b/x/auth/ante_test.go index a841bc7760..e137845122 100644 --- a/x/auth/ante_test.go +++ b/x/auth/ante_test.go @@ -4,14 +4,14 @@ import ( "fmt" "testing" + sdk "github.com/cosmos/cosmos-sdk/types" + wire "github.com/cosmos/cosmos-sdk/wire" "github.com/stretchr/testify/require" abci "github.com/tendermint/tendermint/abci/types" "github.com/tendermint/tendermint/crypto" "github.com/tendermint/tendermint/crypto/ed25519" + "github.com/tendermint/tendermint/crypto/secp256k1" "github.com/tendermint/tendermint/libs/log" - - sdk "github.com/cosmos/cosmos-sdk/types" - wire "github.com/cosmos/cosmos-sdk/wire" ) func newTestMsg(addrs ...sdk.AccAddress) *sdk.TestMsg { @@ -567,3 +567,63 @@ func TestAnteHandlerSetPubKey(t *testing.T) { acc2 = mapper.GetAccount(ctx, addr2) require.Nil(t, acc2.GetPubKey()) } + +func TestProcessPubKey(t *testing.T) { + ms, capKey, _ := setupMultiStore() + cdc := wire.NewCodec() + RegisterBaseAccount(cdc) + mapper := NewAccountMapper(cdc, capKey, ProtoBaseAccount) + ctx := sdk.NewContext(ms, abci.Header{ChainID: "mychainid"}, false, log.NewNopLogger()) + // keys + _, addr1 := privAndAddr() + priv2, _ := privAndAddr() + acc1 := mapper.NewAccountWithAddress(ctx, addr1) + type args struct { + acc Account + sig StdSignature + simulate bool + } + tests := []struct { + name string + args args + wantErr bool + }{ + {"no sigs, simulate off", args{acc1, StdSignature{}, false}, true}, + {"no sigs, simulate on", args{acc1, StdSignature{}, true}, false}, + {"pubkey doesn't match addr, simulate off", args{acc1, StdSignature{PubKey: priv2.PubKey()}, false}, true}, + {"pubkey doesn't match addr, simulate on", args{acc1, StdSignature{PubKey: priv2.PubKey()}, true}, false}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + _, err := processPubKey(tt.args.acc, tt.args.sig, tt.args.simulate) + require.Equal(t, tt.wantErr, !err.IsOK()) + }) + } +} + +func TestConsumeSignatureVerificationGas(t *testing.T) { + type args struct { + meter sdk.GasMeter + pubkey crypto.PubKey + } + tests := []struct { + name string + args args + gasConsumed int64 + wantPanic bool + }{ + {"PubKeyEd25519", args{sdk.NewInfiniteGasMeter(), ed25519.GenPrivKey().PubKey()}, ed25519VerifyCost, false}, + {"PubKeySecp256k1", args{sdk.NewInfiniteGasMeter(), secp256k1.GenPrivKey().PubKey()}, secp256k1VerifyCost, false}, + {"unknown key", args{sdk.NewInfiniteGasMeter(), nil}, 0, true}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if tt.wantPanic { + require.Panics(t, func() { consumeSignatureVerificationGas(tt.args.meter, tt.args.pubkey) }) + } else { + consumeSignatureVerificationGas(tt.args.meter, tt.args.pubkey) + require.Equal(t, tt.args.meter.GasConsumed(), tt.gasConsumed) + } + }) + } +} diff --git a/x/auth/client/context/context.go b/x/auth/client/context/context.go index 8d0a94136d..5e55696b83 100644 --- a/x/auth/client/context/context.go +++ b/x/auth/client/context/context.go @@ -148,3 +148,32 @@ func (ctx TxContext) BuildAndSign(name, passphrase string, msgs []sdk.Msg) ([]by return ctx.Sign(name, passphrase, msg) } + +// BuildWithPubKey builds a single message to be signed from a TxContext given a set of +// messages and attach the public key associated to the given name. +// It returns an error if a fee is supplied but cannot be parsed or the key cannot be +// retrieved. +func (ctx TxContext) BuildWithPubKey(name string, msgs []sdk.Msg) ([]byte, error) { + msg, err := ctx.Build(msgs) + if err != nil { + return nil, err + } + + keybase, err := keys.GetKeyBase() + if err != nil { + return nil, err + } + + info, err := keybase.Get(name) + if err != nil { + return nil, err + } + + sigs := []auth.StdSignature{{ + AccountNumber: msg.AccountNumber, + Sequence: msg.Sequence, + PubKey: info.GetPubKey(), + }} + + return ctx.Codec.MarshalBinary(auth.NewStdTx(msg.Msgs, msg.Fee, sigs, msg.Memo)) +} diff --git a/x/bank/app_test.go b/x/bank/app_test.go index c8d0a417dc..7ef344ffcd 100644 --- a/x/bank/app_test.go +++ b/x/bank/app_test.go @@ -21,6 +21,7 @@ type ( } appTestCase struct { + expSimPass bool expPass bool msgs []sdk.Msg accNums []int64 @@ -107,27 +108,29 @@ func TestMsgSendWithAccounts(t *testing.T) { testCases := []appTestCase{ { - msgs: []sdk.Msg{sendMsg1}, - accNums: []int64{0}, - accSeqs: []int64{0}, - expPass: true, - privKeys: []crypto.PrivKey{priv1}, + msgs: []sdk.Msg{sendMsg1}, + accNums: []int64{0}, + accSeqs: []int64{0}, + expSimPass: true, + expPass: true, + privKeys: []crypto.PrivKey{priv1}, expectedBalances: []expectedBalance{ {addr1, sdk.Coins{sdk.NewInt64Coin("foocoin", 57)}}, {addr2, sdk.Coins{sdk.NewInt64Coin("foocoin", 10)}}, }, }, { - msgs: []sdk.Msg{sendMsg1, sendMsg2}, - accNums: []int64{0}, - accSeqs: []int64{0}, - expPass: false, - privKeys: []crypto.PrivKey{priv1}, + msgs: []sdk.Msg{sendMsg1, sendMsg2}, + accNums: []int64{0}, + accSeqs: []int64{0}, + expSimPass: true, + expPass: false, + privKeys: []crypto.PrivKey{priv1}, }, } for _, tc := range testCases { - mock.SignCheckDeliver(t, mapp.BaseApp, tc.msgs, tc.accNums, tc.accSeqs, tc.expPass, tc.privKeys...) + mock.SignCheckDeliver(t, mapp.BaseApp, tc.msgs, tc.accNums, tc.accSeqs, tc.expSimPass, tc.expPass, tc.privKeys...) for _, eb := range tc.expectedBalances { mock.CheckBalance(t, mapp, eb.addr, eb.coins) @@ -144,7 +147,7 @@ func TestMsgSendWithAccounts(t *testing.T) { require.Equal(t, sdk.ToABCICode(sdk.CodespaceRoot, sdk.CodeUnauthorized), res.Code, res.Log) // resigning the tx with the bumped sequence should work - mock.SignCheckDeliver(t, mapp.BaseApp, []sdk.Msg{sendMsg1, sendMsg2}, []int64{0}, []int64{1}, true, priv1) + mock.SignCheckDeliver(t, mapp.BaseApp, []sdk.Msg{sendMsg1, sendMsg2}, []int64{0}, []int64{1}, true, true, priv1) } func TestMsgSendMultipleOut(t *testing.T) { @@ -163,11 +166,12 @@ func TestMsgSendMultipleOut(t *testing.T) { testCases := []appTestCase{ { - msgs: []sdk.Msg{sendMsg2}, - accNums: []int64{0}, - accSeqs: []int64{0}, - expPass: true, - privKeys: []crypto.PrivKey{priv1}, + msgs: []sdk.Msg{sendMsg2}, + accNums: []int64{0}, + accSeqs: []int64{0}, + expSimPass: true, + expPass: true, + privKeys: []crypto.PrivKey{priv1}, expectedBalances: []expectedBalance{ {addr1, sdk.Coins{sdk.NewInt64Coin("foocoin", 32)}}, {addr2, sdk.Coins{sdk.NewInt64Coin("foocoin", 47)}}, @@ -177,7 +181,7 @@ func TestMsgSendMultipleOut(t *testing.T) { } for _, tc := range testCases { - mock.SignCheckDeliver(t, mapp.BaseApp, tc.msgs, tc.accNums, tc.accSeqs, tc.expPass, tc.privKeys...) + mock.SignCheckDeliver(t, mapp.BaseApp, tc.msgs, tc.accNums, tc.accSeqs, tc.expSimPass, tc.expPass, tc.privKeys...) for _, eb := range tc.expectedBalances { mock.CheckBalance(t, mapp, eb.addr, eb.coins) @@ -205,11 +209,12 @@ func TestSengMsgMultipleInOut(t *testing.T) { testCases := []appTestCase{ { - msgs: []sdk.Msg{sendMsg3}, - accNums: []int64{0, 2}, - accSeqs: []int64{0, 0}, - expPass: true, - privKeys: []crypto.PrivKey{priv1, priv4}, + msgs: []sdk.Msg{sendMsg3}, + accNums: []int64{0, 2}, + accSeqs: []int64{0, 0}, + expSimPass: true, + expPass: true, + privKeys: []crypto.PrivKey{priv1, priv4}, expectedBalances: []expectedBalance{ {addr1, sdk.Coins{sdk.NewInt64Coin("foocoin", 32)}}, {addr4, sdk.Coins{sdk.NewInt64Coin("foocoin", 32)}}, @@ -220,7 +225,7 @@ func TestSengMsgMultipleInOut(t *testing.T) { } for _, tc := range testCases { - mock.SignCheckDeliver(t, mapp.BaseApp, tc.msgs, tc.accNums, tc.accSeqs, tc.expPass, tc.privKeys...) + mock.SignCheckDeliver(t, mapp.BaseApp, tc.msgs, tc.accNums, tc.accSeqs, tc.expSimPass, tc.expPass, tc.privKeys...) for _, eb := range tc.expectedBalances { mock.CheckBalance(t, mapp, eb.addr, eb.coins) @@ -240,22 +245,24 @@ func TestMsgSendDependent(t *testing.T) { testCases := []appTestCase{ { - msgs: []sdk.Msg{sendMsg1}, - accNums: []int64{0}, - accSeqs: []int64{0}, - expPass: true, - privKeys: []crypto.PrivKey{priv1}, + msgs: []sdk.Msg{sendMsg1}, + accNums: []int64{0}, + accSeqs: []int64{0}, + expSimPass: true, + expPass: true, + privKeys: []crypto.PrivKey{priv1}, expectedBalances: []expectedBalance{ {addr1, sdk.Coins{sdk.NewInt64Coin("foocoin", 32)}}, {addr2, sdk.Coins{sdk.NewInt64Coin("foocoin", 10)}}, }, }, { - msgs: []sdk.Msg{sendMsg4}, - accNums: []int64{1}, - accSeqs: []int64{0}, - expPass: true, - privKeys: []crypto.PrivKey{priv2}, + msgs: []sdk.Msg{sendMsg4}, + accNums: []int64{1}, + accSeqs: []int64{0}, + expSimPass: true, + expPass: true, + privKeys: []crypto.PrivKey{priv2}, expectedBalances: []expectedBalance{ {addr1, sdk.Coins{sdk.NewInt64Coin("foocoin", 42)}}, }, @@ -263,7 +270,7 @@ func TestMsgSendDependent(t *testing.T) { } for _, tc := range testCases { - mock.SignCheckDeliver(t, mapp.BaseApp, tc.msgs, tc.accNums, tc.accSeqs, tc.expPass, tc.privKeys...) + mock.SignCheckDeliver(t, mapp.BaseApp, tc.msgs, tc.accNums, tc.accSeqs, tc.expSimPass, tc.expPass, tc.privKeys...) for _, eb := range tc.expectedBalances { mock.CheckBalance(t, mapp, eb.addr, eb.coins) diff --git a/x/bank/client/rest/sendtx.go b/x/bank/client/rest/sendtx.go index c7baa96910..07b0b87862 100644 --- a/x/bank/client/rest/sendtx.go +++ b/x/bank/client/rest/sendtx.go @@ -40,6 +40,7 @@ func init() { } // SendRequestHandlerFn - http request handler to send coins to a address +// nolint: gocyclo func SendRequestHandlerFn(cdc *wire.Codec, kb keys.Keybase, cliCtx context.CLIContext) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { // collect data @@ -85,12 +86,16 @@ func SendRequestHandlerFn(cdc *wire.Codec, kb keys.Keybase, cliCtx context.CLICo Sequence: m.Sequence, } - if m.Gas == 0 { - newCtx, err := utils.EnrichCtxWithGas(txCtx, cliCtx, m.LocalAccountName, m.Password, []sdk.Msg{msg}) + if utils.HasDryRunArg(r) || m.Gas == 0 { + newCtx, err := utils.EnrichCtxWithGas(txCtx, cliCtx, m.LocalAccountName, []sdk.Msg{msg}) if err != nil { utils.WriteErrorResponse(&w, http.StatusInternalServerError, err.Error()) return } + if utils.HasDryRunArg(r) { + utils.WriteSimulationResponse(&w, txCtx.Gas) + return + } txCtx = newCtx } diff --git a/x/gov/client/rest/rest.go b/x/gov/client/rest/rest.go index 4e6a5c1b37..5241a39141 100644 --- a/x/gov/client/rest/rest.go +++ b/x/gov/client/rest/rest.go @@ -81,7 +81,7 @@ func postProposalHandlerFn(cdc *wire.Codec, cliCtx context.CLIContext) http.Hand return } - signAndBuild(w, cliCtx, req.BaseReq, msg, cdc) + signAndBuild(w, r, cliCtx, req.BaseReq, msg, cdc) } } @@ -118,7 +118,7 @@ func depositHandlerFn(cdc *wire.Codec, cliCtx context.CLIContext) http.HandlerFu return } - signAndBuild(w, cliCtx, req.BaseReq, msg, cdc) + signAndBuild(w, r, cliCtx, req.BaseReq, msg, cdc) } } @@ -155,7 +155,7 @@ func voteHandlerFn(cdc *wire.Codec, cliCtx context.CLIContext) http.HandlerFunc return } - signAndBuild(w, cliCtx, req.BaseReq, msg, cdc) + signAndBuild(w, r, cliCtx, req.BaseReq, msg, cdc) } } diff --git a/x/gov/client/rest/util.go b/x/gov/client/rest/util.go index f98f7bfa59..d664be5ad0 100644 --- a/x/gov/client/rest/util.go +++ b/x/gov/client/rest/util.go @@ -66,7 +66,7 @@ func (req baseReq) baseReqValidate(w http.ResponseWriter) bool { // TODO: Build this function out into a more generic base-request // (probably should live in client/lcd). -func signAndBuild(w http.ResponseWriter, cliCtx context.CLIContext, baseReq baseReq, msg sdk.Msg, cdc *wire.Codec) { +func signAndBuild(w http.ResponseWriter, r *http.Request, cliCtx context.CLIContext, baseReq baseReq, msg sdk.Msg, cdc *wire.Codec) { var err error txCtx := authctx.TxContext{ Codec: cdc, @@ -76,12 +76,16 @@ func signAndBuild(w http.ResponseWriter, cliCtx context.CLIContext, baseReq base Gas: baseReq.Gas, } - if baseReq.Gas == 0 { - newCtx, err := utils.EnrichCtxWithGas(txCtx, cliCtx, baseReq.Name, baseReq.Password, []sdk.Msg{msg}) + if utils.HasDryRunArg(r) || baseReq.Gas == 0 { + newCtx, err := utils.EnrichCtxWithGas(txCtx, cliCtx, baseReq.Name, []sdk.Msg{msg}) if err != nil { utils.WriteErrorResponse(&w, http.StatusInternalServerError, err.Error()) return } + if utils.HasDryRunArg(r) { + utils.WriteSimulationResponse(&w, txCtx.Gas) + return + } txCtx = newCtx } txBytes, err := txCtx.BuildAndSign(baseReq.Name, baseReq.Password, []sdk.Msg{msg}) diff --git a/x/ibc/app_test.go b/x/ibc/app_test.go index 5c3a0df78e..f761f861d6 100644 --- a/x/ibc/app_test.go +++ b/x/ibc/app_test.go @@ -70,10 +70,10 @@ func TestIBCMsgs(t *testing.T) { Sequence: 0, } - mock.SignCheckDeliver(t, mapp.BaseApp, []sdk.Msg{transferMsg}, []int64{0}, []int64{0}, true, priv1) + mock.SignCheckDeliver(t, mapp.BaseApp, []sdk.Msg{transferMsg}, []int64{0}, []int64{0}, true, true, priv1) mock.CheckBalance(t, mapp, addr1, emptyCoins) - mock.SignCheckDeliver(t, mapp.BaseApp, []sdk.Msg{transferMsg}, []int64{0}, []int64{1}, false, priv1) - mock.SignCheckDeliver(t, mapp.BaseApp, []sdk.Msg{receiveMsg}, []int64{0}, []int64{2}, true, priv1) + mock.SignCheckDeliver(t, mapp.BaseApp, []sdk.Msg{transferMsg}, []int64{0}, []int64{1}, false, false, priv1) + mock.SignCheckDeliver(t, mapp.BaseApp, []sdk.Msg{receiveMsg}, []int64{0}, []int64{2}, true, true, priv1) mock.CheckBalance(t, mapp, addr1, coins) - mock.SignCheckDeliver(t, mapp.BaseApp, []sdk.Msg{receiveMsg}, []int64{0}, []int64{2}, false, priv1) + mock.SignCheckDeliver(t, mapp.BaseApp, []sdk.Msg{receiveMsg}, []int64{0}, []int64{2}, false, false, priv1) } diff --git a/x/ibc/client/rest/transfer.go b/x/ibc/client/rest/transfer.go index 765208b056..ba0a845c29 100644 --- a/x/ibc/client/rest/transfer.go +++ b/x/ibc/client/rest/transfer.go @@ -33,6 +33,7 @@ type transferBody struct { // TransferRequestHandler - http request handler to transfer coins to a address // on a different chain via IBC +// nolint: gocyclo func TransferRequestHandlerFn(cdc *wire.Codec, kb keys.Keybase, cliCtx context.CLIContext) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { vars := mux.Vars(r) @@ -76,12 +77,16 @@ func TransferRequestHandlerFn(cdc *wire.Codec, kb keys.Keybase, cliCtx context.C Gas: m.Gas, } - if m.Gas == 0 { - newCtx, err := utils.EnrichCtxWithGas(txCtx, cliCtx, m.LocalAccountName, m.Password, []sdk.Msg{msg}) + if utils.HasDryRunArg(r) || m.Gas == 0 { + newCtx, err := utils.EnrichCtxWithGas(txCtx, cliCtx, m.LocalAccountName, []sdk.Msg{msg}) if err != nil { utils.WriteErrorResponse(&w, http.StatusInternalServerError, err.Error()) return } + if utils.HasDryRunArg(r) { + utils.WriteSimulationResponse(&w, txCtx.Gas) + return + } txCtx = newCtx } diff --git a/x/mock/app_test.go b/x/mock/app_test.go index 7477e12044..460757a043 100644 --- a/x/mock/app_test.go +++ b/x/mock/app_test.go @@ -61,14 +61,14 @@ func TestCheckAndDeliverGenTx(t *testing.T) { SignCheckDeliver( t, mApp.BaseApp, []sdk.Msg{msg}, []int64{accs[0].GetAccountNumber()}, []int64{accs[0].GetSequence()}, - true, privKeys[0], + true, true, privKeys[0], ) // Signing a tx with the wrong privKey should result in an auth error res := SignCheckDeliver( t, mApp.BaseApp, []sdk.Msg{msg}, []int64{accs[1].GetAccountNumber()}, []int64{accs[1].GetSequence() + 1}, - false, privKeys[1], + true, false, privKeys[1], ) require.Equal(t, sdk.ToABCICode(sdk.CodespaceRoot, sdk.CodeUnauthorized), res.Code, res.Log) @@ -76,7 +76,7 @@ func TestCheckAndDeliverGenTx(t *testing.T) { SignCheckDeliver( t, mApp.BaseApp, []sdk.Msg{msg}, []int64{accs[0].GetAccountNumber()}, []int64{accs[0].GetSequence() + 1}, - true, privKeys[0], + true, true, privKeys[0], ) } diff --git a/x/mock/test_utils.go b/x/mock/test_utils.go index c97f1c0c85..caaca6c9a5 100644 --- a/x/mock/test_utils.go +++ b/x/mock/test_utils.go @@ -71,13 +71,13 @@ func CheckGenTx( // returned. func SignCheckDeliver( t *testing.T, app *baseapp.BaseApp, msgs []sdk.Msg, accNums []int64, - seq []int64, expPass bool, priv ...crypto.PrivKey, + seq []int64, expSimPass, expPass bool, priv ...crypto.PrivKey, ) sdk.Result { tx := GenTx(msgs, accNums, seq, priv...) // Must simulate now as CheckTx doesn't run Msgs anymore res := app.Simulate(tx) - if expPass { + if expSimPass { require.Equal(t, sdk.ABCICodeOK, res.Code, res.Log) } else { require.NotEqual(t, sdk.ABCICodeOK, res.Code, res.Log) diff --git a/x/slashing/app_test.go b/x/slashing/app_test.go index f9ec0833fa..671c2241e8 100644 --- a/x/slashing/app_test.go +++ b/x/slashing/app_test.go @@ -102,7 +102,7 @@ func TestSlashingMsgs(t *testing.T) { createValidatorMsg := stake.NewMsgCreateValidator( sdk.ValAddress(addr1), priv1.PubKey(), bondCoin, description, ) - mock.SignCheckDeliver(t, mapp.BaseApp, []sdk.Msg{createValidatorMsg}, []int64{0}, []int64{0}, true, priv1) + 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)}) mapp.BeginBlock(abci.RequestBeginBlock{}) @@ -116,6 +116,6 @@ func TestSlashingMsgs(t *testing.T) { checkValidatorSigningInfo(t, mapp, keeper, sdk.ValAddress(addr1), false) // unjail should fail with unknown validator - res := mock.SignCheckDeliver(t, mapp.BaseApp, []sdk.Msg{unjailMsg}, []int64{0}, []int64{1}, false, priv1) + res := mock.SignCheckDeliver(t, mapp.BaseApp, []sdk.Msg{unjailMsg}, []int64{0}, []int64{1}, false, false, priv1) require.Equal(t, sdk.ToABCICode(DefaultCodespace, CodeValidatorNotJailed), res.Code) } diff --git a/x/slashing/client/rest/tx.go b/x/slashing/client/rest/tx.go index c7efdc97e9..3728558839 100644 --- a/x/slashing/client/rest/tx.go +++ b/x/slashing/client/rest/tx.go @@ -36,6 +36,7 @@ type UnjailBody struct { ValidatorAddr string `json:"validator_addr"` } +// nolint: gocyclo func unjailRequestHandlerFn(cdc *wire.Codec, kb keys.Keybase, cliCtx context.CLIContext) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { var m UnjailBody @@ -77,12 +78,16 @@ func unjailRequestHandlerFn(cdc *wire.Codec, kb keys.Keybase, cliCtx context.CLI msg := slashing.NewMsgUnjail(valAddr) - if m.Gas == 0 { - newCtx, err := utils.EnrichCtxWithGas(txCtx, cliCtx, m.LocalAccountName, m.Password, []sdk.Msg{msg}) + if utils.HasDryRunArg(r) || m.Gas == 0 { + newCtx, err := utils.EnrichCtxWithGas(txCtx, cliCtx, m.LocalAccountName, []sdk.Msg{msg}) if err != nil { utils.WriteErrorResponse(&w, http.StatusInternalServerError, err.Error()) return } + if utils.HasDryRunArg(r) { + utils.WriteSimulationResponse(&w, txCtx.Gas) + return + } txCtx = newCtx } diff --git a/x/stake/app_test.go b/x/stake/app_test.go index 587e90b465..bfeff5df76 100644 --- a/x/stake/app_test.go +++ b/x/stake/app_test.go @@ -131,7 +131,7 @@ func TestStakeMsgs(t *testing.T) { sdk.ValAddress(addr1), priv1.PubKey(), bondCoin, description, ) - mock.SignCheckDeliver(t, mApp.BaseApp, []sdk.Msg{createValidatorMsg}, []int64{0}, []int64{0}, true, priv1) + 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)}) mApp.BeginBlock(abci.RequestBeginBlock{}) @@ -145,7 +145,7 @@ func TestStakeMsgs(t *testing.T) { addr1, sdk.ValAddress(addr2), priv2.PubKey(), bondCoin, description, ) - mock.SignCheckDeliver(t, mApp.BaseApp, []sdk.Msg{createValidatorMsgOnBehalfOf}, []int64{0, 1}, []int64{1, 0}, true, priv1, priv2) + mock.SignCheckDeliver(t, mApp.BaseApp, []sdk.Msg{createValidatorMsgOnBehalfOf}, []int64{0, 1}, []int64{1, 0}, true, true, priv1, priv2) mock.CheckBalance(t, mApp, addr1, sdk.Coins{genCoin.Minus(bondCoin).Minus(bondCoin)}) mApp.BeginBlock(abci.RequestBeginBlock{}) @@ -161,7 +161,7 @@ func TestStakeMsgs(t *testing.T) { description = NewDescription("bar_moniker", "", "", "") editValidatorMsg := NewMsgEditValidator(sdk.ValAddress(addr1), description) - mock.SignCheckDeliver(t, mApp.BaseApp, []sdk.Msg{editValidatorMsg}, []int64{0}, []int64{2}, true, priv1) + mock.SignCheckDeliver(t, mApp.BaseApp, []sdk.Msg{editValidatorMsg}, []int64{0}, []int64{2}, true, true, priv1) validator = checkValidator(t, mApp, keeper, sdk.ValAddress(addr1), true) require.Equal(t, description, validator.Description) @@ -169,13 +169,13 @@ func TestStakeMsgs(t *testing.T) { mock.CheckBalance(t, mApp, addr2, sdk.Coins{genCoin}) delegateMsg := NewMsgDelegate(addr2, sdk.ValAddress(addr1), bondCoin) - mock.SignCheckDeliver(t, mApp.BaseApp, []sdk.Msg{delegateMsg}, []int64{1}, []int64{1}, true, priv2) + mock.SignCheckDeliver(t, mApp.BaseApp, []sdk.Msg{delegateMsg}, []int64{1}, []int64{1}, true, true, priv2) mock.CheckBalance(t, mApp, addr2, sdk.Coins{genCoin.Minus(bondCoin)}) checkDelegation(t, mApp, keeper, addr2, sdk.ValAddress(addr1), true, sdk.NewDec(10)) // begin unbonding beginUnbondingMsg := NewMsgBeginUnbonding(addr2, sdk.ValAddress(addr1), sdk.NewDec(10)) - mock.SignCheckDeliver(t, mApp.BaseApp, []sdk.Msg{beginUnbondingMsg}, []int64{1}, []int64{2}, true, priv2) + mock.SignCheckDeliver(t, mApp.BaseApp, []sdk.Msg{beginUnbondingMsg}, []int64{1}, []int64{2}, true, true, priv2) // delegation should exist anymore checkDelegation(t, mApp, keeper, addr2, sdk.ValAddress(addr1), false, sdk.Dec{}) diff --git a/x/stake/client/rest/tx.go b/x/stake/client/rest/tx.go index 24a8c38752..320205dd71 100644 --- a/x/stake/client/rest/tx.go +++ b/x/stake/client/rest/tx.go @@ -276,12 +276,16 @@ func delegationsRequestHandlerFn(cdc *wire.Codec, kb keys.Keybase, cliCtx contex m.Sequence++ - if m.Gas == 0 { - newCtx, err := utils.EnrichCtxWithGas(txCtx, cliCtx, m.LocalAccountName, m.Password, []sdk.Msg{msg}) + if utils.HasDryRunArg(r) || m.Gas == 0 { + newCtx, err := utils.EnrichCtxWithGas(txCtx, cliCtx, m.LocalAccountName, []sdk.Msg{msg}) if err != nil { utils.WriteErrorResponse(&w, http.StatusInternalServerError, err.Error()) return } + if utils.HasDryRunArg(r) { + utils.WriteSimulationResponse(&w, txCtx.Gas) + return + } txCtx = newCtx } From 1370ca611b6077f798d40487d490dcb07f1b58c7 Mon Sep 17 00:00:00 2001 From: Alessio Treglia Date: Fri, 31 Aug 2018 19:04:42 +0200 Subject: [PATCH 33/52] Set GasAdjustment in CLIContext when handling HTTP requests This is to address @alexanderbez's comments --- client/context/context.go | 4 ++++ client/flags.go | 5 ++++- client/lcd/lcd_test.go | 24 +++++++++++++++++------- client/utils/rest.go | 15 +++++++++++++++ client/utils/utils.go | 8 -------- x/bank/client/rest/sendtx.go | 8 ++++++++ x/gov/client/rest/util.go | 8 ++++++++ x/ibc/client/rest/transfer.go | 8 ++++++++ x/slashing/client/rest/tx.go | 8 ++++++++ x/stake/client/rest/tx.go | 8 ++++++++ 10 files changed, 80 insertions(+), 16 deletions(-) diff --git a/client/context/context.go b/client/context/context.go index 64d2375d09..2444c56d13 100644 --- a/client/context/context.go +++ b/client/context/context.go @@ -159,5 +159,9 @@ func (ctx CLIContext) WithUseLedger(useLedger bool) CLIContext { // WithCertifier - return a copy of the context with an updated Certifier func (ctx CLIContext) WithCertifier(certifier tmlite.Certifier) CLIContext { ctx.Certifier = certifier + +// WithGasAdjustment returns a copy of the context with an updated GasAdjustment flag. +func (ctx CLIContext) WithGasAdjustment(adjustment float64) CLIContext { + ctx.GasAdjustment = adjustment return ctx } diff --git a/client/flags.go b/client/flags.go index 3bb4d7bc33..a1d3c6e178 100644 --- a/client/flags.go +++ b/client/flags.go @@ -4,8 +4,11 @@ import "github.com/spf13/cobra" // nolint const ( - DefaultGasLimit = 200000 + // DefaultGasAdjustment is applied to gas estimates to avoid tx + // execution failures due to state changes that might + // occur between the tx simulation and the actual run. DefaultGasAdjustment = 1.0 + DefaultGasLimit = 200000 FlagUseLedger = "ledger" FlagChainID = "chain-id" diff --git a/client/lcd/lcd_test.go b/client/lcd/lcd_test.go index 5a0694976b..707cc21f7a 100644 --- a/client/lcd/lcd_test.go +++ b/client/lcd/lcd_test.go @@ -266,17 +266,21 @@ func TestCoinSend(t *testing.T) { require.Equal(t, int64(1), mycoins.Amount.Int64()) // test failure with too little gas - res, body, _ = doSendWithGas(t, port, seed, name, password, addr, 100, "") + res, body, _ = doSendWithGas(t, port, seed, name, password, addr, 100, 0, "") + require.Equal(t, http.StatusInternalServerError, res.StatusCode, body) + + // test failure with wrong adjustment + res, body, _ = doSendWithGas(t, port, seed, name, password, addr, 0, 0.1, "") require.Equal(t, http.StatusInternalServerError, res.StatusCode, body) // run simulation and test success with estimated gas - res, body, _ = doSendWithGas(t, port, seed, name, password, addr, 0, "?simulate=true") + res, body, _ = doSendWithGas(t, port, seed, name, password, addr, 0, 0, "?simulate=true") require.Equal(t, http.StatusOK, res.StatusCode, body) var responseBody struct { GasEstimate int64 `json:"gas_estimate"` } require.Nil(t, json.Unmarshal([]byte(body), &responseBody)) - res, body, _ = doSendWithGas(t, port, seed, name, password, addr, responseBody.GasEstimate, "") + res, body, _ = doSendWithGas(t, port, seed, name, password, addr, responseBody.GasEstimate, 0, "") require.Equal(t, http.StatusOK, res.StatusCode, body) } @@ -727,7 +731,7 @@ func getAccount(t *testing.T, port string, addr sdk.AccAddress) auth.Account { return acc } -func doSendWithGas(t *testing.T, port, seed, name, password string, addr sdk.AccAddress, gas int64, queryStr string) (res *http.Response, body string, receiveAddr sdk.AccAddress) { +func doSendWithGas(t *testing.T, port, seed, name, password string, addr sdk.AccAddress, gas int64, gasAdjustment float64, queryStr string) (res *http.Response, body string, receiveAddr sdk.AccAddress) { // create receive address kb := client.MockKeyBase() @@ -751,22 +755,28 @@ func doSendWithGas(t *testing.T, port, seed, name, password string, addr sdk.Acc "gas":"%v", `, gas) } + gasAdjustmentStr := "" + if gasAdjustment > 0 { + gasStr = fmt.Sprintf(` + "gas_adjustment":"%v", + `, gasAdjustment) + } jsonStr := []byte(fmt.Sprintf(`{ - %v + %v%v "name":"%s", "password":"%s", "account_number":"%d", "sequence":"%d", "amount":[%s], "chain_id":"%s" - }`, gasStr, name, password, accnum, sequence, coinbz, chainID)) + }`, gasStr, gasAdjustmentStr, name, password, accnum, sequence, coinbz, chainID)) res, body = Request(t, port, "POST", fmt.Sprintf("/accounts/%s/send%v", receiveAddr, queryStr), jsonStr) return } func doSend(t *testing.T, port, seed, name, password string, addr sdk.AccAddress) (receiveAddr sdk.AccAddress, resultTx ctypes.ResultBroadcastTxCommit) { - res, body, receiveAddr := doSendWithGas(t, port, seed, name, password, addr, 0, "") + res, body, receiveAddr := doSendWithGas(t, port, seed, name, password, addr, 0, 0, "") require.Equal(t, http.StatusOK, res.StatusCode, body) err := cdc.UnmarshalJSON([]byte(body), &resultTx) diff --git a/client/utils/rest.go b/client/utils/rest.go index 4bbc10b328..b369a81274 100644 --- a/client/utils/rest.go +++ b/client/utils/rest.go @@ -3,6 +3,7 @@ package utils import ( "fmt" "net/http" + "strconv" ) const ( @@ -28,3 +29,17 @@ func WriteSimulationResponse(w *http.ResponseWriter, gas int64) { func HasDryRunArg(r *http.Request) bool { return r.URL.Query().Get(queryArgDryRun) == "true" } + +// ParseFloat64OrReturnBadRequest converts s to a float64 value. It returns a default +// value if the string is empty. Write +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 +} diff --git a/client/utils/utils.go b/client/utils/utils.go index acf90efaa0..20b1c6bc54 100644 --- a/client/utils/utils.go +++ b/client/utils/utils.go @@ -12,11 +12,6 @@ import ( "github.com/tendermint/tendermint/libs/common" ) -// DefaultGasAdjustment is applied to gas estimates to avoid tx -// execution failures due to state changes that might -// occur between the tx simulation and the actual run. -const DefaultGasAdjustment = 1.0 - // SendTx implements a auxiliary handler that facilitates sending a series of // messages in a signed transaction given a TxContext and a QueryContext. It // ensures that the account exists, has a proper number and sequence set. In @@ -91,9 +86,6 @@ func CalculateGas(queryFunc func(string, common.HexBytes) ([]byte, error), cdc * } func adjustGasEstimate(estimate int64, adjustment float64) int64 { - if adjustment == 0 { - adjustment = DefaultGasAdjustment - } return int64(adjustment * float64(estimate)) } diff --git a/x/bank/client/rest/sendtx.go b/x/bank/client/rest/sendtx.go index 07b0b87862..41635d28a8 100644 --- a/x/bank/client/rest/sendtx.go +++ b/x/bank/client/rest/sendtx.go @@ -4,6 +4,7 @@ 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/crypto/keys" @@ -31,6 +32,7 @@ type sendBody struct { AccountNumber int64 `json:"account_number"` Sequence int64 `json:"sequence"` Gas int64 `json:"gas"` + GasAdjustment string `json:"gas_adjustment"` } var msgCdc = wire.NewCodec() @@ -86,6 +88,12 @@ func SendRequestHandlerFn(cdc *wire.Codec, kb keys.Keybase, cliCtx context.CLICo Sequence: m.Sequence, } + adjustment, ok := utils.ParseFloat64OrReturnBadRequest(&w, m.GasAdjustment, cliclient.DefaultGasAdjustment) + if !ok { + return + } + cliCtx = cliCtx.WithGasAdjustment(adjustment) + if utils.HasDryRunArg(r) || m.Gas == 0 { newCtx, err := utils.EnrichCtxWithGas(txCtx, cliCtx, m.LocalAccountName, []sdk.Msg{msg}) if err != nil { diff --git a/x/gov/client/rest/util.go b/x/gov/client/rest/util.go index d664be5ad0..f563b4ec20 100644 --- a/x/gov/client/rest/util.go +++ b/x/gov/client/rest/util.go @@ -6,6 +6,7 @@ import ( "net/http" "strconv" + "github.com/cosmos/cosmos-sdk/client" "github.com/cosmos/cosmos-sdk/client/context" "github.com/cosmos/cosmos-sdk/client/utils" sdk "github.com/cosmos/cosmos-sdk/types" @@ -20,6 +21,7 @@ type baseReq struct { AccountNumber int64 `json:"account_number"` Sequence int64 `json:"sequence"` Gas int64 `json:"gas"` + GasAdjustment string `json:"gas_adjustment"` } func buildReq(w http.ResponseWriter, r *http.Request, cdc *wire.Codec, req interface{}) error { @@ -76,6 +78,12 @@ func signAndBuild(w http.ResponseWriter, r *http.Request, cliCtx context.CLICont Gas: baseReq.Gas, } + adjustment, ok := utils.ParseFloat64OrReturnBadRequest(&w, baseReq.GasAdjustment, client.DefaultGasAdjustment) + if !ok { + return + } + cliCtx = cliCtx.WithGasAdjustment(adjustment) + if utils.HasDryRunArg(r) || baseReq.Gas == 0 { newCtx, err := utils.EnrichCtxWithGas(txCtx, cliCtx, baseReq.Name, []sdk.Msg{msg}) if err != nil { diff --git a/x/ibc/client/rest/transfer.go b/x/ibc/client/rest/transfer.go index ba0a845c29..111f9086d0 100644 --- a/x/ibc/client/rest/transfer.go +++ b/x/ibc/client/rest/transfer.go @@ -4,6 +4,7 @@ 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/crypto/keys" @@ -29,6 +30,7 @@ type transferBody struct { AccountNumber int64 `json:"account_number"` Sequence int64 `json:"sequence"` Gas int64 `json:"gas"` + GasAdjustment string `json:"gas_adjustment"` } // TransferRequestHandler - http request handler to transfer coins to a address @@ -77,6 +79,12 @@ func TransferRequestHandlerFn(cdc *wire.Codec, kb keys.Keybase, cliCtx context.C Gas: m.Gas, } + adjustment, ok := utils.ParseFloat64OrReturnBadRequest(&w, m.GasAdjustment, client.DefaultGasAdjustment) + if !ok { + return + } + cliCtx = cliCtx.WithGasAdjustment(adjustment) + if utils.HasDryRunArg(r) || m.Gas == 0 { newCtx, err := utils.EnrichCtxWithGas(txCtx, cliCtx, m.LocalAccountName, []sdk.Msg{msg}) if err != nil { diff --git a/x/slashing/client/rest/tx.go b/x/slashing/client/rest/tx.go index 3728558839..322a4aa083 100644 --- a/x/slashing/client/rest/tx.go +++ b/x/slashing/client/rest/tx.go @@ -7,6 +7,7 @@ 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/crypto/keys" @@ -33,6 +34,7 @@ type UnjailBody struct { AccountNumber int64 `json:"account_number"` Sequence int64 `json:"sequence"` Gas int64 `json:"gas"` + GasAdjustment string `json:"gas_adjustment"` ValidatorAddr string `json:"validator_addr"` } @@ -78,6 +80,12 @@ func unjailRequestHandlerFn(cdc *wire.Codec, kb keys.Keybase, cliCtx context.CLI msg := slashing.NewMsgUnjail(valAddr) + adjustment, ok := utils.ParseFloat64OrReturnBadRequest(&w, m.GasAdjustment, client.DefaultGasAdjustment) + if !ok { + return + } + cliCtx = cliCtx.WithGasAdjustment(adjustment) + if utils.HasDryRunArg(r) || m.Gas == 0 { newCtx, err := utils.EnrichCtxWithGas(txCtx, cliCtx, m.LocalAccountName, []sdk.Msg{msg}) if err != nil { diff --git a/x/stake/client/rest/tx.go b/x/stake/client/rest/tx.go index 320205dd71..5e6226dcc7 100644 --- a/x/stake/client/rest/tx.go +++ b/x/stake/client/rest/tx.go @@ -6,6 +6,7 @@ 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/crypto/keys" @@ -60,6 +61,7 @@ type EditDelegationsBody struct { AccountNumber int64 `json:"account_number"` Sequence int64 `json:"sequence"` Gas int64 `json:"gas"` + GasAdjustment string `json:"gas_adjustment"` Delegations []msgDelegationsInput `json:"delegations"` BeginUnbondings []msgBeginUnbondingInput `json:"begin_unbondings"` CompleteUnbondings []msgCompleteUnbondingInput `json:"complete_unbondings"` @@ -276,6 +278,12 @@ func delegationsRequestHandlerFn(cdc *wire.Codec, kb keys.Keybase, cliCtx contex m.Sequence++ + adjustment, ok := utils.ParseFloat64OrReturnBadRequest(&w, m.GasAdjustment, client.DefaultGasAdjustment) + if !ok { + return + } + cliCtx = cliCtx.WithGasAdjustment(adjustment) + if utils.HasDryRunArg(r) || m.Gas == 0 { newCtx, err := utils.EnrichCtxWithGas(txCtx, cliCtx, m.LocalAccountName, []sdk.Msg{msg}) if err != nil { From 122ed3a900d41dcb7200bbf28e528bc3b35153f7 Mon Sep 17 00:00:00 2001 From: Alessio Treglia Date: Fri, 31 Aug 2018 17:10:09 +0200 Subject: [PATCH 34/52] Don't use pointers when you need them not! --- client/utils/rest.go | 14 ++++---- x/auth/client/rest/query.go | 8 ++--- x/bank/client/rest/sendtx.go | 22 ++++++------- x/gov/client/rest/rest.go | 62 +++++++++++++++++------------------ x/gov/client/rest/util.go | 26 +++++++-------- x/ibc/client/rest/transfer.go | 20 +++++------ x/slashing/client/rest/tx.go | 22 ++++++------- x/stake/client/rest/tx.go | 50 ++++++++++++++-------------- 8 files changed, 112 insertions(+), 112 deletions(-) diff --git a/client/utils/rest.go b/client/utils/rest.go index b369a81274..25e1c23d85 100644 --- a/client/utils/rest.go +++ b/client/utils/rest.go @@ -12,16 +12,16 @@ const ( // WriteErrorResponse prepares and writes a HTTP error // given a status code and an error message. -func WriteErrorResponse(w *http.ResponseWriter, status int, msg string) { - (*w).WriteHeader(status) - (*w).Write([]byte(msg)) +func WriteErrorResponse(w http.ResponseWriter, status int, msg string) { + w.WriteHeader(status) + w.Write([]byte(msg)) } // WriteGasEstimateResponse prepares and writes an HTTP // response for transactions simulations. -func WriteSimulationResponse(w *http.ResponseWriter, gas int64) { - (*w).WriteHeader(http.StatusOK) - (*w).Write([]byte(fmt.Sprintf(`{"gas_estimate":%v}`, gas))) +func WriteSimulationResponse(w http.ResponseWriter, gas int64) { + w.WriteHeader(http.StatusOK) + w.Write([]byte(fmt.Sprintf(`{"gas_estimate":%v}`, gas))) } // HasDryRunArg returns true if the request's URL query contains @@ -32,7 +32,7 @@ func HasDryRunArg(r *http.Request) bool { // ParseFloat64OrReturnBadRequest converts s to a float64 value. It returns a default // value if the string is empty. Write -func ParseFloat64OrReturnBadRequest(w *http.ResponseWriter, s string, defaultIfEmpty float64) (n float64, ok bool) { +func ParseFloat64OrReturnBadRequest(w http.ResponseWriter, s string, defaultIfEmpty float64) (n float64, ok bool) { if len(s) == 0 { return defaultIfEmpty, true } diff --git a/x/auth/client/rest/query.go b/x/auth/client/rest/query.go index 07b109d403..6ad50a14dd 100644 --- a/x/auth/client/rest/query.go +++ b/x/auth/client/rest/query.go @@ -33,13 +33,13 @@ func QueryAccountRequestHandlerFn( addr, err := sdk.AccAddressFromBech32(bech32addr) if err != nil { - utils.WriteErrorResponse(&w, http.StatusInternalServerError, err.Error()) + utils.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) return } res, err := cliCtx.QueryStore(auth.AddressStoreKey(addr), storeName) if err != nil { - utils.WriteErrorResponse(&w, http.StatusInternalServerError, fmt.Sprintf("couldn't query account. Error: %s", err.Error())) + utils.WriteErrorResponse(w, http.StatusInternalServerError, fmt.Sprintf("couldn't query account. Error: %s", err.Error())) return } @@ -52,14 +52,14 @@ func QueryAccountRequestHandlerFn( // decode the value account, err := decoder(res) if err != nil { - utils.WriteErrorResponse(&w, http.StatusInternalServerError, fmt.Sprintf("couldn't parse query result. Result: %s. Error: %s", res, err.Error())) + utils.WriteErrorResponse(w, http.StatusInternalServerError, fmt.Sprintf("couldn't parse query result. Result: %s. Error: %s", res, err.Error())) return } // print out whole account output, err := cdc.MarshalJSON(account) if err != nil { - utils.WriteErrorResponse(&w, http.StatusInternalServerError, fmt.Sprintf("couldn't marshall query result. Error: %s", err.Error())) + utils.WriteErrorResponse(w, http.StatusInternalServerError, fmt.Sprintf("couldn't marshall query result. Error: %s", err.Error())) return } diff --git a/x/bank/client/rest/sendtx.go b/x/bank/client/rest/sendtx.go index 41635d28a8..23506e0fec 100644 --- a/x/bank/client/rest/sendtx.go +++ b/x/bank/client/rest/sendtx.go @@ -51,32 +51,32 @@ func SendRequestHandlerFn(cdc *wire.Codec, kb keys.Keybase, cliCtx context.CLICo to, err := sdk.AccAddressFromBech32(bech32addr) if err != nil { - utils.WriteErrorResponse(&w, http.StatusBadRequest, err.Error()) + utils.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) return } var m sendBody body, err := ioutil.ReadAll(r.Body) if err != nil { - utils.WriteErrorResponse(&w, http.StatusBadRequest, err.Error()) + utils.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) return } err = msgCdc.UnmarshalJSON(body, &m) if err != nil { - utils.WriteErrorResponse(&w, http.StatusBadRequest, err.Error()) + utils.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) return } info, err := kb.Get(m.LocalAccountName) if err != nil { - utils.WriteErrorResponse(&w, http.StatusUnauthorized, err.Error()) + 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()) + utils.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) return } @@ -88,7 +88,7 @@ func SendRequestHandlerFn(cdc *wire.Codec, kb keys.Keybase, cliCtx context.CLICo Sequence: m.Sequence, } - adjustment, ok := utils.ParseFloat64OrReturnBadRequest(&w, m.GasAdjustment, cliclient.DefaultGasAdjustment) + adjustment, ok := utils.ParseFloat64OrReturnBadRequest(w, m.GasAdjustment, cliclient.DefaultGasAdjustment) if !ok { return } @@ -97,11 +97,11 @@ func SendRequestHandlerFn(cdc *wire.Codec, kb keys.Keybase, cliCtx context.CLICo if utils.HasDryRunArg(r) || m.Gas == 0 { newCtx, err := utils.EnrichCtxWithGas(txCtx, cliCtx, m.LocalAccountName, []sdk.Msg{msg}) if err != nil { - utils.WriteErrorResponse(&w, http.StatusInternalServerError, err.Error()) + utils.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) return } if utils.HasDryRunArg(r) { - utils.WriteSimulationResponse(&w, txCtx.Gas) + utils.WriteSimulationResponse(w, txCtx.Gas) return } txCtx = newCtx @@ -109,19 +109,19 @@ func SendRequestHandlerFn(cdc *wire.Codec, kb keys.Keybase, cliCtx context.CLICo txBytes, err := txCtx.BuildAndSign(m.LocalAccountName, m.Password, []sdk.Msg{msg}) if err != nil { - utils.WriteErrorResponse(&w, http.StatusUnauthorized, err.Error()) + utils.WriteErrorResponse(w, http.StatusUnauthorized, err.Error()) return } res, err := cliCtx.BroadcastTx(txBytes) if err != nil { - utils.WriteErrorResponse(&w, http.StatusInternalServerError, err.Error()) + utils.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) return } output, err := wire.MarshalJSONIndent(cdc, res) if err != nil { - utils.WriteErrorResponse(&w, http.StatusInternalServerError, err.Error()) + utils.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) return } diff --git a/x/gov/client/rest/rest.go b/x/gov/client/rest/rest.go index 5241a39141..8bf675b025 100644 --- a/x/gov/client/rest/rest.go +++ b/x/gov/client/rest/rest.go @@ -77,7 +77,7 @@ func postProposalHandlerFn(cdc *wire.Codec, cliCtx context.CLIContext) http.Hand msg := gov.NewMsgSubmitProposal(req.Title, req.Description, req.ProposalType, req.Proposer, req.InitialDeposit) err = msg.ValidateBasic() if err != nil { - utils.WriteErrorResponse(&w, http.StatusBadRequest, err.Error()) + utils.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) return } @@ -92,7 +92,7 @@ func depositHandlerFn(cdc *wire.Codec, cliCtx context.CLIContext) http.HandlerFu if len(strProposalID) == 0 { err := errors.New("proposalId required but not specified") - utils.WriteErrorResponse(&w, http.StatusBadRequest, err.Error()) + utils.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) return } @@ -114,7 +114,7 @@ func depositHandlerFn(cdc *wire.Codec, cliCtx context.CLIContext) http.HandlerFu msg := gov.NewMsgDeposit(req.Depositer, proposalID, req.Amount) err = msg.ValidateBasic() if err != nil { - utils.WriteErrorResponse(&w, http.StatusBadRequest, err.Error()) + utils.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) return } @@ -129,7 +129,7 @@ func voteHandlerFn(cdc *wire.Codec, cliCtx context.CLIContext) http.HandlerFunc if len(strProposalID) == 0 { err := errors.New("proposalId required but not specified") - utils.WriteErrorResponse(&w, http.StatusBadRequest, err.Error()) + utils.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) return } @@ -151,7 +151,7 @@ func voteHandlerFn(cdc *wire.Codec, cliCtx context.CLIContext) http.HandlerFunc msg := gov.NewMsgVote(req.Voter, proposalID, req.Option) err = msg.ValidateBasic() if err != nil { - utils.WriteErrorResponse(&w, http.StatusBadRequest, err.Error()) + utils.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) return } @@ -166,7 +166,7 @@ func queryProposalHandlerFn(cdc *wire.Codec) http.HandlerFunc { if len(strProposalID) == 0 { err := errors.New("proposalId required but not specified") - utils.WriteErrorResponse(&w, http.StatusBadRequest, err.Error()) + utils.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) return } @@ -183,13 +183,13 @@ func queryProposalHandlerFn(cdc *wire.Codec) http.HandlerFunc { bz, err := cdc.MarshalJSON(params) if err != nil { - utils.WriteErrorResponse(&w, http.StatusBadRequest, err.Error()) + utils.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) return } res, err := cliCtx.QueryWithData("custom/gov/proposal", bz) if err != nil { - utils.WriteErrorResponse(&w, http.StatusInternalServerError, err.Error()) + utils.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) return } @@ -205,7 +205,7 @@ func queryDepositHandlerFn(cdc *wire.Codec) http.HandlerFunc { if len(strProposalID) == 0 { err := errors.New("proposalId required but not specified") - utils.WriteErrorResponse(&w, http.StatusBadRequest, err.Error()) + utils.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) return } @@ -216,14 +216,14 @@ func queryDepositHandlerFn(cdc *wire.Codec) http.HandlerFunc { if len(bechDepositerAddr) == 0 { err := errors.New("depositer address required but not specified") - utils.WriteErrorResponse(&w, http.StatusBadRequest, err.Error()) + utils.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) return } depositerAddr, err := sdk.AccAddressFromBech32(bechDepositerAddr) if err != nil { err := errors.Errorf("'%s' needs to be bech32 encoded", RestDepositer) - utils.WriteErrorResponse(&w, http.StatusBadRequest, err.Error()) + utils.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) return } @@ -236,13 +236,13 @@ func queryDepositHandlerFn(cdc *wire.Codec) http.HandlerFunc { bz, err := cdc.MarshalJSON(params) if err != nil { - utils.WriteErrorResponse(&w, http.StatusBadRequest, err.Error()) + utils.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) return } res, err := cliCtx.QueryWithData("custom/gov/deposit", bz) if err != nil { - utils.WriteErrorResponse(&w, http.StatusInternalServerError, err.Error()) + utils.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) return } @@ -252,11 +252,11 @@ func queryDepositHandlerFn(cdc *wire.Codec) http.HandlerFunc { res, err := cliCtx.QueryWithData("custom/gov/proposal", cdc.MustMarshalBinary(gov.QueryProposalParams{params.ProposalID})) if err != nil || len(res) == 0 { err := errors.Errorf("proposalID [%d] does not exist", proposalID) - utils.WriteErrorResponse(&w, http.StatusNotFound, err.Error()) + utils.WriteErrorResponse(w, http.StatusNotFound, err.Error()) return } err = errors.Errorf("depositer [%s] did not deposit on proposalID [%d]", bechDepositerAddr, proposalID) - utils.WriteErrorResponse(&w, http.StatusNotFound, err.Error()) + utils.WriteErrorResponse(w, http.StatusNotFound, err.Error()) return } @@ -272,7 +272,7 @@ func queryVoteHandlerFn(cdc *wire.Codec) http.HandlerFunc { if len(strProposalID) == 0 { err := errors.New("proposalId required but not specified") - utils.WriteErrorResponse(&w, http.StatusBadRequest, err.Error()) + utils.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) return } @@ -283,14 +283,14 @@ func queryVoteHandlerFn(cdc *wire.Codec) http.HandlerFunc { if len(bechVoterAddr) == 0 { err := errors.New("voter address required but not specified") - utils.WriteErrorResponse(&w, http.StatusBadRequest, err.Error()) + utils.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) return } voterAddr, err := sdk.AccAddressFromBech32(bechVoterAddr) if err != nil { err := errors.Errorf("'%s' needs to be bech32 encoded", RestVoter) - utils.WriteErrorResponse(&w, http.StatusBadRequest, err.Error()) + utils.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) return } @@ -302,13 +302,13 @@ func queryVoteHandlerFn(cdc *wire.Codec) http.HandlerFunc { } bz, err := cdc.MarshalJSON(params) if err != nil { - utils.WriteErrorResponse(&w, http.StatusBadRequest, err.Error()) + utils.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) return } res, err := cliCtx.QueryWithData("custom/gov/vote", bz) if err != nil { - utils.WriteErrorResponse(&w, http.StatusInternalServerError, err.Error()) + utils.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) return } @@ -317,17 +317,17 @@ func queryVoteHandlerFn(cdc *wire.Codec) http.HandlerFunc { if vote.Empty() { bz, err := cdc.MarshalJSON(gov.QueryProposalParams{params.ProposalID}) if err != nil { - utils.WriteErrorResponse(&w, http.StatusBadRequest, err.Error()) + utils.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) return } res, err := cliCtx.QueryWithData("custom/gov/proposal", bz) if err != nil || len(res) == 0 { err := errors.Errorf("proposalID [%d] does not exist", proposalID) - utils.WriteErrorResponse(&w, http.StatusNotFound, err.Error()) + utils.WriteErrorResponse(w, http.StatusNotFound, err.Error()) return } err = errors.Errorf("voter [%s] did not deposit on proposalID [%d]", bechVoterAddr, proposalID) - utils.WriteErrorResponse(&w, http.StatusNotFound, err.Error()) + utils.WriteErrorResponse(w, http.StatusNotFound, err.Error()) return } w.Write(res) @@ -343,7 +343,7 @@ func queryVotesOnProposalHandlerFn(cdc *wire.Codec) http.HandlerFunc { if len(strProposalID) == 0 { err := errors.New("proposalId required but not specified") - utils.WriteErrorResponse(&w, http.StatusBadRequest, err.Error()) + utils.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) return } @@ -359,13 +359,13 @@ func queryVotesOnProposalHandlerFn(cdc *wire.Codec) http.HandlerFunc { } bz, err := cdc.MarshalJSON(params) if err != nil { - utils.WriteErrorResponse(&w, http.StatusBadRequest, err.Error()) + utils.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) return } res, err := cliCtx.QueryWithData("custom/gov/votes", bz) if err != nil { - utils.WriteErrorResponse(&w, http.StatusInternalServerError, err.Error()) + utils.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) return } @@ -388,7 +388,7 @@ func queryProposalsWithParameterFn(cdc *wire.Codec) http.HandlerFunc { voterAddr, err := sdk.AccAddressFromBech32(bechVoterAddr) if err != nil { err := errors.Errorf("'%s' needs to be bech32 encoded", RestVoter) - utils.WriteErrorResponse(&w, http.StatusBadRequest, err.Error()) + utils.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) return } params.Voter = voterAddr @@ -398,7 +398,7 @@ func queryProposalsWithParameterFn(cdc *wire.Codec) http.HandlerFunc { depositerAddr, err := sdk.AccAddressFromBech32(bechDepositerAddr) if err != nil { err := errors.Errorf("'%s' needs to be bech32 encoded", RestDepositer) - utils.WriteErrorResponse(&w, http.StatusBadRequest, err.Error()) + utils.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) return } params.Depositer = depositerAddr @@ -408,7 +408,7 @@ func queryProposalsWithParameterFn(cdc *wire.Codec) http.HandlerFunc { proposalStatus, err := gov.ProposalStatusFromString(strProposalStatus) if err != nil { err := errors.Errorf("'%s' is not a valid Proposal Status", strProposalStatus) - utils.WriteErrorResponse(&w, http.StatusBadRequest, err.Error()) + utils.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) return } params.ProposalStatus = proposalStatus @@ -423,7 +423,7 @@ func queryProposalsWithParameterFn(cdc *wire.Codec) http.HandlerFunc { bz, err := cdc.MarshalJSON(params) if err != nil { - utils.WriteErrorResponse(&w, http.StatusBadRequest, err.Error()) + utils.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) return } @@ -431,7 +431,7 @@ func queryProposalsWithParameterFn(cdc *wire.Codec) http.HandlerFunc { res, err := cliCtx.QueryWithData("custom/gov/proposals", bz) if err != nil { - utils.WriteErrorResponse(&w, http.StatusInternalServerError, err.Error()) + utils.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) return } diff --git a/x/gov/client/rest/util.go b/x/gov/client/rest/util.go index f563b4ec20..a5fae3c3fa 100644 --- a/x/gov/client/rest/util.go +++ b/x/gov/client/rest/util.go @@ -27,12 +27,12 @@ type baseReq struct { func buildReq(w http.ResponseWriter, r *http.Request, cdc *wire.Codec, req interface{}) error { body, err := ioutil.ReadAll(r.Body) if err != nil { - utils.WriteErrorResponse(&w, http.StatusBadRequest, err.Error()) + utils.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) return err } err = cdc.UnmarshalJSON(body, req) if err != nil { - utils.WriteErrorResponse(&w, http.StatusBadRequest, err.Error()) + utils.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) return err } return nil @@ -40,27 +40,27 @@ func buildReq(w http.ResponseWriter, r *http.Request, cdc *wire.Codec, req inter func (req baseReq) baseReqValidate(w http.ResponseWriter) bool { if len(req.Name) == 0 { - utils.WriteErrorResponse(&w, http.StatusUnauthorized, "Name required but not specified") + 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") + 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") + 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") + 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") + utils.WriteErrorResponse(w, http.StatusUnauthorized, "Sequence required but not specified") return false } return true @@ -78,7 +78,7 @@ func signAndBuild(w http.ResponseWriter, r *http.Request, cliCtx context.CLICont Gas: baseReq.Gas, } - adjustment, ok := utils.ParseFloat64OrReturnBadRequest(&w, baseReq.GasAdjustment, client.DefaultGasAdjustment) + adjustment, ok := utils.ParseFloat64OrReturnBadRequest(w, baseReq.GasAdjustment, client.DefaultGasAdjustment) if !ok { return } @@ -87,30 +87,30 @@ func signAndBuild(w http.ResponseWriter, r *http.Request, cliCtx context.CLICont if utils.HasDryRunArg(r) || baseReq.Gas == 0 { newCtx, err := utils.EnrichCtxWithGas(txCtx, cliCtx, baseReq.Name, []sdk.Msg{msg}) if err != nil { - utils.WriteErrorResponse(&w, http.StatusInternalServerError, err.Error()) + utils.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) return } if utils.HasDryRunArg(r) { - utils.WriteSimulationResponse(&w, txCtx.Gas) + utils.WriteSimulationResponse(w, txCtx.Gas) return } txCtx = newCtx } txBytes, err := txCtx.BuildAndSign(baseReq.Name, baseReq.Password, []sdk.Msg{msg}) if err != nil { - utils.WriteErrorResponse(&w, http.StatusUnauthorized, err.Error()) + utils.WriteErrorResponse(w, http.StatusUnauthorized, err.Error()) return } res, err := cliCtx.BroadcastTx(txBytes) if err != nil { - utils.WriteErrorResponse(&w, http.StatusInternalServerError, err.Error()) + utils.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) return } output, err := wire.MarshalJSONIndent(cdc, res) if err != nil { - utils.WriteErrorResponse(&w, http.StatusInternalServerError, err.Error()) + utils.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) return } diff --git a/x/ibc/client/rest/transfer.go b/x/ibc/client/rest/transfer.go index 111f9086d0..0580ff4062 100644 --- a/x/ibc/client/rest/transfer.go +++ b/x/ibc/client/rest/transfer.go @@ -44,26 +44,26 @@ func TransferRequestHandlerFn(cdc *wire.Codec, kb keys.Keybase, cliCtx context.C to, err := sdk.AccAddressFromBech32(bech32addr) if err != nil { - utils.WriteErrorResponse(&w, http.StatusBadRequest, err.Error()) + utils.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) return } var m transferBody body, err := ioutil.ReadAll(r.Body) if err != nil { - utils.WriteErrorResponse(&w, http.StatusBadRequest, err.Error()) + utils.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) return } err = cdc.UnmarshalJSON(body, &m) if err != nil { - utils.WriteErrorResponse(&w, http.StatusBadRequest, err.Error()) + utils.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) return } info, err := kb.Get(m.LocalAccountName) if err != nil { - utils.WriteErrorResponse(&w, http.StatusUnauthorized, err.Error()) + utils.WriteErrorResponse(w, http.StatusUnauthorized, err.Error()) return } @@ -79,7 +79,7 @@ func TransferRequestHandlerFn(cdc *wire.Codec, kb keys.Keybase, cliCtx context.C Gas: m.Gas, } - adjustment, ok := utils.ParseFloat64OrReturnBadRequest(&w, m.GasAdjustment, client.DefaultGasAdjustment) + adjustment, ok := utils.ParseFloat64OrReturnBadRequest(w, m.GasAdjustment, client.DefaultGasAdjustment) if !ok { return } @@ -88,11 +88,11 @@ func TransferRequestHandlerFn(cdc *wire.Codec, kb keys.Keybase, cliCtx context.C if utils.HasDryRunArg(r) || m.Gas == 0 { newCtx, err := utils.EnrichCtxWithGas(txCtx, cliCtx, m.LocalAccountName, []sdk.Msg{msg}) if err != nil { - utils.WriteErrorResponse(&w, http.StatusInternalServerError, err.Error()) + utils.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) return } if utils.HasDryRunArg(r) { - utils.WriteSimulationResponse(&w, txCtx.Gas) + utils.WriteSimulationResponse(w, txCtx.Gas) return } txCtx = newCtx @@ -100,19 +100,19 @@ func TransferRequestHandlerFn(cdc *wire.Codec, kb keys.Keybase, cliCtx context.C txBytes, err := txCtx.BuildAndSign(m.LocalAccountName, m.Password, []sdk.Msg{msg}) if err != nil { - utils.WriteErrorResponse(&w, http.StatusUnauthorized, err.Error()) + utils.WriteErrorResponse(w, http.StatusUnauthorized, err.Error()) return } res, err := cliCtx.BroadcastTx(txBytes) if err != nil { - utils.WriteErrorResponse(&w, http.StatusInternalServerError, err.Error()) + utils.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) return } output, err := cdc.MarshalJSON(res) if err != nil { - utils.WriteErrorResponse(&w, http.StatusInternalServerError, err.Error()) + utils.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) return } diff --git a/x/slashing/client/rest/tx.go b/x/slashing/client/rest/tx.go index 322a4aa083..faf0341ce5 100644 --- a/x/slashing/client/rest/tx.go +++ b/x/slashing/client/rest/tx.go @@ -44,29 +44,29 @@ func unjailRequestHandlerFn(cdc *wire.Codec, kb keys.Keybase, cliCtx context.CLI var m UnjailBody body, err := ioutil.ReadAll(r.Body) if err != nil { - utils.WriteErrorResponse(&w, http.StatusBadRequest, err.Error()) + utils.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) return } err = json.Unmarshal(body, &m) if err != nil { - utils.WriteErrorResponse(&w, http.StatusBadRequest, err.Error()) + utils.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) return } info, err := kb.Get(m.LocalAccountName) if err != nil { - utils.WriteErrorResponse(&w, http.StatusUnauthorized, err.Error()) + utils.WriteErrorResponse(w, http.StatusUnauthorized, err.Error()) return } valAddr, err := sdk.ValAddressFromBech32(m.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("Couldn't 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 } @@ -80,7 +80,7 @@ func unjailRequestHandlerFn(cdc *wire.Codec, kb keys.Keybase, cliCtx context.CLI msg := slashing.NewMsgUnjail(valAddr) - adjustment, ok := utils.ParseFloat64OrReturnBadRequest(&w, m.GasAdjustment, client.DefaultGasAdjustment) + adjustment, ok := utils.ParseFloat64OrReturnBadRequest(w, m.GasAdjustment, client.DefaultGasAdjustment) if !ok { return } @@ -89,11 +89,11 @@ func unjailRequestHandlerFn(cdc *wire.Codec, kb keys.Keybase, cliCtx context.CLI if utils.HasDryRunArg(r) || m.Gas == 0 { newCtx, err := utils.EnrichCtxWithGas(txCtx, cliCtx, m.LocalAccountName, []sdk.Msg{msg}) if err != nil { - utils.WriteErrorResponse(&w, http.StatusInternalServerError, err.Error()) + utils.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) return } if utils.HasDryRunArg(r) { - utils.WriteSimulationResponse(&w, txCtx.Gas) + utils.WriteSimulationResponse(w, txCtx.Gas) return } txCtx = newCtx @@ -101,19 +101,19 @@ func unjailRequestHandlerFn(cdc *wire.Codec, kb keys.Keybase, cliCtx context.CLI txBytes, err := txCtx.BuildAndSign(m.LocalAccountName, m.Password, []sdk.Msg{msg}) if err != nil { - utils.WriteErrorResponse(&w, http.StatusUnauthorized, "Must use own validator address") + 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()) + utils.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) return } output, err := json.MarshalIndent(res, "", " ") if err != nil { - utils.WriteErrorResponse(&w, http.StatusInternalServerError, err.Error()) + utils.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) return } diff --git a/x/stake/client/rest/tx.go b/x/stake/client/rest/tx.go index 5e6226dcc7..9925b6b1ce 100644 --- a/x/stake/client/rest/tx.go +++ b/x/stake/client/rest/tx.go @@ -108,18 +108,18 @@ func delegationsRequestHandlerFn(cdc *wire.Codec, kb keys.Keybase, cliCtx contex for _, msg := range m.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())) + utils.WriteErrorResponse(w, http.StatusInternalServerError, fmt.Sprintf("Couldn't decode delegator. Error: %s", err.Error())) return } valAddr, err := sdk.ValAddressFromBech32(msg.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("Couldn't decode validator. Error: %s", err.Error())) return } if !bytes.Equal(info.GetPubKey().Address(), delAddr) { - utils.WriteErrorResponse(&w, http.StatusUnauthorized, "Must use own delegator address") + utils.WriteErrorResponse(w, http.StatusUnauthorized, "Must use own delegator address") return } @@ -135,29 +135,29 @@ func delegationsRequestHandlerFn(cdc *wire.Codec, kb keys.Keybase, cliCtx contex for _, msg := range m.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())) + utils.WriteErrorResponse(w, http.StatusInternalServerError, fmt.Sprintf("Couldn't decode validator. Error: %s", err.Error())) return } if !bytes.Equal(info.GetPubKey().Address(), delAddr) { - utils.WriteErrorResponse(&w, http.StatusUnauthorized, "Must use own delegator address") + utils.WriteErrorResponse(w, http.StatusUnauthorized, "Must use own delegator address") return } valSrcAddr, err := sdk.ValAddressFromBech32(msg.ValidatorSrcAddr) 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("Couldn't decode validator. Error: %s", err.Error())) return } valDstAddr, err := sdk.ValAddressFromBech32(msg.ValidatorDstAddr) 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("Couldn't decode validator. Error: %s", err.Error())) return } shares, err := sdk.NewDecFromStr(msg.SharesAmount) if err != nil { - utils.WriteErrorResponse(&w, http.StatusInternalServerError, fmt.Sprintf("Couldn't decode shares amount. Error: %s", err.Error())) + utils.WriteErrorResponse(w, http.StatusInternalServerError, fmt.Sprintf("Couldn't decode shares amount. Error: %s", err.Error())) return } @@ -174,24 +174,24 @@ func delegationsRequestHandlerFn(cdc *wire.Codec, kb keys.Keybase, cliCtx contex for _, msg := range m.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())) + utils.WriteErrorResponse(w, http.StatusInternalServerError, fmt.Sprintf("Couldn't decode delegator. Error: %s", err.Error())) return } valSrcAddr, err := sdk.ValAddressFromBech32(msg.ValidatorSrcAddr) 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("Couldn't decode validator. Error: %s", err.Error())) return } valDstAddr, err := sdk.ValAddressFromBech32(msg.ValidatorDstAddr) 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("Couldn't decode validator. Error: %s", err.Error())) return } if !bytes.Equal(info.GetPubKey().Address(), delAddr) { - utils.WriteErrorResponse(&w, http.StatusUnauthorized, "Must use own delegator address") + utils.WriteErrorResponse(w, http.StatusUnauthorized, "Must use own delegator address") return } @@ -207,24 +207,24 @@ func delegationsRequestHandlerFn(cdc *wire.Codec, kb keys.Keybase, cliCtx contex for _, msg := range m.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())) + utils.WriteErrorResponse(w, http.StatusInternalServerError, fmt.Sprintf("Couldn't decode delegator. Error: %s", err.Error())) return } if !bytes.Equal(info.GetPubKey().Address(), delAddr) { - utils.WriteErrorResponse(&w, http.StatusUnauthorized, "Must use own delegator address") + utils.WriteErrorResponse(w, http.StatusUnauthorized, "Must use own delegator address") return } valAddr, err := sdk.ValAddressFromBech32(msg.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("Couldn't decode validator. Error: %s", err.Error())) return } shares, err := sdk.NewDecFromStr(msg.SharesAmount) if err != nil { - utils.WriteErrorResponse(&w, http.StatusInternalServerError, fmt.Sprintf("Couldn't decode shares amount. Error: %s", err.Error())) + utils.WriteErrorResponse(w, http.StatusInternalServerError, fmt.Sprintf("Couldn't decode shares amount. Error: %s", err.Error())) return } @@ -240,18 +240,18 @@ func delegationsRequestHandlerFn(cdc *wire.Codec, kb keys.Keybase, cliCtx contex for _, msg := range m.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())) + utils.WriteErrorResponse(w, http.StatusInternalServerError, fmt.Sprintf("Couldn't decode delegator. Error: %s", err.Error())) return } valAddr, err := sdk.ValAddressFromBech32(msg.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("Couldn't decode validator. Error: %s", err.Error())) return } if !bytes.Equal(info.GetPubKey().Address(), delAddr) { - utils.WriteErrorResponse(&w, http.StatusUnauthorized, "Must use own delegator address") + utils.WriteErrorResponse(w, http.StatusUnauthorized, "Must use own delegator address") return } @@ -278,7 +278,7 @@ func delegationsRequestHandlerFn(cdc *wire.Codec, kb keys.Keybase, cliCtx contex m.Sequence++ - adjustment, ok := utils.ParseFloat64OrReturnBadRequest(&w, m.GasAdjustment, client.DefaultGasAdjustment) + adjustment, ok := utils.ParseFloat64OrReturnBadRequest(w, m.GasAdjustment, client.DefaultGasAdjustment) if !ok { return } @@ -287,11 +287,11 @@ func delegationsRequestHandlerFn(cdc *wire.Codec, kb keys.Keybase, cliCtx contex if utils.HasDryRunArg(r) || m.Gas == 0 { newCtx, err := utils.EnrichCtxWithGas(txCtx, cliCtx, m.LocalAccountName, []sdk.Msg{msg}) if err != nil { - utils.WriteErrorResponse(&w, http.StatusInternalServerError, err.Error()) + utils.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) return } if utils.HasDryRunArg(r) { - utils.WriteSimulationResponse(&w, txCtx.Gas) + utils.WriteSimulationResponse(w, txCtx.Gas) return } txCtx = newCtx @@ -299,7 +299,7 @@ func delegationsRequestHandlerFn(cdc *wire.Codec, kb keys.Keybase, cliCtx contex txBytes, err := txCtx.BuildAndSign(m.LocalAccountName, m.Password, []sdk.Msg{msg}) if err != nil { - utils.WriteErrorResponse(&w, http.StatusUnauthorized, err.Error()) + utils.WriteErrorResponse(w, http.StatusUnauthorized, err.Error()) return } @@ -313,7 +313,7 @@ func delegationsRequestHandlerFn(cdc *wire.Codec, kb keys.Keybase, cliCtx contex for i, txBytes := range signedTxs { res, err := cliCtx.BroadcastTx(txBytes) if err != nil { - utils.WriteErrorResponse(&w, http.StatusInternalServerError, err.Error()) + utils.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) return } @@ -322,7 +322,7 @@ func delegationsRequestHandlerFn(cdc *wire.Codec, kb keys.Keybase, cliCtx contex output, err := wire.MarshalJSONIndent(cdc, results[:]) if err != nil { - utils.WriteErrorResponse(&w, http.StatusInternalServerError, err.Error()) + utils.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) return } From 1ed76565f2d1e1667011de6f6453836ea13383d0 Mon Sep 17 00:00:00 2001 From: Alessio Treglia Date: Fri, 31 Aug 2018 17:13:24 +0200 Subject: [PATCH 35/52] Remove obsolete comments --- x/auth/ante.go | 3 --- 1 file changed, 3 deletions(-) diff --git a/x/auth/ante.go b/x/auth/ante.go index 94899a8726..51440e2ece 100644 --- a/x/auth/ante.go +++ b/x/auth/ante.go @@ -175,14 +175,11 @@ func processSig( fmt.Sprintf("Invalid sequence. Got %d, expected %d", sig.Sequence, seq)).Result() } } - // Increment sequence number err := acc.SetSequence(seq + 1) if err != nil { // Handle w/ #870 panic(err) } - // If pubkey is not known for account, - // set it from the StdSignature. pubKey, res := processPubKey(acc, sig, simulate) if !res.IsOK() { return nil, res From d84885ca49e557fac2964fb7eae6d6a049656e51 Mon Sep 17 00:00:00 2001 From: Alessio Treglia Date: Fri, 31 Aug 2018 17:47:20 +0200 Subject: [PATCH 36/52] No need to create a fake slice of signatures nor skip account no./sequence check --- client/context/context.go | 2 ++ x/auth/ante.go | 25 +++++++++---------------- x/bank/app_test.go | 2 +- 3 files changed, 12 insertions(+), 17 deletions(-) diff --git a/client/context/context.go b/client/context/context.go index 2444c56d13..8e56cfa1ae 100644 --- a/client/context/context.go +++ b/client/context/context.go @@ -159,6 +159,8 @@ func (ctx CLIContext) WithUseLedger(useLedger bool) CLIContext { // WithCertifier - return a copy of the context with an updated Certifier func (ctx CLIContext) WithCertifier(certifier tmlite.Certifier) CLIContext { ctx.Certifier = certifier + return ctx +} // WithGasAdjustment returns a copy of the context with an updated GasAdjustment flag. func (ctx CLIContext) WithGasAdjustment(adjustment float64) CLIContext { diff --git a/x/auth/ante.go b/x/auth/ante.go index 51440e2ece..65071e69a7 100644 --- a/x/auth/ante.go +++ b/x/auth/ante.go @@ -68,9 +68,6 @@ func NewAnteHandler(am AccountMapper, fck FeeCollectionKeeper) sdk.AnteHandler { sigs := stdTx.GetSignatures() // When simulating, this would just be a 0-length slice. signerAddrs := stdTx.GetSigners() msgs := tx.GetMsgs() - if simulate { - sigs = make([]StdSignature, len(signerAddrs)) - } // charge gas for the memo newCtx.GasMeter().ConsumeGas(memoCostPerByte*sdk.Gas(len(stdTx.GetMemo())), "memo") @@ -160,20 +157,16 @@ func processSig( accnum := acc.GetAccountNumber() seq := acc.GetSequence() - // Perform checks that wouldn't pass successfully in simulation, i.e. sig - // would be empty as simulated transactions come with no signatures whatsoever. - if !simulate { - // 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 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() - } + // 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 { diff --git a/x/bank/app_test.go b/x/bank/app_test.go index 7ef344ffcd..77991f94b7 100644 --- a/x/bank/app_test.go +++ b/x/bank/app_test.go @@ -123,7 +123,7 @@ func TestMsgSendWithAccounts(t *testing.T) { msgs: []sdk.Msg{sendMsg1, sendMsg2}, accNums: []int64{0}, accSeqs: []int64{0}, - expSimPass: true, + expSimPass: false, expPass: false, privKeys: []crypto.PrivKey{priv1}, }, From b92ac313acce45c0c60cfe319f774a4a50f05b02 Mon Sep 17 00:00:00 2001 From: Rigel Date: Fri, 31 Aug 2018 15:21:12 -0400 Subject: [PATCH 37/52] Merg PR #2198: Ensure Legacy Validator Delegation Invariants * Test and allow jailed validator to self-bond * Implement TestJailedValidatorDelegations * Restructure TestJailedValidatorDelegations * Add Delegation to Validator type and update handleMsgUnjail accordingly * Update ErrMissingSelfDelegation error message * Update democoin mock validator set impl * Update pending log * Add comment to ValidatorSet * Fix conflicts/errors due to develop merge --- PENDING.md | 1 + examples/democoin/mock/validator.go | 5 ++ types/stake.go | 4 ++ x/slashing/errors.go | 14 ++++- x/slashing/handler.go | 15 +++-- x/slashing/handler_test.go | 60 +++++++++++++++++++ x/slashing/test_common.go | 11 +++- x/stake/handler.go | 8 ++- x/stake/handler_test.go | 91 +++++++++++++++++++++++++++++ x/stake/keeper/delegation.go | 3 +- x/stake/keeper/sdk_types.go | 1 + 11 files changed, 200 insertions(+), 13 deletions(-) diff --git a/PENDING.md b/PENDING.md index 832f25e8f4..3843be5fd5 100644 --- a/PENDING.md +++ b/PENDING.md @@ -73,6 +73,7 @@ IMPROVEMENTS * Gaia * [x/stake] [#2023](https://github.com/cosmos/cosmos-sdk/pull/2023) Terminate iteration loop in `UpdateBondedValidators` and `UpdateBondedValidatorsFull` when the first revoked validator is encountered and perform a sanity check. * [x/auth] Signature verification's gas cost now accounts for pubkey type. [#2046](https://github.com/tendermint/tendermint/pull/2046) + * [x/stake] [x/slashing] Ensure delegation invariants to jailed validators [#1883](https://github.com/cosmos/cosmos-sdk/issues/1883). * SDK * [tools] Make get_vendor_deps deletes `.vendor-new` directories, in case scratch files are present. diff --git a/examples/democoin/mock/validator.go b/examples/democoin/mock/validator.go index f937e45dcc..9f84786adf 100644 --- a/examples/democoin/mock/validator.go +++ b/examples/democoin/mock/validator.go @@ -135,3 +135,8 @@ func (vs *ValidatorSet) Jail(ctx sdk.Context, pubkey crypto.PubKey) { func (vs *ValidatorSet) Unjail(ctx sdk.Context, pubkey crypto.PubKey) { panic("not implemented") } + +// Implements sdk.ValidatorSet +func (vs *ValidatorSet) Delegation(ctx sdk.Context, addrDel sdk.AccAddress, addrVal sdk.ValAddress) sdk.Delegation { + panic("not implemented") +} diff --git a/types/stake.go b/types/stake.go index f411251775..978398fa51 100644 --- a/types/stake.go +++ b/types/stake.go @@ -75,6 +75,10 @@ type ValidatorSet interface { Slash(Context, crypto.PubKey, int64, int64, Dec) Jail(Context, crypto.PubKey) // jail a validator Unjail(Context, crypto.PubKey) // unjail a validator + + // Delegation allows for getting a particular delegation for a given validator + // and delegator outside the scope of the staking module. + Delegation(Context, AccAddress, ValAddress) Delegation } //_______________________________________________________________________________ diff --git a/x/slashing/errors.go b/x/slashing/errors.go index 4573d5e145..77cb2d28e3 100644 --- a/x/slashing/errors.go +++ b/x/slashing/errors.go @@ -12,20 +12,28 @@ const ( // Default slashing codespace DefaultCodespace sdk.CodespaceType = 10 - CodeInvalidValidator CodeType = 101 - CodeValidatorJailed CodeType = 102 - CodeValidatorNotJailed CodeType = 103 + CodeInvalidValidator CodeType = 101 + CodeValidatorJailed CodeType = 102 + CodeValidatorNotJailed CodeType = 103 + CodeMissingSelfDelegation CodeType = 104 ) func ErrNoValidatorForAddress(codespace sdk.CodespaceType) sdk.Error { return sdk.NewError(codespace, CodeInvalidValidator, "that address is not associated with any known validator") } + func ErrBadValidatorAddr(codespace sdk.CodespaceType) sdk.Error { return sdk.NewError(codespace, CodeInvalidValidator, "validator does not exist for that address") } + func ErrValidatorJailed(codespace sdk.CodespaceType) sdk.Error { return sdk.NewError(codespace, CodeValidatorJailed, "validator still jailed, cannot yet be unjailed") } + func ErrValidatorNotJailed(codespace sdk.CodespaceType) sdk.Error { return sdk.NewError(codespace, CodeValidatorNotJailed, "validator not jailed, cannot be unjailed") } + +func ErrMissingSelfDelegation(codespace sdk.CodespaceType) sdk.Error { + return sdk.NewError(codespace, CodeMissingSelfDelegation, "validator has no self-delegation; cannot be unjailed") +} diff --git a/x/slashing/handler.go b/x/slashing/handler.go index d79ea73c2d..c18587955b 100644 --- a/x/slashing/handler.go +++ b/x/slashing/handler.go @@ -19,35 +19,38 @@ func NewHandler(k Keeper) sdk.Handler { // Validators must submit a transaction to unjail itself after // having been jailed (and thus unbonded) for downtime func handleMsgUnjail(ctx sdk.Context, msg MsgUnjail, k Keeper) sdk.Result { - - // Validator must exist validator := k.validatorSet.Validator(ctx, msg.ValidatorAddr) if validator == nil { return ErrNoValidatorForAddress(k.codespace).Result() } + // cannot be unjailed if no self-delegation exists + selfDel := k.validatorSet.Delegation(ctx, sdk.AccAddress(msg.ValidatorAddr), msg.ValidatorAddr) + if selfDel == nil { + return ErrMissingSelfDelegation(k.codespace).Result() + } + if !validator.GetJailed() { return ErrValidatorNotJailed(k.codespace).Result() } addr := sdk.ValAddress(validator.GetPubKey().Address()) - // Signing info must exist info, found := k.getValidatorSigningInfo(ctx, addr) if !found { return ErrNoValidatorForAddress(k.codespace).Result() } - // Cannot be unjailed until out of jail + // cannot be unjailed until out of jail if ctx.BlockHeader().Time.Before(info.JailedUntil) { return ErrValidatorJailed(k.codespace).Result() } - // Update the starting height (so the validator can't be immediately jailed again) + // update the starting height so the validator can't be immediately jailed + // again info.StartHeight = ctx.BlockHeight() k.setValidatorSigningInfo(ctx, addr, info) - // Unjail the validator k.validatorSet.Unjail(ctx, validator.GetPubKey()) tags := sdk.NewTags("action", []byte("unjail"), "validator", []byte(msg.ValidatorAddr.String())) diff --git a/x/slashing/handler_test.go b/x/slashing/handler_test.go index 8e3b719f49..8c522215a2 100644 --- a/x/slashing/handler_test.go +++ b/x/slashing/handler_test.go @@ -2,6 +2,7 @@ package slashing import ( "testing" + "time" "github.com/stretchr/testify/require" @@ -27,3 +28,62 @@ func TestCannotUnjailUnlessJailed(t *testing.T) { require.False(t, got.IsOK(), "allowed unjail of non-jailed validator") require.Equal(t, sdk.ToABCICode(DefaultCodespace, CodeValidatorNotJailed), got.Code) } + +func TestJailedValidatorDelegations(t *testing.T) { + ctx, _, stakeKeeper, _, slashingKeeper := createTestInput(t) + + stakeParams := stakeKeeper.GetParams(ctx) + stakeParams.UnbondingTime = 0 + stakeKeeper.SetParams(ctx, stakeParams) + + // create a validator + amount := int64(10) + valAddr, valPubKey, bondAmount := sdk.ValAddress(addrs[0]), pks[0], sdk.NewInt(amount) + msgCreateVal := newTestMsgCreateValidator(valAddr, valPubKey, bondAmount) + got := stake.NewHandler(stakeKeeper)(ctx, msgCreateVal) + require.True(t, got.IsOK(), "expected create validator msg to be ok, got: %v", got) + + // set dummy signing info + newInfo := ValidatorSigningInfo{ + StartHeight: int64(0), + IndexOffset: int64(0), + JailedUntil: time.Unix(0, 0), + SignedBlocksCounter: int64(0), + } + slashingKeeper.setValidatorSigningInfo(ctx, valAddr, newInfo) + + // delegate tokens to the validator + delAddr := addrs[1] + msgDelegate := newTestMsgDelegate(delAddr, valAddr, bondAmount) + got = stake.NewHandler(stakeKeeper)(ctx, msgDelegate) + require.True(t, got.IsOK(), "expected delegation to be ok, got %v", got) + + unbondShares := sdk.NewDec(10) + + // unbond validator total self-delegations (which should jail the validator) + msgBeginUnbonding := stake.NewMsgBeginUnbonding(sdk.AccAddress(valAddr), valAddr, unbondShares) + got = stake.NewHandler(stakeKeeper)(ctx, msgBeginUnbonding) + require.True(t, got.IsOK(), "expected begin unbonding validator msg to be ok, got: %v", got) + + msgCompleteUnbonding := stake.NewMsgCompleteUnbonding(sdk.AccAddress(valAddr), valAddr) + got = stake.NewHandler(stakeKeeper)(ctx, msgCompleteUnbonding) + require.True(t, got.IsOK(), "expected complete unbonding validator msg to be ok, got: %v", got) + + // verify validator still exists and is jailed + validator, found := stakeKeeper.GetValidator(ctx, valAddr) + require.True(t, found) + require.True(t, validator.GetJailed()) + + // verify the validator cannot unjail itself + got = NewHandler(slashingKeeper)(ctx, NewMsgUnjail(valAddr)) + require.False(t, got.IsOK(), "expected jailed validator to not be able to unjail, got: %v", got) + + // self-delegate to validator + msgSelfDelegate := newTestMsgDelegate(sdk.AccAddress(valAddr), valAddr, bondAmount) + got = stake.NewHandler(stakeKeeper)(ctx, msgSelfDelegate) + require.True(t, got.IsOK(), "expected delegation to not be ok, got %v", got) + + // verify the validator can now unjail itself + got = NewHandler(slashingKeeper)(ctx, NewMsgUnjail(valAddr)) + require.True(t, got.IsOK(), "expected jailed validator to be able to unjail, got: %v", got) +} diff --git a/x/slashing/test_common.go b/x/slashing/test_common.go index 1053823786..6974113100 100644 --- a/x/slashing/test_common.go +++ b/x/slashing/test_common.go @@ -4,6 +4,7 @@ import ( "encoding/hex" "os" "testing" + "time" "github.com/stretchr/testify/require" @@ -61,7 +62,7 @@ func createTestInput(t *testing.T) (sdk.Context, bank.Keeper, stake.Keeper, para ms.MountStoreWithDB(keyParams, sdk.StoreTypeIAVL, db) err := ms.LoadLatestVersion() require.Nil(t, err) - ctx := sdk.NewContext(ms, abci.Header{}, false, log.NewTMLogger(os.Stdout)) + ctx := sdk.NewContext(ms, abci.Header{Time: time.Unix(0, 0)}, false, log.NewTMLogger(os.Stdout)) cdc := createTestCodec() accountMapper := auth.NewAccountMapper(cdc, keyAcc, auth.ProtoBaseAccount) ck := bank.NewKeeper(accountMapper) @@ -108,3 +109,11 @@ func newTestMsgCreateValidator(address sdk.ValAddress, pubKey crypto.PubKey, amt Delegation: sdk.Coin{"steak", amt}, } } + +func newTestMsgDelegate(delAddr sdk.AccAddress, valAddr sdk.ValAddress, delAmount sdk.Int) stake.MsgDelegate { + return stake.MsgDelegate{ + DelegatorAddr: delAddr, + ValidatorAddr: valAddr, + Delegation: sdk.Coin{"steak", delAmount}, + } +} diff --git a/x/stake/handler.go b/x/stake/handler.go index c8be6a835a..4b478fffd7 100644 --- a/x/stake/handler.go +++ b/x/stake/handler.go @@ -1,6 +1,7 @@ package stake import ( + "bytes" "time" sdk "github.com/cosmos/cosmos-sdk/types" @@ -128,17 +129,19 @@ func handleMsgEditValidator(ctx sdk.Context, msg types.MsgEditValidator, k keepe } func handleMsgDelegate(ctx sdk.Context, msg types.MsgDelegate, k keeper.Keeper) sdk.Result { - validator, found := k.GetValidator(ctx, msg.ValidatorAddr) if !found { return ErrNoValidatorFound(k.Codespace()).Result() } + if msg.Delegation.Denom != k.GetParams(ctx).BondDenom { return ErrBadDenom(k.Codespace()).Result() } - if validator.Jailed { + + if validator.Jailed && !bytes.Equal(validator.Operator, msg.DelegatorAddr) { return ErrValidatorJailed(k.Codespace()).Result() } + _, err := k.Delegate(ctx, msg.DelegatorAddr, msg.Delegation, validator, true) if err != nil { return err.Result() @@ -149,6 +152,7 @@ func handleMsgDelegate(ctx sdk.Context, msg types.MsgDelegate, k keeper.Keeper) tags.Delegator, []byte(msg.DelegatorAddr.String()), tags.DstValidator, []byte(msg.ValidatorAddr.String()), ) + return sdk.Result{ Tags: tags, } diff --git a/x/stake/handler_test.go b/x/stake/handler_test.go index f7d01bcfc7..2006745d89 100644 --- a/x/stake/handler_test.go +++ b/x/stake/handler_test.go @@ -193,6 +193,97 @@ func TestDuplicatesMsgCreateValidatorOnBehalfOf(t *testing.T) { require.False(t, got.IsOK(), "%v", got) } +func TestLegacyValidatorDelegations(t *testing.T) { + ctx, _, keeper := keep.CreateTestInput(t, false, int64(1000)) + setInstantUnbondPeriod(keeper, ctx) + + bondAmount := int64(10) + valAddr, valPubKey := sdk.ValAddress(keep.Addrs[0]), keep.PKs[0] + delAddr := keep.Addrs[1] + + // create validator + msgCreateVal := newTestMsgCreateValidator(valAddr, valPubKey, bondAmount) + got := handleMsgCreateValidator(ctx, msgCreateVal, keeper) + require.True(t, got.IsOK(), "expected create validator msg to be ok, got %v", got) + + // verify the validator exists and has the correct attributes + validator, found := keeper.GetValidator(ctx, valAddr) + require.True(t, found) + require.Equal(t, sdk.Bonded, validator.Status) + require.Equal(t, bondAmount, validator.DelegatorShares.RoundInt64()) + require.Equal(t, bondAmount, validator.BondedTokens().RoundInt64()) + + // delegate tokens to the validator + msgDelegate := newTestMsgDelegate(delAddr, valAddr, bondAmount) + got = handleMsgDelegate(ctx, msgDelegate, keeper) + require.True(t, got.IsOK(), "expected delegation to be ok, got %v", got) + + // verify validator bonded shares + validator, found = keeper.GetValidator(ctx, valAddr) + require.True(t, found) + require.Equal(t, bondAmount*2, validator.DelegatorShares.RoundInt64()) + require.Equal(t, bondAmount*2, validator.BondedTokens().RoundInt64()) + + // unbond validator total self-delegations (which should jail the validator) + unbondShares := sdk.NewDec(10) + msgBeginUnbonding := NewMsgBeginUnbonding(sdk.AccAddress(valAddr), valAddr, unbondShares) + msgCompleteUnbonding := NewMsgCompleteUnbonding(sdk.AccAddress(valAddr), valAddr) + + got = handleMsgBeginUnbonding(ctx, msgBeginUnbonding, keeper) + require.True(t, got.IsOK(), "expected begin unbonding validator msg to be ok, got %v", got) + + got = handleMsgCompleteUnbonding(ctx, msgCompleteUnbonding, keeper) + require.True(t, got.IsOK(), "expected complete unbonding validator msg to be ok, got %v", got) + + // verify the validator record still exists, is jailed, and has correct tokens + validator, found = keeper.GetValidator(ctx, valAddr) + require.True(t, found) + require.True(t, validator.Jailed) + require.Equal(t, sdk.NewDec(10), validator.Tokens) + + // verify delegation still exists + bond, found := keeper.GetDelegation(ctx, delAddr, valAddr) + require.True(t, found) + require.Equal(t, bondAmount, bond.Shares.RoundInt64()) + require.Equal(t, bondAmount, validator.DelegatorShares.RoundInt64()) + + // verify a delegator cannot create a new delegation to the now jailed validator + msgDelegate = newTestMsgDelegate(delAddr, valAddr, bondAmount) + got = handleMsgDelegate(ctx, msgDelegate, keeper) + require.False(t, got.IsOK(), "expected delegation to not be ok, got %v", got) + + // verify the validator can still self-delegate + msgSelfDelegate := newTestMsgDelegate(sdk.AccAddress(valAddr), valAddr, bondAmount) + got = handleMsgDelegate(ctx, msgSelfDelegate, keeper) + require.True(t, got.IsOK(), "expected delegation to not be ok, got %v", got) + + // verify validator bonded shares + validator, found = keeper.GetValidator(ctx, valAddr) + require.True(t, found) + require.Equal(t, bondAmount*2, validator.DelegatorShares.RoundInt64()) + require.Equal(t, bondAmount*2, validator.Tokens.RoundInt64()) + + // unjail the validator now that is has non-zero self-delegated shares + keeper.Unjail(ctx, valPubKey) + + // verify the validator can now accept delegations + msgDelegate = newTestMsgDelegate(delAddr, valAddr, bondAmount) + got = handleMsgDelegate(ctx, msgDelegate, keeper) + require.True(t, got.IsOK(), "expected delegation to be ok, got %v", got) + + // verify validator bonded shares + validator, found = keeper.GetValidator(ctx, valAddr) + require.True(t, found) + require.Equal(t, bondAmount*3, validator.DelegatorShares.RoundInt64()) + require.Equal(t, bondAmount*3, validator.Tokens.RoundInt64()) + + // verify new delegation + bond, found = keeper.GetDelegation(ctx, delAddr, valAddr) + require.True(t, found) + require.Equal(t, bondAmount*2, bond.Shares.RoundInt64()) + require.Equal(t, bondAmount*3, validator.DelegatorShares.RoundInt64()) +} + func TestIncrementsMsgDelegate(t *testing.T) { initBond := int64(1000) ctx, accMapper, keeper := keep.CreateTestInput(t, false, initBond) diff --git a/x/stake/keeper/delegation.go b/x/stake/keeper/delegation.go index 9f7a402fc2..2c997b4633 100644 --- a/x/stake/keeper/delegation.go +++ b/x/stake/keeper/delegation.go @@ -288,6 +288,7 @@ func (k Keeper) unbond(ctx sdk.Context, delAddr sdk.AccAddress, valAddr sdk.ValA if bytes.Equal(delegation.DelegatorAddr, validator.Operator) && validator.Jailed == false { validator.Jailed = true } + k.RemoveDelegation(ctx, delegation) } else { // Update height @@ -307,7 +308,7 @@ func (k Keeper) unbond(ctx sdk.Context, delAddr sdk.AccAddress, valAddr sdk.ValA k.RemoveValidator(ctx, validator.Operator) } - return + return amount, nil } //______________________________________________________________________________________________________ diff --git a/x/stake/keeper/sdk_types.go b/x/stake/keeper/sdk_types.go index 480df701b1..e4c7a1c193 100644 --- a/x/stake/keeper/sdk_types.go +++ b/x/stake/keeper/sdk_types.go @@ -91,6 +91,7 @@ func (k Keeper) Delegation(ctx sdk.Context, addrDel sdk.AccAddress, addrVal sdk. if !ok { return nil } + return bond } From 12048576940ae9f9273650b463655c11f0f54528 Mon Sep 17 00:00:00 2001 From: Christopher Goes Date: Sat, 1 Sep 2018 02:01:23 +0200 Subject: [PATCH 38/52] Merge PR #2122: Implement slashing period * Update PENDING.md * SlashingPeriod struct * Seperate keys.go, constant prefixes * Make linter happy * Update Gopkg.lock * Seek slashing period by infraction height * Slashing period hooks * Slashing period unit tests; bugfix * Add simple hook tests * Add sdk.ValidatorHooks interface * No-op hooks * Real hooks * Fix iteration direction & duplicate key, update Gaia * Correctly simulate past validator set signatures * Tiny rename * Update dep; 'make format' * Add quick slashing period functionality test * Additional unit tests * Use current validators when selected * Panic in the right place * Address @rigelrozanski comments * Fix linter errors * Address @melekes suggestion * Rename hook * Update for new bech32 types * 'make format' --- PENDING.md | 1 + cmd/gaia/app/app.go | 3 +- server/tm_cmds.go | 2 +- types/decimal.go | 8 ++ types/stake.go | 10 ++ x/mock/simulation/random_simulate_blocks.go | 54 +++++----- x/slashing/app_test.go | 4 +- x/slashing/client/cli/query.go | 2 +- x/slashing/client/rest/query.go | 2 +- x/slashing/handler.go | 2 +- x/slashing/handler_test.go | 2 +- x/slashing/hooks.go | 46 +++++++++ x/slashing/hooks_test.go | 26 +++++ x/slashing/keeper.go | 16 +-- x/slashing/keeper_test.go | 75 ++++++++++++-- x/slashing/keys.go | 43 ++++++++ x/slashing/signing_info.go | 21 +--- x/slashing/signing_info_test.go | 12 +-- x/slashing/slashing_period.go | 107 ++++++++++++++++++++ x/slashing/slashing_period_test.go | 86 ++++++++++++++++ x/slashing/test_common.go | 10 +- x/slashing/tick_test.go | 4 +- x/stake/keeper/keeper.go | 25 +++-- x/stake/keeper/validator.go | 13 +++ x/stake/types/validator.go | 5 + 25 files changed, 494 insertions(+), 85 deletions(-) create mode 100644 x/slashing/hooks.go create mode 100644 x/slashing/hooks_test.go create mode 100644 x/slashing/keys.go create mode 100644 x/slashing/slashing_period.go create mode 100644 x/slashing/slashing_period_test.go diff --git a/PENDING.md b/PENDING.md index 32f1face82..17f9b2a458 100644 --- a/PENDING.md +++ b/PENDING.md @@ -30,6 +30,7 @@ BREAKING CHANGES * SDK * [core] \#1807 Switch from use of rational to decimal * [types] \#1901 Validator interface's GetOwner() renamed to GetOperator() + * [x/slashing] [#2122](https://github.com/cosmos/cosmos-sdk/pull/2122) - Implement slashing period * [types] \#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) diff --git a/cmd/gaia/app/app.go b/cmd/gaia/app/app.go index 4ce6b2806d..f1ca2a7b66 100644 --- a/cmd/gaia/app/app.go +++ b/cmd/gaia/app/app.go @@ -93,9 +93,10 @@ func NewGaiaApp(logger log.Logger, db dbm.DB, traceStore io.Writer, baseAppOptio app.ibcMapper = ibc.NewMapper(app.cdc, app.keyIBC, app.RegisterCodespace(ibc.DefaultCodespace)) app.paramsKeeper = params.NewKeeper(app.cdc, app.keyParams) app.stakeKeeper = stake.NewKeeper(app.cdc, app.keyStake, app.coinKeeper, app.RegisterCodespace(stake.DefaultCodespace)) + app.slashingKeeper = slashing.NewKeeper(app.cdc, app.keySlashing, app.stakeKeeper, app.paramsKeeper.Getter(), app.RegisterCodespace(slashing.DefaultCodespace)) + app.stakeKeeper = app.stakeKeeper.WithValidatorHooks(app.slashingKeeper.ValidatorHooks()) app.govKeeper = gov.NewKeeper(app.cdc, app.keyGov, app.paramsKeeper.Setter(), app.coinKeeper, app.stakeKeeper, app.RegisterCodespace(gov.DefaultCodespace)) app.feeCollectionKeeper = auth.NewFeeCollectionKeeper(app.cdc, app.keyFeeCollection) - app.slashingKeeper = slashing.NewKeeper(app.cdc, app.keySlashing, app.stakeKeeper, app.paramsKeeper.Getter(), app.RegisterCodespace(slashing.DefaultCodespace)) // register message routes app.Router(). diff --git a/server/tm_cmds.go b/server/tm_cmds.go index b6daf07753..bf208a5bec 100644 --- a/server/tm_cmds.go +++ b/server/tm_cmds.go @@ -7,11 +7,11 @@ import ( "github.com/spf13/cobra" "github.com/spf13/viper" + "github.com/cosmos/cosmos-sdk/client" sdk "github.com/cosmos/cosmos-sdk/types" tcmd "github.com/tendermint/tendermint/cmd/tendermint/commands" "github.com/tendermint/tendermint/p2p" pvm "github.com/tendermint/tendermint/privval" - "github.com/cosmos/cosmos-sdk/client" ) // ShowNodeIDCmd - ported from Tendermint, dump node ID to stdout diff --git a/types/decimal.go b/types/decimal.go index baf2d9573d..8e7db1340b 100644 --- a/types/decimal.go +++ b/types/decimal.go @@ -415,6 +415,14 @@ func MinDec(d1, d2 Dec) Dec { return d2 } +// maximum decimal between two +func MaxDec(d1, d2 Dec) Dec { + if d1.LT(d2) { + return d2 + } + return d1 +} + // intended to be used with require/assert: require.True(DecEq(...)) func DecEq(t *testing.T, exp, got Dec) (*testing.T, bool, string, Dec, Dec) { return t, exp.Equal(got), "expected:\t%v\ngot:\t\t%v", exp, got diff --git a/types/stake.go b/types/stake.go index 978398fa51..ba11cd5f86 100644 --- a/types/stake.go +++ b/types/stake.go @@ -99,3 +99,13 @@ type DelegationSet interface { IterateDelegations(ctx Context, delegator AccAddress, 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 { + 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 +} diff --git a/x/mock/simulation/random_simulate_blocks.go b/x/mock/simulation/random_simulate_blocks.go index 995013ef8f..a99b3a4c97 100644 --- a/x/mock/simulation/random_simulate_blocks.go +++ b/x/mock/simulation/random_simulate_blocks.go @@ -67,9 +67,10 @@ func SimulateFromSeed( header := abci.Header{Height: 0, Time: timestamp} opCount := 0 - request := abci.RequestBeginBlock{Header: header} - var pastTimes []time.Time + var pastSigningValidators [][]abci.SigningValidator + + request := RandomRequestBeginBlock(t, r, validators, livenessTransitionMatrix, evidenceFraction, pastTimes, pastSigningValidators, event, header, log) // These are operations which have been queued by previous operations operationQueue := make(map[int][]Operation) @@ -77,6 +78,7 @@ func SimulateFromSeed( // Log the header time for future lookup pastTimes = append(pastTimes, header.Time) + pastSigningValidators = append(pastSigningValidators, request.LastCommitInfo.Validators) // Run the BeginBlock handler app.BeginBlock(request) @@ -131,7 +133,7 @@ func SimulateFromSeed( } // Generate a random RequestBeginBlock with the current validator set for the next block - request = RandomRequestBeginBlock(t, r, validators, livenessTransitionMatrix, evidenceFraction, pastTimes, event, header, log) + request = RandomRequestBeginBlock(t, r, validators, livenessTransitionMatrix, evidenceFraction, pastTimes, pastSigningValidators, event, header, log) // Update the validator set validators = updateValidators(t, r, validators, res.ValidatorUpdates, event) @@ -187,13 +189,12 @@ func getKeys(validators map[string]mockValidator) []string { // RandomRequestBeginBlock generates a list of signing validators according to the provided list of validators, signing fraction, and evidence fraction func RandomRequestBeginBlock(t *testing.T, r *rand.Rand, validators map[string]mockValidator, livenessTransitions TransitionMatrix, evidenceFraction float64, - pastTimes []time.Time, event func(string), header abci.Header, log string) abci.RequestBeginBlock { + pastTimes []time.Time, pastSigningValidators [][]abci.SigningValidator, event func(string), header abci.Header, log string) abci.RequestBeginBlock { if len(validators) == 0 { return abci.RequestBeginBlock{Header: header} } signingValidators := make([]abci.SigningValidator, len(validators)) i := 0 - for _, key := range getKeys(validators) { mVal := validators[key] mVal.livenessState = livenessTransitions.NextState(r, mVal.livenessState) @@ -220,26 +221,31 @@ func RandomRequestBeginBlock(t *testing.T, r *rand.Rand, validators map[string]m i++ } evidence := make([]abci.Evidence, 0) - for r.Float64() < evidenceFraction { - height := header.Height - time := header.Time - if r.Float64() < pastEvidenceFraction { - height = int64(r.Intn(int(header.Height))) - time = pastTimes[height] + // Anything but the first block + if len(pastTimes) > 0 { + for r.Float64() < evidenceFraction { + height := header.Height + time := header.Time + vals := signingValidators + if r.Float64() < pastEvidenceFraction { + height = int64(r.Intn(int(header.Height))) + time = pastTimes[height] + vals = pastSigningValidators[height] + } + validator := vals[r.Intn(len(vals))].Validator + var totalVotingPower int64 + for _, val := range vals { + totalVotingPower += val.Validator.Power + } + evidence = append(evidence, abci.Evidence{ + Type: tmtypes.ABCIEvidenceTypeDuplicateVote, + Validator: validator, + Height: height, + Time: time, + TotalVotingPower: totalVotingPower, + }) + event("beginblock/evidence") } - validator := signingValidators[r.Intn(len(signingValidators))].Validator - var currentTotalVotingPower int64 - for _, mVal := range validators { - currentTotalVotingPower += mVal.val.Power - } - evidence = append(evidence, abci.Evidence{ - Type: tmtypes.ABCIEvidenceTypeDuplicateVote, - Validator: validator, - Height: height, - Time: time, - TotalVotingPower: currentTotalVotingPower, - }) - event("beginblock/evidence") } return abci.RequestBeginBlock{ Header: header, diff --git a/x/slashing/app_test.go b/x/slashing/app_test.go index 671c2241e8..96dd1ff871 100644 --- a/x/slashing/app_test.go +++ b/x/slashing/app_test.go @@ -79,7 +79,7 @@ func checkValidator(t *testing.T, mapp *mock.App, keeper stake.Keeper, } func checkValidatorSigningInfo(t *testing.T, mapp *mock.App, keeper Keeper, - addr sdk.ValAddress, expFound bool) ValidatorSigningInfo { + addr sdk.ConsAddress, expFound bool) ValidatorSigningInfo { ctxCheck := mapp.BaseApp.NewContext(true, abci.Header{}) signingInfo, found := keeper.getValidatorSigningInfo(ctxCheck, addr) require.Equal(t, expFound, found) @@ -113,7 +113,7 @@ func TestSlashingMsgs(t *testing.T) { unjailMsg := MsgUnjail{ValidatorAddr: sdk.ValAddress(validator.PubKey.Address())} // no signing info yet - checkValidatorSigningInfo(t, mapp, keeper, sdk.ValAddress(addr1), false) + checkValidatorSigningInfo(t, mapp, keeper, sdk.ConsAddress(addr1), false) // unjail should fail with unknown validator res := mock.SignCheckDeliver(t, mapp.BaseApp, []sdk.Msg{unjailMsg}, []int64{0}, []int64{1}, false, false, priv1) diff --git a/x/slashing/client/cli/query.go b/x/slashing/client/cli/query.go index 9f6d834dda..87d0ad41d3 100644 --- a/x/slashing/client/cli/query.go +++ b/x/slashing/client/cli/query.go @@ -25,7 +25,7 @@ func GetCmdQuerySigningInfo(storeName string, cdc *wire.Codec) *cobra.Command { return err } - key := slashing.GetValidatorSigningInfoKey(sdk.ValAddress(pk.Address())) + key := slashing.GetValidatorSigningInfoKey(sdk.ConsAddress(pk.Address())) cliCtx := context.NewCLIContext().WithCodec(cdc) res, err := cliCtx.QueryStore(key, storeName) diff --git a/x/slashing/client/rest/query.go b/x/slashing/client/rest/query.go index 291679375b..78c4a2d2f8 100644 --- a/x/slashing/client/rest/query.go +++ b/x/slashing/client/rest/query.go @@ -30,7 +30,7 @@ func signingInfoHandlerFn(cliCtx context.CLIContext, storeName string, cdc *wire return } - key := slashing.GetValidatorSigningInfoKey(sdk.ValAddress(pk.Address())) + key := slashing.GetValidatorSigningInfoKey(sdk.ConsAddress(pk.Address())) res, err := cliCtx.QueryStore(key, storeName) if err != nil { diff --git a/x/slashing/handler.go b/x/slashing/handler.go index c18587955b..c43ed6be61 100644 --- a/x/slashing/handler.go +++ b/x/slashing/handler.go @@ -34,7 +34,7 @@ func handleMsgUnjail(ctx sdk.Context, msg MsgUnjail, k Keeper) sdk.Result { return ErrValidatorNotJailed(k.codespace).Result() } - addr := sdk.ValAddress(validator.GetPubKey().Address()) + addr := sdk.ConsAddress(validator.GetPubKey().Address()) info, found := k.getValidatorSigningInfo(ctx, addr) if !found { diff --git a/x/slashing/handler_test.go b/x/slashing/handler_test.go index 8c522215a2..2ba8dcc4db 100644 --- a/x/slashing/handler_test.go +++ b/x/slashing/handler_test.go @@ -20,7 +20,7 @@ func TestCannotUnjailUnlessJailed(t *testing.T) { got := stake.NewHandler(sk)(ctx, msg) require.True(t, got.IsOK()) stake.EndBlocker(ctx, sk) - require.Equal(t, ck.GetCoins(ctx, addr), sdk.Coins{{sk.GetParams(ctx).BondDenom, initCoins.Sub(amt)}}) + require.Equal(t, ck.GetCoins(ctx, sdk.AccAddress(addr)), sdk.Coins{{sk.GetParams(ctx).BondDenom, initCoins.Sub(amt)}}) require.True(t, sdk.NewDecFromInt(amt).Equal(sk.Validator(ctx, sdk.ValAddress(addr)).GetPower())) // assert non-jailed validator can't be unjailed diff --git a/x/slashing/hooks.go b/x/slashing/hooks.go new file mode 100644 index 0000000000..f5f3cc48c3 --- /dev/null +++ b/x/slashing/hooks.go @@ -0,0 +1,46 @@ +package slashing + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" +) + +// Create a new slashing period when a validator is bonded +func (k Keeper) onValidatorBonded(ctx sdk.Context, address sdk.ConsAddress) { + slashingPeriod := ValidatorSlashingPeriod{ + ValidatorAddr: address, + StartHeight: ctx.BlockHeight(), + EndHeight: 0, + SlashedSoFar: sdk.ZeroDec(), + } + k.addOrUpdateValidatorSlashingPeriod(ctx, slashingPeriod) +} + +// Mark the slashing period as having ended when a validator begins unbonding +func (k Keeper) onValidatorBeginUnbonding(ctx sdk.Context, address sdk.ConsAddress) { + slashingPeriod := k.getValidatorSlashingPeriodForHeight(ctx, address, ctx.BlockHeight()) + slashingPeriod.EndHeight = ctx.BlockHeight() + k.addOrUpdateValidatorSlashingPeriod(ctx, slashingPeriod) +} + +// Wrapper struct for sdk.ValidatorHooks +type ValidatorHooks struct { + k Keeper +} + +// Assert implementation +var _ sdk.ValidatorHooks = ValidatorHooks{} + +// Return a sdk.ValidatorHooks interface over the wrapper struct +func (k Keeper) ValidatorHooks() sdk.ValidatorHooks { + return ValidatorHooks{k} +} + +// Implements sdk.ValidatorHooks +func (v ValidatorHooks) OnValidatorBonded(ctx sdk.Context, address sdk.ConsAddress) { + v.k.onValidatorBonded(ctx, address) +} + +// Implements sdk.ValidatorHooks +func (v ValidatorHooks) OnValidatorBeginUnbonding(ctx sdk.Context, address sdk.ConsAddress) { + v.k.onValidatorBeginUnbonding(ctx, address) +} diff --git a/x/slashing/hooks_test.go b/x/slashing/hooks_test.go new file mode 100644 index 0000000000..0731fd8f26 --- /dev/null +++ b/x/slashing/hooks_test.go @@ -0,0 +1,26 @@ +package slashing + +import ( + "testing" + + "github.com/stretchr/testify/require" + + sdk "github.com/cosmos/cosmos-sdk/types" +) + +func TestHookOnValidatorBonded(t *testing.T) { + ctx, _, _, _, keeper := createTestInput(t) + addr := sdk.ConsAddress(addrs[0]) + keeper.onValidatorBonded(ctx, addr) + period := keeper.getValidatorSlashingPeriodForHeight(ctx, addr, ctx.BlockHeight()) + require.Equal(t, ValidatorSlashingPeriod{addr, ctx.BlockHeight(), 0, sdk.ZeroDec()}, period) +} + +func TestHookOnValidatorBeginUnbonding(t *testing.T) { + ctx, _, _, _, keeper := createTestInput(t) + addr := sdk.ConsAddress(addrs[0]) + keeper.onValidatorBonded(ctx, addr) + keeper.onValidatorBeginUnbonding(ctx, addr) + period := keeper.getValidatorSlashingPeriodForHeight(ctx, addr, ctx.BlockHeight()) + require.Equal(t, ValidatorSlashingPeriod{addr, ctx.BlockHeight(), ctx.BlockHeight(), sdk.ZeroDec()}, period) +} diff --git a/x/slashing/keeper.go b/x/slashing/keeper.go index 6d8e47cbe2..2725165857 100644 --- a/x/slashing/keeper.go +++ b/x/slashing/keeper.go @@ -40,7 +40,7 @@ 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.ValAddress(addr) + address := sdk.ConsAddress(addr) pubkey, err := k.getPubkey(ctx, addr) if err != nil { panic(fmt.Sprintf("Validator address %v not found", addr)) @@ -56,8 +56,14 @@ func (k Keeper) handleDoubleSign(ctx sdk.Context, addr crypto.Address, infractio // Double sign confirmed logger.Info(fmt.Sprintf("Confirmed double sign from %s at height %d, age of %d less than max age of %d", pubkey.Address(), infractionHeight, age, maxEvidenceAge)) + // 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) + 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, k.SlashFractionDoubleSign(ctx)) + k.validatorSet.Slash(ctx, pubkey, infractionHeight, power, revisedFraction) // Jail validator k.validatorSet.Jail(ctx, pubkey) @@ -76,7 +82,7 @@ func (k Keeper) handleDoubleSign(ctx sdk.Context, addr crypto.Address, infractio 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.ValAddress(addr) + address := sdk.ConsAddress(addr) pubkey, err := k.getPubkey(ctx, addr) if err != nil { panic(fmt.Sprintf("Validator address %v not found", addr)) @@ -169,7 +175,3 @@ func (k Keeper) deleteAddrPubkeyRelation(ctx sdk.Context, addr crypto.Address) { store := ctx.KVStore(k.storeKey) store.Delete(getAddrPubkeyRelationKey(addr)) } - -func getAddrPubkeyRelationKey(address []byte) []byte { - return append([]byte{0x03}, address...) -} diff --git a/x/slashing/keeper_test.go b/x/slashing/keeper_test.go index 3bdb043a82..af15bc2b28 100644 --- a/x/slashing/keeper_test.go +++ b/x/slashing/keeper_test.go @@ -24,13 +24,14 @@ func TestHandleDoubleSign(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(sdk.ValAddress(addr), val, amt)) require.True(t, got.IsOK()) validatorUpdates := stake.EndBlocker(ctx, sk) keeper.AddValidators(ctx, validatorUpdates) - require.Equal(t, ck.GetCoins(ctx, addr), sdk.Coins{{sk.GetParams(ctx).BondDenom, initCoins.Sub(amt)}}) + require.Equal(t, ck.GetCoins(ctx, sdk.AccAddress(addr)), sdk.Coins{{sk.GetParams(ctx).BondDenom, initCoins.Sub(amt)}}) require.True(t, sdk.NewDecFromInt(amt).Equal(sk.Validator(ctx, sdk.ValAddress(addr)).GetPower())) // handle a signature to set signing info @@ -58,12 +59,68 @@ 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 +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)) + require.True(t, got.IsOK()) + validatorUpdates := stake.EndBlocker(ctx, sk) + keeper.AddValidators(ctx, validatorUpdates) + require.Equal(t, ck.GetCoins(ctx, sdk.AccAddress(addr)), sdk.Coins{{sk.GetParams(ctx).BondDenom, initCoins.Sub(amt)}}) + 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) + + // double sign less than max age + keeper.handleDoubleSign(ctx, val.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) + // 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) + // 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) + // 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) + // should be jailed + require.True(t, sk.Validator(ctx, addr).GetJailed()) + // unjail to measure power + sk.Unjail(ctx, val) + // 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()) +} + // Test a validator through uptime, downtime, revocation, // unrevocation, starting height reset, and revocation again func TestHandleAbsentValidator(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) sh := stake.NewHandler(sk) @@ -72,9 +129,9 @@ func TestHandleAbsentValidator(t *testing.T) { require.True(t, got.IsOK()) validatorUpdates := stake.EndBlocker(ctx, sk) keeper.AddValidators(ctx, validatorUpdates) - require.Equal(t, ck.GetCoins(ctx, addr), sdk.Coins{{sk.GetParams(ctx).BondDenom, initCoins.Sub(amt)}}) + require.Equal(t, ck.GetCoins(ctx, sdk.AccAddress(addr)), sdk.Coins{{sk.GetParams(ctx).BondDenom, initCoins.Sub(amt)}}) require.True(t, sdk.NewDecFromInt(amt).Equal(sk.Validator(ctx, sdk.ValAddress(addr)).GetPower())) - info, found := keeper.getValidatorSigningInfo(ctx, sdk.ValAddress(val.Address())) + info, found := keeper.getValidatorSigningInfo(ctx, sdk.ConsAddress(val.Address())) require.False(t, found) require.Equal(t, int64(0), info.StartHeight) require.Equal(t, int64(0), info.IndexOffset) @@ -89,7 +146,7 @@ func TestHandleAbsentValidator(t *testing.T) { ctx = ctx.WithBlockHeight(height) keeper.handleValidatorSignature(ctx, val.Address(), amtInt, true) } - info, found = keeper.getValidatorSigningInfo(ctx, sdk.ValAddress(val.Address())) + info, found = keeper.getValidatorSigningInfo(ctx, sdk.ConsAddress(val.Address())) require.True(t, found) require.Equal(t, int64(0), info.StartHeight) require.Equal(t, keeper.SignedBlocksWindow(ctx), info.SignedBlocksCounter) @@ -99,7 +156,7 @@ func TestHandleAbsentValidator(t *testing.T) { ctx = ctx.WithBlockHeight(height) keeper.handleValidatorSignature(ctx, val.Address(), amtInt, false) } - info, found = keeper.getValidatorSigningInfo(ctx, sdk.ValAddress(val.Address())) + info, found = keeper.getValidatorSigningInfo(ctx, sdk.ConsAddress(val.Address())) require.True(t, found) require.Equal(t, int64(0), info.StartHeight) require.Equal(t, keeper.SignedBlocksWindow(ctx)-keeper.MinSignedPerWindow(ctx), info.SignedBlocksCounter) @@ -113,7 +170,7 @@ func TestHandleAbsentValidator(t *testing.T) { // 501st block missed ctx = ctx.WithBlockHeight(height) keeper.handleValidatorSignature(ctx, val.Address(), amtInt, false) - info, found = keeper.getValidatorSigningInfo(ctx, sdk.ValAddress(val.Address())) + info, found = keeper.getValidatorSigningInfo(ctx, sdk.ConsAddress(val.Address())) require.True(t, found) require.Equal(t, int64(0), info.StartHeight) require.Equal(t, keeper.SignedBlocksWindow(ctx)-keeper.MinSignedPerWindow(ctx)-1, info.SignedBlocksCounter) @@ -141,7 +198,7 @@ func TestHandleAbsentValidator(t *testing.T) { require.Equal(t, int64(amtInt)-slashAmt, pool.BondedTokens.RoundInt64()) // validator start height should have been changed - info, found = keeper.getValidatorSigningInfo(ctx, sdk.ValAddress(val.Address())) + info, found = keeper.getValidatorSigningInfo(ctx, sdk.ConsAddress(val.Address())) require.True(t, found) require.Equal(t, height, info.StartHeight) require.Equal(t, keeper.SignedBlocksWindow(ctx)-keeper.MinSignedPerWindow(ctx)-1, info.SignedBlocksCounter) @@ -182,7 +239,7 @@ func TestHandleNewValidator(t *testing.T) { require.True(t, got.IsOK()) validatorUpdates := stake.EndBlocker(ctx, sk) keeper.AddValidators(ctx, validatorUpdates) - require.Equal(t, ck.GetCoins(ctx, addr), sdk.Coins{{sk.GetParams(ctx).BondDenom, initCoins.SubRaw(amt)}}) + require.Equal(t, ck.GetCoins(ctx, sdk.AccAddress(addr)), sdk.Coins{{sk.GetParams(ctx).BondDenom, initCoins.SubRaw(amt)}}) require.Equal(t, sdk.NewDec(amt), sk.Validator(ctx, sdk.ValAddress(addr)).GetPower()) // 1000 first blocks not a validator @@ -193,7 +250,7 @@ func TestHandleNewValidator(t *testing.T) { ctx = ctx.WithBlockHeight(keeper.SignedBlocksWindow(ctx) + 2) keeper.handleValidatorSignature(ctx, val.Address(), 100, false) - info, found := keeper.getValidatorSigningInfo(ctx, sdk.ValAddress(val.Address())) + info, found := keeper.getValidatorSigningInfo(ctx, sdk.ConsAddress(val.Address())) require.True(t, found) require.Equal(t, int64(keeper.SignedBlocksWindow(ctx)+1), info.StartHeight) require.Equal(t, int64(2), info.IndexOffset) diff --git a/x/slashing/keys.go b/x/slashing/keys.go new file mode 100644 index 0000000000..2af9e069a1 --- /dev/null +++ b/x/slashing/keys.go @@ -0,0 +1,43 @@ +package slashing + +import ( + "encoding/binary" + + sdk "github.com/cosmos/cosmos-sdk/types" +) + +// key prefix bytes +var ( + ValidatorSigningInfoKey = []byte{0x01} // Prefix for signing info + ValidatorSigningBitArrayKey = []byte{0x02} // Prefix for signature bit array + ValidatorSlashingPeriodKey = []byte{0x03} // Prefix for slashing period + AddrPubkeyRelationKey = []byte{0x04} // Prefix for address-pubkey relation +) + +// stored by *Tendermint* address (not owner address) +func GetValidatorSigningInfoKey(v sdk.ConsAddress) []byte { + return append(ValidatorSigningInfoKey, v.Bytes()...) +} + +// stored by *Tendermint* address (not owner address) +func GetValidatorSigningBitArrayKey(v sdk.ConsAddress, i int64) []byte { + b := make([]byte, 8) + binary.LittleEndian.PutUint64(b, uint64(i)) + return append(ValidatorSigningBitArrayKey, append(v.Bytes(), b...)...) +} + +// stored by *Tendermint* address (not owner address) +func GetValidatorSlashingPeriodPrefix(v sdk.ConsAddress) []byte { + return append(ValidatorSlashingPeriodKey, v.Bytes()...) +} + +// stored by *Tendermint* address (not owner address) followed by start height +func GetValidatorSlashingPeriodKey(v sdk.ConsAddress, startHeight int64) []byte { + b := make([]byte, 8) + binary.LittleEndian.PutUint64(b, uint64(startHeight)) + return append(GetValidatorSlashingPeriodPrefix(v), b...) +} + +func getAddrPubkeyRelationKey(address []byte) []byte { + return append(AddrPubkeyRelationKey, address...) +} diff --git a/x/slashing/signing_info.go b/x/slashing/signing_info.go index 25a83e833d..e76fea53f2 100644 --- a/x/slashing/signing_info.go +++ b/x/slashing/signing_info.go @@ -1,7 +1,6 @@ package slashing import ( - "encoding/binary" "fmt" "time" @@ -9,7 +8,7 @@ import ( ) // Stored by *validator* address (not owner address) -func (k Keeper) getValidatorSigningInfo(ctx sdk.Context, address sdk.ValAddress) (info ValidatorSigningInfo, found bool) { +func (k Keeper) getValidatorSigningInfo(ctx sdk.Context, address sdk.ConsAddress) (info ValidatorSigningInfo, found bool) { store := ctx.KVStore(k.storeKey) bz := store.Get(GetValidatorSigningInfoKey(address)) if bz == nil { @@ -22,14 +21,14 @@ func (k Keeper) getValidatorSigningInfo(ctx sdk.Context, address sdk.ValAddress) } // Stored by *validator* address (not owner address) -func (k Keeper) setValidatorSigningInfo(ctx sdk.Context, address sdk.ValAddress, info ValidatorSigningInfo) { +func (k Keeper) setValidatorSigningInfo(ctx sdk.Context, address sdk.ConsAddress, info ValidatorSigningInfo) { store := ctx.KVStore(k.storeKey) bz := k.cdc.MustMarshalBinary(info) store.Set(GetValidatorSigningInfoKey(address), bz) } // Stored by *validator* address (not owner address) -func (k Keeper) getValidatorSigningBitArray(ctx sdk.Context, address sdk.ValAddress, index int64) (signed bool) { +func (k Keeper) getValidatorSigningBitArray(ctx sdk.Context, address sdk.ConsAddress, index int64) (signed bool) { store := ctx.KVStore(k.storeKey) bz := store.Get(GetValidatorSigningBitArrayKey(address, index)) if bz == nil { @@ -42,7 +41,7 @@ func (k Keeper) getValidatorSigningBitArray(ctx sdk.Context, address sdk.ValAddr } // Stored by *validator* address (not owner address) -func (k Keeper) setValidatorSigningBitArray(ctx sdk.Context, address sdk.ValAddress, index int64, signed bool) { +func (k Keeper) setValidatorSigningBitArray(ctx sdk.Context, address sdk.ConsAddress, index int64, signed bool) { store := ctx.KVStore(k.storeKey) bz := k.cdc.MustMarshalBinary(signed) store.Set(GetValidatorSigningBitArrayKey(address, index), bz) @@ -71,15 +70,3 @@ func (i ValidatorSigningInfo) HumanReadableString() string { return fmt.Sprintf("Start height: %d, index offset: %d, jailed until: %v, signed blocks counter: %d", i.StartHeight, i.IndexOffset, i.JailedUntil, i.SignedBlocksCounter) } - -// Stored by *validator* address (not owner address) -func GetValidatorSigningInfoKey(v sdk.ValAddress) []byte { - return append([]byte{0x01}, v.Bytes()...) -} - -// Stored by *validator* address (not owner address) -func GetValidatorSigningBitArrayKey(v sdk.ValAddress, i int64) []byte { - b := make([]byte, 8) - binary.LittleEndian.PutUint64(b, uint64(i)) - return append([]byte{0x02}, append(v.Bytes(), b...)...) -} diff --git a/x/slashing/signing_info_test.go b/x/slashing/signing_info_test.go index f92c43581b..7aff0da95f 100644 --- a/x/slashing/signing_info_test.go +++ b/x/slashing/signing_info_test.go @@ -11,7 +11,7 @@ import ( func TestGetSetValidatorSigningInfo(t *testing.T) { ctx, _, _, _, keeper := createTestInput(t) - info, found := keeper.getValidatorSigningInfo(ctx, sdk.ValAddress(addrs[0])) + info, found := keeper.getValidatorSigningInfo(ctx, sdk.ConsAddress(addrs[0])) require.False(t, found) newInfo := ValidatorSigningInfo{ StartHeight: int64(4), @@ -19,8 +19,8 @@ func TestGetSetValidatorSigningInfo(t *testing.T) { JailedUntil: time.Unix(2, 0), SignedBlocksCounter: int64(10), } - keeper.setValidatorSigningInfo(ctx, sdk.ValAddress(addrs[0]), newInfo) - info, found = keeper.getValidatorSigningInfo(ctx, sdk.ValAddress(addrs[0])) + keeper.setValidatorSigningInfo(ctx, sdk.ConsAddress(addrs[0]), newInfo) + info, found = keeper.getValidatorSigningInfo(ctx, sdk.ConsAddress(addrs[0])) require.True(t, found) require.Equal(t, info.StartHeight, int64(4)) require.Equal(t, info.IndexOffset, int64(3)) @@ -30,9 +30,9 @@ func TestGetSetValidatorSigningInfo(t *testing.T) { func TestGetSetValidatorSigningBitArray(t *testing.T) { ctx, _, _, _, keeper := createTestInput(t) - signed := keeper.getValidatorSigningBitArray(ctx, sdk.ValAddress(addrs[0]), 0) + signed := keeper.getValidatorSigningBitArray(ctx, sdk.ConsAddress(addrs[0]), 0) require.False(t, signed) // treat empty key as unsigned - keeper.setValidatorSigningBitArray(ctx, sdk.ValAddress(addrs[0]), 0, true) - signed = keeper.getValidatorSigningBitArray(ctx, sdk.ValAddress(addrs[0]), 0) + keeper.setValidatorSigningBitArray(ctx, sdk.ConsAddress(addrs[0]), 0, true) + signed = keeper.getValidatorSigningBitArray(ctx, sdk.ConsAddress(addrs[0]), 0) require.True(t, signed) // now should be signed } diff --git a/x/slashing/slashing_period.go b/x/slashing/slashing_period.go new file mode 100644 index 0000000000..61d25071eb --- /dev/null +++ b/x/slashing/slashing_period.go @@ -0,0 +1,107 @@ +package slashing + +import ( + "encoding/binary" + "fmt" + + sdk "github.com/cosmos/cosmos-sdk/types" +) + +// Cap an infraction's slash amount by the slashing period in which it was committed +func (k Keeper) capBySlashingPeriod(ctx sdk.Context, address sdk.ConsAddress, fraction sdk.Dec, infractionHeight int64) (revisedFraction sdk.Dec) { + + // Fetch the newest slashing period starting before this infraction was committed + slashingPeriod := k.getValidatorSlashingPeriodForHeight(ctx, address, infractionHeight) + + // Sanity check + if slashingPeriod.EndHeight > 0 && slashingPeriod.EndHeight < infractionHeight { + panic(fmt.Sprintf("slashing period ended before infraction: infraction height %d, slashing period ended at %d", infractionHeight, slashingPeriod.EndHeight)) + } + + // Calculate the updated total slash amount + // This is capped at the slashing fraction for the worst infraction within this slashing period + totalToSlash := sdk.MaxDec(slashingPeriod.SlashedSoFar, fraction) + + // Calculate the remainder which we now must slash + revisedFraction = totalToSlash.Sub(slashingPeriod.SlashedSoFar) + + // Update the slashing period struct + slashingPeriod.SlashedSoFar = totalToSlash + k.addOrUpdateValidatorSlashingPeriod(ctx, slashingPeriod) + + return +} + +// Stored by validator Tendermint address (not owner address) +// This function retrieves the most recent slashing period starting +// before a particular height - so the slashing period that was "in effect" +// at the time of an infraction committed at that height. +func (k Keeper) getValidatorSlashingPeriodForHeight(ctx sdk.Context, address sdk.ConsAddress, height int64) (slashingPeriod ValidatorSlashingPeriod) { + store := ctx.KVStore(k.storeKey) + // Get the most recent slashing period at or before the infraction height + start := GetValidatorSlashingPeriodPrefix(address) + end := sdk.PrefixEndBytes(GetValidatorSlashingPeriodKey(address, height)) + iterator := store.ReverseIterator(start, end) + if !iterator.Valid() { + panic("expected to find slashing period, but none was found") + } + slashingPeriod = k.unmarshalSlashingPeriodKeyValue(iterator.Key(), iterator.Value()) + return +} + +// Stored by validator Tendermint address (not owner address) +// This function sets a validator slashing period for a particular validator, +// start height, end height, and current slashed-so-far total, or updates +// an existing slashing period for the same validator and start height. +func (k Keeper) addOrUpdateValidatorSlashingPeriod(ctx sdk.Context, slashingPeriod ValidatorSlashingPeriod) { + slashingPeriodValue := ValidatorSlashingPeriodValue{ + EndHeight: slashingPeriod.EndHeight, + SlashedSoFar: slashingPeriod.SlashedSoFar, + } + store := ctx.KVStore(k.storeKey) + bz := k.cdc.MustMarshalBinary(slashingPeriodValue) + store.Set(GetValidatorSlashingPeriodKey(slashingPeriod.ValidatorAddr, slashingPeriod.StartHeight), bz) +} + +// Unmarshal key/value into a ValidatorSlashingPeriod +func (k Keeper) unmarshalSlashingPeriodKeyValue(key []byte, value []byte) ValidatorSlashingPeriod { + var slashingPeriodValue ValidatorSlashingPeriodValue + k.cdc.MustUnmarshalBinary(value, &slashingPeriodValue) + address := sdk.ConsAddress(key[1 : 1+sdk.AddrLen]) + startHeight := int64(binary.LittleEndian.Uint64(key[1+sdk.AddrLen : 1+sdk.AddrLen+8])) + return ValidatorSlashingPeriod{ + ValidatorAddr: address, + StartHeight: startHeight, + EndHeight: slashingPeriodValue.EndHeight, + SlashedSoFar: slashingPeriodValue.SlashedSoFar, + } +} + +// Construct a new `ValidatorSlashingPeriod` struct +func NewValidatorSlashingPeriod(startHeight int64, endHeight int64, slashedSoFar sdk.Dec) ValidatorSlashingPeriod { + return ValidatorSlashingPeriod{ + StartHeight: startHeight, + EndHeight: endHeight, + SlashedSoFar: slashedSoFar, + } +} + +// Slashing period for a validator +type ValidatorSlashingPeriod struct { + ValidatorAddr sdk.ConsAddress `json:"validator_addr"` // validator which this slashing period is for + StartHeight int64 `json:"start_height"` // starting height of the slashing period + EndHeight int64 `json:"end_height"` // ending height of the slashing period, or sentinel value of 0 for in-progress + SlashedSoFar sdk.Dec `json:"slashed_so_far"` // fraction of validator stake slashed so far in this slashing period +} + +// Value part of slashing period (validator address & start height are stored in the key) +type ValidatorSlashingPeriodValue struct { + EndHeight int64 `json:"end_height"` + SlashedSoFar sdk.Dec `json:"slashed_so_far"` +} + +// Return human readable slashing period +func (p ValidatorSlashingPeriod) HumanReadableString() string { + return fmt.Sprintf("Start height: %d, end height: %d, slashed so far: %v", + p.StartHeight, p.EndHeight, p.SlashedSoFar) +} diff --git a/x/slashing/slashing_period_test.go b/x/slashing/slashing_period_test.go new file mode 100644 index 0000000000..54157bb9cc --- /dev/null +++ b/x/slashing/slashing_period_test.go @@ -0,0 +1,86 @@ +package slashing + +import ( + "testing" + + "github.com/stretchr/testify/require" + + sdk "github.com/cosmos/cosmos-sdk/types" +) + +func TestGetSetValidatorSlashingPeriod(t *testing.T) { + ctx, _, _, _, keeper := createTestInput(t) + addr := sdk.ConsAddress(addrs[0]) + height := int64(5) + require.Panics(t, func() { keeper.getValidatorSlashingPeriodForHeight(ctx, addr, height) }) + newPeriod := ValidatorSlashingPeriod{ + ValidatorAddr: addr, + StartHeight: height, + EndHeight: height + 10, + SlashedSoFar: sdk.ZeroDec(), + } + keeper.addOrUpdateValidatorSlashingPeriod(ctx, newPeriod) + + // Get at start height + retrieved := keeper.getValidatorSlashingPeriodForHeight(ctx, addr, height) + require.Equal(t, newPeriod, retrieved) + + // Get after start height (works) + retrieved = keeper.getValidatorSlashingPeriodForHeight(ctx, addr, int64(6)) + require.Equal(t, newPeriod, retrieved) + + // Get before start height (panic) + require.Panics(t, func() { keeper.getValidatorSlashingPeriodForHeight(ctx, addr, int64(0)) }) + + // Get after end height (panic) + newPeriod.EndHeight = int64(4) + keeper.addOrUpdateValidatorSlashingPeriod(ctx, newPeriod) + require.Panics(t, func() { keeper.capBySlashingPeriod(ctx, addr, sdk.ZeroDec(), height) }) + + // Back to old end height + newPeriod.EndHeight = height + 10 + keeper.addOrUpdateValidatorSlashingPeriod(ctx, newPeriod) + + // Set a new, later period + anotherPeriod := ValidatorSlashingPeriod{ + ValidatorAddr: addr, + StartHeight: height + 1, + EndHeight: height + 11, + SlashedSoFar: sdk.ZeroDec(), + } + keeper.addOrUpdateValidatorSlashingPeriod(ctx, anotherPeriod) + + // Old period retrieved for prior height + retrieved = keeper.getValidatorSlashingPeriodForHeight(ctx, addr, height) + require.Equal(t, newPeriod, retrieved) + + // New period retrieved at new height + retrieved = keeper.getValidatorSlashingPeriodForHeight(ctx, addr, height+1) + require.Equal(t, anotherPeriod, retrieved) +} + +func TestValidatorSlashingPeriodCap(t *testing.T) { + ctx, _, _, _, keeper := createTestInput(t) + addr := sdk.ConsAddress(addrs[0]) + height := int64(5) + newPeriod := ValidatorSlashingPeriod{ + ValidatorAddr: addr, + StartHeight: height, + EndHeight: height + 10, + SlashedSoFar: sdk.ZeroDec(), + } + keeper.addOrUpdateValidatorSlashingPeriod(ctx, newPeriod) + half := sdk.NewDec(1).Quo(sdk.NewDec(2)) + + // First slash should be full + fractionA := keeper.capBySlashingPeriod(ctx, addr, half, height) + require.True(t, fractionA.Equal(half)) + + // Second slash should be capped + fractionB := keeper.capBySlashingPeriod(ctx, addr, half, height) + require.True(t, fractionB.Equal(sdk.ZeroDec())) + + // Third slash should be capped to difference + fractionC := keeper.capBySlashingPeriod(ctx, addr, sdk.OneDec(), height) + require.True(t, fractionC.Equal(half)) +} diff --git a/x/slashing/test_common.go b/x/slashing/test_common.go index 6974113100..afbe47d553 100644 --- a/x/slashing/test_common.go +++ b/x/slashing/test_common.go @@ -31,10 +31,10 @@ var ( newPubKey("0B485CFC0EECC619440448436F8FC9DF40566F2369E72400281454CB552AFB51"), newPubKey("0B485CFC0EECC619440448436F8FC9DF40566F2369E72400281454CB552AFB52"), } - addrs = []sdk.AccAddress{ - sdk.AccAddress(pks[0].Address()), - sdk.AccAddress(pks[1].Address()), - sdk.AccAddress(pks[2].Address()), + addrs = []sdk.ValAddress{ + sdk.ValAddress(pks[0].Address()), + sdk.ValAddress(pks[1].Address()), + sdk.ValAddress(pks[2].Address()), } initCoins = sdk.NewInt(200) ) @@ -76,7 +76,7 @@ func createTestInput(t *testing.T) (sdk.Context, bank.Keeper, stake.Keeper, para require.Nil(t, err) for _, addr := range addrs { - _, _, err = ck.AddCoins(ctx, addr, sdk.Coins{ + _, _, err = ck.AddCoins(ctx, sdk.AccAddress(addr), sdk.Coins{ {sk.GetParams(ctx).BondDenom, initCoins}, }) } diff --git a/x/slashing/tick_test.go b/x/slashing/tick_test.go index 9eb956e671..40705cf515 100644 --- a/x/slashing/tick_test.go +++ b/x/slashing/tick_test.go @@ -21,7 +21,7 @@ func TestBeginBlocker(t *testing.T) { require.True(t, got.IsOK()) validatorUpdates := stake.EndBlocker(ctx, sk) keeper.AddValidators(ctx, validatorUpdates) - require.Equal(t, ck.GetCoins(ctx, addr), sdk.Coins{{sk.GetParams(ctx).BondDenom, initCoins.Sub(amt)}}) + require.Equal(t, ck.GetCoins(ctx, sdk.AccAddress(addr)), sdk.Coins{{sk.GetParams(ctx).BondDenom, initCoins.Sub(amt)}}) require.True(t, sdk.NewDecFromInt(amt).Equal(sk.Validator(ctx, sdk.ValAddress(addr)).GetPower())) val := abci.Validator{ @@ -40,7 +40,7 @@ func TestBeginBlocker(t *testing.T) { } BeginBlocker(ctx, req, keeper) - info, found := keeper.getValidatorSigningInfo(ctx, sdk.ValAddress(pk.Address())) + info, found := keeper.getValidatorSigningInfo(ctx, sdk.ConsAddress(pk.Address())) require.True(t, found) require.Equal(t, ctx.BlockHeight(), info.StartHeight) require.Equal(t, int64(1), info.IndexOffset) diff --git a/x/stake/keeper/keeper.go b/x/stake/keeper/keeper.go index 187649c5f7..14c8343878 100644 --- a/x/stake/keeper/keeper.go +++ b/x/stake/keeper/keeper.go @@ -10,9 +10,10 @@ import ( // keeper of the stake store type Keeper struct { - storeKey sdk.StoreKey - cdc *wire.Codec - coinKeeper bank.Keeper + storeKey sdk.StoreKey + cdc *wire.Codec + coinKeeper bank.Keeper + validatorHooks sdk.ValidatorHooks // codespace codespace sdk.CodespaceType @@ -20,14 +21,24 @@ type Keeper struct { func NewKeeper(cdc *wire.Codec, key sdk.StoreKey, ck bank.Keeper, codespace sdk.CodespaceType) Keeper { keeper := Keeper{ - storeKey: key, - cdc: cdc, - coinKeeper: ck, - codespace: codespace, + storeKey: key, + cdc: cdc, + coinKeeper: ck, + validatorHooks: nil, + codespace: codespace, } return keeper } +// Set the validator hooks +func (k Keeper) WithValidatorHooks(v sdk.ValidatorHooks) Keeper { + if k.validatorHooks != nil { + panic("cannot set validator hooks twice") + } + k.validatorHooks = v + return k +} + //_________________________________________________________________________ // return the codespace diff --git a/x/stake/keeper/validator.go b/x/stake/keeper/validator.go index cb225df6cc..0ea24e6396 100644 --- a/x/stake/keeper/validator.go +++ b/x/stake/keeper/validator.go @@ -591,6 +591,13 @@ func (k Keeper) unbondValidator(ctx sdk.Context, validator types.Validator) type // also remove from the Bonded types.Validators Store store.Delete(GetValidatorsBondedIndexKey(validator.Operator)) + + // call the unbond hook if present + if k.validatorHooks != nil { + k.validatorHooks.OnValidatorBeginUnbonding(ctx, validator.ConsAddress()) + } + + // return updated validator return validator } @@ -617,6 +624,12 @@ func (k Keeper) bondValidator(ctx sdk.Context, validator types.Validator) types. bzABCI := k.cdc.MustMarshalBinary(validator.ABCIValidator()) store.Set(GetTendermintUpdatesKey(validator.Operator), bzABCI) + // call the bond hook if present + if k.validatorHooks != nil { + k.validatorHooks.OnValidatorBonded(ctx, validator.ConsAddress()) + } + + // return updated validator return validator } diff --git a/x/stake/types/validator.go b/x/stake/types/validator.go index 2cb952db28..6a53965ca8 100644 --- a/x/stake/types/validator.go +++ b/x/stake/types/validator.go @@ -246,6 +246,11 @@ func (v Validator) Equal(c2 Validator) bool { v.LastBondedTokens.Equal(c2.LastBondedTokens) } +// return the TM validator address +func (v Validator) ConsAddress() sdk.ConsAddress { + return sdk.ConsAddress(v.PubKey.Address()) +} + // constant used in flags to indicate that description field should not be updated const DoNotModifyDesc = "[do-not-modify]" From 07ba719c752fb3a746470fe342044861ee52c1e3 Mon Sep 17 00:00:00 2001 From: rigelrozanski Date: Fri, 31 Aug 2018 20:50:12 -0400 Subject: [PATCH 39/52] minor corrections --- x/slashing/handler_test.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/x/slashing/handler_test.go b/x/slashing/handler_test.go index 2ba8dcc4db..748164ebbe 100644 --- a/x/slashing/handler_test.go +++ b/x/slashing/handler_test.go @@ -50,10 +50,11 @@ func TestJailedValidatorDelegations(t *testing.T) { JailedUntil: time.Unix(0, 0), SignedBlocksCounter: int64(0), } - slashingKeeper.setValidatorSigningInfo(ctx, valAddr, newInfo) + consAddr := sdk.ConsAddress(valAddr.Bytes()) + slashingKeeper.setValidatorSigningInfo(ctx, consAddr, newInfo) // delegate tokens to the validator - delAddr := addrs[1] + delAddr := sdk.AccAddress(addrs[1]) msgDelegate := newTestMsgDelegate(delAddr, valAddr, bondAmount) got = stake.NewHandler(stakeKeeper)(ctx, msgDelegate) require.True(t, got.IsOK(), "expected delegation to be ok, got %v", got) From 9593a2641ae15e93a12df4a70a3500201219a98a Mon Sep 17 00:00:00 2001 From: rigelrozanski Date: Fri, 31 Aug 2018 20:52:45 -0400 Subject: [PATCH 40/52] ... --- x/slashing/handler_test.go | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/x/slashing/handler_test.go b/x/slashing/handler_test.go index 748164ebbe..67139a06ec 100644 --- a/x/slashing/handler_test.go +++ b/x/slashing/handler_test.go @@ -38,7 +38,9 @@ func TestJailedValidatorDelegations(t *testing.T) { // create a validator amount := int64(10) - valAddr, valPubKey, bondAmount := sdk.ValAddress(addrs[0]), pks[0], sdk.NewInt(amount) + valPubKey, bondAmount := pks[0], sdk.NewInt(amount) + consAddr, valAddr := sdk.ConsAddress(addrs[0]), sdk.ValAddress(addrs[1]) + msgCreateVal := newTestMsgCreateValidator(valAddr, valPubKey, bondAmount) got := stake.NewHandler(stakeKeeper)(ctx, msgCreateVal) require.True(t, got.IsOK(), "expected create validator msg to be ok, got: %v", got) @@ -50,11 +52,10 @@ func TestJailedValidatorDelegations(t *testing.T) { JailedUntil: time.Unix(0, 0), SignedBlocksCounter: int64(0), } - consAddr := sdk.ConsAddress(valAddr.Bytes()) slashingKeeper.setValidatorSigningInfo(ctx, consAddr, newInfo) // delegate tokens to the validator - delAddr := sdk.AccAddress(addrs[1]) + delAddr := sdk.AccAddress(addrs[2]) msgDelegate := newTestMsgDelegate(delAddr, valAddr, bondAmount) got = stake.NewHandler(stakeKeeper)(ctx, msgDelegate) require.True(t, got.IsOK(), "expected delegation to be ok, got %v", got) From 6e295085c191a92ed8ac87421bc7071c8701e205 Mon Sep 17 00:00:00 2001 From: rigelrozanski Date: Fri, 31 Aug 2018 20:56:35 -0400 Subject: [PATCH 41/52] ... --- x/slashing/handler_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x/slashing/handler_test.go b/x/slashing/handler_test.go index 67139a06ec..c5afb87389 100644 --- a/x/slashing/handler_test.go +++ b/x/slashing/handler_test.go @@ -39,7 +39,7 @@ func TestJailedValidatorDelegations(t *testing.T) { // create a validator amount := int64(10) valPubKey, bondAmount := pks[0], sdk.NewInt(amount) - consAddr, valAddr := sdk.ConsAddress(addrs[0]), sdk.ValAddress(addrs[1]) + valAddr, consAddr := sdk.ValAddress(addrs[1]), sdk.ConsAddress(addrs[0]) msgCreateVal := newTestMsgCreateValidator(valAddr, valPubKey, bondAmount) got := stake.NewHandler(stakeKeeper)(ctx, msgCreateVal) From b29620953604bf2ce611ec39ca8c76013bd9c71b Mon Sep 17 00:00:00 2001 From: Rigel Date: Fri, 31 Aug 2018 21:23:30 -0400 Subject: [PATCH 42/52] Merge PR #2205: minor develop fix * minor corrections * ... * ... --- x/slashing/handler_test.go | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/x/slashing/handler_test.go b/x/slashing/handler_test.go index 2ba8dcc4db..c5afb87389 100644 --- a/x/slashing/handler_test.go +++ b/x/slashing/handler_test.go @@ -38,7 +38,9 @@ func TestJailedValidatorDelegations(t *testing.T) { // create a validator amount := int64(10) - valAddr, valPubKey, bondAmount := sdk.ValAddress(addrs[0]), pks[0], sdk.NewInt(amount) + valPubKey, bondAmount := pks[0], sdk.NewInt(amount) + valAddr, consAddr := sdk.ValAddress(addrs[1]), sdk.ConsAddress(addrs[0]) + msgCreateVal := newTestMsgCreateValidator(valAddr, valPubKey, bondAmount) got := stake.NewHandler(stakeKeeper)(ctx, msgCreateVal) require.True(t, got.IsOK(), "expected create validator msg to be ok, got: %v", got) @@ -50,10 +52,10 @@ func TestJailedValidatorDelegations(t *testing.T) { JailedUntil: time.Unix(0, 0), SignedBlocksCounter: int64(0), } - slashingKeeper.setValidatorSigningInfo(ctx, valAddr, newInfo) + slashingKeeper.setValidatorSigningInfo(ctx, consAddr, newInfo) // delegate tokens to the validator - delAddr := addrs[1] + delAddr := sdk.AccAddress(addrs[2]) msgDelegate := newTestMsgDelegate(delAddr, valAddr, bondAmount) got = stake.NewHandler(stakeKeeper)(ctx, msgDelegate) require.True(t, got.IsOK(), "expected delegation to be ok, got %v", got) From f0c13bbfbd835d4f05177f84dd1137f3c0d3d510 Mon Sep 17 00:00:00 2001 From: rigelrozanski Date: Fri, 31 Aug 2018 21:38:50 -0400 Subject: [PATCH 43/52] resolve merge errors --- x/stake/keeper/delegation.go | 10 +++++----- x/stake/keeper/delegation_test.go | 30 ++++++++++++++++++------------ 2 files changed, 23 insertions(+), 17 deletions(-) diff --git a/x/stake/keeper/delegation.go b/x/stake/keeper/delegation.go index f6a99f529b..9a596ffbce 100644 --- a/x/stake/keeper/delegation.go +++ b/x/stake/keeper/delegation.go @@ -315,10 +315,10 @@ func (k Keeper) unbond(ctx sdk.Context, delAddr sdk.AccAddress, valAddr sdk.ValA //______________________________________________________________________________________________________ // get info for begin functions: MinTime and CreationHeight -func (k Keeper) getBeginInfo(ctx sdk.Context, params types.Params, validatorSrcAddr sdk.AccAddress) ( +func (k Keeper) getBeginInfo(ctx sdk.Context, params types.Params, valSrcAddr sdk.ValAddress) ( minTime time.Time, height int64, completeNow bool) { - validator, found := k.GetValidator(ctx, validatorSrcAddr) + validator, found := k.GetValidator(ctx, valSrcAddr) switch { case !found || validator.Status == sdk.Bonded: @@ -357,12 +357,12 @@ func (k Keeper) BeginUnbonding(ctx sdk.Context, // create the unbonding delegation params := k.GetParams(ctx) - minTime, height, completeNow := k.getBeginInfo(ctx, params, validatorAddr) + minTime, height, completeNow := k.getBeginInfo(ctx, params, valAddr) balance := sdk.Coin{params.BondDenom, returnAmount.RoundInt()} // no need to create the ubd object just complete now if completeNow { - _, _, err := k.coinKeeper.AddCoins(ctx, delegatorAddr, sdk.Coins{balance}) + _, _, err := k.coinKeeper.AddCoins(ctx, delAddr, sdk.Coins{balance}) if err != nil { return err } @@ -429,7 +429,7 @@ func (k Keeper) BeginRedelegation(ctx sdk.Context, delAddr sdk.AccAddress, } // create the unbonding delegation - minTime, height, completeNow := k.getBeginInfo(ctx, params, validatorSrcAddr) + minTime, height, completeNow := k.getBeginInfo(ctx, params, valSrcAddr) if completeNow { // no need to create the redelegation object return nil diff --git a/x/stake/keeper/delegation_test.go b/x/stake/keeper/delegation_test.go index d0fd736d93..023642bb22 100644 --- a/x/stake/keeper/delegation_test.go +++ b/x/stake/keeper/delegation_test.go @@ -195,7 +195,7 @@ func TestUndelegateSelfDelegation(t *testing.T) { validator = keeper.UpdateValidator(ctx, validator) pool = keeper.GetPool(ctx) selfDelegation := types.Delegation{ - DelegatorAddr: addrVals[0], + DelegatorAddr: sdk.AccAddress(addrVals[0].Bytes()), ValidatorAddr: addrVals[0], Shares: issuedShares, } @@ -214,7 +214,8 @@ func TestUndelegateSelfDelegation(t *testing.T) { } keeper.SetDelegation(ctx, delegation) - err := keeper.BeginUnbonding(ctx, addrVals[0], addrVals[0], sdk.NewDec(10)) + val0AccAddr := sdk.AccAddress(addrVals[0].Bytes()) + err := keeper.BeginUnbonding(ctx, val0AccAddr, addrVals[0], sdk.NewDec(10)) require.NoError(t, err) validator, found := keeper.GetValidator(ctx, addrVals[0]) @@ -237,7 +238,7 @@ func TestUndelegateFromUnbondingValidator(t *testing.T) { validator = keeper.UpdateValidator(ctx, validator) pool = keeper.GetPool(ctx) selfDelegation := types.Delegation{ - DelegatorAddr: addrVals[0], + DelegatorAddr: sdk.AccAddress(addrVals[0].Bytes()), ValidatorAddr: addrVals[0], Shares: issuedShares, } @@ -264,7 +265,8 @@ func TestUndelegateFromUnbondingValidator(t *testing.T) { ctx = ctx.WithBlockHeader(header) // unbond the all self-delegation to put validator in unbonding state - err := keeper.BeginUnbonding(ctx, addrVals[0], addrVals[0], sdk.NewDec(10)) + val0AccAddr := sdk.AccAddress(addrVals[0].Bytes()) + err := keeper.BeginUnbonding(ctx, val0AccAddr, addrVals[0], sdk.NewDec(10)) require.NoError(t, err) validator, found := keeper.GetValidator(ctx, addrVals[0]) @@ -306,8 +308,9 @@ func TestUndelegateFromUnbondedValidator(t *testing.T) { keeper.SetPool(ctx, pool) validator = keeper.UpdateValidator(ctx, validator) pool = keeper.GetPool(ctx) + val0AccAddr := sdk.AccAddress(addrVals[0].Bytes()) selfDelegation := types.Delegation{ - DelegatorAddr: addrVals[0], + DelegatorAddr: val0AccAddr, ValidatorAddr: addrVals[0], Shares: issuedShares, } @@ -334,7 +337,7 @@ func TestUndelegateFromUnbondedValidator(t *testing.T) { ctx = ctx.WithBlockHeader(header) // unbond the all self-delegation to put validator in unbonding state - err := keeper.BeginUnbonding(ctx, addrVals[0], addrVals[0], sdk.NewDec(10)) + err := keeper.BeginUnbonding(ctx, val0AccAddr, addrVals[0], sdk.NewDec(10)) require.NoError(t, err) validator, found := keeper.GetValidator(ctx, addrVals[0]) @@ -453,8 +456,9 @@ func TestRedelegateSelfDelegation(t *testing.T) { keeper.SetPool(ctx, pool) validator = keeper.UpdateValidator(ctx, validator) pool = keeper.GetPool(ctx) + val0AccAddr := sdk.AccAddress(addrVals[0].Bytes()) selfDelegation := types.Delegation{ - DelegatorAddr: addrVals[0], + DelegatorAddr: val0AccAddr, ValidatorAddr: addrVals[0], Shares: issuedShares, } @@ -480,7 +484,7 @@ func TestRedelegateSelfDelegation(t *testing.T) { } keeper.SetDelegation(ctx, delegation) - err := keeper.BeginRedelegation(ctx, addrVals[0], addrVals[0], addrVals[1], sdk.NewDec(10)) + err := keeper.BeginRedelegation(ctx, val0AccAddr, addrVals[0], addrVals[1], sdk.NewDec(10)) require.NoError(t, err) validator, found := keeper.GetValidator(ctx, addrVals[0]) @@ -502,8 +506,9 @@ func TestRedelegateFromUnbondingValidator(t *testing.T) { keeper.SetPool(ctx, pool) validator = keeper.UpdateValidator(ctx, validator) pool = keeper.GetPool(ctx) + val0AccAddr := sdk.AccAddress(addrVals[0].Bytes()) selfDelegation := types.Delegation{ - DelegatorAddr: addrVals[0], + DelegatorAddr: val0AccAddr, ValidatorAddr: addrVals[0], Shares: issuedShares, } @@ -537,7 +542,7 @@ func TestRedelegateFromUnbondingValidator(t *testing.T) { ctx = ctx.WithBlockHeader(header) // unbond the all self-delegation to put validator in unbonding state - err := keeper.BeginUnbonding(ctx, addrVals[0], addrVals[0], sdk.NewDec(10)) + err := keeper.BeginUnbonding(ctx, val0AccAddr, addrVals[0], sdk.NewDec(10)) require.NoError(t, err) validator, found := keeper.GetValidator(ctx, addrVals[0]) @@ -579,8 +584,9 @@ func TestRedelegateFromUnbondedValidator(t *testing.T) { keeper.SetPool(ctx, pool) validator = keeper.UpdateValidator(ctx, validator) pool = keeper.GetPool(ctx) + val0AccAddr := sdk.AccAddress(addrVals[0].Bytes()) selfDelegation := types.Delegation{ - DelegatorAddr: addrVals[0], + DelegatorAddr: val0AccAddr, ValidatorAddr: addrVals[0], Shares: issuedShares, } @@ -614,7 +620,7 @@ func TestRedelegateFromUnbondedValidator(t *testing.T) { ctx = ctx.WithBlockHeader(header) // unbond the all self-delegation to put validator in unbonding state - err := keeper.BeginUnbonding(ctx, addrVals[0], addrVals[0], sdk.NewDec(10)) + err := keeper.BeginUnbonding(ctx, val0AccAddr, addrVals[0], sdk.NewDec(10)) require.NoError(t, err) validator, found := keeper.GetValidator(ctx, addrVals[0]) From 75025720a3f63ae162ebe9ae48aa789b81287958 Mon Sep 17 00:00:00 2001 From: ValarDragon Date: Fri, 31 Aug 2018 21:57:33 -0700 Subject: [PATCH 44/52] simulation: Initialize governance properly --- cmd/gaia/app/sim_test.go | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/cmd/gaia/app/sim_test.go b/cmd/gaia/app/sim_test.go index 38dd352bde..be42511d05 100644 --- a/cmd/gaia/app/sim_test.go +++ b/cmd/gaia/app/sim_test.go @@ -16,6 +16,7 @@ import ( "github.com/cosmos/cosmos-sdk/baseapp" sdk "github.com/cosmos/cosmos-sdk/types" banksim "github.com/cosmos/cosmos-sdk/x/bank/simulation" + "github.com/cosmos/cosmos-sdk/x/gov" govsim "github.com/cosmos/cosmos-sdk/x/gov/simulation" "github.com/cosmos/cosmos-sdk/x/mock/simulation" slashingsim "github.com/cosmos/cosmos-sdk/x/slashing/simulation" @@ -54,7 +55,7 @@ func appStateFn(r *rand.Rand, keys []crypto.PrivKey, accs []sdk.AccAddress) json Coins: coins, }) } - + govGenesis := gov.DefaultGenesisState() // Default genesis state stakeGenesis := stake.DefaultGenesisState() var validators []stake.Validator @@ -78,6 +79,7 @@ func appStateFn(r *rand.Rand, keys []crypto.PrivKey, accs []sdk.AccAddress) json genesis := GenesisState{ Accounts: genesisAccounts, StakeData: stakeGenesis, + GovData: govGenesis, } // Marshal genesis @@ -141,8 +143,8 @@ func BenchmarkFullGaiaSimulation(b *testing.B) { testAndRunTxs(app), []simulation.RandSetup{}, invariants(app), // these shouldn't get ran - 10, - 100, + 210, + blockSize, commit, ) } From efb3b95476c006cb19f82e05e7b905a7f082593d Mon Sep 17 00:00:00 2001 From: LLLeon Date: Sat, 1 Sep 2018 19:14:17 +0800 Subject: [PATCH 45/52] types: fixed the error returned from AccAddress unmarshal --- types/account.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/types/account.go b/types/account.go index 1df600c36e..429cbf2857 100644 --- a/types/account.go +++ b/types/account.go @@ -105,7 +105,7 @@ func (aa *AccAddress) UnmarshalJSON(data []byte) error { var s string err := json.Unmarshal(data, &s) if err != nil { - return nil + return err } aa2, err := AccAddressFromBech32(s) From 3c2100793eb8ba584a5dff16706acee39463b76b Mon Sep 17 00:00:00 2001 From: ValarDragon Date: Sat, 1 Sep 2018 12:32:24 -0700 Subject: [PATCH 46/52] Address @cwgoes comments --- cmd/gaia/app/sim_test.go | 28 ++++++------- x/mock/simulation/random_simulate_blocks.go | 46 +++++++++++---------- 2 files changed, 36 insertions(+), 38 deletions(-) diff --git a/cmd/gaia/app/sim_test.go b/cmd/gaia/app/sim_test.go index be42511d05..68f67709a3 100644 --- a/cmd/gaia/app/sim_test.go +++ b/cmd/gaia/app/sim_test.go @@ -25,13 +25,12 @@ import ( ) var ( - seed int64 - numBlocks int - blockSize int - enabled bool - verbose bool - usegoleveldb bool - commit bool + seed int64 + numBlocks int + blockSize int + enabled bool + verbose bool + commit bool ) func init() { @@ -40,7 +39,6 @@ func init() { flag.IntVar(&blockSize, "SimulationBlockSize", 200, "Operations per block") flag.BoolVar(&enabled, "SimulationEnabled", false, "Enable the simulation") flag.BoolVar(&verbose, "SimulationVerbose", false, "Verbose log output") - flag.BoolVar(&usegoleveldb, "SimulationGoLevelDB", false, "Use GoLevelDB instead of memdb") flag.BoolVar(&commit, "SimulationCommit", false, "Have the simulation commit") } @@ -126,14 +124,12 @@ func BenchmarkFullGaiaSimulation(b *testing.B) { var logger log.Logger logger = log.NewNopLogger() var db dbm.DB - if usegoleveldb { - dir := os.TempDir() - db, _ = dbm.NewGoLevelDB("Simulation", dir) - defer os.RemoveAll(dir) - } else { - db = dbm.NewMemDB() - } - defer db.Close() + dir := os.TempDir() + db, _ = dbm.NewGoLevelDB("Simulation", dir) + defer func() { + db.Close() + os.RemoveAll(dir) + }() app := NewGaiaApp(logger, db, nil) // Run randomized simulation diff --git a/x/mock/simulation/random_simulate_blocks.go b/x/mock/simulation/random_simulate_blocks.go index 42a616331d..be1963a9e8 100644 --- a/x/mock/simulation/random_simulate_blocks.go +++ b/x/mock/simulation/random_simulate_blocks.go @@ -54,11 +54,11 @@ func SimulateFromSeed( tb testing.TB, app *baseapp.BaseApp, appStateFn func(r *rand.Rand, keys []crypto.PrivKey, accs []sdk.AccAddress) json.RawMessage, seed int64, ops []Operation, setups []RandSetup, invariants []Invariant, numBlocks int, blockSize int, commit bool, ) { - testingmode, t, b := getTestingMode(tb) + testingMode, t, b := getTestingMode(tb) log := fmt.Sprintf("Starting SimulateFromSeed with randomness created with seed %d", int(seed)) r := rand.New(rand.NewSource(seed)) timestamp := randTimestamp(r) - log = updateLog(testingmode, log, "Starting the simulation from time %v, unixtime %v", timestamp.UTC().Format(time.UnixDate), timestamp.Unix()) + log = updateLog(testingMode, log, "Starting the simulation from time %v, unixtime %v", timestamp.UTC().Format(time.UnixDate), timestamp.Unix()) fmt.Printf("%s\n", log) timeDiff := maxTimePerBlock - minTimePerBlock @@ -67,7 +67,7 @@ func SimulateFromSeed( // Setup event stats events := make(map[string]uint) event := func(what string) { - log = updateLog(testingmode, log, "event - %s", what) + log = updateLog(testingMode, log, "event - %s", what) events[what]++ } @@ -78,21 +78,24 @@ func SimulateFromSeed( request := abci.RequestBeginBlock{Header: header} - var lastHeaderTime time.Time + var pastTimes []time.Time // These are operations which have been queued by previous operations operationQueue := make(map[int][]Operation) - if !testingmode { + if !testingMode { b.ResetTimer() } - blockSimulator := createBlockSimulator(testingmode, tb, t, event, invariants, ops, operationQueue, numBlocks) + blockSimulator := createBlockSimulator(testingMode, tb, t, event, invariants, ops, operationQueue, numBlocks) for i := 0; i < numBlocks; i++ { + // Log the header time for future lookup + pastTimes = append(pastTimes, header.Time) + // Run the BeginBlock handler app.BeginBlock(request) - log = updateLog(testingmode, log, "BeginBlock") + log = updateLog(testingMode, log, "BeginBlock") - if testingmode { + if testingMode { // Make sure invariants hold at beginning of block AssertAllInvariants(t, app, invariants, log) } @@ -109,11 +112,10 @@ func SimulateFromSeed( res := app.EndBlock(abci.RequestEndBlock{}) header.Height++ - lastHeaderTime = header.Time header.Time = header.Time.Add(time.Duration(minTimePerBlock) * time.Second).Add(time.Duration(int64(r.Intn(int(timeDiff)))) * time.Second) - log = updateLog(testingmode, log, "EndBlock") + log = updateLog(testingMode, log, "EndBlock") - if testingmode { + if testingMode { // Make sure invariants hold at end of block AssertAllInvariants(t, app, invariants, log) } @@ -122,7 +124,7 @@ func SimulateFromSeed( } // Generate a random RequestBeginBlock with the current validator set for the next block - request = RandomRequestBeginBlock(r, validators, livenessTransitionMatrix, evidenceFraction, lastHeaderTime, event, header, log) + request = RandomRequestBeginBlock(r, validators, livenessTransitionMatrix, evidenceFraction, pastTimes, event, header, log) // Update the validator set validators = updateValidators(tb, r, validators, res.ValidatorUpdates, event) @@ -134,19 +136,19 @@ func SimulateFromSeed( // 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 []Operation, operationQueue map[int][]Operation, totalNumBlocks int) func( +func createBlockSimulator(testingMode bool, tb testing.TB, t *testing.T, event func(string), invariants []Invariant, ops []Operation, operationQueue map[int][]Operation, totalNumBlocks int) func( blocksize int, r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, privKeys []crypto.PrivKey, log string, header abci.Header) (updatedLog string, opCount int) { return func(blocksize int, r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, keys []crypto.PrivKey, log string, header abci.Header) (updatedLog string, opCount int) { for j := 0; j < blocksize; j++ { logUpdate, futureOps, err := ops[r.Intn(len(ops))](tb, r, app, ctx, keys, log, event) - log = updateLog(testingmode, log, logUpdate) + log = updateLog(testingMode, log, logUpdate) if err != nil { tb.Fatalf("error on operation %d within block %d, %v, log %s", header.Height, opCount, err, log) } queueOperations(operationQueue, futureOps) - if testingmode { + if testingMode { if onOperation { AssertAllInvariants(t, app, invariants, log) } @@ -160,19 +162,19 @@ func createBlockSimulator(testingmode bool, tb testing.TB, t *testing.T, event f } } -func getTestingMode(tb testing.TB) (testingmode bool, t *testing.T, b *testing.B) { - testingmode = false +func getTestingMode(tb testing.TB) (testingMode bool, t *testing.T, b *testing.B) { + testingMode = false if _t, ok := tb.(*testing.T); ok { t = _t - testingmode = true + testingMode = true } else { b = tb.(*testing.B) } return } -func updateLog(testingmode bool, log string, update string, args ...interface{}) (updatedLog string) { - if testingmode { +func updateLog(testingMode bool, log string, update string, args ...interface{}) (updatedLog string) { + if testingMode { update = fmt.Sprintf(update, args...) return fmt.Sprintf("%s\n%s", log, update) } @@ -240,7 +242,7 @@ func getKeys(validators map[string]mockValidator) []string { // RandomRequestBeginBlock generates a list of signing validators according to the provided list of validators, signing fraction, and evidence fraction func RandomRequestBeginBlock(r *rand.Rand, validators map[string]mockValidator, livenessTransitions TransitionMatrix, evidenceFraction float64, - lastHeaderTime time.Time, event func(string), header abci.Header, log string) abci.RequestBeginBlock { + pastTimes []time.Time, event func(string), header abci.Header, log string) abci.RequestBeginBlock { if len(validators) == 0 { return abci.RequestBeginBlock{Header: header} } @@ -279,7 +281,7 @@ func RandomRequestBeginBlock(r *rand.Rand, validators map[string]mockValidator, time := header.Time if r.Float64() < pastEvidenceFraction { height = int64(r.Intn(int(header.Height))) - time = lastHeaderTime + time = pastTimes[height] } validator := signingValidators[r.Intn(len(signingValidators))].Validator var currentTotalVotingPower int64 From a991a2e1c4b328cb70af86fb5b794a1967813c5a Mon Sep 17 00:00:00 2001 From: ValarDragon Date: Fri, 31 Aug 2018 00:37:25 -0700 Subject: [PATCH 47/52] Improve GetValidator speed In simulation, this was shown to cause a 4x speedup. There are no safety concerns here, as amino encoding is deterministic. --- PENDING.md | 2 +- x/stake/keeper/validator.go | 26 ++++++++++++++++++++++++++ 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/PENDING.md b/PENDING.md index 0aef9fd193..5557a8771e 100644 --- a/PENDING.md +++ b/PENDING.md @@ -78,7 +78,7 @@ IMPROVEMENTS * [x/stake] [#2023](https://github.com/cosmos/cosmos-sdk/pull/2023) Terminate iteration loop in `UpdateBondedValidators` and `UpdateBondedValidatorsFull` when the first revoked validator is encountered and perform a sanity check. * [x/auth] Signature verification's gas cost now accounts for pubkey type. [#2046](https://github.com/tendermint/tendermint/pull/2046) * [x/stake] [x/slashing] Ensure delegation invariants to jailed validators [#1883](https://github.com/cosmos/cosmos-sdk/issues/1883). - + * [x/stake] Improve speed of GetValidator, which was shown to be a performance bottleneck. [#2046](https://github.com/tendermint/tendermint/pull/2200) * SDK * [tools] Make get_vendor_deps deletes `.vendor-new` directories, in case scratch files are present. * [cli] \#1632 Add integration tests to ensure `basecoind init && basecoind` start sequences run successfully for both `democoin` and `basecoin` examples. diff --git a/x/stake/keeper/validator.go b/x/stake/keeper/validator.go index 0ea24e6396..5dc075742e 100644 --- a/x/stake/keeper/validator.go +++ b/x/stake/keeper/validator.go @@ -2,6 +2,7 @@ package keeper import ( "bytes" + "container/list" "fmt" abci "github.com/tendermint/tendermint/abci/types" @@ -11,6 +12,14 @@ import ( "github.com/cosmos/cosmos-sdk/x/stake/types" ) +type cachedValidator struct { + val types.Validator + marshalled string +} + +var validatorCache = make(map[string]cachedValidator, 1000) +var validatorCacheList = list.New() + // get a single validator func (k Keeper) GetValidator(ctx sdk.Context, addr sdk.ValAddress) (validator types.Validator, found bool) { store := ctx.KVStore(k.storeKey) @@ -18,6 +27,23 @@ func (k Keeper) GetValidator(ctx sdk.Context, addr sdk.ValAddress) (validator ty if value == nil { return validator, false } + // return cached validator + strValue := string(value) + if val, ok := validatorCache[strValue]; ok { + valToReturn := val.val + // Doesn't mutate the cache's value + valToReturn.Operator = addr + return valToReturn, true + } else { // get validator from cache + validator = types.MustUnmarshalValidator(k.cdc, addr, value) + cachedVal := cachedValidator{validator, strValue} + validatorCache[strValue] = cachedValidator{validator, strValue} + validatorCacheList.PushBack(cachedVal) + if validatorCacheList.Len() > 500 { + valToRemove := validatorCacheList.Remove(validatorCacheList.Front()).(cachedValidator) + delete(validatorCache, valToRemove.marshalled) + } + } validator = types.MustUnmarshalValidator(k.cdc, addr, value) return validator, true } From 3b4caa5dd2bf218f24fd53616a41aefea79c9f20 Mon Sep 17 00:00:00 2001 From: ValarDragon Date: Fri, 31 Aug 2018 08:29:32 -0700 Subject: [PATCH 48/52] fix lint --- x/stake/keeper/validator.go | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/x/stake/keeper/validator.go b/x/stake/keeper/validator.go index 5dc075742e..a12801dc9e 100644 --- a/x/stake/keeper/validator.go +++ b/x/stake/keeper/validator.go @@ -34,16 +34,17 @@ func (k Keeper) GetValidator(ctx sdk.Context, addr sdk.ValAddress) (validator ty // Doesn't mutate the cache's value valToReturn.Operator = addr return valToReturn, true - } else { // get validator from cache - validator = types.MustUnmarshalValidator(k.cdc, addr, value) - cachedVal := cachedValidator{validator, strValue} - validatorCache[strValue] = cachedValidator{validator, strValue} - validatorCacheList.PushBack(cachedVal) - if validatorCacheList.Len() > 500 { - valToRemove := validatorCacheList.Remove(validatorCacheList.Front()).(cachedValidator) - delete(validatorCache, valToRemove.marshalled) - } } + // get validator from cache + validator = types.MustUnmarshalValidator(k.cdc, addr, value) + cachedVal := cachedValidator{validator, strValue} + validatorCache[strValue] = cachedValidator{validator, strValue} + validatorCacheList.PushBack(cachedVal) + if validatorCacheList.Len() > 500 { + valToRemove := validatorCacheList.Remove(validatorCacheList.Front()).(cachedValidator) + delete(validatorCache, valToRemove.marshalled) + } + validator = types.MustUnmarshalValidator(k.cdc, addr, value) return validator, true } From f29fdcafddd84e1faf290305b840aca1cddd7ec2 Mon Sep 17 00:00:00 2001 From: ValarDragon Date: Sat, 1 Sep 2018 13:21:42 -0700 Subject: [PATCH 49/52] Add comments --- x/stake/keeper/validator.go | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/x/stake/keeper/validator.go b/x/stake/keeper/validator.go index a12801dc9e..d5fba14337 100644 --- a/x/stake/keeper/validator.go +++ b/x/stake/keeper/validator.go @@ -12,12 +12,16 @@ import ( "github.com/cosmos/cosmos-sdk/x/stake/types" ) +// Cache the amino decoding of validators, as it can be the case that repeated slashing calls +// cause many calls to GetValidator, which were shown to throttle the state machine in our +// simulation. Note this is quite biased though, as the simulator does more slashes than a +// live chain should, however we require the slashing to be fast as noone pays gas for it. type cachedValidator struct { val types.Validator marshalled string } -var validatorCache = make(map[string]cachedValidator, 1000) +var validatorCache = make(map[string]cachedValidator, 500) var validatorCacheList = list.New() // get a single validator @@ -27,7 +31,7 @@ func (k Keeper) GetValidator(ctx sdk.Context, addr sdk.ValAddress) (validator ty if value == nil { return validator, false } - // return cached validator + // If these amino encoded bytes are in the cache, return the cached validator strValue := string(value) if val, ok := validatorCache[strValue]; ok { valToReturn := val.val @@ -35,11 +39,12 @@ func (k Keeper) GetValidator(ctx sdk.Context, addr sdk.ValAddress) (validator ty valToReturn.Operator = addr return valToReturn, true } - // get validator from cache + // amino bytes weren't found in cache, so amino unmarshal and add it to the cache validator = types.MustUnmarshalValidator(k.cdc, addr, value) cachedVal := cachedValidator{validator, strValue} validatorCache[strValue] = cachedValidator{validator, strValue} validatorCacheList.PushBack(cachedVal) + // if the cache is too big, pop off the last element from it if validatorCacheList.Len() > 500 { valToRemove := validatorCacheList.Remove(validatorCacheList.Front()).(cachedValidator) delete(validatorCache, valToRemove.marshalled) From 52775f33294eb833bb14d7d2f83e32d84391b696 Mon Sep 17 00:00:00 2001 From: ValarDragon Date: Sat, 1 Sep 2018 13:54:47 -0700 Subject: [PATCH 50/52] Make the slashing logger print a string representation of slashFactor It used to print the decimal number, which has an extra factor of 10**10, and is thus confusing. --- x/stake/keeper/slash.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/x/stake/keeper/slash.go b/x/stake/keeper/slash.go index aa6fe974d8..14ad1551fd 100644 --- a/x/stake/keeper/slash.go +++ b/x/stake/keeper/slash.go @@ -108,8 +108,8 @@ func (k Keeper) Slash(ctx sdk.Context, pubkey crypto.PubKey, infractionHeight in // Log that a slash occurred! logger.Info(fmt.Sprintf( - "Validator %s slashed by slashFactor %v, burned %v tokens", - pubkey.Address(), slashFactor, tokensToBurn)) + "Validator %s slashed by slashFactor %s, burned %v tokens", + pubkey.Address(), slashFactor.String(), tokensToBurn)) // TODO Return event(s), blocked on https://github.com/tendermint/tendermint/pull/1803 return From 8a452b940a38c799ebb432361a0daeeec833fe63 Mon Sep 17 00:00:00 2001 From: ValarDragon Date: Sat, 1 Sep 2018 19:04:44 -0700 Subject: [PATCH 51/52] simulation: display db size at end of simulation, add makefile entries --- Makefile | 11 +++++++++++ PENDING.md | 2 +- cmd/gaia/app/sim_test.go | 14 +++++++++++--- 3 files changed, 23 insertions(+), 4 deletions(-) diff --git a/Makefile b/Makefile index 983234dbc3..e363891e2a 100644 --- a/Makefile +++ b/Makefile @@ -163,6 +163,17 @@ test_sim_gaia_slow: @echo "Running full Gaia simulation. This may take awhile!" @go test ./cmd/gaia/app -run TestFullGaiaSimulation -SimulationEnabled=true -SimulationNumBlocks=1000 -SimulationVerbose=true -v -timeout 24h +SIM_NUM_BLOCKS ?= 210 +SIM_BLOCK_SIZE ?= 200 +SIM_COMMIT ?= true +test_sim_gaia_benchmark: + @echo "Running Gaia benchmark for numBlocks=$(SIM_NUM_BLOCKS), blockSize=$(SIM_BLOCK_SIZE). This may take awhile!" + @go test -benchmem -run=^$$ github.com/cosmos/cosmos-sdk/cmd/gaia/app -bench ^BenchmarkFullGaiaSimulation$$ -SimulationEnabled=true -SimulationNumBlocks=$(SIM_NUM_BLOCKS) -SimulationBlockSize=$(SIM_BLOCK_SIZE) -SimulationCommit=$(SIM_COMMIT) -timeout 24h + +test_sim_gaia_profile: + @echo "Running Gaia benchmark for numBlocks=$(SIM_NUM_BLOCKS), blockSize=$(SIM_BLOCK_SIZE). This may take awhile!" + @go test -benchmem -run=^$$ github.com/cosmos/cosmos-sdk/cmd/gaia/app -bench ^BenchmarkFullGaiaSimulation$$ -SimulationEnabled=true -SimulationNumBlocks=$(SIM_NUM_BLOCKS) -SimulationBlockSize=$(SIM_BLOCK_SIZE) -SimulationCommit=$(SIM_COMMIT) -timeout 24h -cpuprofile cpu.out -memprofile mem.out + test_cover: @bash tests/test_cover.sh diff --git a/PENDING.md b/PENDING.md index 5557a8771e..34e202999d 100644 --- a/PENDING.md +++ b/PENDING.md @@ -58,7 +58,7 @@ FEATURES * SDK * [querier] added custom querier functionality, so ABCI query requests can be handled by keepers * [simulation] \#1924 allow operations to specify future operations - * [simulation] \#1924 Add benchmarking capabilities + * [simulation] \#1924 Add benchmarking capabilities, with makefile commands "test_sim_gaia_benchmark, test_sim_gaia_profile" * Tendermint diff --git a/cmd/gaia/app/sim_test.go b/cmd/gaia/app/sim_test.go index 58d7ca18d3..3878166b3a 100644 --- a/cmd/gaia/app/sim_test.go +++ b/cmd/gaia/app/sim_test.go @@ -3,6 +3,7 @@ package app import ( "encoding/json" "flag" + "fmt" "math/rand" "os" "testing" @@ -118,7 +119,7 @@ func invariants(app *GaiaApp) []simulation.Invariant { } // Profile with: -// /usr/local/go/bin/go test -benchmem -run=^$ github.com/cosmos/cosmos-sdk/cmd/gaia/app -bench ^BenchmarkFullGaiaSimulation$ -cpuprofile cpu.out +// /usr/local/go/bin/go test -benchmem -run=^$ github.com/cosmos/cosmos-sdk/cmd/gaia/app -bench ^BenchmarkFullGaiaSimulation$ -SimulationCommit=true -cpuprofile cpu.out func BenchmarkFullGaiaSimulation(b *testing.B) { // Setup Gaia application var logger log.Logger @@ -139,10 +140,15 @@ func BenchmarkFullGaiaSimulation(b *testing.B) { testAndRunTxs(app), []simulation.RandSetup{}, invariants(app), // these shouldn't get ran - 210, + numBlocks, blockSize, commit, ) + if commit { + fmt.Println("GoLevelDB Stats") + fmt.Println(db.Stats()["leveldb.stats"]) + fmt.Println("GoLevelDB cached block size", db.Stats()["leveldb.cachedblock"]) + } } func TestFullGaiaSimulation(t *testing.T) { @@ -171,7 +177,9 @@ func TestFullGaiaSimulation(t *testing.T) { blockSize, commit, ) - + if commit { + fmt.Println("Database Size", db.Stats()["database.size"]) + } } // TODO: Make another test for the fuzzer itself, which just has noOp txs From 2c66ba0bd4e47a4692f55105b0c405a63b004875 Mon Sep 17 00:00:00 2001 From: rigelrozanski Date: Sun, 2 Sep 2018 15:42:25 -0400 Subject: [PATCH 52/52] extra comment on cache key usage --- x/stake/keeper/validator.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/x/stake/keeper/validator.go b/x/stake/keeper/validator.go index d5fba14337..2d8f947fc0 100644 --- a/x/stake/keeper/validator.go +++ b/x/stake/keeper/validator.go @@ -18,9 +18,10 @@ import ( // live chain should, however we require the slashing to be fast as noone pays gas for it. type cachedValidator struct { val types.Validator - marshalled string + marshalled string // marshalled amino bytes for the validator object (not operator address) } +// validatorCache-key: validator amino bytes var validatorCache = make(map[string]cachedValidator, 500) var validatorCacheList = list.New() @@ -31,6 +32,7 @@ func (k Keeper) GetValidator(ctx sdk.Context, addr sdk.ValAddress) (validator ty if value == nil { return validator, false } + // If these amino encoded bytes are in the cache, return the cached validator strValue := string(value) if val, ok := validatorCache[strValue]; ok { @@ -39,11 +41,13 @@ func (k Keeper) GetValidator(ctx sdk.Context, addr sdk.ValAddress) (validator ty valToReturn.Operator = addr return valToReturn, true } + // amino bytes weren't found in cache, so amino unmarshal and add it to the cache validator = types.MustUnmarshalValidator(k.cdc, addr, value) cachedVal := cachedValidator{validator, strValue} validatorCache[strValue] = cachedValidator{validator, strValue} validatorCacheList.PushBack(cachedVal) + // if the cache is too big, pop off the last element from it if validatorCacheList.Len() > 500 { valToRemove := validatorCacheList.Remove(validatorCacheList.Front()).(cachedValidator)