Merge pull request #1600: Friend can create validator and delegate on behalf of genesis validator

* Added msg and handling for surrogate create validator

* changelog and error fix

* fix changelog

* Remove unnecessary msg by combining into CreateValidator

* Refactor

* Appease linter

* Added onbehalfof functionality in client

* fmt

* Added gaia onbehalfof test

* Update test for onbehalfof validator creation

* fix test

* Fix flag error

* Add app test

* fmt

* Fixed signer for onbehalfof createvalidator

* Fix error msg

* Simplify test

* fmt
This commit is contained in:
Aditya 2018-07-10 17:16:37 -07:00 committed by Rigel
parent 9afb89491c
commit b195c556e2
11 changed files with 180 additions and 53 deletions

View File

@ -89,6 +89,7 @@ FEATURES
* [tests] created a randomized testing framework.
- Currently bank has limited functionality in the framework
- Auth has its invariants checked within the framework
* [x/stake] Allow validator to be created with starting delegation by a third-party delegator on behalf of validator.
* [tests] Add WaitForNextNBlocksTM helper method
* [keys] New keys now have 24 word recovery keys, for heightened security

View File

@ -729,7 +729,7 @@ func doDelegate(t *testing.T, port, seed, name, password string, delegatorAddr,
{
"delegator_addr": "%s",
"validator_addr": "%s",
"bond": { "denom": "%s", "amount": "60" }
"delegation": { "denom": "%s", "amount": "60" }
}
],
"begin_unbondings": [],

View File

@ -94,9 +94,10 @@ func testAddr(addr string) sdk.AccAddress {
func newTestMsgCreateValidator(address sdk.AccAddress, pubKey crypto.PubKey, amt sdk.Int) stake.MsgCreateValidator {
return stake.MsgCreateValidator{
Description: stake.Description{},
ValidatorAddr: address,
PubKey: pubKey,
SelfDelegation: sdk.Coin{"steak", amt},
Description: stake.Description{},
DelegatorAddr: address,
ValidatorAddr: address,
PubKey: pubKey,
Delegation: sdk.Coin{"steak", amt},
}
}

View File

@ -80,7 +80,7 @@ func checkValidator(t *testing.T, mapp *mock.App, keeper Keeper,
addr sdk.AccAddress, expFound bool) Validator {
ctxCheck := mapp.BaseApp.NewContext(true, abci.Header{})
validator, found := keeper.GetValidator(ctxCheck, addr1)
validator, found := keeper.GetValidator(ctxCheck, addr)
require.Equal(t, expFound, found)
return validator
@ -138,6 +138,18 @@ func TestStakeMsgs(t *testing.T) {
require.Equal(t, sdk.Bonded, validator.Status())
require.True(sdk.RatEq(t, sdk.NewRat(10), validator.PoolShares.Bonded()))
// addr1 create validator on behalf of addr2
createValidatorMsgOnBehalfOf := NewMsgCreateValidatorOnBehalfOf(addr1, addr2, priv2.PubKey(), bondCoin, description)
mock.SignCheckDeliver(t, mApp.BaseApp, []sdk.Msg{createValidatorMsgOnBehalfOf}, []int64{0, 1}, []int64{1, 0}, true, priv1, priv2)
mock.CheckBalance(t, mApp, addr1, sdk.Coins{genCoin.Minus(bondCoin).Minus(bondCoin)})
mApp.BeginBlock(abci.RequestBeginBlock{})
validator = checkValidator(t, mApp, keeper, addr2, true)
require.Equal(t, addr2, validator.Owner)
require.Equal(t, sdk.Bonded, validator.Status())
require.True(sdk.RatEq(t, sdk.NewRat(10), validator.PoolShares.Bonded()))
// check the bond that should have been created as well
checkDelegation(t, mApp, keeper, addr1, addr1, true, sdk.NewRat(10))
@ -145,7 +157,7 @@ func TestStakeMsgs(t *testing.T) {
description = NewDescription("bar_moniker", "", "", "")
editValidatorMsg := NewMsgEditValidator(addr1, description)
mock.SignCheckDeliver(t, mApp.BaseApp, []sdk.Msg{editValidatorMsg}, []int64{0}, []int64{1}, true, priv1)
mock.SignCheckDeliver(t, mApp.BaseApp, []sdk.Msg{editValidatorMsg}, []int64{0}, []int64{2}, true, priv1)
validator = checkValidator(t, mApp, keeper, addr1, true)
require.Equal(t, description, validator.Description)
@ -153,13 +165,13 @@ func TestStakeMsgs(t *testing.T) {
mock.CheckBalance(t, mApp, addr2, sdk.Coins{genCoin})
delegateMsg := NewMsgDelegate(addr2, addr1, bondCoin)
mock.SignCheckDeliver(t, mApp.BaseApp, []sdk.Msg{delegateMsg}, []int64{1}, []int64{0}, true, priv2)
mock.SignCheckDeliver(t, mApp.BaseApp, []sdk.Msg{delegateMsg}, []int64{1}, []int64{1}, true, priv2)
mock.CheckBalance(t, mApp, addr2, sdk.Coins{genCoin.Minus(bondCoin)})
checkDelegation(t, mApp, keeper, addr2, addr1, true, sdk.NewRat(10))
// begin unbonding
beginUnbondingMsg := NewMsgBeginUnbonding(addr2, addr1, sdk.NewRat(10))
mock.SignCheckDeliver(t, mApp.BaseApp, []sdk.Msg{beginUnbondingMsg}, []int64{1}, []int64{1}, true, priv2)
mock.SignCheckDeliver(t, mApp.BaseApp, []sdk.Msg{beginUnbondingMsg}, []int64{1}, []int64{2}, true, priv2)
// delegation should exist anymore
checkDelegation(t, mApp, keeper, addr2, addr1, false, sdk.Rat{})

View File

@ -50,7 +50,17 @@ func GetCmdCreateValidator(cdc *wire.Codec) *cobra.Command {
Website: viper.GetString(FlagWebsite),
Details: viper.GetString(FlagDetails),
}
msg := stake.NewMsgCreateValidator(validatorAddr, pk, amount, description)
var msg sdk.Msg
if viper.GetString(FlagAddressDelegator) != "" {
delegatorAddr, err := sdk.AccAddressFromBech32(viper.GetString(FlagAddressDelegator))
if err != nil {
return err
}
msg = stake.NewMsgCreateValidatorOnBehalfOf(delegatorAddr, validatorAddr, pk, amount, description)
} else {
msg = stake.NewMsgCreateValidator(validatorAddr, pk, amount, description)
}
// build and sign the transaction, then broadcast to Tendermint
err = ctx.EnsureSignBuildBroadcast(ctx.FromAddressName, []sdk.Msg{msg}, cdc)
@ -65,6 +75,7 @@ func GetCmdCreateValidator(cdc *wire.Codec) *cobra.Command {
cmd.Flags().AddFlagSet(fsAmount)
cmd.Flags().AddFlagSet(fsDescription)
cmd.Flags().AddFlagSet(fsValidator)
cmd.Flags().AddFlagSet(fsDelegator)
return cmd
}

View File

@ -27,7 +27,7 @@ func registerTxRoutes(ctx context.CoreContext, r *mux.Router, cdc *wire.Codec, k
type msgDelegationsInput struct {
DelegatorAddr string `json:"delegator_addr"` // in bech32
ValidatorAddr string `json:"validator_addr"` // in bech32
Bond sdk.Coin `json:"bond"`
Delegation sdk.Coin `json:"delegation"`
}
type msgBeginRedelegateInput struct {
DelegatorAddr string `json:"delegator_addr"` // in bech32
@ -119,7 +119,7 @@ func editDelegationsRequestHandlerFn(cdc *wire.Codec, kb keys.Keybase, ctx conte
messages[i] = stake.MsgDelegate{
DelegatorAddr: delegatorAddr,
ValidatorAddr: validatorAddr,
Bond: msg.Bond,
Delegation: msg.Delegation,
}
i++
}

View File

@ -71,7 +71,7 @@ func handleMsgCreateValidator(ctx sdk.Context, msg types.MsgCreateValidator, k k
if found {
return ErrValidatorPubKeyExists(k.Codespace()).Result()
}
if msg.SelfDelegation.Denom != k.GetParams(ctx).BondDenom {
if msg.Delegation.Denom != k.GetParams(ctx).BondDenom {
return ErrBadDenom(k.Codespace()).Result()
}
@ -81,7 +81,7 @@ func handleMsgCreateValidator(ctx sdk.Context, msg types.MsgCreateValidator, k k
// move coins from the msg.Address account to a (self-delegation) delegator account
// the validator account and global shares are updated within here
_, err := k.Delegate(ctx, msg.ValidatorAddr, msg.SelfDelegation, validator)
_, err := k.Delegate(ctx, msg.DelegatorAddr, msg.Delegation, validator)
if err != nil {
return err.Result()
}
@ -130,13 +130,13 @@ func handleMsgDelegate(ctx sdk.Context, msg types.MsgDelegate, k keeper.Keeper)
if !found {
return ErrNoValidatorFound(k.Codespace()).Result()
}
if msg.Bond.Denom != k.GetParams(ctx).BondDenom {
if msg.Delegation.Denom != k.GetParams(ctx).BondDenom {
return ErrBadDenom(k.Codespace()).Result()
}
if validator.Revoked == true {
return ErrValidatorRevoked(k.Codespace()).Result()
}
_, err := k.Delegate(ctx, msg.DelegatorAddr, msg.Bond, validator)
_, err := k.Delegate(ctx, msg.DelegatorAddr, msg.Delegation, validator)
if err != nil {
return err.Result()
}

View File

@ -16,19 +16,24 @@ import (
//______________________________________________________________________
func newTestMsgCreateValidator(address sdk.AccAddress, pubKey crypto.PubKey, amt int64) MsgCreateValidator {
return MsgCreateValidator{
Description: Description{},
ValidatorAddr: address,
PubKey: pubKey,
SelfDelegation: sdk.Coin{"steak", sdk.NewInt(amt)},
}
return types.NewMsgCreateValidator(address, pubKey, sdk.Coin{"steak", sdk.NewInt(amt)}, Description{})
}
func newTestMsgDelegate(delegatorAddr, validatorAddr sdk.AccAddress, amt int64) MsgDelegate {
return MsgDelegate{
DelegatorAddr: delegatorAddr,
ValidatorAddr: validatorAddr,
Bond: sdk.Coin{"steak", sdk.NewInt(amt)},
Delegation: sdk.Coin{"steak", sdk.NewInt(amt)},
}
}
func newTestMsgCreateValidatorOnBehalfOf(delegatorAddr, validatorAddr sdk.AccAddress, valPubKey crypto.PubKey, amt int64) MsgCreateValidator {
return MsgCreateValidator{
Description: Description{},
DelegatorAddr: delegatorAddr,
ValidatorAddr: validatorAddr,
PubKey: valPubKey,
Delegation: sdk.Coin{"steak", sdk.NewInt(amt)},
}
}
@ -160,6 +165,32 @@ func TestDuplicatesMsgCreateValidator(t *testing.T) {
assert.Equal(t, Description{}, validator.Description)
}
func TestDuplicatesMsgCreateValidatorOnBehalfOf(t *testing.T) {
ctx, _, keeper := keep.CreateTestInput(t, false, 1000)
validatorAddr := keep.Addrs[0]
delegatorAddr := keep.Addrs[1]
pk := keep.PKs[0]
msgCreateValidatorOnBehalfOf := newTestMsgCreateValidatorOnBehalfOf(delegatorAddr, validatorAddr, pk, 10)
got := handleMsgCreateValidator(ctx, msgCreateValidatorOnBehalfOf, keeper)
require.True(t, got.IsOK(), "%v", got)
validator, found := keeper.GetValidator(ctx, validatorAddr)
require.True(t, found)
require.Equal(t, sdk.Bonded, validator.Status())
require.Equal(t, validatorAddr, validator.Owner)
require.Equal(t, pk, validator.PubKey)
require.Equal(t, sdk.NewRat(10), validator.PoolShares.Bonded())
require.Equal(t, sdk.NewRat(10), validator.DelegatorShares)
require.Equal(t, Description{}, validator.Description)
// one validator cannot be created twice even from different delegator
msgCreateValidatorOnBehalfOf.DelegatorAddr = keep.Addrs[2]
msgCreateValidatorOnBehalfOf.PubKey = keep.PKs[1]
got = handleMsgCreateValidator(ctx, msgCreateValidatorOnBehalfOf, keeper)
require.False(t, got.IsOK(), "%v", got)
}
func TestIncrementsMsgDelegate(t *testing.T) {
initBond := int64(1000)
ctx, accMapper, keeper := keep.CreateTestInput(t, false, initBond)
@ -329,11 +360,12 @@ func TestMultipleMsgCreateValidator(t *testing.T) {
params := setInstantUnbondPeriod(keeper, ctx)
validatorAddrs := []sdk.AccAddress{keep.Addrs[0], keep.Addrs[1], keep.Addrs[2]}
delegatorAddrs := []sdk.AccAddress{keep.Addrs[3], keep.Addrs[4], keep.Addrs[5]}
// bond them all
for i, validatorAddr := range validatorAddrs {
msgCreateValidator := newTestMsgCreateValidator(validatorAddr, keep.PKs[i], 10)
got := handleMsgCreateValidator(ctx, msgCreateValidator, keeper)
msgCreateValidatorOnBehalfOf := newTestMsgCreateValidatorOnBehalfOf(delegatorAddrs[i], validatorAddr, keep.PKs[i], 10)
got := handleMsgCreateValidator(ctx, msgCreateValidatorOnBehalfOf, keeper)
require.True(t, got.IsOK(), "expected msg %d to be ok, got %v", i, got)
//Check that the account is bonded
@ -341,18 +373,18 @@ func TestMultipleMsgCreateValidator(t *testing.T) {
require.Equal(t, (i + 1), len(validators))
val := validators[i]
balanceExpd := sdk.NewInt(initBond - 10)
balanceGot := accMapper.GetAccount(ctx, val.Owner).GetCoins().AmountOf(params.BondDenom)
balanceGot := accMapper.GetAccount(ctx, delegatorAddrs[i]).GetCoins().AmountOf(params.BondDenom)
require.Equal(t, i+1, len(validators), "expected %d validators got %d, validators: %v", i+1, len(validators), validators)
require.Equal(t, 10, int(val.DelegatorShares.RoundInt64()), "expected %d shares, got %d", 10, val.DelegatorShares)
require.Equal(t, balanceExpd, balanceGot, "expected account to have %d, got %d", balanceExpd, balanceGot)
}
// unbond them all
// unbond them all by revoking delegation
for i, validatorAddr := range validatorAddrs {
validatorPre, found := keeper.GetValidator(ctx, validatorAddr)
_, found := keeper.GetValidator(ctx, validatorAddr)
require.True(t, found)
msgBeginUnbonding := NewMsgBeginUnbonding(validatorAddr, validatorAddr, sdk.NewRat(10)) // self-delegation
msgCompleteUnbonding := NewMsgCompleteUnbonding(validatorAddr, validatorAddr)
msgBeginUnbonding := NewMsgBeginUnbonding(delegatorAddrs[i], validatorAddr, sdk.NewRat(10)) // remove delegation
msgCompleteUnbonding := NewMsgCompleteUnbonding(delegatorAddrs[i], validatorAddr)
got := handleMsgBeginUnbonding(ctx, msgBeginUnbonding, keeper)
require.True(t, got.IsOK(), "expected msg %d to be ok, got %v", i, got)
got = handleMsgCompleteUnbonding(ctx, msgCompleteUnbonding, keeper)
@ -367,7 +399,7 @@ func TestMultipleMsgCreateValidator(t *testing.T) {
require.False(t, found)
expBalance := sdk.NewInt(initBond)
gotBalance := accMapper.GetAccount(ctx, validatorPre.Owner).GetCoins().AmountOf(params.BondDenom)
gotBalance := accMapper.GetAccount(ctx, delegatorAddrs[i]).GetCoins().AmountOf(params.BondDenom)
require.Equal(t, expBalance, gotBalance, "expected account to have %d, got %d", expBalance, gotBalance)
}
}

View File

@ -71,13 +71,14 @@ var (
DefaultGenesisState = types.DefaultGenesisState
RegisterWire = types.RegisterWire
NewMsgCreateValidator = types.NewMsgCreateValidator
NewMsgEditValidator = types.NewMsgEditValidator
NewMsgDelegate = types.NewMsgDelegate
NewMsgBeginUnbonding = types.NewMsgBeginUnbonding
NewMsgCompleteUnbonding = types.NewMsgCompleteUnbonding
NewMsgBeginRedelegate = types.NewMsgBeginRedelegate
NewMsgCompleteRedelegate = types.NewMsgCompleteRedelegate
NewMsgCreateValidator = types.NewMsgCreateValidator
NewMsgCreateValidatorOnBehalfOf = types.NewMsgCreateValidatorOnBehalfOf
NewMsgEditValidator = types.NewMsgEditValidator
NewMsgDelegate = types.NewMsgDelegate
NewMsgBeginUnbonding = types.NewMsgBeginUnbonding
NewMsgCompleteUnbonding = types.NewMsgCompleteUnbonding
NewMsgBeginRedelegate = types.NewMsgBeginRedelegate
NewMsgCompleteRedelegate = types.NewMsgCompleteRedelegate
)
const (

View File

@ -2,6 +2,7 @@ package types
import (
"math"
"reflect"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/tendermint/tendermint/crypto"
@ -27,38 +28,63 @@ var maximumBondingRationalDenominator sdk.Int = sdk.NewInt(int64(math.Pow10(MaxB
// MsgCreateValidator - struct for unbonding transactions
type MsgCreateValidator struct {
Description
ValidatorAddr sdk.AccAddress `json:"address"`
PubKey crypto.PubKey `json:"pubkey"`
SelfDelegation sdk.Coin `json:"self_delegation"`
DelegatorAddr sdk.AccAddress `json:"delegator_address"`
ValidatorAddr sdk.AccAddress `json:"validator_address"`
PubKey crypto.PubKey `json:"pubkey"`
Delegation sdk.Coin `json:"delegation"`
}
// Default way to create validator. Delegator address and validator address are the same
func NewMsgCreateValidator(validatorAddr sdk.AccAddress, pubkey crypto.PubKey,
selfDelegation sdk.Coin, description Description) MsgCreateValidator {
return MsgCreateValidator{
Description: description,
ValidatorAddr: validatorAddr,
PubKey: pubkey,
SelfDelegation: selfDelegation,
Description: description,
DelegatorAddr: validatorAddr,
ValidatorAddr: validatorAddr,
PubKey: pubkey,
Delegation: selfDelegation,
}
}
// Creates validator msg by delegator address on behalf of validator address
func NewMsgCreateValidatorOnBehalfOf(delegatorAddr, validatorAddr sdk.AccAddress, pubkey crypto.PubKey,
delegation sdk.Coin, description Description) MsgCreateValidator {
return MsgCreateValidator{
Description: description,
DelegatorAddr: delegatorAddr,
ValidatorAddr: validatorAddr,
PubKey: pubkey,
Delegation: delegation,
}
}
//nolint
func (msg MsgCreateValidator) Type() string { return MsgType }
// Return address(es) that must sign over msg.GetSignBytes()
func (msg MsgCreateValidator) GetSigners() []sdk.AccAddress {
return []sdk.AccAddress{msg.ValidatorAddr}
// delegator is first signer so delegator pays fees
addrs := []sdk.AccAddress{msg.DelegatorAddr}
if !reflect.DeepEqual(msg.DelegatorAddr, msg.ValidatorAddr) {
// if validator addr is not same as delegator addr, validator must sign msg as well
addrs = append(addrs, msg.ValidatorAddr)
}
return addrs
}
// get the bytes for the message signer to sign on
func (msg MsgCreateValidator) GetSignBytes() []byte {
b, err := MsgCdc.MarshalJSON(struct {
Description
ValidatorAddr sdk.AccAddress `json:"address"`
DelegatorAddr sdk.AccAddress `json:"delegator_address"`
ValidatorAddr sdk.AccAddress `json:"validator_address"`
PubKey string `json:"pubkey"`
Bond sdk.Coin `json:"bond"`
Delegation sdk.Coin `json:"delegation"`
}{
Description: msg.Description,
ValidatorAddr: msg.ValidatorAddr,
PubKey: sdk.MustBech32ifyValPub(msg.PubKey),
Delegation: msg.Delegation,
})
if err != nil {
panic(err)
@ -68,10 +94,13 @@ func (msg MsgCreateValidator) GetSignBytes() []byte {
// quick validity check
func (msg MsgCreateValidator) ValidateBasic() sdk.Error {
if msg.DelegatorAddr == nil {
return ErrNilDelegatorAddr(DefaultCodespace)
}
if msg.ValidatorAddr == nil {
return ErrNilValidatorAddr(DefaultCodespace)
}
if !(msg.SelfDelegation.Amount.GT(sdk.ZeroInt())) {
if !(msg.Delegation.Amount.GT(sdk.ZeroInt())) {
return ErrBadDelegationAmount(DefaultCodespace)
}
empty := Description{}
@ -135,14 +164,14 @@ func (msg MsgEditValidator) ValidateBasic() sdk.Error {
type MsgDelegate struct {
DelegatorAddr sdk.AccAddress `json:"delegator_addr"`
ValidatorAddr sdk.AccAddress `json:"validator_addr"`
Bond sdk.Coin `json:"bond"`
Delegation sdk.Coin `json:"delegation"`
}
func NewMsgDelegate(delegatorAddr, validatorAddr sdk.AccAddress, bond sdk.Coin) MsgDelegate {
func NewMsgDelegate(delegatorAddr, validatorAddr sdk.AccAddress, delegation sdk.Coin) MsgDelegate {
return MsgDelegate{
DelegatorAddr: delegatorAddr,
ValidatorAddr: validatorAddr,
Bond: bond,
Delegation: delegation,
}
}
@ -169,7 +198,7 @@ func (msg MsgDelegate) ValidateBasic() sdk.Error {
if msg.ValidatorAddr == nil {
return ErrNilValidatorAddr(DefaultCodespace)
}
if !(msg.Bond.Amount.GT(sdk.ZeroInt())) {
if !(msg.Delegation.Amount.GT(sdk.ZeroInt())) {
return ErrBadDelegationAmount(DefaultCodespace)
}
return nil

View File

@ -69,6 +69,46 @@ func TestMsgEditValidator(t *testing.T) {
}
}
// test ValidateBasic and GetSigners for MsgCreateValidatorOnBehalfOf
func TestMsgCreateValidatorOnBehalfOf(t *testing.T) {
tests := []struct {
name, moniker, identity, website, details string
delegatorAddr sdk.AccAddress
validatorAddr sdk.AccAddress
validatorPubKey crypto.PubKey
bond sdk.Coin
expectPass bool
}{
{"basic good", "a", "b", "c", "d", addr1, addr2, pk2, coinPos, true},
{"partial description", "", "", "c", "", addr1, addr2, pk2, coinPos, true},
{"empty description", "", "", "", "", addr1, addr2, pk2, coinPos, false},
{"empty delegator address", "a", "b", "c", "d", emptyAddr, addr2, pk2, coinPos, false},
{"empty validator address", "a", "b", "c", "d", addr1, emptyAddr, pk2, coinPos, false},
{"empty pubkey", "a", "b", "c", "d", addr1, addr2, emptyPubkey, coinPos, true},
{"empty bond", "a", "b", "c", "d", addr1, addr2, pk2, coinZero, false},
{"negative bond", "a", "b", "c", "d", addr1, addr2, pk2, coinNeg, false},
{"negative bond", "a", "b", "c", "d", addr1, addr2, pk2, coinNeg, false},
}
for _, tc := range tests {
description := NewDescription(tc.moniker, tc.identity, tc.website, tc.details)
msg := NewMsgCreateValidatorOnBehalfOf(tc.delegatorAddr, tc.validatorAddr, tc.validatorPubKey, tc.bond, description)
if tc.expectPass {
require.Nil(t, msg.ValidateBasic(), "test: %v", tc.name)
} else {
require.NotNil(t, msg.ValidateBasic(), "test: %v", tc.name)
}
}
msg := NewMsgCreateValidator(addr1, pk1, coinPos, Description{})
addrs := msg.GetSigners()
require.Equal(t, []sdk.AccAddress{addr1}, addrs, "Signers on default msg is wrong")
msg = NewMsgCreateValidatorOnBehalfOf(addr2, addr1, pk1, coinPos, Description{})
addrs = msg.GetSigners()
require.Equal(t, []sdk.AccAddress{addr2, addr1}, addrs, "Signers for onbehalfof msg is wrong")
}
// test ValidateBasic for MsgDelegate
func TestMsgDelegate(t *testing.T) {
tests := []struct {