From 0c92477b4dc9bd3f704850ea6cbdc23996c7817d Mon Sep 17 00:00:00 2001 From: mossid Date: Tue, 9 Oct 2018 22:23:22 +0900 Subject: [PATCH 01/40] mv types/lib/* store/ --- {types/lib => store}/linear_test.go | 70 ++++---- store/list.go | 108 ++++++++++++ store/queue.go | 88 ++++++++++ types/lib/linear.go | 254 ---------------------------- 4 files changed, 232 insertions(+), 288 deletions(-) rename {types/lib => store}/linear_test.go (64%) create mode 100644 store/list.go create mode 100644 store/queue.go delete mode 100644 types/lib/linear.go diff --git a/types/lib/linear_test.go b/store/linear_test.go similarity index 64% rename from types/lib/linear_test.go rename to store/linear_test.go index d19c04061..c8153a041 100644 --- a/types/lib/linear_test.go +++ b/store/linear_test.go @@ -1,7 +1,7 @@ -package lib +package store import ( - "fmt" + // "math/rand" "testing" "github.com/stretchr/testify/require" @@ -12,7 +12,6 @@ import ( abci "github.com/tendermint/tendermint/abci/types" "github.com/cosmos/cosmos-sdk/codec" - "github.com/cosmos/cosmos-sdk/store" sdk "github.com/cosmos/cosmos-sdk/types" ) @@ -23,7 +22,7 @@ type S struct { func defaultComponents(key sdk.StoreKey) (sdk.Context, *codec.Codec) { db := dbm.NewMemDB() - cms := store.NewCommitMultiStore(db) + cms := NewCommitMultiStore(db) cms.MountStoreWithDB(key, sdk.StoreTypeIAVL, db) cms.LoadLatestVersion() ctx := sdk.NewContext(cms, abci.Header{}, false, log.NewNopLogger()) @@ -31,23 +30,11 @@ func defaultComponents(key sdk.StoreKey) (sdk.Context, *codec.Codec) { return ctx, cdc } -func TestNewLinear(t *testing.T) { - cdc := codec.New() - require.NotPanics(t, func() { NewLinear(cdc, nil, nil) }) - require.NotPanics(t, func() { NewLinear(cdc, nil, DefaultLinearKeys()) }) - require.NotPanics(t, func() { NewLinear(cdc, nil, &LinearKeys{[]byte{0xAA}, []byte{0xBB}, []byte{0xCC}}) }) - - require.Panics(t, func() { NewLinear(cdc, nil, &LinearKeys{nil, nil, nil}) }) - require.Panics(t, func() { NewLinear(cdc, nil, &LinearKeys{[]byte{0xAA}, nil, nil}) }) - require.Panics(t, func() { NewLinear(cdc, nil, &LinearKeys{nil, []byte{0xBB}, nil}) }) - require.Panics(t, func() { NewLinear(cdc, nil, &LinearKeys{nil, nil, []byte{0xCC}}) }) -} - func TestList(t *testing.T) { key := sdk.NewKVStoreKey("test") ctx, cdc := defaultComponents(key) store := ctx.KVStore(key) - lm := NewList(cdc, store, nil) + lm := NewList(cdc, store) val := S{1, true} var res S @@ -94,7 +81,7 @@ func TestQueue(t *testing.T) { ctx, cdc := defaultComponents(key) store := ctx.KVStore(key) - qm := NewQueue(cdc, store, nil) + qm := NewQueue(cdc, store) val := S{1, true} var res S @@ -125,20 +112,14 @@ func TestQueue(t *testing.T) { require.True(t, qm.IsEmpty()) } -func TestOptions(t *testing.T) { +func TestKeys(t *testing.T) { key := sdk.NewKVStoreKey("test") ctx, cdc := defaultComponents(key) store := ctx.KVStore(key) - - keys := &LinearKeys{ - LengthKey: []byte{0xDE, 0xAD}, - ElemKey: []byte{0xBE, 0xEF}, - TopKey: []byte{0x12, 0x34}, - } - linear := NewLinear(cdc, store, keys) + queue := NewQueue(cdc, store) for i := 0; i < 10; i++ { - linear.Push(i) + queue.Push(i) } var len uint64 @@ -147,23 +128,44 @@ func TestOptions(t *testing.T) { var actual int // Checking keys.LengthKey - err := cdc.UnmarshalBinary(store.Get(keys.LengthKey), &len) + err := cdc.UnmarshalBinary(store.Get(LengthKey()), &len) require.Nil(t, err) - require.Equal(t, len, linear.Len()) + require.Equal(t, len, queue.List.Len()) // Checking keys.ElemKey for i := 0; i < 10; i++ { - linear.Get(uint64(i), &expected) - bz := store.Get(append(keys.ElemKey, []byte(fmt.Sprintf("%020d", i))...)) + queue.List.Get(uint64(i), &expected) + bz := store.Get(ElemKey(uint64(i))) err = cdc.UnmarshalBinary(bz, &actual) require.Nil(t, err) require.Equal(t, expected, actual) } - linear.Pop() + queue.Pop() - err = cdc.UnmarshalBinary(store.Get(keys.TopKey), &top) + err = cdc.UnmarshalBinary(store.Get(TopKey()), &top) require.Nil(t, err) - require.Equal(t, top, linear.getTop()) + require.Equal(t, top, queue.getTop()) } + +/* +func TestListRandom(t *testing.T) { + key := sdk.NewKVStoreKey("test") + ctx, cdc := defaultComponents(key) + store := ctx.KVStore(key) + list := NewList(cdc, store) + mocklist := []uint32{} + + for i := 0; i < 10000; { + limit := rand.Int31() % 1000 + for j := int32(0); j < limit; j++ { + item := rand.Uint32() + list.Push(item) + mocklist = append(mocklist, item) + } + + require.Equal() + } +} +*/ diff --git a/store/list.go b/store/list.go new file mode 100644 index 000000000..9905b0d6f --- /dev/null +++ b/store/list.go @@ -0,0 +1,108 @@ +package store + +import ( + "fmt" + "strconv" + + "github.com/cosmos/cosmos-sdk/codec" + sdk "github.com/cosmos/cosmos-sdk/types" +) + +// Key for the length of the list +func LengthKey() []byte { + return []byte{0x00} +} + +// Key for the elements of the list +func ElemKey(index uint64) []byte { + return append([]byte{0x01}, []byte(fmt.Sprintf("%020d", index))...) +} + +// List defines an integer indexable mapper +// It panics when the element type cannot be (un/)marshalled by the codec +type List struct { + cdc *codec.Codec + store sdk.KVStore +} + +// NewList constructs new List +func NewList(cdc *codec.Codec, store sdk.KVStore) List { + return List{ + cdc: cdc, + store: store, + } +} + +// Len() returns the length of the list +// The length is only increased by Push() and not decreased +// List dosen't check if an index is in bounds +// The user should check Len() before doing any actions +func (m List) Len() (res uint64) { + bz := m.store.Get(LengthKey()) + if bz == nil { + return 0 + } + m.cdc.MustUnmarshalBinary(bz, &res) + return +} + +// Get() returns the element by its index +func (m List) Get(index uint64, ptr interface{}) error { + bz := m.store.Get(ElemKey(index)) + return m.cdc.UnmarshalBinary(bz, ptr) +} + +// Set() stores the element to the given position +// Setting element out of range will break length counting +// Use Push() instead of Set() to append a new element +func (m List) Set(index uint64, value interface{}) { + bz := m.cdc.MustMarshalBinary(value) + m.store.Set(ElemKey(index), bz) +} + +// Delete() deletes the element in the given position +// Other elements' indices are preserved after deletion +// Panics when the index is out of range +func (m List) Delete(index uint64) { + m.store.Delete(ElemKey(index)) +} + +// Push() inserts the element to the end of the list +// It will increase the length when it is called +func (m List) Push(value interface{}) { + length := m.Len() + m.Set(length, value) + m.store.Set(LengthKey(), m.cdc.MustMarshalBinary(length+1)) +} + +// Iterate() is used to iterate over all existing elements in the list +// Return true in the continuation to break +// The second element of the continuation will indicate the position of the element +// Using it with Get() will return the same one with the provided element + +// CONTRACT: No writes may happen within a domain while iterating over it. +func (m List) Iterate(ptr interface{}, fn func(uint64) bool) { + iter := sdk.KVStorePrefixIterator(m.store, []byte{0x01}) + for ; iter.Valid(); iter.Next() { + v := iter.Value() + m.cdc.MustUnmarshalBinary(v, ptr) + k := iter.Key() + s := string(k[len(k)-20:]) + index, err := strconv.ParseUint(s, 10, 64) + if err != nil { + panic(err) + } + if fn(index) { + break + } + } + + iter.Close() +} + +func subspace(prefix []byte) (start, end []byte) { + end = make([]byte, len(prefix)) + copy(end, prefix) + end[len(end)-1]++ + return prefix, end +} diff --git a/store/queue.go b/store/queue.go new file mode 100644 index 000000000..4081f63b3 --- /dev/null +++ b/store/queue.go @@ -0,0 +1,88 @@ +package store + +import ( + "github.com/cosmos/cosmos-sdk/codec" + sdk "github.com/cosmos/cosmos-sdk/types" +) + +// Key for the top element position in the queue +func TopKey() []byte { + return []byte{0x02} +} + +// Queue is a List wrapper that provides queue-like functions +// It panics when the element type cannot be (un/)marshalled by the codec +type Queue struct { + List List +} + +// NewQueue constructs new Queue +func NewQueue(cdc *codec.Codec, store sdk.KVStore) Queue { + return Queue{NewList(cdc, store)} +} + +func (m Queue) getTop() (res uint64) { + bz := m.List.store.Get(TopKey()) + if bz == nil { + return 0 + } + + m.List.cdc.MustUnmarshalBinary(bz, &res) + return +} + +func (m Queue) setTop(top uint64) { + bz := m.List.cdc.MustMarshalBinary(top) + m.List.store.Set(TopKey(), bz) +} + +// Push() inserts the elements to the rear of the queue +func (m Queue) Push(value interface{}) { + m.List.Push(value) +} + +// Popping/Peeking on an empty queue will cause panic +// The user should check IsEmpty() before doing any actions +// Peek() returns the element at the front of the queue without removing it +func (m Queue) Peek(ptr interface{}) error { + top := m.getTop() + return m.List.Get(top, ptr) +} + +// Pop() returns the element at the front of the queue and removes it +func (m Queue) Pop() { + top := m.getTop() + m.List.Delete(top) + m.setTop(top + 1) +} + +// IsEmpty() checks if the queue is empty +func (m Queue) IsEmpty() bool { + top := m.getTop() + length := m.List.Len() + return top >= length +} + +// Flush() removes elements it processed +// Return true in the continuation to break +// The interface{} is unmarshalled before the continuation is called +// Starts from the top(head) of the queue +// CONTRACT: Pop() or Push() should not be performed while flushing +func (m Queue) Flush(ptr interface{}, fn func() bool) { + top := m.getTop() + length := m.List.Len() + + var i uint64 + for i = top; i < length; i++ { + err := m.List.Get(i, ptr) + if err != nil { + // TODO: Handle with #870 + panic(err) + } + m.List.Delete(i) + if fn() { + break + } + } + m.setTop(i) +} diff --git a/types/lib/linear.go b/types/lib/linear.go deleted file mode 100644 index 1c25f4eb4..000000000 --- a/types/lib/linear.go +++ /dev/null @@ -1,254 +0,0 @@ -package lib - -import ( - "fmt" - "strconv" - - "github.com/cosmos/cosmos-sdk/codec" - sdk "github.com/cosmos/cosmos-sdk/types" -) - -// Linear defines a primitive mapper type -type Linear struct { - cdc *codec.Codec - store sdk.KVStore - keys *LinearKeys -} - -// LinearKeys defines keysions for the key bytes -type LinearKeys struct { - LengthKey []byte - ElemKey []byte - TopKey []byte -} - -// Should never be modified -var cachedDefaultLinearKeys = DefaultLinearKeys() - -// DefaultLinearKeys returns the default setting of LinearOption -func DefaultLinearKeys() *LinearKeys { - keys := LinearKeys{ - LengthKey: []byte{0x00}, - ElemKey: []byte{0x01}, - TopKey: []byte{0x02}, - } - return &keys -} - -// NewLinear constructs new Linear -func NewLinear(cdc *codec.Codec, store sdk.KVStore, keys *LinearKeys) Linear { - if keys == nil { - keys = cachedDefaultLinearKeys - } - if keys.LengthKey == nil || keys.ElemKey == nil || keys.TopKey == nil { - panic("Invalid LinearKeys") - } - return Linear{ - cdc: cdc, - store: store, - keys: keys, - } -} - -// List is a Linear interface that provides list-like functions -// It panics when the element type cannot be (un/)marshalled by the codec -type List interface { - - // Len() returns the length of the list - // The length is only increased by Push() and not decreased - // List dosen't check if an index is in bounds - // The user should check Len() before doing any actions - Len() uint64 - - // Get() returns the element by its index - Get(uint64, interface{}) error - - // Set() stores the element to the given position - // Setting element out of range will break length counting - // Use Push() instead of Set() to append a new element - Set(uint64, interface{}) - - // Delete() deletes the element in the given position - // Other elements' indices are preserved after deletion - // Panics when the index is out of range - Delete(uint64) - - // Push() inserts the element to the end of the list - // It will increase the length when it is called - Push(interface{}) - - // Iterate*() is used to iterate over all existing elements in the list - // Return true in the continuation to break - // The second element of the continuation will indicate the position of the element - // Using it with Get() will return the same one with the provided element - - // CONTRACT: No writes may happen within a domain while iterating over it. - Iterate(interface{}, func(uint64) bool) -} - -// NewList constructs new List -func NewList(cdc *codec.Codec, store sdk.KVStore, keys *LinearKeys) List { - return NewLinear(cdc, store, keys) -} - -// Key for the length of the list -func (m Linear) LengthKey() []byte { - return m.keys.LengthKey -} - -// Key for the elements of the list -func (m Linear) ElemKey(index uint64) []byte { - return append(m.keys.ElemKey, []byte(fmt.Sprintf("%020d", index))...) -} - -// Len implements List -func (m Linear) Len() (res uint64) { - bz := m.store.Get(m.LengthKey()) - if bz == nil { - return 0 - } - m.cdc.MustUnmarshalBinary(bz, &res) - return -} - -// Get implements List -func (m Linear) Get(index uint64, ptr interface{}) error { - bz := m.store.Get(m.ElemKey(index)) - return m.cdc.UnmarshalBinary(bz, ptr) -} - -// Set implements List -func (m Linear) Set(index uint64, value interface{}) { - bz := m.cdc.MustMarshalBinary(value) - m.store.Set(m.ElemKey(index), bz) -} - -// Delete implements List -func (m Linear) Delete(index uint64) { - m.store.Delete(m.ElemKey(index)) -} - -// Push implements List -func (m Linear) Push(value interface{}) { - length := m.Len() - m.Set(length, value) - m.store.Set(m.LengthKey(), m.cdc.MustMarshalBinary(length+1)) -} - -// IterateRead implements List -func (m Linear) Iterate(ptr interface{}, fn func(uint64) bool) { - iter := sdk.KVStorePrefixIterator(m.store, []byte{0x01}) - for ; iter.Valid(); iter.Next() { - v := iter.Value() - m.cdc.MustUnmarshalBinary(v, ptr) - k := iter.Key() - s := string(k[len(k)-20:]) - index, err := strconv.ParseUint(s, 10, 64) - if err != nil { - panic(err) - } - if fn(index) { - break - } - } - - iter.Close() -} - -// Queue is a Linear interface that provides queue-like functions -// It panics when the element type cannot be (un/)marshalled by the codec -type Queue interface { - // Push() inserts the elements to the rear of the queue - Push(interface{}) - - // Popping/Peeking on an empty queue will cause panic - // The user should check IsEmpty() before doing any actions - - // Peek() returns the element at the front of the queue without removing it - Peek(interface{}) error - - // Pop() returns the element at the front of the queue and removes it - Pop() - - // IsEmpty() checks if the queue is empty - IsEmpty() bool - - // Flush() removes elements it processed - // Return true in the continuation to break - // The interface{} is unmarshalled before the continuation is called - // Starts from the top(head) of the queue - // CONTRACT: Pop() or Push() should not be performed while flushing - Flush(interface{}, func() bool) -} - -// NewQueue constructs new Queue -func NewQueue(cdc *codec.Codec, store sdk.KVStore, keys *LinearKeys) Queue { - return NewLinear(cdc, store, keys) -} - -// Key for the top element position in the queue -func (m Linear) TopKey() []byte { - return m.keys.TopKey -} - -func (m Linear) getTop() (res uint64) { - bz := m.store.Get(m.TopKey()) - if bz == nil { - return 0 - } - - m.cdc.MustUnmarshalBinary(bz, &res) - return -} - -func (m Linear) setTop(top uint64) { - bz := m.cdc.MustMarshalBinary(top) - m.store.Set(m.TopKey(), bz) -} - -// Peek implements Queue -func (m Linear) Peek(ptr interface{}) error { - top := m.getTop() - return m.Get(top, ptr) -} - -// Pop implements Queue -func (m Linear) Pop() { - top := m.getTop() - m.Delete(top) - m.setTop(top + 1) -} - -// IsEmpty implements Queue -func (m Linear) IsEmpty() bool { - top := m.getTop() - length := m.Len() - return top >= length -} - -// Flush implements Queue -func (m Linear) Flush(ptr interface{}, fn func() bool) { - top := m.getTop() - length := m.Len() - - var i uint64 - for i = top; i < length; i++ { - err := m.Get(i, ptr) - if err != nil { - // TODO: Handle with #870 - panic(err) - } - m.Delete(i) - if fn() { - break - } - } - m.setTop(i) -} - -func subspace(prefix []byte) (start, end []byte) { - end = make([]byte, len(prefix)) - copy(end, prefix) - end[len(end)-1]++ - return prefix, end -} From f6e92df3fae1a281624f62b9d97794dcb72861f6 Mon Sep 17 00:00:00 2001 From: Christopher Goes Date: Thu, 11 Oct 2018 22:48:00 +0200 Subject: [PATCH 02/40] Create signing info struct on validator bond hook --- x/slashing/hooks.go | 16 +++++++++++++++- x/slashing/keeper.go | 3 +-- x/slashing/keeper_test.go | 17 +++++++---------- x/slashing/test_common.go | 1 + 4 files changed, 24 insertions(+), 13 deletions(-) diff --git a/x/slashing/hooks.go b/x/slashing/hooks.go index 701a6b2cd..a2e6c506a 100644 --- a/x/slashing/hooks.go +++ b/x/slashing/hooks.go @@ -1,11 +1,25 @@ package slashing import ( + "time" + 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) { + // Create a new signing info if necessary + _, found := k.getValidatorSigningInfo(ctx, address) + if !found { + signingInfo := ValidatorSigningInfo{ + StartHeight: ctx.BlockHeight(), + IndexOffset: 0, + JailedUntil: time.Unix(0, 0), + SignedBlocksCounter: 0, + } + k.setValidatorSigningInfo(ctx, address, signingInfo) + } + + // Create a new slashing period when a validator is bonded slashingPeriod := ValidatorSlashingPeriod{ ValidatorAddr: address, StartHeight: ctx.BlockHeight(), diff --git a/x/slashing/keeper.go b/x/slashing/keeper.go index 94dd9f0d0..9cd7076cb 100644 --- a/x/slashing/keeper.go +++ b/x/slashing/keeper.go @@ -101,8 +101,7 @@ func (k Keeper) handleValidatorSignature(ctx sdk.Context, addr crypto.Address, p // Will use the 0-value default signing info if not present, except for start height signInfo, found := k.getValidatorSigningInfo(ctx, consAddr) if !found { - // If this validator has never been seen before, construct a new SigningInfo with the correct start height - signInfo = NewValidatorSigningInfo(height, 0, time.Unix(0, 0), 0) + panic(fmt.Sprintf("Expected signing info for validator %s but not found", consAddr)) } index := signInfo.IndexOffset % k.SignedBlocksWindow(ctx) signInfo.IndexOffset++ diff --git a/x/slashing/keeper_test.go b/x/slashing/keeper_test.go index cf67af192..23a3ae2de 100644 --- a/x/slashing/keeper_test.go +++ b/x/slashing/keeper_test.go @@ -28,7 +28,6 @@ func TestHandleDoubleSign(t *testing.T) { ctx, ck, sk, _, keeper := createTestInput(t) // validator added pre-genesis ctx = ctx.WithBlockHeight(-1) - sk = sk.WithHooks(keeper.Hooks()) amtInt := int64(100) operatorAddr, val, amt := addrs[0], pks[0], sdk.NewInt(amtInt) got := stake.NewHandler(sk)(ctx, newTestMsgCreateValidator(operatorAddr, val, amt)) @@ -69,7 +68,6 @@ func TestSlashingPeriodCap(t *testing.T) { // initial setup ctx, ck, sk, _, keeper := createTestInput(t) - sk = sk.WithHooks(keeper.Hooks()) amtInt := int64(100) operatorAddr, amt := addrs[0], sdk.NewInt(amtInt) valConsPubKey, valConsAddr := pks[0], pks[0].Address() @@ -135,7 +133,6 @@ func TestHandleAbsentValidator(t *testing.T) { // initial setup ctx, ck, sk, _, keeper := createTestInput(t) - sk = sk.WithHooks(keeper.Hooks()) amtInt := int64(100) addr, val, amt := addrs[0], pks[0], sdk.NewInt(amtInt) sh := stake.NewHandler(sk) @@ -146,14 +143,12 @@ func TestHandleAbsentValidator(t *testing.T) { 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())) + // will exist since the validator has been bonded info, found := keeper.getValidatorSigningInfo(ctx, sdk.ConsAddress(val.Address())) - require.False(t, found) + require.True(t, found) require.Equal(t, int64(0), info.StartHeight) require.Equal(t, int64(0), info.IndexOffset) require.Equal(t, int64(0), info.SignedBlocksCounter) - // default time.Time value - var blankTime time.Time - require.Equal(t, blankTime, info.JailedUntil) height := int64(0) // 1000 first blocks OK @@ -292,6 +287,11 @@ func TestHandleNewValidator(t *testing.T) { ctx, ck, sk, _, keeper := createTestInput(t) addr, val, amt := addrs[0], pks[0], int64(100) sh := stake.NewHandler(sk) + + // 1000 first blocks not a validator + ctx = ctx.WithBlockHeight(keeper.SignedBlocksWindow(ctx) + 1) + + // Validator created got := sh(ctx, newTestMsgCreateValidator(addr, val, sdk.NewInt(amt))) require.True(t, got.IsOK()) validatorUpdates := stake.EndBlocker(ctx, sk) @@ -299,9 +299,6 @@ func TestHandleNewValidator(t *testing.T) { 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, addr).GetPower()) - // 1000 first blocks not a validator - ctx = ctx.WithBlockHeight(keeper.SignedBlocksWindow(ctx) + 1) - // Now a validator, for two blocks keeper.handleValidatorSignature(ctx, val.Address(), 100, true) ctx = ctx.WithBlockHeight(keeper.SignedBlocksWindow(ctx) + 2) diff --git a/x/slashing/test_common.go b/x/slashing/test_common.go index 7c97a8537..25140c824 100644 --- a/x/slashing/test_common.go +++ b/x/slashing/test_common.go @@ -84,6 +84,7 @@ func createTestInput(t *testing.T) (sdk.Context, bank.Keeper, stake.Keeper, para } require.Nil(t, err) keeper := NewKeeper(cdc, keySlashing, sk, params.Getter(), DefaultCodespace) + sk = sk.WithHooks(keeper.Hooks()) return ctx, ck, sk, params.Setter(), keeper } From b55f29f72d48355e848af75db0475b749dab8686 Mon Sep 17 00:00:00 2001 From: Christopher Goes Date: Thu, 11 Oct 2018 22:58:20 +0200 Subject: [PATCH 03/40] Update PENDING.md --- PENDING.md | 1 + 1 file changed, 1 insertion(+) diff --git a/PENDING.md b/PENDING.md index dbae0cc2c..1ced472c7 100644 --- a/PENDING.md +++ b/PENDING.md @@ -69,6 +69,7 @@ BREAKING CHANGES * [x/staking] \#2244 staking now holds a consensus-address-index instead of a consensus-pubkey-index * [x/staking] \#2236 more distribution hooks for distribution * [x/stake] \#2394 Split up UpdateValidator into distinct state transitions applied only in EndBlock + * [x/slashing] \#2480 Fix signing info handling bugs & faulty slashing * Tendermint * Update tendermint version from v0.23.0 to v0.25.0, notable changes From 5fd7297e259865452f16eadba4249086165aae72 Mon Sep 17 00:00:00 2001 From: Christopher Goes Date: Fri, 12 Oct 2018 00:04:44 +0200 Subject: [PATCH 04/40] Count missed blocks instead of signed blocks --- x/slashing/handler_test.go | 2 +- x/slashing/hooks.go | 2 +- x/slashing/keeper.go | 24 +++++++++++++----------- x/slashing/keeper_test.go | 14 +++++++------- x/slashing/signing_info.go | 16 ++++++++-------- x/slashing/signing_info_test.go | 12 ++++++------ x/slashing/tick_test.go | 2 +- 7 files changed, 37 insertions(+), 35 deletions(-) diff --git a/x/slashing/handler_test.go b/x/slashing/handler_test.go index 059b19059..79092aebb 100644 --- a/x/slashing/handler_test.go +++ b/x/slashing/handler_test.go @@ -53,7 +53,7 @@ func TestJailedValidatorDelegations(t *testing.T) { StartHeight: int64(0), IndexOffset: int64(0), JailedUntil: time.Unix(0, 0), - SignedBlocksCounter: int64(0), + MissedBlocksCounter: int64(0), } slashingKeeper.setValidatorSigningInfo(ctx, consAddr, newInfo) diff --git a/x/slashing/hooks.go b/x/slashing/hooks.go index a2e6c506a..808260830 100644 --- a/x/slashing/hooks.go +++ b/x/slashing/hooks.go @@ -14,7 +14,7 @@ func (k Keeper) onValidatorBonded(ctx sdk.Context, address sdk.ConsAddress) { StartHeight: ctx.BlockHeight(), IndexOffset: 0, JailedUntil: time.Unix(0, 0), - SignedBlocksCounter: 0, + MissedBlocksCounter: 0, } k.setValidatorSigningInfo(ctx, address, signingInfo) } diff --git a/x/slashing/keeper.go b/x/slashing/keeper.go index 9cd7076cb..23e30fe1e 100644 --- a/x/slashing/keeper.go +++ b/x/slashing/keeper.go @@ -110,23 +110,25 @@ func (k Keeper) handleValidatorSignature(ctx sdk.Context, addr crypto.Address, p // This counter just tracks the sum of the bit array // That way we avoid needing to read/write the whole array each time previous := k.getValidatorSigningBitArray(ctx, consAddr, index) - if previous == signed { + missed := !signed + if previous == missed { // Array value at this index has not changed, no need to update counter - } else if previous && !signed { - // Array value has changed from signed to unsigned, decrement counter - k.setValidatorSigningBitArray(ctx, consAddr, index, false) - signInfo.SignedBlocksCounter-- - } else if !previous && signed { - // Array value has changed from unsigned to signed, increment counter + } else if !previous && missed { + // Array value has changed from not missed to missed, increment counter k.setValidatorSigningBitArray(ctx, consAddr, index, true) - signInfo.SignedBlocksCounter++ + signInfo.MissedBlocksCounter++ + } else if previous && !missed { + // Array value has changed from missed to not missed, decrement counter + k.setValidatorSigningBitArray(ctx, consAddr, index, false) + signInfo.MissedBlocksCounter-- } - if !signed { - logger.Info(fmt.Sprintf("Absent validator %s at height %d, %d signed, threshold %d", addr, height, signInfo.SignedBlocksCounter, k.MinSignedPerWindow(ctx))) + if missed { + logger.Info(fmt.Sprintf("Absent validator %s at height %d, %d missed, threshold %d", addr, height, signInfo.MissedBlocksCounter, k.MinSignedPerWindow(ctx))) } minHeight := signInfo.StartHeight + k.SignedBlocksWindow(ctx) - if height > minHeight && signInfo.SignedBlocksCounter < k.MinSignedPerWindow(ctx) { + maxMissed := k.SignedBlocksWindow(ctx) - k.MinSignedPerWindow(ctx) + if height > minHeight && signInfo.MissedBlocksCounter > maxMissed { validator := k.validatorSet.ValidatorByConsAddr(ctx, consAddr) if validator != nil && !validator.GetJailed() { // Downtime confirmed: slash and jail the validator diff --git a/x/slashing/keeper_test.go b/x/slashing/keeper_test.go index 23a3ae2de..32469b6d3 100644 --- a/x/slashing/keeper_test.go +++ b/x/slashing/keeper_test.go @@ -148,7 +148,7 @@ func TestHandleAbsentValidator(t *testing.T) { require.True(t, found) require.Equal(t, int64(0), info.StartHeight) require.Equal(t, int64(0), info.IndexOffset) - require.Equal(t, int64(0), info.SignedBlocksCounter) + require.Equal(t, int64(0), info.MissedBlocksCounter) height := int64(0) // 1000 first blocks OK @@ -159,7 +159,7 @@ func TestHandleAbsentValidator(t *testing.T) { 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) + require.Equal(t, int64(0), info.MissedBlocksCounter) // 500 blocks missed for ; height < keeper.SignedBlocksWindow(ctx)+(keeper.SignedBlocksWindow(ctx)-keeper.MinSignedPerWindow(ctx)); height++ { @@ -169,7 +169,7 @@ func TestHandleAbsentValidator(t *testing.T) { 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) + require.Equal(t, keeper.SignedBlocksWindow(ctx)-keeper.MinSignedPerWindow(ctx), info.MissedBlocksCounter) // validator should be bonded still validator, _ := sk.GetValidatorByConsAddr(ctx, sdk.GetConsAddress(val)) @@ -183,7 +183,7 @@ func TestHandleAbsentValidator(t *testing.T) { 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) + require.Equal(t, keeper.SignedBlocksWindow(ctx)-keeper.MinSignedPerWindow(ctx)+1, info.MissedBlocksCounter) // end block stake.EndBlocker(ctx, sk) @@ -204,7 +204,7 @@ func TestHandleAbsentValidator(t *testing.T) { 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)-2, info.SignedBlocksCounter) + require.Equal(t, keeper.SignedBlocksWindow(ctx)-keeper.MinSignedPerWindow(ctx)+2, info.MissedBlocksCounter) // end block stake.EndBlocker(ctx, sk) @@ -246,7 +246,7 @@ func TestHandleAbsentValidator(t *testing.T) { require.True(t, found) require.Equal(t, height, info.StartHeight) // we've missed 2 blocks more than the maximum - require.Equal(t, keeper.SignedBlocksWindow(ctx)-keeper.MinSignedPerWindow(ctx)-2, info.SignedBlocksCounter) + require.Equal(t, keeper.SignedBlocksWindow(ctx)-keeper.MinSignedPerWindow(ctx)+2, info.MissedBlocksCounter) // validator should not be immediately jailed again height++ @@ -308,7 +308,7 @@ func TestHandleNewValidator(t *testing.T) { require.True(t, found) require.Equal(t, keeper.SignedBlocksWindow(ctx)+1, info.StartHeight) require.Equal(t, int64(2), info.IndexOffset) - require.Equal(t, int64(1), info.SignedBlocksCounter) + require.Equal(t, int64(1), info.MissedBlocksCounter) require.Equal(t, time.Unix(0, 0).UTC(), info.JailedUntil) // validator should be bonded still, should not have been jailed or slashed diff --git a/x/slashing/signing_info.go b/x/slashing/signing_info.go index 1adf49abc..64f47838a 100644 --- a/x/slashing/signing_info.go +++ b/x/slashing/signing_info.go @@ -48,25 +48,25 @@ func (k Keeper) setValidatorSigningBitArray(ctx sdk.Context, address sdk.ConsAdd } // Construct a new `ValidatorSigningInfo` struct -func NewValidatorSigningInfo(startHeight int64, indexOffset int64, jailedUntil time.Time, signedBlocksCounter int64) ValidatorSigningInfo { +func NewValidatorSigningInfo(startHeight int64, indexOffset int64, jailedUntil time.Time, missedBlocksCounter int64) ValidatorSigningInfo { return ValidatorSigningInfo{ StartHeight: startHeight, IndexOffset: indexOffset, JailedUntil: jailedUntil, - SignedBlocksCounter: signedBlocksCounter, + MissedBlocksCounter: missedBlocksCounter, } } // Signing info for a validator type ValidatorSigningInfo struct { - StartHeight int64 `json:"start_height"` // height at which validator was first a candidate OR was unjailed - IndexOffset int64 `json:"index_offset"` // index offset into signed block bit array - JailedUntil time.Time `json:"jailed_until"` // timestamp validator cannot be unjailed until - SignedBlocksCounter int64 `json:"signed_blocks_counter"` // signed blocks counter (to avoid scanning the array every time) + StartHeight int64 `json:"start_height"` // height at which validator was first a candidate OR was unjailed + IndexOffset int64 `json:"index_offset"` // index offset into signed block bit array + JailedUntil time.Time `json:"jailed_until"` // timestamp validator cannot be unjailed until + MissedBlocksCounter int64 `json:"misseded_blocks_counter"` // missed blocks counter (to avoid scanning the array every time) } // Return human readable signing info 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) + return fmt.Sprintf("Start height: %d, index offset: %d, jailed until: %v, missed blocks counter: %d", + i.StartHeight, i.IndexOffset, i.JailedUntil, i.MissedBlocksCounter) } diff --git a/x/slashing/signing_info_test.go b/x/slashing/signing_info_test.go index 7aff0da95..1b22d7461 100644 --- a/x/slashing/signing_info_test.go +++ b/x/slashing/signing_info_test.go @@ -17,7 +17,7 @@ func TestGetSetValidatorSigningInfo(t *testing.T) { StartHeight: int64(4), IndexOffset: int64(3), JailedUntil: time.Unix(2, 0), - SignedBlocksCounter: int64(10), + MissedBlocksCounter: int64(10), } keeper.setValidatorSigningInfo(ctx, sdk.ConsAddress(addrs[0]), newInfo) info, found = keeper.getValidatorSigningInfo(ctx, sdk.ConsAddress(addrs[0])) @@ -25,14 +25,14 @@ func TestGetSetValidatorSigningInfo(t *testing.T) { require.Equal(t, info.StartHeight, int64(4)) require.Equal(t, info.IndexOffset, int64(3)) require.Equal(t, info.JailedUntil, time.Unix(2, 0).UTC()) - require.Equal(t, info.SignedBlocksCounter, int64(10)) + require.Equal(t, info.MissedBlocksCounter, int64(10)) } func TestGetSetValidatorSigningBitArray(t *testing.T) { ctx, _, _, _, keeper := createTestInput(t) - signed := keeper.getValidatorSigningBitArray(ctx, sdk.ConsAddress(addrs[0]), 0) - require.False(t, signed) // treat empty key as unsigned + missed := keeper.getValidatorSigningBitArray(ctx, sdk.ConsAddress(addrs[0]), 0) + require.False(t, missed) // treat empty key as not missed 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 + missed = keeper.getValidatorSigningBitArray(ctx, sdk.ConsAddress(addrs[0]), 0) + require.True(t, missed) // now should be missed } diff --git a/x/slashing/tick_test.go b/x/slashing/tick_test.go index a98d97c54..c85d4f4cb 100644 --- a/x/slashing/tick_test.go +++ b/x/slashing/tick_test.go @@ -45,7 +45,7 @@ func TestBeginBlocker(t *testing.T) { require.Equal(t, ctx.BlockHeight(), info.StartHeight) require.Equal(t, int64(1), info.IndexOffset) require.Equal(t, time.Unix(0, 0).UTC(), info.JailedUntil) - require.Equal(t, int64(1), info.SignedBlocksCounter) + require.Equal(t, int64(0), info.MissedBlocksCounter) height := int64(0) From 8c2c9dba8a9450298e61ac77607ed39dff64bed6 Mon Sep 17 00:00:00 2001 From: Christopher Goes Date: Fri, 12 Oct 2018 00:15:58 +0200 Subject: [PATCH 05/40] Update block height on validator bonding --- client/lcd/lcd_test.go | 1 - x/slashing/hooks.go | 12 +++++++----- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/client/lcd/lcd_test.go b/client/lcd/lcd_test.go index 6eef4afd0..4a999b066 100644 --- a/client/lcd/lcd_test.go +++ b/client/lcd/lcd_test.go @@ -713,7 +713,6 @@ func TestUnjail(t *testing.T) { tests.WaitForHeight(4, port) require.Equal(t, true, signingInfo.IndexOffset > 0) require.Equal(t, time.Unix(0, 0).UTC(), signingInfo.JailedUntil) - require.Equal(t, true, signingInfo.SignedBlocksCounter > 0) } func TestProposalsQuery(t *testing.T) { diff --git a/x/slashing/hooks.go b/x/slashing/hooks.go index 808260830..3a5ddd6fd 100644 --- a/x/slashing/hooks.go +++ b/x/slashing/hooks.go @@ -7,17 +7,19 @@ import ( ) func (k Keeper) onValidatorBonded(ctx sdk.Context, address sdk.ConsAddress) { - // Create a new signing info if necessary - _, found := k.getValidatorSigningInfo(ctx, address) - if !found { - signingInfo := ValidatorSigningInfo{ + // Update the signing info start height or create a new signing info + signingInfo, found := k.getValidatorSigningInfo(ctx, address) + if found { + signingInfo.StartHeight = ctx.BlockHeight() + } else { + signingInfo = ValidatorSigningInfo{ StartHeight: ctx.BlockHeight(), IndexOffset: 0, JailedUntil: time.Unix(0, 0), MissedBlocksCounter: 0, } - k.setValidatorSigningInfo(ctx, address, signingInfo) } + k.setValidatorSigningInfo(ctx, address, signingInfo) // Create a new slashing period when a validator is bonded slashingPeriod := ValidatorSlashingPeriod{ From 6e4a8d9e166925d0abea7b8dbf3a25923580aa5b Mon Sep 17 00:00:00 2001 From: Christopher Goes Date: Fri, 12 Oct 2018 01:04:57 +0200 Subject: [PATCH 06/40] Update spec --- client/lcd/lcd_test.go | 1 + docs/spec/slashing/begin-block.md | 14 +++++++------- docs/spec/slashing/state.md | 4 ++-- 3 files changed, 10 insertions(+), 9 deletions(-) diff --git a/client/lcd/lcd_test.go b/client/lcd/lcd_test.go index 4a999b066..1a7aa7ffe 100644 --- a/client/lcd/lcd_test.go +++ b/client/lcd/lcd_test.go @@ -713,6 +713,7 @@ func TestUnjail(t *testing.T) { tests.WaitForHeight(4, port) require.Equal(t, true, signingInfo.IndexOffset > 0) require.Equal(t, time.Unix(0, 0).UTC(), signingInfo.JailedUntil) + require.Equal(t, true, signingInfo.MissedBlocksCounter == 0) } func TestProposalsQuery(t *testing.T) { diff --git a/docs/spec/slashing/begin-block.md b/docs/spec/slashing/begin-block.md index 375e19185..fb7d04e5d 100644 --- a/docs/spec/slashing/begin-block.md +++ b/docs/spec/slashing/begin-block.md @@ -96,20 +96,20 @@ for val in block.Validators: previous = SigningBitArray.Get(val.Address, index) // update counter if array has changed - if previous and val in block.AbsentValidators: - SigningBitArray.Set(val.Address, index, false) - signInfo.SignedBlocksCounter-- - else if !previous and val not in block.AbsentValidators: + if !previous and val in block.AbsentValidators: SigningBitArray.Set(val.Address, index, true) - signInfo.SignedBlocksCounter++ + signInfo.MissedBlocksCounter++ + else if previous and val not in block.AbsentValidators: + SigningBitArray.Set(val.Address, index, false) + signInfo.MissedBlocksCounter-- // else previous == val not in block.AbsentValidators, no change // validator must be active for at least SIGNED_BLOCKS_WINDOW // before they can be automatically unbonded for failing to be // included in 50% of the recent LastCommits minHeight = signInfo.StartHeight + SIGNED_BLOCKS_WINDOW - minSigned = SIGNED_BLOCKS_WINDOW / 2 - if height > minHeight AND signInfo.SignedBlocksCounter < minSigned: + maxMissed = SIGNED_BLOCKS_WINDOW / 2 + if height > minHeight AND signInfo.MissedBlocksCounter > maxMissed: signInfo.JailedUntil = block.Time + DOWNTIME_UNBOND_DURATION slash & unbond the validator diff --git a/docs/spec/slashing/state.md b/docs/spec/slashing/state.md index ae426db7b..fe6f39f5c 100644 --- a/docs/spec/slashing/state.md +++ b/docs/spec/slashing/state.md @@ -40,7 +40,7 @@ type ValidatorSigningInfo struct { IndexOffset int64 // Offset into the signed block bit array JailedUntilHeight int64 // Block height until which the validator is jailed, // or sentinel value of 0 for not jailed - SignedBlocksCounter int64 // Running counter of signed blocks + MissedBlocksCounter int64 // Running counter of missed blocks } ``` @@ -49,7 +49,7 @@ Where: * `StartHeight` is set to the height that the candidate became an active validator (with non-zero voting power). * `IndexOffset` is incremented each time the candidate was a bonded validator in a block (and may have signed a precommit or not). * `JailedUntil` is set whenever the candidate is jailed due to downtime -* `SignedBlocksCounter` is a counter kept to avoid unnecessary array reads. `SignedBlocksBitArray.Sum() == SignedBlocksCounter` always. +* `MissedBlocksCounter` is a counter kept to avoid unnecessary array reads. `MissedBlocksBitArray.Sum() == MissedBlocksCounter` always. ## Slashing Period From a83535aef34443180c7769a3a2ed7ad04b0ef7ca Mon Sep 17 00:00:00 2001 From: Christopher Goes Date: Fri, 12 Oct 2018 21:09:23 +0200 Subject: [PATCH 07/40] Greater than to greater than or equal to --- docs/spec/slashing/begin-block.md | 2 +- x/slashing/keeper.go | 2 +- x/slashing/keeper_test.go | 85 +++++++++++++++++++++++++++++++ 3 files changed, 87 insertions(+), 2 deletions(-) diff --git a/docs/spec/slashing/begin-block.md b/docs/spec/slashing/begin-block.md index fb7d04e5d..2d03fb6a7 100644 --- a/docs/spec/slashing/begin-block.md +++ b/docs/spec/slashing/begin-block.md @@ -109,7 +109,7 @@ for val in block.Validators: // included in 50% of the recent LastCommits minHeight = signInfo.StartHeight + SIGNED_BLOCKS_WINDOW maxMissed = SIGNED_BLOCKS_WINDOW / 2 - if height > minHeight AND signInfo.MissedBlocksCounter > maxMissed: + if height >= minHeight AND signInfo.MissedBlocksCounter > maxMissed: signInfo.JailedUntil = block.Time + DOWNTIME_UNBOND_DURATION slash & unbond the validator diff --git a/x/slashing/keeper.go b/x/slashing/keeper.go index 23e30fe1e..1e0725ae8 100644 --- a/x/slashing/keeper.go +++ b/x/slashing/keeper.go @@ -128,7 +128,7 @@ func (k Keeper) handleValidatorSignature(ctx sdk.Context, addr crypto.Address, p } minHeight := signInfo.StartHeight + k.SignedBlocksWindow(ctx) maxMissed := k.SignedBlocksWindow(ctx) - k.MinSignedPerWindow(ctx) - if height > minHeight && signInfo.MissedBlocksCounter > maxMissed { + if height >= minHeight && signInfo.MissedBlocksCounter > maxMissed { validator := k.validatorSet.ValidatorByConsAddr(ctx, consAddr) if validator != nil && !validator.GetJailed() { // Downtime confirmed: slash and jail the validator diff --git a/x/slashing/keeper_test.go b/x/slashing/keeper_test.go index 32469b6d3..9dcaae6e5 100644 --- a/x/slashing/keeper_test.go +++ b/x/slashing/keeper_test.go @@ -364,3 +364,88 @@ func TestHandleAlreadyJailed(t *testing.T) { require.Equal(t, amtInt-1, validator.GetTokens().RoundInt64()) } + +// Test a validator dipping in and out of the validator set +// Ensure that missed blocks are tracked correctly +func TestValidatorDippingInAndOut(t *testing.T) { + + // initial setup + ctx, _, sk, _, keeper := createTestInput(t) + params := sk.GetParams(ctx) + params.MaxValidators = 1 + sk.SetParams(ctx, params) + amtInt := int64(100) + addr, val, amt := addrs[0], pks[0], sdk.NewInt(amtInt) + sh := stake.NewHandler(sk) + got := sh(ctx, newTestMsgCreateValidator(addr, val, amt)) + require.True(t, got.IsOK()) + validatorUpdates := stake.EndBlocker(ctx, sk) + keeper.AddValidators(ctx, validatorUpdates) + + // 100 first blocks OK + height := int64(0) + for ; height < int64(100); height++ { + ctx = ctx.WithBlockHeight(height) + keeper.handleValidatorSignature(ctx, val.Address(), amtInt, true) + } + + // validator kicked out of validator set + newAmt := int64(101) + got = sh(ctx, newTestMsgCreateValidator(addrs[1], pks[1], sdk.NewInt(newAmt))) + require.True(t, got.IsOK()) + validatorUpdates = stake.EndBlocker(ctx, sk) + require.Equal(t, 2, len(validatorUpdates)) + keeper.AddValidators(ctx, validatorUpdates) + validator, _ := sk.GetValidator(ctx, addr) + require.Equal(t, sdk.Unbonding, validator.Status) + + // 600 more blocks happened + height = int64(700) + ctx = ctx.WithBlockHeight(height) + + // validator added back in + got = sh(ctx, newTestMsgDelegate(sdk.AccAddress(addrs[2]), addrs[0], sdk.NewInt(2))) + require.True(t, got.IsOK()) + validatorUpdates = stake.EndBlocker(ctx, sk) + require.Equal(t, 2, len(validatorUpdates)) + validator, _ = sk.GetValidator(ctx, addr) + require.Equal(t, sdk.Bonded, validator.Status) + newAmt = int64(102) + + // validator misses a block + keeper.handleValidatorSignature(ctx, val.Address(), newAmt, false) + height++ + + // shouldn't be jailed/kicked yet + validator, _ = sk.GetValidator(ctx, addr) + require.Equal(t, sdk.Bonded, validator.Status) + + // validator misses 500 blocks + for ; height < int64(1202); height++ { + ctx = ctx.WithBlockHeight(height) + keeper.handleValidatorSignature(ctx, val.Address(), newAmt, false) + } + + // should not yet be jailed & kicked + stake.EndBlocker(ctx, sk) + validator, _ = sk.GetValidator(ctx, addr) + require.Equal(t, sdk.Bonded, validator.Status) + + // validator signs 500 blocks + for ; height < int64(1701); height++ { + ctx = ctx.WithBlockHeight(height) + keeper.handleValidatorSignature(ctx, val.Address(), newAmt, true) + } + + // should have exceeded threshold + signingInfo, found := keeper.getValidatorSigningInfo(ctx, sdk.ConsAddress(val.Address())) + require.True(t, found) + require.Equal(t, int64(700), signingInfo.StartHeight) + require.Equal(t, int64(1101), signingInfo.IndexOffset) + require.Equal(t, int64(501), signingInfo.MissedBlocksCounter) + + // should be jailed & kicked + stake.EndBlocker(ctx, sk) + validator, _ = sk.GetValidator(ctx, addr) + require.Equal(t, sdk.Unbonding, validator.Status) +} From bdfb10f5511768e6f4e38604465d59261f89f9ea Mon Sep 17 00:00:00 2001 From: Christopher Goes Date: Fri, 12 Oct 2018 21:10:55 +0200 Subject: [PATCH 08/40] Clarify testcase --- x/slashing/keeper_test.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/x/slashing/keeper_test.go b/x/slashing/keeper_test.go index 9dcaae6e5..6b53eaf8c 100644 --- a/x/slashing/keeper_test.go +++ b/x/slashing/keeper_test.go @@ -366,7 +366,8 @@ func TestHandleAlreadyJailed(t *testing.T) { } // Test a validator dipping in and out of the validator set -// Ensure that missed blocks are tracked correctly +// Ensure that missed blocks are tracked correctly and that +// the start height of the signing info is reset correctly func TestValidatorDippingInAndOut(t *testing.T) { // initial setup From 1ff2e865a8ce845eed0e433a1bfe4c040947fc69 Mon Sep 17 00:00:00 2001 From: Christopher Goes Date: Fri, 12 Oct 2018 21:15:39 +0200 Subject: [PATCH 09/40] Back to greater than --- docs/spec/slashing/begin-block.md | 2 +- x/slashing/keeper.go | 2 +- x/slashing/keeper_test.go | 8 ++++---- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/spec/slashing/begin-block.md b/docs/spec/slashing/begin-block.md index 2d03fb6a7..fb7d04e5d 100644 --- a/docs/spec/slashing/begin-block.md +++ b/docs/spec/slashing/begin-block.md @@ -109,7 +109,7 @@ for val in block.Validators: // included in 50% of the recent LastCommits minHeight = signInfo.StartHeight + SIGNED_BLOCKS_WINDOW maxMissed = SIGNED_BLOCKS_WINDOW / 2 - if height >= minHeight AND signInfo.MissedBlocksCounter > maxMissed: + if height > minHeight AND signInfo.MissedBlocksCounter > maxMissed: signInfo.JailedUntil = block.Time + DOWNTIME_UNBOND_DURATION slash & unbond the validator diff --git a/x/slashing/keeper.go b/x/slashing/keeper.go index 1e0725ae8..23e30fe1e 100644 --- a/x/slashing/keeper.go +++ b/x/slashing/keeper.go @@ -128,7 +128,7 @@ func (k Keeper) handleValidatorSignature(ctx sdk.Context, addr crypto.Address, p } minHeight := signInfo.StartHeight + k.SignedBlocksWindow(ctx) maxMissed := k.SignedBlocksWindow(ctx) - k.MinSignedPerWindow(ctx) - if height >= minHeight && signInfo.MissedBlocksCounter > maxMissed { + if height > minHeight && signInfo.MissedBlocksCounter > maxMissed { validator := k.validatorSet.ValidatorByConsAddr(ctx, consAddr) if validator != nil && !validator.GetJailed() { // Downtime confirmed: slash and jail the validator diff --git a/x/slashing/keeper_test.go b/x/slashing/keeper_test.go index 6b53eaf8c..2efc8dd84 100644 --- a/x/slashing/keeper_test.go +++ b/x/slashing/keeper_test.go @@ -421,8 +421,8 @@ func TestValidatorDippingInAndOut(t *testing.T) { validator, _ = sk.GetValidator(ctx, addr) require.Equal(t, sdk.Bonded, validator.Status) - // validator misses 500 blocks - for ; height < int64(1202); height++ { + // validator misses 501 blocks + for ; height < int64(1203); height++ { ctx = ctx.WithBlockHeight(height) keeper.handleValidatorSignature(ctx, val.Address(), newAmt, false) } @@ -433,7 +433,7 @@ func TestValidatorDippingInAndOut(t *testing.T) { require.Equal(t, sdk.Bonded, validator.Status) // validator signs 500 blocks - for ; height < int64(1701); height++ { + for ; height < int64(1702); height++ { ctx = ctx.WithBlockHeight(height) keeper.handleValidatorSignature(ctx, val.Address(), newAmt, true) } @@ -442,7 +442,7 @@ func TestValidatorDippingInAndOut(t *testing.T) { signingInfo, found := keeper.getValidatorSigningInfo(ctx, sdk.ConsAddress(val.Address())) require.True(t, found) require.Equal(t, int64(700), signingInfo.StartHeight) - require.Equal(t, int64(1101), signingInfo.IndexOffset) + require.Equal(t, int64(1102), signingInfo.IndexOffset) require.Equal(t, int64(501), signingInfo.MissedBlocksCounter) // should be jailed & kicked From fa372d3b82b3fe668315a348f155b7d5d8f01344 Mon Sep 17 00:00:00 2001 From: Christopher Goes Date: Mon, 15 Oct 2018 21:08:56 +0200 Subject: [PATCH 10/40] Test correct JailedUntil --- x/slashing/keeper_test.go | 1 + 1 file changed, 1 insertion(+) diff --git a/x/slashing/keeper_test.go b/x/slashing/keeper_test.go index 7b1ee24bc..52defda8d 100644 --- a/x/slashing/keeper_test.go +++ b/x/slashing/keeper_test.go @@ -151,6 +151,7 @@ func TestHandleAbsentValidator(t *testing.T) { require.Equal(t, int64(0), info.StartHeight) require.Equal(t, int64(0), info.IndexOffset) require.Equal(t, int64(0), info.MissedBlocksCounter) + require.Equal(t, time.Unix(0, 0).UTC(), info.JailedUntil) height := int64(0) // 1000 first blocks OK From 27b8322a274dcea00fde17f023cfebe96d92df33 Mon Sep 17 00:00:00 2001 From: mossid Date: Tue, 16 Oct 2018 04:09:41 +0900 Subject: [PATCH 11/40] split test --- store/list_test.go | 75 +++++++++++++++++++++++++ store/{linear_test.go => queue_test.go} | 69 ----------------------- 2 files changed, 75 insertions(+), 69 deletions(-) create mode 100644 store/list_test.go rename store/{linear_test.go => queue_test.go} (60%) diff --git a/store/list_test.go b/store/list_test.go new file mode 100644 index 000000000..396e2d1a1 --- /dev/null +++ b/store/list_test.go @@ -0,0 +1,75 @@ +package store + +import ( + "math/rand" + "testing" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/stretchr/testify/require" +) + +func TestList(t *testing.T) { + key := sdk.NewKVStoreKey("test") + ctx, cdc := defaultComponents(key) + store := ctx.KVStore(key) + lm := NewList(cdc, store) + + val := S{1, true} + var res S + + lm.Push(val) + require.Equal(t, uint64(1), lm.Len()) + lm.Get(uint64(0), &res) + require.Equal(t, val, res) + + val = S{2, false} + lm.Set(uint64(0), val) + lm.Get(uint64(0), &res) + require.Equal(t, val, res) + + val = S{100, false} + lm.Push(val) + require.Equal(t, uint64(2), lm.Len()) + lm.Get(uint64(1), &res) + require.Equal(t, val, res) + + lm.Delete(uint64(1)) + require.Equal(t, uint64(2), lm.Len()) + + lm.Iterate(&res, func(index uint64) (brk bool) { + var temp S + lm.Get(index, &temp) + require.Equal(t, temp, res) + + require.True(t, index != 1) + return + }) + + lm.Iterate(&res, func(index uint64) (brk bool) { + lm.Set(index, S{res.I + 1, !res.B}) + return + }) + + lm.Get(uint64(0), &res) + require.Equal(t, S{3, true}, res) +} + +func TestListRandom(t *testing.T) { + key := sdk.NewKVStoreKey("test") + ctx, cdc := defaultComponents(key) + store := ctx.KVStore(key) + list := NewList(cdc, store) + mocklist := []uint32{} + + for i := 0; i < 100; i++ { + item := rand.Uint32() + list.Push(item) + mocklist = append(mocklist, item) + } + + for k, v := range mocklist { + var i uint32 + require.NotPanics(t, func() { list.Get(uint64(k), &i) }) + require.Equal(t, v, i) + } +} diff --git a/store/linear_test.go b/store/queue_test.go similarity index 60% rename from store/linear_test.go rename to store/queue_test.go index c8153a041..5ea6d906a 100644 --- a/store/linear_test.go +++ b/store/queue_test.go @@ -1,7 +1,6 @@ package store import ( - // "math/rand" "testing" "github.com/stretchr/testify/require" @@ -30,52 +29,6 @@ func defaultComponents(key sdk.StoreKey) (sdk.Context, *codec.Codec) { return ctx, cdc } -func TestList(t *testing.T) { - key := sdk.NewKVStoreKey("test") - ctx, cdc := defaultComponents(key) - store := ctx.KVStore(key) - lm := NewList(cdc, store) - - val := S{1, true} - var res S - - lm.Push(val) - require.Equal(t, uint64(1), lm.Len()) - lm.Get(uint64(0), &res) - require.Equal(t, val, res) - - val = S{2, false} - lm.Set(uint64(0), val) - lm.Get(uint64(0), &res) - require.Equal(t, val, res) - - val = S{100, false} - lm.Push(val) - require.Equal(t, uint64(2), lm.Len()) - lm.Get(uint64(1), &res) - require.Equal(t, val, res) - - lm.Delete(uint64(1)) - require.Equal(t, uint64(2), lm.Len()) - - lm.Iterate(&res, func(index uint64) (brk bool) { - var temp S - lm.Get(index, &temp) - require.Equal(t, temp, res) - - require.True(t, index != 1) - return - }) - - lm.Iterate(&res, func(index uint64) (brk bool) { - lm.Set(index, S{res.I + 1, !res.B}) - return - }) - - lm.Get(uint64(0), &res) - require.Equal(t, S{3, true}, res) -} - func TestQueue(t *testing.T) { key := sdk.NewKVStoreKey("test") ctx, cdc := defaultComponents(key) @@ -146,26 +99,4 @@ func TestKeys(t *testing.T) { err = cdc.UnmarshalBinary(store.Get(TopKey()), &top) require.Nil(t, err) require.Equal(t, top, queue.getTop()) - } - -/* -func TestListRandom(t *testing.T) { - key := sdk.NewKVStoreKey("test") - ctx, cdc := defaultComponents(key) - store := ctx.KVStore(key) - list := NewList(cdc, store) - mocklist := []uint32{} - - for i := 0; i < 10000; { - limit := rand.Int31() % 1000 - for j := int32(0); j < limit; j++ { - item := rand.Uint32() - list.Push(item) - mocklist = append(mocklist, item) - } - - require.Equal() - } -} -*/ From 6e0f3d6baf106eca44c736b03e75da9b292c3ad0 Mon Sep 17 00:00:00 2001 From: Christopher Goes Date: Mon, 15 Oct 2018 21:11:32 +0200 Subject: [PATCH 12/40] Update function names to clarify semantics --- x/slashing/keeper.go | 6 +++--- x/slashing/signing_info.go | 4 ++-- x/slashing/signing_info_test.go | 6 +++--- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/x/slashing/keeper.go b/x/slashing/keeper.go index 61cfbb15c..68cbd90b0 100644 --- a/x/slashing/keeper.go +++ b/x/slashing/keeper.go @@ -110,17 +110,17 @@ func (k Keeper) handleValidatorSignature(ctx sdk.Context, addr crypto.Address, p // Update signed block bit array & counter // This counter just tracks the sum of the bit array // That way we avoid needing to read/write the whole array each time - previous := k.getValidatorSigningBitArray(ctx, consAddr, index) + previous := k.getValidatorMissedBlockBitArray(ctx, consAddr, index) missed := !signed if previous == missed { // Array value at this index has not changed, no need to update counter } else if !previous && missed { // Array value has changed from not missed to missed, increment counter - k.setValidatorSigningBitArray(ctx, consAddr, index, true) + k.setValidatorMissedBlockBitArray(ctx, consAddr, index, true) signInfo.MissedBlocksCounter++ } else if previous && !missed { // Array value has changed from missed to not missed, decrement counter - k.setValidatorSigningBitArray(ctx, consAddr, index, false) + k.setValidatorMissedBlockBitArray(ctx, consAddr, index, false) signInfo.MissedBlocksCounter-- } diff --git a/x/slashing/signing_info.go b/x/slashing/signing_info.go index 64f47838a..88e00fd67 100644 --- a/x/slashing/signing_info.go +++ b/x/slashing/signing_info.go @@ -28,7 +28,7 @@ func (k Keeper) setValidatorSigningInfo(ctx sdk.Context, address sdk.ConsAddress } // Stored by *validator* address (not operator address) -func (k Keeper) getValidatorSigningBitArray(ctx sdk.Context, address sdk.ConsAddress, index int64) (signed bool) { +func (k Keeper) getValidatorMissedBlockBitArray(ctx sdk.Context, address sdk.ConsAddress, index int64) (signed bool) { store := ctx.KVStore(k.storeKey) bz := store.Get(GetValidatorSigningBitArrayKey(address, index)) if bz == nil { @@ -41,7 +41,7 @@ func (k Keeper) getValidatorSigningBitArray(ctx sdk.Context, address sdk.ConsAdd } // Stored by *validator* address (not operator address) -func (k Keeper) setValidatorSigningBitArray(ctx sdk.Context, address sdk.ConsAddress, index int64, signed bool) { +func (k Keeper) setValidatorMissedBlockBitArray(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) diff --git a/x/slashing/signing_info_test.go b/x/slashing/signing_info_test.go index f5c8d6ea3..c5ffc8bf1 100644 --- a/x/slashing/signing_info_test.go +++ b/x/slashing/signing_info_test.go @@ -30,9 +30,9 @@ func TestGetSetValidatorSigningInfo(t *testing.T) { func TestGetSetValidatorSigningBitArray(t *testing.T) { ctx, _, _, _, keeper := createTestInput(t, DefaultParams()) - missed := keeper.getValidatorSigningBitArray(ctx, sdk.ConsAddress(addrs[0]), 0) + missed := keeper.getValidatorMissedBlockBitArray(ctx, sdk.ConsAddress(addrs[0]), 0) require.False(t, missed) // treat empty key as not missed - keeper.setValidatorSigningBitArray(ctx, sdk.ConsAddress(addrs[0]), 0, true) - missed = keeper.getValidatorSigningBitArray(ctx, sdk.ConsAddress(addrs[0]), 0) + keeper.setValidatorMissedBlockBitArray(ctx, sdk.ConsAddress(addrs[0]), 0, true) + missed = keeper.getValidatorMissedBlockBitArray(ctx, sdk.ConsAddress(addrs[0]), 0) require.True(t, missed) // now should be missed } From 096a8bb9ec68141d6b3eb974bcac30340789997e Mon Sep 17 00:00:00 2001 From: Christopher Goes Date: Mon, 15 Oct 2018 21:20:37 +0200 Subject: [PATCH 13/40] Further clarification of nomenclature --- x/slashing/keys.go | 17 +++++++++++------ x/slashing/signing_info.go | 25 +++++++++++++++++-------- x/slashing/signing_info_test.go | 2 +- 3 files changed, 29 insertions(+), 15 deletions(-) diff --git a/x/slashing/keys.go b/x/slashing/keys.go index ec453cedc..8f4fecc6c 100644 --- a/x/slashing/keys.go +++ b/x/slashing/keys.go @@ -9,10 +9,10 @@ import ( // 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 + ValidatorSigningInfoKey = []byte{0x01} // Prefix for signing info + ValidatorMissedBlockBitArrayKey = []byte{0x02} // Prefix for missed block bit array + ValidatorSlashingPeriodKey = []byte{0x03} // Prefix for slashing period + AddrPubkeyRelationKey = []byte{0x04} // Prefix for address-pubkey relation ) // stored by *Tendermint* address (not operator address) @@ -21,10 +21,15 @@ func GetValidatorSigningInfoKey(v sdk.ConsAddress) []byte { } // stored by *Tendermint* address (not operator address) -func GetValidatorSigningBitArrayKey(v sdk.ConsAddress, i int64) []byte { +func GetValidatorMissedBlockBitArrayPrefixKey(v sdk.ConsAddress) []byte { + return append(ValidatorMissedBlockBitArrayKey, v.Bytes()...) +} + +// stored by *Tendermint* address (not operator address) +func GetValidatorMissedBlockBitArrayKey(v sdk.ConsAddress, i int64) []byte { b := make([]byte, 8) binary.LittleEndian.PutUint64(b, uint64(i)) - return append(ValidatorSigningBitArrayKey, append(v.Bytes(), b...)...) + return append(GetValidatorMissedBlockBitArrayPrefixKey(v), b...) } // stored by *Tendermint* address (not operator address) diff --git a/x/slashing/signing_info.go b/x/slashing/signing_info.go index 88e00fd67..d921f9d7e 100644 --- a/x/slashing/signing_info.go +++ b/x/slashing/signing_info.go @@ -28,23 +28,32 @@ func (k Keeper) setValidatorSigningInfo(ctx sdk.Context, address sdk.ConsAddress } // Stored by *validator* address (not operator address) -func (k Keeper) getValidatorMissedBlockBitArray(ctx sdk.Context, address sdk.ConsAddress, index int64) (signed bool) { +func (k Keeper) getValidatorMissedBlockBitArray(ctx sdk.Context, address sdk.ConsAddress, index int64) (missed bool) { store := ctx.KVStore(k.storeKey) - bz := store.Get(GetValidatorSigningBitArrayKey(address, index)) + bz := store.Get(GetValidatorMissedBlockBitArrayKey(address, index)) if bz == nil { - // lazy: treat empty key as unsigned - signed = false + // lazy: treat empty key as not missed + missed = false return } - k.cdc.MustUnmarshalBinary(bz, &signed) + k.cdc.MustUnmarshalBinary(bz, &missed) return } // Stored by *validator* address (not operator address) -func (k Keeper) setValidatorMissedBlockBitArray(ctx sdk.Context, address sdk.ConsAddress, index int64, signed bool) { +func (k Keeper) setValidatorMissedBlockBitArray(ctx sdk.Context, address sdk.ConsAddress, index int64, missed bool) { store := ctx.KVStore(k.storeKey) - bz := k.cdc.MustMarshalBinary(signed) - store.Set(GetValidatorSigningBitArrayKey(address, index), bz) + bz := k.cdc.MustMarshalBinary(missed) + store.Set(GetValidatorMissedBlockBitArrayKey(address, index), bz) +} + +// Stored by *validator* address (not operator address) +func (k Keeper) clearValidatorMissedBlockBitArray(ctx sdk.Context, address sdk.ConsAddress) { + store := ctx.KVStore(k.storeKey) + iter := sdk.KVStorePrefixIterator(store, GetValidatorMissedBlockBitArrayPrefixKey(address)) + for ; iter.Valid(); iter.Next() { + store.Delete(iter.Key()) + } } // Construct a new `ValidatorSigningInfo` struct diff --git a/x/slashing/signing_info_test.go b/x/slashing/signing_info_test.go index c5ffc8bf1..15863ebc7 100644 --- a/x/slashing/signing_info_test.go +++ b/x/slashing/signing_info_test.go @@ -28,7 +28,7 @@ func TestGetSetValidatorSigningInfo(t *testing.T) { require.Equal(t, info.MissedBlocksCounter, int64(10)) } -func TestGetSetValidatorSigningBitArray(t *testing.T) { +func TestGetSetValidatorMissedBlockBitArray(t *testing.T) { ctx, _, _, _, keeper := createTestInput(t, DefaultParams()) missed := keeper.getValidatorMissedBlockBitArray(ctx, sdk.ConsAddress(addrs[0]), 0) require.False(t, missed) // treat empty key as not missed From 9ec35a0ae4e151e68c5696b5f665c3718ec127ea Mon Sep 17 00:00:00 2001 From: Christopher Goes Date: Mon, 15 Oct 2018 21:44:23 +0200 Subject: [PATCH 14/40] Clear array when validator is jailed --- x/slashing/hooks.go | 4 +--- x/slashing/keeper.go | 4 ++++ x/slashing/keeper_test.go | 44 +++++++++++++++++++++++++++------------ 3 files changed, 36 insertions(+), 16 deletions(-) diff --git a/x/slashing/hooks.go b/x/slashing/hooks.go index 3a5ddd6fd..3710e4072 100644 --- a/x/slashing/hooks.go +++ b/x/slashing/hooks.go @@ -9,9 +9,7 @@ import ( func (k Keeper) onValidatorBonded(ctx sdk.Context, address sdk.ConsAddress) { // Update the signing info start height or create a new signing info signingInfo, found := k.getValidatorSigningInfo(ctx, address) - if found { - signingInfo.StartHeight = ctx.BlockHeight() - } else { + if !found { signingInfo = ValidatorSigningInfo{ StartHeight: ctx.BlockHeight(), IndexOffset: 0, diff --git a/x/slashing/keeper.go b/x/slashing/keeper.go index 68cbd90b0..73734198d 100644 --- a/x/slashing/keeper.go +++ b/x/slashing/keeper.go @@ -144,6 +144,10 @@ func (k Keeper) handleValidatorSignature(ctx sdk.Context, addr crypto.Address, p k.validatorSet.Slash(ctx, consAddr, distributionHeight, power, k.SlashFractionDowntime(ctx)) k.validatorSet.Jail(ctx, consAddr) signInfo.JailedUntil = ctx.BlockHeader().Time.Add(k.DowntimeUnbondDuration(ctx)) + // We need to reset the counter & array so that the validator won't be immediately slashed for downtime upon rebonding. + signInfo.MissedBlocksCounter = 0 + signInfo.IndexOffset = 0 + k.clearValidatorMissedBlockBitArray(ctx, consAddr) } else { // Validator was (a) not found or (b) already jailed, don't slash logger.Info(fmt.Sprintf("Validator %s would have been slashed for downtime, but was either not found in store or already jailed", diff --git a/x/slashing/keeper_test.go b/x/slashing/keeper_test.go index 52defda8d..4299bb7cc 100644 --- a/x/slashing/keeper_test.go +++ b/x/slashing/keeper_test.go @@ -380,6 +380,7 @@ func TestValidatorDippingInAndOut(t *testing.T) { sk.SetParams(ctx, params) amtInt := int64(100) addr, val, amt := addrs[0], pks[0], sdk.NewInt(amtInt) + consAddr := sdk.ConsAddress(addr) sh := stake.NewHandler(sk) got := sh(ctx, newTestMsgCreateValidator(addr, val, amt)) require.True(t, got.IsOK()) @@ -425,31 +426,48 @@ func TestValidatorDippingInAndOut(t *testing.T) { require.Equal(t, sdk.Bonded, validator.Status) // validator misses 501 blocks - for ; height < int64(1203); height++ { + for ; height < int64(1201); height++ { ctx = ctx.WithBlockHeight(height) keeper.handleValidatorSignature(ctx, val.Address(), newAmt, false) } - // should not yet be jailed & kicked + // should now be jailed & kicked + stake.EndBlocker(ctx, sk) + validator, _ = sk.GetValidator(ctx, addr) + require.Equal(t, sdk.Unbonding, validator.Status) + + signInfo, found := keeper.getValidatorSigningInfo(ctx, consAddr) + require.True(t, found) + require.Equal(t, int64(0), signInfo.MissedBlocksCounter) + require.Equal(t, int64(0), signInfo.IndexOffset) + // array should be cleared + for offset := int64(0); offset < keeper.SignedBlocksWindow(ctx); offset++ { + missed := keeper.getValidatorMissedBlockBitArray(ctx, consAddr, offset) + require.False(t, missed) + } + + // some blocks pass + height = int64(5000) + ctx = ctx.WithBlockHeight(height) + + // validator rejoins and starts signing again + sk.Unjail(ctx, consAddr) + keeper.handleValidatorSignature(ctx, val.Address(), newAmt, true) + + // validator should not be kicked stake.EndBlocker(ctx, sk) validator, _ = sk.GetValidator(ctx, addr) require.Equal(t, sdk.Bonded, validator.Status) - // validator signs 500 blocks - for ; height < int64(1702); height++ { + // validator misses 501 blocks + for height = int64(5001); height < int64(5503); height++ { ctx = ctx.WithBlockHeight(height) - keeper.handleValidatorSignature(ctx, val.Address(), newAmt, true) + keeper.handleValidatorSignature(ctx, val.Address(), newAmt, false) } - // should have exceeded threshold - signingInfo, found := keeper.getValidatorSigningInfo(ctx, sdk.ConsAddress(val.Address())) - require.True(t, found) - require.Equal(t, int64(700), signingInfo.StartHeight) - require.Equal(t, int64(1102), signingInfo.IndexOffset) - require.Equal(t, int64(501), signingInfo.MissedBlocksCounter) - - // should be jailed & kicked + // validator should now be jailed & kicked stake.EndBlocker(ctx, sk) validator, _ = sk.GetValidator(ctx, addr) require.Equal(t, sdk.Unbonding, validator.Status) + } From ec53a14b57ea7b81968a5a73d273e80deaf1ba06 Mon Sep 17 00:00:00 2001 From: Christopher Goes Date: Mon, 15 Oct 2018 21:51:26 +0200 Subject: [PATCH 15/40] Fix testcases --- x/slashing/keeper_test.go | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/x/slashing/keeper_test.go b/x/slashing/keeper_test.go index 4299bb7cc..d1cc44408 100644 --- a/x/slashing/keeper_test.go +++ b/x/slashing/keeper_test.go @@ -186,7 +186,8 @@ func TestHandleAbsentValidator(t *testing.T) { 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.MissedBlocksCounter) + // counter now reset to zero + require.Equal(t, int64(0), info.MissedBlocksCounter) // end block stake.EndBlocker(ctx, sk) @@ -207,7 +208,7 @@ func TestHandleAbsentValidator(t *testing.T) { 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)+2, info.MissedBlocksCounter) + require.Equal(t, int64(1), info.MissedBlocksCounter) // end block stake.EndBlocker(ctx, sk) @@ -248,8 +249,8 @@ func TestHandleAbsentValidator(t *testing.T) { info, found = keeper.getValidatorSigningInfo(ctx, sdk.ConsAddress(val.Address())) require.True(t, found) require.Equal(t, height, info.StartHeight) - // we've missed 2 blocks more than the maximum - require.Equal(t, keeper.SignedBlocksWindow(ctx)-keeper.MinSignedPerWindow(ctx)+2, info.MissedBlocksCounter) + // we've missed 2 blocks more than the maximum, so the counter was reset to 0 at 1 block more and is now 1 + require.Equal(t, int64(1), info.MissedBlocksCounter) // validator should not be immediately jailed again height++ From 83f7a4cb7b56c845eb355f521d4d72a0ecfbdf2d Mon Sep 17 00:00:00 2001 From: Christopher Goes Date: Mon, 15 Oct 2018 23:01:29 +0200 Subject: [PATCH 16/40] Bugfix; update slashing spec --- docs/spec/slashing/begin-block.md | 10 ++++++---- docs/spec/slashing/hooks.md | 11 +++++++++++ docs/spec/slashing/state.md | 8 ++++---- docs/spec/slashing/transactions.md | 4 ---- x/slashing/handler.go | 6 +----- x/slashing/hooks.go | 2 +- x/slashing/keeper_test.go | 4 ++-- 7 files changed, 25 insertions(+), 20 deletions(-) diff --git a/docs/spec/slashing/begin-block.md b/docs/spec/slashing/begin-block.md index fb7d04e5d..43691b38d 100644 --- a/docs/spec/slashing/begin-block.md +++ b/docs/spec/slashing/begin-block.md @@ -93,14 +93,14 @@ for val in block.Validators: index := signInfo.IndexOffset % SIGNED_BLOCKS_WINDOW signInfo.IndexOffset++ - previous = SigningBitArray.Get(val.Address, index) + previous = MissedBlockBitArray.Get(val.Address, index) // update counter if array has changed if !previous and val in block.AbsentValidators: - SigningBitArray.Set(val.Address, index, true) + MissedBlockBitArray.Set(val.Address, index, true) signInfo.MissedBlocksCounter++ else if previous and val not in block.AbsentValidators: - SigningBitArray.Set(val.Address, index, false) + MissedBlockBitArray.Set(val.Address, index, false) signInfo.MissedBlocksCounter-- // else previous == val not in block.AbsentValidators, no change @@ -111,7 +111,9 @@ for val in block.Validators: maxMissed = SIGNED_BLOCKS_WINDOW / 2 if height > minHeight AND signInfo.MissedBlocksCounter > maxMissed: signInfo.JailedUntil = block.Time + DOWNTIME_UNBOND_DURATION - + signInfo.IndexOffset = 0 + signInfo.MissedBlocksCounter = 0 + clearMissedBlockBitArray() slash & unbond the validator SigningInfo.Set(val.Address, signInfo) diff --git a/docs/spec/slashing/hooks.md b/docs/spec/slashing/hooks.md index 36dde61f9..1888c1d2f 100644 --- a/docs/spec/slashing/hooks.md +++ b/docs/spec/slashing/hooks.md @@ -12,6 +12,17 @@ and `SlashedSoFar` of `0`: ``` onValidatorBonded(address sdk.ValAddress) + signingInfo, found = getValidatorSigningInfo(address) + if !found { + signingInfo = ValidatorSigningInfo { + StartHeight : CurrentHeight, + IndexOffset : 0, + JailedUntil : time.Unix(0, 0), + MissedBloskCounter : 0 + } + setValidatorSigningInfo(signingInfo) + } + slashingPeriod = SlashingPeriod{ ValidatorAddr : address, StartHeight : CurrentHeight, diff --git a/docs/spec/slashing/state.md b/docs/spec/slashing/state.md index fe6f39f5c..9a9a188ff 100644 --- a/docs/spec/slashing/state.md +++ b/docs/spec/slashing/state.md @@ -17,18 +17,18 @@ Information about validator activity is tracked in a `ValidatorSigningInfo`. It is indexed in the store as follows: - SigningInfo: ` 0x01 | ValTendermintAddr -> amino(valSigningInfo)` -- SigningBitArray: ` 0x02 | ValTendermintAddr | LittleEndianUint64(signArrayIndex) -> VarInt(didSign)` +- MissedBlocksBitArray: ` 0x02 | ValTendermintAddr | LittleEndianUint64(signArrayIndex) -> VarInt(didMiss)` The first map allows us to easily lookup the recent signing info for a validator, according to the Tendermint validator address. The second map acts as -a bit-array of size `SIGNED_BLOCKS_WINDOW` that tells us if the validator signed for a given index in the bit-array. +a bit-array of size `SIGNED_BLOCKS_WINDOW` that tells us if the validator missed the block for a given index in the bit-array. The index in the bit-array is given as little endian uint64. The result is a `varint` that takes on `0` or `1`, where `0` indicates the -validator did not sign the corresponding block, and `1` indicates they did. +validator did not miss (did sign) the corresponding block, and `1` indicates they missed the block (did not sign). -Note that the SigningBitArray is not explicitly initialized up-front. Keys are +Note that the MissedBlocksBitArray is not explicitly initialized up-front. Keys are added as we progress through the first `SIGNED_BLOCKS_WINDOW` blocks for a newly bonded validator. diff --git a/docs/spec/slashing/transactions.md b/docs/spec/slashing/transactions.md index be33ee096..c33e96047 100644 --- a/docs/spec/slashing/transactions.md +++ b/docs/spec/slashing/transactions.md @@ -25,10 +25,6 @@ handleMsgUnjail(tx TxUnjail) if block time < info.JailedUntil fail with "Validator still jailed, cannot unjail until period has expired" - // Update the start height so the validator won't be immediately unbonded again - info.StartHeight = BlockHeight - setValidatorSigningInfo(info) - validator.Jailed = false setValidator(validator) diff --git a/x/slashing/handler.go b/x/slashing/handler.go index 740166d2a..701577080 100644 --- a/x/slashing/handler.go +++ b/x/slashing/handler.go @@ -46,11 +46,7 @@ func handleMsgUnjail(ctx sdk.Context, msg MsgUnjail, k Keeper) sdk.Result { return ErrValidatorJailed(k.codespace).Result() } - // update the starting height so the validator can't be immediately jailed - // again - info.StartHeight = ctx.BlockHeight() - k.setValidatorSigningInfo(ctx, consAddr, info) - + // unjail the validator k.validatorSet.Unjail(ctx, consAddr) tags := sdk.NewTags("action", []byte("unjail"), "validator", []byte(msg.ValidatorAddr.String())) diff --git a/x/slashing/hooks.go b/x/slashing/hooks.go index 3710e4072..669536928 100644 --- a/x/slashing/hooks.go +++ b/x/slashing/hooks.go @@ -16,8 +16,8 @@ func (k Keeper) onValidatorBonded(ctx sdk.Context, address sdk.ConsAddress) { JailedUntil: time.Unix(0, 0), MissedBlocksCounter: 0, } + k.setValidatorSigningInfo(ctx, address, signingInfo) } - k.setValidatorSigningInfo(ctx, address, signingInfo) // Create a new slashing period when a validator is bonded slashingPeriod := ValidatorSlashingPeriod{ diff --git a/x/slashing/keeper_test.go b/x/slashing/keeper_test.go index d1cc44408..0af3936b2 100644 --- a/x/slashing/keeper_test.go +++ b/x/slashing/keeper_test.go @@ -245,10 +245,10 @@ func TestHandleAbsentValidator(t *testing.T) { pool = sk.GetPool(ctx) require.Equal(t, amtInt-slashAmt-secondSlashAmt, pool.BondedTokens.RoundInt64()) - // validator start height should have been changed + // validator start height should not have been changed info, found = keeper.getValidatorSigningInfo(ctx, sdk.ConsAddress(val.Address())) require.True(t, found) - require.Equal(t, height, info.StartHeight) + require.Equal(t, int64(0), info.StartHeight) // we've missed 2 blocks more than the maximum, so the counter was reset to 0 at 1 block more and is now 1 require.Equal(t, int64(1), info.MissedBlocksCounter) From e3fa9820ec2b22e7469ab7f8ff10835e59cbc41f Mon Sep 17 00:00:00 2001 From: Christopher Goes Date: Mon, 15 Oct 2018 23:06:46 +0200 Subject: [PATCH 17/40] Fix linter warning --- x/slashing/hooks.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/x/slashing/hooks.go b/x/slashing/hooks.go index 669536928..ed07bc0c7 100644 --- a/x/slashing/hooks.go +++ b/x/slashing/hooks.go @@ -8,9 +8,9 @@ import ( func (k Keeper) onValidatorBonded(ctx sdk.Context, address sdk.ConsAddress) { // Update the signing info start height or create a new signing info - signingInfo, found := k.getValidatorSigningInfo(ctx, address) + _, found := k.getValidatorSigningInfo(ctx, address) if !found { - signingInfo = ValidatorSigningInfo{ + signingInfo := ValidatorSigningInfo{ StartHeight: ctx.BlockHeight(), IndexOffset: 0, JailedUntil: time.Unix(0, 0), From 7bf8a41463aa79f7ec9efcf04871f4762cd52777 Mon Sep 17 00:00:00 2001 From: Christopher Goes Date: Mon, 15 Oct 2018 23:09:16 +0200 Subject: [PATCH 18/40] Fix typo --- x/slashing/signing_info.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/x/slashing/signing_info.go b/x/slashing/signing_info.go index d921f9d7e..e8795cd00 100644 --- a/x/slashing/signing_info.go +++ b/x/slashing/signing_info.go @@ -68,10 +68,10 @@ func NewValidatorSigningInfo(startHeight int64, indexOffset int64, jailedUntil t // Signing info for a validator type ValidatorSigningInfo struct { - StartHeight int64 `json:"start_height"` // height at which validator was first a candidate OR was unjailed - IndexOffset int64 `json:"index_offset"` // index offset into signed block bit array - JailedUntil time.Time `json:"jailed_until"` // timestamp validator cannot be unjailed until - MissedBlocksCounter int64 `json:"misseded_blocks_counter"` // missed blocks counter (to avoid scanning the array every time) + StartHeight int64 `json:"start_height"` // height at which validator was first a candidate OR was unjailed + IndexOffset int64 `json:"index_offset"` // index offset into signed block bit array + JailedUntil time.Time `json:"jailed_until"` // timestamp validator cannot be unjailed until + MissedBlocksCounter int64 `json:"missed_blocks_counter"` // missed blocks counter (to avoid scanning the array every time) } // Return human readable signing info From c0c5e293a8c53cdadd55a5975f0bc742f75fb9c7 Mon Sep 17 00:00:00 2001 From: Christopher Goes Date: Mon, 15 Oct 2018 23:09:56 +0200 Subject: [PATCH 19/40] iter.Close() --- x/slashing/signing_info.go | 1 + 1 file changed, 1 insertion(+) diff --git a/x/slashing/signing_info.go b/x/slashing/signing_info.go index e8795cd00..8c35693f1 100644 --- a/x/slashing/signing_info.go +++ b/x/slashing/signing_info.go @@ -54,6 +54,7 @@ func (k Keeper) clearValidatorMissedBlockBitArray(ctx sdk.Context, address sdk.C for ; iter.Valid(); iter.Next() { store.Delete(iter.Key()) } + iter.Close() } // Construct a new `ValidatorSigningInfo` struct From f9877508e5f2af5d3e5d0ec235502cdbbddd70d1 Mon Sep 17 00:00:00 2001 From: Christopher Goes Date: Mon, 15 Oct 2018 23:58:39 +0200 Subject: [PATCH 20/40] Address @rigelrozanski comments --- x/slashing/keeper.go | 9 +++++---- x/slashing/keeper_test.go | 13 +++++++++---- 2 files changed, 14 insertions(+), 8 deletions(-) diff --git a/x/slashing/keeper.go b/x/slashing/keeper.go index 73734198d..861391ac2 100644 --- a/x/slashing/keeper.go +++ b/x/slashing/keeper.go @@ -112,16 +112,17 @@ func (k Keeper) handleValidatorSignature(ctx sdk.Context, addr crypto.Address, p // That way we avoid needing to read/write the whole array each time previous := k.getValidatorMissedBlockBitArray(ctx, consAddr, index) missed := !signed - if previous == missed { - // Array value at this index has not changed, no need to update counter - } else if !previous && missed { + switch { + case !previous && missed: // Array value has changed from not missed to missed, increment counter k.setValidatorMissedBlockBitArray(ctx, consAddr, index, true) signInfo.MissedBlocksCounter++ - } else if previous && !missed { + case previous && !missed: // Array value has changed from missed to not missed, decrement counter k.setValidatorMissedBlockBitArray(ctx, consAddr, index, false) signInfo.MissedBlocksCounter-- + default: + // Array value at this index has not changed, no need to update counter } if missed { diff --git a/x/slashing/keeper_test.go b/x/slashing/keeper_test.go index 0af3936b2..6ad5c3792 100644 --- a/x/slashing/keeper_test.go +++ b/x/slashing/keeper_test.go @@ -375,6 +375,7 @@ func TestHandleAlreadyJailed(t *testing.T) { func TestValidatorDippingInAndOut(t *testing.T) { // initial setup + // keeperTestParams set the SignedBlocksWindow to 1000 and MaxMissedBlocksPerWindow to 500 ctx, _, sk, _, keeper := createTestInput(t, keeperTestParams()) params := sk.GetParams(ctx) params.MaxValidators = 1 @@ -426,8 +427,9 @@ func TestValidatorDippingInAndOut(t *testing.T) { validator, _ = sk.GetValidator(ctx, addr) require.Equal(t, sdk.Bonded, validator.Status) - // validator misses 501 blocks - for ; height < int64(1201); height++ { + // validator misses 500 more blocks, 501 total + latest := height + for ; height < latest+500; height++ { ctx = ctx.WithBlockHeight(height) keeper.handleValidatorSignature(ctx, val.Address(), newAmt, false) } @@ -437,6 +439,7 @@ func TestValidatorDippingInAndOut(t *testing.T) { validator, _ = sk.GetValidator(ctx, addr) require.Equal(t, sdk.Unbonding, validator.Status) + // check all the signing information signInfo, found := keeper.getValidatorSigningInfo(ctx, consAddr) require.True(t, found) require.Equal(t, int64(0), signInfo.MissedBlocksCounter) @@ -454,14 +457,16 @@ func TestValidatorDippingInAndOut(t *testing.T) { // validator rejoins and starts signing again sk.Unjail(ctx, consAddr) keeper.handleValidatorSignature(ctx, val.Address(), newAmt, true) + height++ - // validator should not be kicked + // validator should not be kicked since we reset counter/array when it was jailed stake.EndBlocker(ctx, sk) validator, _ = sk.GetValidator(ctx, addr) require.Equal(t, sdk.Bonded, validator.Status) // validator misses 501 blocks - for height = int64(5001); height < int64(5503); height++ { + latest = height + for ; height < latest+501; height++ { ctx = ctx.WithBlockHeight(height) keeper.handleValidatorSignature(ctx, val.Address(), newAmt, false) } From d0a00b4174884a76d8ce59ea1adf409e5d21cb68 Mon Sep 17 00:00:00 2001 From: Peng Zhong <172531+nylira@users.noreply.github.com> Date: Tue, 16 Oct 2018 12:11:57 +0800 Subject: [PATCH 21/40] add Google Analytics for documentation pages Also renames the static docs site to "Cosmos Documentation", which is a better representation of the contents than "Cosmos Network". --- docs/.vuepress/config.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/.vuepress/config.js b/docs/.vuepress/config.js index 7925697f5..4042de37e 100644 --- a/docs/.vuepress/config.js +++ b/docs/.vuepress/config.js @@ -1,6 +1,7 @@ module.exports = { - title: "Cosmos Network", + title: "Cosmos Documentation", description: "Documentation for the Cosmos Network.", + ga: "UA-51029217-2", dest: "./dist/docs", base: "/docs/", markdown: { From 7d46b8b50c2895f2cd871df9aae1b3549f2d0fc5 Mon Sep 17 00:00:00 2001 From: Christopher Goes Date: Tue, 16 Oct 2018 19:27:16 +0200 Subject: [PATCH 22/40] Alphanumeric parameter keys --- x/distribution/keeper/key.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/x/distribution/keeper/key.go b/x/distribution/keeper/key.go index 2e5989081..466d83c24 100644 --- a/x/distribution/keeper/key.go +++ b/x/distribution/keeper/key.go @@ -13,9 +13,9 @@ var ( ProposerKey = []byte{0x04} // key for storing the proposer operator address // params store - ParamStoreKeyCommunityTax = []byte("community-tax") - ParamStoreKeyBaseProposerReward = []byte("base-proposer-reward") - ParamStoreKeyBonusProposerReward = []byte("bonus-proposer-reward") + ParamStoreKeyCommunityTax = []byte("communitytax") + ParamStoreKeyBaseProposerReward = []byte("baseproposerreward") + ParamStoreKeyBonusProposerReward = []byte("bonusproposerreward") ) const ( From 0d1104330f48df912529a7714f07d2d086fa1319 Mon Sep 17 00:00:00 2001 From: Christopher Goes Date: Tue, 16 Oct 2018 19:42:51 +0200 Subject: [PATCH 23/40] Fix merge rename --- x/slashing/keeper_test.go | 6 +++--- x/stake/handler_test.go | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/x/slashing/keeper_test.go b/x/slashing/keeper_test.go index 77d0ebcf8..caf1cf3da 100644 --- a/x/slashing/keeper_test.go +++ b/x/slashing/keeper_test.go @@ -296,7 +296,7 @@ func TestHandleNewValidator(t *testing.T) { ctx = ctx.WithBlockHeight(keeper.SignedBlocksWindow(ctx) + 1) // Validator created - got := sh(ctx, newTestMsgCreateValidator(addr, val, sdk.NewInt(amt))) + got := sh(ctx, NewTestMsgCreateValidator(addr, val, sdk.NewInt(amt))) require.True(t, got.IsOK()) validatorUpdates := stake.EndBlocker(ctx, sk) keeper.AddValidators(ctx, validatorUpdates) @@ -384,7 +384,7 @@ func TestValidatorDippingInAndOut(t *testing.T) { addr, val, amt := addrs[0], pks[0], sdk.NewInt(amtInt) consAddr := sdk.ConsAddress(addr) sh := stake.NewHandler(sk) - got := sh(ctx, newTestMsgCreateValidator(addr, val, amt)) + got := sh(ctx, NewTestMsgCreateValidator(addr, val, amt)) require.True(t, got.IsOK()) validatorUpdates := stake.EndBlocker(ctx, sk) keeper.AddValidators(ctx, validatorUpdates) @@ -398,7 +398,7 @@ func TestValidatorDippingInAndOut(t *testing.T) { // validator kicked out of validator set newAmt := int64(101) - got = sh(ctx, newTestMsgCreateValidator(addrs[1], pks[1], sdk.NewInt(newAmt))) + got = sh(ctx, NewTestMsgCreateValidator(addrs[1], pks[1], sdk.NewInt(newAmt))) require.True(t, got.IsOK()) validatorUpdates = stake.EndBlocker(ctx, sk) require.Equal(t, 2, len(validatorUpdates)) diff --git a/x/stake/handler_test.go b/x/stake/handler_test.go index 083bb9a91..8755c91b0 100644 --- a/x/stake/handler_test.go +++ b/x/stake/handler_test.go @@ -847,11 +847,11 @@ func TestConflictingRedelegation(t *testing.T) { keeper.SetParams(ctx, params) // create the validators - msgCreateValidator := newTestMsgCreateValidator(validatorAddr, keep.PKs[0], 10) + msgCreateValidator := NewTestMsgCreateValidator(validatorAddr, keep.PKs[0], 10) got := handleMsgCreateValidator(ctx, msgCreateValidator, keeper) require.True(t, got.IsOK(), "expected no error on runMsgCreateValidator") - msgCreateValidator = newTestMsgCreateValidator(validatorAddr2, keep.PKs[1], 10) + msgCreateValidator = NewTestMsgCreateValidator(validatorAddr2, keep.PKs[1], 10) got = handleMsgCreateValidator(ctx, msgCreateValidator, keeper) require.True(t, got.IsOK(), "expected no error on runMsgCreateValidator") From abef57a9e67fc7bf26511f30419a5579c627e2d2 Mon Sep 17 00:00:00 2001 From: Christopher Goes Date: Tue, 16 Oct 2018 21:30:52 +0200 Subject: [PATCH 24/40] Utilize Tendermint power instead --- x/stake/keeper/key.go | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/x/stake/keeper/key.go b/x/stake/keeper/key.go index 9f6592ef3..aef685cb9 100644 --- a/x/stake/keeper/key.go +++ b/x/stake/keeper/key.go @@ -71,8 +71,15 @@ func GetBondedValidatorIndexKey(operator sdk.ValAddress) []byte { func getValidatorPowerRank(validator types.Validator) []byte { potentialPower := validator.Tokens - powerBytes := []byte(potentialPower.ToLeftPadded(maxDigitsForAccount)) // power big-endian (more powerful validators first) + + // todo: deal with cases above 2**64, ref https://github.com/cosmos/cosmos-sdk/issues/2439#issuecomment-427167556 + tendermintPower := potentialPower.RoundInt64() + tendermintPowerBytes := make([]byte, 8) + binary.BigEndian.PutUint64(tendermintPowerBytes[:], uint64(tendermintPower)) + + powerBytes := tendermintPowerBytes powerBytesLen := len(powerBytes) + // key is of format prefix || powerbytes || heightBytes || counterBytes key := make([]byte, 1+powerBytesLen+8+2) From d8038fcddc4ce474d92f2e1a66d9ed2eba162bfe Mon Sep 17 00:00:00 2001 From: Christopher Goes Date: Tue, 16 Oct 2018 21:44:23 +0200 Subject: [PATCH 25/40] Update testcases & pending --- PENDING.md | 1 + x/stake/keeper/key_test.go | 8 ++++---- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/PENDING.md b/PENDING.md index 376cd2daf..ca147ebb4 100644 --- a/PENDING.md +++ b/PENDING.md @@ -73,6 +73,7 @@ BREAKING CHANGES * [x/stake] \#2412 Added an unbonding validator queue to EndBlock to automatically update validator.Status when finished Unbonding * [x/stake] \#2500 Block conflicting redelegations until we add an index * [x/params] Global Paramstore refactored + * [x/stake] \#2508 Utilize Tendermint power for validator power key * Tendermint * Update tendermint version from v0.23.0 to v0.25.0, notable changes diff --git a/x/stake/keeper/key_test.go b/x/stake/keeper/key_test.go index 591e7d1bc..dfd6a44e7 100644 --- a/x/stake/keeper/key_test.go +++ b/x/stake/keeper/key_test.go @@ -35,10 +35,10 @@ func TestGetValidatorPowerRank(t *testing.T) { validator types.Validator wantHex string }{ - {val1, "05303030303030303030303030ffffffffffffffffffff"}, - {val2, "05303030303030303030303031ffffffffffffffffffff"}, - {val3, "05303030303030303030303130ffffffffffffffffffff"}, - {val4, "0531303939353131363237373736ffffffffffffffffffff"}, + {val1, "050000000000000000ffffffffffffffffffff"}, + {val2, "050000000000000001ffffffffffffffffffff"}, + {val3, "05000000000000000affffffffffffffffffff"}, + {val4, "050000010000000000ffffffffffffffffffff"}, } for i, tt := range tests { got := hex.EncodeToString(getValidatorPowerRank(tt.validator)) From 070bda3a2534f961138940819d77a4a62a68ecba Mon Sep 17 00:00:00 2001 From: Sunny Aggarwal Date: Wed, 17 Oct 2018 00:55:36 -0400 Subject: [PATCH 26/40] Merge PR #2515: Fix gov deposit query lcd non-determinism * non deterministic addr ordering * sorted addrs at gen * fixed comment * fixed names and passwords --- client/lcd/lcd_test.go | 54 ++++++++++++++++---------------- client/lcd/test_helpers.go | 64 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 90 insertions(+), 28 deletions(-) diff --git a/client/lcd/lcd_test.go b/client/lcd/lcd_test.go index f675afe14..94174398d 100644 --- a/client/lcd/lcd_test.go +++ b/client/lcd/lcd_test.go @@ -717,55 +717,53 @@ func TestUnjail(t *testing.T) { } func TestProposalsQuery(t *testing.T) { - name, password1 := "test", "1234567890" - name2, password2 := "test2", "1234567890" - addr, seed := CreateAddr(t, "test", password1, GetKeyBase(t)) - addr2, seed2 := CreateAddr(t, "test2", password2, GetKeyBase(t)) - cleanup, _, _, port := InitializeTestLCD(t, 1, []sdk.AccAddress{addr, addr2}) + addrs, seeds, names, passwords := CreateAddrs(t, GetKeyBase(t), 2) + + cleanup, _, _, port := InitializeTestLCD(t, 1, []sdk.AccAddress{addrs[0], addrs[1]}) defer cleanup() // Addr1 proposes (and deposits) proposals #1 and #2 - resultTx := doSubmitProposal(t, port, seed, name, password1, addr, 5) + resultTx := doSubmitProposal(t, port, seeds[0], names[0], passwords[0], addrs[0], 5) var proposalID1 int64 cdc.UnmarshalBinaryBare(resultTx.DeliverTx.GetData(), &proposalID1) tests.WaitForHeight(resultTx.Height+1, port) - resultTx = doSubmitProposal(t, port, seed, name, password1, addr, 5) + resultTx = doSubmitProposal(t, port, seeds[0], names[0], passwords[0], addrs[0], 5) var proposalID2 int64 cdc.UnmarshalBinaryBare(resultTx.DeliverTx.GetData(), &proposalID2) tests.WaitForHeight(resultTx.Height+1, port) // Addr2 proposes (and deposits) proposals #3 - resultTx = doSubmitProposal(t, port, seed2, name2, password2, addr2, 5) + resultTx = doSubmitProposal(t, port, seeds[1], names[1], passwords[1], addrs[1], 5) var proposalID3 int64 cdc.UnmarshalBinaryBare(resultTx.DeliverTx.GetData(), &proposalID3) tests.WaitForHeight(resultTx.Height+1, port) // Addr2 deposits on proposals #2 & #3 - resultTx = doDeposit(t, port, seed2, name2, password2, addr2, proposalID2, 5) + resultTx = doDeposit(t, port, seeds[1], names[1], passwords[1], addrs[1], proposalID2, 5) tests.WaitForHeight(resultTx.Height+1, port) - resultTx = doDeposit(t, port, seed2, name2, password2, addr2, proposalID3, 5) + resultTx = doDeposit(t, port, seeds[1], names[1], passwords[1], addrs[1], proposalID3, 5) tests.WaitForHeight(resultTx.Height+1, port) // check deposits match proposal and individual deposits deposits := getDeposits(t, port, proposalID1) require.Len(t, deposits, 1) - deposit := getDeposit(t, port, proposalID1, addr) + deposit := getDeposit(t, port, proposalID1, addrs[0]) require.Equal(t, deposit, deposits[0]) deposits = getDeposits(t, port, proposalID2) require.Len(t, deposits, 2) - deposit = getDeposit(t, port, proposalID2, addr) - require.Equal(t, deposit, deposits[0]) - deposit = getDeposit(t, port, proposalID2, addr2) - require.Equal(t, deposit, deposits[1]) + deposit = getDeposit(t, port, proposalID2, addrs[0]) + require.True(t, deposit.Equals(deposits[0])) + deposit = getDeposit(t, port, proposalID2, addrs[1]) + require.True(t, deposit.Equals(deposits[1])) deposits = getDeposits(t, port, proposalID3) require.Len(t, deposits, 1) - deposit = getDeposit(t, port, proposalID3, addr2) + deposit = getDeposit(t, port, proposalID3, addrs[1]) require.Equal(t, deposit, deposits[0]) // increasing the amount of the deposit should update the existing one - resultTx = doDeposit(t, port, seed, name, password1, addr, proposalID1, 1) + resultTx = doDeposit(t, port, seeds[0], names[0], passwords[0], addrs[0], proposalID1, 1) tests.WaitForHeight(resultTx.Height+1, port) deposits = getDeposits(t, port, proposalID1) @@ -782,13 +780,13 @@ func TestProposalsQuery(t *testing.T) { require.Equal(t, proposalID3, proposals[1].GetProposalID()) // Addr1 votes on proposals #2 & #3 - resultTx = doVote(t, port, seed, name, password1, addr, proposalID2) + resultTx = doVote(t, port, seeds[0], names[0], passwords[0], addrs[0], proposalID2) tests.WaitForHeight(resultTx.Height+1, port) - resultTx = doVote(t, port, seed, name, password1, addr, proposalID3) + resultTx = doVote(t, port, seeds[0], names[0], passwords[0], addrs[0], proposalID3) tests.WaitForHeight(resultTx.Height+1, port) // Addr2 votes on proposal #3 - resultTx = doVote(t, port, seed2, name2, password2, addr2, proposalID3) + resultTx = doVote(t, port, seeds[1], names[1], passwords[1], addrs[1], proposalID3) tests.WaitForHeight(resultTx.Height+1, port) // Test query all proposals @@ -798,37 +796,37 @@ func TestProposalsQuery(t *testing.T) { require.Equal(t, proposalID3, (proposals[2]).GetProposalID()) // Test query deposited by addr1 - proposals = getProposalsFilterDepositer(t, port, addr) + proposals = getProposalsFilterDepositer(t, port, addrs[0]) require.Equal(t, proposalID1, (proposals[0]).GetProposalID()) // Test query deposited by addr2 - proposals = getProposalsFilterDepositer(t, port, addr2) + proposals = getProposalsFilterDepositer(t, port, addrs[1]) require.Equal(t, proposalID2, (proposals[0]).GetProposalID()) require.Equal(t, proposalID3, (proposals[1]).GetProposalID()) // Test query voted by addr1 - proposals = getProposalsFilterVoter(t, port, addr) + proposals = getProposalsFilterVoter(t, port, addrs[0]) require.Equal(t, proposalID2, (proposals[0]).GetProposalID()) require.Equal(t, proposalID3, (proposals[1]).GetProposalID()) // Test query voted by addr2 - proposals = getProposalsFilterVoter(t, port, addr2) + proposals = getProposalsFilterVoter(t, port, addrs[1]) require.Equal(t, proposalID3, (proposals[0]).GetProposalID()) // Test query voted and deposited by addr1 - proposals = getProposalsFilterVoterDepositer(t, port, addr, addr) + proposals = getProposalsFilterVoterDepositer(t, port, addrs[0], addrs[0]) require.Equal(t, proposalID2, (proposals[0]).GetProposalID()) // Test query votes on Proposal 2 votes := getVotes(t, port, proposalID2) require.Len(t, votes, 1) - require.Equal(t, addr, votes[0].Voter) + require.Equal(t, addrs[0], votes[0].Voter) // Test query votes on Proposal 3 votes = getVotes(t, port, proposalID3) require.Len(t, votes, 2) - require.True(t, addr.String() == votes[0].Voter.String() || addr.String() == votes[1].Voter.String()) - require.True(t, addr2.String() == votes[0].Voter.String() || addr2.String() == votes[1].Voter.String()) + require.True(t, addrs[0].String() == votes[0].Voter.String() || addrs[0].String() == votes[1].Voter.String()) + require.True(t, addrs[1].String() == votes[0].Voter.String() || addrs[1].String() == votes[1].Voter.String()) } //_____________________________________________________________________________ diff --git a/client/lcd/test_helpers.go b/client/lcd/test_helpers.go index cd48b89ea..3585023cd 100644 --- a/client/lcd/test_helpers.go +++ b/client/lcd/test_helpers.go @@ -9,6 +9,7 @@ import ( "net/http" "os" "path/filepath" + "sort" "strings" "testing" @@ -111,6 +112,69 @@ func CreateAddr(t *testing.T, name, password string, kb crkeys.Keybase) (sdk.Acc return sdk.AccAddress(info.GetPubKey().Address()), seed } +// Type that combines an Address with the pnemonic of the private key to that address +type AddrSeed struct { + Address sdk.AccAddress + Seed string + Name string + Password string +} + +// CreateAddr adds multiple address to the key store and returns the addresses and associated seeds in lexographical order by address. +// It also requires that the keys could be created. +func CreateAddrs(t *testing.T, kb crkeys.Keybase, numAddrs int) (addrs []sdk.AccAddress, seeds, names, passwords []string) { + var ( + err error + info crkeys.Info + seed string + ) + + addrSeeds := AddrSeedSlice{} + + for i := 0; i < numAddrs; i++ { + name := fmt.Sprintf("test%d", i) + password := "1234567890" + info, seed, err = kb.CreateMnemonic(name, crkeys.English, password, crkeys.Secp256k1) + require.NoError(t, err) + addrSeeds = append(addrSeeds, AddrSeed{Address: sdk.AccAddress(info.GetPubKey().Address()), Seed: seed, Name: name, Password: password}) + } + + sort.Sort(addrSeeds) + + for i := range addrSeeds { + addrs = append(addrs, addrSeeds[i].Address) + seeds = append(seeds, addrSeeds[i].Seed) + names = append(names, addrSeeds[i].Name) + passwords = append(passwords, addrSeeds[i].Password) + } + + return addrs, seeds, names, passwords +} + +// implement `Interface` in sort package. +type AddrSeedSlice []AddrSeed + +func (b AddrSeedSlice) Len() int { + return len(b) +} + +// Sorts lexographically by Address +func (b AddrSeedSlice) Less(i, j int) bool { + // bytes package already implements Comparable for []byte. + switch bytes.Compare(b[i].Address.Bytes(), b[j].Address.Bytes()) { + case -1: + return true + case 0, 1: + return false + default: + panic("not fail-able with `bytes.Comparable` bounded [-1, 1].") + } +} + +func (b AddrSeedSlice) Swap(i, j int) { + b[j], b[i] = b[i], b[j] +} + // InitializeTestLCD starts Tendermint and the LCD in process, listening on // their respective sockets where nValidators is the total number of validators // and initAddrs are the accounts to initialize with some steak tokens. It From e419396bd199632b2478b7d83ccc936c4bfbb5d5 Mon Sep 17 00:00:00 2001 From: Sunny Aggarwal Date: Wed, 17 Oct 2018 02:09:19 -0400 Subject: [PATCH 27/40] fixed time key marshal (#2516) Do not use Amino Binary for key sorting. --- types/utils.go | 13 +++++++++++++ x/stake/keeper/key.go | 17 ++++++++++++++--- 2 files changed, 27 insertions(+), 3 deletions(-) diff --git a/types/utils.go b/types/utils.go index b196acb23..fa2999a18 100644 --- a/types/utils.go +++ b/types/utils.go @@ -2,6 +2,8 @@ package types import ( "encoding/json" + "time" + tcmd "github.com/tendermint/tendermint/cmd/tendermint/commands" tmtypes "github.com/tendermint/tendermint/types" ) @@ -34,6 +36,17 @@ func MustSortJSON(toSortJSON []byte) []byte { return js } +// Formats a time.Time into a []byte that can be sorted +func FormatTimeBytes(t time.Time) []byte { + return []byte(t.UTC().Round(0).Format("2006-01-02T15:04:05.000000000")) +} + +// Parses a []byte encoded using FormatTimeKey back into a time.Time +func ParseTimeBytes(bz []byte) (time.Time, error) { + str := string(bz) + return time.Parse("2006-01-02T15:04:05.000000000", str) +} + // DefaultChainID returns the chain ID from the genesis file if present. An // error is returned if the file cannot be read or parsed. // diff --git a/x/stake/keeper/key.go b/x/stake/keeper/key.go index aef685cb9..6e6ff54ff 100644 --- a/x/stake/keeper/key.go +++ b/x/stake/keeper/key.go @@ -34,6 +34,17 @@ var ( const maxDigitsForAccount = 12 // ~220,000,000 atoms created at launch +// Formats a time.Time into a []byte that can be sorted +func FormatTimeKey(t time.Time) []byte { + return []byte(t.UTC().Round(0).Format("2006-01-02T15:04:05.000000000")) +} + +// Parses a []byte encoded using FormatTimeKey back into a time.Time +func ParseTimeKey(bz []byte) (time.Time, error) { + str := string(bz) + return time.Parse("2006-01-02T15:04:05.000000000", str) +} + // gets the key for the validator with address // VALUE: stake/types.Validator func GetValidatorKey(operatorAddr sdk.ValAddress) []byte { @@ -96,7 +107,7 @@ func getValidatorPowerRank(validator types.Validator) []byte { // gets the prefix for all unbonding delegations from a delegator func GetValidatorQueueTimeKey(timestamp time.Time) []byte { - bz := types.MsgCdc.MustMarshalBinary(timestamp) + bz := sdk.FormatTimeBytes(timestamp) return append(ValidatorQueueKey, bz...) } @@ -154,7 +165,7 @@ func GetUBDsByValIndexKey(valAddr sdk.ValAddress) []byte { // gets the prefix for all unbonding delegations from a delegator func GetUnbondingDelegationTimeKey(timestamp time.Time) []byte { - bz := types.MsgCdc.MustMarshalBinary(timestamp) + bz := sdk.FormatTimeBytes(timestamp) return append(UnbondingQueueKey, bz...) } @@ -228,7 +239,7 @@ func GetREDKeyFromValDstIndexKey(indexKey []byte) []byte { // gets the prefix for all unbonding delegations from a delegator func GetRedelegationTimeKey(timestamp time.Time) []byte { - bz, _ := timestamp.MarshalBinary() + bz := sdk.FormatTimeBytes(timestamp) return append(RedelegationQueueKey, bz...) } From b51d41a2e315f031fbe94b42414638e8d6091bea Mon Sep 17 00:00:00 2001 From: Sunny Aggarwal Date: Wed, 17 Oct 2018 01:03:56 -0700 Subject: [PATCH 28/40] added sdk time format tests, const, and removed duplicate in stake module --- types/utils.go | 11 +++++++++-- types/utils_test.go | 21 +++++++++++++++++++++ x/stake/keeper/key.go | 11 ----------- 3 files changed, 30 insertions(+), 13 deletions(-) diff --git a/types/utils.go b/types/utils.go index fa2999a18..10ec85472 100644 --- a/types/utils.go +++ b/types/utils.go @@ -36,15 +36,22 @@ func MustSortJSON(toSortJSON []byte) []byte { return js } +// Slight modification of the RFC3339Nano but it right pads all zeros and drops the time zone info +const SortableTimeFormat = "2006-01-02T15:04:05.000000000" + // Formats a time.Time into a []byte that can be sorted func FormatTimeBytes(t time.Time) []byte { - return []byte(t.UTC().Round(0).Format("2006-01-02T15:04:05.000000000")) + return []byte(t.UTC().Round(0).Format(SortableTimeFormat)) } // Parses a []byte encoded using FormatTimeKey back into a time.Time func ParseTimeBytes(bz []byte) (time.Time, error) { str := string(bz) - return time.Parse("2006-01-02T15:04:05.000000000", str) + t, err := time.Parse(SortableTimeFormat, str) + if err != nil { + return t, err + } + return t.UTC().Round(0), nil } // DefaultChainID returns the chain ID from the genesis file if present. An diff --git a/types/utils_test.go b/types/utils_test.go index 05bc622e7..dbdd08c0a 100644 --- a/types/utils_test.go +++ b/types/utils_test.go @@ -2,6 +2,7 @@ package types import ( "testing" + "time" "github.com/stretchr/testify/require" ) @@ -43,3 +44,23 @@ func TestSortJSON(t *testing.T) { require.Equal(t, string(got), tc.want) } } + +func TestTimeFormatAndParse(t *testing.T) { + cases := []struct { + RFC3339NanoStr string + SDKSortableTimeStr string + Equal bool + }{ + {"2009-11-10T23:00:00Z", "2009-11-10T23:00:00.000000000", true}, + {"2011-01-10T23:10:05.758230235Z", "2011-01-10T23:10:05.758230235", true}, + } + for _, tc := range cases { + timeFromRFC, err := time.Parse(time.RFC3339Nano, tc.RFC3339NanoStr) + require.Nil(t, err) + timeFromSDKFormat, err := time.Parse(SortableTimeFormat, tc.SDKSortableTimeStr) + require.Nil(t, err) + + require.True(t, timeFromRFC.Equal(timeFromSDKFormat)) + require.Equal(t, timeFromRFC.Format(SortableTimeFormat), tc.SDKSortableTimeStr) + } +} diff --git a/x/stake/keeper/key.go b/x/stake/keeper/key.go index 6e6ff54ff..d5f2fc9d9 100644 --- a/x/stake/keeper/key.go +++ b/x/stake/keeper/key.go @@ -34,17 +34,6 @@ var ( const maxDigitsForAccount = 12 // ~220,000,000 atoms created at launch -// Formats a time.Time into a []byte that can be sorted -func FormatTimeKey(t time.Time) []byte { - return []byte(t.UTC().Round(0).Format("2006-01-02T15:04:05.000000000")) -} - -// Parses a []byte encoded using FormatTimeKey back into a time.Time -func ParseTimeKey(bz []byte) (time.Time, error) { - str := string(bz) - return time.Parse("2006-01-02T15:04:05.000000000", str) -} - // gets the key for the validator with address // VALUE: stake/types.Validator func GetValidatorKey(operatorAddr sdk.ValAddress) []byte { From e5b664f1544afcbfc6a70277d08d8ee78ae55f0c Mon Sep 17 00:00:00 2001 From: Federico Kunze Date: Wed, 17 Oct 2018 14:26:25 +0200 Subject: [PATCH 29/40] fixed naming for proposal id and time --- x/gov/msgs.go | 12 ++++++------ x/gov/proposals.go | 4 ++-- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/x/gov/msgs.go b/x/gov/msgs.go index a5a68ea21..ac2b2c170 100644 --- a/x/gov/msgs.go +++ b/x/gov/msgs.go @@ -84,9 +84,9 @@ func (msg MsgSubmitProposal) GetSigners() []sdk.AccAddress { //----------------------------------------------------------- // MsgDeposit type MsgDeposit struct { - ProposalID int64 `json:"proposalID"` // ID of the proposal - Depositer sdk.AccAddress `json:"depositer"` // Address of the depositer - Amount sdk.Coins `json:"amount"` // Coins to add to the proposal's deposit + ProposalID int64 `json:"proposal_id"` // ID of the proposal + Depositer sdk.AccAddress `json:"depositer"` // Address of the depositer + Amount sdk.Coins `json:"amount"` // Coins to add to the proposal's deposit } func NewMsgDeposit(depositer sdk.AccAddress, proposalID int64, amount sdk.Coins) MsgDeposit { @@ -145,9 +145,9 @@ func (msg MsgDeposit) GetSigners() []sdk.AccAddress { //----------------------------------------------------------- // MsgVote type MsgVote struct { - ProposalID int64 // proposalID of the proposal - Voter sdk.AccAddress // address of the voter - Option VoteOption // option from OptionSet chosen by the voter + ProposalID int64 `json:"proposal_id"` // ID of the proposal + Voter sdk.AccAddress `json:"voter"` // address of the voter + Option VoteOption `json:"option"` // option from OptionSet chosen by the voter } func NewMsgVote(voter sdk.AccAddress, proposalID int64, option VoteOption) MsgVote { diff --git a/x/gov/proposals.go b/x/gov/proposals.go index 37e29df70..9d1ba860a 100644 --- a/x/gov/proposals.go +++ b/x/gov/proposals.go @@ -68,10 +68,10 @@ type TextProposal struct { Status ProposalStatus `json:"proposal_status"` // Status of the Proposal {Pending, Active, Passed, Rejected} TallyResult TallyResult `json:"tally_result"` // Result of Tallys - SubmitTime time.Time `json:"submit_block"` // Height of the block where TxGovSubmitProposal was included + SubmitTime time.Time `json:"submit_time"` // Height of the block where TxGovSubmitProposal was included TotalDeposit sdk.Coins `json:"total_deposit"` // Current deposit on this proposal. Initial value is set at InitialDeposit - VotingStartTime time.Time `json:"voting_start_block"` // Height of the block where MinDeposit was reached. -1 if MinDeposit is not reached + VotingStartTime time.Time `json:"voting_start_time"` // Height of the block where MinDeposit was reached. -1 if MinDeposit is not reached } // Implements Proposal Interface From 1ee8deed2bcee90973729b8ff58e3e3787088eed Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Wed, 17 Oct 2018 13:37:58 -0400 Subject: [PATCH 30/40] Merge PR #2090: Improve crypto/keys and add `keys mnemonic` and `keys new` commands * crypto/keys/hd: use btcec to remove dep on tendermint * crypto/keys/bcrypt: improve comment about fork * crypto/keys/bip39 -> crypto/keys/bip39/fundraiser * crypto/keys/bip39: bring in fork of tyler-smith * crypto/keys/hd: update dep * crypto/keys: update deps * crypto/keys: move mintkey.go into new crypto/keys/mintkey * crypto/keys/hd: NewParamsFromPath * crypto/keys: keybase.Derive takes a bip39 passphrase too * crypto/keys/hd: BIP44Params.DerivationPath * gaiacli keys: add commands new and mnemonic * fix lints * minor fixes from review * update Gopkg.toml * add tendermint fork of golang.org/x/crypto * pin some transitive deps * crypto/keys/bcrypt: remove * remove in favour of fork of golang.org/x/crypto/bcrypt at github.com/tendermint/crypto/bcrypt * crypto/keys/bip39: remove completely * use fork cosmos/go-bip39 instead * Gopkg.toml: dont use master * Pull in changes from my PR * fixes from review * enforce min len for --unsafe-entropy * lint fix * feedback from review * fix dep --- Gopkg.lock | 38 ++-- Gopkg.toml | 39 +++- client/input.go | 14 +- client/keys/mnemonic.go | 78 +++++++ client/keys/new.go | 182 ++++++++++++++++ client/keys/root.go | 2 + client/lcd/lcd_test.go | 12 +- crypto/keys/bcrypt/base64.go | 35 ---- crypto/keys/bcrypt/bcrypt.go | 297 --------------------------- crypto/keys/bip39/wordcodec.go | 66 ------ crypto/keys/bip39/wordcodec_test.go | 15 -- crypto/keys/hd/fundraiser_test.go | 3 +- crypto/keys/hd/hdpath.go | 94 ++++++++- crypto/keys/hd/hdpath_test.go | 68 +++++- crypto/keys/keybase.go | 59 ++++-- crypto/keys/keybase_test.go | 9 +- crypto/keys/{ => mintkey}/mintkey.go | 59 ++++-- crypto/keys/types.go | 8 +- 18 files changed, 575 insertions(+), 503 deletions(-) create mode 100644 client/keys/mnemonic.go create mode 100644 client/keys/new.go delete mode 100644 crypto/keys/bcrypt/base64.go delete mode 100644 crypto/keys/bcrypt/bcrypt.go delete mode 100644 crypto/keys/bip39/wordcodec.go delete mode 100644 crypto/keys/bip39/wordcodec_test.go rename crypto/keys/{ => mintkey}/mintkey.go (79%) diff --git a/Gopkg.lock b/Gopkg.lock index 169893877..3817fd238 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -48,12 +48,19 @@ revision = "d4cc87b860166d00d6b5b9e0d3b3d71d6088d4d4" [[projects]] - digest = "1:ffe9824d294da03b391f44e1ae8281281b4afc1bdaa9588c9097785e3af10cec" + digest = "1:e8a3550c8786316675ff54ad6f09d265d129c9d986919af7f541afba50d87ce2" + name = "github.com/cosmos/go-bip39" + packages = ["."] + pruneopts = "UT" + revision = "52158e4697b87de16ed390e1bdaf813e581008fa" + +[[projects]] + digest = "1:a2c1d0e43bd3baaa071d1b9ed72c27d78169b2b269f71c105ac4ba34b1be4a39" name = "github.com/davecgh/go-spew" packages = ["spew"] pruneopts = "UT" - revision = "8991bc29aa16c548c550c7ff78260e27b9ab7c73" - version = "v1.1.1" + revision = "346938d642f2ec3594ed81d874461961cd0faa76" + version = "v1.1.0" [[projects]] digest = "1:c7644c73a3d23741fdba8a99b1464e021a224b7e205be497271a8003a15ca41b" @@ -390,8 +397,7 @@ version = "v1.2.1" [[projects]] - branch = "master" - digest = "1:f2ffd421680b0a3f7887501b3c6974bcf19217ecd301d0e2c9b681940ec363d5" + digest = "1:b3cfb8d82b1601a846417c3f31c03a7961862cb2c98dcf0959c473843e6d9a2b" name = "github.com/syndtr/goleveldb" packages = [ "leveldb", @@ -408,7 +414,7 @@ "leveldb/util", ] pruneopts = "UT" - revision = "ae2bd5eed72d46b28834ec3f60db3a3ebedd8dbd" + revision = "c4c61651e9e37fa117f53c5a906d3b63090d8445" [[projects]] digest = "1:605b6546f3f43745695298ec2d342d3e952b6d91cdf9f349bea9315f677d759f" @@ -524,10 +530,10 @@ version = "v0.1.0" [[projects]] - branch = "master" - digest = "1:27507554c6d4f060d8d700c31c624a43d3a92baa634e178ddc044bdf7d13b44a" + digest = "1:aaff04fa01d9b824fde6799759cc597b3ac3671b9ad31924c28b6557d0ee5284" name = "golang.org/x/crypto" packages = [ + "bcrypt", "blowfish", "chacha20poly1305", "curve25519", @@ -544,7 +550,8 @@ "salsa20/salsa", ] pruneopts = "UT" - revision = "e3636079e1a4c1f337f212cc5cd2aca108f6c900" + revision = "3764759f34a542a3aef74d6b02e35be7ab893bba" + source = "https://github.com/tendermint/crypto" [[projects]] digest = "1:d36f55a999540d29b6ea3c2ea29d71c76b1d9853fdcd3e5c5cb4836f2ba118f1" @@ -563,15 +570,14 @@ revision = "292b43bbf7cb8d35ddf40f8d5100ef3837cced3f" [[projects]] - branch = "master" - digest = "1:8bc8ecef1d63576cfab4d08b44a1f255dd67e5b019b7a44837d62380f266a91c" + digest = "1:4bd75b1a219bc590b05c976bbebf47f4e993314ebb5c7cbf2efe05a09a184d54" name = "golang.org/x/sys" packages = [ "cpu", "unix", ] pruneopts = "UT" - revision = "e4b3c5e9061176387e7cea65e4dc5853801f3fb7" + revision = "4e1fef5609515ec7a2cee7b5de30ba6d9b438cbf" [[projects]] digest = "1:a2ab62866c75542dd18d2b069fec854577a20211d7c0ea6ae746072a1dccdd18" @@ -597,12 +603,11 @@ version = "v0.3.0" [[projects]] - branch = "master" - digest = "1:1e6b0176e8c5dd8ff551af65c76f8b73a99bcf4d812cedff1b91711b7df4804c" + digest = "1:077c1c599507b3b3e9156d17d36e1e61928ee9b53a5b420f10f28ebd4a0b275c" name = "google.golang.org/genproto" packages = ["googleapis/rpc/status"] pruneopts = "UT" - revision = "c7e5094acea1ca1b899e2259d80a6b0f882f81f8" + revision = "383e8b2c3b9e36c4076b235b32537292176bae20" [[projects]] digest = "1:2dab32a43451e320e49608ff4542fdfc653c95dcc35d0065ec9c6c3dd540ed74" @@ -653,6 +658,7 @@ "github.com/bartekn/go-bip39", "github.com/bgentry/speakeasy", "github.com/btcsuite/btcd/btcec", + "github.com/cosmos/go-bip39", "github.com/golang/protobuf/proto", "github.com/gorilla/mux", "github.com/mattn/go-isatty", @@ -699,7 +705,7 @@ "github.com/tendermint/tendermint/types", "github.com/tendermint/tendermint/version", "github.com/zondax/ledger-goclient", - "golang.org/x/crypto/blowfish", + "golang.org/x/crypto/bcrypt", ] solver-name = "gps-cdcl" solver-version = 1 diff --git a/Gopkg.toml b/Gopkg.toml index b63234f46..05140857d 100644 --- a/Gopkg.toml +++ b/Gopkg.toml @@ -59,22 +59,51 @@ name = "github.com/tendermint/tendermint" version = "=0.25.0" +## deps without releases: + [[constraint]] - name = "github.com/bartekn/go-bip39" - revision = "a05967ea095d81c8fe4833776774cfaff8e5036c" + name = "golang.org/x/crypto" + source = "https://github.com/tendermint/crypto" + revision = "3764759f34a542a3aef74d6b02e35be7ab893bba" + +[[constraint]] + name = "github.com/cosmos/go-bip39" + revision = "52158e4697b87de16ed390e1bdaf813e581008fa" [[constraint]] name = "github.com/zondax/ledger-goclient" version = "=v0.1.0" +## transitive deps, with releases: + +[[override]] + name = "github.com/davecgh/go-spew" + version = "=v1.1.0" + [[constraint]] name = "github.com/rakyll/statik" version = "=v0.1.4" +[[constraint]] + name = "github.com/mitchellh/go-homedir" + version = "1.0.0" + +## transitive deps, without releases: +# + +[[override]] + name = "github.com/syndtr/goleveldb" + revision = "c4c61651e9e37fa117f53c5a906d3b63090d8445" + +[[override]] + name = "golang.org/x/sys" + revision = "4e1fef5609515ec7a2cee7b5de30ba6d9b438cbf" + +[[override]] + name = "google.golang.org/genproto" + revision = "383e8b2c3b9e36c4076b235b32537292176bae20" + [prune] go-tests = true unused-packages = true -[[constraint]] - name = "github.com/mitchellh/go-homedir" - version = "1.0.0" diff --git a/client/input.go b/client/input.go index a456f1b92..46c838e2e 100644 --- a/client/input.go +++ b/client/input.go @@ -7,7 +7,7 @@ import ( "strings" "github.com/bgentry/speakeasy" - isatty "github.com/mattn/go-isatty" + "github.com/mattn/go-isatty" "github.com/pkg/errors" ) @@ -44,13 +44,8 @@ func GetPassword(prompt string, buf *bufio.Reader) (pass string, err error) { // GetSeed will request a seed phrase from stdin and trims off // leading/trailing spaces -func GetSeed(prompt string, buf *bufio.Reader) (seed string, err error) { - if inputIsTty() { - fmt.Println(prompt) - } - seed, err = readLineFromBuf(buf) - seed = strings.TrimSpace(seed) - return +func GetSeed(prompt string, buf *bufio.Reader) (string, error) { + return GetString(prompt, buf) } // GetCheckPassword will prompt for a password twice to verify they @@ -133,5 +128,6 @@ func readLineFromBuf(buf *bufio.Reader) (string, error) { // PrintPrefixed prints a string with > prefixed for use in prompts. func PrintPrefixed(msg string) { - fmt.Printf("> %s\n", msg) + msg = fmt.Sprintf("> %s\n", msg) + fmt.Fprint(os.Stderr, msg) } diff --git a/client/keys/mnemonic.go b/client/keys/mnemonic.go new file mode 100644 index 000000000..33270a087 --- /dev/null +++ b/client/keys/mnemonic.go @@ -0,0 +1,78 @@ +package keys + +import ( + "crypto/sha256" + "fmt" + + "github.com/cosmos/cosmos-sdk/client" + "github.com/spf13/cobra" + + bip39 "github.com/bartekn/go-bip39" +) + +const ( + flagUserEntropy = "unsafe-entropy" + + mnemonicEntropySize = 256 +) + +func mnemonicKeyCommand() *cobra.Command { + cmd := &cobra.Command{ + Use: "mnemonic", + Short: "Compute the bip39 mnemonic for some input entropy", + Long: "Create a bip39 mnemonic, sometimes called a seed phrase, by reading from the system entropy. To pass your own entropy, use --unsafe-entropy", + RunE: runMnemonicCmd, + } + cmd.Flags().Bool(flagUserEntropy, false, "Prompt the user to supply their own entropy, instead of relying on the system") + return cmd +} + +func runMnemonicCmd(cmd *cobra.Command, args []string) error { + flags := cmd.Flags() + + userEntropy, _ := flags.GetBool(flagUserEntropy) + + var entropySeed []byte + + if userEntropy { + // prompt the user to enter some entropy + buf := client.BufferStdin() + inputEntropy, err := client.GetString("> WARNING: Generate at least 256-bits of entropy and enter the results here:", buf) + if err != nil { + return err + } + if len(inputEntropy) < 43 { + return fmt.Errorf("256-bits is 43 characters in Base-64, and 100 in Base-6. You entered %v, and probably want more", len(inputEntropy)) + } + conf, err := client.GetConfirmation( + fmt.Sprintf("> Input length: %d", len(inputEntropy)), + buf) + if err != nil { + return err + } + if !conf { + return nil + } + + // hash input entropy to get entropy seed + hashedEntropy := sha256.Sum256([]byte(inputEntropy)) + entropySeed = hashedEntropy[:] + printStep() + } else { + // read entropy seed straight from crypto.Rand + var err error + entropySeed, err = bip39.NewEntropy(mnemonicEntropySize) + if err != nil { + return err + } + } + + mnemonic, err := bip39.NewMnemonic(entropySeed[:]) + if err != nil { + return err + } + + fmt.Println(mnemonic) + + return nil +} diff --git a/client/keys/new.go b/client/keys/new.go new file mode 100644 index 000000000..3408eb9d0 --- /dev/null +++ b/client/keys/new.go @@ -0,0 +1,182 @@ +package keys + +import ( + "bufio" + "fmt" + "os" + + "github.com/bartekn/go-bip39" + "github.com/pkg/errors" + "github.com/spf13/cobra" + "github.com/spf13/viper" + + "github.com/cosmos/cosmos-sdk/client" + "github.com/cosmos/cosmos-sdk/crypto/keys" + "github.com/cosmos/cosmos-sdk/crypto/keys/hd" +) + +const ( + flagNewDefault = "default" + flagBIP44Path = "bip44-path" +) + +func newKeyCommand() *cobra.Command { + cmd := &cobra.Command{ + Use: "new", + Short: "Interactive command to derive a new private key, encrypt it, and save to disk", + Long: `Derive a new private key using an interactive command that will prompt you for each input. +Optionally specify a bip39 mnemonic, a bip39 passphrase to further secure the mnemonic, +and a bip32 HD path to derive a specific account. The key will be stored under the given name +and encrypted with the given password. The only input that is required is the encryption password.`, + Args: cobra.ExactArgs(1), + RunE: runNewCmd, + } + cmd.Flags().Bool(flagNewDefault, false, "Skip the prompts and just use the default values for everything") + cmd.Flags().Bool(client.FlagUseLedger, false, "Store a local reference to a private key on a Ledger device") + cmd.Flags().String(flagBIP44Path, "44'/118'/0'/0/0", "BIP44 path from which to derive a private key") + return cmd +} + +/* +input + - bip39 mnemonic + - bip39 passphrase + - bip44 path + - local encryption password +output + - armor encrypted private key (saved to file) +*/ +// nolint: gocyclo +func runNewCmd(cmd *cobra.Command, args []string) error { + name := args[0] + kb, err := GetKeyBase() + if err != nil { + return err + } + + buf := client.BufferStdin() + _, err = kb.Get(name) + if err == nil { + // account exists, ask for user confirmation + if response, err := client.GetConfirmation( + fmt.Sprintf("> override the existing name %s", name), buf); err != nil || !response { + return err + } + } + + flags := cmd.Flags() + useDefaults, _ := flags.GetBool(flagNewDefault) + bipFlag := flags.Lookup(flagBIP44Path) + + bip44Params, err := getBIP44ParamsAndPath(bipFlag.Value.String(), bipFlag.Changed || useDefaults) + if err != nil { + return err + } + + // if we're using ledger, only thing we need is the path. + // generate key and we're done. + if viper.GetBool(client.FlagUseLedger) { + + algo := keys.Secp256k1 // SigningAlgo(viper.GetString(flagType)) + path := bip44Params.DerivationPath() // ccrypto.DerivationPath{44, 118, account, 0, index} + info, err := kb.CreateLedger(name, path, algo) + if err != nil { + return err + } + printCreate(info, "") + return nil + } + + // get the mnemonic + var mnemonic string + if !useDefaults { + mnemonic, err = client.GetString("> Enter your bip39 mnemonic, or hit enter to generate one.", buf) + if err != nil { + return err + } + } + + if len(mnemonic) == 0 { + // read entropy seed straight from crypto.Rand and convert to mnemonic + entropySeed, err := bip39.NewEntropy(mnemonicEntropySize) + if err != nil { + return err + } + mnemonic, err = bip39.NewMnemonic(entropySeed[:]) + if err != nil { + return err + } + } + + // get bip39 passphrase + var bip39Passphrase string + if !useDefaults { + printStep() + printPrefixed("Enter your bip39 passphrase. This is combined with the mnemonic to derive the seed") + bip39Passphrase, err = client.GetString("> Most users should just hit enter to use the default, \"\"", buf) + if err != nil { + return err + } + + // if they use one, make them re-enter it + if len(bip39Passphrase) != 0 { + p2, err := client.GetString("Repeat the passphrase:", buf) + if err != nil { + return err + } + if bip39Passphrase != p2 { + return errors.New("passphrases don't match") + } + } + } + + // get the encryption password + printStep() + encryptPassword, err := client.GetCheckPassword( + "> Enter a passphrase to encrypt your key to disk:", + "> Repeat the passphrase:", buf) + if err != nil { + return err + } + + info, err := kb.Derive(name, mnemonic, bip39Passphrase, encryptPassword, *bip44Params) + if err != nil { + return err + } + _ = info + + return nil +} + +func getBIP44ParamsAndPath(path string, flagSet bool) (*hd.BIP44Params, error) { + buf := bufio.NewReader(os.Stdin) + bip44Path := path + + // if it wasnt set in the flag, give it a chance to overide interactively + if !flagSet { + printStep() + + var err error + bip44Path, err = client.GetString(fmt.Sprintf("> Enter your bip44 path. Default is %s\n", path), buf) + if err != nil { + return nil, err + } + if len(bip44Path) == 0 { + bip44Path = path + } + } + + bip44params, err := hd.NewParamsFromPath(bip44Path) + if err != nil { + return nil, err + } + return bip44params, nil +} + +func printPrefixed(msg string) { + fmt.Printf("> %s\n", msg) +} + +func printStep() { + printPrefixed("-------------------------------------") +} diff --git a/client/keys/root.go b/client/keys/root.go index a7a7d2e6f..b10cd2b55 100644 --- a/client/keys/root.go +++ b/client/keys/root.go @@ -19,6 +19,8 @@ func Commands() *cobra.Command { needs to sign with a private key.`, } cmd.AddCommand( + mnemonicKeyCommand(), + newKeyCommand(), addKeyCommand(), listKeysCmd, showKeysCmd(), diff --git a/client/lcd/lcd_test.go b/client/lcd/lcd_test.go index 94174398d..7aea16d86 100644 --- a/client/lcd/lcd_test.go +++ b/client/lcd/lcd_test.go @@ -10,20 +10,20 @@ import ( "testing" "time" - "github.com/cosmos/cosmos-sdk/client/rpc" - "github.com/cosmos/cosmos-sdk/client/tx" - p2p "github.com/tendermint/tendermint/p2p" - "github.com/spf13/viper" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - cryptoKeys "github.com/cosmos/cosmos-sdk/crypto/keys" + p2p "github.com/tendermint/tendermint/p2p" ctypes "github.com/tendermint/tendermint/rpc/core/types" client "github.com/cosmos/cosmos-sdk/client" keys "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/codec" + cryptoKeys "github.com/cosmos/cosmos-sdk/crypto/keys" + "github.com/cosmos/cosmos-sdk/crypto/keys/mintkey" tests "github.com/cosmos/cosmos-sdk/tests" sdk "github.com/cosmos/cosmos-sdk/types" version "github.com/cosmos/cosmos-sdk/version" @@ -35,7 +35,7 @@ import ( ) func init() { - cryptoKeys.BcryptSecurityParameter = 1 + mintkey.BcryptSecurityParameter = 1 version.Version = os.Getenv("VERSION") } diff --git a/crypto/keys/bcrypt/base64.go b/crypto/keys/bcrypt/base64.go deleted file mode 100644 index fc3116090..000000000 --- a/crypto/keys/bcrypt/base64.go +++ /dev/null @@ -1,35 +0,0 @@ -// Copyright 2011 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package bcrypt - -import "encoding/base64" - -const alphabet = "./ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789" - -var bcEncoding = base64.NewEncoding(alphabet) - -func base64Encode(src []byte) []byte { - n := bcEncoding.EncodedLen(len(src)) - dst := make([]byte, n) - bcEncoding.Encode(dst, src) - for dst[n-1] == '=' { - n-- - } - return dst[:n] -} - -func base64Decode(src []byte) ([]byte, error) { - numOfEquals := 4 - (len(src) % 4) - for i := 0; i < numOfEquals; i++ { - src = append(src, '=') - } - - dst := make([]byte, bcEncoding.DecodedLen(len(src))) - n, err := bcEncoding.Decode(dst, src) - if err != nil { - return nil, err - } - return dst[:n], nil -} diff --git a/crypto/keys/bcrypt/bcrypt.go b/crypto/keys/bcrypt/bcrypt.go deleted file mode 100644 index e24120bfb..000000000 --- a/crypto/keys/bcrypt/bcrypt.go +++ /dev/null @@ -1,297 +0,0 @@ -package bcrypt - -// MODIFIED BY TENDERMINT TO EXPOSE NONCE -// Copyright 2011 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -// Package bcrypt implements Provos and Mazières's bcrypt adaptive hashing -// algorithm. See http://www.usenix.org/event/usenix99/provos/provos.pdf - -// The code is a port of Provos and Mazières's C implementation. -import ( - "crypto/subtle" - "errors" - "fmt" - "strconv" - - "golang.org/x/crypto/blowfish" -) - -const ( - // the minimum allowable cost as passed in to GenerateFromPassword - MinCost int = 4 - // the maximum allowable cost as passed in to GenerateFromPassword - MaxCost int = 31 - // the cost that will actually be set if a cost below MinCost is passed into GenerateFromPassword - DefaultCost int = 10 -) - -// The error returned from CompareHashAndPassword when a password and hash do -// not match. -var ErrMismatchedHashAndPassword = errors.New("crypto/bcrypt: hashedPassword is not the hash of the given password") - -// The error returned from CompareHashAndPassword when a hash is too short to -// be a bcrypt hash. -var ErrHashTooShort = errors.New("crypto/bcrypt: hashedSecret too short to be a bcrypted password") - -// The error returned from CompareHashAndPassword when a hash was created with -// a bcrypt algorithm newer than this implementation. -type HashVersionTooNewError byte - -func (hv HashVersionTooNewError) Error() string { - return fmt.Sprintf("crypto/bcrypt: bcrypt algorithm version '%c' requested is newer than current version '%c'", byte(hv), majorVersion) -} - -// The error returned from CompareHashAndPassword when a hash starts with something other than '$' -type InvalidHashPrefixError byte - -// Format error -func (ih InvalidHashPrefixError) Error() string { - return fmt.Sprintf("crypto/bcrypt: bcrypt hashes must start with '$', but hashedSecret started with '%c'", byte(ih)) -} - -// Invalid bcrypt cost -type InvalidCostError int - -func (ic InvalidCostError) Error() string { - return fmt.Sprintf("crypto/bcrypt: cost %d is outside allowed range (%d,%d)", int(ic), int(MinCost), int(MaxCost)) // nolint: unconvert -} - -const ( - majorVersion = '2' - minorVersion = 'a' - maxSaltSize = 16 - maxCryptedHashSize = 23 - encodedSaltSize = 22 - encodedHashSize = 31 - minHashSize = 59 -) - -// magicCipherData is an IV for the 64 Blowfish encryption calls in -// bcrypt(). It's the string "OrpheanBeholderScryDoubt" in big-endian bytes. -var magicCipherData = []byte{ - 0x4f, 0x72, 0x70, 0x68, - 0x65, 0x61, 0x6e, 0x42, - 0x65, 0x68, 0x6f, 0x6c, - 0x64, 0x65, 0x72, 0x53, - 0x63, 0x72, 0x79, 0x44, - 0x6f, 0x75, 0x62, 0x74, -} - -type hashed struct { - hash []byte - salt []byte - cost int // allowed range is MinCost to MaxCost - major byte - minor byte -} - -// GenerateFromPassword returns the bcrypt hash of the password at the given -// cost. If the cost given is less than MinCost, the cost will be set to -// DefaultCost, instead. Use CompareHashAndPassword, as defined in this package, -// to compare the returned hashed password with its cleartext version. -func GenerateFromPassword(salt []byte, password []byte, cost int) ([]byte, error) { - if len(salt) != maxSaltSize { - return nil, fmt.Errorf("salt len must be %v", maxSaltSize) - } - p, err := newFromPassword(salt, password, cost) - if err != nil { - return nil, err - } - return p.Hash(), nil -} - -// CompareHashAndPassword compares a bcrypt hashed password with its possible -// plaintext equivalent. Returns nil on success, or an error on failure. -func CompareHashAndPassword(hashedPassword, password []byte) error { - p, err := newFromHash(hashedPassword) - if err != nil { - return err - } - - otherHash, err := bcrypt(password, p.cost, p.salt) - if err != nil { - return err - } - - otherP := &hashed{otherHash, p.salt, p.cost, p.major, p.minor} - if subtle.ConstantTimeCompare(p.Hash(), otherP.Hash()) == 1 { - return nil - } - - return ErrMismatchedHashAndPassword -} - -// Cost returns the hashing cost used to create the given hashed -// password. When, in the future, the hashing cost of a password system needs -// to be increased in order to adjust for greater computational power, this -// function allows one to establish which passwords need to be updated. -func Cost(hashedPassword []byte) (int, error) { - p, err := newFromHash(hashedPassword) - if err != nil { - return 0, err - } - return p.cost, nil -} - -func newFromPassword(salt []byte, password []byte, cost int) (*hashed, error) { - if cost < MinCost { - cost = DefaultCost - } - p := new(hashed) - p.major = majorVersion - p.minor = minorVersion - - err := checkCost(cost) - if err != nil { - return nil, err - } - p.cost = cost - - p.salt = base64Encode(salt) - hash, err := bcrypt(password, p.cost, p.salt) - if err != nil { - return nil, err - } - p.hash = hash - return p, err -} - -func newFromHash(hashedSecret []byte) (*hashed, error) { - if len(hashedSecret) < minHashSize { - return nil, ErrHashTooShort - } - p := new(hashed) - n, err := p.decodeVersion(hashedSecret) - if err != nil { - return nil, err - } - hashedSecret = hashedSecret[n:] - n, err = p.decodeCost(hashedSecret) - if err != nil { - return nil, err - } - hashedSecret = hashedSecret[n:] - - // The "+2" is here because we'll have to append at most 2 '=' to the salt - // when base64 decoding it in expensiveBlowfishSetup(). - p.salt = make([]byte, encodedSaltSize, encodedSaltSize+2) - copy(p.salt, hashedSecret[:encodedSaltSize]) - - hashedSecret = hashedSecret[encodedSaltSize:] - p.hash = make([]byte, len(hashedSecret)) - copy(p.hash, hashedSecret) - - return p, nil -} - -func bcrypt(password []byte, cost int, salt []byte) ([]byte, error) { - cipherData := make([]byte, len(magicCipherData)) - copy(cipherData, magicCipherData) - - c, err := expensiveBlowfishSetup(password, uint32(cost), salt) - if err != nil { - return nil, err - } - - for i := 0; i < 24; i += 8 { - for j := 0; j < 64; j++ { - c.Encrypt(cipherData[i:i+8], cipherData[i:i+8]) - } - } - - // Bug compatibility with C bcrypt implementations. We only encode 23 of - // the 24 bytes encrypted. - hsh := base64Encode(cipherData[:maxCryptedHashSize]) - return hsh, nil -} - -func expensiveBlowfishSetup(key []byte, cost uint32, salt []byte) (*blowfish.Cipher, error) { - - csalt, err := base64Decode(salt) - if err != nil { - return nil, err - } - - // Bug compatibility with C bcrypt implementations. They use the trailing - // NULL in the key string during expansion. - ckey := append(key, 0) - - c, err := blowfish.NewSaltedCipher(ckey, csalt) - if err != nil { - return nil, err - } - - var i, rounds uint64 - rounds = 1 << cost - for i = 0; i < rounds; i++ { - blowfish.ExpandKey(ckey, c) - blowfish.ExpandKey(csalt, c) - } - - return c, nil -} - -func (p *hashed) Hash() []byte { - arr := make([]byte, 60) - arr[0] = '$' - arr[1] = p.major - n := 2 - if p.minor != 0 { - arr[2] = p.minor - n = 3 - } - arr[n] = '$' - n++ - copy(arr[n:], []byte(fmt.Sprintf("%02d", p.cost))) - n += 2 - arr[n] = '$' - n++ - copy(arr[n:], p.salt) - n += encodedSaltSize - copy(arr[n:], p.hash) - n += encodedHashSize - return arr[:n] -} - -func (p *hashed) decodeVersion(sbytes []byte) (int, error) { - if sbytes[0] != '$' { - return -1, InvalidHashPrefixError(sbytes[0]) - } - if sbytes[1] > majorVersion { - return -1, HashVersionTooNewError(sbytes[1]) - } - p.major = sbytes[1] - n := 3 - if sbytes[2] != '$' { - p.minor = sbytes[2] - n++ - } - return n, nil -} - -// sbytes should begin where decodeVersion left off. -func (p *hashed) decodeCost(sbytes []byte) (int, error) { - cost, err := strconv.Atoi(string(sbytes[0:2])) - if err != nil { - return -1, err - } - err = checkCost(cost) - if err != nil { - return -1, err - } - p.cost = cost - return 3, nil -} - -func (p *hashed) String() string { - return fmt.Sprintf("&{hash: %#v, salt: %#v, cost: %d, major: %c, minor: %c}", string(p.hash), p.salt, p.cost, p.major, p.minor) -} - -func checkCost(cost int) error { - if cost < MinCost || cost > MaxCost { - return InvalidCostError(cost) - } - return nil -} diff --git a/crypto/keys/bip39/wordcodec.go b/crypto/keys/bip39/wordcodec.go deleted file mode 100644 index 074d1393c..000000000 --- a/crypto/keys/bip39/wordcodec.go +++ /dev/null @@ -1,66 +0,0 @@ -package bip39 - -import ( - "strings" - - "github.com/bartekn/go-bip39" -) - -// ValidSentenceLen defines the mnemonic sentence lengths supported by this BIP 39 library. -type ValidSentenceLen uint8 - -const ( - // FundRaiser is the sentence length used during the cosmos fundraiser (12 words). - FundRaiser ValidSentenceLen = 12 - // Size of the checksum employed for the fundraiser - FundRaiserChecksumSize = 4 - // FreshKey is the sentence length used for newly created keys (24 words). - FreshKey ValidSentenceLen = 24 - // Size of the checksum employed for new keys - FreshKeyChecksumSize = 8 -) - -// NewMnemonic will return a string consisting of the mnemonic words for -// the given sentence length. -func NewMnemonic(len ValidSentenceLen) (words []string, err error) { - // len = (entropySize + checksum) / 11 - var entropySize int - switch len { - case FundRaiser: - // entropySize = 128 - entropySize = int(len)*11 - FundRaiserChecksumSize - case FreshKey: - // entropySize = 256 - entropySize = int(len)*11 - FreshKeyChecksumSize - } - var entropy []byte - entropy, err = bip39.NewEntropy(entropySize) - if err != nil { - return - } - var mnemonic string - mnemonic, err = bip39.NewMnemonic(entropy) - if err != nil { - return - } - words = strings.Split(mnemonic, " ") - return -} - -// MnemonicToSeed creates a BIP 39 seed from the passed mnemonic (with an empty BIP 39 password). -// This method does not validate the mnemonics checksum. -func MnemonicToSeed(mne string) (seed []byte) { - // we do not checksum here... - seed = bip39.NewSeed(mne, "") - return -} - -// MnemonicToSeedWithErrChecking returns the same seed as MnemonicToSeed. -// It creates a BIP 39 seed from the passed mnemonic (with an empty BIP 39 password). -// -// Different from MnemonicToSeed it validates the checksum. -// For details on the checksum see the BIP 39 spec. -func MnemonicToSeedWithErrChecking(mne string) (seed []byte, err error) { - seed, err = bip39.NewSeedWithErrorChecking(mne, "") - return -} diff --git a/crypto/keys/bip39/wordcodec_test.go b/crypto/keys/bip39/wordcodec_test.go deleted file mode 100644 index a821239d7..000000000 --- a/crypto/keys/bip39/wordcodec_test.go +++ /dev/null @@ -1,15 +0,0 @@ -package bip39 - -import ( - "testing" - - "github.com/stretchr/testify/require" -) - -func TestWordCodec_NewMnemonic(t *testing.T) { - _, err := NewMnemonic(FundRaiser) - require.NoError(t, err, "unexpected error generating fundraiser mnemonic") - - _, err = NewMnemonic(FreshKey) - require.NoError(t, err, "unexpected error generating new 24-word mnemonic") -} diff --git a/crypto/keys/hd/fundraiser_test.go b/crypto/keys/hd/fundraiser_test.go index 84de09758..5e3cf06f3 100644 --- a/crypto/keys/hd/fundraiser_test.go +++ b/crypto/keys/hd/fundraiser_test.go @@ -7,9 +7,10 @@ import ( "io/ioutil" "testing" - "github.com/bartekn/go-bip39" "github.com/stretchr/testify/require" + "github.com/cosmos/go-bip39" + "github.com/tendermint/tendermint/crypto" "github.com/tendermint/tendermint/crypto/secp256k1" ) diff --git a/crypto/keys/hd/hdpath.go b/crypto/keys/hd/hdpath.go index ef2e6f783..112abe0b6 100644 --- a/crypto/keys/hd/hdpath.go +++ b/crypto/keys/hd/hdpath.go @@ -22,7 +22,6 @@ import ( "strings" "github.com/btcsuite/btcd/btcec" - "github.com/tendermint/tendermint/crypto/secp256k1" ) // BIP44Prefix is the parts of the BIP32 HD path that are fixed by what we used during the fundraiser. @@ -55,6 +54,77 @@ func NewParams(purpose, coinType, account uint32, change bool, addressIdx uint32 } } +// Parse the BIP44 path and unmarshal into the struct. +// nolint: gocyclo +func NewParamsFromPath(path string) (*BIP44Params, error) { + spl := strings.Split(path, "/") + if len(spl) != 5 { + return nil, fmt.Errorf("path length is wrong. Expected 5, got %d", len(spl)) + } + + if spl[0] != "44'" { + return nil, fmt.Errorf("first field in path must be 44', got %v", spl[0]) + } + + if !isHardened(spl[1]) || !isHardened(spl[2]) { + return nil, + fmt.Errorf("second and third field in path must be hardened (ie. contain the suffix ', got %v and %v", spl[1], spl[2]) + } + if isHardened(spl[3]) || isHardened(spl[4]) { + return nil, + fmt.Errorf("fourth and fifth field in path must not be hardened (ie. not contain the suffix ', got %v and %v", spl[3], spl[4]) + } + + purpose, err := hardenedInt(spl[0]) + if err != nil { + return nil, err + } + coinType, err := hardenedInt(spl[1]) + if err != nil { + return nil, err + } + account, err := hardenedInt(spl[2]) + if err != nil { + return nil, err + } + change, err := hardenedInt(spl[3]) + if err != nil { + return nil, err + } + if !(change == 0 || change == 1) { + return nil, fmt.Errorf("change field can only be 0 or 1") + } + + addressIdx, err := hardenedInt(spl[4]) + if err != nil { + return nil, err + } + + return &BIP44Params{ + purpose: purpose, + coinType: coinType, + account: account, + change: change > 0, + addressIdx: addressIdx, + }, nil +} + +func hardenedInt(field string) (uint32, error) { + field = strings.TrimSuffix(field, "'") + i, err := strconv.Atoi(field) + if err != nil { + return 0, err + } + if i < 0 { + return 0, fmt.Errorf("fields must not be negative. got %d", i) + } + return uint32(i), nil +} + +func isHardened(field string) bool { + return strings.HasSuffix(field, "'") +} + // NewFundraiserParams creates a BIP 44 parameter object from the params: // m / 44' / 118' / account' / 0 / address_index // The fixed parameters (purpose', coin_type', and change) are determined by what was used in the fundraiser. @@ -62,6 +132,21 @@ func NewFundraiserParams(account uint32, addressIdx uint32) *BIP44Params { return NewParams(44, 118, account, false, addressIdx) } +// Return the BIP44 fields as an array. +func (p BIP44Params) DerivationPath() []uint32 { + change := uint32(0) + if p.change { + change = 1 + } + return []uint32{ + p.purpose, + p.coinType, + p.account, + change, + p.addressIdx, + } +} + func (p BIP44Params) String() string { var changeStr string if p.change { @@ -128,10 +213,15 @@ func derivePrivateKey(privKeyBytes [32]byte, chainCode [32]byte, index uint32, h data = append([]byte{byte(0)}, privKeyBytes[:]...) } else { // this can't return an error: - pubkey := secp256k1.PrivKeySecp256k1(privKeyBytes).PubKey() + _, ecPub := btcec.PrivKeyFromBytes(btcec.S256(), privKeyBytes[:]) + pubkeyBytes := ecPub.SerializeCompressed() + data = pubkeyBytes + /* By using btcec, we can remove the dependency on tendermint/crypto/secp256k1 + pubkey := secp256k1.PrivKeySecp256k1(privKeyBytes).PubKey() public := pubkey.(secp256k1.PubKeySecp256k1) data = public[:] + */ } data = append(data, uint32ToBytes(index)...) data2, chainCode2 := i64(chainCode[:], data) diff --git a/crypto/keys/hd/hdpath_test.go b/crypto/keys/hd/hdpath_test.go index 58398655f..f310fc355 100644 --- a/crypto/keys/hd/hdpath_test.go +++ b/crypto/keys/hd/hdpath_test.go @@ -3,9 +3,20 @@ package hd import ( "encoding/hex" "fmt" - "github.com/cosmos/cosmos-sdk/crypto/keys/bip39" + "testing" + + "github.com/stretchr/testify/assert" + + "github.com/cosmos/go-bip39" ) +var defaultBIP39Passphrase = "" + +// return bip39 seed with empty passphrase +func mnemonicToSeed(mnemonic string) []byte { + return bip39.NewSeed(mnemonic, defaultBIP39Passphrase) +} + //nolint func ExampleStringifyPathParams() { path := NewParams(44, 0, 0, false, 0) @@ -13,10 +24,57 @@ func ExampleStringifyPathParams() { // Output: 44'/0'/0'/0/0 } +func TestParamsFromPath(t *testing.T) { + goodCases := []struct { + params *BIP44Params + path string + }{ + {&BIP44Params{44, 0, 0, false, 0}, "44'/0'/0'/0/0"}, + {&BIP44Params{44, 1, 0, false, 0}, "44'/1'/0'/0/0"}, + {&BIP44Params{44, 0, 1, false, 0}, "44'/0'/1'/0/0"}, + {&BIP44Params{44, 0, 0, true, 0}, "44'/0'/0'/1/0"}, + {&BIP44Params{44, 0, 0, false, 1}, "44'/0'/0'/0/1"}, + {&BIP44Params{44, 1, 1, true, 1}, "44'/1'/1'/1/1"}, + {&BIP44Params{44, 118, 52, true, 41}, "44'/118'/52'/1/41"}, + } + + for i, c := range goodCases { + params, err := NewParamsFromPath(c.path) + errStr := fmt.Sprintf("%d %v", i, c) + assert.NoError(t, err, errStr) + assert.EqualValues(t, c.params, params, errStr) + assert.Equal(t, c.path, c.params.String()) + } + + badCases := []struct { + path string + }{ + {"43'/0'/0'/0/0"}, // doesnt start with 44 + {"44'/1'/0'/0/0/5"}, // too many fields + {"44'/0'/1'/0"}, // too few fields + {"44'/0'/0'/2/0"}, // change field can only be 0/1 + {"44/0'/0'/0/0"}, // first field needs ' + {"44'/0/0'/0/0"}, // second field needs ' + {"44'/0'/0/0/0"}, // third field needs ' + {"44'/0'/0'/0'/0"}, // fourth field must not have ' + {"44'/0'/0'/0/0'"}, // fifth field must not have ' + {"44'/-1'/0'/0/0"}, // no negatives + {"44'/0'/0'/-1/0"}, // no negatives + } + + for i, c := range badCases { + params, err := NewParamsFromPath(c.path) + errStr := fmt.Sprintf("%d %v", i, c) + assert.Nil(t, params, errStr) + assert.Error(t, err, errStr) + } + +} + //nolint func ExampleSomeBIP32TestVecs() { - seed := bip39.MnemonicToSeed("barrel original fuel morning among eternal " + + seed := mnemonicToSeed("barrel original fuel morning among eternal " + "filter ball stove pluck matrix mechanic") master, ch := ComputeMastersFromSeed(seed) fmt.Println("keys from fundraiser test-vector (cosmos, bitcoin, ether)") @@ -35,14 +93,14 @@ func ExampleSomeBIP32TestVecs() { fmt.Println("keys generated via https://coinomi.com/recovery-phrase-tool.html") fmt.Println() - seed = bip39.MnemonicToSeed( + seed = mnemonicToSeed( "advice process birth april short trust crater change bacon monkey medal garment " + "gorilla ranch hour rival razor call lunar mention taste vacant woman sister") master, ch = ComputeMastersFromSeed(seed) priv, _ = DerivePrivateKeyForPath(master, ch, "44'/1'/1'/0/4") fmt.Println(hex.EncodeToString(priv[:])) - seed = bip39.MnemonicToSeed("idea naive region square margin day captain habit " + + seed = mnemonicToSeed("idea naive region square margin day captain habit " + "gun second farm pact pulse someone armed") master, ch = ComputeMastersFromSeed(seed) priv, _ = DerivePrivateKeyForPath(master, ch, "44'/0'/0'/0/420") @@ -53,7 +111,7 @@ func ExampleSomeBIP32TestVecs() { fmt.Println() // bip32 path: m/0/7 - seed = bip39.MnemonicToSeed("monitor flock loyal sick object grunt duty ride develop assault harsh history") + seed = mnemonicToSeed("monitor flock loyal sick object grunt duty ride develop assault harsh history") master, ch = ComputeMastersFromSeed(seed) priv, _ = DerivePrivateKeyForPath(master, ch, "0/7") fmt.Println(hex.EncodeToString(priv[:])) diff --git a/crypto/keys/keybase.go b/crypto/keys/keybase.go index 99632e764..ddcd0357e 100644 --- a/crypto/keys/keybase.go +++ b/crypto/keys/keybase.go @@ -6,11 +6,15 @@ import ( "os" "strings" - "github.com/cosmos/cosmos-sdk/crypto" - "github.com/cosmos/cosmos-sdk/crypto/keys/bip39" - "github.com/cosmos/cosmos-sdk/crypto/keys/hd" - "github.com/cosmos/cosmos-sdk/types" "github.com/pkg/errors" + + "github.com/cosmos/go-bip39" + + "github.com/cosmos/cosmos-sdk/crypto" + "github.com/cosmos/cosmos-sdk/crypto/keys/hd" + "github.com/cosmos/cosmos-sdk/crypto/keys/mintkey" + "github.com/cosmos/cosmos-sdk/types" + tmcrypto "github.com/tendermint/tendermint/crypto" "github.com/tendermint/tendermint/crypto/encoding/amino" "github.com/tendermint/tendermint/crypto/secp256k1" @@ -46,6 +50,14 @@ const ( infoSuffix = "info" ) +const ( + // used for deriving seed from mnemonic + defaultBIP39Passphrase = "" + + // bits of entropy to draw when creating a mnemonic + defaultEntropySize = 256 +) + var ( // ErrUnsupportedSigningAlgo is raised when the caller tries to use a // different signing scheme than secp256k1. @@ -85,12 +97,17 @@ func (kb dbKeybase) CreateMnemonic(name string, language Language, passwd string } // default number of words (24): - mnemonicS, err := bip39.NewMnemonic(bip39.FreshKey) + // this generates a mnemonic directly from the number of words by reading system entropy. + entropy, err := bip39.NewEntropy(defaultEntropySize) if err != nil { return } - mnemonic = strings.Join(mnemonicS, " ") - seed := bip39.MnemonicToSeed(mnemonic) + mnemonic, err = bip39.NewMnemonic(entropy) + if err != nil { + return + } + + seed := bip39.NewSeed(mnemonic, defaultBIP39Passphrase) info, err = kb.persistDerivedKey(seed, passwd, name, hd.FullFundraiserPath) return } @@ -102,7 +119,7 @@ func (kb dbKeybase) CreateKey(name, mnemonic, passwd string) (info Info, err err err = fmt.Errorf("recovering only works with 12 word (fundraiser) or 24 word mnemonics, got: %v words", len(words)) return } - seed, err := bip39.MnemonicToSeedWithErrChecking(mnemonic) + seed, err := bip39.NewSeedWithErrorChecking(mnemonic, defaultBIP39Passphrase) if err != nil { return } @@ -119,7 +136,7 @@ func (kb dbKeybase) CreateFundraiserKey(name, mnemonic, passwd string) (info Inf err = fmt.Errorf("recovering only works with 12 word (fundraiser), got: %v words", len(words)) return } - seed, err := bip39.MnemonicToSeedWithErrChecking(mnemonic) + seed, err := bip39.NewSeedWithErrorChecking(mnemonic, defaultBIP39Passphrase) if err != nil { return } @@ -127,12 +144,12 @@ func (kb dbKeybase) CreateFundraiserKey(name, mnemonic, passwd string) (info Inf return } -func (kb dbKeybase) Derive(name, mnemonic, passwd string, params hd.BIP44Params) (info Info, err error) { - seed, err := bip39.MnemonicToSeedWithErrChecking(mnemonic) +func (kb dbKeybase) Derive(name, mnemonic, bip39Passphrase, encryptPasswd string, params hd.BIP44Params) (info Info, err error) { + seed, err := bip39.NewSeedWithErrorChecking(mnemonic, bip39Passphrase) if err != nil { return } - info, err = kb.persistDerivedKey(seed, passwd, name, params.String()) + info, err = kb.persistDerivedKey(seed, encryptPasswd, name, params.String()) return } @@ -229,7 +246,7 @@ func (kb dbKeybase) Sign(name, passphrase string, msg []byte) (sig []byte, pub t err = fmt.Errorf("private key not available") return } - priv, err = unarmorDecryptPrivKey(linfo.PrivKeyArmor, passphrase) + priv, err = mintkey.UnarmorDecryptPrivKey(linfo.PrivKeyArmor, passphrase) if err != nil { return nil, nil, err } @@ -279,7 +296,7 @@ func (kb dbKeybase) ExportPrivateKeyObject(name string, passphrase string) (tmcr err = fmt.Errorf("private key not available") return nil, err } - priv, err = unarmorDecryptPrivKey(linfo.PrivKeyArmor, passphrase) + priv, err = mintkey.UnarmorDecryptPrivKey(linfo.PrivKeyArmor, passphrase) if err != nil { return nil, err } @@ -296,7 +313,7 @@ func (kb dbKeybase) Export(name string) (armor string, err error) { if bz == nil { return "", fmt.Errorf("no key to export with name %s", name) } - return armorInfoBytes(bz), nil + return mintkey.ArmorInfoBytes(bz), nil } // ExportPubKey returns public keys in ASCII armored format. @@ -311,7 +328,7 @@ func (kb dbKeybase) ExportPubKey(name string) (armor string, err error) { if err != nil { return } - return armorPubKeyBytes(info.GetPubKey().Bytes()), nil + return mintkey.ArmorPubKeyBytes(info.GetPubKey().Bytes()), nil } func (kb dbKeybase) Import(name string, armor string) (err error) { @@ -319,7 +336,7 @@ func (kb dbKeybase) Import(name string, armor string) (err error) { if len(bz) > 0 { return errors.New("Cannot overwrite data for name " + name) } - infoBytes, err := unarmorInfoBytes(armor) + infoBytes, err := mintkey.UnarmorInfoBytes(armor) if err != nil { return } @@ -335,7 +352,7 @@ func (kb dbKeybase) ImportPubKey(name string, armor string) (err error) { if len(bz) > 0 { return errors.New("Cannot overwrite data for name " + name) } - pubBytes, err := unarmorPubKeyBytes(armor) + pubBytes, err := mintkey.UnarmorPubKeyBytes(armor) if err != nil { return } @@ -360,7 +377,7 @@ func (kb dbKeybase) Delete(name, passphrase string) error { switch info.(type) { case localInfo: linfo := info.(localInfo) - _, err = unarmorDecryptPrivKey(linfo.PrivKeyArmor, passphrase) + _, err = mintkey.UnarmorDecryptPrivKey(linfo.PrivKeyArmor, passphrase) if err != nil { return err } @@ -394,7 +411,7 @@ func (kb dbKeybase) Update(name, oldpass string, getNewpass func() (string, erro switch info.(type) { case localInfo: linfo := info.(localInfo) - key, err := unarmorDecryptPrivKey(linfo.PrivKeyArmor, oldpass) + key, err := mintkey.UnarmorDecryptPrivKey(linfo.PrivKeyArmor, oldpass) if err != nil { return err } @@ -411,7 +428,7 @@ func (kb dbKeybase) Update(name, oldpass string, getNewpass func() (string, erro func (kb dbKeybase) writeLocalKey(priv tmcrypto.PrivKey, name, passphrase string) Info { // encrypt private key using passphrase - privArmor := encryptArmorPrivKey(priv, passphrase) + privArmor := mintkey.EncryptArmorPrivKey(priv, passphrase) // make Info pub := priv.PubKey() info := newLocalInfo(name, pub, privArmor) diff --git a/crypto/keys/keybase_test.go b/crypto/keys/keybase_test.go index 3273c229a..cafa2382a 100644 --- a/crypto/keys/keybase_test.go +++ b/crypto/keys/keybase_test.go @@ -4,9 +4,12 @@ import ( "fmt" "testing" - "github.com/cosmos/cosmos-sdk/crypto/keys/hd" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/cosmos/cosmos-sdk/crypto/keys/hd" + "github.com/cosmos/cosmos-sdk/crypto/keys/mintkey" + "github.com/tendermint/tendermint/crypto" "github.com/tendermint/tendermint/crypto/ed25519" @@ -15,7 +18,7 @@ import ( ) func init() { - BcryptSecurityParameter = 1 + mintkey.BcryptSecurityParameter = 1 } // TestKeyManagement makes sure we can manipulate these keys well @@ -342,7 +345,7 @@ func TestSeedPhrase(t *testing.T) { // let us re-create it from the mnemonic-phrase params := *hd.NewFundraiserParams(0, 0) - newInfo, err := cstore.Derive(n2, mnemonic, p2, params) + newInfo, err := cstore.Derive(n2, mnemonic, defaultBIP39Passphrase, p2, params) require.NoError(t, err) require.Equal(t, n2, newInfo.GetName()) require.Equal(t, info.GetPubKey().Address(), newInfo.GetPubKey().Address()) diff --git a/crypto/keys/mintkey.go b/crypto/keys/mintkey/mintkey.go similarity index 79% rename from crypto/keys/mintkey.go rename to crypto/keys/mintkey/mintkey.go index 70e1bc44e..80377920f 100644 --- a/crypto/keys/mintkey.go +++ b/crypto/keys/mintkey/mintkey.go @@ -1,16 +1,17 @@ -package keys +package mintkey import ( "encoding/hex" "fmt" - cmn "github.com/tendermint/tendermint/libs/common" + "golang.org/x/crypto/bcrypt" - "github.com/cosmos/cosmos-sdk/crypto/keys/bcrypt" "github.com/tendermint/tendermint/crypto" "github.com/tendermint/tendermint/crypto/armor" "github.com/tendermint/tendermint/crypto/encoding/amino" "github.com/tendermint/tendermint/crypto/xsalsa20symmetric" + + cmn "github.com/tendermint/tendermint/libs/common" ) const ( @@ -34,11 +35,16 @@ const ( // TODO: Consider increasing default var BcryptSecurityParameter = 12 -func armorInfoBytes(bz []byte) string { +//----------------------------------------------------------------- +// add armor + +// Armor the InfoBytes +func ArmorInfoBytes(bz []byte) string { return armorBytes(bz, blockTypeKeyInfo) } -func armorPubKeyBytes(bz []byte) string { +// Armor the PubKeyBytes +func ArmorPubKeyBytes(bz []byte) string { return armorBytes(bz, blockTypePubKey) } @@ -50,11 +56,16 @@ func armorBytes(bz []byte, blockType string) string { return armor.EncodeArmor(blockType, header, bz) } -func unarmorInfoBytes(armorStr string) (bz []byte, err error) { +//----------------------------------------------------------------- +// remove armor + +// Unarmor the InfoBytes +func UnarmorInfoBytes(armorStr string) (bz []byte, err error) { return unarmorBytes(armorStr, blockTypeKeyInfo) } -func unarmorPubKeyBytes(armorStr string) (bz []byte, err error) { +// Unarmor the PubKeyBytes +func UnarmorPubKeyBytes(armorStr string) (bz []byte, err error) { return unarmorBytes(armorStr, blockTypePubKey) } @@ -74,7 +85,11 @@ func unarmorBytes(armorStr, blockType string) (bz []byte, err error) { return } -func encryptArmorPrivKey(privKey crypto.PrivKey, passphrase string) string { +//----------------------------------------------------------------- +// encrypt/decrypt with armor + +// Encrypt and armor the private key. +func EncryptArmorPrivKey(privKey crypto.PrivKey, passphrase string) string { saltBytes, encBytes := encryptPrivKey(privKey, passphrase) header := map[string]string{ "kdf": "bcrypt", @@ -84,7 +99,22 @@ func encryptArmorPrivKey(privKey crypto.PrivKey, passphrase string) string { return armorStr } -func unarmorDecryptPrivKey(armorStr string, passphrase string) (crypto.PrivKey, error) { +// encrypt the given privKey with the passphrase using a randomly +// generated salt and the xsalsa20 cipher. returns the salt and the +// encrypted priv key. +func encryptPrivKey(privKey crypto.PrivKey, passphrase string) (saltBytes []byte, encBytes []byte) { + saltBytes = crypto.CRandBytes(16) + key, err := bcrypt.GenerateFromPassword(saltBytes, []byte(passphrase), BcryptSecurityParameter) + if err != nil { + cmn.Exit("Error generating bcrypt key from passphrase: " + err.Error()) + } + key = crypto.Sha256(key) // get 32 bytes + privKeyBytes := privKey.Bytes() + return saltBytes, xsalsa20symmetric.EncryptSymmetric(privKeyBytes, key) +} + +// Unarmor and decrypt the private key. +func UnarmorDecryptPrivKey(armorStr string, passphrase string) (crypto.PrivKey, error) { var privKey crypto.PrivKey blockType, header, encBytes, err := armor.DecodeArmor(armorStr) if err != nil { @@ -107,17 +137,6 @@ func unarmorDecryptPrivKey(armorStr string, passphrase string) (crypto.PrivKey, return privKey, err } -func encryptPrivKey(privKey crypto.PrivKey, passphrase string) (saltBytes []byte, encBytes []byte) { - saltBytes = crypto.CRandBytes(16) - key, err := bcrypt.GenerateFromPassword(saltBytes, []byte(passphrase), BcryptSecurityParameter) - if err != nil { - cmn.Exit("Error generating bcrypt key from passphrase: " + err.Error()) - } - key = crypto.Sha256(key) // Get 32 bytes - privKeyBytes := privKey.Bytes() - return saltBytes, xsalsa20symmetric.EncryptSymmetric(privKeyBytes, key) -} - func decryptPrivKey(saltBytes []byte, encBytes []byte, passphrase string) (privKey crypto.PrivKey, err error) { key, err := bcrypt.GenerateFromPassword(saltBytes, []byte(passphrase), BcryptSecurityParameter) if err != nil { diff --git a/crypto/keys/types.go b/crypto/keys/types.go index c5e97d5fb..f5194748a 100644 --- a/crypto/keys/types.go +++ b/crypto/keys/types.go @@ -26,8 +26,12 @@ type Keybase interface { CreateKey(name, mnemonic, passwd string) (info Info, err error) // CreateFundraiserKey takes a mnemonic and derives, a password CreateFundraiserKey(name, mnemonic, passwd string) (info Info, err error) - // Derive derives a key from the passed mnemonic using a BIP44 path. - Derive(name, mnemonic, passwd string, params hd.BIP44Params) (Info, error) + // Compute a BIP39 seed from th mnemonic and bip39Passwd. + // Derive private key from the seed using the BIP44 params. + // Encrypt the key to disk using encryptPasswd. + // See https://github.com/cosmos/cosmos-sdk/issues/2095 + Derive(name, mnemonic, bip39Passwd, + encryptPasswd string, params hd.BIP44Params) (Info, error) // Create, store, and return a new Ledger key reference CreateLedger(name string, path ccrypto.DerivationPath, algo SigningAlgo) (info Info, err error) From e845569d168e95f6f6732266c2379d3f26d13bd8 Mon Sep 17 00:00:00 2001 From: David Braun Date: Wed, 17 Oct 2018 14:25:02 -0400 Subject: [PATCH 31/40] Merge PR #2496: Add example to Swagger specification for /keys/seed --- PENDING.md | 1 + client/lcd/swagger-ui/swagger.yaml | 1 + 2 files changed, 2 insertions(+) diff --git a/PENDING.md b/PENDING.md index ca147ebb4..c278722b9 100644 --- a/PENDING.md +++ b/PENDING.md @@ -141,6 +141,7 @@ IMPROVEMENTS * Gaia REST API (`gaiacli advanced rest-server`) * [x/stake] [\#2000](https://github.com/cosmos/cosmos-sdk/issues/2000) Added tests for new staking endpoints + * [gaia-lite] Added example to Swagger specification for /keys/seed. * Gaia CLI (`gaiacli`) * [cli] #2060 removed `--select` from `block` command diff --git a/client/lcd/swagger-ui/swagger.yaml b/client/lcd/swagger-ui/swagger.yaml index b7fb70ff3..cde32050d 100644 --- a/client/lcd/swagger-ui/swagger.yaml +++ b/client/lcd/swagger-ui/swagger.yaml @@ -400,6 +400,7 @@ paths: description: 16 word Seed schema: type: string + example: blossom pool issue kidney elevator blame furnace winter account merry vessel security depend exact travel bargain problem jelly rural net again mask roast chest /keys/{name}/recover: post: summary: Recover a account from a seed From 2e59e2626b5f41bfcf477142bf94abb09e7162f0 Mon Sep 17 00:00:00 2001 From: Jack Zampolin Date: Wed, 17 Oct 2018 12:41:36 -0700 Subject: [PATCH 32/40] Merge PR #2510: Update PRIORITIES.md --- docs/PRIORITIES.md | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/docs/PRIORITIES.md b/docs/PRIORITIES.md index 20eb20e1a..02aa495bb 100644 --- a/docs/PRIORITIES.md +++ b/docs/PRIORITIES.md @@ -1,13 +1,8 @@ # Post-0.25/GoS Pre-Release -## Staking/Slashing/Stability +## Staking / Slashing - Stability -- Other slashing issues blocking for launch - [#1256](https://github.com/cosmos/cosmos-sdk/issues/1256) -- Miscellaneous minor staking issues - - [List here](https://github.com/cosmos/cosmos-sdk/issues?utf8=%E2%9C%93&q=is%3Aissue+is%3Aopen+label%3Astaking+label%3Aprelaunch) - - Need to figure out scope of work here to estimate time - - @rigelrozanski to start next -- Consider "tombstone" / "prison" - double-sign and you can never validate again - https://github.com/cosmos/cosmos-sdk/issues/2363 +- [Prelaunch Issues](https://github.com/cosmos/cosmos-sdk/issues?utf8=%E2%9C%93&q=is%3Aissue+is%3Aopen+label%3Astaking+label%3Aprelaunch-2.0) ## Multisig @@ -16,24 +11,29 @@ ## ABCI Changes -- Need to update for new ABCI changes when/if they land - error string, tags are list of lists +- CheckEvidence/DeliverEvidence +- CheckTx/DeliverTx ordering semantics +- ABCI Error string update (only on the SDK side) - Need to verify correct proposer reward semantics -- CheckEvidence/DeliverEvidence, CheckTx/DeliverTx ordering semantics ## Gas -- Charge for transaction size -- Decide what "one gas" corresponds to (standard hardware benchmarks?) -- More benchmarking -- Consider charging based on maximum depth of IAVL tree iteration +- Write specification and explainer document for Gas in Cosmos + * Charge for transaction size + * Decide what "one gas" corresponds to (standard hardware benchmarks?) + * Consider charging based on maximum depth of IAVL tree iteration - Test out gas estimation in CLI and LCD and ensure the UX works ## LCD -- Bianje working on implementation of ICS standards -- Additional PR incoming for ICS 22 and ICS 23 -- Decide what ought to be ICS-standardized and what ought not to +- Bianje working with Voyager team (@fedekunze) to complete implementation and documentation. +## Documentation + +- gaiad / gaiacli +- LCD +- Each module +- Tags [#1780](https://github.com/cosmos/cosmos-sdk/issues/1780) # Lower priority ## Governance v2 @@ -41,6 +41,6 @@ - Circuit breaker - https://github.com/cosmos/cosmos-sdk/issues/926 - Parameter change proposals (roughly the same implementation as circuit breaker) -## Documentation +## Staking / Slashing - Stability -- gaiad / gaiacli / gaialite documentation! +- Consider "tombstone" / "prison" - double-sign and you can never validate again - https://github.com/cosmos/cosmos-sdk/issues/2363 From ad05d7cc334d8a1648180e9b8b96350a51bd4467 Mon Sep 17 00:00:00 2001 From: Alessio Treglia Date: Wed, 17 Oct 2018 15:49:57 -0700 Subject: [PATCH 33/40] Merge PR #2523: Remove 'import "C"' from client/utils package --- client/utils/rest.go | 1 - 1 file changed, 1 deletion(-) diff --git a/client/utils/rest.go b/client/utils/rest.go index 07b258ce9..effb7bb73 100644 --- a/client/utils/rest.go +++ b/client/utils/rest.go @@ -1,6 +1,5 @@ package utils -import "C" import ( "fmt" "io/ioutil" From ce23ad41ce7bc53494c80a2d73fdc1c9e39f7ec7 Mon Sep 17 00:00:00 2001 From: Zach Date: Wed, 17 Oct 2018 20:46:03 -0400 Subject: [PATCH 34/40] Merge PR #2447: Cleanup docs/light --- docs/.vuepress/config.js | 8 + docs/light/api.md | 961 ------------------ docs/light/load_balancer.md | 203 ---- docs/light/todo.md | 16 - docs/{light => lite}/getting_started.md | 31 +- docs/{light => lite}/pics/C2H.png | Bin docs/{light => lite}/pics/H2C.png | Bin docs/{light => lite}/pics/MA.png | Bin docs/{light => lite}/pics/absence1.png | Bin docs/{light => lite}/pics/absence2.png | Bin docs/{light => lite}/pics/absence3.png | Bin docs/{light => lite}/pics/architecture.png | Bin docs/{light => lite}/pics/changeProcess.png | Bin .../{light => lite}/pics/commitValidation.png | Bin docs/{light => lite}/pics/create-account.png | Bin docs/{light => lite}/pics/deposit.png | Bin docs/{light => lite}/pics/existProof.png | Bin docs/{light => lite}/pics/high-level.png | Bin .../pics/light-client-architecture.png | Bin .../pics/loadbalanceDiagram.png | Bin .../{light => lite}/pics/simpleMerkleTree.png | Bin docs/{light => lite}/pics/substoreProof.png | Bin docs/{light => lite}/pics/transfer-tokens.png | Bin docs/{light => lite}/pics/transfer.png | Bin docs/{light => lite}/pics/trustPropagate.png | Bin .../pics/updateValidatorToHeight.png | Bin .../pics/validatorSetChange.png | Bin docs/{light => lite}/pics/withdraw.png | Bin docs/{light => lite}/readme.md | 43 +- docs/{light => lite}/specification.md | 147 --- 30 files changed, 29 insertions(+), 1380 deletions(-) delete mode 100644 docs/light/api.md delete mode 100644 docs/light/load_balancer.md delete mode 100644 docs/light/todo.md rename docs/{light => lite}/getting_started.md (59%) rename docs/{light => lite}/pics/C2H.png (100%) rename docs/{light => lite}/pics/H2C.png (100%) rename docs/{light => lite}/pics/MA.png (100%) rename docs/{light => lite}/pics/absence1.png (100%) rename docs/{light => lite}/pics/absence2.png (100%) rename docs/{light => lite}/pics/absence3.png (100%) rename docs/{light => lite}/pics/architecture.png (100%) rename docs/{light => lite}/pics/changeProcess.png (100%) rename docs/{light => lite}/pics/commitValidation.png (100%) rename docs/{light => lite}/pics/create-account.png (100%) rename docs/{light => lite}/pics/deposit.png (100%) rename docs/{light => lite}/pics/existProof.png (100%) rename docs/{light => lite}/pics/high-level.png (100%) rename docs/{light => lite}/pics/light-client-architecture.png (100%) rename docs/{light => lite}/pics/loadbalanceDiagram.png (100%) rename docs/{light => lite}/pics/simpleMerkleTree.png (100%) rename docs/{light => lite}/pics/substoreProof.png (100%) rename docs/{light => lite}/pics/transfer-tokens.png (100%) rename docs/{light => lite}/pics/transfer.png (100%) rename docs/{light => lite}/pics/trustPropagate.png (100%) rename docs/{light => lite}/pics/updateValidatorToHeight.png (100%) rename docs/{light => lite}/pics/validatorSetChange.png (100%) rename docs/{light => lite}/pics/withdraw.png (100%) rename docs/{light => lite}/readme.md (81%) rename docs/{light => lite}/specification.md (67%) diff --git a/docs/.vuepress/config.js b/docs/.vuepress/config.js index 4042de37e..5f09b5bb0 100644 --- a/docs/.vuepress/config.js +++ b/docs/.vuepress/config.js @@ -68,6 +68,14 @@ module.exports = { "/sdk/sdk-by-examples/simple-governance/running-the-application" ] }, + { + title: "Light Client", + collapsable: false, + children: [ + "/light/", + "/light/getting_started" + ] + }, { title: "Lotion JS", collapsable: false, diff --git a/docs/light/api.md b/docs/light/api.md deleted file mode 100644 index 7fbf9fbe1..000000000 --- a/docs/light/api.md +++ /dev/null @@ -1,961 +0,0 @@ -# Cosmos Hub (Gaia-Lite) LCD API - -This document describes the API that is exposed by the specific Light Client Daemon (LCD) implementation of the Cosmos Hub (Gaia). Those APIs are exposed by a REST server and can easily be accessed over HTTP/WS (websocket) -connections. - -The complete API is comprised of the sub-APIs of different modules. The modules in the Cosmos Hub (Gaia-Lite) API are: - -- ICS0 ([TendermintAPI](api.md#ics0---tendermintapi)) -- ICS1 ([KeyAPI](api.md#ics1---keyapi)) -- ICS20 ([TokenAPI](api.md#ics20---tokenapi)) -- ICS21 ([StakingAPI](api.md#ics21---stakingapi)) -- ICS22 ([GovernanceAPI](api.md#ics22---governanceapi)) -- ICS23 ([SlashingAPI](api.md#ics23---slashingapi)) - -Error messages my change and should be only used for display purposes. Error messages should not be -used for determining the error type. - -## ICS0 - TendermintAPI - -Exposes the same functionality as the Tendermint RPC from a full node. It aims to have a very similar API. - -### POST /txs - -- **URL**: `/txs` -- Query Parameters: - - `?return={sync|async|block}`: - - `return=sync`: Waits for the transaction to pass `CheckTx` - - `return=async`: Returns the request immediately after it is received by the server - - `return=block`: waits for for the transaction to be committed in a block -- POST Body: - -```json -{ - "transaction": "string", - "return": "string", -} -``` - -- Returns on success: - -```json -{ - "rest api":"2.0", - "code":200, - "error":"", - "result":{ - "code":0, - "hash":"0D33F2F03A5234F38706E43004489E061AC40A2E", - "data":"", - "log":"" - } -} -``` - -## ICS1 - KeyAPI - -This API exposes all functionality needed for key creation, signing and management. - -### GET /keys - -- **URL**: `/keys` -- **Functionality**: Gets a list of all the keys. -- Returns on success: - -```json -{ - "rest api":"1.0", - "code":200, - "error":"", - "result":{ - "account":[ - { - "name":"monkey", - "address":"cosmos1fedh326uxqlxs8ph9ej7cf854gz7fd5zlym5pd", - "pub_key":"cosmospub1zcjduc3q8s8ha96ry4xc5xvjp9tr9w9p0e5lk5y0rpjs5epsfxs4wmf72x3shvus0t" - }, - { - "name":"test", - "address":"cosmos1thlqhjqw78zvcy0ua4ldj9gnazqzavyw4eske2", - "pub_key":"cosmospub1zcjduc3qyx6hlf825jcnj39adpkaxjer95q7yvy25yhfj3dmqy2ctev0rxmse9cuak" - } - ], - "block_height":5241 - } -} -``` - -### POST /keys - -- **URL**: `/keys` -- **Functionality**: Create a new key. -- POST Body: - -```json -{ - "name": "string", - "password": "string", - "seed": "string", -} -``` - -Returns on success: - -```json -{ - "rest api":"1.0", - "code":200, - "error":"", - "result":{ - "seed":"crime carpet recycle erase simple prepare moral dentist fee cause pitch trigger when velvet animal abandon" - } -} -``` - -### GET /keys/{name} - -- **URL** : `/keys/{name}` -- **Functionality**: Get the information for the specified key. -- Returns on success: - -```json -{ - "rest api":"1.0", - "code":200, - "error":"", - "result":{ - "name":"test", - "address":"cosmos1thlqhjqw78zvcy0ua4ldj9gnazqzavyw4eske2", - "pub_key":"cosmospub1zcjduc3qyx6hlf825jcnj39adpkaxjer95q7yvy25yhfj3dmqy2ctev0rxmse9cuak" - } -} -``` - -### PUT /keys/{name} - -- **URL** : `/keys/{name}` -- **Functionality**: Change the encryption password for the specified key. -- PUT Body: - -```json -{ - "old_password": "string", - "new_password": "string", -} -``` - -- Returns on success: - -```json -{ - "rest api":"2.0", - "code":200, - "error":"", - "result":{} -} -``` - -### DELETE /keys/{name} - -- **URL**: `/keys/{name}` -- **Functionality**: Delete the specified key. -- DELETE Body: - -```json -{ - "password": "string", -} -``` - -- Returns on success: - -```json -{ - "rest api":"1.0", - "code":200, - "error":"", - "result":{} -} -``` - -### POST /keys/{name}/recover - -- **URL**: `/keys/{name}/recover` -- **Functionality**: Recover your key from seed and persist it encrypted with the password. -- POST Body: - -```json -{ - "password": "string", - "seed": "string", -} -``` - -- Returns on success: - -```json -{ - "rest api":"1.0", - "code":200, - "error":"", - "result":{ - "address":"BD607C37147656A507A5A521AA9446EB72B2C907" - } -} -``` - -### GET /auth/accounts/{address} - -- **URL**: `/auth/accounts/{address}` -- **Functionality**: Query the information of an account . -- Returns on success: - -```json -{ - "rest api":"1.0", - "code":200, - "error":"", - "result":{ - "address": "82A57F8575BDFA22F5164C75361A21D2B0E11089", - "public_key": "PubKeyEd25519{A0EEEED3C9CE1A6988DEBFE347635834A1C0EBA0B4BB1125896A7072D22E650D}", - "coins": [ - {"atom": 300}, - {"photon": 15} - ], - "account_number": 1, - "sequence": 7 - } -} -``` - -### POST /auth/tx/sign - -- **URL**: `/auth/tx/sign` -- **Functionality**: Sign a transaction without broadcasting it. -- Returns on success: - -```json -{ - "rest api": "1.0", - "code": 200, - "error": "", - "result": { - "type": "auth/StdTx", - "value": { - "msg": [ - { - "type": "cosmos-sdk/Send", - "value": { - "inputs": [ - { - "address": "cosmos1ql4ekxkujf3xllk8h5ldhhgh4ylpu7kwec6q3d", - "coins": [ - { - "denom": "steak", - "amount": "1" - } - ] - } - ], - "outputs": [ - { - "address": "cosmos1dhyqhg4px33ed3erqymls0hc7q2lxw9hhfwklj", - "coins": [ - { - "denom": "steak", - "amount": "1" - } - ] - } - ] - } - } - ], - "fee": { - "amount": [ - { - "denom": "", - "amount": "0" - } - ], - "gas": "2742" - }, - "signatures": [ - { - "pub_key": { - "type": "tendermint/PubKeySecp256k1", - "value": "A2A/f2IYnrPUMTMqhwN81oas9jurtfcsvxdeLlNw3gGy" - }, - "signature": "MEQCIGVn73y9QLwBa3vmsAD1bs3ygX75Wo+lAFSAUDs431ZPAiBWAf2amyqTCDXE9J87rL9QF9sd5JvVMt7goGSuamPJwg==", - "account_number": "1", - "sequence": "0" - } - ], - "memo": "" - } - } -} -``` - -### POST /auth/tx/broadcast - -- **URL**: `/auth/broadcast` -- **Functionality**: Broadcast a transaction. -- Returns on success: - -```json -{ - "rest api": "1.0", - "code": 200, - "error": "", - "result": - { - "check_tx": { - "log": "Msg 0: ", - "gasWanted": "2742", - "gasUsed": "1002" - }, - "deliver_tx": { - "log": "Msg 0: ", - "gasWanted": "2742", - "gasUsed": "2742", - "tags": [ - { - "key": "c2VuZGVy", - "value": "Y29zbW9zMXdjNTl6ZXU3MmNjdnp5ZWR6ZGE1N3pzcXh2eXZ2Y3poaHBhdDI4" - }, - { - "key": "cmVjaXBpZW50", - "value": "Y29zbW9zMTJ4OTNmY3V2azg3M3o1ejZnejRlNTl2dnlxcXp1eDdzdDcwNWd5" - } - ] - }, - "hash": "784314784503582AC885BD6FB0D2A5B79FF703A7", - "height": "5" - } -} -``` - -## ICS20 - TokenAPI - -The TokenAPI exposes all functionality needed to query account balances and send transactions. - -### GET /bank/balance/{account} - -- **URL**: `/bank/balance/{account}` -- **Functionality**: Query the specified account's balance. -- Returns on success: - -```json -{ - "rest api":"2.0", - "code":200, - "error":"", - "result": { - "atom":1000, - "photon":500, - "ether":20 - } -} -``` - -### POST /bank/transfers - -- **URL**: `/bank/transfers` -- **Functionality**: Create a transfer in the bank module. -- POST Body: - -```json -{ - "amount": [ - { - "denom": "string", - "amount": 64, - } - ], - "name": "string", - "password": "string", - "chain_id": "string", - "account_number": 64, - "sequence": 64, - "gas": 64, -} -``` - -- Returns on success: - -```json -{ - "rest api":"2.0", - "code":200, - "error":"", - "result":{ - "transaction":"TODO:" - } -} -``` - -## ICS21 - StakingAPI - -The StakingAPI exposes all functionality needed for validation and delegation in Proof-of-Stake. - -### GET /stake/delegators/{delegatorAddr} - -- **URL**: `/stake/delegators/{delegatorAddr}` -- **Functionality**: Get all delegations (delegation, undelegation) from a delegator. -- Returns on success: - -```json -{ - "rest api":"2.1", - "code":200, - "error":"", - "result": { - "atom":1000, - "photon":500, - "ether":20 - } -} -``` - -### GET /stake/delegators/{delegatorAddr}/validators - -- **URL**: `/stake/delegators/{delegatorAddr}/validators` -- **Functionality**: Query all validators that a delegator is bonded to. -- Returns on success: - -```json -{ - "rest api":"2.1", - "code":200, - "error":"", - "result":{} -} -``` - -### GET /stake/delegators/{delegatorAddr}/validators/{validatorAddr} - -- **URL**: `/stake/delegators/{delegatorAddr}/validators/{validatorAddr}` -- **Functionality**: Query a validator that a delegator is bonded to -- Returns on success: - -```json -{ - "rest api":"2.1", - "code":200, - "error":"", - "result":{} -} -``` - -### GET /stake/delegators/{delegatorAddr}/txs - -- **URL**: `/stake/delegators/{delegatorAddr}/txs` -- **Functionality**: Get all staking txs (i.e msgs) from a delegator. -- Returns on success: - -```json -{ - "rest api":"2.1", - "code":200, - "error":"", - "result":{ - "transaction":"TODO" - } -} -``` - -### POST /stake/delegators/{delegatorAddr}/delegations - -- **URL**: `/stake/delegators/{delegatorAddr}/delegations` -- **Functionality**: Submit or edit a delegation. - -- POST Body: - -```json -{ - "name": "string", - "password": "string", - "chain_id": "string", - "account_number": 64, - "sequence": 64, - "gas": 64, - "delegations": [ - { - "delegator_addr": "string", - "validator_addr": "string", - "delegation": { - "denom": "string", - "amount": 1234 - } - } - ], - "begin_unbondings": [ - { - "delegator_addr": "string", - "validator_addr": "string", - "shares": "string", - } - ], - "complete_unbondings": [ - { - "delegator_addr": "string", - "validator_addr": "string", - } - ], - "begin_redelegates": [ - { - "delegator_addr": "string", - "validator_src_addr": "string", - "validator_dst_addr": "string", - "shares": "string", - } - ], - "complete_redelegates": [ - { - "delegator_addr": "string", - "validator_src_addr": "string", - "validator_dst_addr": "string", - } - ] -} - -``` - -- Returns on success: - -```json -{ - "rest api":"2.1", - "code":200, - "error":"", - "result":{ - "transaction":"TODO" - } -} -``` - -### GET /stake/delegators/{delegatorAddr}/delegations/{validatorAddr} - -- **URL**: `/stake/delegators/{delegatorAddr}/delegations/{validatorAddr}` -- **Functionality**: Query the current delegation status between a delegator and a validator. -- Returns on success: - -```json -{ - "rest api":"2.1", - "code":200, - "error":"", - "result":{ - "transaction":"TODO" - } -} -``` - -### GET /stake/delegators/{delegatorAddr}/unbonding_delegations/{validatorAddr} - -- **URL**: `/stake/delegators/{delegatorAddr}/unbonding_delegations/{validatorAddr}` -- **Functionality**: Query all unbonding delegations between a delegator and a validator. -- Returns on success: - -```json -{ - "rest api":"2.1", - "code":200, - "error":"", - "result":{ - "transaction":"TODO" - } -} -``` - -### GET /stake/validators - -- **URL**: `/stake/validators` -- **Functionality**: Get all validator candidates. -- Returns on success: - -```json -{ - "rest api":"2.1", - "code":200, - "error":"", - "result":{ - "transaction":"TODO" - } -} -``` - -### GET /stake/validators/{validatorAddr} - -- **URL**: `/stake/validators/{validatorAddr}` -- **Functionality**: Query the information from a single validator. -- Returns on success: - -```json -{ - "rest api":"2.1", - "code":200, - "error":"", - "result":{ - "transaction":"TODO" - } -} -``` - -### GET /stake/parameters - -- **URL**: `/stake/parameters` -- **Functionality**: Get the current value of staking parameters. -- Returns on success: - -```json -{ - "rest api":"2.1", - "code":200, - "error":"", - "result":{ - "inflation_rate_change": 1300000000, - "inflation_max": 2000000000, - "inflation_min": 700000000, - "goal_bonded": 6700000000, - "unbonding_time": "72h0m0s", - "max_validators": 100, - "bond_denom": "atom" - } -} -``` - -### GET /stake/pool - -- **URL**: `/stake/pool` -- **Functionality**: Get the current value of the dynamic parameters of the current state (*i.e* `Pool`). -- Returns on success: - -```json -{ - "rest api":"2.1", - "code":200, - "error":"", - "result":{ - "loose_tokens": 0, - "bonded_tokens": 0, - "inflation_last_time": "1970-01-01 01:00:00 +0100 CET", - "inflation": 700000000, - "date_last_commission_reset": 0, - "prev_bonded_shares": 0, - } -} -``` - -## ICS22 - GovernanceAPI - -The GovernanceAPI exposes all functionality needed for casting votes on plain text, software upgrades and parameter change proposals. - -### GET /gov/proposals - -- **URL**: `/gov/proposals` -- **Functionality**: Query all submited proposals -- Response on Success: - -```json -{ - "rest api":"2.2", - "code":200, - "error":"", - "result":{ - "proposals":[ - "TODO" - ] - } -} -``` - -### POST /gov/proposals - -- **URL**: `/gov/proposals` -- **Functionality**: Submit a proposal -- POST Body: - -```js -{ - "base_req": { - // Name of key to use - "name": "string", - // Password for that key - "password": "string", - "chain_id": "string", - "account_number": 64, - "sequence": 64, - "gas": 64 - }, - // Title of the proposal - "title": "string", - // Description of the proposal - "description": "string", - // PlainTextProposal supported now. SoftwareUpgradeProposal and other types may be supported soon - "proposal_type": "string", - // A cosmos address - "proposer": "string", - "initial_deposit": [ - { - "denom": "string", - "amount": 64, - } - ] -} -``` - -- Returns on success: - -```json -{ - "rest api":"2.2", - "code":200, - "error":"", - "result":{ - "TODO": "TODO", - } -} -``` - -### GET /gov/proposals/{proposal-id} - -- **URL**: `/gov/proposals/{proposal-id}` -- **Functionality**: Query a proposal -- Response on Success: - -```json -{ - "rest api":"2.2", - "code":200, - "error":"", - "result":{ - "proposal_id": 1, - "title": "Example title", - "description": "a larger description with the details of the proposal", - "proposal_type": "Text", - "proposal_status": "DepositPeriod", - "tally_result": { - "yes": 0, - "abstain": 0, - "no": 0, - "no_with_veto": 0 - }, - "submit_block": 5238512, - "total_deposit": {"atom": 50}, - "voting_start_block": -1 - } -} -``` - -### POST /gov/proposals/{proposal-id}/deposits - -- **URL**: `/gov/proposals/{proposal-id}/deposits` -- **Functionality**: Submit or rise a deposit to a proposal in order to make it active -- POST Body: - -```json -{ - "base_req": { - "name": "string", - "password": "string", - "chain_id": "string", - "account_number": 0, - "sequence": 0, - "gas": "simulate" - }, - "depositer": "string", - "amount": 0, -} -``` - -- Returns on success: - -```json -{ - "rest api":"2.2", - "code":200, - "error":"", - "result":{ - "TODO": "TODO", - } -} -``` - -### GET /gov/proposals/{proposal-id}/deposits/{address} - -- **URL**: `/gov/proposals/{proposal-id}/deposits/{address}` -- **Functionality**: Query a validator's deposit to submit a proposal -- Returns on success: - -```json -{ - "rest api":"2.2", - "code":200, - "error":"", - "result":{ - "amount": {"atom": 150}, - "depositer": "cosmos1fedh326uxqlxs8ph9ej7cf854gz7fd5zlym5pd", - "proposal-id": 16 - } -} -``` - -### GET /gov/proposals/{proposal-id}/tally - -- **URL**: `/gov/proposals/{proposal-id}/tally` -- **Functionality**: Get the tally of a given proposal. -- Returns on success: - -```json -{ - "rest api":"2.2", - "code":200, - "error":"", - "result": { - "yes": 0, - "abstain": 0, - "no": 0, - "no_with_veto": 0 - } -} -``` - - - -### GET /gov/proposals/{proposal-id}/votes - -- **URL**: `/gov/proposals/{proposal-id}/votes` -- **Functionality**: Query all votes from a specific proposal -- Returns on success: - -```json -{ - "rest api":"2.2", - "code":200, - "error":"", - "result": [ - { - "proposal-id": 1, - "voter": "cosmos1fedh326uxqlxs8ph9ej7cf854gz7fd5zlym5pd", - "option": "no_with_veto" - }, - { - "proposal-id": 1, - "voter": "cosmos1849m9wncrqp6v4tkss6a3j8uzvuv0cp7f75lrq", - "option": "yes" - }, - ] -} -``` - - - -### POST /gov/proposals/{proposal-id}/votes - -- **URL**: `/gov/proposals/{proposal-id}/votes` -- **Functionality**: Vote for a specific proposal -- POST Body: - -```js -{ - "base_req": { - "name": "string", - "password": "string", - "chain_id": "string", - "account_number": 0, - "sequence": 0, - "gas": "simulate" - }, - // A cosmos address - "voter": "string", - // Value of the vote option `Yes`, `No` `Abstain`, `NoWithVeto` - "option": "string", -} -``` - -- Returns on success: - -```json -{ - "rest api":"2.2", - "code":200, - "error":"", - "result":{ - "TODO": "TODO", - } -} -``` - -### GET /gov/proposals/{proposal-id}/votes/{address} - -- **URL** : `/gov/proposals/{proposal-id}/votes/{address}` -- **Functionality**: Get the current `Option` submited by an address -- Returns on success: - -```json -{ - "rest api":"2.2", - "code":200, - "error":"", - "result":{ - "proposal-id": 1, - "voter": "cosmos1fedh326uxqlxs8ph9ej7cf854gz7fd5zlym5pd", - "option": "no_with_veto" - } -} -``` - -## ICS23 - SlashingAPI - -The SlashingAPI exposes all functionalities needed to slash (*i.e* penalize) validators and delegators in Proof-of-Stake. The penalization is a fine of the staking coin and jail time, defined by governance parameters. During the jail period, the penalized validator is "jailed". - -### GET /slashing/validator/{validatorAddr}/signing-info - -- **URL**: `/slashing/validator/{validatorAddr}/signing-info` -- **Functionality**: Query the information from a single validator. -- Returns on success: - -```json -{ - "rest api":"2.3", - "code":200, - "error":"", - "result":{ - "transaction":"TODO" - } -} -``` - -### POST /slashing/validators/{validatorAddr}/unjail - -- **URL**: `/slashing/validators/{validatorAddr}/unjail` -- **Functionality**: Submit a message to unjail a validator after it has been penalized. -- POST Body: - -```js -{ - // Name of key to use - "name": "string", - // Password for that key - "password": "string", - "chain_id": "string", - "account_number": 64, - "sequence": 64, - "gas": 64, -} -``` - -- Returns on success: - -```json -{ - "rest api":"2.3", - "code":200, - "error":"", - "result":{ - "transaction":"TODO" - } -} -``` diff --git a/docs/light/load_balancer.md b/docs/light/load_balancer.md deleted file mode 100644 index 0cc280827..000000000 --- a/docs/light/load_balancer.md +++ /dev/null @@ -1,203 +0,0 @@ -# Load Balancing Module - WIP - -The LCD will be an important bridge between service providers and cosmos blockchain network. Suppose -a service provider wants to monitor token information for millions of accounts. Then it has to keep -sending a large mount of requests to LCD to query token information. As a result, LCD will send huge -requests to full node to get token information and necessary proof which will cost full node much -computing and bandwidth resource. Too many requests to a single full node may result in some bad -situations: - -```text -1. The full node crash possibility increases. -2. The reply delay increases. -3. The system reliability will decrease. -4. As the full node may belong to other people or associates, they may deny too frequent access from a single client. -``` - -It is very urgent to solve this problems. Here we consider to import load balancing into LCD. By the -help of load balancing, LCD can distribute millions of requests to a set of full nodes. Thus the -load of each full node won't be too heavy and the unavailable full nodes will be wiped out of query -list. In addition, the system reliability will increase. - -## Design - -This module need combine with client to realize the real load balancing. It can embed the -[HTTP Client](https://github.com/tendermint/tendermint/rpc/lib/client/httpclient.go). In other -words,we realise the new httpclient based on `HTTP`. - -```go -type HTTPLoadBalancer struct { - rpcs map[string]*rpcclient.JSONRPCClient - *WSEvents -} -``` - -## The Diagram of LCD RPC WorkFlow with LoadBalance - -![The Diagram of LCD RPC WorkFlow](pics/loadbalanceDiagram.png) - -In the above sequence diagram, application calls the `Request()`, and LCD finally call the -`HTTP.Request()` through the SecureClient `Wrapper`. In every `HTTP.Request()`, `Getclient()` -selects the current working rpcclient by the load balancing algorithm,then run the -`JSONRPCClient.Call()` to request from the Full Node, finally `UpdateClient()` updates the weight of - the current rpcclient according to the status that is returned by the full node. The `GetAddr()` - and `UpdateAddrWeight()` are realized in the load balancing module. - -There are some abilities to do: - -* Add the Remote Address -* Delete the Remote Address -* Update the weights of the addresses - -## Load balancing Strategies - -We can design some strategies like nginx to combine the different load balancing algorithms to get -the final remote. We can also get the status of the remote server to add or delete the addresses and - update weights of the addresses. - -In a word,it can make the entire LCD work more effective in actual conditions. -We are working this module independently in this [Github Repository](https://github.com/MrXJC/GoLoadBalance). - -## Interface And Type - -### Balancer - -This interface `Balancer`is the core of the package. Every load balancing algorithm should realize -it,and it defined two interfaces. - -* `init` initialize the balancer, assigns the variables which `DoBalance` needs. -* `DoBalance` load balance the full node addresses according to the current situation. - -```go -package balance - -type Balancer interface { - init(NodeAddrs) - DoBalance(NodeAddrs) (*NodeAddr,int,error) -} -``` - -### NodeAddr - -* host: ip address -* port: the number of port -* weight: the weight of this full node address,default:1 - -This NodeAddr is the base struct of the address. - -```go -type NodeAddr struct{ - host string - port int - weight int -} - -func (p *NodeAddr) GetHost() string - -func (p *NodeAddr) GetPort() int - -func (p *NodeAddr) GetWeight() int - -func (p *NodeAddr) updateWeight(weight int) -``` - -The `weight` is the important factor that schedules which full node the LCD calls. The weight can be -changed by the information from the full node. So we have the function `updateWegiht`. - -### NodeAddrs - ->in `balance/types.go` - -`NodeAddrs` is the list of the full node address. This is the member variable in the -BalanceManager(`BalancerMgr`). - -```go -type NodeAddrs []*NodeAddr -``` - -## Load Balancing Algorithm - -### Random - ->in `balance/random.go` - -Random algorithm selects a remote address randomly to process the request. The probability of them -being selected is the same. - -### RandomWeight - ->in `balance/random.go` - -RandomWeight Algorithm also selects a remote address randomly to process the request. But the higher -the weight, the greater the probability. - -### RoundRobin - ->in `balance/roundrobin.go` - -RoundRobin Algorithm selects a remote address orderly. Every remote address have the same -probability to be selected. - -### RoundRobinWeight - ->in `balance/roundrobin.go` - -RoundRobinWeight Algorthm selects a remote address orderly. But every remote address have different -probability to be selected which are determined by their weight. - -### Hash - -//TODO - -## Load Balancing Manager - -### BalanceMgr - ->in `balance/manager.go` - -* addrs: the set of the remote full node addresses -* balancers: map the string of balancer name to the specific balancer -* change: record whether the machine reinitialize after the `addrs` changes - -`BalanceMgr` is the manager of many balancer. It is the access of load balancing. Its main function -is to maintain the `NodeAddrs` and to call the specific load balancing algorithm above. - -```go -type BalanceMgr struct{ - addrs NodeAddrs - balancers map[string]Balancer - change map[string]bool -} - -func (p *BalanceMgr) RegisterBalancer(name string,balancer Balancer) - -func (p *BalanceMgr) updateBalancer(name string) - -func (p *BalanceMgr) AddNodeAddr(addr *NodeAddr) - -func (p *BalanceMgr) DeleteNodeAddr(i int) - -func (p *BalanceMgr) UpdateWeightNodeAddr(i int,weight int) - -func (p *BalanceMgr) GetAddr(name string)(*NodeAddr,int,error) { - // if addrs change,update the balancer which we use. - if p.change[name]{ - p.updateBalancer(name) - } - - // get the balancer by name - balancer := p.balancers[name] - - // use the load balancing algorithm - addr,index,err := balancer.DoBalance(p.addrs) - - return addr,index,err -} -``` - -* `RegisterBalancer`: register the basic balancer implementing the `Balancer` interface and initialize them. -* `updateBalancer`: update the specific balancer after the `addrs` change. -* `AddNodeAddr`: add the remote address and set all the values of the `change` to true. -* `DeleteNodeAddr`: delete the remote address and set all the values of the `change` to true. -* `UpdateWeightNodeAddr`: update the weight of the remote address and set all the values of the `change` to true. -* `GetAddr`:select the address by the balancer the `name` decides. diff --git a/docs/light/todo.md b/docs/light/todo.md deleted file mode 100644 index ce1f8508a..000000000 --- a/docs/light/todo.md +++ /dev/null @@ -1,16 +0,0 @@ -# TODO - -This document is a place to gather all points for future development. - -## API - -* finalise ICS0 - TendermintAPI - * make sure that the explorer and voyager can use it -* add ICS21 - StakingAPI -* add ICS22 - GovernanceAPI -* split Gaia Light into reusable components that other zones can leverage - * it should be possible to register extra standards on the light client - * the setup should be similar to how the app is currently started -* implement Gaia light and the general light client in Rust - * export the API as a C interface - * write thin wrappers around the C interface in JS, Swift and Kotlin/Java diff --git a/docs/light/getting_started.md b/docs/lite/getting_started.md similarity index 59% rename from docs/light/getting_started.md rename to docs/lite/getting_started.md index 21497477a..b602bcc47 100644 --- a/docs/light/getting_started.md +++ b/docs/lite/getting_started.md @@ -1,6 +1,7 @@ # Getting Started To start a REST server, we need to specify the following parameters: + | Parameter | Type | Default | Required | Description | | ----------- | --------- | ----------------------- | -------- | ---------------------------------------------------- | | chain-id | string | null | true | chain id of the full node to connect | @@ -9,12 +10,12 @@ To start a REST server, we need to specify the following parameters: | trust-node | bool | "false" | true | Whether this LCD is connected to a trusted full node | | trust-store | DIRECTORY | "$HOME/.lcd" | false | directory for save checkpoints and validator sets | -Sample command: +For example:: ```bash gaiacli rest-server --chain-id=test \ --laddr=tcp://localhost:1317 \ - --node tcp://localhost:46657 \ + --node tcp://localhost:26657 \ --trust-node=false ``` @@ -23,7 +24,7 @@ The server listens on HTTPS by default. You can set the SSL certificate to be us ```bash gaiacli rest-server --chain-id=test \ --laddr=tcp://localhost:1317 \ - --node tcp://localhost:46657 \ + --node tcp://localhost:26657 \ --trust-node=false \ --certfile=mycert.pem --keyfile=mykey.key ``` @@ -31,26 +32,4 @@ gaiacli rest-server --chain-id=test \ If no certificate/keyfile pair is supplied, a self-signed certificate will be generated and its fingerprint printed out. Append `--insecure` to the command line if you want to disable the secure layer and listen on an insecure HTTP port. -## Gaia Light Use Cases - -LCD could be very helpful for related service providers. For a wallet service provider, LCD could -make transaction faster and more reliable in the following cases. - -### Create an account - -![deposit](pics/create-account.png) - -First you need to get a new seed phrase :[get-seed](api.md#keysseed---get) - -After having new seed, you could generate a new account with it : [keys](api.md#keys---post) - -### Transfer a token - -![transfer](pics/transfer-tokens.png) - -The first step is to build an asset transfer transaction. Here we can post all necessary parameters -to /create_transfer to get the unsigned transaction byte array. Refer to this link for detailed -operation: [build transaction](api.md#create_transfer---post) - -Then sign the returned transaction byte array with users' private key. Finally broadcast the signed -transaction. Refer to this link for how to broadcast the signed transaction: [broadcast transaction](api.md#create_transfer---post) +For more information about the Gaia-Lite RPC, see the [swagger documentation](https://cosmos.network/rpc/) diff --git a/docs/light/pics/C2H.png b/docs/lite/pics/C2H.png similarity index 100% rename from docs/light/pics/C2H.png rename to docs/lite/pics/C2H.png diff --git a/docs/light/pics/H2C.png b/docs/lite/pics/H2C.png similarity index 100% rename from docs/light/pics/H2C.png rename to docs/lite/pics/H2C.png diff --git a/docs/light/pics/MA.png b/docs/lite/pics/MA.png similarity index 100% rename from docs/light/pics/MA.png rename to docs/lite/pics/MA.png diff --git a/docs/light/pics/absence1.png b/docs/lite/pics/absence1.png similarity index 100% rename from docs/light/pics/absence1.png rename to docs/lite/pics/absence1.png diff --git a/docs/light/pics/absence2.png b/docs/lite/pics/absence2.png similarity index 100% rename from docs/light/pics/absence2.png rename to docs/lite/pics/absence2.png diff --git a/docs/light/pics/absence3.png b/docs/lite/pics/absence3.png similarity index 100% rename from docs/light/pics/absence3.png rename to docs/lite/pics/absence3.png diff --git a/docs/light/pics/architecture.png b/docs/lite/pics/architecture.png similarity index 100% rename from docs/light/pics/architecture.png rename to docs/lite/pics/architecture.png diff --git a/docs/light/pics/changeProcess.png b/docs/lite/pics/changeProcess.png similarity index 100% rename from docs/light/pics/changeProcess.png rename to docs/lite/pics/changeProcess.png diff --git a/docs/light/pics/commitValidation.png b/docs/lite/pics/commitValidation.png similarity index 100% rename from docs/light/pics/commitValidation.png rename to docs/lite/pics/commitValidation.png diff --git a/docs/light/pics/create-account.png b/docs/lite/pics/create-account.png similarity index 100% rename from docs/light/pics/create-account.png rename to docs/lite/pics/create-account.png diff --git a/docs/light/pics/deposit.png b/docs/lite/pics/deposit.png similarity index 100% rename from docs/light/pics/deposit.png rename to docs/lite/pics/deposit.png diff --git a/docs/light/pics/existProof.png b/docs/lite/pics/existProof.png similarity index 100% rename from docs/light/pics/existProof.png rename to docs/lite/pics/existProof.png diff --git a/docs/light/pics/high-level.png b/docs/lite/pics/high-level.png similarity index 100% rename from docs/light/pics/high-level.png rename to docs/lite/pics/high-level.png diff --git a/docs/light/pics/light-client-architecture.png b/docs/lite/pics/light-client-architecture.png similarity index 100% rename from docs/light/pics/light-client-architecture.png rename to docs/lite/pics/light-client-architecture.png diff --git a/docs/light/pics/loadbalanceDiagram.png b/docs/lite/pics/loadbalanceDiagram.png similarity index 100% rename from docs/light/pics/loadbalanceDiagram.png rename to docs/lite/pics/loadbalanceDiagram.png diff --git a/docs/light/pics/simpleMerkleTree.png b/docs/lite/pics/simpleMerkleTree.png similarity index 100% rename from docs/light/pics/simpleMerkleTree.png rename to docs/lite/pics/simpleMerkleTree.png diff --git a/docs/light/pics/substoreProof.png b/docs/lite/pics/substoreProof.png similarity index 100% rename from docs/light/pics/substoreProof.png rename to docs/lite/pics/substoreProof.png diff --git a/docs/light/pics/transfer-tokens.png b/docs/lite/pics/transfer-tokens.png similarity index 100% rename from docs/light/pics/transfer-tokens.png rename to docs/lite/pics/transfer-tokens.png diff --git a/docs/light/pics/transfer.png b/docs/lite/pics/transfer.png similarity index 100% rename from docs/light/pics/transfer.png rename to docs/lite/pics/transfer.png diff --git a/docs/light/pics/trustPropagate.png b/docs/lite/pics/trustPropagate.png similarity index 100% rename from docs/light/pics/trustPropagate.png rename to docs/lite/pics/trustPropagate.png diff --git a/docs/light/pics/updateValidatorToHeight.png b/docs/lite/pics/updateValidatorToHeight.png similarity index 100% rename from docs/light/pics/updateValidatorToHeight.png rename to docs/lite/pics/updateValidatorToHeight.png diff --git a/docs/light/pics/validatorSetChange.png b/docs/lite/pics/validatorSetChange.png similarity index 100% rename from docs/light/pics/validatorSetChange.png rename to docs/lite/pics/validatorSetChange.png diff --git a/docs/light/pics/withdraw.png b/docs/lite/pics/withdraw.png similarity index 100% rename from docs/light/pics/withdraw.png rename to docs/lite/pics/withdraw.png diff --git a/docs/light/readme.md b/docs/lite/readme.md similarity index 81% rename from docs/light/readme.md rename to docs/lite/readme.md index 55e0c72ac..215bc25f7 100644 --- a/docs/light/readme.md +++ b/docs/lite/readme.md @@ -1,30 +1,20 @@ -# Cosmos-Sdk Light Client +# Overview + +**See the Cosmos SDK lite Client RPC documentation [here](https://cosmos.network/rpc/)** ## Introduction -A light client allows clients, such as mobile phones, to receive proofs of the state of the -blockchain from any full node. Light clients do not have to trust any full node, since they are able +A lite client allows clients, such as mobile phones, to receive proofs of the state of the +blockchain from any full node. lite clients do not have to trust any full node, since they are able to verify any proof they receive and hence full nodes cannot lie about the state of the network. -A light client can provide the same security as a full node with the minimal requirements on -bandwidth, computing and storage resource. Besides, it can also provide modular functionality +A lite client can provide the same security as a full node with the minimal requirements on +bandwidth, computing and storage resource. As well, it can also provide modular functionality according to users' configuration. These fantastic features allow developers to build fully secure, efficient and usable mobile apps, websites or any other applications without deploying or maintaining any full blockchain nodes. -LCD will be used in the Cosmos Hub, the first Hub in the Cosmos network. - -## Contents - -1. [**Overview**](##Overview) -2. [**Get Started**](getting_started.md) -3. [**API**](api.md) -4. [**Specifications**](specification.md) -4. [**Update API docs To Swagger-UI**](update_API_docs.md) - -## Overview - -### What is a Light Client +### What is a lite Client The LCD is split into two separate components. The first component is generic for any Tendermint based application. It handles the security and connectivity aspects of following the header chain @@ -54,7 +44,7 @@ that offers stability guarantees around the zone API. ### Comparision -A full node of ABCI is different from its light client in the following ways: +A full node of ABCI is different from its lite client in the following ways: || Full Node | LCD | Description| |-| ------------- | ----- | -------------- | @@ -71,22 +61,22 @@ A full node of ABCI is different from its light client in the following ways: According to the above table, LCD can meet all users' functionality and security requirements, but only requires little resource on bandwidth, computing, storage and power. -## How does LCD achieve high security? +## Achieving Security -### Trusted validator set +### Trusted Validator Set -The base design philosophy of lcd follows the two rules: +The base design philosophy of the LCD follows two rules: 1. **Doesn't trust any blockchain nodes, including validator nodes and other full nodes** 2. **Only trusts the whole validator set** The original trusted validator set should be prepositioned into its trust store, usually this -validator set comes from genesis file. During running time, if LCD detects different validator set, -it will verify it and save new validated validator set to trust store. +validator set comes from genesis file. During runtime, if LCD detects a different validator set, +it will verify it and save new validated validator set to the trust store. ![validator-set-change](pics/validatorSetChange.png) -### Trust propagation +### Trust Propagation From the above section, we come to know how to get trusted validator set and how lcd keeps track of validator set evolution. Validator set is the foundation of trust, and the trust can propagate to @@ -97,5 +87,4 @@ follows: In general, by trusted validator set, LCD can verify each block commit which contains all pre-commit data and block header data. Then the block hash, data hash and appHash are trusted. Based on this -and merkle proof, all transactions data and ABCI states can be verified too. Detailed implementation -will be posted on technical specification. +and merkle proof, all transactions data and ABCI states can be verified too. diff --git a/docs/light/specification.md b/docs/lite/specification.md similarity index 67% rename from docs/light/specification.md rename to docs/lite/specification.md index 15f36b014..4feac508c 100644 --- a/docs/light/specification.md +++ b/docs/lite/specification.md @@ -207,150 +207,3 @@ For instance: * Update to 10000,tooMuchChangeErr * Update to 7525, Success * Update to 10000, Success - -## Load Balancing - -To improve LCD reliability and TPS, we recommend to connect LCD to more than one fullnode. But the -complexity will increase a lot. So load balancing module will be imported as the adapter. Please -refer to this link for detailed description: [load balancer](load_balancer.md) - -## ICS1 (KeyAPI) - -### [/keys - GET](api.md#keys---get) - -Load the key store: - -```go -db, err := dbm.NewGoLevelDB(KeyDBName, filepath.Join(rootDir, "keys")) -if err != nil { - return nil, err -} - -keybase = client.GetKeyBase(db) -``` - -Iterate through the key store. - -```go -var res []Info -iter := kb.db.Iterator(nil, nil) -defer iter.Close() - -for ; iter.Valid(); iter.Next() { - // key := iter.Key() - info, err := readInfo(iter.Value()) - if err != nil { - return nil, err - } - res = append(res, info) -} - -return res, nil -``` - -Encode the addresses and public keys in bech32. - -```go -bechAccount, err := sdk.Bech32ifyAcc(sdk.Address(info.PubKey.Address().Bytes())) -if err != nil { - return KeyOutput{}, err -} - -bechPubKey, err := sdk.Bech32ifyAccPub(info.PubKey) -if err != nil { - return KeyOutput{}, err -} - -return KeyOutput{ - Name: info.Name, - Address: bechAccount, - PubKey: bechPubKey, -}, nil -``` - -### [/keys/recover - POST](api.md#keys/recover---get) - -1. Load the key store. -2. Parameter checking. Name, password and seed should not be empty. -3. Check for keys with the same name. -4. Build the key from the name, password and seed. -5. Persist the key to key store. - -### [/keys/create - GET](api.md#keys/create---get)** - -1. Load the key store. -2. Create a new key in the key store. -3. Save the key to disk. -4. Return the seed. - -### [/keys/{name} - GET](api.md#keysname---get) - -1. Load the key store. -2. Iterate the whole key store to find the key by name. -3. Encode address and public key in bech32. - -### [/keys/{name} - PUT](api.md#keysname---put) - -1. Load the key store. -2. Iterate the whole key store to find the key by name. -3. Verify if that the old-password matches the current key password. -4. Re-persist the key with the new password. - -### [/keys/{name} - DELETE](api.md#keysname---delete) - -1. Load the key store. -2. Iterate the whole key store to find the key by name. -3. Verify that the specified password matches the current key password. -4. Delete the key from the key store. - -## ICS20 (TokenAPI) - -### [/bank/balance/{account}](api.md#bankbalanceaccount---get) - -1. Decode the address from bech32 to hex. -2. Send a query request to a full node. Ask for proof if required by Gaia Light. -3. Verify the proof against the root of trust. - -### [/bank/create_transfer](api.md#bankcreate_transfer---post) - -1. Check the parameters. -2. Build the transaction with the specified parameters. -3. Serialise the transaction and return the JSON encoded sign bytes. - -## ICS21 (StakingAPI) - -### [/stake/delegators/{delegatorAddr}](api.md#stakedelegatorsdelegatorAddr---get) - -TODO - -### [/stake/delegators/{delegatorAddr}/validators](api.md#stakedelegatorsdelegatorAddrvalidators---get) - -TODO - -### [/stake/delegators/{delegatorAddr}/validators/{validatorAddr}](api.md#stakedelegatorsdelegatorAddrvalidatorsvalidatorAddr---get) - -TODO - -### [/stake/delegators/{delegatorAddr}/txs](api.md#stakedelegatorsdelegatorAddrtxs---get) - -TODO - -### [/stake/delegators/{delegatorAddr}/delegations](api.md#stakedelegatorsdelegatorAddrdelegations---post) - -TODO - -### [/stake/delegators/{delegatorAddr}/delegations/{validatorAddr}](api.md#stakedelegatorsdelegatorAddrdelegationsvalidatorAddr---get) - -TODO - -### [/stake/delegators/{delegatorAddr}/unbonding_delegations/{validatorAddr}](api.md#stakedelegatorsdelegatorAddrunbonding_delegationsvalidatorAddr---get) - -TODO - -### [/stake/validators](api.md#stakevalidators---get) - -TODO - -### [/stake/validators/{validatorAddr}](api.md#stakevalidatorsvalidatorAddr---get) - -TODO From a7e6969029fa6edf3ab5553514e279eae20dcdc0 Mon Sep 17 00:00:00 2001 From: gamarin2 Date: Thu, 18 Oct 2018 20:50:05 +0200 Subject: [PATCH 35/40] Merge PR #2493: Fix service provider docs * Fix link * Default curve * Link to website * Update to latest docs * test relative link * test links 2 * Link test 3 * test link 4 * Proper docs + links Note: Current swagger doc lives on cosmos staging site. The doc on here refer to the main website and will therefore 404 until staging is deployed to main site --- docs/clients/service-providers.md | 27 ++++++++++++++------------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/docs/clients/service-providers.md b/docs/clients/service-providers.md index 79fe2a667..11fe2c779 100644 --- a/docs/clients/service-providers.md +++ b/docs/clients/service-providers.md @@ -18,9 +18,9 @@ There are three main pieces to consider: We will describe the steps to run and interract with a full-node for the Cosmos Hub. For other SDK-based blockchain, the process should be similar. -First, you need to [install the software](https://github.com/cosmos/cosmos-sdk/blob/develop/docs/getting-started/installation.md). +First, you need to [install the software](../getting-started/installation.md). -Then, you can start [running a full-node](https://github.com/cosmos/cosmos-sdk/blob/develop/docs/getting-started/full-node.md). +Then, you can start [running a full-node](../getting-started/join-testnet.md). ### Command-Line interface @@ -28,7 +28,7 @@ Next you will find a few useful CLI commands to interact with the Full-Node. #### Creating a key-pair -To generate a new key (default ed25519 elliptic curve): +To generate a new key (default secp256k1 elliptic curve): ```bash gaiacli keys add @@ -62,13 +62,13 @@ gaiacli account Here is the command to send coins via the CLI: ```bash -gaiacli tx send --amount=10faucetToken --chain-id= --name= --to= +gaiacli send --amount=10faucetToken --chain-id= --from= --to= ``` Flags: - `--amount`: This flag accepts the format ``. - `--chain-id`: This flag allows you to specify the id of the chain. There will be different ids for different testnet chains and main chain. -- `--name`: Name of the key of the sending account. +- `--from`: Name of the key of the sending account. - `--to`: Address of the recipient. #### Help @@ -88,24 +88,25 @@ The Rest Server acts as an intermediary between the front-end and the full-node. To start the Rest server: ```bash -gaiacli rest-server --trust-node=false --node= +gaiacli advanced rest-server --node= ``` Flags: -- `--trust-node`: A boolean. If `true`, light-client verification is enabled. If `false`, it is disabled. For service providers, this should be set to `false`. -- `--node`: This is where you indicate the address and the port of your full-node. The format is . If the full-node is on the same machine, the address should be "tcp://localhost". -- `--laddr`: This flag allows you to specify the address and port for the Rest Server. You will mostly use this flag only to specify the port, in which case just input "localhost" for the address. The format is . +- `--trust-node`: A boolean. If `true`, light-client verification is disabled. If `false`, it is disabled. For service providers, this should be set to `true`. By default, it set to `true`. +- `--node`: This is where you indicate the address and the port of your full-node. The format is . If the full-node is on the same machine, the address should be `tcp://localhost:26657`. +- `--laddr`: This flag allows you to specify the address and port for the Rest Server (default `1317`). You will mostly use this flag only to specify the port, in which case just input "localhost" for the address. The format is . + ### Listening for incoming transaction The recommended way to listen for incoming transaction is to periodically query the blockchain through the following endpoint of the LCD: -[`/bank/balance/{account}`](https://github.com/cosmos/cosmos-sdk/blob/develop/docs/light/api.md#bankbalanceaccount---get) +[`/bank/balance/{account}`](https://cosmos.network/rpc/#/ICS20/get_bank_balances__address_) ## Rest API -The Rest API documents all the available endpoints that you can use to interract with your full node. It can be found [here](https://github.com/cosmos/cosmos-sdk/blob/develop/docs/light/api.md). +The Rest API documents all the available endpoints that you can use to interract with your full node. It can be found [here](https://cosmos.network/rpc/). -The API is divided into ICS standards for each category of endpoints. For example, the [ICS20](https://github.com/cosmos/cosmos-sdk/blob/develop/docs/light/api.md#ics20---tokenapi) describes the API to interact with tokens. +The API is divided into ICS standards for each category of endpoints. For example, the [ICS20](https://cosmos.network/rpc/#/ICS20/) describes the API to interact with tokens. -To give more flexibility to implementers, we have separated the different steps that are involved in the process of sending transactions. You will be able to generate unsigned transactions (example with [coin transfer](https://github.com/cosmos/cosmos-sdk/blob/develop/docs/light/api.md#post-banktransfers)), [sign](https://github.com/cosmos/cosmos-sdk/blob/develop/docs/light/api.md#post-authtxsign) and [broadcast](https://github.com/cosmos/cosmos-sdk/blob/develop/docs/light/api.md#post-authtxbroadcast) them with different API endpoints. This allows service providers to use their own signing mechanism for instance. +To give more flexibility to implementers, we have separated the different steps that are involved in the process of sending transactions. You will be able to generate unsigned transactions (example with [coin transfer](https://cosmos.network/rpc/#/ICS20/post_bank_accounts__address__transfers)), [sign](https://cosmos.network/rpc/#/ICS20/post_tx_sign) and [broadcast](https://cosmos.network/rpc/#/ICS20/post_tx_broadcast) them with different API endpoints. This allows service providers to use their own signing mechanism for instance. From 505c356f20d0d8ecbe3ccaeaba7f63f0a018ca38 Mon Sep 17 00:00:00 2001 From: Sunny Aggarwal Date: Thu, 18 Oct 2018 12:58:18 -0700 Subject: [PATCH 36/40] Merge PR #2514: Refactor validator deletion --- PENDING.md | 1 + docs/spec/staking/end_block.md | 12 +++-- docs/spec/staking/transactions.md | 5 +- x/stake/keeper/delegation.go | 5 +- x/stake/keeper/delegation_test.go | 82 ++++++++++++++++++++++++++++++- x/stake/keeper/slash.go | 4 +- x/stake/keeper/validator.go | 6 ++- 7 files changed, 102 insertions(+), 13 deletions(-) diff --git a/PENDING.md b/PENDING.md index c278722b9..ca0c316be 100644 --- a/PENDING.md +++ b/PENDING.md @@ -44,6 +44,7 @@ BREAKING CHANGES * [simulation] \#2162 Added back correct supply invariants * [x/slashing] \#2430 Simulate more slashes, check if validator is jailed before jailing * [x/stake] \#2393 Removed `CompleteUnbonding` and `CompleteRedelegation` Msg types, and instead added unbonding/redelegation queues to endblocker + * [x/stake] \#1673 Validators are no longer deleted until they can no longer possibly be slashed * SDK * [core] \#2219 Update to Tendermint 0.24.0 diff --git a/docs/spec/staking/end_block.md b/docs/spec/staking/end_block.md index 6439ca0c7..e1d769ed4 100644 --- a/docs/spec/staking/end_block.md +++ b/docs/spec/staking/end_block.md @@ -3,15 +3,19 @@ ## Unbonding Validator Queue For all unbonding validators that have finished their unbonding period, this switches their validator.Status -from sdk.Unbonding to sdk.Unbonded +from sdk.Unbonding to sdk.Unbonded if they still have any delegation left. Otherwise, it deletes it from state. ```golang validatorQueue(currTime time.Time): // unbonding validators are in ordered queue from oldest to newest for all unbondingValidators whose CompleteTime < currTime: validator = GetValidator(unbondingValidator.ValidatorAddr) - validator.Status = sdk.Bonded - SetValidator(unbondingValidator) + if validator.DelegatorShares == 0 { + RemoveValidator(unbondingValidator) + } else { + validator.Status = sdk.Unbonded + SetValidator(unbondingValidator) + } return ``` @@ -61,4 +65,4 @@ redelegationQueue(currTime time.Time): for all redelegations whose CompleteTime < currTime: removeRedelegation(redelegation) return -``` \ No newline at end of file +``` diff --git a/docs/spec/staking/transactions.md b/docs/spec/staking/transactions.md index 74ea67c85..ee5b2dc93 100644 --- a/docs/spec/staking/transactions.md +++ b/docs/spec/staking/transactions.md @@ -180,7 +180,7 @@ startUnbonding(tx TxStartUnbonding): validator = updateValidator(validator) - if validator.DelegatorShares == 0 { + if validator.Status == Unbonded && validator.DelegatorShares == 0 { removeValidator(validator.Operator) return @@ -189,8 +189,7 @@ startUnbonding(tx TxStartUnbonding): ### TxRedelegation The redelegation command allows delegators to instantly switch validators. Once -the unbonding period has passed, the redelegation must be completed with -txRedelegationComplete. +the unbonding period has passed, the redelegation is automatically completed in the EndBlocker. ```golang type TxRedelegate struct { diff --git a/x/stake/keeper/delegation.go b/x/stake/keeper/delegation.go index 25ea3d08b..8bb468d99 100644 --- a/x/stake/keeper/delegation.go +++ b/x/stake/keeper/delegation.go @@ -425,8 +425,8 @@ func (k Keeper) unbond(ctx sdk.Context, delAddr sdk.AccAddress, valAddr sdk.ValA // remove the coins from the validator validator, amount = k.RemoveValidatorTokensAndShares(ctx, validator, shares) - if validator.DelegatorShares.IsZero() && validator.Status != sdk.Bonded { - // if bonded, we must remove in EndBlocker instead + if validator.DelegatorShares.IsZero() && validator.Status == sdk.Unbonded { + // if not unbonded, we must instead remove validator in EndBlocker once it finishes its unbonding period k.RemoveValidator(ctx, validator.OperatorAddr) } @@ -501,6 +501,7 @@ func (k Keeper) BeginUnbonding(ctx sdk.Context, } k.SetUnbondingDelegation(ctx, ubd) k.InsertUnbondingQueue(ctx, ubd) + return ubd, nil } diff --git a/x/stake/keeper/delegation_test.go b/x/stake/keeper/delegation_test.go index 46bde785a..24476ca3c 100644 --- a/x/stake/keeper/delegation_test.go +++ b/x/stake/keeper/delegation_test.go @@ -1,6 +1,7 @@ package keeper import ( + "fmt" "testing" "time" @@ -392,7 +393,13 @@ func TestUndelegateFromUnbondedValidator(t *testing.T) { require.True(t, ctx.BlockHeader().Time.Add(params.UnbondingTime).Equal(validator.UnbondingMinTime)) // unbond the validator - keeper.unbondingToUnbonded(ctx, validator) + ctx = ctx.WithBlockTime(validator.UnbondingMinTime) + keeper.UnbondAllMatureValidatorQueue(ctx) + + // Make sure validator is still in state because there is still an outstanding delegation + validator, found = keeper.GetValidator(ctx, addrVals[0]) + require.True(t, found) + require.Equal(t, validator.Status, sdk.Unbonded) // unbond some of the other delegation's shares _, err = keeper.BeginUnbonding(ctx, addrDels[0], addrVals[0], sdk.NewDec(6)) @@ -401,6 +408,79 @@ func TestUndelegateFromUnbondedValidator(t *testing.T) { // no ubd should have been found, coins should have been returned direcly to account ubd, found := keeper.GetUnbondingDelegation(ctx, addrDels[0], addrVals[0]) require.False(t, found, "%v", ubd) + + // unbond rest of the other delegation's shares + _, err = keeper.BeginUnbonding(ctx, addrDels[0], addrVals[0], sdk.NewDec(4)) + require.NoError(t, err) + + // now validator should now be deleted from state + validator, found = keeper.GetValidator(ctx, addrVals[0]) + fmt.Println(validator) + require.False(t, found) +} + +func TestUnbondingAllDelegationFromValidator(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 = TestingUpdateValidator(keeper, ctx, validator) + pool = keeper.GetPool(ctx) + val0AccAddr := sdk.AccAddress(addrVals[0].Bytes()) + selfDelegation := types.Delegation{ + DelegatorAddr: val0AccAddr, + ValidatorAddr: addrVals[0], + Shares: issuedShares, + } + keeper.SetDelegation(ctx, selfDelegation) + + // create a second delegation to this validator + keeper.DeleteValidatorByPowerIndex(ctx, validator, pool) + validator, pool, issuedShares = validator.AddTokensFromDel(pool, sdk.NewInt(10)) + require.Equal(t, int64(10), issuedShares.RoundInt64()) + keeper.SetPool(ctx, pool) + validator = TestingUpdateValidator(keeper, ctx, validator) + pool = keeper.GetPool(ctx) + delegation := types.Delegation{ + DelegatorAddr: addrDels[0], + ValidatorAddr: addrVals[0], + Shares: issuedShares, + } + keeper.SetDelegation(ctx, delegation) + + ctx = ctx.WithBlockHeight(10) + ctx = ctx.WithBlockTime(time.Unix(333, 0)) + + // unbond the all self-delegation to put validator in unbonding state + _, err := keeper.BeginUnbonding(ctx, val0AccAddr, addrVals[0], sdk.NewDec(10)) + require.NoError(t, err) + + // end block + updates := keeper.ApplyAndReturnValidatorSetUpdates(ctx) + require.Equal(t, 1, len(updates)) + + // unbond all the remaining delegation + _, err = keeper.BeginUnbonding(ctx, addrDels[0], addrVals[0], sdk.NewDec(10)) + require.NoError(t, err) + + // validator should still be in state and still be in unbonding state + validator, found := keeper.GetValidator(ctx, addrVals[0]) + require.True(t, found) + require.Equal(t, validator.Status, sdk.Unbonding) + + // unbond the validator + ctx = ctx.WithBlockTime(validator.UnbondingMinTime) + keeper.UnbondAllMatureValidatorQueue(ctx) + + // validator should now be deleted from state + _, found = keeper.GetValidator(ctx, addrVals[0]) + require.False(t, found) } // Make sure that that the retrieving the delegations doesn't affect the state diff --git a/x/stake/keeper/slash.go b/x/stake/keeper/slash.go index a1562da33..af59726ae 100644 --- a/x/stake/keeper/slash.go +++ b/x/stake/keeper/slash.go @@ -105,8 +105,8 @@ func (k Keeper) Slash(ctx sdk.Context, consAddr sdk.ConsAddress, infractionHeigh k.SetPool(ctx, pool) // remove validator if it has no more tokens - if validator.Tokens.IsZero() && validator.Status != sdk.Bonded { - // if bonded, we must remove in ApplyAndReturnValidatorSetUpdates instead + if validator.DelegatorShares.IsZero() && validator.Status == sdk.Unbonded { + // if not unbonded, we must instead remove validator in EndBlocker once it finishes its unbonding period k.RemoveValidator(ctx, validator.OperatorAddr) } diff --git a/x/stake/keeper/validator.go b/x/stake/keeper/validator.go index 41cfe6faa..95e5f0001 100644 --- a/x/stake/keeper/validator.go +++ b/x/stake/keeper/validator.go @@ -343,7 +343,11 @@ func (k Keeper) UnbondAllMatureValidatorQueue(ctx sdk.Context) { if !found || val.GetStatus() != sdk.Unbonding { continue } - k.unbondingToUnbonded(ctx, val) + if val.GetDelegatorShares().IsZero() { + k.RemoveValidator(ctx, val.OperatorAddr) + } else { + k.unbondingToUnbonded(ctx, val) + } } store.Delete(validatorTimesliceIterator.Key()) } From ad355d6c6906fe872080db56811b40ca0e12f71d Mon Sep 17 00:00:00 2001 From: Federico Kunze <31522760+fedekunze@users.noreply.github.com> Date: Fri, 19 Oct 2018 18:55:20 +0200 Subject: [PATCH 37/40] Merge PR #2444: Standardize REST error responses --- PENDING.md | 1 + client/context/broadcast.go | 8 +-- client/context/query.go | 2 +- client/keys/add.go | 2 + client/keys/update.go | 1 + client/lcd/version.go | 6 +-- client/tx/query.go | 6 +-- client/tx/search.go | 13 ++--- client/utils/rest.go | 6 +-- types/errors.go | 30 ++++++++++- types/errors_test.go | 26 ++++++++++ x/auth/client/rest/query.go | 9 ++-- x/gov/client/rest/rest.go | 8 --- x/gov/errors.go | 4 +- x/gov/queryable.go | 34 ++++++------ x/slashing/client/rest/query.go | 15 +++--- x/slashing/client/rest/tx.go | 6 +-- x/stake/client/rest/query.go | 91 ++++++++++----------------------- x/stake/client/rest/tx.go | 28 +++++----- x/stake/querier/queryable.go | 32 ++++++------ 20 files changed, 160 insertions(+), 168 deletions(-) diff --git a/PENDING.md b/PENDING.md index ca0c316be..00d0ca3cc 100644 --- a/PENDING.md +++ b/PENDING.md @@ -142,6 +142,7 @@ IMPROVEMENTS * Gaia REST API (`gaiacli advanced rest-server`) * [x/stake] [\#2000](https://github.com/cosmos/cosmos-sdk/issues/2000) Added tests for new staking endpoints + * [gaia-lite] [\#2445](https://github.com/cosmos/cosmos-sdk/issues/2445) Standarized REST error responses * [gaia-lite] Added example to Swagger specification for /keys/seed. * Gaia CLI (`gaiacli`) diff --git a/client/context/broadcast.go b/client/context/broadcast.go index b15b2a8e3..9f88ce7b9 100644 --- a/client/context/broadcast.go +++ b/client/context/broadcast.go @@ -55,15 +55,11 @@ func (ctx CLIContext) BroadcastTxAndAwaitCommit(tx []byte) (*ctypes.ResultBroadc } if !res.CheckTx.IsOK() { - return res, errors.Errorf("checkTx failed: (%d) %s", - res.CheckTx.Code, - res.CheckTx.Log) + return res, errors.Errorf(res.CheckTx.Log) } if !res.DeliverTx.IsOK() { - return res, errors.Errorf("deliverTx failed: (%d) %s", - res.DeliverTx.Code, - res.DeliverTx.Log) + return res, errors.Errorf(res.DeliverTx.Log) } return res, err diff --git a/client/context/query.go b/client/context/query.go index e4e488190..8fca9becf 100644 --- a/client/context/query.go +++ b/client/context/query.go @@ -168,7 +168,7 @@ func (ctx CLIContext) query(path string, key cmn.HexBytes) (res []byte, err erro resp := result.Response if !resp.IsOK() { - return res, errors.Errorf("query failed: (%d) %s", resp.Code, resp.Log) + return res, errors.Errorf(resp.Log) } // data from trusted node or subspace query doesn't need verification diff --git a/client/keys/add.go b/client/keys/add.go index 21a372e2f..f13fac547 100644 --- a/client/keys/add.go +++ b/client/keys/add.go @@ -262,6 +262,8 @@ func SeedRequestHandler(w http.ResponseWriter, r *http.Request) { algo := keys.SigningAlgo(algoType) seed := getSeed(algo) + + w.Header().Set("Content-Type", "application/json") w.Write([]byte(seed)) } diff --git a/client/keys/update.go b/client/keys/update.go index 18a18be58..9416e9b0b 100644 --- a/client/keys/update.go +++ b/client/keys/update.go @@ -91,5 +91,6 @@ func UpdateKeyRequestHandler(w http.ResponseWriter, r *http.Request) { return } + w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) } diff --git a/client/lcd/version.go b/client/lcd/version.go index a124388e6..4f4ce611c 100644 --- a/client/lcd/version.go +++ b/client/lcd/version.go @@ -1,10 +1,10 @@ package lcd import ( - "fmt" "net/http" "github.com/cosmos/cosmos-sdk/client/context" + "github.com/cosmos/cosmos-sdk/client/utils" "github.com/cosmos/cosmos-sdk/version" ) @@ -19,11 +19,11 @@ func NodeVersionRequestHandler(cliCtx context.CLIContext) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { version, err := cliCtx.Query("/app/version", nil) if err != nil { - w.WriteHeader(http.StatusInternalServerError) - w.Write([]byte(fmt.Sprintf("Could't query version. Error: %s", err.Error()))) + utils.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) return } + w.Header().Set("Content-Type", "application/json") w.Write(version) } } diff --git a/client/tx/query.go b/client/tx/query.go index ec7daf41b..da94404f2 100644 --- a/client/tx/query.go +++ b/client/tx/query.go @@ -3,9 +3,10 @@ package tx import ( "encoding/hex" "fmt" - "github.com/tendermint/tendermint/libs/common" "net/http" + "github.com/tendermint/tendermint/libs/common" + "github.com/gorilla/mux" "github.com/spf13/cobra" abci "github.com/tendermint/tendermint/abci/types" @@ -142,8 +143,7 @@ func QueryTxRequestHandlerFn(cdc *codec.Codec, cliCtx context.CLIContext) http.H output, err := queryTx(cdc, cliCtx, hashHexStr) if err != nil { - w.WriteHeader(500) - w.Write([]byte(err.Error())) + utils.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) return } utils.PostProcessResponse(w, cdc, output, cliCtx.Indent) diff --git a/client/tx/search.go b/client/tx/search.go index 672df0e33..198bc4b33 100644 --- a/client/tx/search.go +++ b/client/tx/search.go @@ -30,7 +30,7 @@ func SearchTxCmd(cdc *codec.Codec) *cobra.Command { Use: "txs", Short: "Search for all transactions that match the given tags.", Long: strings.TrimSpace(` -Search for transactions that match the given tags. By default, transactions must match ALL tags +Search for transactions that match the given tags. By default, transactions must match ALL tags passed to the --tags option. To match any transaction, use the --any option. For example: @@ -141,7 +141,7 @@ func SearchTxRequestHandlerFn(cliCtx context.CLIContext, cdc *codec.Codec) http. return func(w http.ResponseWriter, r *http.Request) { tag := r.FormValue("tag") if tag == "" { - w.WriteHeader(400) + w.WriteHeader(http.StatusBadRequest) w.Write([]byte("You need to provide at least a tag as a key=value pair to search for. Postfix the key with _bech32 to search bech32-encoded addresses or public keys")) return } @@ -151,8 +151,7 @@ func SearchTxRequestHandlerFn(cliCtx context.CLIContext, cdc *codec.Codec) http. value, err := url.QueryUnescape(keyValue[1]) if err != nil { - w.WriteHeader(400) - w.Write([]byte("Could not decode address: " + err.Error())) + utils.WriteErrorResponse(w, http.StatusBadRequest, sdk.AppendMsgToErr("could not decode address", err.Error())) return } @@ -161,8 +160,7 @@ func SearchTxRequestHandlerFn(cliCtx context.CLIContext, cdc *codec.Codec) http. prefix := strings.Split(bech32address, "1")[0] bz, err := sdk.GetFromBech32(bech32address, prefix) if err != nil { - w.WriteHeader(400) - w.Write([]byte(err.Error())) + utils.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) return } @@ -171,8 +169,7 @@ func SearchTxRequestHandlerFn(cliCtx context.CLIContext, cdc *codec.Codec) http. txs, err := searchTxs(cliCtx, cdc, []string{tag}) if err != nil { - w.WriteHeader(500) - w.Write([]byte(err.Error())) + utils.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) return } diff --git a/client/utils/rest.go b/client/utils/rest.go index effb7bb73..b74b91d03 100644 --- a/client/utils/rest.go +++ b/client/utils/rest.go @@ -26,12 +26,12 @@ 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) { +func WriteErrorResponse(w http.ResponseWriter, status int, err string) { w.WriteHeader(status) - w.Write([]byte(msg)) + w.Write([]byte(err)) } -// WriteGasEstimateResponse prepares and writes an HTTP +// WriteSimulationResponse prepares and writes an HTTP // response for transactions simulations. func WriteSimulationResponse(w http.ResponseWriter, gas int64) { w.WriteHeader(http.StatusOK) diff --git a/types/errors.go b/types/errors.go index 266e6d14c..e05800b53 100644 --- a/types/errors.go +++ b/types/errors.go @@ -2,6 +2,7 @@ package types import ( "fmt" + "strings" "github.com/cosmos/cosmos-sdk/codec" cmn "github.com/tendermint/tendermint/libs/common" @@ -286,7 +287,34 @@ func (err *sdkError) QueryResult() abci.ResponseQuery { } } -// nolint +//---------------------------------------- +// REST error utilities + +// appends a message to the head of the given error +func AppendMsgToErr(msg string, err string) string { + msgIdx := strings.Index(err, "message\":\"") + if msgIdx != -1 { + errMsg := err[msgIdx+len("message\":\"") : len(err)-2] + errMsg = fmt.Sprintf("%s; %s", msg, errMsg) + return fmt.Sprintf("%s%s%s", + err[:msgIdx+len("message\":\"")], + errMsg, + err[len(err)-2:], + ) + } + return fmt.Sprintf("%s; %s", msg, err) +} + +// returns the index of the message in the ABCI Log +func mustGetMsgIndex(abciLog string) int { + msgIdx := strings.Index(abciLog, "message\":\"") + if msgIdx == -1 { + panic(fmt.Sprintf("invalid error format: %s", abciLog)) + } + return msgIdx + len("message\":\"") +} + +// parses the error into an object-like struct for exporting type humanReadableError struct { Codespace CodespaceType `json:"codespace"` Code CodeType `json:"code"` diff --git a/types/errors_test.go b/types/errors_test.go index f00b2600c..1d63e0990 100644 --- a/types/errors_test.go +++ b/types/errors_test.go @@ -1,6 +1,7 @@ package types import ( + "fmt" "testing" "github.com/stretchr/testify/require" @@ -65,3 +66,28 @@ func TestErrFn(t *testing.T) { require.Equal(t, ABCICodeOK, ToABCICode(CodespaceRoot, CodeOK)) } + +func TestAppendMsgToErr(t *testing.T) { + for i, errFn := range errFns { + err := errFn("") + errMsg := err.Stacktrace().Error() + abciLog := err.ABCILog() + + // plain msg error + msg := AppendMsgToErr("something unexpected happened", errMsg) + require.Equal(t, fmt.Sprintf("something unexpected happened; %s", + errMsg), + msg, + fmt.Sprintf("Should have formatted the error message of ABCI Log. tc #%d", i)) + + // ABCI Log msg error + msg = AppendMsgToErr("something unexpected happened", abciLog) + msgIdx := mustGetMsgIndex(abciLog) + require.Equal(t, fmt.Sprintf("%s%s; %s}", + abciLog[:msgIdx], + "something unexpected happened", + abciLog[msgIdx:len(abciLog)-1]), + msg, + fmt.Sprintf("Should have formatted the error message of ABCI Log. tc #%d", i)) + } +} diff --git a/x/auth/client/rest/query.go b/x/auth/client/rest/query.go index 5d47eb692..0629cc939 100644 --- a/x/auth/client/rest/query.go +++ b/x/auth/client/rest/query.go @@ -1,7 +1,6 @@ package rest import ( - "fmt" "net/http" "github.com/cosmos/cosmos-sdk/client/context" @@ -47,7 +46,7 @@ func QueryAccountRequestHandlerFn( 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, err.Error()) return } @@ -60,7 +59,7 @@ 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, err.Error()) return } @@ -86,7 +85,7 @@ func QueryBalancesRequestHandlerFn( 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, err.Error()) return } @@ -99,7 +98,7 @@ func QueryBalancesRequestHandlerFn( // 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, err.Error()) return } diff --git a/x/gov/client/rest/rest.go b/x/gov/client/rest/rest.go index e7d9770e2..5ed12ac70 100644 --- a/x/gov/client/rest/rest.go +++ b/x/gov/client/rest/rest.go @@ -254,7 +254,6 @@ func queryDepositHandlerFn(cdc *codec.Codec, cliCtx context.CLIContext) http.Han 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()) return } @@ -319,7 +318,6 @@ func queryVoteHandlerFn(cdc *codec.Codec, cliCtx context.CLIContext) http.Handle 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()) return } @@ -393,7 +391,6 @@ func queryVotesOnProposalHandlerFn(cdc *codec.Codec, cliCtx context.CLIContext) utils.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) return } - utils.PostProcessResponse(w, cdc, res, cliCtx.Indent) } } @@ -411,7 +408,6 @@ func queryProposalsWithParameterFn(cdc *codec.Codec, cliCtx context.CLIContext) if len(bechVoterAddr) != 0 { 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()) return } @@ -421,7 +417,6 @@ func queryProposalsWithParameterFn(cdc *codec.Codec, cliCtx context.CLIContext) if len(bechDepositerAddr) != 0 { 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()) return } @@ -431,7 +426,6 @@ func queryProposalsWithParameterFn(cdc *codec.Codec, cliCtx context.CLIContext) if len(strProposalStatus) != 0 { 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()) return } @@ -456,7 +450,6 @@ func queryProposalsWithParameterFn(cdc *codec.Codec, cliCtx context.CLIContext) utils.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) return } - utils.PostProcessResponse(w, cdc, res, cliCtx.Indent) } } @@ -496,7 +489,6 @@ func queryTallyOnProposalHandlerFn(cdc *codec.Codec, cliCtx context.CLIContext) w.Write([]byte(err.Error())) return } - utils.PostProcessResponse(w, cdc, res, cliCtx.Indent) } } diff --git a/x/gov/errors.go b/x/gov/errors.go index 0825e00f8..cd1cf8a17 100644 --- a/x/gov/errors.go +++ b/x/gov/errors.go @@ -27,11 +27,11 @@ const ( // Error constructors func ErrUnknownProposal(codespace sdk.CodespaceType, proposalID int64) sdk.Error { - return sdk.NewError(codespace, CodeUnknownProposal, fmt.Sprintf("Unknown proposal - %d", proposalID)) + return sdk.NewError(codespace, CodeUnknownProposal, fmt.Sprintf("Unknown proposal with id %d", proposalID)) } func ErrInactiveProposal(codespace sdk.CodespaceType, proposalID int64) sdk.Error { - return sdk.NewError(codespace, CodeInactiveProposal, fmt.Sprintf("Inactive proposal - %d", proposalID)) + return sdk.NewError(codespace, CodeInactiveProposal, fmt.Sprintf("Inactive proposal with id %d", proposalID)) } func ErrAlreadyActiveProposal(codespace sdk.CodespaceType, proposalID int64) sdk.Error { diff --git a/x/gov/queryable.go b/x/gov/queryable.go index b0cb42374..93469a5ac 100644 --- a/x/gov/queryable.go +++ b/x/gov/queryable.go @@ -1,8 +1,6 @@ package gov import ( - "fmt" - "github.com/cosmos/cosmos-sdk/codec" sdk "github.com/cosmos/cosmos-sdk/types" abci "github.com/tendermint/tendermint/abci/types" @@ -52,17 +50,17 @@ func queryProposal(ctx sdk.Context, path []string, req abci.RequestQuery, keeper var params QueryProposalParams err2 := keeper.cdc.UnmarshalJSON(req.Data, ¶ms) if err2 != nil { - return []byte{}, sdk.ErrUnknownRequest(fmt.Sprintf("incorrectly formatted request data - %s", err2.Error())) + return nil, sdk.ErrUnknownRequest(sdk.AppendMsgToErr("incorrectly formatted request data", err2.Error())) } proposal := keeper.GetProposal(ctx, params.ProposalID) if proposal == nil { - return []byte{}, ErrUnknownProposal(DefaultCodespace, params.ProposalID) + return nil, ErrUnknownProposal(DefaultCodespace, params.ProposalID) } bz, err2 := codec.MarshalJSONIndent(keeper.cdc, proposal) if err2 != nil { - panic("could not marshal result to JSON") + return nil, sdk.ErrInternal(sdk.AppendMsgToErr("could not marshal result to JSON", err2.Error())) } return bz, nil } @@ -78,13 +76,13 @@ func queryDeposit(ctx sdk.Context, path []string, req abci.RequestQuery, keeper var params QueryDepositParams err2 := keeper.cdc.UnmarshalJSON(req.Data, ¶ms) if err2 != nil { - return []byte{}, sdk.ErrUnknownRequest(fmt.Sprintf("incorrectly formatted request data - %s", err2.Error())) + return nil, sdk.ErrUnknownRequest(sdk.AppendMsgToErr("incorrectly formatted request data", err2.Error())) } deposit, _ := keeper.GetDeposit(ctx, params.ProposalID, params.Depositer) bz, err2 := codec.MarshalJSONIndent(keeper.cdc, deposit) if err2 != nil { - panic("could not marshal result to JSON") + return nil, sdk.ErrInternal(sdk.AppendMsgToErr("could not marshal result to JSON", err2.Error())) } return bz, nil } @@ -100,13 +98,13 @@ func queryVote(ctx sdk.Context, path []string, req abci.RequestQuery, keeper Kee var params QueryVoteParams err2 := keeper.cdc.UnmarshalJSON(req.Data, ¶ms) if err2 != nil { - return []byte{}, sdk.ErrUnknownRequest(fmt.Sprintf("incorrectly formatted request data - %s", err2.Error())) + return nil, sdk.ErrUnknownRequest(sdk.AppendMsgToErr("incorrectly formatted request data", err2.Error())) } vote, _ := keeper.GetVote(ctx, params.ProposalID, params.Voter) bz, err2 := codec.MarshalJSONIndent(keeper.cdc, vote) if err2 != nil { - panic("could not marshal result to JSON") + return nil, sdk.ErrInternal(sdk.AppendMsgToErr("could not marshal result to JSON", err2.Error())) } return bz, nil } @@ -121,7 +119,7 @@ func queryDeposits(ctx sdk.Context, path []string, req abci.RequestQuery, keeper var params QueryDepositsParams err2 := keeper.cdc.UnmarshalJSON(req.Data, ¶ms) if err2 != nil { - return []byte{}, sdk.ErrUnknownRequest(fmt.Sprintf("incorrectly formatted request data - %s", err2.Error())) + return nil, sdk.ErrUnknownRequest(sdk.AppendMsgToErr("incorrectly formatted request data", err2.Error())) } var deposits []Deposit @@ -134,7 +132,7 @@ func queryDeposits(ctx sdk.Context, path []string, req abci.RequestQuery, keeper bz, err2 := codec.MarshalJSONIndent(keeper.cdc, deposits) if err2 != nil { - panic("could not marshal result to JSON") + return nil, sdk.ErrInternal(sdk.AppendMsgToErr("could not marshal result to JSON", err2.Error())) } return bz, nil } @@ -150,7 +148,7 @@ func queryVotes(ctx sdk.Context, path []string, req abci.RequestQuery, keeper Ke err2 := keeper.cdc.UnmarshalJSON(req.Data, ¶ms) if err2 != nil { - return []byte{}, sdk.ErrUnknownRequest(fmt.Sprintf("incorrectly formatted request data - %s", err2.Error())) + return nil, sdk.ErrUnknownRequest(sdk.AppendMsgToErr("incorrectly formatted request data", err2.Error())) } var votes []Vote @@ -163,7 +161,7 @@ func queryVotes(ctx sdk.Context, path []string, req abci.RequestQuery, keeper Ke bz, err2 := codec.MarshalJSONIndent(keeper.cdc, votes) if err2 != nil { - panic("could not marshal result to JSON") + return nil, sdk.ErrInternal(sdk.AppendMsgToErr("could not marshal result to JSON", err2.Error())) } return bz, nil } @@ -181,14 +179,14 @@ func queryProposals(ctx sdk.Context, path []string, req abci.RequestQuery, keepe var params QueryProposalsParams err2 := keeper.cdc.UnmarshalJSON(req.Data, ¶ms) if err2 != nil { - return []byte{}, sdk.ErrUnknownRequest(fmt.Sprintf("incorrectly formatted request data - %s", err2.Error())) + return nil, sdk.ErrUnknownRequest(sdk.AppendMsgToErr("incorrectly formatted request data", err2.Error())) } proposals := keeper.GetProposalsFiltered(ctx, params.Voter, params.Depositer, params.ProposalStatus, params.NumLatestProposals) bz, err2 := codec.MarshalJSONIndent(keeper.cdc, proposals) if err2 != nil { - panic("could not marshal result to JSON") + return nil, sdk.ErrInternal(sdk.AppendMsgToErr("could not marshal result to JSON", err2.Error())) } return bz, nil } @@ -205,12 +203,12 @@ func queryTally(ctx sdk.Context, path []string, req abci.RequestQuery, keeper Ke var proposalID int64 err2 := keeper.cdc.UnmarshalJSON(req.Data, proposalID) if err2 != nil { - return res, sdk.ErrUnknownRequest(fmt.Sprintf("incorrectly formatted request data - %s", err2.Error())) + return nil, sdk.ErrUnknownRequest(sdk.AppendMsgToErr("incorrectly formatted request data", err2.Error())) } proposal := keeper.GetProposal(ctx, proposalID) if proposal == nil { - return res, ErrUnknownProposal(DefaultCodespace, proposalID) + return nil, ErrUnknownProposal(DefaultCodespace, proposalID) } var tallyResult TallyResult @@ -225,7 +223,7 @@ func queryTally(ctx sdk.Context, path []string, req abci.RequestQuery, keeper Ke bz, err2 := codec.MarshalJSONIndent(keeper.cdc, tallyResult) if err2 != nil { - panic("could not marshal result to JSON") + return nil, sdk.ErrInternal(sdk.AppendMsgToErr("could not marshal result to JSON", err2.Error())) } return bz, nil } diff --git a/x/slashing/client/rest/query.go b/x/slashing/client/rest/query.go index e83f1a235..408b1f592 100644 --- a/x/slashing/client/rest/query.go +++ b/x/slashing/client/rest/query.go @@ -1,10 +1,10 @@ package rest import ( - "fmt" "net/http" "github.com/cosmos/cosmos-sdk/client/context" + "github.com/cosmos/cosmos-sdk/client/utils" "github.com/cosmos/cosmos-sdk/codec" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/x/slashing" @@ -26,8 +26,7 @@ func signingInfoHandlerFn(cliCtx context.CLIContext, storeName string, cdc *code pk, err := sdk.GetConsPubKeyBech32(vars["validator"]) if err != nil { - w.WriteHeader(http.StatusBadRequest) - w.Write([]byte(err.Error())) + utils.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) return } @@ -35,8 +34,7 @@ func signingInfoHandlerFn(cliCtx context.CLIContext, storeName string, cdc *code res, err := cliCtx.QueryStore(key, storeName) if err != nil { - w.WriteHeader(http.StatusInternalServerError) - w.Write([]byte(fmt.Sprintf("couldn't query signing info. Error: %s", err.Error()))) + utils.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) return } @@ -44,18 +42,17 @@ func signingInfoHandlerFn(cliCtx context.CLIContext, storeName string, cdc *code err = cdc.UnmarshalBinary(res, &signingInfo) if err != nil { - w.WriteHeader(http.StatusInternalServerError) - w.Write([]byte(fmt.Sprintf("couldn't decode signing info. Error: %s", err.Error()))) + utils.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) return } output, err := cdc.MarshalJSON(signingInfo) if err != nil { - w.WriteHeader(http.StatusInternalServerError) - w.Write([]byte(err.Error())) + utils.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) return } + w.Header().Set("Content-Type", "application/json") w.Write(output) } } diff --git a/x/slashing/client/rest/tx.go b/x/slashing/client/rest/tx.go index 972d4351f..eca88c65c 100644 --- a/x/slashing/client/rest/tx.go +++ b/x/slashing/client/rest/tx.go @@ -2,7 +2,6 @@ package rest import ( "bytes" - "fmt" "net/http" "github.com/cosmos/cosmos-sdk/client/context" @@ -49,10 +48,7 @@ func unjailRequestHandlerFn(cdc *codec.Codec, kb keys.Keybase, cliCtx context.CL valAddr, err := sdk.ValAddressFromBech32(req.ValidatorAddr) if err != nil { - utils.WriteErrorResponse( - w, http.StatusInternalServerError, - fmt.Sprintf("failed to decode validator; error: %s", err.Error()), - ) + utils.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) return } diff --git a/x/stake/client/rest/query.go b/x/stake/client/rest/query.go index a8fbfecf4..1995efcf6 100644 --- a/x/stake/client/rest/query.go +++ b/x/stake/client/rest/query.go @@ -1,12 +1,12 @@ package rest import ( - "fmt" "net/http" "strings" "github.com/cosmos/cosmos-sdk/client/context" "github.com/cosmos/cosmos-sdk/client/tx" + "github.com/cosmos/cosmos-sdk/client/utils" "github.com/cosmos/cosmos-sdk/codec" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/x/stake" @@ -92,8 +92,7 @@ func delegatorHandlerFn(cliCtx context.CLIContext, cdc *codec.Codec) http.Handle delegatorAddr, err := sdk.AccAddressFromBech32(bech32delegator) if err != nil { - w.WriteHeader(http.StatusBadRequest) - w.Write([]byte(err.Error())) + utils.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) return } @@ -103,16 +102,13 @@ func delegatorHandlerFn(cliCtx context.CLIContext, cdc *codec.Codec) http.Handle bz, err := cdc.MarshalJSON(params) if err != nil { - w.WriteHeader(http.StatusBadRequest) - w.Write([]byte(err.Error())) + utils.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) return } res, err := cliCtx.QueryWithData("custom/stake/delegator", bz) if err != nil { - w.WriteHeader(http.StatusInternalServerError) - w.Write([]byte(err.Error())) - + utils.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) return } @@ -132,15 +128,14 @@ func delegatorTxsHandlerFn(cliCtx context.CLIContext, cdc *codec.Codec) http.Han _, err := sdk.AccAddressFromBech32(delegatorAddr) if err != nil { - w.WriteHeader(http.StatusBadRequest) - w.Write([]byte(err.Error())) + utils.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) return } node, err := cliCtx.GetNode() if err != nil { w.WriteHeader(http.StatusInternalServerError) - w.Write([]byte(fmt.Sprintf("Couldn't get current Node information. Error: %s", err.Error()))) + w.Write([]byte(err.Error())) return } @@ -182,16 +177,14 @@ func delegatorTxsHandlerFn(cliCtx context.CLIContext, cdc *codec.Codec) http.Han for _, action := range actions { foundTxs, errQuery := queryTxs(node, cliCtx, cdc, action, delegatorAddr) if errQuery != nil { - w.WriteHeader(http.StatusInternalServerError) - w.Write([]byte(errQuery.Error())) + utils.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) } txs = append(txs, foundTxs...) } output, err = cdc.MarshalJSON(txs) if err != nil { - w.WriteHeader(http.StatusInternalServerError) - w.Write([]byte(err.Error())) + utils.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) return } w.Write(output) @@ -209,15 +202,13 @@ func unbondingDelegationHandlerFn(cliCtx context.CLIContext, cdc *codec.Codec) h delegatorAddr, err := sdk.AccAddressFromBech32(bech32delegator) if err != nil { - w.WriteHeader(http.StatusBadRequest) - w.Write([]byte(err.Error())) + utils.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) return } validatorAddr, err := sdk.ValAddressFromBech32(bech32validator) if err != nil { - w.WriteHeader(http.StatusBadRequest) - w.Write([]byte(err.Error())) + utils.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) return } @@ -228,16 +219,13 @@ func unbondingDelegationHandlerFn(cliCtx context.CLIContext, cdc *codec.Codec) h bz, err := cdc.MarshalJSON(params) if err != nil { - w.WriteHeader(http.StatusBadRequest) - w.Write([]byte(err.Error())) + utils.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) return } res, err := cliCtx.QueryWithData("custom/stake/unbondingDelegation", bz) if err != nil { - w.WriteHeader(http.StatusInternalServerError) - w.Write([]byte(err.Error())) - + utils.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) return } @@ -257,15 +245,13 @@ func delegationHandlerFn(cliCtx context.CLIContext, cdc *codec.Codec) http.Handl delegatorAddr, err := sdk.AccAddressFromBech32(bech32delegator) if err != nil { - w.WriteHeader(http.StatusBadRequest) - w.Write([]byte(err.Error())) + utils.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) return } validatorAddr, err := sdk.ValAddressFromBech32(bech32validator) if err != nil { - w.WriteHeader(http.StatusBadRequest) - w.Write([]byte(err.Error())) + utils.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) return } @@ -276,16 +262,13 @@ func delegationHandlerFn(cliCtx context.CLIContext, cdc *codec.Codec) http.Handl bz, err := cdc.MarshalJSON(params) if err != nil { - w.WriteHeader(http.StatusBadRequest) - w.Write([]byte(err.Error())) + utils.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) return } res, err := cliCtx.QueryWithData("custom/stake/delegation", bz) if err != nil { - w.WriteHeader(http.StatusInternalServerError) - w.Write([]byte(err.Error())) - + utils.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) return } @@ -304,8 +287,7 @@ func delegatorValidatorsHandlerFn(cliCtx context.CLIContext, cdc *codec.Codec) h delegatorAddr, err := sdk.AccAddressFromBech32(bech32delegator) if err != nil { - w.WriteHeader(http.StatusBadRequest) - w.Write([]byte(err.Error())) + utils.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) return } @@ -315,16 +297,13 @@ func delegatorValidatorsHandlerFn(cliCtx context.CLIContext, cdc *codec.Codec) h bz, err := cdc.MarshalJSON(params) if err != nil { - w.WriteHeader(http.StatusBadRequest) - w.Write([]byte(err.Error())) + utils.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) return } res, err := cliCtx.QueryWithData("custom/stake/delegatorValidators", bz) if err != nil { - w.WriteHeader(http.StatusInternalServerError) - w.Write([]byte(err.Error())) - + utils.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) return } @@ -345,8 +324,7 @@ func delegatorValidatorHandlerFn(cliCtx context.CLIContext, cdc *codec.Codec) ht delegatorAddr, err := sdk.AccAddressFromBech32(bech32delegator) validatorAddr, err := sdk.ValAddressFromBech32(bech32validator) if err != nil { - w.WriteHeader(http.StatusBadRequest) - w.Write([]byte(err.Error())) + utils.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) return } @@ -357,16 +335,13 @@ func delegatorValidatorHandlerFn(cliCtx context.CLIContext, cdc *codec.Codec) ht bz, err := cdc.MarshalJSON(params) if err != nil { - w.WriteHeader(http.StatusBadRequest) - w.Write([]byte(err.Error())) + utils.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) return } res, err := cliCtx.QueryWithData("custom/stake/delegatorValidator", bz) if err != nil { - w.WriteHeader(http.StatusInternalServerError) - w.Write([]byte(err.Error())) - + utils.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) return } @@ -382,9 +357,7 @@ func validatorsHandlerFn(cliCtx context.CLIContext) http.HandlerFunc { res, err := cliCtx.QueryWithData("custom/stake/validators", nil) if err != nil { - w.WriteHeader(http.StatusInternalServerError) - w.Write([]byte(err.Error())) - + utils.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) return } @@ -404,8 +377,7 @@ func validatorHandlerFn(cliCtx context.CLIContext, cdc *codec.Codec) http.Handle validatorAddr, err := sdk.ValAddressFromBech32(bech32validatorAddr) if err != nil { - w.WriteHeader(http.StatusBadRequest) - w.Write([]byte(err.Error())) + utils.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) return } @@ -415,16 +387,13 @@ func validatorHandlerFn(cliCtx context.CLIContext, cdc *codec.Codec) http.Handle bz, err := cdc.MarshalJSON(params) if err != nil { - w.WriteHeader(http.StatusBadRequest) - w.Write([]byte(err.Error())) + utils.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) return } res, err := cliCtx.QueryWithData("custom/stake/validator", bz) if err != nil { - w.WriteHeader(http.StatusInternalServerError) - w.Write([]byte(err.Error())) - + utils.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) return } @@ -440,9 +409,7 @@ func poolHandlerFn(cliCtx context.CLIContext) http.HandlerFunc { res, err := cliCtx.QueryWithData("custom/stake/pool", nil) if err != nil { - w.WriteHeader(http.StatusInternalServerError) - w.Write([]byte(err.Error())) - + utils.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) return } @@ -458,9 +425,7 @@ func paramsHandlerFn(cliCtx context.CLIContext) http.HandlerFunc { res, err := cliCtx.QueryWithData("custom/stake/parameters", nil) if err != nil { - w.WriteHeader(http.StatusInternalServerError) - w.Write([]byte(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 427fb5e19..ae991dd4b 100644 --- a/x/stake/client/rest/tx.go +++ b/x/stake/client/rest/tx.go @@ -2,7 +2,6 @@ package rest import ( "bytes" - "fmt" "io/ioutil" "net/http" @@ -66,15 +65,13 @@ func delegationsRequestHandlerFn(cdc *codec.Codec, kb keys.Keybase, cliCtx conte body, err := ioutil.ReadAll(r.Body) if err != nil { - w.WriteHeader(http.StatusBadRequest) - w.Write([]byte(err.Error())) + utils.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) return } err = cdc.UnmarshalJSON(body, &req) if err != nil { - w.WriteHeader(http.StatusBadRequest) - w.Write([]byte(err.Error())) + utils.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) return } @@ -85,8 +82,7 @@ func delegationsRequestHandlerFn(cdc *codec.Codec, kb keys.Keybase, cliCtx conte info, err := kb.Get(baseReq.Name) if err != nil { - w.WriteHeader(http.StatusUnauthorized) - w.Write([]byte(err.Error())) + utils.WriteErrorResponse(w, http.StatusUnauthorized, err.Error()) return } @@ -99,13 +95,13 @@ func delegationsRequestHandlerFn(cdc *codec.Codec, kb keys.Keybase, cliCtx conte for _, msg := range req.Delegations { delAddr, err := sdk.AccAddressFromBech32(msg.DelegatorAddr) if err != nil { - utils.WriteErrorResponse(w, http.StatusInternalServerError, fmt.Sprintf("Couldn't decode delegator. Error: %s", err.Error())) + utils.WriteErrorResponse(w, http.StatusInternalServerError, 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, err.Error()) return } @@ -126,7 +122,7 @@ func delegationsRequestHandlerFn(cdc *codec.Codec, kb keys.Keybase, cliCtx conte for _, msg := range req.BeginRedelegates { delAddr, err := sdk.AccAddressFromBech32(msg.DelegatorAddr) if err != nil { - utils.WriteErrorResponse(w, http.StatusInternalServerError, fmt.Sprintf("Couldn't decode validator. Error: %s", err.Error())) + utils.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) return } @@ -137,18 +133,18 @@ func delegationsRequestHandlerFn(cdc *codec.Codec, kb keys.Keybase, cliCtx conte 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, 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, 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, err.Error()) return } @@ -165,7 +161,7 @@ func delegationsRequestHandlerFn(cdc *codec.Codec, kb keys.Keybase, cliCtx conte for _, msg := range req.BeginUnbondings { delAddr, err := sdk.AccAddressFromBech32(msg.DelegatorAddr) if err != nil { - utils.WriteErrorResponse(w, http.StatusInternalServerError, fmt.Sprintf("Couldn't decode delegator. Error: %s", err.Error())) + utils.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) return } @@ -176,13 +172,13 @@ func delegationsRequestHandlerFn(cdc *codec.Codec, kb keys.Keybase, cliCtx conte 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, 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, err.Error()) return } diff --git a/x/stake/querier/queryable.go b/x/stake/querier/queryable.go index 8537f2bd4..7cf01d0d3 100644 --- a/x/stake/querier/queryable.go +++ b/x/stake/querier/queryable.go @@ -1,8 +1,6 @@ package querier import ( - "fmt" - "github.com/cosmos/cosmos-sdk/codec" sdk "github.com/cosmos/cosmos-sdk/types" keep "github.com/cosmos/cosmos-sdk/x/stake/keeper" @@ -79,7 +77,7 @@ func queryValidators(ctx sdk.Context, cdc *codec.Codec, k keep.Keeper) (res []by res, errRes := codec.MarshalJSONIndent(cdc, validators) if err != nil { - return nil, sdk.ErrInternal(fmt.Sprintf("could not marshal result to JSON: %s", errRes.Error())) + return nil, sdk.ErrInternal(sdk.AppendMsgToErr("could not marshal result to JSON", errRes.Error())) } return res, nil } @@ -89,7 +87,7 @@ func queryValidator(ctx sdk.Context, cdc *codec.Codec, req abci.RequestQuery, k errRes := cdc.UnmarshalJSON(req.Data, ¶ms) if errRes != nil { - return []byte{}, sdk.ErrUnknownAddress(fmt.Sprintf("incorrectly formatted request address: %s", err.Error())) + return []byte{}, sdk.ErrUnknownAddress("") } validator, found := k.GetValidator(ctx, params.ValidatorAddr) @@ -99,7 +97,7 @@ func queryValidator(ctx sdk.Context, cdc *codec.Codec, req abci.RequestQuery, k res, errRes = codec.MarshalJSONIndent(cdc, validator) if errRes != nil { - return nil, sdk.ErrInternal(fmt.Sprintf("could not marshal result to JSON: %s", errRes.Error())) + return nil, sdk.ErrInternal(sdk.AppendMsgToErr("could not marshal result to JSON", errRes.Error())) } return res, nil } @@ -108,7 +106,7 @@ func queryDelegator(ctx sdk.Context, cdc *codec.Codec, req abci.RequestQuery, k var params QueryDelegatorParams errRes := cdc.UnmarshalJSON(req.Data, ¶ms) if errRes != nil { - return []byte{}, sdk.ErrUnknownAddress(fmt.Sprintf("incorrectly formatted request address: %s", errRes.Error())) + return []byte{}, sdk.ErrUnknownAddress("") } delegations := k.GetAllDelegatorDelegations(ctx, params.DelegatorAddr) unbondingDelegations := k.GetAllUnbondingDelegations(ctx, params.DelegatorAddr) @@ -122,7 +120,7 @@ func queryDelegator(ctx sdk.Context, cdc *codec.Codec, req abci.RequestQuery, k res, errRes = codec.MarshalJSONIndent(cdc, summary) if errRes != nil { - return nil, sdk.ErrInternal(fmt.Sprintf("could not marshal result to JSON: %s", errRes.Error())) + return nil, sdk.ErrInternal(sdk.AppendMsgToErr("could not marshal result to JSON", errRes.Error())) } return res, nil } @@ -134,14 +132,14 @@ func queryDelegatorValidators(ctx sdk.Context, cdc *codec.Codec, req abci.Reques errRes := cdc.UnmarshalJSON(req.Data, ¶ms) if errRes != nil { - return []byte{}, sdk.ErrUnknownAddress(fmt.Sprintf("incorrectly formatted request address: %s", errRes.Error())) + return []byte{}, sdk.ErrUnknownAddress("") } validators := k.GetDelegatorValidators(ctx, params.DelegatorAddr, stakeParams.MaxValidators) res, errRes = codec.MarshalJSONIndent(cdc, validators) if errRes != nil { - return nil, sdk.ErrInternal(fmt.Sprintf("could not marshal result to JSON: %s", errRes.Error())) + return nil, sdk.ErrInternal(sdk.AppendMsgToErr("could not marshal result to JSON", errRes.Error())) } return res, nil } @@ -151,7 +149,7 @@ func queryDelegatorValidator(ctx sdk.Context, cdc *codec.Codec, req abci.Request errRes := cdc.UnmarshalJSON(req.Data, ¶ms) if errRes != nil { - return []byte{}, sdk.ErrUnknownRequest(fmt.Sprintf("incorrectly formatted request address: %s", errRes.Error())) + return []byte{}, sdk.ErrUnknownAddress("") } validator, err := k.GetDelegatorValidator(ctx, params.DelegatorAddr, params.ValidatorAddr) @@ -161,7 +159,7 @@ func queryDelegatorValidator(ctx sdk.Context, cdc *codec.Codec, req abci.Request res, errRes = codec.MarshalJSONIndent(cdc, validator) if errRes != nil { - return nil, sdk.ErrInternal(fmt.Sprintf("could not marshal result to JSON: %s", errRes.Error())) + return nil, sdk.ErrInternal(sdk.AppendMsgToErr("could not marshal result to JSON", errRes.Error())) } return res, nil } @@ -171,7 +169,7 @@ func queryDelegation(ctx sdk.Context, cdc *codec.Codec, req abci.RequestQuery, k errRes := cdc.UnmarshalJSON(req.Data, ¶ms) if errRes != nil { - return []byte{}, sdk.ErrUnknownRequest(fmt.Sprintf("incorrectly formatted request address: %s", errRes.Error())) + return []byte{}, sdk.ErrUnknownAddress("") } delegation, found := k.GetDelegation(ctx, params.DelegatorAddr, params.ValidatorAddr) @@ -181,7 +179,7 @@ func queryDelegation(ctx sdk.Context, cdc *codec.Codec, req abci.RequestQuery, k res, errRes = codec.MarshalJSONIndent(cdc, delegation) if errRes != nil { - return nil, sdk.ErrInternal(fmt.Sprintf("could not marshal result to JSON: %s", errRes.Error())) + return nil, sdk.ErrInternal(sdk.AppendMsgToErr("could not marshal result to JSON", errRes.Error())) } return res, nil } @@ -191,7 +189,7 @@ func queryUnbondingDelegation(ctx sdk.Context, cdc *codec.Codec, req abci.Reques errRes := cdc.UnmarshalJSON(req.Data, ¶ms) if errRes != nil { - return []byte{}, sdk.ErrUnknownRequest(fmt.Sprintf("incorrectly formatted request address: %s", errRes.Error())) + return []byte{}, sdk.ErrUnknownAddress("") } unbond, found := k.GetUnbondingDelegation(ctx, params.DelegatorAddr, params.ValidatorAddr) @@ -201,7 +199,7 @@ func queryUnbondingDelegation(ctx sdk.Context, cdc *codec.Codec, req abci.Reques res, errRes = codec.MarshalJSONIndent(cdc, unbond) if errRes != nil { - return nil, sdk.ErrInternal(fmt.Sprintf("could not marshal result to JSON: %s", errRes.Error())) + return nil, sdk.ErrInternal(sdk.AppendMsgToErr("could not marshal result to JSON", errRes.Error())) } return res, nil } @@ -211,7 +209,7 @@ func queryPool(ctx sdk.Context, cdc *codec.Codec, k keep.Keeper) (res []byte, er res, errRes := codec.MarshalJSONIndent(cdc, pool) if errRes != nil { - return nil, sdk.ErrInternal(fmt.Sprintf("could not marshal result to JSON: %s", errRes.Error())) + return nil, sdk.ErrInternal(sdk.AppendMsgToErr("could not marshal result to JSON", errRes.Error())) } return res, nil } @@ -221,7 +219,7 @@ func queryParameters(ctx sdk.Context, cdc *codec.Codec, k keep.Keeper) (res []by res, errRes := codec.MarshalJSONIndent(cdc, params) if errRes != nil { - return nil, sdk.ErrInternal(fmt.Sprintf("could not marshal result to JSON: %s", errRes.Error())) + return nil, sdk.ErrInternal(sdk.AppendMsgToErr("could not marshal result to JSON", errRes.Error())) } return res, nil } From 593921d04d6eecf026441a6cc1067369baa1f4a0 Mon Sep 17 00:00:00 2001 From: Alessio Treglia Date: Fri, 19 Oct 2018 11:00:27 -0700 Subject: [PATCH 38/40] Merge PR #2524: Replace GenTx with StdTx Rework the process of loading a genesis.json file to load a starting app state and set of initial transactions to process. * New function to create genesis account from MsgCreateValidator * Add arg to PrintUnsignedStdTx() to actually operate in offline mode * New func processStdTxs() * Remove gen-tx command * Cleanup, return validators as they need to be written into genesis.json * Modify gaiad init to allow auto-create of stdTx * Remove server/testnet.go * Don't load node_key.json, which might not be available * Get the txs through DeliverTx * Add app.slashingKeeper.AddValidators at the end of genesis * On InitChain(), Signature's account number must be 0 * Add (tentative?) command to generate {node_key,priv_validator}.json files * Reintroduce gaiad testnet * Prompt user for passwords * Update gaia to work with auth.StdTx * Remove test_utils, NewTestGaiaAppGenState is now deprecated * Combine --genesis-format and --generate-only * Improve sign command's --offline flag documentation * Moniker must be set * Call app.slashingKeeper.AddValidators() even if len(txs) == 0 * Refactoring, introduce gaiad init --skip-genesis, code cleanup * Drop unnecessary workaround to make lcd_tests pass * Reintroduce gentx * Simple name changes, GenesisState.Txs -> .GenTxs; OWK -> OverwriteKey; OverwriteKeys -> OverwriteKey --- Gopkg.lock | 6 +- Gopkg.toml | 4 +- PENDING.md | 8 + client/lcd/lcd_test.go | 2 +- client/lcd/test_helpers.go | 66 ++-- client/utils/utils.go | 9 +- cmd/gaia/app/app.go | 52 ++- cmd/gaia/app/genesis.go | 241 +++++------- cmd/gaia/app/genesis_test.go | 39 +- cmd/gaia/app/test_utils.go | 80 ---- cmd/gaia/cli_test/cli_test.go | 30 +- cmd/gaia/cmd/gaiad/main.go | 1 + cmd/gaia/init/gentx.go | 120 ++++++ cmd/gaia/init/init.go | 416 +++++++++----------- cmd/gaia/init/init_test.go | 49 ++- cmd/gaia/init/testnet.go | 112 ++++-- docs/getting-started/join-testnet.md | 2 +- examples/basecoin/app/app.go | 7 + examples/basecoin/cli_test/cli_test.go | 13 +- examples/basecoin/cmd/basecli/main.go | 4 +- examples/basecoin/cmd/basecoind/main.go | 74 +++- examples/democoin/app/app.go | 7 + examples/democoin/cli_test/cli_test.go | 13 +- examples/democoin/cmd/democli/main.go | 4 +- examples/democoin/cmd/democoind/main.go | 75 +++- server/config/config.go | 13 - server/init.go | 83 +--- server/mock/app.go | 14 - server/test_helpers.go | 2 + tests/gobash.go | 8 +- x/auth/ante.go | 19 +- x/auth/ante_test.go | 66 ++++ x/auth/client/cli/sign.go | 10 +- x/bank/app_test.go | 4 +- x/distribution/keeper/hooks.go | 6 +- x/mock/simulation/random_simulate_blocks.go | 4 +- x/stake/app_test.go | 6 +- x/stake/client/cli/flags.go | 6 + x/stake/client/cli/tx.go | 15 +- 39 files changed, 983 insertions(+), 707 deletions(-) delete mode 100644 cmd/gaia/app/test_utils.go create mode 100644 cmd/gaia/init/gentx.go diff --git a/Gopkg.lock b/Gopkg.lock index 3817fd238..c679450b9 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -452,7 +452,7 @@ version = "v0.11.0" [[projects]] - digest = "1:a69eebd15b05045ffdb10a984e001fadc5666f74383de3d2a9ee5862ee99cfdc" + digest = "1:f9c7a1f3ee087476f4883c33cc7c1bdbe56b9670b2fb27855ea2f386393272f5" name = "github.com/tendermint/tendermint" packages = [ "abci/client", @@ -518,8 +518,8 @@ "version", ] pruneopts = "UT" - revision = "0c9c3292c918617624f6f3fbcd95eceade18bcd5" - version = "v0.25.0" + revision = "90eda9bfb6e6daeed1c8015df41cb36772d91778" + version = "v0.25.1-rc0" [[projects]] digest = "1:7886f86064faff6f8d08a3eb0e8c773648ff5a2e27730831e2bfbf07467f6666" diff --git a/Gopkg.toml b/Gopkg.toml index 05140857d..0902bc412 100644 --- a/Gopkg.toml +++ b/Gopkg.toml @@ -57,11 +57,11 @@ [[override]] name = "github.com/tendermint/tendermint" - version = "=0.25.0" + version = "=0.25.1-rc0" ## deps without releases: -[[constraint]] +[[override]] name = "golang.org/x/crypto" source = "https://github.com/tendermint/crypto" revision = "3764759f34a542a3aef74d6b02e35be7ab893bba" diff --git a/PENDING.md b/PENDING.md index 00d0ca3cc..2dc3013de 100644 --- a/PENDING.md +++ b/PENDING.md @@ -20,6 +20,7 @@ BREAKING CHANGES * [cli] [\#2190](https://github.com/cosmos/cosmos-sdk/issues/2190) `gaiacli init --gen-txs` is now `gaiacli init --with-txs` to reduce confusion * [cli] \#2073 --from can now be either an address or a key name * [cli] [\#1184](https://github.com/cosmos/cosmos-sdk/issues/1184) Subcommands reorganisation, see [\#2390](https://github.com/cosmos/cosmos-sdk/pull/2390) for a comprehensive list of changes. + * [cli] [\#2524](https://github.com/cosmos/cosmos-sdk/issues/2524) Add support offline mode to `gaiacli tx sign`. Lookups are not performed if the flag `--offline` is on. * Gaia * Make the transient store key use a distinct store key. [#2013](https://github.com/cosmos/cosmos-sdk/pull/2013) @@ -45,6 +46,12 @@ BREAKING CHANGES * [x/slashing] \#2430 Simulate more slashes, check if validator is jailed before jailing * [x/stake] \#2393 Removed `CompleteUnbonding` and `CompleteRedelegation` Msg types, and instead added unbonding/redelegation queues to endblocker * [x/stake] \#1673 Validators are no longer deleted until they can no longer possibly be slashed + * [\#1890](https://github.com/cosmos/cosmos-sdk/issues/1890) Start chain with initial state + sequence of transactions + * [cli] Rename `gaiad init gentx` to `gaiad gentx`. + * [cli] Add `--skip-genesis` flag to `gaiad init` to prevent `genesis.json` generation. + * Drop `GenesisTx` in favor of a signed `StdTx` with only one `MsgCreateValidator` message. + * [cli] Port `gaiad init` and `gaiad testnet` to work with `StdTx` genesis transactions. + * [cli] Add `--moniker` flag to `gaiad init` to override moniker when generating `genesis.json` - i.e. it takes effect when running with the `--with-txs` flag, it is ignored otherwise. * SDK * [core] \#2219 Update to Tendermint 0.24.0 @@ -115,6 +122,7 @@ FEATURES * [cli] \#2220 Add `gaiacli config` feature to interactively create CLI config files to reduce the number of required flags * [stake][cli] [\#1672](https://github.com/cosmos/cosmos-sdk/issues/1672) Introduced new commission flags for validator commands `create-validator` and `edit-validator`. + * [stake][cli] [\#1890](https://github.com/cosmos/cosmos-sdk/issues/1890) Add `--genesis-format` flag to `gaiacli tx create-validator` to produce transactions in genesis-friendly format. * Gaia * [cli] #2170 added ability to show the node's address via `gaiad tendermint show-address` diff --git a/client/lcd/lcd_test.go b/client/lcd/lcd_test.go index 7aea16d86..c1c018778 100644 --- a/client/lcd/lcd_test.go +++ b/client/lcd/lcd_test.go @@ -496,7 +496,7 @@ func TestValidatorsQuery(t *testing.T) { require.Equal(t, 1, len(operAddrs)) validators := getValidators(t, port) - require.Equal(t, len(validators), 1) + require.Equal(t, 1, len(validators), fmt.Sprintf("%+v", validators)) // make sure all the validators were found (order unknown because sorted by operator addr) foundVal := false diff --git a/client/lcd/test_helpers.go b/client/lcd/test_helpers.go index 3585023cd..ee7fdbdef 100644 --- a/client/lcd/test_helpers.go +++ b/client/lcd/test_helpers.go @@ -4,6 +4,8 @@ import ( "bytes" "encoding/json" "fmt" + "github.com/cosmos/cosmos-sdk/x/stake" + "github.com/tendermint/tendermint/crypto/secp256k1" "io/ioutil" "net" "net/http" @@ -14,7 +16,7 @@ import ( "testing" "github.com/cosmos/cosmos-sdk/client" - keys "github.com/cosmos/cosmos-sdk/client/keys" + "github.com/cosmos/cosmos-sdk/client/keys" gapp "github.com/cosmos/cosmos-sdk/cmd/gaia/app" "github.com/cosmos/cosmos-sdk/codec" crkeys "github.com/cosmos/cosmos-sdk/crypto/keys" @@ -26,6 +28,7 @@ import ( "github.com/spf13/viper" "github.com/stretchr/testify/require" + txbuilder "github.com/cosmos/cosmos-sdk/x/auth/client/txbuilder" abci "github.com/tendermint/tendermint/abci/types" tmcfg "github.com/tendermint/tendermint/config" "github.com/tendermint/tendermint/crypto" @@ -205,42 +208,43 @@ func InitializeTestLCD( genesisFile := config.GenesisFile() genDoc, err := tmtypes.GenesisDocFromFile(genesisFile) - require.NoError(t, err) - - // append initial (proposing) validator - genDoc.Validators[0] = tmtypes.GenesisValidator{ - PubKey: privVal.GetPubKey(), - Power: 100, // create enough power to enable 2/3 voting power - Name: "validator-1", - } + require.Nil(t, err) + genDoc.Validators = nil + genDoc.SaveAs(genesisFile) + genTxs := []json.RawMessage{} // append any additional (non-proposing) validators - for i := 1; i < nValidators; i++ { - genDoc.Validators = append(genDoc.Validators, - tmtypes.GenesisValidator{ - PubKey: ed25519.GenPrivKey().PubKey(), - Power: 1, - Name: fmt.Sprintf("validator-%d", i+1), - }, + for i := 0; i < nValidators; i++ { + operPrivKey := secp256k1.GenPrivKey() + operAddr := operPrivKey.PubKey().Address() + pubKey := privVal.PubKey + delegation := 100 + if i > 0 { + pubKey = ed25519.GenPrivKey().PubKey() + delegation = 1 + } + msg := stake.NewMsgCreateValidator( + sdk.ValAddress(operAddr), + pubKey, + sdk.NewCoin("steak", sdk.NewInt(int64(delegation))), + stake.Description{Moniker: fmt.Sprintf("validator-%d", i+1)}, + stake.NewCommissionMsg(sdk.ZeroDec(), sdk.ZeroDec(), sdk.ZeroDec()), ) - } - - var appGenTxs []json.RawMessage - - for _, gdValidator := range genDoc.Validators { - operAddr := ed25519.GenPrivKey().PubKey().Address() - pk := gdValidator.PubKey - - valConsPubKeys = append(valConsPubKeys, pk) + stdSignMsg := txbuilder.StdSignMsg{ + ChainID: genDoc.ChainID, + Msgs: []sdk.Msg{msg}, + } + sig, err := operPrivKey.Sign(stdSignMsg.Bytes()) + require.Nil(t, err) + tx := auth.NewStdTx([]sdk.Msg{msg}, auth.StdFee{}, []auth.StdSignature{{Signature: sig, PubKey: operPrivKey.PubKey()}}, "") + txBytes, err := cdc.MarshalJSON(tx) + require.Nil(t, err) + genTxs = append(genTxs, txBytes) + valConsPubKeys = append(valConsPubKeys, pubKey) valOperAddrs = append(valOperAddrs, sdk.ValAddress(operAddr)) - - appGenTx, _, _, err := gapp.GaiaAppGenTxNF(cdc, pk, sdk.AccAddress(operAddr), gdValidator.Name) - require.NoError(t, err) - - appGenTxs = append(appGenTxs, appGenTx) } - genesisState, err := gapp.NewTestGaiaAppGenState(cdc, appGenTxs[:], genDoc.Validators, valOperAddrs) + genesisState, err := gapp.GaiaAppGenState(cdc, genTxs) require.NoError(t, err) // add some tokens to init accounts diff --git a/client/utils/utils.go b/client/utils/utils.go index 364b9e692..f5cf04168 100644 --- a/client/utils/utils.go +++ b/client/utils/utils.go @@ -107,7 +107,8 @@ func PrintUnsignedStdTx(txBldr authtxb.TxBuilder, cliCtx context.CLIContext, msg // SignStdTx appends a signature to a StdTx and returns a copy of a it. If appendSig // is false, it replaces the signatures already attached with the new signature. -func SignStdTx(txBldr authtxb.TxBuilder, cliCtx context.CLIContext, name string, stdTx auth.StdTx, appendSig bool) (auth.StdTx, error) { +// Don't perform online validation or lookups if offline is true. +func SignStdTx(txBldr authtxb.TxBuilder, cliCtx context.CLIContext, name string, stdTx auth.StdTx, appendSig bool, offline bool) (auth.StdTx, error) { var signedStdTx auth.StdTx keybase, err := keys.GetKeyBase() @@ -122,10 +123,10 @@ func SignStdTx(txBldr authtxb.TxBuilder, cliCtx context.CLIContext, name string, // Check whether the address is a signer if !isTxSigner(sdk.AccAddress(addr), stdTx.GetSigners()) { - fmt.Fprintf(os.Stderr, "WARNING: The generated transaction's intended signer does not match the given signer: '%v'", name) + fmt.Fprintf(os.Stderr, "WARNING: The generated transaction's intended signer does not match the given signer: '%v'\n", name) } - if txBldr.AccountNumber == 0 { + if !offline && txBldr.AccountNumber == 0 { accNum, err := cliCtx.GetAccountNumber(addr) if err != nil { return signedStdTx, err @@ -133,7 +134,7 @@ func SignStdTx(txBldr authtxb.TxBuilder, cliCtx context.CLIContext, name string, txBldr = txBldr.WithAccountNumber(accNum) } - if txBldr.Sequence == 0 { + if !offline && txBldr.Sequence == 0 { accSeq, err := cliCtx.GetAccountSequence(addr) if err != nil { return signedStdTx, err diff --git a/cmd/gaia/app/app.go b/cmd/gaia/app/app.go index 2c72e3aaf..6afeae793 100644 --- a/cmd/gaia/app/app.go +++ b/cmd/gaia/app/app.go @@ -2,15 +2,7 @@ package app import ( "encoding/json" - "io" - "os" - - abci "github.com/tendermint/tendermint/abci/types" - cmn "github.com/tendermint/tendermint/libs/common" - dbm "github.com/tendermint/tendermint/libs/db" - "github.com/tendermint/tendermint/libs/log" - tmtypes "github.com/tendermint/tendermint/types" - + "fmt" bam "github.com/cosmos/cosmos-sdk/baseapp" "github.com/cosmos/cosmos-sdk/codec" sdk "github.com/cosmos/cosmos-sdk/types" @@ -21,10 +13,20 @@ import ( "github.com/cosmos/cosmos-sdk/x/params" "github.com/cosmos/cosmos-sdk/x/slashing" "github.com/cosmos/cosmos-sdk/x/stake" + abci "github.com/tendermint/tendermint/abci/types" + cmn "github.com/tendermint/tendermint/libs/common" + dbm "github.com/tendermint/tendermint/libs/db" + "github.com/tendermint/tendermint/libs/log" + tmtypes "github.com/tendermint/tendermint/types" + "io" + "os" + "sort" ) const ( appName = "GaiaApp" + // DefaultKeyPass contains the default key password for genesis transactions + DefaultKeyPass = "12345678" ) // default home directories for expected binaries @@ -238,6 +240,38 @@ func (app *GaiaApp) initChainer(ctx sdk.Context, req abci.RequestInitChain) abci panic(err) // TODO find a way to do this w/o panics } + if len(genesisState.GenTxs) > 0 { + for _, genTx := range genesisState.GenTxs { + var tx auth.StdTx + err = app.cdc.UnmarshalJSON(genTx, &tx) + if err != nil { + panic(err) + } + bz := app.cdc.MustMarshalBinary(tx) + res := app.BaseApp.DeliverTx(bz) + if !res.IsOK() { + panic(res.Log) + } + } + + validators = app.stakeKeeper.ApplyAndReturnValidatorSetUpdates(ctx) + } + app.slashingKeeper.AddValidators(ctx, validators) + + // sanity check + if len(req.Validators) > 0 { + if len(req.Validators) != len(validators) { + panic(fmt.Errorf("len(RequestInitChain.Validators) != len(validators) (%d != %d) ", len(req.Validators), len(validators))) + } + sort.Sort(abci.ValidatorUpdates(req.Validators)) + sort.Sort(abci.ValidatorUpdates(validators)) + for i, val := range validators { + if !val.Equal(req.Validators[i]) { + panic(fmt.Errorf("validators[%d] != req.Validators[%d] ", i, i)) + } + } + } + return abci.ResponseInitChain{ Validators: validators, } diff --git a/cmd/gaia/app/genesis.go b/cmd/gaia/app/genesis.go index 690d92502..9d52c9a57 100644 --- a/cmd/gaia/app/genesis.go +++ b/cmd/gaia/app/genesis.go @@ -4,31 +4,27 @@ import ( "encoding/json" "errors" "fmt" + "io/ioutil" + "os" + "path/filepath" + "sort" + "strings" - "github.com/cosmos/cosmos-sdk/client" "github.com/cosmos/cosmos-sdk/codec" "github.com/cosmos/cosmos-sdk/server" - "github.com/cosmos/cosmos-sdk/server/config" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/x/auth" distr "github.com/cosmos/cosmos-sdk/x/distribution" "github.com/cosmos/cosmos-sdk/x/gov" "github.com/cosmos/cosmos-sdk/x/slashing" "github.com/cosmos/cosmos-sdk/x/stake" - - "github.com/spf13/pflag" - - "github.com/tendermint/tendermint/crypto" tmtypes "github.com/tendermint/tendermint/types" ) -// DefaultKeyPass contains the default key password for genesis transactions -const DefaultKeyPass = "12345678" - var ( // bonded tokens given to genesis validators/accounts freeFermionVal = int64(100) - freeFermionsAcc = sdk.NewInt(50) + freeFermionsAcc = sdk.NewInt(150) ) // State to Unmarshal @@ -38,6 +34,7 @@ type GenesisState struct { DistrData distr.GenesisState `json:"distr"` GovData gov.GenesisState `json:"gov"` SlashingData slashing.GenesisState `json:"slashing"` + GenTxs []json.RawMessage `json:"gentxs"` } // GenesisAccount doesn't need pubkey or sequence @@ -70,97 +67,12 @@ func (ga *GenesisAccount) ToAccount() (acc *auth.BaseAccount) { // get app init parameters for server init command func GaiaAppInit() server.AppInit { - fsAppGenState := pflag.NewFlagSet("", pflag.ContinueOnError) - - fsAppGenTx := pflag.NewFlagSet("", pflag.ContinueOnError) - fsAppGenTx.String(server.FlagName, "", "validator moniker, required") - fsAppGenTx.String(server.FlagClientHome, DefaultCLIHome, - "home directory for the client, used for key generation") - fsAppGenTx.Bool(server.FlagOWK, false, "overwrite the accounts created") return server.AppInit{ - FlagsAppGenState: fsAppGenState, - FlagsAppGenTx: fsAppGenTx, - AppGenTx: GaiaAppGenTx, - AppGenState: GaiaAppGenStateJSON, + AppGenState: GaiaAppGenStateJSON, } } -// simple genesis tx -type GaiaGenTx struct { - Name string `json:"name"` - Address sdk.AccAddress `json:"address"` - PubKey string `json:"pub_key"` -} - -// GaiaAppGenTx generates a Gaia genesis transaction. -func GaiaAppGenTx( - cdc *codec.Codec, pk crypto.PubKey, genTxConfig config.GenTx, -) (appGenTx, cliPrint json.RawMessage, validator tmtypes.GenesisValidator, err error) { - if genTxConfig.Name == "" { - return nil, nil, tmtypes.GenesisValidator{}, errors.New("Must specify --name (validator moniker)") - } - - buf := client.BufferStdin() - prompt := fmt.Sprintf("Password for account '%s' (default %s):", genTxConfig.Name, DefaultKeyPass) - - keyPass, err := client.GetPassword(prompt, buf) - if err != nil && keyPass != "" { - // An error was returned that either failed to read the password from - // STDIN or the given password is not empty but failed to meet minimum - // length requirements. - return appGenTx, cliPrint, validator, err - } - - if keyPass == "" { - keyPass = DefaultKeyPass - } - - addr, secret, err := server.GenerateSaveCoinKey( - genTxConfig.CliRoot, - genTxConfig.Name, - keyPass, - genTxConfig.Overwrite, - ) - if err != nil { - return appGenTx, cliPrint, validator, err - } - - mm := map[string]string{"secret": secret} - bz, err := cdc.MarshalJSON(mm) - if err != nil { - return appGenTx, cliPrint, validator, err - } - - cliPrint = json.RawMessage(bz) - appGenTx, _, validator, err = GaiaAppGenTxNF(cdc, pk, addr, genTxConfig.Name) - - return appGenTx, cliPrint, validator, err -} - -// Generate a gaia genesis transaction without flags -func GaiaAppGenTxNF(cdc *codec.Codec, pk crypto.PubKey, addr sdk.AccAddress, name string) ( - appGenTx, cliPrint json.RawMessage, validator tmtypes.GenesisValidator, err error) { - - var bz []byte - gaiaGenTx := GaiaGenTx{ - Name: name, - Address: addr, - PubKey: sdk.MustBech32ifyConsPub(pk), - } - bz, err = codec.MarshalJSONIndent(cdc, gaiaGenTx) - if err != nil { - return - } - appGenTx = json.RawMessage(bz) - - validator = tmtypes.GenesisValidator{ - PubKey: pk, - Power: freeFermionVal, - } - return -} - // Create the core parameters for genesis initialization for gaia // note that the pubkey input is this machines pubkey func GaiaAppGenState(cdc *codec.Codec, appGenTxs []json.RawMessage) (genesisState GenesisState, err error) { @@ -171,27 +83,27 @@ func GaiaAppGenState(cdc *codec.Codec, appGenTxs []json.RawMessage) (genesisStat // start with the default staking genesis state stakeData := stake.DefaultGenesisState() - slashingData := slashing.DefaultGenesisState() // get genesis flag account information genaccs := make([]GenesisAccount, len(appGenTxs)) - for i, appGenTx := range appGenTxs { - var genTx GaiaGenTx - err = cdc.UnmarshalJSON(appGenTx, &genTx) + for i, genTx := range appGenTxs { + var tx auth.StdTx + err = cdc.UnmarshalJSON(genTx, &tx) if err != nil { return } + msgs := tx.GetMsgs() + if len(msgs) != 1 { + err = errors.New("must provide genesis StdTx with exactly 1 CreateValidator message") + return + } + msg := msgs[0].(stake.MsgCreateValidator) // create the genesis account, give'm few steaks and a buncha token with there name - genaccs[i] = genesisAccountFromGenTx(genTx) + genaccs[i] = genesisAccountFromMsgCreateValidator(msg, freeFermionsAcc) stakeData.Pool.LooseTokens = stakeData.Pool.LooseTokens.Add(sdk.NewDecFromInt(freeFermionsAcc)) // increase the supply - - // add the validator - if len(genTx.Name) > 0 { - stakeData = addValidatorToStakeData(genTx, stakeData) - } } // create the final app state @@ -201,41 +113,17 @@ func GaiaAppGenState(cdc *codec.Codec, appGenTxs []json.RawMessage) (genesisStat DistrData: distr.DefaultGenesisState(), GovData: gov.DefaultGenesisState(), SlashingData: slashingData, + GenTxs: appGenTxs, } return } -func addValidatorToStakeData(genTx GaiaGenTx, stakeData stake.GenesisState) stake.GenesisState { - desc := stake.NewDescription(genTx.Name, "", "", "") - validator := stake.NewValidator( - sdk.ValAddress(genTx.Address), sdk.MustGetConsPubKeyBech32(genTx.PubKey), desc, - ) - - stakeData.Pool.LooseTokens = stakeData.Pool.LooseTokens.Add(sdk.NewDec(freeFermionVal)) // increase the supply - - // add some new shares to the validator - var issuedDelShares sdk.Dec - validator, stakeData.Pool, issuedDelShares = validator.AddTokensFromDel(stakeData.Pool, sdk.NewInt(freeFermionVal)) - stakeData.Validators = append(stakeData.Validators, validator) - - // create the self-delegation from the issuedDelShares - delegation := stake.Delegation{ - DelegatorAddr: sdk.AccAddress(validator.OperatorAddr), - ValidatorAddr: validator.OperatorAddr, - Shares: issuedDelShares, - Height: 0, - } - - stakeData.Bonds = append(stakeData.Bonds, delegation) - return stakeData -} - -func genesisAccountFromGenTx(genTx GaiaGenTx) GenesisAccount { - accAuth := auth.NewBaseAccountWithAddress(genTx.Address) - accAuth.Coins = sdk.Coins{ - {genTx.Name + "Token", sdk.NewInt(1000)}, - {"steak", freeFermionsAcc}, +func genesisAccountFromMsgCreateValidator(msg stake.MsgCreateValidator, amount sdk.Int) GenesisAccount { + accAuth := auth.NewBaseAccountWithAddress(sdk.AccAddress(msg.ValidatorAddr)) + accAuth.Coins = []sdk.Coin{ + {msg.Description.Moniker + "Token", sdk.NewInt(1000)}, + {"steak", amount}, } return NewGenesisAccount(&accAuth) } @@ -249,11 +137,11 @@ func GaiaValidateGenesisState(genesisState GenesisState) (err error) { if err != nil { return } - err = stake.ValidateGenesis(genesisState.StakeData) - if err != nil { - return + // skip stakeData validation as genesis is created from txs + if len(genesisState.GenTxs) > 0 { + return nil } - return + return stake.ValidateGenesis(genesisState.StakeData) } // Ensures that there are no duplicate accounts in the genesis state, @@ -272,7 +160,6 @@ func validateGenesisStateAccounts(accs []GenesisAccount) (err error) { // GaiaAppGenState but with JSON func GaiaAppGenStateJSON(cdc *codec.Codec, appGenTxs []json.RawMessage) (appState json.RawMessage, err error) { - // create the final app state genesisState, err := GaiaAppGenState(cdc, appGenTxs) if err != nil { @@ -281,3 +168,75 @@ func GaiaAppGenStateJSON(cdc *codec.Codec, appGenTxs []json.RawMessage) (appStat appState, err = codec.MarshalJSONIndent(cdc, genesisState) return } + +// CollectStdTxs processes and validates application's genesis StdTxs and returns the list of validators, +// appGenTxs, and persistent peers required to generate genesis.json. +func CollectStdTxs(moniker string, genTxsDir string, cdc *codec.Codec) ( + validators []tmtypes.GenesisValidator, appGenTxs []auth.StdTx, persistentPeers string, err error) { + var fos []os.FileInfo + fos, err = ioutil.ReadDir(genTxsDir) + if err != nil { + return + } + + var addresses []string + for _, fo := range fos { + filename := filepath.Join(genTxsDir, fo.Name()) + if !fo.IsDir() && (filepath.Ext(filename) != ".json") { + continue + } + + // get the genStdTx + var jsonRawTx []byte + jsonRawTx, err = ioutil.ReadFile(filename) + if err != nil { + return + } + var genStdTx auth.StdTx + err = cdc.UnmarshalJSON(jsonRawTx, &genStdTx) + if err != nil { + return + } + appGenTxs = append(appGenTxs, genStdTx) + + nodeAddr := genStdTx.GetMemo() + if len(nodeAddr) == 0 { + err = fmt.Errorf("couldn't find node's address in %s", fo.Name()) + return + } + + msgs := genStdTx.GetMsgs() + if len(msgs) != 1 { + err = errors.New("each genesis transaction must provide a single genesis message") + return + } + + // TODO: this could be decoupled from stake.MsgCreateValidator + // TODO: and we likely want to do it for real world Gaia + msg := msgs[0].(stake.MsgCreateValidator) + validators = append(validators, tmtypes.GenesisValidator{ + PubKey: msg.PubKey, + Power: freeFermionVal, + Name: msg.Description.Moniker, + }) + + // exclude itself from persistent peers + if msg.Description.Moniker != moniker { + addresses = append(addresses, nodeAddr) + } + } + + sort.Strings(addresses) + persistentPeers = strings.Join(addresses, ",") + + return +} + +func NewDefaultGenesisAccount(addr sdk.AccAddress) GenesisAccount { + accAuth := auth.NewBaseAccountWithAddress(addr) + accAuth.Coins = []sdk.Coin{ + {"fooToken", sdk.NewInt(1000)}, + {"steak", freeFermionsAcc}, + } + return NewGenesisAccount(&accAuth) +} diff --git a/cmd/gaia/app/genesis_test.go b/cmd/gaia/app/genesis_test.go index 9cbd1f49f..1acc9f393 100644 --- a/cmd/gaia/app/genesis_test.go +++ b/cmd/gaia/app/genesis_test.go @@ -25,25 +25,24 @@ var ( emptyPubkey crypto.PubKey ) -func makeGenesisState(genTxs []GaiaGenTx) GenesisState { +func makeGenesisState(t *testing.T, genTxs []auth.StdTx) GenesisState { // start with the default staking genesis state stakeData := stake.DefaultGenesisState() + genAccs := make([]GenesisAccount, len(genTxs)) - // get genesis flag account information - genaccs := make([]GenesisAccount, len(genTxs)) for i, genTx := range genTxs { - genaccs[i] = genesisAccountFromGenTx(genTx) - stakeData.Pool.LooseTokens = stakeData.Pool.LooseTokens.Add(sdk.NewDecFromInt(freeFermionsAcc)) // increase the supply + msgs := genTx.GetMsgs() + require.Equal(t, 1, len(msgs)) + msg := msgs[0].(stake.MsgCreateValidator) - // add the validator - if len(genTx.Name) > 0 { - stakeData = addValidatorToStakeData(genTx, stakeData) - } + // get genesis flag account information + genAccs[i] = genesisAccountFromMsgCreateValidator(msg, freeFermionsAcc) + stakeData.Pool.LooseTokens = stakeData.Pool.LooseTokens.Add(sdk.NewDecFromInt(freeFermionsAcc)) // increase the supply } // create the final app state return GenesisState{ - Accounts: genaccs, + Accounts: genAccs, StakeData: stakeData, GovData: gov.DefaultGenesisState(), } @@ -75,17 +74,23 @@ func TestGaiaAppGenState(t *testing.T) { // TODO correct: genesis account created, canididates created, pool token variance } +func makeMsg(name string, pk crypto.PubKey) auth.StdTx { + desc := stake.NewDescription(name, "", "", "") + comm := stakeTypes.CommissionMsg{} + msg := stake.NewMsgCreateValidator(sdk.ValAddress(pk.Address()), pk, sdk.NewInt64Coin("steak", 50), desc, comm) + return auth.NewStdTx([]sdk.Msg{msg}, auth.StdFee{}, nil, "") +} + func TestGaiaGenesisValidation(t *testing.T) { - genTxs := make([]GaiaGenTx, 2) - addr := pk1.Address() + genTxs := make([]auth.StdTx, 2) // Test duplicate accounts fails - genTxs[0] = GaiaGenTx{"", sdk.AccAddress(addr), ""} - genTxs[1] = GaiaGenTx{"", sdk.AccAddress(addr), ""} - genesisState := makeGenesisState(genTxs) + genTxs[0] = makeMsg("test-0", pk1) + genTxs[1] = makeMsg("test-1", pk1) + genesisState := makeGenesisState(t, genTxs) err := GaiaValidateGenesisState(genesisState) require.NotNil(t, err) // Test bonded + jailed validator fails - genesisState = makeGenesisState(genTxs[:1]) + genesisState = makeGenesisState(t, genTxs) val1 := stakeTypes.NewValidator(addr1, pk1, stakeTypes.Description{Moniker: "test #2"}) val1.Jailed = true val1.Status = sdk.Bonded @@ -94,7 +99,7 @@ func TestGaiaGenesisValidation(t *testing.T) { require.NotNil(t, err) // Test duplicate validator fails val1.Jailed = false - genesisState = makeGenesisState(genTxs[:1]) + genesisState = makeGenesisState(t, genTxs) val2 := stakeTypes.NewValidator(addr1, pk1, stakeTypes.Description{Moniker: "test #3"}) genesisState.StakeData.Validators = append(genesisState.StakeData.Validators, val1) genesisState.StakeData.Validators = append(genesisState.StakeData.Validators, val2) diff --git a/cmd/gaia/app/test_utils.go b/cmd/gaia/app/test_utils.go deleted file mode 100644 index 18946b397..000000000 --- a/cmd/gaia/app/test_utils.go +++ /dev/null @@ -1,80 +0,0 @@ -package app - -import ( - "encoding/json" - "errors" - - "github.com/cosmos/cosmos-sdk/codec" - sdk "github.com/cosmos/cosmos-sdk/types" - distr "github.com/cosmos/cosmos-sdk/x/distribution" - "github.com/cosmos/cosmos-sdk/x/gov" - "github.com/cosmos/cosmos-sdk/x/slashing" - "github.com/cosmos/cosmos-sdk/x/stake" - tmtypes "github.com/tendermint/tendermint/types" -) - -// NewTestGaiaAppGenState creates the core parameters for a test genesis -// initialization given a set of genesis txs, TM validators and their respective -// operating addresses. -func NewTestGaiaAppGenState( - cdc *codec.Codec, appGenTxs []json.RawMessage, tmVals []tmtypes.GenesisValidator, valOperAddrs []sdk.ValAddress, -) (GenesisState, error) { - - switch { - case len(appGenTxs) == 0: - return GenesisState{}, errors.New("must provide at least genesis transaction") - case len(tmVals) != len(valOperAddrs): - return GenesisState{}, errors.New("number of TM validators does not match number of operator addresses") - } - - // start with the default staking genesis state - stakeData := stake.DefaultGenesisState() - - // get genesis account information - genAccs := make([]GenesisAccount, len(appGenTxs)) - for i, appGenTx := range appGenTxs { - - var genTx GaiaGenTx - if err := cdc.UnmarshalJSON(appGenTx, &genTx); err != nil { - return GenesisState{}, err - } - - stakeData.Pool.LooseTokens = stakeData.Pool.LooseTokens.Add(sdk.NewDecFromInt(freeFermionsAcc)) - - // create the genesis account for the given genesis tx - genAccs[i] = genesisAccountFromGenTx(genTx) - } - - for i, tmVal := range tmVals { - var issuedDelShares sdk.Dec - - // increase total supply by validator's power - power := sdk.NewInt(tmVal.Power) - stakeData.Pool.LooseTokens = stakeData.Pool.LooseTokens.Add(sdk.NewDecFromInt(power)) - - // add the validator - desc := stake.NewDescription(tmVal.Name, "", "", "") - validator := stake.NewValidator(valOperAddrs[i], tmVal.PubKey, desc) - - validator, stakeData.Pool, issuedDelShares = validator.AddTokensFromDel(stakeData.Pool, power) - stakeData.Validators = append(stakeData.Validators, validator) - - // create the self-delegation from the issuedDelShares - selfDel := stake.Delegation{ - DelegatorAddr: sdk.AccAddress(validator.OperatorAddr), - ValidatorAddr: validator.OperatorAddr, - Shares: issuedDelShares, - Height: 0, - } - - stakeData.Bonds = append(stakeData.Bonds, selfDel) - } - - return GenesisState{ - Accounts: genAccs, - StakeData: stakeData, - DistrData: distr.DefaultGenesisState(), - SlashingData: slashing.DefaultGenesisState(), - GovData: gov.DefaultGenesisState(), - }, nil -} diff --git a/cmd/gaia/cli_test/cli_test.go b/cmd/gaia/cli_test/cli_test.go index 04bc4c84a..1639e143e 100644 --- a/cmd/gaia/cli_test/cli_test.go +++ b/cmd/gaia/cli_test/cli_test.go @@ -311,7 +311,7 @@ func TestGaiaCLISubmitProposal(t *testing.T) { fooAcc := executeGetAccount(t, fmt.Sprintf("gaiacli query account %s %v", fooAddr, flags)) require.Equal(t, int64(50), fooAcc.GetCoins().AmountOf("steak").Int64()) - proposalsQuery := tests.ExecuteT(t, fmt.Sprintf("gaiacli query proposals %v", flags), "") + proposalsQuery, _ := tests.ExecuteT(t, fmt.Sprintf("gaiacli query proposals %v", flags), "") require.Equal(t, "No matching proposals found", proposalsQuery) // submit a test proposal @@ -346,7 +346,7 @@ func TestGaiaCLISubmitProposal(t *testing.T) { require.Equal(t, int64(1), proposal1.GetProposalID()) require.Equal(t, gov.StatusDepositPeriod, proposal1.GetStatus()) - proposalsQuery = tests.ExecuteT(t, fmt.Sprintf("gaiacli query proposals %v", flags), "") + proposalsQuery, _ = tests.ExecuteT(t, fmt.Sprintf("gaiacli query proposals %v", flags), "") require.Equal(t, " 1 - Test", proposalsQuery) depositStr := fmt.Sprintf("gaiacli tx deposit %v", flags) @@ -400,10 +400,10 @@ func TestGaiaCLISubmitProposal(t *testing.T) { require.Equal(t, int64(1), votes[0].ProposalID) require.Equal(t, gov.OptionYes, votes[0].Option) - proposalsQuery = tests.ExecuteT(t, fmt.Sprintf("gaiacli query proposals --status=DepositPeriod %v", flags), "") + proposalsQuery, _ = tests.ExecuteT(t, fmt.Sprintf("gaiacli query proposals --status=DepositPeriod %v", flags), "") require.Equal(t, "No matching proposals found", proposalsQuery) - proposalsQuery = tests.ExecuteT(t, fmt.Sprintf("gaiacli query proposals --status=VotingPeriod %v", flags), "") + proposalsQuery, _ = tests.ExecuteT(t, fmt.Sprintf("gaiacli query proposals --status=VotingPeriod %v", flags), "") require.Equal(t, " 1 - Test", proposalsQuery) // submit a second test proposal @@ -417,7 +417,7 @@ func TestGaiaCLISubmitProposal(t *testing.T) { executeWrite(t, spStr, app.DefaultKeyPass) tests.WaitForNextNBlocksTM(2, port) - proposalsQuery = tests.ExecuteT(t, fmt.Sprintf("gaiacli query proposals --latest=1 %v", flags), "") + proposalsQuery, _ = tests.ExecuteT(t, fmt.Sprintf("gaiacli query proposals --latest=1 %v", flags), "") require.Equal(t, " 2 - Apples", proposalsQuery) } @@ -628,10 +628,10 @@ func executeWriteRetStdStreams(t *testing.T, cmdStr string, writes ...string) (b } func executeInit(t *testing.T, cmdStr string) (chainID string) { - out := tests.ExecuteT(t, cmdStr, app.DefaultKeyPass) + _, stderr := tests.ExecuteT(t, cmdStr, app.DefaultKeyPass) var initRes map[string]json.RawMessage - err := json.Unmarshal([]byte(out), &initRes) + err := json.Unmarshal([]byte(stderr), &initRes) require.NoError(t, err) err = json.Unmarshal(initRes["chain_id"], &chainID) @@ -641,7 +641,7 @@ func executeInit(t *testing.T, cmdStr string) (chainID string) { } func executeGetAddrPK(t *testing.T, cmdStr string) (sdk.AccAddress, crypto.PubKey) { - out := tests.ExecuteT(t, cmdStr, "") + out, _ := tests.ExecuteT(t, cmdStr, "") var ko keys.KeyOutput keys.UnmarshalJSON([]byte(out), &ko) @@ -655,7 +655,7 @@ func executeGetAddrPK(t *testing.T, cmdStr string) (sdk.AccAddress, crypto.PubKe } func executeGetAccount(t *testing.T, cmdStr string) auth.BaseAccount { - out := tests.ExecuteT(t, cmdStr, "") + out, _ := tests.ExecuteT(t, cmdStr, "") var initRes map[string]json.RawMessage err := json.Unmarshal([]byte(out), &initRes) require.NoError(t, err, "out %v, err %v", out, err) @@ -672,7 +672,7 @@ func executeGetAccount(t *testing.T, cmdStr string) auth.BaseAccount { // stake func executeGetValidator(t *testing.T, cmdStr string) stake.Validator { - out := tests.ExecuteT(t, cmdStr, "") + out, _ := tests.ExecuteT(t, cmdStr, "") var validator stake.Validator cdc := app.MakeCodec() err := cdc.UnmarshalJSON([]byte(out), &validator) @@ -681,7 +681,7 @@ func executeGetValidator(t *testing.T, cmdStr string) stake.Validator { } func executeGetPool(t *testing.T, cmdStr string) stake.Pool { - out := tests.ExecuteT(t, cmdStr, "") + out, _ := tests.ExecuteT(t, cmdStr, "") var pool stake.Pool cdc := app.MakeCodec() err := cdc.UnmarshalJSON([]byte(out), &pool) @@ -690,7 +690,7 @@ func executeGetPool(t *testing.T, cmdStr string) stake.Pool { } func executeGetParams(t *testing.T, cmdStr string) stake.Params { - out := tests.ExecuteT(t, cmdStr, "") + out, _ := tests.ExecuteT(t, cmdStr, "") var params stake.Params cdc := app.MakeCodec() err := cdc.UnmarshalJSON([]byte(out), ¶ms) @@ -702,7 +702,7 @@ func executeGetParams(t *testing.T, cmdStr string) stake.Params { // gov func executeGetProposal(t *testing.T, cmdStr string) gov.Proposal { - out := tests.ExecuteT(t, cmdStr, "") + out, _ := tests.ExecuteT(t, cmdStr, "") var proposal gov.Proposal cdc := app.MakeCodec() err := cdc.UnmarshalJSON([]byte(out), &proposal) @@ -711,7 +711,7 @@ func executeGetProposal(t *testing.T, cmdStr string) gov.Proposal { } func executeGetVote(t *testing.T, cmdStr string) gov.Vote { - out := tests.ExecuteT(t, cmdStr, "") + out, _ := tests.ExecuteT(t, cmdStr, "") var vote gov.Vote cdc := app.MakeCodec() err := cdc.UnmarshalJSON([]byte(out), &vote) @@ -720,7 +720,7 @@ func executeGetVote(t *testing.T, cmdStr string) gov.Vote { } func executeGetVotes(t *testing.T, cmdStr string) []gov.Vote { - out := tests.ExecuteT(t, cmdStr, "") + out, _ := tests.ExecuteT(t, cmdStr, "") var votes []gov.Vote cdc := app.MakeCodec() err := cdc.UnmarshalJSON([]byte(out), &votes) diff --git a/cmd/gaia/cmd/gaiad/main.go b/cmd/gaia/cmd/gaiad/main.go index adfb12d57..1304b32f3 100644 --- a/cmd/gaia/cmd/gaiad/main.go +++ b/cmd/gaia/cmd/gaiad/main.go @@ -32,6 +32,7 @@ func main() { appInit := app.GaiaAppInit() rootCmd.AddCommand(gaiaInit.InitCmd(ctx, cdc, appInit)) rootCmd.AddCommand(gaiaInit.TestnetFilesCmd(ctx, cdc, appInit)) + rootCmd.AddCommand(gaiaInit.GenTxCmd(ctx, cdc)) server.AddCommands(ctx, cdc, rootCmd, appInit, newApp, exportAppStateAndTMValidators) diff --git a/cmd/gaia/init/gentx.go b/cmd/gaia/init/gentx.go new file mode 100644 index 000000000..1b24a3576 --- /dev/null +++ b/cmd/gaia/init/gentx.go @@ -0,0 +1,120 @@ +package init + +import ( + "fmt" + "github.com/cosmos/cosmos-sdk/client" + "github.com/cosmos/cosmos-sdk/cmd/gaia/app" + "github.com/cosmos/cosmos-sdk/codec" + "github.com/cosmos/cosmos-sdk/server" + sdk "github.com/cosmos/cosmos-sdk/types" + authcmd "github.com/cosmos/cosmos-sdk/x/auth/client/cli" + "github.com/cosmos/cosmos-sdk/x/stake/client/cli" + "github.com/spf13/cobra" + "github.com/spf13/viper" + cfg "github.com/tendermint/tendermint/config" + "github.com/tendermint/tendermint/crypto" + tmcli "github.com/tendermint/tendermint/libs/cli" + "github.com/tendermint/tendermint/libs/common" + "io/ioutil" + "os" + "path/filepath" +) + +const ( + defaultAmount = "100steak" + defaultCommissionRate = "0.1" + defaultCommissionMaxRate = "0.2" + defaultCommissionMaxChangeRate = "0.01" +) + +// GenTxCmd builds the gaiad gentx command. +// nolint: errcheck +func GenTxCmd(ctx *server.Context, cdc *codec.Codec) *cobra.Command { + cmd := &cobra.Command{ + Use: "gentx", + Short: "Generate a genesis tx carrying a self delegation", + Long: fmt.Sprintf(`This command is an alias of the 'gaiad tx create-validator' command'. + +It creates a genesis piece carrying a self delegation with the +following delegation and commission default parameters: + + delegation amount: %s + commission rate: %s + commission max rate: %s + commission max change rate: %s +`, defaultAmount, defaultCommissionRate, defaultCommissionMaxRate, defaultCommissionMaxChangeRate), + RunE: func(cmd *cobra.Command, args []string) error { + + config := ctx.Config + config.SetRoot(viper.GetString(tmcli.HomeFlag)) + nodeID, valPubKey, err := InitializeNodeValidatorFiles(ctx.Config) + if err != nil { + return err + } + ip, err := server.ExternalIP() + if err != nil { + return err + } + + // Run gaiad tx create-validator + prepareFlagsForTxCreateValidator(config, nodeID, ip, valPubKey) + createValidatorCmd := cli.GetCmdCreateValidator(cdc) + + w, err := ioutil.TempFile("", "gentx") + if err != nil { + return err + } + unsignedGenTxFilename := w.Name() + defer os.Remove(unsignedGenTxFilename) + os.Stdout = w + if err = createValidatorCmd.RunE(nil, args); err != nil { + return err + } + w.Close() + + prepareFlagsForTxSign() + signCmd := authcmd.GetSignCommand(cdc, authcmd.GetAccountDecoder(cdc)) + if w, err = prepareOutputFile(config.RootDir, nodeID); err != nil { + return err + } + os.Stdout = w + return signCmd.RunE(nil, []string{unsignedGenTxFilename}) + }, + } + + cmd.Flags().String(flagClientHome, app.DefaultCLIHome, "client's home directory") + cmd.Flags().String(client.FlagChainID, "", "genesis file chain-id") + cmd.Flags().String(client.FlagName, "", "name of private key with which to sign the gentx") + cmd.MarkFlagRequired(client.FlagName) + return cmd +} + +func prepareFlagsForTxCreateValidator(config *cfg.Config, nodeID, ip string, valPubKey crypto.PubKey) { + viper.Set(tmcli.HomeFlag, viper.GetString(flagClientHome)) // --home + viper.Set(client.FlagFrom, viper.GetString(client.FlagName)) // --from + viper.Set(cli.FlagNodeID, nodeID) // --node-id + viper.Set(cli.FlagIP, ip) // --ip + viper.Set(cli.FlagPubKey, sdk.MustBech32ifyConsPub(valPubKey)) // --pubkey + viper.Set(cli.FlagAmount, defaultAmount) // --amount + viper.Set(cli.FlagCommissionRate, defaultCommissionRate) + viper.Set(cli.FlagCommissionMaxRate, defaultCommissionMaxRate) + viper.Set(cli.FlagCommissionMaxChangeRate, defaultCommissionMaxChangeRate) + viper.Set(cli.FlagGenesisFormat, true) // --genesis-format + viper.Set(cli.FlagMoniker, config.Moniker) // --moniker + if config.Moniker == "" { + viper.Set(cli.FlagMoniker, viper.GetString(client.FlagName)) + } +} + +func prepareFlagsForTxSign() { + viper.Set("offline", true) +} + +func prepareOutputFile(rootDir, nodeID string) (w *os.File, err error) { + writePath := filepath.Join(rootDir, "config", "gentx") + if err = common.EnsureDir(writePath, 0700); err != nil { + return + } + filename := filepath.Join(writePath, fmt.Sprintf("gentx-%v.json", nodeID)) + return os.Create(filename) +} diff --git a/cmd/gaia/init/init.go b/cmd/gaia/init/init.go index a04c1d2ae..467ea3fc2 100644 --- a/cmd/gaia/init/init.go +++ b/cmd/gaia/init/init.go @@ -2,311 +2,258 @@ package init import ( "encoding/json" + "errors" "fmt" - "io/ioutil" + "github.com/cosmos/cosmos-sdk/client" + "github.com/cosmos/cosmos-sdk/cmd/gaia/app" + "github.com/cosmos/cosmos-sdk/x/auth" + authtx "github.com/cosmos/cosmos-sdk/x/auth/client/txbuilder" + "github.com/cosmos/cosmos-sdk/x/stake" + "github.com/tendermint/tendermint/crypto" + "github.com/tendermint/tendermint/privval" "os" - "path" "path/filepath" - "sort" "github.com/cosmos/cosmos-sdk/codec" "github.com/cosmos/cosmos-sdk/server" - servercfg "github.com/cosmos/cosmos-sdk/server/config" + sdk "github.com/cosmos/cosmos-sdk/types" "github.com/spf13/cobra" "github.com/spf13/viper" cfg "github.com/tendermint/tendermint/config" - "github.com/tendermint/tendermint/crypto" "github.com/tendermint/tendermint/libs/cli" "github.com/tendermint/tendermint/libs/common" "github.com/tendermint/tendermint/p2p" - "github.com/tendermint/tendermint/privval" "github.com/tendermint/tendermint/types" ) -// get cmd to initialize all files for tendermint and application -func GenTxCmd(ctx *server.Context, cdc *codec.Codec, appInit server.AppInit) *cobra.Command { - cmd := &cobra.Command{ - Use: "gen-tx", - Short: "Create genesis transaction file (under [--home]/config/gentx/gentx-[nodeID].json)", - Args: cobra.NoArgs, - RunE: func(_ *cobra.Command, args []string) error { - config := ctx.Config - config.SetRoot(viper.GetString(cli.HomeFlag)) +const ( + flagWithTxs = "with-txs" + flagOverwrite = "overwrite" + flagClientHome = "home-client" + flagOverwriteKey = "overwrite-key" + flagSkipGenesis = "skip-genesis" + flagMoniker = "moniker" +) - ip := viper.GetString(server.FlagIP) - if len(ip) == 0 { - eip, err := server.ExternalIP() - if err != nil { - return err - } - ip = eip - } - - genTxConfig := servercfg.GenTx{ - viper.GetString(server.FlagName), - viper.GetString(server.FlagClientHome), - viper.GetBool(server.FlagOWK), - ip, - } - cliPrint, genTxFile, err := gentxWithConfig(cdc, appInit, config, genTxConfig) - if err != nil { - return err - } - toPrint := struct { - AppMessage json.RawMessage `json:"app_message"` - GenTxFile json.RawMessage `json:"gen_tx_file"` - }{ - cliPrint, - genTxFile, - } - out, err := codec.MarshalJSONIndent(cdc, toPrint) - if err != nil { - return err - } - fmt.Println(string(out)) - return nil - }, - } - cmd.Flags().String(server.FlagIP, "", "external facing IP to use if left blank IP will be retrieved from this machine") - cmd.Flags().AddFlagSet(appInit.FlagsAppGenTx) - return cmd +type initConfig struct { + ChainID string + GenTxsDir string + Name string + NodeID string + ClientHome string + WithTxs bool + Overwrite bool + OverwriteKey bool + ValPubKey crypto.PubKey } -// NOTE: This will update (write) the config file with -// updated name (moniker) for node. -func gentxWithConfig(cdc *codec.Codec, appInit server.AppInit, config *cfg.Config, genTxConfig servercfg.GenTx) ( - cliPrint json.RawMessage, genTxFile json.RawMessage, err error) { - nodeKey, err := p2p.LoadOrGenNodeKey(config.NodeKeyFile()) - if err != nil { - return - } - nodeID := string(nodeKey.ID()) - pubKey := readOrCreatePrivValidator(config) +type printInfo struct { + Moniker string `json:"moniker"` + ChainID string `json:"chain_id"` + NodeID string `json:"node_id"` + AppMessage json.RawMessage `json:"app_message"` +} - appGenTx, cliPrint, validator, err := appInit.AppGenTx(cdc, pubKey, genTxConfig) +// nolint: errcheck +func displayInfo(cdc *codec.Codec, info printInfo) error { + out, err := codec.MarshalJSONIndent(cdc, info) if err != nil { - return + return err } - - tx := server.GenesisTx{ - NodeID: nodeID, - IP: genTxConfig.IP, - Validator: validator, - AppGenTx: appGenTx, - } - bz, err := codec.MarshalJSONIndent(cdc, tx) - if err != nil { - return - } - genTxFile = json.RawMessage(bz) - name := fmt.Sprintf("gentx-%v.json", nodeID) - writePath := filepath.Join(config.RootDir, "config", "gentx") - file := filepath.Join(writePath, name) - err = common.EnsureDir(writePath, 0700) - if err != nil { - return - } - err = common.WriteFile(file, bz, 0644) - if err != nil { - return - } - - // Write updated config with moniker - config.Moniker = genTxConfig.Name - configFilePath := filepath.Join(config.RootDir, "config", "config.toml") - cfg.WriteConfigFile(configFilePath, config) - - return + fmt.Fprintf(os.Stderr, "%s\n", string(out)) + return nil } // get cmd to initialize all files for tendermint and application -// nolint: golint +// nolint func InitCmd(ctx *server.Context, cdc *codec.Codec, appInit server.AppInit) *cobra.Command { cmd := &cobra.Command{ Use: "init", - Short: "Initialize genesis config, priv-validator file, and p2p-node file", - Args: cobra.NoArgs, - RunE: func(_ *cobra.Command, _ []string) error { + Short: "Initialize private validator, p2p, genesis, and application configuration files", + Long: `Initialize validators's and node's configuration files. +Note that only node's configuration files will be written if the flag --skip-genesis is +enabled, and the genesis file will not be generated. +`, + Args: cobra.NoArgs, + RunE: func(_ *cobra.Command, _ []string) error { config := ctx.Config config.SetRoot(viper.GetString(cli.HomeFlag)) - initConfig := server.InitConfig{ - viper.GetString(server.FlagChainID), - viper.GetBool(server.FlagWithTxs), - filepath.Join(config.RootDir, "config", "gentx"), - viper.GetBool(server.FlagOverwrite), + + name := viper.GetString(client.FlagName) + chainID := viper.GetString(client.FlagChainID) + if chainID == "" { + chainID = fmt.Sprintf("test-chain-%v", common.RandStr(6)) + } + nodeID, valPubKey, err := InitializeNodeValidatorFiles(config) + if err != nil { + return err } - chainID, nodeID, appMessage, err := initWithConfig(cdc, appInit, config, initConfig) - if err != nil { - return err + if viper.GetString(flagMoniker) != "" { + config.Moniker = viper.GetString(flagMoniker) } + if config.Moniker == "" && name != "" { + config.Moniker = name + } + toPrint := printInfo{ + ChainID: chainID, + Moniker: config.Moniker, + NodeID: nodeID, + } + if viper.GetBool(flagSkipGenesis) { + cfg.WriteConfigFile(filepath.Join(config.RootDir, "config", "config.toml"), config) + return displayInfo(cdc, toPrint) + } + + initCfg := initConfig{ + ChainID: chainID, + GenTxsDir: filepath.Join(config.RootDir, "config", "gentx"), + Name: name, + NodeID: nodeID, + ClientHome: viper.GetString(flagClientHome), + WithTxs: viper.GetBool(flagWithTxs), + Overwrite: viper.GetBool(flagOverwrite), + OverwriteKey: viper.GetBool(flagOverwriteKey), + ValPubKey: valPubKey, + } + appMessage, err := initWithConfig(cdc, config, initCfg) // print out some key information - toPrint := struct { - ChainID string `json:"chain_id"` - NodeID string `json:"node_id"` - AppMessage json.RawMessage `json:"app_message"` - }{ - chainID, - nodeID, - appMessage, - } - out, err := codec.MarshalJSONIndent(cdc, toPrint) if err != nil { return err } - fmt.Println(string(out)) - return nil + + toPrint.AppMessage = appMessage + return displayInfo(cdc, toPrint) }, } - cmd.Flags().BoolP(server.FlagOverwrite, "o", false, "overwrite the genesis.json file") - cmd.Flags().String(server.FlagChainID, "", "genesis file chain-id, if left blank will be randomly created") - cmd.Flags().Bool(server.FlagWithTxs, false, "apply existing genesis transactions from [--home]/config/gentx/") - cmd.Flags().AddFlagSet(appInit.FlagsAppGenState) - cmd.Flags().AddFlagSet(appInit.FlagsAppGenTx) // need to add this flagset for when no GenTx's provided - cmd.AddCommand(GenTxCmd(ctx, cdc, appInit)) + + cmd.Flags().String(cli.HomeFlag, app.DefaultNodeHome, "node's home directory") + cmd.Flags().BoolP(flagOverwrite, "o", false, "overwrite the genesis.json file") + cmd.Flags().String(client.FlagChainID, "", "genesis file chain-id, if left blank will be randomly created") + cmd.Flags().Bool(flagWithTxs, false, "apply existing genesis transactions from [--home]/config/gentx/") + cmd.Flags().String(client.FlagName, "", "name of private key with which to sign the gentx") + cmd.Flags().String(flagMoniker, "", "overrides --name flag and set the validator's moniker to a different value; ignored if it runs without the --with-txs flag") + cmd.Flags().String(flagClientHome, app.DefaultCLIHome, "client's home directory") + cmd.Flags().Bool(flagOverwriteKey, false, "overwrite client's key") + cmd.Flags().Bool(flagSkipGenesis, false, "do not create genesis.json") return cmd } -func initWithConfig(cdc *codec.Codec, appInit server.AppInit, config *cfg.Config, initConfig server.InitConfig) ( - chainID string, nodeID string, appMessage json.RawMessage, err error) { +// InitializeNodeValidatorFiles creates private validator and p2p configuration files. +func InitializeNodeValidatorFiles(config *cfg.Config) (nodeID string, valPubKey crypto.PubKey, err error) { nodeKey, err := p2p.LoadOrGenNodeKey(config.NodeKeyFile()) if err != nil { return } nodeID = string(nodeKey.ID()) - pubKey := readOrCreatePrivValidator(config) - - if initConfig.ChainID == "" { - initConfig.ChainID = fmt.Sprintf("test-chain-%v", common.RandStr(6)) - } - chainID = initConfig.ChainID + valPubKey = ReadOrCreatePrivValidator(config.PrivValidatorFile()) + return +} +func initWithConfig(cdc *codec.Codec, config *cfg.Config, initCfg initConfig) ( + appMessage json.RawMessage, err error) { genFile := config.GenesisFile() - if !initConfig.Overwrite && common.FileExists(genFile) { + if !initCfg.Overwrite && common.FileExists(genFile) { err = fmt.Errorf("genesis.json file already exists: %v", genFile) return } - // process genesis transactions, or otherwise create one for defaults - var appGenTxs []json.RawMessage - var validators []types.GenesisValidator + // process genesis transactions, else create default genesis.json + var appGenTxs []auth.StdTx var persistentPeers string + var genTxs []json.RawMessage + var appState json.RawMessage + var jsonRawTx json.RawMessage + chainID := initCfg.ChainID - if initConfig.GenTxs { - validators, appGenTxs, persistentPeers, err = processGenTxs(initConfig.GenTxsDir, cdc) + if initCfg.WithTxs { + _, appGenTxs, persistentPeers, err = app.CollectStdTxs(config.Moniker, initCfg.GenTxsDir, cdc) if err != nil { return } + genTxs = make([]json.RawMessage, len(appGenTxs)) config.P2P.PersistentPeers = persistentPeers - configFilePath := filepath.Join(config.RootDir, "config", "config.toml") - cfg.WriteConfigFile(configFilePath, config) + for i, stdTx := range appGenTxs { + jsonRawTx, err = cdc.MarshalJSON(stdTx) + if err != nil { + return + } + genTxs[i] = jsonRawTx + } } else { - genTxConfig := servercfg.GenTx{ - viper.GetString(server.FlagName), - viper.GetString(server.FlagClientHome), - viper.GetBool(server.FlagOWK), - "127.0.0.1", + var ip, keyPass, secret string + var addr sdk.AccAddress + var signedTx auth.StdTx + + if initCfg.Name == "" { + err = errors.New("must specify validator's moniker (--name)") + return } - // Write updated config with moniker - config.Moniker = genTxConfig.Name - configFilePath := filepath.Join(config.RootDir, "config", "config.toml") - cfg.WriteConfigFile(configFilePath, config) - appGenTx, am, validator, err := appInit.AppGenTx(cdc, pubKey, genTxConfig) - appMessage = am - if err != nil { - return "", "", nil, err - } - validators = []types.GenesisValidator{validator} - appGenTxs = []json.RawMessage{appGenTx} - } - - appState, err := appInit.AppGenState(cdc, appGenTxs) - if err != nil { - return - } - - err = writeGenesisFile(cdc, genFile, initConfig.ChainID, validators, appState) - if err != nil { - return - } - - return -} - -// append a genesis-piece -func processGenTxs(genTxsDir string, cdc *codec.Codec) ( - validators []types.GenesisValidator, appGenTxs []json.RawMessage, persistentPeers string, err error) { - - var fos []os.FileInfo - fos, err = ioutil.ReadDir(genTxsDir) - if err != nil { - return - } - - genTxs := make(map[string]server.GenesisTx) - var nodeIDs []string - for _, fo := range fos { - filename := path.Join(genTxsDir, fo.Name()) - if !fo.IsDir() && (path.Ext(filename) != ".json") { - continue - } - - // get the genTx - var bz []byte - bz, err = ioutil.ReadFile(filename) + config.Moniker = initCfg.Name + ip, err = server.ExternalIP() if err != nil { return } - var genTx server.GenesisTx - err = cdc.UnmarshalJSON(bz, &genTx) + memo := fmt.Sprintf("%s@%s:26656", initCfg.NodeID, ip) + buf := client.BufferStdin() + prompt := fmt.Sprintf("Password for account %q (default: %q):", initCfg.Name, app.DefaultKeyPass) + keyPass, err = client.GetPassword(prompt, buf) + if err != nil && keyPass != "" { + // An error was returned that either failed to read the password from + // STDIN or the given password is not empty but failed to meet minimum + // length requirements. + return + } + if keyPass == "" { + keyPass = app.DefaultKeyPass + } + + addr, secret, err = server.GenerateSaveCoinKey(initCfg.ClientHome, initCfg.Name, keyPass, initCfg.OverwriteKey) + if err != nil { + return + } + appMessage, err = json.Marshal(map[string]string{"secret": secret}) if err != nil { return } - genTxs[genTx.NodeID] = genTx - nodeIDs = append(nodeIDs, genTx.NodeID) - } - - sort.Strings(nodeIDs) - - for _, nodeID := range nodeIDs { - genTx := genTxs[nodeID] - - // combine some stuff - validators = append(validators, genTx.Validator) - appGenTxs = append(appGenTxs, genTx.AppGenTx) - - // Add a persistent peer - comma := "," - if len(persistentPeers) == 0 { - comma = "" + msg := stake.NewMsgCreateValidator( + sdk.ValAddress(addr), + initCfg.ValPubKey, + sdk.NewInt64Coin("steak", 100), + stake.NewDescription(config.Moniker, "", "", ""), + stake.NewCommissionMsg(sdk.ZeroDec(), sdk.ZeroDec(), sdk.ZeroDec()), + ) + txBldr := authtx.NewTxBuilderFromCLI().WithCodec(cdc).WithMemo(memo).WithChainID(chainID) + signedTx, err = txBldr.SignStdTx( + initCfg.Name, keyPass, auth.NewStdTx([]sdk.Msg{msg}, auth.StdFee{}, []auth.StdSignature{}, memo), false, + ) + if err != nil { + return } - persistentPeers += fmt.Sprintf("%s%s@%s:26656", comma, genTx.NodeID, genTx.IP) + jsonRawTx, err = cdc.MarshalJSON(signedTx) + if err != nil { + return + } + genTxs = []json.RawMessage{jsonRawTx} } + cfg.WriteConfigFile(filepath.Join(config.RootDir, "config", "config.toml"), config) + appState, err = app.GaiaAppGenStateJSON(cdc, genTxs) + if err != nil { + return + } + err = WriteGenesisFile(genFile, chainID, nil, appState) + return } -// read of create the private key file for this config -func readOrCreatePrivValidator(tmConfig *cfg.Config) crypto.PubKey { - // private validator - privValFile := tmConfig.PrivValidatorFile() - var privValidator *privval.FilePV - if common.FileExists(privValFile) { - privValidator = privval.LoadFilePV(privValFile) - } else { - privValidator = privval.GenFilePV(privValFile) - privValidator.Save() - } - return privValidator.GetPubKey() -} - -// writeGenesisFile creates and writes the genesis configuration to disk. An +// WriteGenesisFile creates and writes the genesis configuration to disk. An // error is returned if building or writing the configuration to file fails. // nolint: unparam -func writeGenesisFile(cdc *codec.Codec, genesisFile, chainID string, validators []types.GenesisValidator, appState json.RawMessage) error { +func WriteGenesisFile(genesisFile, chainID string, validators []types.GenesisValidator, appState json.RawMessage) error { genDoc := types.GenesisDoc{ ChainID: chainID, Validators: validators, @@ -319,3 +266,16 @@ func writeGenesisFile(cdc *codec.Codec, genesisFile, chainID string, validators return genDoc.SaveAs(genesisFile) } + +// read of create the private key file for this config +func ReadOrCreatePrivValidator(privValFile string) crypto.PubKey { + // private validator + var privValidator *privval.FilePV + if common.FileExists(privValFile) { + privValidator = privval.LoadFilePV(privValFile) + } else { + privValidator = privval.GenFilePV(privValFile) + privValidator.Save() + } + return privValidator.GetPubKey() +} diff --git a/cmd/gaia/init/init_test.go b/cmd/gaia/init/init_test.go index 3a7f0a358..48a5d9247 100644 --- a/cmd/gaia/init/init_test.go +++ b/cmd/gaia/init/init_test.go @@ -2,47 +2,64 @@ package init import ( "bytes" + "github.com/cosmos/cosmos-sdk/client" + "github.com/cosmos/cosmos-sdk/cmd/gaia/app" + "github.com/tendermint/tendermint/libs/cli" "io" "io/ioutil" "os" "testing" "time" - "github.com/cosmos/cosmos-sdk/codec" "github.com/cosmos/cosmos-sdk/server" "github.com/cosmos/cosmos-sdk/server/mock" "github.com/stretchr/testify/require" abciServer "github.com/tendermint/tendermint/abci/server" tcmd "github.com/tendermint/tendermint/cmd/tendermint/commands" "github.com/tendermint/tendermint/libs/log" + + "github.com/spf13/viper" ) func TestInitCmd(t *testing.T) { defer server.SetupViper(t)() + defer setupClientHome(t)() logger := log.NewNopLogger() cfg, err := tcmd.ParseConfig() require.Nil(t, err) ctx := server.NewContext(cfg, logger) - cdc := codec.New() + cdc := app.MakeCodec() appInit := server.AppInit{ AppGenState: mock.AppGenState, - AppGenTx: mock.AppGenTx, } cmd := InitCmd(ctx, cdc, appInit) err = cmd.RunE(nil, nil) require.NoError(t, err) } +func setupClientHome(t *testing.T) func() { + clientDir, err := ioutil.TempDir("", "mock-sdk-cmd") + require.Nil(t, err) + viper.Set(flagClientHome, clientDir) + viper.Set(flagOverwriteKey, true) + return func() { + if err := os.RemoveAll(clientDir); err != nil { + // TODO: Handle with #870 + panic(err) + } + } +} + func TestEmptyState(t *testing.T) { defer server.SetupViper(t)() + defer setupClientHome(t)() logger := log.NewNopLogger() cfg, err := tcmd.ParseConfig() require.Nil(t, err) ctx := server.NewContext(cfg, logger) - cdc := codec.New() + cdc := app.MakeCodec() appInit := server.AppInit{ - AppGenTx: mock.AppGenTx, AppGenState: mock.AppGenStateEmpty, } cmd := InitCmd(ctx, cdc, appInit) @@ -80,15 +97,17 @@ func TestStartStandAlone(t *testing.T) { defer func() { os.RemoveAll(home) }() + viper.Set(cli.HomeFlag, home) + viper.Set(client.FlagName, "moniker") + defer setupClientHome(t)() logger := log.NewNopLogger() cfg, err := tcmd.ParseConfig() require.Nil(t, err) ctx := server.NewContext(cfg, logger) - cdc := codec.New() + cdc := app.MakeCodec() appInit := server.AppInit{ AppGenState: mock.AppGenState, - AppGenTx: mock.AppGenTx, } initCmd := InitCmd(ctx, cdc, appInit) err = initCmd.RunE(nil, nil) @@ -109,3 +128,19 @@ func TestStartStandAlone(t *testing.T) { svr.Stop() } } + +func TestInitNodeValidatorFiles(t *testing.T) { + home, err := ioutil.TempDir("", "mock-sdk-cmd") + require.Nil(t, err) + defer func() { + os.RemoveAll(home) + }() + viper.Set(cli.HomeFlag, home) + viper.Set(client.FlagName, "moniker") + cfg, err := tcmd.ParseConfig() + require.Nil(t, err) + nodeID, valPubKey, err := InitializeNodeValidatorFiles(cfg) + require.Nil(t, err) + require.NotEqual(t, "", nodeID) + require.NotEqual(t, 0, len(valPubKey.Bytes())) +} diff --git a/cmd/gaia/init/testnet.go b/cmd/gaia/init/testnet.go index 48b714f2d..3002b83a0 100644 --- a/cmd/gaia/init/testnet.go +++ b/cmd/gaia/init/testnet.go @@ -1,20 +1,24 @@ package init import ( + "encoding/json" "fmt" - "github.com/cosmos/cosmos-sdk/server" + "github.com/cosmos/cosmos-sdk/client" + "github.com/cosmos/cosmos-sdk/cmd/gaia/app" + "github.com/cosmos/cosmos-sdk/codec" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/auth" + authtx "github.com/cosmos/cosmos-sdk/x/auth/client/txbuilder" + "github.com/cosmos/cosmos-sdk/x/stake" "net" + "os" "path/filepath" + "github.com/cosmos/cosmos-sdk/server" "github.com/spf13/cobra" - - gc "github.com/cosmos/cosmos-sdk/server/config" - - "os" - - "github.com/cosmos/cosmos-sdk/codec" "github.com/spf13/viper" cfg "github.com/tendermint/tendermint/config" + "github.com/tendermint/tendermint/crypto" cmn "github.com/tendermint/tendermint/libs/common" ) @@ -46,8 +50,7 @@ Example: `, RunE: func(_ *cobra.Command, _ []string) error { config := ctx.Config - err := testnetWithConfig(config, cdc, appInit) - return err + return testnetWithConfig(config, cdc, appInit) }, } cmd.Flags().Int(nValidators, 4, @@ -70,6 +73,12 @@ func testnetWithConfig(config *cfg.Config, cdc *codec.Codec, appInit server.AppI outDir := viper.GetString(outputDir) numValidators := viper.GetInt(nValidators) + // Generate genesis.json and config.toml + chainID := "chain-" + cmn.RandStr(6) + monikers := make([]string, numValidators) + nodeIDs := make([]string, numValidators) + valPubKeys := make([]crypto.PubKey, numValidators) + // Generate private key, node ID, initial transaction for i := 0; i < numValidators; i++ { nodeDirName := fmt.Sprintf("%s%d", viper.GetString(nodeDirPrefix), i) @@ -92,60 +101,101 @@ func testnetWithConfig(config *cfg.Config, cdc *codec.Codec, appInit server.AppI return err } + monikers = append(monikers, nodeDirName) config.Moniker = nodeDirName ip, err := getIP(i) if err != nil { + _ = os.RemoveAll(outDir) return err } + nodeIDs[i], valPubKeys[i], err = InitializeNodeValidatorFiles(config) + if err != nil { + _ = os.RemoveAll(outDir) + return err + } + memo := fmt.Sprintf("%s@%s:26656", nodeIDs[i], ip) - genTxConfig := gc.GenTx{ - nodeDirName, - clientDir, - true, - ip, + buf := client.BufferStdin() + prompt := fmt.Sprintf("Password for account '%s' (default %s):", nodeDirName, app.DefaultKeyPass) + keyPass, err := client.GetPassword(prompt, buf) + if err != nil && keyPass != "" { + // An error was returned that either failed to read the password from + // STDIN or the given password is not empty but failed to meet minimum + // length requirements. + return err + } + if keyPass == "" { + keyPass = app.DefaultKeyPass } - // Run `init gen-tx` and generate initial transactions - cliPrint, genTxFile, err := gentxWithConfig(cdc, appInit, config, genTxConfig) + addr, secret, err := server.GenerateSaveCoinKey(clientDir, nodeDirName, keyPass, true) + if err != nil { + _ = os.RemoveAll(outDir) + return err + } + info := map[string]string{"secret": secret} + cliPrint, err := json.Marshal(info) if err != nil { return err } - // Save private key seed words - name := fmt.Sprintf("%v.json", "key_seed") - err = writeFile(name, clientDir, cliPrint) + err = writeFile(fmt.Sprintf("%v.json", "key_seed"), clientDir, cliPrint) if err != nil { return err } + msg := stake.NewMsgCreateValidator( + sdk.ValAddress(addr), + valPubKeys[i], + sdk.NewInt64Coin("steak", 100), + stake.NewDescription(nodeDirName, "", "", ""), + stake.NewCommissionMsg(sdk.ZeroDec(), sdk.ZeroDec(), sdk.ZeroDec()), + ) + tx := auth.NewStdTx([]sdk.Msg{msg}, auth.StdFee{}, []auth.StdSignature{}, memo) + txBldr := authtx.NewTxBuilderFromCLI().WithChainID(chainID).WithMemo(memo) + signedTx, err := txBldr.SignStdTx(nodeDirName, app.DefaultKeyPass, tx, false) + if err != nil { + _ = os.RemoveAll(outDir) + return err + } + + txBytes, err := cdc.MarshalJSON(signedTx) + if err != nil { + _ = os.RemoveAll(outDir) + return err + } + // Gather gentxs folder - name = fmt.Sprintf("%v.json", nodeDirName) - err = writeFile(name, gentxsDir, genTxFile) + err = writeFile(fmt.Sprintf("%v.json", nodeDirName), gentxsDir, txBytes) if err != nil { + _ = os.RemoveAll(outDir) return err } } - // Generate genesis.json and config.toml - chainID := "chain-" + cmn.RandStr(6) for i := 0; i < numValidators; i++ { nodeDirName := fmt.Sprintf("%s%d", viper.GetString(nodeDirPrefix), i) nodeDaemonHomeName := viper.GetString(nodeDaemonHome) nodeDir := filepath.Join(outDir, nodeDirName, nodeDaemonHomeName) gentxsDir := filepath.Join(outDir, "gentxs") - initConfig := server.InitConfig{ - chainID, - true, - gentxsDir, - true, - } + moniker := monikers[i] config.Moniker = nodeDirName config.SetRoot(nodeDir) + nodeID, valPubKey := nodeIDs[i], valPubKeys[i] // Run `init` and generate genesis.json and config.toml - _, _, _, err := initWithConfig(cdc, appInit, config, initConfig) - if err != nil { + initCfg := initConfig{ + ChainID: chainID, + GenTxsDir: gentxsDir, + Name: moniker, + WithTxs: true, + Overwrite: true, + OverwriteKey: false, + NodeID: nodeID, + ValPubKey: valPubKey, + } + if _, err := initWithConfig(cdc, config, initCfg); err != nil { return err } } diff --git a/docs/getting-started/join-testnet.md b/docs/getting-started/join-testnet.md index 66ec97cad..ce070c4fe 100644 --- a/docs/getting-started/join-testnet.md +++ b/docs/getting-started/join-testnet.md @@ -15,7 +15,7 @@ These instructions are for setting up a brand new full node from scratch. First, initialize the node and create the necessary config files: ```bash -gaiad init --name +gaiad init --skip-genesis --name ``` ::: warning Note diff --git a/examples/basecoin/app/app.go b/examples/basecoin/app/app.go index 657cbcf0f..3d9ac8173 100644 --- a/examples/basecoin/app/app.go +++ b/examples/basecoin/app/app.go @@ -2,6 +2,7 @@ package app import ( "encoding/json" + "os" bam "github.com/cosmos/cosmos-sdk/baseapp" "github.com/cosmos/cosmos-sdk/codec" @@ -21,6 +22,12 @@ const ( appName = "BasecoinApp" ) +// default home directories for expected binaries +var ( + DefaultCLIHome = os.ExpandEnv("$HOME/.basecli") + DefaultNodeHome = os.ExpandEnv("$HOME/.basecoind") +) + // BasecoinApp implements an extended ABCI application. It contains a BaseApp, // a codec for serialization, KVStore keys for multistore state management, and // various mappers and keepers to manage getting, setting, and serializing the diff --git a/examples/basecoin/cli_test/cli_test.go b/examples/basecoin/cli_test/cli_test.go index 3a33135e3..635b54c3c 100644 --- a/examples/basecoin/cli_test/cli_test.go +++ b/examples/basecoin/cli_test/cli_test.go @@ -6,6 +6,7 @@ import ( "os" "testing" + "github.com/cosmos/cosmos-sdk/cmd/gaia/app" "github.com/cosmos/cosmos-sdk/server" "github.com/cosmos/cosmos-sdk/tests" "github.com/stretchr/testify/require" @@ -13,10 +14,11 @@ import ( var ( basecoindHome = "" + basecliHome = "" ) func init() { - basecoindHome = getTestingHomeDir() + basecoindHome, basecliHome = getTestingHomeDirs() } func TestInitStartSequence(t *testing.T) { @@ -32,8 +34,8 @@ func executeInit(t *testing.T) { chainID string initRes map[string]json.RawMessage ) - out := tests.ExecuteT(t, fmt.Sprintf("basecoind --home=%s init", basecoindHome), "") - err := json.Unmarshal([]byte(out), &initRes) + _, stderr := tests.ExecuteT(t, fmt.Sprintf("basecoind --home=%s --home-client=%s init --name=test", basecoindHome, basecliHome), app.DefaultKeyPass) + err := json.Unmarshal([]byte(stderr), &initRes) require.NoError(t, err) err = json.Unmarshal(initRes["chain_id"], &chainID) require.NoError(t, err) @@ -45,8 +47,9 @@ func executeStart(t *testing.T, servAddr, port string) { tests.WaitForTMStart(port) } -func getTestingHomeDir() string { +func getTestingHomeDirs() (string, string) { tmpDir := os.TempDir() basecoindHome := fmt.Sprintf("%s%s.test_basecoind", tmpDir, string(os.PathSeparator)) - return basecoindHome + basecliHome := fmt.Sprintf("%s%s.test_basecli", tmpDir, string(os.PathSeparator)) + return basecoindHome, basecliHome } diff --git a/examples/basecoin/cmd/basecli/main.go b/examples/basecoin/cmd/basecli/main.go index cbfae5fe0..94c5c6e01 100644 --- a/examples/basecoin/cmd/basecli/main.go +++ b/examples/basecoin/cmd/basecli/main.go @@ -1,8 +1,6 @@ package main import ( - "os" - "github.com/cosmos/cosmos-sdk/client" "github.com/cosmos/cosmos-sdk/client/keys" "github.com/cosmos/cosmos-sdk/client/lcd" @@ -86,7 +84,7 @@ func main() { ) // prepare and add flags - executor := cli.PrepareMainCmd(rootCmd, "BC", os.ExpandEnv("$HOME/.basecli")) + executor := cli.PrepareMainCmd(rootCmd, "BC", app.DefaultCLIHome) err := executor.Execute() if err != nil { // Note: Handle with #870 diff --git a/examples/basecoin/cmd/basecoind/main.go b/examples/basecoin/cmd/basecoind/main.go index 362193544..ac79eaad0 100644 --- a/examples/basecoin/cmd/basecoind/main.go +++ b/examples/basecoin/cmd/basecoind/main.go @@ -2,23 +2,32 @@ package main import ( "encoding/json" + "fmt" + "github.com/tendermint/tendermint/p2p" "io" "os" "github.com/cosmos/cosmos-sdk/baseapp" gaiaInit "github.com/cosmos/cosmos-sdk/cmd/gaia/init" + "github.com/cosmos/cosmos-sdk/client" + "github.com/cosmos/cosmos-sdk/codec" "github.com/cosmos/cosmos-sdk/examples/basecoin/app" "github.com/cosmos/cosmos-sdk/server" "github.com/spf13/cobra" "github.com/spf13/viper" abci "github.com/tendermint/tendermint/abci/types" "github.com/tendermint/tendermint/libs/cli" + "github.com/tendermint/tendermint/libs/common" dbm "github.com/tendermint/tendermint/libs/db" "github.com/tendermint/tendermint/libs/log" tmtypes "github.com/tendermint/tendermint/types" ) +const ( + flagClientHome = "home-client" +) + func main() { cdc := app.MakeCodec() ctx := server.NewDefaultContext() @@ -30,7 +39,7 @@ func main() { } appInit := server.DefaultAppInit - rootCmd.AddCommand(gaiaInit.InitCmd(ctx, cdc, appInit)) + rootCmd.AddCommand(InitCmd(ctx, cdc, appInit)) rootCmd.AddCommand(gaiaInit.TestnetFilesCmd(ctx, cdc, appInit)) server.AddCommands(ctx, cdc, rootCmd, appInit, @@ -47,6 +56,69 @@ func main() { } } +// get cmd to initialize all files for tendermint and application +// nolint: errcheck +func InitCmd(ctx *server.Context, cdc *codec.Codec, appInit server.AppInit) *cobra.Command { + cmd := &cobra.Command{ + Use: "init", + Short: "Initialize genesis config, priv-validator file, and p2p-node file", + Args: cobra.NoArgs, + RunE: func(_ *cobra.Command, _ []string) error { + + config := ctx.Config + config.SetRoot(viper.GetString(cli.HomeFlag)) + chainID := viper.GetString(client.FlagChainID) + if chainID == "" { + chainID = fmt.Sprintf("test-chain-%v", common.RandStr(6)) + } + + nodeKey, err := p2p.LoadOrGenNodeKey(config.NodeKeyFile()) + if err != nil { + return err + } + nodeID := string(nodeKey.ID()) + + pk := gaiaInit.ReadOrCreatePrivValidator(config.PrivValidatorFile()) + genTx, appMessage, validator, err := server.SimpleAppGenTx(cdc, pk) + if err != nil { + return err + } + + appState, err := appInit.AppGenState(cdc, []json.RawMessage{genTx}) + if err != nil { + return err + } + appStateJSON, err := cdc.MarshalJSON(appState) + if err != nil { + return err + } + + toPrint := struct { + ChainID string `json:"chain_id"` + NodeID string `json:"noide_id"` + AppMessage json.RawMessage `json:"app_message"` + }{ + chainID, + nodeID, + appMessage, + } + out, err := codec.MarshalJSONIndent(cdc, toPrint) + if err != nil { + return err + } + fmt.Fprintf(os.Stderr, "%s\n", string(out)) + return gaiaInit.WriteGenesisFile(config.GenesisFile(), chainID, []tmtypes.GenesisValidator{validator}, appStateJSON) + }, + } + + cmd.Flags().String(cli.HomeFlag, app.DefaultNodeHome, "node's home directory") + cmd.Flags().String(flagClientHome, app.DefaultCLIHome, "client's home directory") + cmd.Flags().String(client.FlagChainID, "", "genesis file chain-id, if left blank will be randomly created") + cmd.Flags().String(client.FlagName, "", "validator's moniker") + cmd.MarkFlagRequired(client.FlagName) + return cmd +} + func newApp(logger log.Logger, db dbm.DB, storeTracer io.Writer) abci.Application { return app.NewBasecoinApp(logger, db, baseapp.SetPruning(viper.GetString("pruning"))) } diff --git a/examples/democoin/app/app.go b/examples/democoin/app/app.go index 0449f6251..97dcf4e35 100644 --- a/examples/democoin/app/app.go +++ b/examples/democoin/app/app.go @@ -2,6 +2,7 @@ package app import ( "encoding/json" + "os" abci "github.com/tendermint/tendermint/abci/types" cmn "github.com/tendermint/tendermint/libs/common" @@ -27,6 +28,12 @@ const ( appName = "DemocoinApp" ) +// default home directories for expected binaries +var ( + DefaultCLIHome = os.ExpandEnv("$HOME/.democli") + DefaultNodeHome = os.ExpandEnv("$HOME/.democoind") +) + // Extended ABCI application type DemocoinApp struct { *bam.BaseApp diff --git a/examples/democoin/cli_test/cli_test.go b/examples/democoin/cli_test/cli_test.go index 2db2fff09..8df97c987 100644 --- a/examples/democoin/cli_test/cli_test.go +++ b/examples/democoin/cli_test/cli_test.go @@ -3,6 +3,7 @@ package clitest import ( "encoding/json" "fmt" + "github.com/cosmos/cosmos-sdk/cmd/gaia/app" "os" "testing" @@ -13,10 +14,11 @@ import ( var ( democoindHome = "" + democliHome = "" ) func init() { - democoindHome = getTestingHomeDir() + democoindHome, democliHome = getTestingHomeDirs() } func TestInitStartSequence(t *testing.T) { @@ -32,8 +34,8 @@ func executeInit(t *testing.T) { chainID string initRes map[string]json.RawMessage ) - out := tests.ExecuteT(t, fmt.Sprintf("democoind --home=%s init", democoindHome), "") - err := json.Unmarshal([]byte(out), &initRes) + _, stderr := tests.ExecuteT(t, fmt.Sprintf("democoind --home=%s --home-client=%s init --name=test", democoindHome, democliHome), app.DefaultKeyPass) + err := json.Unmarshal([]byte(stderr), &initRes) require.NoError(t, err) err = json.Unmarshal(initRes["chain_id"], &chainID) require.NoError(t, err) @@ -45,8 +47,9 @@ func executeStart(t *testing.T, servAddr, port string) { tests.WaitForTMStart(port) } -func getTestingHomeDir() string { +func getTestingHomeDirs() (string, string) { tmpDir := os.TempDir() democoindHome := fmt.Sprintf("%s%s.test_democoind", tmpDir, string(os.PathSeparator)) - return democoindHome + democliHome := fmt.Sprintf("%s%s.test_democli", tmpDir, string(os.PathSeparator)) + return democoindHome, democliHome } diff --git a/examples/democoin/cmd/democli/main.go b/examples/democoin/cmd/democli/main.go index 43f86504e..08f131168 100644 --- a/examples/democoin/cmd/democli/main.go +++ b/examples/democoin/cmd/democli/main.go @@ -1,8 +1,6 @@ package main import ( - "os" - "github.com/spf13/cobra" "github.com/tendermint/tendermint/libs/cli" @@ -91,7 +89,7 @@ func main() { ) // prepare and add flags - executor := cli.PrepareMainCmd(rootCmd, "BC", os.ExpandEnv("$HOME/.democli")) + executor := cli.PrepareMainCmd(rootCmd, "BC", app.DefaultCLIHome) err := executor.Execute() if err != nil { // handle with #870 diff --git a/examples/democoin/cmd/democoind/main.go b/examples/democoin/cmd/democoind/main.go index 7f8c7c54d..8dfa2f95f 100644 --- a/examples/democoin/cmd/democoind/main.go +++ b/examples/democoin/cmd/democoind/main.go @@ -2,6 +2,11 @@ package main import ( "encoding/json" + "fmt" + "github.com/cosmos/cosmos-sdk/client" + "github.com/spf13/viper" + "github.com/tendermint/tendermint/libs/common" + "github.com/tendermint/tendermint/p2p" "io" "os" @@ -19,10 +24,13 @@ import ( "github.com/cosmos/cosmos-sdk/server" ) +const ( + flagClientHome = "home-client" +) + // init parameters var CoolAppInit = server.AppInit{ AppGenState: CoolAppGenState, - AppGenTx: server.SimpleAppGenTx, } // coolGenAppParams sets up the app_state and appends the cool app state @@ -52,6 +60,69 @@ func CoolAppGenState(cdc *codec.Codec, appGenTxs []json.RawMessage) (appState js return } +// get cmd to initialize all files for tendermint and application +// nolint: errcheck +func InitCmd(ctx *server.Context, cdc *codec.Codec, appInit server.AppInit) *cobra.Command { + cmd := &cobra.Command{ + Use: "init", + Short: "Initialize genesis config, priv-validator file, and p2p-node file", + Args: cobra.NoArgs, + RunE: func(_ *cobra.Command, _ []string) error { + + config := ctx.Config + config.SetRoot(viper.GetString(cli.HomeFlag)) + chainID := viper.GetString(client.FlagChainID) + if chainID == "" { + chainID = fmt.Sprintf("test-chain-%v", common.RandStr(6)) + } + + nodeKey, err := p2p.LoadOrGenNodeKey(config.NodeKeyFile()) + if err != nil { + return err + } + nodeID := string(nodeKey.ID()) + + pk := gaiaInit.ReadOrCreatePrivValidator(config.PrivValidatorFile()) + genTx, appMessage, validator, err := server.SimpleAppGenTx(cdc, pk) + if err != nil { + return err + } + + appState, err := appInit.AppGenState(cdc, []json.RawMessage{genTx}) + if err != nil { + return err + } + appStateJSON, err := cdc.MarshalJSON(appState) + if err != nil { + return err + } + + toPrint := struct { + ChainID string `json:"chain_id"` + NodeID string `json:"noide_id"` + AppMessage json.RawMessage `json:"app_message"` + }{ + chainID, + nodeID, + appMessage, + } + out, err := codec.MarshalJSONIndent(cdc, toPrint) + if err != nil { + return err + } + fmt.Fprintf(os.Stderr, "%s\n", string(out)) + return gaiaInit.WriteGenesisFile(config.GenesisFile(), chainID, []tmtypes.GenesisValidator{validator}, appStateJSON) + }, + } + + cmd.Flags().String(cli.HomeFlag, app.DefaultNodeHome, "node's home directory") + cmd.Flags().String(flagClientHome, app.DefaultCLIHome, "client's home directory") + cmd.Flags().String(client.FlagChainID, "", "genesis file chain-id, if left blank will be randomly created") + cmd.Flags().String(client.FlagName, "", "validator's moniker") + cmd.MarkFlagRequired(client.FlagName) + return cmd +} + func newApp(logger log.Logger, db dbm.DB, _ io.Writer) abci.Application { return app.NewDemocoinApp(logger, db) } @@ -71,7 +142,7 @@ func main() { PersistentPreRunE: server.PersistentPreRunEFn(ctx), } - rootCmd.AddCommand(gaiaInit.InitCmd(ctx, cdc, CoolAppInit)) + rootCmd.AddCommand(InitCmd(ctx, cdc, CoolAppInit)) rootCmd.AddCommand(gaiaInit.TestnetFilesCmd(ctx, cdc, CoolAppInit)) server.AddCommands(ctx, cdc, rootCmd, CoolAppInit, diff --git a/server/config/config.go b/server/config/config.go index bd0d966e3..239097b13 100644 --- a/server/config/config.go +++ b/server/config/config.go @@ -35,16 +35,3 @@ func (c *Config) MinimumFees() sdk.Coins { // DefaultConfig returns server's default configuration. func DefaultConfig() *Config { return &Config{BaseConfig{MinFees: defaultMinimumFees}} } - -//_____________________________________________________________________ - -// Configuration structure for command functions that share configuration. -// For example: init, init gen-tx and testnet commands need similar input and run the same code - -// Storage for init gen-tx command input parameters -type GenTx struct { - Name string - CliRoot string - Overwrite bool - IP string -} diff --git a/server/init.go b/server/init.go index 091ffa948..65a896e2a 100644 --- a/server/init.go +++ b/server/init.go @@ -2,111 +2,59 @@ package server import ( "encoding/json" + "errors" "fmt" "github.com/cosmos/cosmos-sdk/crypto/keys" - "github.com/pkg/errors" - "github.com/spf13/pflag" "github.com/tendermint/tendermint/crypto" - dbm "github.com/tendermint/tendermint/libs/db" - tmtypes "github.com/tendermint/tendermint/types" + "github.com/tendermint/tendermint/types" clkeys "github.com/cosmos/cosmos-sdk/client/keys" "github.com/cosmos/cosmos-sdk/codec" - serverconfig "github.com/cosmos/cosmos-sdk/server/config" sdk "github.com/cosmos/cosmos-sdk/types" + tmtypes "github.com/tendermint/tendermint/types" ) -//Parameter names, for init gen-tx command -var ( - FlagName = "name" - FlagClientHome = "home-client" - FlagOWK = "owk" -) - -//parameter names, init command -var ( - FlagOverwrite = "overwrite" - FlagWithTxs = "with-txs" - FlagIP = "ip" - FlagChainID = "chain-id" -) - -// genesis piece structure for creating combined genesis -type GenesisTx struct { - NodeID string `json:"node_id"` - IP string `json:"ip"` - Validator tmtypes.GenesisValidator `json:"validator"` - AppGenTx json.RawMessage `json:"app_gen_tx"` -} - -// Storage for init command input parameters -type InitConfig struct { - ChainID string - GenTxs bool - GenTxsDir string - Overwrite bool -} - -//________________________________________________________________________________________ - -//_____________________________________________________________________ - // Core functionality passed from the application to the server init command type AppInit struct { - - // flags required for application init functions - FlagsAppGenState *pflag.FlagSet - FlagsAppGenTx *pflag.FlagSet - - // create the application genesis tx - AppGenTx func(cdc *codec.Codec, pk crypto.PubKey, genTxConfig serverconfig.GenTx) ( - appGenTx, cliPrint json.RawMessage, validator tmtypes.GenesisValidator, err error) - // AppGenState creates the core parameters initialization. It takes in a // pubkey meant to represent the pubkey of the validator of this machine. - AppGenState func(cdc *codec.Codec, appGenTxs []json.RawMessage) (appState json.RawMessage, err error) + AppGenState func(cdc *codec.Codec, appGenTx []json.RawMessage) (appState json.RawMessage, err error) +} + +// SimpleGenTx is a simple genesis tx +type SimpleGenTx struct { + Addr sdk.AccAddress `json:"addr"` } //_____________________________________________________________________ // simple default application init var DefaultAppInit = AppInit{ - AppGenTx: SimpleAppGenTx, AppGenState: SimpleAppGenState, } -// simple genesis tx -type SimpleGenTx struct { - Addr sdk.AccAddress `json:"addr"` -} - // Generate a genesis transaction -func SimpleAppGenTx(cdc *codec.Codec, pk crypto.PubKey, genTxConfig serverconfig.GenTx) ( - appGenTx, cliPrint json.RawMessage, validator tmtypes.GenesisValidator, err error) { - +func SimpleAppGenTx(cdc *codec.Codec, pk crypto.PubKey) (appGenTx, cliPrint json.RawMessage, validator types.GenesisValidator, err error) { var addr sdk.AccAddress var secret string addr, secret, err = GenerateCoinKey() if err != nil { return } - var bz []byte - simpleGenTx := SimpleGenTx{addr} + simpleGenTx := SimpleGenTx{Addr: addr} bz, err = cdc.MarshalJSON(simpleGenTx) if err != nil { return } appGenTx = json.RawMessage(bz) - mm := map[string]string{"secret": secret} bz, err = cdc.MarshalJSON(mm) if err != nil { return } cliPrint = json.RawMessage(bz) - validator = tmtypes.GenesisValidator{ PubKey: pk, Power: 10, @@ -122,8 +70,8 @@ func SimpleAppGenState(cdc *codec.Codec, appGenTxs []json.RawMessage) (appState return } - var genTx SimpleGenTx - err = cdc.UnmarshalJSON(appGenTxs[0], &genTx) + var tx SimpleGenTx + err = cdc.UnmarshalJSON(appGenTxs[0], &tx) if err != nil { return } @@ -138,7 +86,7 @@ func SimpleAppGenState(cdc *codec.Codec, appGenTxs []json.RawMessage) (appState } ] }] -}`, genTx.Addr)) +}`, tx.Addr)) return } @@ -176,7 +124,8 @@ func GenerateSaveCoinKey(clientRoot, keyName, keyPass string, overwrite bool) (s if !overwrite { _, err := keybase.Get(keyName) if err == nil { - return sdk.AccAddress([]byte{}), "", errors.New("key already exists, overwrite is disabled") + return sdk.AccAddress([]byte{}), "", fmt.Errorf( + "key already exists, overwrite is disabled (clientRoot: %s)", clientRoot) } } diff --git a/server/mock/app.go b/server/mock/app.go index abdec6be5..18afa164e 100644 --- a/server/mock/app.go +++ b/server/mock/app.go @@ -6,14 +6,11 @@ import ( "path/filepath" abci "github.com/tendermint/tendermint/abci/types" - "github.com/tendermint/tendermint/crypto" dbm "github.com/tendermint/tendermint/libs/db" "github.com/tendermint/tendermint/libs/log" - tmtypes "github.com/tendermint/tendermint/types" bam "github.com/cosmos/cosmos-sdk/baseapp" "github.com/cosmos/cosmos-sdk/codec" - gc "github.com/cosmos/cosmos-sdk/server/config" sdk "github.com/cosmos/cosmos-sdk/types" ) @@ -126,14 +123,3 @@ func AppGenStateEmpty(_ *codec.Codec, _ []json.RawMessage) (appState json.RawMes appState = json.RawMessage(``) return } - -// Return a validator, not much else -func AppGenTx(_ *codec.Codec, pk crypto.PubKey, genTxConfig gc.GenTx) ( - appGenTx, cliPrint json.RawMessage, validator tmtypes.GenesisValidator, err error) { - - validator = tmtypes.GenesisValidator{ - PubKey: pk, - Power: 10, - } - return -} diff --git a/server/test_helpers.go b/server/test_helpers.go index 8de164e08..4347bad6c 100644 --- a/server/test_helpers.go +++ b/server/test_helpers.go @@ -2,6 +2,7 @@ package server import ( "fmt" + "github.com/cosmos/cosmos-sdk/client" "io/ioutil" "net" "os" @@ -42,6 +43,7 @@ func SetupViper(t *testing.T) func() { rootDir, err := ioutil.TempDir("", "mock-sdk-cmd") require.Nil(t, err) viper.Set(cli.HomeFlag, rootDir) + viper.Set(client.FlagName, "moniker") return func() { err := os.RemoveAll(rootDir) if err != nil { diff --git a/tests/gobash.go b/tests/gobash.go index 6282f2fda..87d56a297 100644 --- a/tests/gobash.go +++ b/tests/gobash.go @@ -14,7 +14,7 @@ import ( // ExecuteT executes the command, pipes any input to STDIN and return STDOUT, // logging STDOUT/STDERR to t. // nolint: errcheck -func ExecuteT(t *testing.T, cmd, input string) (out string) { +func ExecuteT(t *testing.T, cmd, input string) (stdout, stderr string) { t.Log("Running", cmn.Cyan(cmd)) // split cmd to name and args @@ -50,8 +50,10 @@ func ExecuteT(t *testing.T, cmd, input string) (out string) { t.Log("Stderr:", cmn.Red(string(errbz))) } - out = strings.Trim(string(outbz), "\n") - return out + stdout = strings.Trim(string(outbz), "\n") + stderr = strings.Trim(string(errbz), "\n") + + return } // Execute the command, launch goroutines to log stdout/err to t. diff --git a/x/auth/ante.go b/x/auth/ante.go index b6f880254..8a10a0239 100644 --- a/x/auth/ante.go +++ b/x/auth/ante.go @@ -4,7 +4,6 @@ import ( "bytes" "encoding/hex" "fmt" - sdk "github.com/cosmos/cosmos-sdk/types" "github.com/tendermint/tendermint/crypto" "github.com/tendermint/tendermint/crypto/ed25519" @@ -81,7 +80,7 @@ func NewAnteHandler(am AccountMapper, fck FeeCollectionKeeper) sdk.AnteHandler { if !res.IsOK() { return newCtx, res, true } - res = validateAccNumAndSequence(signerAccs, stdSigs) + res = validateAccNumAndSequence(ctx, signerAccs, stdSigs) if !res.IsOK() { return newCtx, res, true } @@ -149,17 +148,23 @@ func getSignerAccs(ctx sdk.Context, am AccountMapper, addrs []sdk.AccAddress) (a return } -func validateAccNumAndSequence(accs []Account, sigs []StdSignature) sdk.Result { +func validateAccNumAndSequence(ctx sdk.Context, accs []Account, sigs []StdSignature) sdk.Result { for i := 0; i < len(accs); i++ { - accnum := accs[i].GetAccountNumber() - seq := accs[i].GetSequence() + // On InitChain, make sure account number == 0 + if ctx.BlockHeight() == 0 && sigs[i].AccountNumber != 0 { + return sdk.ErrInvalidSequence( + fmt.Sprintf("Invalid account number for BlockHeight == 0. Got %d, expected 0", sigs[i].AccountNumber)).Result() + } + // Check account number. - if accnum != sigs[i].AccountNumber { + accnum := accs[i].GetAccountNumber() + if ctx.BlockHeight() != 0 && accnum != sigs[i].AccountNumber { return sdk.ErrInvalidSequence( fmt.Sprintf("Invalid account number. Got %d, expected %d", sigs[i].AccountNumber, accnum)).Result() } // Check sequence number. + seq := accs[i].GetSequence() if seq != sigs[i].Sequence { return sdk.ErrInvalidSequence( fmt.Sprintf("Invalid sequence. Got %d, expected %d", sigs[i].Sequence, seq)).Result() @@ -287,7 +292,7 @@ func ensureSufficientMempoolFees(ctx sdk.Context, stdTx StdTx) sdk.Result { func setGasMeter(simulate bool, ctx sdk.Context, stdTx StdTx) sdk.Context { // set the gas meter - if simulate { + if simulate || ctx.BlockHeight() == 0 { return ctx.WithGasMeter(sdk.NewInfiniteGasMeter()) } return ctx.WithGasMeter(sdk.NewGasMeter(stdTx.Fee.Gas)) diff --git a/x/auth/ante_test.go b/x/auth/ante_test.go index 2a289f317..bacb3013f 100644 --- a/x/auth/ante_test.go +++ b/x/auth/ante_test.go @@ -169,6 +169,7 @@ func TestAnteHandlerAccountNumbers(t *testing.T) { feeCollector := NewFeeCollectionKeeper(cdc, capKey2) anteHandler := NewAnteHandler(mapper, feeCollector) ctx := sdk.NewContext(ms, abci.Header{ChainID: "mychainid"}, false, log.NewNopLogger()) + ctx = ctx.WithBlockHeight(1) // keys and addresses priv1, addr1 := privAndAddr() @@ -218,6 +219,66 @@ func TestAnteHandlerAccountNumbers(t *testing.T) { checkValidTx(t, anteHandler, ctx, tx, false) } +// Test logic around account number checking with many signers when BlockHeight is 0. +func TestAnteHandlerAccountNumbersAtBlockHeightZero(t *testing.T) { + // setup + ms, capKey, capKey2 := setupMultiStore() + cdc := codec.New() + RegisterBaseAccount(cdc) + mapper := NewAccountMapper(cdc, capKey, ProtoBaseAccount) + feeCollector := NewFeeCollectionKeeper(cdc, capKey2) + anteHandler := NewAnteHandler(mapper, feeCollector) + ctx := sdk.NewContext(ms, abci.Header{ChainID: "mychainid"}, false, log.NewNopLogger()) + ctx = ctx.WithBlockHeight(0) + + // keys and addresses + priv1, addr1 := privAndAddr() + priv2, addr2 := privAndAddr() + + // set the accounts + acc1 := mapper.NewAccountWithAddress(ctx, addr1) + acc1.SetCoins(newCoins()) + mapper.SetAccount(ctx, acc1) + acc2 := mapper.NewAccountWithAddress(ctx, addr2) + acc2.SetCoins(newCoins()) + mapper.SetAccount(ctx, acc2) + + // msg and signatures + var tx sdk.Tx + msg := newTestMsg(addr1) + fee := newStdFee() + + msgs := []sdk.Msg{msg} + + // test good tx from one signer + privs, accnums, seqs := []crypto.PrivKey{priv1}, []int64{0}, []int64{0} + tx = newTestTx(ctx, msgs, privs, accnums, seqs, fee) + checkValidTx(t, anteHandler, ctx, tx, false) + + // new tx from wrong account number + seqs = []int64{1} + tx = newTestTx(ctx, msgs, privs, []int64{1}, seqs, fee) + checkInvalidTx(t, anteHandler, ctx, tx, false, sdk.CodeInvalidSequence) + + // from correct account number + seqs = []int64{1} + tx = newTestTx(ctx, msgs, privs, []int64{0}, seqs, fee) + checkValidTx(t, anteHandler, ctx, tx, false) + + // new tx with another signer and incorrect account numbers + msg1 := newTestMsg(addr1, addr2) + msg2 := newTestMsg(addr2, addr1) + msgs = []sdk.Msg{msg1, msg2} + privs, accnums, seqs = []crypto.PrivKey{priv1, priv2}, []int64{1, 0}, []int64{2, 0} + tx = newTestTx(ctx, msgs, privs, accnums, seqs, fee) + checkInvalidTx(t, anteHandler, ctx, tx, false, sdk.CodeInvalidSequence) + + // correct account numbers + privs, accnums, seqs = []crypto.PrivKey{priv1, priv2}, []int64{0, 0}, []int64{2, 0} + tx = newTestTx(ctx, msgs, privs, accnums, seqs, fee) + checkValidTx(t, anteHandler, ctx, tx, false) +} + // Test logic around sequence checking with one signer and many signers. func TestAnteHandlerSequences(t *testing.T) { // setup @@ -228,6 +289,7 @@ func TestAnteHandlerSequences(t *testing.T) { feeCollector := NewFeeCollectionKeeper(cdc, capKey2) anteHandler := NewAnteHandler(mapper, feeCollector) ctx := sdk.NewContext(ms, abci.Header{ChainID: "mychainid"}, false, log.NewNopLogger()) + ctx = ctx.WithBlockHeight(1) // keys and addresses priv1, addr1 := privAndAddr() @@ -348,6 +410,7 @@ func TestAnteHandlerMemoGas(t *testing.T) { feeCollector := NewFeeCollectionKeeper(cdc, capKey2) anteHandler := NewAnteHandler(mapper, feeCollector) ctx := sdk.NewContext(ms, abci.Header{ChainID: "mychainid"}, false, log.NewNopLogger()) + ctx = ctx.WithBlockHeight(1) // keys and addresses priv1, addr1 := privAndAddr() @@ -391,6 +454,7 @@ func TestAnteHandlerMultiSigner(t *testing.T) { feeCollector := NewFeeCollectionKeeper(cdc, capKey2) anteHandler := NewAnteHandler(mapper, feeCollector) ctx := sdk.NewContext(ms, abci.Header{ChainID: "mychainid"}, false, log.NewNopLogger()) + ctx = ctx.WithBlockHeight(1) // keys and addresses priv1, addr1 := privAndAddr() @@ -442,6 +506,7 @@ func TestAnteHandlerBadSignBytes(t *testing.T) { feeCollector := NewFeeCollectionKeeper(cdc, capKey2) anteHandler := NewAnteHandler(mapper, feeCollector) ctx := sdk.NewContext(ms, abci.Header{ChainID: "mychainid"}, false, log.NewNopLogger()) + ctx = ctx.WithBlockHeight(1) // keys and addresses priv1, addr1 := privAndAddr() @@ -523,6 +588,7 @@ func TestAnteHandlerSetPubKey(t *testing.T) { feeCollector := NewFeeCollectionKeeper(cdc, capKey2) anteHandler := NewAnteHandler(mapper, feeCollector) ctx := sdk.NewContext(ms, abci.Header{ChainID: "mychainid"}, false, log.NewNopLogger()) + ctx = ctx.WithBlockHeight(1) // keys and addresses priv1, addr1 := privAndAddr() diff --git a/x/auth/client/cli/sign.go b/x/auth/client/cli/sign.go index 30704a500..67c64dc2a 100644 --- a/x/auth/client/cli/sign.go +++ b/x/auth/client/cli/sign.go @@ -19,6 +19,7 @@ import ( const ( flagAppend = "append" flagPrintSigs = "print-sigs" + flagOffline = "offline" ) // GetSignCommand returns the sign command @@ -27,13 +28,18 @@ func GetSignCommand(codec *amino.Codec, decoder auth.AccountDecoder) *cobra.Comm Use: "sign ", Short: "Sign transactions generated offline", Long: `Sign transactions created with the --generate-only flag. -Read a transaction from , sign it, and print its JSON encoding.`, +Read a transaction from , sign it, and print its JSON encoding. + +The --offline flag makes sure that the client will not reach out to the local cache. +Thus account number or sequence number lookups will not be performed and it is +recommended to set such parameters manually.`, RunE: makeSignCmd(codec, decoder), Args: cobra.ExactArgs(1), } cmd.Flags().String(client.FlagName, "", "Name of private key with which to sign") cmd.Flags().Bool(flagAppend, true, "Append the signature to the existing ones. If disabled, old signatures would be overwritten") cmd.Flags().Bool(flagPrintSigs, false, "Print the addresses that must sign the transaction and those who have already signed it, then exit") + cmd.Flags().Bool(flagOffline, false, "Offline mode. Do not query local cache.") return cmd } @@ -53,7 +59,7 @@ func makeSignCmd(cdc *amino.Codec, decoder auth.AccountDecoder) func(cmd *cobra. cliCtx := context.NewCLIContext().WithCodec(cdc).WithAccountDecoder(decoder) txBldr := authtxb.NewTxBuilderFromCLI() - newTx, err := utils.SignStdTx(txBldr, cliCtx, name, stdTx, viper.GetBool(flagAppend)) + newTx, err := utils.SignStdTx(txBldr, cliCtx, name, stdTx, viper.GetBool(flagAppend), viper.GetBool(flagOffline)) if err != nil { return err } diff --git a/x/bank/app_test.go b/x/bank/app_test.go index 77991f94b..7c320a9bd 100644 --- a/x/bank/app_test.go +++ b/x/bank/app_test.go @@ -210,7 +210,7 @@ func TestSengMsgMultipleInOut(t *testing.T) { testCases := []appTestCase{ { msgs: []sdk.Msg{sendMsg3}, - accNums: []int64{0, 2}, + accNums: []int64{0, 0}, accSeqs: []int64{0, 0}, expSimPass: true, expPass: true, @@ -258,7 +258,7 @@ func TestMsgSendDependent(t *testing.T) { }, { msgs: []sdk.Msg{sendMsg4}, - accNums: []int64{1}, + accNums: []int64{0}, accSeqs: []int64{0}, expSimPass: true, expPass: true, diff --git a/x/distribution/keeper/hooks.go b/x/distribution/keeper/hooks.go index 721a26db1..d551cc3a3 100644 --- a/x/distribution/keeper/hooks.go +++ b/x/distribution/keeper/hooks.go @@ -14,9 +14,9 @@ func (k Keeper) onValidatorCreated(ctx sdk.Context, addr sdk.ValAddress) { vdi := types.ValidatorDistInfo{ OperatorAddr: addr, FeePoolWithdrawalHeight: height, - Pool: types.DecCoins{}, - PoolCommission: types.DecCoins{}, - DelAccum: types.NewTotalAccum(height), + Pool: types.DecCoins{}, + PoolCommission: types.DecCoins{}, + DelAccum: types.NewTotalAccum(height), } k.SetValidatorDistInfo(ctx, vdi) } diff --git a/x/mock/simulation/random_simulate_blocks.go b/x/mock/simulation/random_simulate_blocks.go index 0af6fbbac..e52ca1068 100644 --- a/x/mock/simulation/random_simulate_blocks.go +++ b/x/mock/simulation/random_simulate_blocks.go @@ -82,7 +82,7 @@ func SimulateFromSeed(tb testing.TB, app *baseapp.BaseApp, // Initially this is the same as the initial validator set nextValidators := validators - header := abci.Header{Height: 0, Time: timestamp, ProposerAddress: randomProposer(r, validators)} + header := abci.Header{Height: 1, Time: timestamp, ProposerAddress: randomProposer(r, validators)} opCount := 0 // Setup code to catch SIGTERM's @@ -384,7 +384,7 @@ func RandomRequestBeginBlock(r *rand.Rand, validators map[string]mockValidator, time := header.Time vals := voteInfos if r.Float64() < pastEvidenceFraction { - height = int64(r.Intn(int(header.Height))) + height = int64(r.Intn(int(header.Height) - 1)) time = pastTimes[height] vals = pastVoteInfos[height] } diff --git a/x/stake/app_test.go b/x/stake/app_test.go index 4ab210f70..faafb6664 100644 --- a/x/stake/app_test.go +++ b/x/stake/app_test.go @@ -137,7 +137,7 @@ func TestStakeMsgs(t *testing.T) { addr1, sdk.ValAddress(addr2), priv2.PubKey(), bondCoin, description, commissionMsg, ) - mock.SignCheckDeliver(t, mApp.BaseApp, []sdk.Msg{createValidatorMsgOnBehalfOf}, []int64{0, 1}, []int64{1, 0}, true, true, priv1, priv2) + mock.SignCheckDeliver(t, mApp.BaseApp, []sdk.Msg{createValidatorMsgOnBehalfOf}, []int64{0, 0}, []int64{1, 0}, true, true, priv1, priv2) mock.CheckBalance(t, mApp, addr1, sdk.Coins{genCoin.Minus(bondCoin).Minus(bondCoin)}) mApp.BeginBlock(abci.RequestBeginBlock{}) @@ -161,13 +161,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, true, priv2) + mock.SignCheckDeliver(t, mApp.BaseApp, []sdk.Msg{delegateMsg}, []int64{0}, []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, true, priv2) + mock.SignCheckDeliver(t, mApp.BaseApp, []sdk.Msg{beginUnbondingMsg}, []int64{0}, []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/cli/flags.go b/x/stake/client/cli/flags.go index bec76298f..29237b6ba 100644 --- a/x/stake/client/cli/flags.go +++ b/x/stake/client/cli/flags.go @@ -25,6 +25,12 @@ const ( FlagCommissionRate = "commission-rate" FlagCommissionMaxRate = "commission-max-rate" FlagCommissionMaxChangeRate = "commission-max-change-rate" + + FlagGenesisFormat = "genesis-format" + FlagNodeID = "node-id" + FlagIP = "ip" + + FlagOutputDocument = "output-document" // inspired by wget -O ) // common flagsets to add to various functions diff --git a/x/stake/client/cli/tx.go b/x/stake/client/cli/tx.go index 9640c5c05..e3f1060ee 100644 --- a/x/stake/client/cli/tx.go +++ b/x/stake/client/cli/tx.go @@ -2,7 +2,6 @@ package cli import ( "fmt" - "github.com/cosmos/cosmos-sdk/client" "github.com/cosmos/cosmos-sdk/client/context" "github.com/cosmos/cosmos-sdk/client/utils" @@ -87,7 +86,15 @@ func GetCmdCreateValidator(cdc *codec.Codec) *cobra.Command { ) } - if cliCtx.GenerateOnly { + if viper.GetBool(FlagGenesisFormat) { + ip := viper.GetString(FlagIP) + nodeID := viper.GetString(FlagNodeID) + if nodeID != "" && ip != "" { + txBldr = txBldr.WithMemo(fmt.Sprintf("%s@%s:26656", nodeID, ip)) + } + } + + if viper.GetBool(FlagGenesisFormat) || cliCtx.GenerateOnly { return utils.PrintUnsignedStdTx(txBldr, cliCtx, []sdk.Msg{msg}, true) } @@ -101,6 +108,10 @@ func GetCmdCreateValidator(cdc *codec.Codec) *cobra.Command { cmd.Flags().AddFlagSet(fsDescriptionCreate) cmd.Flags().AddFlagSet(fsCommissionCreate) cmd.Flags().AddFlagSet(fsDelegator) + cmd.Flags().Bool(FlagGenesisFormat, false, "Export the transaction in gen-tx format; it implies --generate-only") + cmd.Flags().String(FlagIP, "", fmt.Sprintf("Node's public IP. It takes effect only when used in combination with --%s", FlagGenesisFormat)) + cmd.Flags().String(FlagNodeID, "", "Node's ID") + cmd.MarkFlagRequired(client.FlagFrom) return cmd } From b48d0d56239411788fd64d5ab287fbabc60b3aef Mon Sep 17 00:00:00 2001 From: Rigel Date: Fri, 19 Oct 2018 14:36:00 -0400 Subject: [PATCH 39/40] Merge PR #2527: Minting --- PENDING.md | 2 + client/lcd/lcd_test.go | 4 - cmd/gaia/app/app.go | 32 +++-- cmd/gaia/app/genesis.go | 16 +++ cmd/gaia/app/sim_test.go | 8 +- cmd/gaia/cli_test/cli_test.go | 3 - docs/spec/distribution/end_block.md | 2 +- docs/spec/mint/begin_block.md | 28 +++++ docs/spec/mint/state.md | 31 +++++ docs/spec/staking/state.md | 9 -- x/auth/ante.go | 2 +- x/auth/feekeeper.go | 3 +- x/auth/feekeeper_test.go | 4 +- x/distribution/abci_app.go | 2 +- x/distribution/keeper/allocation.go | 2 +- x/distribution/keeper/allocation_test.go | 12 +- x/distribution/keeper/delegation_test.go | 12 +- x/distribution/keeper/validator_test.go | 10 +- x/mint/abci_app.go | 25 ++++ x/mint/expected_keepers.go | 15 +++ x/mint/genesis.go | 55 +++++++++ x/mint/keeper.go | 85 +++++++++++++ x/mint/minter.go | 71 +++++++++++ x/mint/minter_test.go | 53 +++++++++ x/mint/params.go | 43 +++++++ x/stake/genesis.go | 11 -- x/stake/genesis_test.go | 10 -- x/stake/handler.go | 14 +-- x/stake/handler_test.go | 7 -- x/stake/keeper/params.go | 28 ----- x/stake/keeper/sdk_types.go | 13 ++ x/stake/keeper/test_common.go | 12 -- x/stake/simulation/msgs.go | 2 - x/stake/stake.go | 11 +- x/stake/types/inflation_test.go | 145 ----------------------- x/stake/types/params.go | 36 ++---- x/stake/types/pool.go | 70 +---------- x/stake/types/validator_test.go | 13 +- 38 files changed, 510 insertions(+), 391 deletions(-) create mode 100644 docs/spec/mint/begin_block.md create mode 100644 docs/spec/mint/state.md create mode 100644 x/mint/abci_app.go create mode 100644 x/mint/expected_keepers.go create mode 100644 x/mint/genesis.go create mode 100644 x/mint/keeper.go create mode 100644 x/mint/minter.go create mode 100644 x/mint/minter_test.go create mode 100644 x/mint/params.go delete mode 100644 x/stake/types/inflation_test.go diff --git a/PENDING.md b/PENDING.md index 2dc3013de..76c575368 100644 --- a/PENDING.md +++ b/PENDING.md @@ -82,6 +82,8 @@ BREAKING CHANGES * [x/stake] \#2500 Block conflicting redelegations until we add an index * [x/params] Global Paramstore refactored * [x/stake] \#2508 Utilize Tendermint power for validator power key + * [x/stake] \#2531 Remove all inflation logic + * [x/mint] \#2531 Add minting module and inflation logic * Tendermint * Update tendermint version from v0.23.0 to v0.25.0, notable changes diff --git a/client/lcd/lcd_test.go b/client/lcd/lcd_test.go index c1c018778..d4d038016 100644 --- a/client/lcd/lcd_test.go +++ b/client/lcd/lcd_test.go @@ -480,11 +480,7 @@ func TestPoolParamsQuery(t *testing.T) { var pool stake.Pool err = cdc.UnmarshalJSON([]byte(body), &pool) require.Nil(t, err) - require.Equal(t, initialPool.DateLastCommissionReset, pool.DateLastCommissionReset) - require.Equal(t, initialPool.PrevBondedShares, pool.PrevBondedShares) require.Equal(t, initialPool.BondedTokens, pool.BondedTokens) - require.Equal(t, initialPool.NextInflation(params), pool.Inflation) - initialPool = initialPool.ProcessProvisions(params) // provisions are added to the pool every hour require.Equal(t, initialPool.LooseTokens, pool.LooseTokens) } diff --git a/cmd/gaia/app/app.go b/cmd/gaia/app/app.go index 6afeae793..8fba41f60 100644 --- a/cmd/gaia/app/app.go +++ b/cmd/gaia/app/app.go @@ -10,6 +10,7 @@ import ( "github.com/cosmos/cosmos-sdk/x/bank" distr "github.com/cosmos/cosmos-sdk/x/distribution" "github.com/cosmos/cosmos-sdk/x/gov" + "github.com/cosmos/cosmos-sdk/x/mint" "github.com/cosmos/cosmos-sdk/x/params" "github.com/cosmos/cosmos-sdk/x/slashing" "github.com/cosmos/cosmos-sdk/x/stake" @@ -46,6 +47,7 @@ type GaiaApp struct { keyStake *sdk.KVStoreKey tkeyStake *sdk.TransientStoreKey keySlashing *sdk.KVStoreKey + keyMint *sdk.KVStoreKey keyDistr *sdk.KVStoreKey tkeyDistr *sdk.TransientStoreKey keyGov *sdk.KVStoreKey @@ -59,6 +61,7 @@ type GaiaApp struct { bankKeeper bank.Keeper stakeKeeper stake.Keeper slashingKeeper slashing.Keeper + mintKeeper mint.Keeper distrKeeper distr.Keeper govKeeper gov.Keeper paramsKeeper params.Keeper @@ -78,6 +81,7 @@ func NewGaiaApp(logger log.Logger, db dbm.DB, traceStore io.Writer, baseAppOptio keyAccount: sdk.NewKVStoreKey("acc"), keyStake: sdk.NewKVStoreKey("stake"), tkeyStake: sdk.NewTransientStoreKey("transient_stake"), + keyMint: sdk.NewKVStoreKey("mint"), keyDistr: sdk.NewKVStoreKey("distr"), tkeyDistr: sdk.NewTransientStoreKey("transient_distr"), keySlashing: sdk.NewKVStoreKey("slashing"), @@ -110,6 +114,10 @@ func NewGaiaApp(logger log.Logger, db dbm.DB, traceStore io.Writer, baseAppOptio app.bankKeeper, app.paramsKeeper.Subspace(stake.DefaultParamspace), app.RegisterCodespace(stake.DefaultCodespace), ) + app.mintKeeper = mint.NewKeeper(app.cdc, app.keyMint, + app.paramsKeeper.Subspace(mint.DefaultParamspace), + app.stakeKeeper, app.feeCollectionKeeper, + ) app.distrKeeper = distr.NewKeeper( app.cdc, app.keyDistr, @@ -147,11 +155,11 @@ func NewGaiaApp(logger log.Logger, db dbm.DB, traceStore io.Writer, baseAppOptio AddRoute("stake", stake.NewQuerier(app.stakeKeeper, app.cdc)) // initialize BaseApp + app.MountStoresIAVL(app.keyMain, app.keyAccount, app.keyStake, app.keyMint, app.keyDistr, + app.keySlashing, app.keyGov, app.keyFeeCollection, app.keyParams) app.SetInitChainer(app.initChainer) app.SetBeginBlocker(app.BeginBlocker) app.SetAnteHandler(auth.NewAnteHandler(app.accountMapper, app.feeCollectionKeeper)) - app.MountStoresIAVL(app.keyMain, app.keyAccount, app.keyStake, app.keyDistr, - app.keySlashing, app.keyGov, app.keyFeeCollection, app.keyParams) app.MountStoresTransient(app.tkeyParams, app.tkeyStake, app.tkeyDistr) app.SetEndBlocker(app.EndBlocker) @@ -184,6 +192,9 @@ func (app *GaiaApp) BeginBlocker(ctx sdk.Context, req abci.RequestBeginBlock) ab // distribute rewards from previous block distr.BeginBlocker(ctx, req, app.distrKeeper) + // mint new tokens for this new block + mint.BeginBlocker(ctx, app.mintKeeper) + return abci.ResponseBeginBlock{ Tags: tags.ToKVPairs(), } @@ -232,8 +243,8 @@ func (app *GaiaApp) initChainer(ctx sdk.Context, req abci.RequestInitChain) abci // load the address to pubkey map slashing.InitGenesis(ctx, app.slashingKeeper, genesisState.SlashingData, genesisState.StakeData) - gov.InitGenesis(ctx, app.govKeeper, genesisState.GovData) + mint.InitGenesis(ctx, app.mintKeeper, genesisState.MintData) distr.InitGenesis(ctx, app.distrKeeper, genesisState.DistrData) err = GaiaValidateGenesisState(genesisState) if err != nil { @@ -289,13 +300,14 @@ func (app *GaiaApp) ExportAppStateAndValidators() (appState json.RawMessage, val return false } app.accountMapper.IterateAccounts(ctx, appendAccount) - - genState := GenesisState{ - Accounts: accounts, - StakeData: stake.WriteGenesis(ctx, app.stakeKeeper), - DistrData: distr.WriteGenesis(ctx, app.distrKeeper), - GovData: gov.WriteGenesis(ctx, app.govKeeper), - } + genState := NewGenesisState( + accounts, + stake.WriteGenesis(ctx, app.stakeKeeper), + mint.WriteGenesis(ctx, app.mintKeeper), + distr.WriteGenesis(ctx, app.distrKeeper), + gov.WriteGenesis(ctx, app.govKeeper), + slashing.GenesisState{}, // TODO create write methods + ) appState, err = codec.MarshalJSONIndent(app.cdc, genState) if err != nil { return nil, nil, err diff --git a/cmd/gaia/app/genesis.go b/cmd/gaia/app/genesis.go index 9d52c9a57..df28bcf6c 100644 --- a/cmd/gaia/app/genesis.go +++ b/cmd/gaia/app/genesis.go @@ -16,6 +16,7 @@ import ( "github.com/cosmos/cosmos-sdk/x/auth" distr "github.com/cosmos/cosmos-sdk/x/distribution" "github.com/cosmos/cosmos-sdk/x/gov" + "github.com/cosmos/cosmos-sdk/x/mint" "github.com/cosmos/cosmos-sdk/x/slashing" "github.com/cosmos/cosmos-sdk/x/stake" tmtypes "github.com/tendermint/tendermint/types" @@ -31,12 +32,26 @@ var ( type GenesisState struct { Accounts []GenesisAccount `json:"accounts"` StakeData stake.GenesisState `json:"stake"` + MintData mint.GenesisState `json:"mint"` DistrData distr.GenesisState `json:"distr"` GovData gov.GenesisState `json:"gov"` SlashingData slashing.GenesisState `json:"slashing"` GenTxs []json.RawMessage `json:"gentxs"` } +func NewGenesisState(accounts []GenesisAccount, stakeData stake.GenesisState, mintData mint.GenesisState, + distrData distr.GenesisState, govData gov.GenesisState, slashingData slashing.GenesisState) GenesisState { + + return GenesisState{ + Accounts: accounts, + StakeData: stakeData, + MintData: mintData, + DistrData: distrData, + GovData: govData, + SlashingData: slashingData, + } +} + // GenesisAccount doesn't need pubkey or sequence type GenesisAccount struct { Address sdk.AccAddress `json:"address"` @@ -110,6 +125,7 @@ func GaiaAppGenState(cdc *codec.Codec, appGenTxs []json.RawMessage) (genesisStat genesisState = GenesisState{ Accounts: genaccs, StakeData: stakeData, + MintData: mint.DefaultGenesisState(), DistrData: distr.DefaultGenesisState(), GovData: gov.DefaultGenesisState(), SlashingData: slashingData, diff --git a/cmd/gaia/app/sim_test.go b/cmd/gaia/app/sim_test.go index 41b3da741..e24919e10 100644 --- a/cmd/gaia/app/sim_test.go +++ b/cmd/gaia/app/sim_test.go @@ -18,6 +18,7 @@ import ( distr "github.com/cosmos/cosmos-sdk/x/distribution" "github.com/cosmos/cosmos-sdk/x/gov" govsim "github.com/cosmos/cosmos-sdk/x/gov/simulation" + "github.com/cosmos/cosmos-sdk/x/mint" "github.com/cosmos/cosmos-sdk/x/mock/simulation" "github.com/cosmos/cosmos-sdk/x/slashing" slashingsim "github.com/cosmos/cosmos-sdk/x/slashing/simulation" @@ -79,13 +80,16 @@ func appStateFn(r *rand.Rand, accs []simulation.Account) json.RawMessage { stakeGenesis.Pool.LooseTokens = sdk.NewDec(int64(100*250) + (numInitiallyBonded * 100)) stakeGenesis.Validators = validators stakeGenesis.Bonds = delegations + // No inflation, for now - stakeGenesis.Params.InflationMax = sdk.NewDec(0) - stakeGenesis.Params.InflationMin = sdk.NewDec(0) + mintGenesis := mint.DefaultGenesisState() + mintGenesis.Params.InflationMax = sdk.NewDec(0) + mintGenesis.Params.InflationMin = sdk.NewDec(0) genesis := GenesisState{ Accounts: genesisAccounts, StakeData: stakeGenesis, + MintData: mintGenesis, DistrData: distr.DefaultGenesisWithValidators(valAddrs), SlashingData: slashingGenesis, GovData: govGenesis, diff --git a/cmd/gaia/cli_test/cli_test.go b/cmd/gaia/cli_test/cli_test.go index 1639e143e..2b717244c 100644 --- a/cmd/gaia/cli_test/cli_test.go +++ b/cmd/gaia/cli_test/cli_test.go @@ -231,7 +231,6 @@ func TestGaiaCLICreateValidator(t *testing.T) { defaultParams := stake.DefaultParams() initialPool := stake.InitialPool() initialPool.BondedTokens = initialPool.BondedTokens.Add(sdk.NewDec(100)) // Delegate tx on GaiaAppGenState - initialPool = initialPool.ProcessProvisions(defaultParams) // provisions are added to the pool every hour // create validator cvStr := fmt.Sprintf("gaiacli tx create-validator %v", flags) @@ -290,8 +289,6 @@ func TestGaiaCLICreateValidator(t *testing.T) { require.True(t, defaultParams.Equal(params)) pool := executeGetPool(t, fmt.Sprintf("gaiacli query pool --output=json %v", flags)) - require.Equal(t, initialPool.DateLastCommissionReset, pool.DateLastCommissionReset) - require.Equal(t, initialPool.PrevBondedShares, pool.PrevBondedShares) require.Equal(t, initialPool.BondedTokens, pool.BondedTokens) } diff --git a/docs/spec/distribution/end_block.md b/docs/spec/distribution/end_block.md index 7f54ee972..2a3db9d4b 100644 --- a/docs/spec/distribution/end_block.md +++ b/docs/spec/distribution/end_block.md @@ -15,7 +15,7 @@ pool which validator holds individually (`ValidatorDistribution.ProvisionsRewardPool`). ``` -func AllocateFees(feesCollected sdk.Coins, feePool FeePool, proposer ValidatorDistribution, +func AllocateTokens(feesCollected sdk.Coins, feePool FeePool, proposer ValidatorDistribution, sumPowerPrecommitValidators, totalBondedTokens, communityTax, proposerCommissionRate sdk.Dec) diff --git a/docs/spec/mint/begin_block.md b/docs/spec/mint/begin_block.md new file mode 100644 index 000000000..7588db38b --- /dev/null +++ b/docs/spec/mint/begin_block.md @@ -0,0 +1,28 @@ +# Begin-Block + +## Inflation + +Inflation occurs at the beginning of each block. + +### NextInflation + +The target annual inflation rate is recalculated for each provisions cycle. The +inflation is also subject to a rate change (positive or negative) depending on +the distance from the desired ratio (67%). The maximum rate change possible is +defined to be 13% per year, however the annual inflation is capped as between +7% and 20%. + +NextInflation(params Params, bondedRatio sdk.Dec) (inflation sdk.Dec) { + inflationRateChangePerYear = (1 - bondedRatio/params.GoalBonded) * params.InflationRateChange + inflationRateChange = inflationRateChangePerYear/hrsPerYr + + // increase the new annual inflation for this next cycle + inflation += inflationRateChange + if inflation > params.InflationMax { + inflation = params.InflationMax + } + if inflation < params.InflationMin { + inflation = params.InflationMin + } + + return inflation diff --git a/docs/spec/mint/state.md b/docs/spec/mint/state.md new file mode 100644 index 000000000..98e8e63dd --- /dev/null +++ b/docs/spec/mint/state.md @@ -0,0 +1,31 @@ +## State + +### Minter + +The minter is a space for holding current inflation information. + + - Minter: `0x00 -> amino(minter)` + +```golang +type Minter struct { + InflationLastTime time.Time // block time which the last inflation was processed + Inflation sdk.Dec // current annual inflation rate +} +``` + +### Params + +Minting params are held in the global params store. + + - Params: `mint/params -> amino(params)` + +```golang +type Params struct { + MintDenom string // type of coin to mint + InflationRateChange sdk.Dec // maximum annual change in inflation rate + InflationMax sdk.Dec // maximum inflation rate + InflationMin sdk.Dec // minimum inflation rate + GoalBonded sdk.Dec // goal of percent bonded atoms +} +``` + diff --git a/docs/spec/staking/state.md b/docs/spec/staking/state.md index 9454aca7d..6568293cc 100644 --- a/docs/spec/staking/state.md +++ b/docs/spec/staking/state.md @@ -12,10 +12,6 @@ inflation information, etc. type Pool struct { LooseTokens int64 // tokens not associated with any bonded validator BondedTokens int64 // reserve of bonded tokens - InflationLastTime int64 // block which the last inflation was processed // TODO make time - Inflation sdk.Dec // current annual inflation rate - - DateLastCommissionReset int64 // unix timestamp for last commission accounting reset (daily) } ``` @@ -28,11 +24,6 @@ overall functioning of the stake module. ```golang type Params struct { - InflationRateChange sdk.Dec // maximum annual change in inflation rate - InflationMax sdk.Dec // maximum inflation rate - InflationMin sdk.Dec // minimum inflation rate - GoalBonded sdk.Dec // Goal of percent bonded atoms - MaxValidators uint16 // maximum number of validators BondDenom string // bondable coin denomination } diff --git a/x/auth/ante.go b/x/auth/ante.go index 8a10a0239..3fd183a0c 100644 --- a/x/auth/ante.go +++ b/x/auth/ante.go @@ -92,7 +92,7 @@ func NewAnteHandler(am AccountMapper, fck FeeCollectionKeeper) sdk.AnteHandler { if !res.IsOK() { return newCtx, res, true } - fck.addCollectedFees(newCtx, stdTx.Fee.Amount) + fck.AddCollectedFees(newCtx, stdTx.Fee.Amount) } for i := 0; i < len(stdSigs); i++ { diff --git a/x/auth/feekeeper.go b/x/auth/feekeeper.go index 45894be1b..a6be2e12d 100644 --- a/x/auth/feekeeper.go +++ b/x/auth/feekeeper.go @@ -46,7 +46,8 @@ func (fck FeeCollectionKeeper) setCollectedFees(ctx sdk.Context, coins sdk.Coins store.Set(collectedFeesKey, bz) } -func (fck FeeCollectionKeeper) addCollectedFees(ctx sdk.Context, coins sdk.Coins) sdk.Coins { +// add to the fee pool +func (fck FeeCollectionKeeper) AddCollectedFees(ctx sdk.Context, coins sdk.Coins) sdk.Coins { newCoins := fck.GetCollectedFees(ctx).Plus(coins) fck.setCollectedFees(ctx, newCoins) diff --git a/x/auth/feekeeper_test.go b/x/auth/feekeeper_test.go index 82bbe9c35..d48151161 100644 --- a/x/auth/feekeeper_test.go +++ b/x/auth/feekeeper_test.go @@ -49,11 +49,11 @@ func TestFeeCollectionKeeperAdd(t *testing.T) { require.True(t, fck.GetCollectedFees(ctx).IsEqual(emptyCoins)) // add oneCoin and check that pool is now oneCoin - fck.addCollectedFees(ctx, oneCoin) + fck.AddCollectedFees(ctx, oneCoin) require.True(t, fck.GetCollectedFees(ctx).IsEqual(oneCoin)) // add oneCoin again and check that pool is now twoCoins - fck.addCollectedFees(ctx, oneCoin) + fck.AddCollectedFees(ctx, oneCoin) require.True(t, fck.GetCollectedFees(ctx).IsEqual(twoCoins)) } diff --git a/x/distribution/abci_app.go b/x/distribution/abci_app.go index 2bdcadb6a..800ede928 100644 --- a/x/distribution/abci_app.go +++ b/x/distribution/abci_app.go @@ -13,7 +13,7 @@ func BeginBlocker(ctx sdk.Context, req abci.RequestBeginBlock, k keeper.Keeper) if ctx.BlockHeight() > 1 { previousPercentPrecommitVotes := getPreviousPercentPrecommitVotes(req) previousProposer := k.GetPreviousProposerConsAddr(ctx) - k.AllocateFees(ctx, previousPercentPrecommitVotes, previousProposer) + k.AllocateTokens(ctx, previousPercentPrecommitVotes, previousProposer) } consAddr := sdk.ConsAddress(req.Header.ProposerAddress) diff --git a/x/distribution/keeper/allocation.go b/x/distribution/keeper/allocation.go index e6dd1c969..debf0eb62 100644 --- a/x/distribution/keeper/allocation.go +++ b/x/distribution/keeper/allocation.go @@ -8,7 +8,7 @@ import ( ) // Allocate fees handles distribution of the collected fees -func (k Keeper) AllocateFees(ctx sdk.Context, percentVotes sdk.Dec, proposer sdk.ConsAddress) { +func (k Keeper) AllocateTokens(ctx sdk.Context, percentVotes sdk.Dec, proposer sdk.ConsAddress) { ctx.Logger().With("module", "x/distribution").Error(fmt.Sprintf("allocation height: %v", ctx.BlockHeight())) // get the proposer of this block diff --git a/x/distribution/keeper/allocation_test.go b/x/distribution/keeper/allocation_test.go index 441739ebe..18dfe78e5 100644 --- a/x/distribution/keeper/allocation_test.go +++ b/x/distribution/keeper/allocation_test.go @@ -9,7 +9,7 @@ import ( "github.com/stretchr/testify/require" ) -func TestAllocateFeesBasic(t *testing.T) { +func TestAllocateTokensBasic(t *testing.T) { // no community tax on inputs ctx, _, keeper, sk, fck := CreateTestInputAdvanced(t, false, 100, sdk.ZeroDec()) @@ -41,7 +41,7 @@ func TestAllocateFeesBasic(t *testing.T) { feeInputs := sdk.NewInt(100) fck.SetCollectedFees(sdk.Coins{sdk.NewCoin(denom, feeInputs)}) require.Equal(t, feeInputs, fck.GetCollectedFees(ctx).AmountOf(denom)) - keeper.AllocateFees(ctx, sdk.OneDec(), valConsAddr1) + keeper.AllocateTokens(ctx, sdk.OneDec(), valConsAddr1) // verify that these fees have been received by the feePool percentProposer := sdk.NewDecWithPrec(5, 2) @@ -52,7 +52,7 @@ func TestAllocateFeesBasic(t *testing.T) { require.True(sdk.DecEq(t, expRes, feePool.Pool[0].Amount)) } -func TestAllocateFeesWithCommunityTax(t *testing.T) { +func TestAllocateTokensWithCommunityTax(t *testing.T) { communityTax := sdk.NewDecWithPrec(1, 2) //1% ctx, _, keeper, sk, fck := CreateTestInputAdvanced(t, false, 100, communityTax) stakeHandler := stake.NewHandler(sk) @@ -68,7 +68,7 @@ func TestAllocateFeesWithCommunityTax(t *testing.T) { // allocate 100 denom of fees feeInputs := sdk.NewInt(100) fck.SetCollectedFees(sdk.Coins{sdk.NewCoin(denom, feeInputs)}) - keeper.AllocateFees(ctx, sdk.OneDec(), valConsAddr1) + keeper.AllocateTokens(ctx, sdk.OneDec(), valConsAddr1) // verify that these fees have been received by the feePool feePool := keeper.GetFeePool(ctx) @@ -80,7 +80,7 @@ func TestAllocateFeesWithCommunityTax(t *testing.T) { require.True(sdk.DecEq(t, expRes, feePool.Pool[0].Amount)) } -func TestAllocateFeesWithPartialPrecommitPower(t *testing.T) { +func TestAllocateTokensWithPartialPrecommitPower(t *testing.T) { communityTax := sdk.NewDecWithPrec(1, 2) ctx, _, keeper, sk, fck := CreateTestInputAdvanced(t, false, 100, communityTax) stakeHandler := stake.NewHandler(sk) @@ -97,7 +97,7 @@ func TestAllocateFeesWithPartialPrecommitPower(t *testing.T) { feeInputs := sdk.NewInt(100) fck.SetCollectedFees(sdk.Coins{sdk.NewCoin(denom, feeInputs)}) percentPrecommitVotes := sdk.NewDecWithPrec(25, 2) - keeper.AllocateFees(ctx, percentPrecommitVotes, valConsAddr1) + keeper.AllocateTokens(ctx, percentPrecommitVotes, valConsAddr1) // verify that these fees have been received by the feePool feePool := keeper.GetFeePool(ctx) diff --git a/x/distribution/keeper/delegation_test.go b/x/distribution/keeper/delegation_test.go index 8415c708a..3455d48c8 100644 --- a/x/distribution/keeper/delegation_test.go +++ b/x/distribution/keeper/delegation_test.go @@ -30,7 +30,7 @@ func TestWithdrawDelegationRewardBasic(t *testing.T) { feeInputs := sdk.NewInt(100) fck.SetCollectedFees(sdk.Coins{sdk.NewCoin(denom, feeInputs)}) require.Equal(t, feeInputs, fck.GetCollectedFees(ctx).AmountOf(denom)) - keeper.AllocateFees(ctx, sdk.OneDec(), valConsAddr1) + keeper.AllocateTokens(ctx, sdk.OneDec(), valConsAddr1) // withdraw delegation ctx = ctx.WithBlockHeight(1) @@ -64,7 +64,7 @@ func TestWithdrawDelegationRewardWithCommission(t *testing.T) { feeInputs := sdk.NewInt(100) fck.SetCollectedFees(sdk.Coins{sdk.NewCoin(denom, feeInputs)}) require.Equal(t, feeInputs, fck.GetCollectedFees(ctx).AmountOf(denom)) - keeper.AllocateFees(ctx, sdk.OneDec(), valConsAddr1) + keeper.AllocateTokens(ctx, sdk.OneDec(), valConsAddr1) // withdraw delegation ctx = ctx.WithBlockHeight(1) @@ -104,7 +104,7 @@ func TestWithdrawDelegationRewardTwoDelegators(t *testing.T) { feeInputs := sdk.NewInt(100) fck.SetCollectedFees(sdk.Coins{sdk.NewCoin(denom, feeInputs)}) require.Equal(t, feeInputs, fck.GetCollectedFees(ctx).AmountOf(denom)) - keeper.AllocateFees(ctx, sdk.OneDec(), valConsAddr1) + keeper.AllocateTokens(ctx, sdk.OneDec(), valConsAddr1) // delegator 1 withdraw delegation ctx = ctx.WithBlockHeight(1) @@ -146,7 +146,7 @@ func TestWithdrawDelegationRewardTwoDelegatorsUneven(t *testing.T) { feeInputs := sdk.NewInt(90) fck.SetCollectedFees(sdk.Coins{sdk.NewCoin(denom, feeInputs)}) require.Equal(t, feeInputs, fck.GetCollectedFees(ctx).AmountOf(denom)) - keeper.AllocateFees(ctx, sdk.OneDec(), valConsAddr1) + keeper.AllocateTokens(ctx, sdk.OneDec(), valConsAddr1) ctx = ctx.WithBlockHeight(1) // delegator 1 withdraw delegation early, delegator 2 just keeps it's accum @@ -160,7 +160,7 @@ func TestWithdrawDelegationRewardTwoDelegatorsUneven(t *testing.T) { feeInputs = sdk.NewInt(180) fck.SetCollectedFees(sdk.Coins{sdk.NewCoin(denom, feeInputs)}) require.Equal(t, feeInputs, fck.GetCollectedFees(ctx).AmountOf(denom)) - keeper.AllocateFees(ctx, sdk.OneDec(), valConsAddr1) + keeper.AllocateTokens(ctx, sdk.OneDec(), valConsAddr1) ctx = ctx.WithBlockHeight(2) // delegator 2 now withdraws everything it's entitled to @@ -228,7 +228,7 @@ func TestWithdrawDelegationRewardsAll(t *testing.T) { feeInputs := sdk.NewInt(1000) fck.SetCollectedFees(sdk.Coins{sdk.NewCoin(denom, feeInputs)}) require.Equal(t, feeInputs, fck.GetCollectedFees(ctx).AmountOf(denom)) - keeper.AllocateFees(ctx, sdk.OneDec(), valConsAddr1) + keeper.AllocateTokens(ctx, sdk.OneDec(), valConsAddr1) // withdraw delegation ctx = ctx.WithBlockHeight(1) diff --git a/x/distribution/keeper/validator_test.go b/x/distribution/keeper/validator_test.go index 57c9a8b81..62638a4d2 100644 --- a/x/distribution/keeper/validator_test.go +++ b/x/distribution/keeper/validator_test.go @@ -23,7 +23,7 @@ func TestWithdrawValidatorRewardsAllNoDelegator(t *testing.T) { feeInputs := sdk.NewInt(100) fck.SetCollectedFees(sdk.Coins{sdk.NewCoin(denom, feeInputs)}) require.Equal(t, feeInputs, fck.GetCollectedFees(ctx).AmountOf(denom)) - keeper.AllocateFees(ctx, sdk.OneDec(), valConsAddr1) + keeper.AllocateTokens(ctx, sdk.OneDec(), valConsAddr1) // withdraw self-delegation reward ctx = ctx.WithBlockHeight(1) @@ -55,7 +55,7 @@ func TestWithdrawValidatorRewardsAllDelegatorNoCommission(t *testing.T) { feeInputs := sdk.NewInt(100) fck.SetCollectedFees(sdk.Coins{sdk.NewCoin(denom, feeInputs)}) require.Equal(t, feeInputs, fck.GetCollectedFees(ctx).AmountOf(denom)) - keeper.AllocateFees(ctx, sdk.OneDec(), valConsAddr1) + keeper.AllocateTokens(ctx, sdk.OneDec(), valConsAddr1) // withdraw self-delegation reward ctx = ctx.WithBlockHeight(1) @@ -89,7 +89,7 @@ func TestWithdrawValidatorRewardsAllDelegatorWithCommission(t *testing.T) { feeInputs := sdk.NewInt(100) fck.SetCollectedFees(sdk.Coins{sdk.NewCoin(denom, feeInputs)}) require.Equal(t, feeInputs, fck.GetCollectedFees(ctx).AmountOf(denom)) - keeper.AllocateFees(ctx, sdk.OneDec(), valConsAddr1) + keeper.AllocateTokens(ctx, sdk.OneDec(), valConsAddr1) // withdraw validator reward ctx = ctx.WithBlockHeight(1) @@ -129,7 +129,7 @@ func TestWithdrawValidatorRewardsAllMultipleValidator(t *testing.T) { feeInputs := sdk.NewInt(1000) fck.SetCollectedFees(sdk.Coins{sdk.NewCoin(denom, feeInputs)}) require.Equal(t, feeInputs, fck.GetCollectedFees(ctx).AmountOf(denom)) - keeper.AllocateFees(ctx, sdk.OneDec(), valConsAddr1) + keeper.AllocateTokens(ctx, sdk.OneDec(), valConsAddr1) // withdraw validator reward ctx = ctx.WithBlockHeight(1) @@ -175,7 +175,7 @@ func TestWithdrawValidatorRewardsAllMultipleDelegator(t *testing.T) { feeInputs := sdk.NewInt(100) fck.SetCollectedFees(sdk.Coins{sdk.NewCoin(denom, feeInputs)}) require.Equal(t, feeInputs, fck.GetCollectedFees(ctx).AmountOf(denom)) - keeper.AllocateFees(ctx, sdk.OneDec(), valConsAddr1) + keeper.AllocateTokens(ctx, sdk.OneDec(), valConsAddr1) // withdraw validator reward ctx = ctx.WithBlockHeight(1) diff --git a/x/mint/abci_app.go b/x/mint/abci_app.go new file mode 100644 index 000000000..cb3bd44c5 --- /dev/null +++ b/x/mint/abci_app.go @@ -0,0 +1,25 @@ +package mint + +import ( + "time" + + sdk "github.com/cosmos/cosmos-sdk/types" +) + +// Called every block, process inflation on the first block of every hour +func BeginBlocker(ctx sdk.Context, k Keeper) { + + blockTime := ctx.BlockHeader().Time + minter := k.GetMinter(ctx) + if blockTime.Sub(minter.InflationLastTime) < time.Hour { // only mint on the hour! + return + } + + params := k.GetParams(ctx) + totalSupply := k.sk.TotalPower(ctx) + bondedRatio := k.sk.BondedRatio(ctx) + minter.InflationLastTime = blockTime + minter, mintedCoin := minter.ProcessProvisions(params, totalSupply, bondedRatio) + k.fck.AddCollectedFees(ctx, sdk.Coins{mintedCoin}) + k.SetMinter(ctx, minter) +} diff --git a/x/mint/expected_keepers.go b/x/mint/expected_keepers.go new file mode 100644 index 000000000..8daaaf7ac --- /dev/null +++ b/x/mint/expected_keepers.go @@ -0,0 +1,15 @@ +package mint + +import sdk "github.com/cosmos/cosmos-sdk/types" + +// expected stake keeper +type StakeKeeper interface { + TotalPower(ctx sdk.Context) sdk.Dec + BondedRatio(ctx sdk.Context) sdk.Dec + InflateSupply(ctx sdk.Context, newTokens sdk.Dec) +} + +// expected fee collection keeper interface +type FeeCollectionKeeper interface { + AddCollectedFees(sdk.Context, sdk.Coins) sdk.Coins +} diff --git a/x/mint/genesis.go b/x/mint/genesis.go new file mode 100644 index 000000000..9dab64628 --- /dev/null +++ b/x/mint/genesis.go @@ -0,0 +1,55 @@ +package mint + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" +) + +// GenesisState - all distribution state that must be provided at genesis +type GenesisState struct { + Minter Minter `json:"Minter"` // minter object + Params Params `json:"params"` // inflation params +} + +func NewGenesisState(minter Minter, params Params) GenesisState { + return GenesisState{ + Minter: minter, + Params: params, + } +} + +// get raw genesis raw message for testing +func DefaultGenesisState() GenesisState { + return GenesisState{ + Minter: InitialMinter(), + Params: DefaultParams(), + } +} + +// new mint genesis +func InitGenesis(ctx sdk.Context, keeper Keeper, data GenesisState) { + keeper.SetMinter(ctx, data.Minter) + keeper.SetParams(ctx, data.Params) +} + +// WriteGenesis returns a GenesisState for a given context and keeper. The +// GenesisState will contain the pool, and validator/delegator distribution info's +func WriteGenesis(ctx sdk.Context, keeper Keeper) GenesisState { + + minter := keeper.GetMinter(ctx) + params := keeper.GetParams(ctx) + return NewGenesisState(minter, params) +} + +// ValidateGenesis validates the provided staking genesis state to ensure the +// expected invariants holds. (i.e. params in correct bounds, no duplicate validators) +func ValidateGenesis(data GenesisState) error { + err := validateParams(data.Params) + if err != nil { + return err + } + err = validateMinter(data.Minter) + if err != nil { + return err + } + return nil +} diff --git a/x/mint/keeper.go b/x/mint/keeper.go new file mode 100644 index 000000000..6e6a642f4 --- /dev/null +++ b/x/mint/keeper.go @@ -0,0 +1,85 @@ +package mint + +import ( + "github.com/cosmos/cosmos-sdk/codec" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/params" +) + +// keeper of the stake store +type Keeper struct { + storeKey sdk.StoreKey + cdc *codec.Codec + paramSpace params.Subspace + sk StakeKeeper + fck FeeCollectionKeeper +} + +func NewKeeper(cdc *codec.Codec, key sdk.StoreKey, + paramSpace params.Subspace, sk StakeKeeper, fck FeeCollectionKeeper) Keeper { + + keeper := Keeper{ + storeKey: key, + cdc: cdc, + paramSpace: paramSpace.WithTypeTable(ParamTypeTable()), + sk: sk, + fck: fck, + } + return keeper +} + +//____________________________________________________________________ +// Keys + +var ( + minterKey = []byte{0x00} // the one key to use for the keeper store + + // params store for inflation params + ParamStoreKeyParams = []byte("params") +) + +// ParamTable for stake module +func ParamTypeTable() params.TypeTable { + return params.NewTypeTable( + ParamStoreKeyParams, Params{}, + ) +} + +const ( + // default paramspace for params keeper + DefaultParamspace = "mint" +) + +//______________________________________________________________________ + +// get the minter +func (k Keeper) GetMinter(ctx sdk.Context) (minter Minter) { + store := ctx.KVStore(k.storeKey) + b := store.Get(minterKey) + if b == nil { + panic("Stored fee pool should not have been nil") + } + k.cdc.MustUnmarshalBinary(b, &minter) + return +} + +// set the minter +func (k Keeper) SetMinter(ctx sdk.Context, minter Minter) { + store := ctx.KVStore(k.storeKey) + b := k.cdc.MustMarshalBinary(minter) + store.Set(minterKey, b) +} + +//______________________________________________________________________ + +// get inflation params from the global param store +func (k Keeper) GetParams(ctx sdk.Context) Params { + var params Params + k.paramSpace.Get(ctx, ParamStoreKeyParams, ¶ms) + return params +} + +// set inflation params from the global param store +func (k Keeper) SetParams(ctx sdk.Context, params Params) { + k.paramSpace.Set(ctx, ParamStoreKeyParams, ¶ms) +} diff --git a/x/mint/minter.go b/x/mint/minter.go new file mode 100644 index 000000000..da2f6c5be --- /dev/null +++ b/x/mint/minter.go @@ -0,0 +1,71 @@ +package mint + +import ( + "fmt" + "time" + + sdk "github.com/cosmos/cosmos-sdk/types" +) + +// current inflation state +type Minter struct { + InflationLastTime time.Time `json:"inflation_last_time"` // block time which the last inflation was processed + Inflation sdk.Dec `json:"inflation"` // current annual inflation rate +} + +// minter object for a new minter +func InitialMinter() Minter { + return Minter{ + InflationLastTime: time.Unix(0, 0), + Inflation: sdk.NewDecWithPrec(13, 2), + } +} + +func validateMinter(minter Minter) error { + if minter.Inflation.LT(sdk.ZeroDec()) { + return fmt.Errorf("mint parameter Inflation should be positive, is %s ", minter.Inflation.String()) + } + if minter.Inflation.GT(sdk.OneDec()) { + return fmt.Errorf("mint parameter Inflation must be <= 1, is %s", minter.Inflation.String()) + } + return nil +} + +var hrsPerYr = sdk.NewDec(8766) // as defined by a julian year of 365.25 days + +// process provisions for an hour period +func (m Minter) ProcessProvisions(params Params, totalSupply, bondedRatio sdk.Dec) ( + minter Minter, provisions sdk.Coin) { + + m.Inflation = m.NextInflation(params, bondedRatio) + provisionsDec := m.Inflation.Mul(totalSupply).Quo(hrsPerYr) + provisions = sdk.NewCoin(params.MintDenom, provisionsDec.TruncateInt()) + return m, provisions +} + +// get the next inflation rate for the hour +func (m Minter) NextInflation(params Params, bondedRatio sdk.Dec) (inflation sdk.Dec) { + + // The target annual inflation rate is recalculated for each previsions cycle. The + // inflation is also subject to a rate change (positive or negative) depending on + // the distance from the desired ratio (67%). The maximum rate change possible is + // defined to be 13% per year, however the annual inflation is capped as between + // 7% and 20%. + + // (1 - bondedRatio/GoalBonded) * InflationRateChange + inflationRateChangePerYear := sdk.OneDec(). + Sub(bondedRatio.Quo(params.GoalBonded)). + Mul(params.InflationRateChange) + inflationRateChange := inflationRateChangePerYear.Quo(hrsPerYr) + + // increase the new annual inflation for this next cycle + inflation = m.Inflation.Add(inflationRateChange) + if inflation.GT(params.InflationMax) { + inflation = params.InflationMax + } + if inflation.LT(params.InflationMin) { + inflation = params.InflationMin + } + + return inflation +} diff --git a/x/mint/minter_test.go b/x/mint/minter_test.go new file mode 100644 index 000000000..b022b0ec8 --- /dev/null +++ b/x/mint/minter_test.go @@ -0,0 +1,53 @@ +package mint + +import ( + "testing" + + "github.com/stretchr/testify/require" + + sdk "github.com/cosmos/cosmos-sdk/types" +) + +func TestNextInflation(t *testing.T) { + minter := InitialMinter() + params := DefaultParams() + + // Governing Mechanism: + // inflationRateChangePerYear = (1- BondedRatio/ GoalBonded) * MaxInflationRateChange + + tests := []struct { + bondedRatio, setInflation, expChange sdk.Dec + }{ + // with 0% bonded atom supply the inflation should increase by InflationRateChange + {sdk.ZeroDec(), sdk.NewDecWithPrec(7, 2), params.InflationRateChange.Quo(hrsPerYr)}, + + // 100% bonded, starting at 20% inflation and being reduced + // (1 - (1/0.67))*(0.13/8667) + {sdk.OneDec(), sdk.NewDecWithPrec(20, 2), + sdk.OneDec().Sub(sdk.OneDec().Quo(params.GoalBonded)).Mul(params.InflationRateChange).Quo(hrsPerYr)}, + + // 50% bonded, starting at 10% inflation and being increased + {sdk.NewDecWithPrec(5, 1), sdk.NewDecWithPrec(10, 2), + sdk.OneDec().Sub(sdk.NewDecWithPrec(5, 1).Quo(params.GoalBonded)).Mul(params.InflationRateChange).Quo(hrsPerYr)}, + + // test 7% minimum stop (testing with 100% bonded) + {sdk.OneDec(), sdk.NewDecWithPrec(7, 2), sdk.ZeroDec()}, + {sdk.OneDec(), sdk.NewDecWithPrec(70001, 6), sdk.NewDecWithPrec(-1, 6)}, + + // test 20% maximum stop (testing with 0% bonded) + {sdk.ZeroDec(), sdk.NewDecWithPrec(20, 2), sdk.ZeroDec()}, + {sdk.ZeroDec(), sdk.NewDecWithPrec(199999, 6), sdk.NewDecWithPrec(1, 6)}, + + // perfect balance shouldn't change inflation + {sdk.NewDecWithPrec(67, 2), sdk.NewDecWithPrec(15, 2), sdk.ZeroDec()}, + } + for i, tc := range tests { + minter.Inflation = tc.setInflation + + inflation := minter.NextInflation(params, tc.bondedRatio) + diffInflation := inflation.Sub(tc.setInflation) + + require.True(t, diffInflation.Equal(tc.expChange), + "Test Index: %v\nDiff: %v\nExpected: %v\n", i, diffInflation, tc.expChange) + } +} diff --git a/x/mint/params.go b/x/mint/params.go new file mode 100644 index 000000000..05edc8fd9 --- /dev/null +++ b/x/mint/params.go @@ -0,0 +1,43 @@ +package mint + +import ( + "fmt" + + sdk "github.com/cosmos/cosmos-sdk/types" +) + +// mint parameters +type Params struct { + MintDenom string `json:"mint_denom"` // type of coin to mint + InflationRateChange sdk.Dec `json:"inflation_rate_change"` // maximum annual change in inflation rate + InflationMax sdk.Dec `json:"inflation_max"` // maximum inflation rate + InflationMin sdk.Dec `json:"inflation_min"` // minimum inflation rate + GoalBonded sdk.Dec `json:"goal_bonded"` // goal of percent bonded atoms +} + +// default minting module parameters +func DefaultParams() Params { + return Params{ + MintDenom: "steak", + InflationRateChange: sdk.NewDecWithPrec(13, 2), + InflationMax: sdk.NewDecWithPrec(20, 2), + InflationMin: sdk.NewDecWithPrec(7, 2), + GoalBonded: sdk.NewDecWithPrec(67, 2), + } +} + +func validateParams(params Params) error { + if params.GoalBonded.LT(sdk.ZeroDec()) { + return fmt.Errorf("mint parameter GoalBonded should be positive, is %s ", params.GoalBonded.String()) + } + if params.GoalBonded.GT(sdk.OneDec()) { + return fmt.Errorf("mint parameter GoalBonded must be <= 1, is %s", params.GoalBonded.String()) + } + if params.InflationMax.LT(params.InflationMin) { + return fmt.Errorf("mint parameter Max inflation must be greater than or equal to min inflation") + } + if params.MintDenom == "" { + return fmt.Errorf("mint parameter MintDenom can't be an empty string") + } + return nil +} diff --git a/x/stake/genesis.go b/x/stake/genesis.go index 6e1e055f2..ff8f59d44 100644 --- a/x/stake/genesis.go +++ b/x/stake/genesis.go @@ -103,20 +103,9 @@ func ValidateGenesis(data types.GenesisState) error { } func validateParams(params types.Params) error { - if params.GoalBonded.LTE(sdk.ZeroDec()) { - bondedPercent := params.GoalBonded.MulInt(sdk.NewInt(100)).String() - return fmt.Errorf("staking parameter GoalBonded should be positive, instead got %s percent", bondedPercent) - } - if params.GoalBonded.GT(sdk.OneDec()) { - bondedPercent := params.GoalBonded.MulInt(sdk.NewInt(100)).String() - return fmt.Errorf("staking parameter GoalBonded should be less than 100 percent, instead got %s percent", bondedPercent) - } if params.BondDenom == "" { return fmt.Errorf("staking parameter BondDenom can't be an empty string") } - if params.InflationMax.LT(params.InflationMin) { - return fmt.Errorf("staking parameter Max inflation must be greater than or equal to min inflation") - } return nil } diff --git a/x/stake/genesis_test.go b/x/stake/genesis_test.go index 2bbd8e97a..7ee16a453 100644 --- a/x/stake/genesis_test.go +++ b/x/stake/genesis_test.go @@ -121,16 +121,6 @@ func TestValidateGenesis(t *testing.T) { wantErr bool }{ {"default", func(*types.GenesisState) {}, false}, - // validate params - {"200% goalbonded", func(data *types.GenesisState) { (*data).Params.GoalBonded = sdk.OneDec().Add(sdk.OneDec()) }, true}, - {"-67% goalbonded", func(data *types.GenesisState) { (*data).Params.GoalBonded = sdk.OneDec().Neg() }, true}, - {"no bond denom", func(data *types.GenesisState) { (*data).Params.BondDenom = "" }, true}, - {"min inflation > max inflation", func(data *types.GenesisState) { - (*data).Params.InflationMin = (*data).Params.InflationMax.Add(sdk.OneDec()) - }, true}, - {"min inflation = max inflation", func(data *types.GenesisState) { - (*data).Params.InflationMax = (*data).Params.InflationMin - }, false}, // validate genesis validators {"duplicate validator", func(data *types.GenesisState) { (*data).Validators = genValidators1 diff --git a/x/stake/handler.go b/x/stake/handler.go index aff38ac6e..a75c30055 100644 --- a/x/stake/handler.go +++ b/x/stake/handler.go @@ -2,7 +2,6 @@ package stake import ( "bytes" - "time" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/x/stake/keeper" @@ -31,7 +30,7 @@ func NewHandler(k keeper.Keeper) sdk.Handler { } } -// Called every block, process inflation, update validator set +// Called every block, update validator set func EndBlocker(ctx sdk.Context, k keeper.Keeper) (ValidatorUpdates []abci.ValidatorUpdate) { endBlockerTags := sdk.EmptyTags() @@ -64,17 +63,6 @@ func EndBlocker(ctx sdk.Context, k keeper.Keeper) (ValidatorUpdates []abci.Valid )) } - pool := k.GetPool(ctx) - - // Process provision inflation - blockTime := ctx.BlockHeader().Time - if blockTime.Sub(pool.InflationLastTime) >= time.Hour { - params := k.GetParams(ctx) - pool.InflationLastTime = blockTime - pool = pool.ProcessProvisions(params) - k.SetPool(ctx, pool) - } - // reset the intra-transaction counter k.SetIntraTxCounter(ctx, 0) diff --git a/x/stake/handler_test.go b/x/stake/handler_test.go index 8755c91b0..3cd81202a 100644 --- a/x/stake/handler_test.go +++ b/x/stake/handler_test.go @@ -85,13 +85,6 @@ func TestValidatorByPowerIndex(t *testing.T) { power2 := GetValidatorsByPowerIndexKey(validator, pool) require.True(t, keep.ValidatorByPowerIndexExists(ctx, keeper, power2)) - // inflate a bunch - params := keeper.GetParams(ctx) - for i := 0; i < 200; i++ { - pool = pool.ProcessProvisions(params) - keeper.SetPool(ctx, pool) - } - // now the new record power index should be the same as the original record power3 := GetValidatorsByPowerIndexKey(validator, pool) require.Equal(t, power2, power3) diff --git a/x/stake/keeper/params.go b/x/stake/keeper/params.go index 294c6dcd5..b62a7688f 100644 --- a/x/stake/keeper/params.go +++ b/x/stake/keeper/params.go @@ -18,30 +18,6 @@ func ParamTypeTable() params.TypeTable { return params.NewTypeTable().RegisterParamSet(&types.Params{}) } -// InflationRateChange - Maximum annual change in inflation rate -func (k Keeper) InflationRateChange(ctx sdk.Context) (res sdk.Dec) { - k.paramstore.Get(ctx, types.KeyInflationRateChange, &res) - return -} - -// InflationMax - Maximum inflation rate -func (k Keeper) InflationMax(ctx sdk.Context) (res sdk.Dec) { - k.paramstore.Get(ctx, types.KeyInflationMax, &res) - return -} - -// InflationMin - Minimum inflation rate -func (k Keeper) InflationMin(ctx sdk.Context) (res sdk.Dec) { - k.paramstore.Get(ctx, types.KeyInflationMin, &res) - return -} - -// GoalBonded - Goal of percent bonded atoms -func (k Keeper) GoalBonded(ctx sdk.Context) (res sdk.Dec) { - k.paramstore.Get(ctx, types.KeyGoalBonded, &res) - return -} - // UnbondingTime func (k Keeper) UnbondingTime(ctx sdk.Context) (res time.Duration) { k.paramstore.Get(ctx, types.KeyUnbondingTime, &res) @@ -62,10 +38,6 @@ func (k Keeper) BondDenom(ctx sdk.Context) (res string) { // Get all parameteras as types.Params func (k Keeper) GetParams(ctx sdk.Context) (res types.Params) { - res.InflationRateChange = k.InflationRateChange(ctx) - res.InflationMax = k.InflationMax(ctx) - res.InflationMin = k.InflationMin(ctx) - res.GoalBonded = k.GoalBonded(ctx) res.UnbondingTime = k.UnbondingTime(ctx) res.MaxValidators = k.MaxValidators(ctx) res.BondDenom = k.BondDenom(ctx) diff --git a/x/stake/keeper/sdk_types.go b/x/stake/keeper/sdk_types.go index d702e845d..667284356 100644 --- a/x/stake/keeper/sdk_types.go +++ b/x/stake/keeper/sdk_types.go @@ -72,6 +72,19 @@ func (k Keeper) TotalPower(ctx sdk.Context) sdk.Dec { return pool.BondedTokens } +// total power from the bond +func (k Keeper) BondedRatio(ctx sdk.Context) sdk.Dec { + pool := k.GetPool(ctx) + return pool.BondedRatio() +} + +// when minting new tokens +func (k Keeper) InflateSupply(ctx sdk.Context, newTokens sdk.Dec) { + pool := k.GetPool(ctx) + pool.LooseTokens = pool.LooseTokens.Add(newTokens) + k.SetPool(ctx, pool) +} + //__________________________________________________________________________ // Implements DelegationSet diff --git a/x/stake/keeper/test_common.go b/x/stake/keeper/test_common.go index aeeaf0c0d..d0ac4a282 100644 --- a/x/stake/keeper/test_common.go +++ b/x/stake/keeper/test_common.go @@ -73,18 +73,6 @@ func MakeTestCodec() *codec.Codec { return cdc } -// default params without inflation -func ParamsNoInflation() types.Params { - return types.Params{ - InflationRateChange: sdk.ZeroDec(), - InflationMax: sdk.ZeroDec(), - InflationMin: sdk.ZeroDec(), - GoalBonded: sdk.NewDecWithPrec(67, 2), - MaxValidators: 100, - BondDenom: "steak", - } -} - // hogpodge of all sorts of input required for testing func CreateTestInput(t *testing.T, isCheckTx bool, initCoins int64) (sdk.Context, auth.AccountMapper, Keeper) { diff --git a/x/stake/simulation/msgs.go b/x/stake/simulation/msgs.go index 76b996363..f2224c865 100644 --- a/x/stake/simulation/msgs.go +++ b/x/stake/simulation/msgs.go @@ -234,8 +234,6 @@ func Setup(mapp *mock.App, k stake.Keeper) simulation.RandSetup { return func(r *rand.Rand, accs []simulation.Account) { ctx := mapp.NewContext(false, abci.Header{}) gen := stake.DefaultGenesisState() - gen.Params.InflationMax = sdk.NewDec(0) - gen.Params.InflationMin = sdk.NewDec(0) stake.InitGenesis(ctx, k, gen) params := k.GetParams(ctx) denom := params.BondDenom diff --git a/x/stake/stake.go b/x/stake/stake.go index 527111d2c..c4aa54702 100644 --- a/x/stake/stake.go +++ b/x/stake/stake.go @@ -58,13 +58,10 @@ var ( GetREDsByDelToValDstIndexKey = keeper.GetREDsByDelToValDstIndexKey TestingUpdateValidator = keeper.TestingUpdateValidator - DefaultParamspace = keeper.DefaultParamspace - KeyInflationRateChange = types.KeyInflationRateChange - KeyInflationMax = types.KeyInflationMax - KeyGoalBonded = types.KeyGoalBonded - KeyUnbondingTime = types.KeyUnbondingTime - KeyMaxValidators = types.KeyMaxValidators - KeyBondDenom = types.KeyBondDenom + DefaultParamspace = keeper.DefaultParamspace + KeyUnbondingTime = types.KeyUnbondingTime + KeyMaxValidators = types.KeyMaxValidators + KeyBondDenom = types.KeyBondDenom DefaultParams = types.DefaultParams InitialPool = types.InitialPool diff --git a/x/stake/types/inflation_test.go b/x/stake/types/inflation_test.go deleted file mode 100644 index 159ecb4c4..000000000 --- a/x/stake/types/inflation_test.go +++ /dev/null @@ -1,145 +0,0 @@ -package types - -import ( - "math/rand" - "testing" - - "github.com/stretchr/testify/require" - - sdk "github.com/cosmos/cosmos-sdk/types" -) - -//changing the int in NewSource will allow you to test different, deterministic, sets of operations -var r = rand.New(rand.NewSource(6595)) - -func TestGetInflation(t *testing.T) { - pool := InitialPool() - params := DefaultParams() - - // Governing Mechanism: - // BondedRatio = BondedTokens / TotalSupply - // inflationRateChangePerYear = (1- BondedRatio/ GoalBonded) * MaxInflationRateChange - - tests := []struct { - name string - setBondedTokens, setLooseTokens, - setInflation, expectedChange sdk.Dec - }{ - // with 0% bonded atom supply the inflation should increase by InflationRateChange - {"test 1", sdk.ZeroDec(), sdk.ZeroDec(), sdk.NewDecWithPrec(7, 2), params.InflationRateChange.Quo(hrsPerYrDec)}, - - // 100% bonded, starting at 20% inflation and being reduced - // (1 - (1/0.67))*(0.13/8667) - {"test 2", sdk.OneDec(), sdk.ZeroDec(), sdk.NewDecWithPrec(20, 2), - sdk.OneDec().Sub(sdk.OneDec().Quo(params.GoalBonded)).Mul(params.InflationRateChange).Quo(hrsPerYrDec)}, - - // 50% bonded, starting at 10% inflation and being increased - {"test 3", sdk.OneDec(), sdk.OneDec(), sdk.NewDecWithPrec(10, 2), - sdk.OneDec().Sub(sdk.NewDecWithPrec(5, 1).Quo(params.GoalBonded)).Mul(params.InflationRateChange).Quo(hrsPerYrDec)}, - - // test 7% minimum stop (testing with 100% bonded) - {"test 4", sdk.OneDec(), sdk.ZeroDec(), sdk.NewDecWithPrec(7, 2), sdk.ZeroDec()}, - {"test 5", sdk.OneDec(), sdk.ZeroDec(), sdk.NewDecWithPrec(70001, 6), sdk.NewDecWithPrec(-1, 6)}, - - // test 20% maximum stop (testing with 0% bonded) - {"test 6", sdk.ZeroDec(), sdk.ZeroDec(), sdk.NewDecWithPrec(20, 2), sdk.ZeroDec()}, - {"test 7", sdk.ZeroDec(), sdk.ZeroDec(), sdk.NewDecWithPrec(199999, 6), sdk.NewDecWithPrec(1, 6)}, - - // perfect balance shouldn't change inflation - {"test 8", sdk.NewDec(67), sdk.NewDec(33), sdk.NewDecWithPrec(15, 2), sdk.ZeroDec()}, - } - for _, tc := range tests { - pool.BondedTokens, pool.LooseTokens = tc.setBondedTokens, tc.setLooseTokens - pool.Inflation = tc.setInflation - - inflation := pool.NextInflation(params) - diffInflation := inflation.Sub(tc.setInflation) - - require.True(t, diffInflation.Equal(tc.expectedChange), - "Name: %v\nDiff: %v\nExpected: %v\n", tc.name, diffInflation, tc.expectedChange) - } -} - -// Test that provisions are correctly added to the pool and validators each hour for 1 year -func TestProcessProvisions(t *testing.T) { - pool := InitialPool() - params := DefaultParams() - - var ( - initialTotalTokens int64 = 550000000 - cumulativeExpProvs = sdk.ZeroDec() - ) - pool.LooseTokens = sdk.NewDec(initialTotalTokens) - - // process the provisions for a year - for hr := 0; hr < 100; hr++ { - var expProvisions sdk.Dec - _, expProvisions, pool = updateProvisions(t, pool, params, hr) - cumulativeExpProvs = cumulativeExpProvs.Add(expProvisions) - } - - //get the pool and do the final value checks from checkFinalPoolValues - checkFinalPoolValues(t, pool, sdk.NewDec(initialTotalTokens), cumulativeExpProvs) -} - -//_________________________________________________________________________________________ -////////////////////////////////HELPER FUNCTIONS BELOW///////////////////////////////////// - -// Final check on the global pool values for what the total tokens accumulated from each hour of provisions -func checkFinalPoolValues(t *testing.T, pool Pool, initialTotalTokens, cumulativeExpProvs sdk.Dec) { - calculatedTotalTokens := initialTotalTokens.Add(cumulativeExpProvs) - require.True(sdk.DecEq(t, calculatedTotalTokens, pool.TokenSupply())) -} - -// Processes provisions are added to the pool correctly every hour -// Returns expected Provisions, expected Inflation, and pool, to help with cumulative calculations back in main Tests -func updateProvisions(t *testing.T, pool Pool, params Params, hr int) (sdk.Dec, sdk.Dec, Pool) { - - expInflation := pool.NextInflation(params) - expProvisions := expInflation.Mul(pool.TokenSupply()).Quo(hrsPerYrDec) - startTotalSupply := pool.TokenSupply() - pool = pool.ProcessProvisions(params) - - //check provisions were added to pool - require.True(sdk.DecEq(t, startTotalSupply.Add(expProvisions), pool.TokenSupply())) - - return expInflation, expProvisions, pool -} - -// Checks that The inflation will correctly increase or decrease after an update to the pool -func checkInflation(t *testing.T, pool Pool, previousInflation, updatedInflation sdk.Dec, msg string) { - inflationChange := updatedInflation.Sub(previousInflation) - - switch { - //BELOW 67% - Rate of change positive and increasing, while we are between 7% <= and < 20% inflation - case pool.BondedRatio().LT(sdk.NewDecWithPrec(67, 2)) && updatedInflation.LT(sdk.NewDecWithPrec(20, 2)): - require.Equal(t, true, inflationChange.GT(sdk.ZeroDec()), msg) - - //BELOW 67% - Rate of change should be 0 while inflation continually stays at 20% until we reach 67% bonded ratio - case pool.BondedRatio().LT(sdk.NewDecWithPrec(67, 2)) && updatedInflation.Equal(sdk.NewDecWithPrec(20, 2)): - if previousInflation.Equal(sdk.NewDecWithPrec(20, 2)) { - require.Equal(t, true, inflationChange.IsZero(), msg) - - //This else statement covers the one off case where we first hit 20%, but we still needed a positive ROC to get to 67% bonded ratio (i.e. we went from 19.99999% to 20%) - } else { - require.Equal(t, true, inflationChange.GT(sdk.ZeroDec()), msg) - } - - //ABOVE 67% - Rate of change should be negative while the bond is above 67, and should stay negative until we reach inflation of 7% - case pool.BondedRatio().GT(sdk.NewDecWithPrec(67, 2)) && - updatedInflation.LT(sdk.NewDecWithPrec(20, 2)) && updatedInflation.GT(sdk.NewDecWithPrec(7, 2)): - require.Equal(t, true, inflationChange.LT(sdk.ZeroDec()), msg) - - //ABOVE 67% - Rate of change should be 0 while inflation continually stays at 7%. - case pool.BondedRatio().GT(sdk.NewDecWithPrec(67, 2)) && - updatedInflation.Equal(sdk.NewDecWithPrec(7, 2)): - - if previousInflation.Equal(sdk.NewDecWithPrec(7, 2)) { - require.Equal(t, true, inflationChange.IsZero(), msg) - - //This else statement covers the one off case where we first hit 7%, but we still needed a negative ROC to continue to get down to 67%. (i.e. we went from 7.00001% to 7%) - } else { - require.Equal(t, true, inflationChange.LT(sdk.ZeroDec()), msg) - } - } -} diff --git a/x/stake/types/params.go b/x/stake/types/params.go index abc1db4d5..5915570c1 100644 --- a/x/stake/types/params.go +++ b/x/stake/types/params.go @@ -6,7 +6,6 @@ import ( "time" "github.com/cosmos/cosmos-sdk/codec" - sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/x/params" ) @@ -24,24 +23,15 @@ const ( // nolint - Keys for parameter access var ( - KeyInflationRateChange = []byte("InflationRateChange") - KeyInflationMax = []byte("InflationMax") - KeyInflationMin = []byte("InflationMin") - KeyGoalBonded = []byte("GoalBonded") - KeyUnbondingTime = []byte("UnbondingTime") - KeyMaxValidators = []byte("MaxValidators") - KeyBondDenom = []byte("BondDenom") + KeyUnbondingTime = []byte("UnbondingTime") + KeyMaxValidators = []byte("MaxValidators") + KeyBondDenom = []byte("BondDenom") ) var _ params.ParamSet = (*Params)(nil) // Params defines the high level settings for staking type Params struct { - InflationRateChange sdk.Dec `json:"inflation_rate_change"` // maximum annual change in inflation rate - InflationMax sdk.Dec `json:"inflation_max"` // maximum inflation rate - InflationMin sdk.Dec `json:"inflation_min"` // minimum inflation rate - GoalBonded sdk.Dec `json:"goal_bonded"` // Goal of percent bonded atoms - UnbondingTime time.Duration `json:"unbonding_time"` MaxValidators uint16 `json:"max_validators"` // maximum number of validators @@ -51,10 +41,6 @@ type Params struct { // Implements params.ParamSet func (p *Params) KeyValuePairs() params.KeyValuePairs { return params.KeyValuePairs{ - {KeyInflationRateChange, &p.InflationRateChange}, - {KeyInflationMax, &p.InflationMax}, - {KeyInflationMin, &p.InflationMin}, - {KeyGoalBonded, &p.GoalBonded}, {KeyUnbondingTime, &p.UnbondingTime}, {KeyMaxValidators, &p.MaxValidators}, {KeyBondDenom, &p.BondDenom}, @@ -71,13 +57,9 @@ func (p Params) Equal(p2 Params) bool { // DefaultParams returns a default set of parameters. func DefaultParams() Params { return Params{ - InflationRateChange: sdk.NewDecWithPrec(13, 2), - InflationMax: sdk.NewDecWithPrec(20, 2), - InflationMin: sdk.NewDecWithPrec(7, 2), - GoalBonded: sdk.NewDecWithPrec(67, 2), - UnbondingTime: defaultUnbondingTime, - MaxValidators: 100, - BondDenom: "steak", + UnbondingTime: defaultUnbondingTime, + MaxValidators: 100, + BondDenom: "steak", } } @@ -85,11 +67,7 @@ func DefaultParams() Params { // parameters. func (p Params) HumanReadableString() string { - resp := "Pool \n" - resp += fmt.Sprintf("Maximum Annual Inflation Rate Change: %s\n", p.InflationRateChange) - resp += fmt.Sprintf("Max Inflation Rate: %s\n", p.InflationMax) - resp += fmt.Sprintf("Min Inflation Tate: %s\n", p.InflationMin) - resp += fmt.Sprintf("Bonded Token Goal (%s): %s\n", "s", p.GoalBonded) + resp := "Params \n" resp += fmt.Sprintf("Unbonding Time: %s\n", p.UnbondingTime) resp += fmt.Sprintf("Max Validators: %d: \n", p.MaxValidators) resp += fmt.Sprintf("Bonded Coin Denomination: %s\n", p.BondDenom) diff --git a/x/stake/types/pool.go b/x/stake/types/pool.go index c7cb69748..e2015dcaf 100644 --- a/x/stake/types/pool.go +++ b/x/stake/types/pool.go @@ -3,7 +3,6 @@ package types import ( "bytes" "fmt" - "time" "github.com/cosmos/cosmos-sdk/codec" sdk "github.com/cosmos/cosmos-sdk/types" @@ -11,15 +10,8 @@ import ( // Pool - dynamic parameters of the current state type Pool struct { - LooseTokens sdk.Dec `json:"loose_tokens"` // tokens which are not bonded in a validator - BondedTokens sdk.Dec `json:"bonded_tokens"` // reserve of bonded tokens - InflationLastTime time.Time `json:"inflation_last_time"` // block which the last inflation was processed - Inflation sdk.Dec `json:"inflation"` // current annual inflation rate - - DateLastCommissionReset int64 `json:"date_last_commission_reset"` // unix timestamp for last commission accounting reset (daily) - - // Fee Related - PrevBondedShares sdk.Dec `json:"prev_bonded_shares"` // last recorded bonded shares - for fee calculations + LooseTokens sdk.Dec `json:"loose_tokens"` // tokens which are not bonded in a validator + BondedTokens sdk.Dec `json:"bonded_tokens"` // reserve of bonded tokens } // nolint @@ -32,12 +24,8 @@ func (p Pool) Equal(p2 Pool) bool { // initial pool for testing func InitialPool() Pool { return Pool{ - LooseTokens: sdk.ZeroDec(), - BondedTokens: sdk.ZeroDec(), - InflationLastTime: time.Unix(0, 0), - Inflation: sdk.NewDecWithPrec(7, 2), - DateLastCommissionReset: 0, - PrevBondedShares: sdk.ZeroDec(), + LooseTokens: sdk.ZeroDec(), + BondedTokens: sdk.ZeroDec(), } } @@ -79,52 +67,6 @@ func (p Pool) bondedTokensToLoose(bondedTokens sdk.Dec) Pool { return p } -//_______________________________________________________________________ -// Inflation - -const precision = 10000 // increased to this precision for accuracy -var hrsPerYrDec = sdk.NewDec(8766) // as defined by a julian year of 365.25 days - -// process provisions for an hour period -func (p Pool) ProcessProvisions(params Params) Pool { - p.Inflation = p.NextInflation(params) - provisions := p.Inflation. - Mul(p.TokenSupply()). - Quo(hrsPerYrDec) - - // TODO add to the fees provisions - p.LooseTokens = p.LooseTokens.Add(provisions) - return p -} - -// get the next inflation rate for the hour -func (p Pool) NextInflation(params Params) (inflation sdk.Dec) { - - // The target annual inflation rate is recalculated for each previsions cycle. The - // inflation is also subject to a rate change (positive or negative) depending on - // the distance from the desired ratio (67%). The maximum rate change possible is - // defined to be 13% per year, however the annual inflation is capped as between - // 7% and 20%. - - // (1 - bondedRatio/GoalBonded) * InflationRateChange - inflationRateChangePerYear := sdk.OneDec(). - Sub(p.BondedRatio(). - Quo(params.GoalBonded)). - Mul(params.InflationRateChange) - inflationRateChange := inflationRateChangePerYear.Quo(hrsPerYrDec) - - // increase the new annual inflation for this next cycle - inflation = p.Inflation.Add(inflationRateChange) - if inflation.GT(params.InflationMax) { - inflation = params.InflationMax - } - if inflation.LT(params.InflationMin) { - inflation = params.InflationMin - } - - return inflation -} - // HumanReadableString returns a human readable string representation of a // pool. func (p Pool) HumanReadableString() string { @@ -134,10 +76,6 @@ func (p Pool) HumanReadableString() string { resp += fmt.Sprintf("Bonded Tokens: %s\n", p.BondedTokens) resp += fmt.Sprintf("Token Supply: %s\n", p.TokenSupply()) resp += fmt.Sprintf("Bonded Ratio: %v\n", p.BondedRatio()) - resp += fmt.Sprintf("Previous Inflation Block: %s\n", p.InflationLastTime) - resp += fmt.Sprintf("Inflation: %v\n", p.Inflation) - resp += fmt.Sprintf("Date of Last Commission Reset: %d\n", p.DateLastCommissionReset) - resp += fmt.Sprintf("Previous Bonded Shares: %v\n", p.PrevBondedShares) return resp } diff --git a/x/stake/types/validator_test.go b/x/stake/types/validator_test.go index 36ac0da57..b01b2b744 100644 --- a/x/stake/types/validator_test.go +++ b/x/stake/types/validator_test.go @@ -3,7 +3,6 @@ package types import ( "fmt" "testing" - "time" "github.com/cosmos/cosmos-sdk/codec" sdk "github.com/cosmos/cosmos-sdk/types" @@ -185,10 +184,8 @@ func TestRemoveDelShares(t *testing.T) { DelegatorShares: delShares, } pool := Pool{ - BondedTokens: sdk.NewDec(248305), - LooseTokens: sdk.NewDec(232147), - InflationLastTime: time.Unix(0, 0), - Inflation: sdk.NewDecWithPrec(7, 2), + BondedTokens: sdk.NewDec(248305), + LooseTokens: sdk.NewDec(232147), } shares := sdk.NewDec(29) _, newPool, tokens := validator.RemoveDelShares(pool, shares) @@ -238,10 +235,8 @@ func TestPossibleOverflow(t *testing.T) { DelegatorShares: delShares, } pool := Pool{ - LooseTokens: sdk.NewDec(100), - BondedTokens: poolTokens, - InflationLastTime: time.Unix(0, 0), - Inflation: sdk.NewDecWithPrec(7, 2), + LooseTokens: sdk.NewDec(100), + BondedTokens: poolTokens, } tokens := int64(71) msg := fmt.Sprintf("validator %#v", validator) From 57277d11431a7cd1de313ef9325446dfcd5a057c Mon Sep 17 00:00:00 2001 From: Federico Kunze <31522760+fedekunze@users.noreply.github.com> Date: Sat, 20 Oct 2018 00:49:10 +0200 Subject: [PATCH 40/40] Merge PR #2537: Staking REST utils refactor and querier tests (pt 1 redelegation PR) --- CHANGELOG.md | 2 +- PENDING.md | 1 + docs/DOCS_README.md | 2 +- x/stake/client/rest/query.go | 231 +++--------------------------- x/stake/client/rest/tx.go | 4 +- x/stake/client/rest/utils.go | 68 +++++++++ x/stake/querier/queryable_test.go | 77 ++++++++++ x/stake/types/delegation.go | 4 +- 8 files changed, 173 insertions(+), 216 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bb5d1e639..d72440bae 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,7 +7,7 @@ BUG FIXES * Tendermint - - Fix unbounded consensus WAL growth + - Fix unbounded consensus WAL growth ## 0.24.1 diff --git a/PENDING.md b/PENDING.md index 76c575368..3a92041c3 100644 --- a/PENDING.md +++ b/PENDING.md @@ -154,6 +154,7 @@ IMPROVEMENTS * [x/stake] [\#2000](https://github.com/cosmos/cosmos-sdk/issues/2000) Added tests for new staking endpoints * [gaia-lite] [\#2445](https://github.com/cosmos/cosmos-sdk/issues/2445) Standarized REST error responses * [gaia-lite] Added example to Swagger specification for /keys/seed. + * [x/stake] Refactor REST utils * Gaia CLI (`gaiacli`) * [cli] #2060 removed `--select` from `block` command diff --git a/docs/DOCS_README.md b/docs/DOCS_README.md index 59a1c9b6d..48a31c2ea 100644 --- a/docs/DOCS_README.md +++ b/docs/DOCS_README.md @@ -112,4 +112,4 @@ much as possible with its [counterpart in the Tendermint Core repo](https://gith 4. Compile gaiacli ``` make install - ``` \ No newline at end of file + ``` diff --git a/x/stake/client/rest/query.go b/x/stake/client/rest/query.go index 1995efcf6..0398119d8 100644 --- a/x/stake/client/rest/query.go +++ b/x/stake/client/rest/query.go @@ -58,74 +58,41 @@ func registerQueryRoutes(cliCtx context.CLIContext, r *mux.Router, cdc *codec.Co // Get all validators r.HandleFunc( "/stake/validators", - validatorsHandlerFn(cliCtx), + validatorsHandlerFn(cliCtx, cdc), ).Methods("GET") // Get a single validator info r.HandleFunc( - "/stake/validators/{addr}", + "/stake/validators/{validatorAddr}", validatorHandlerFn(cliCtx, cdc), ).Methods("GET") // Get the current state of the staking pool r.HandleFunc( "/stake/pool", - poolHandlerFn(cliCtx), + poolHandlerFn(cliCtx, cdc), ).Methods("GET") // Get the current staking parameter values r.HandleFunc( "/stake/parameters", - paramsHandlerFn(cliCtx), + paramsHandlerFn(cliCtx, cdc), ).Methods("GET") } // HTTP request handler to query a delegator delegations func delegatorHandlerFn(cliCtx context.CLIContext, cdc *codec.Codec) http.HandlerFunc { - return func(w http.ResponseWriter, r *http.Request) { - - vars := mux.Vars(r) - bech32delegator := vars["delegatorAddr"] - - w.Header().Set("Content-Type", "application/json") - - delegatorAddr, err := sdk.AccAddressFromBech32(bech32delegator) - if err != nil { - utils.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) - return - } - - params := stake.QueryDelegatorParams{ - DelegatorAddr: delegatorAddr, - } - - bz, err := cdc.MarshalJSON(params) - if err != nil { - utils.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) - return - } - - res, err := cliCtx.QueryWithData("custom/stake/delegator", bz) - if err != nil { - utils.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) - return - } - - w.Write(res) - } + return queryDelegator(cliCtx, cdc, "custom/stake/delegator") } // HTTP request handler to query all staking txs (msgs) from a delegator func delegatorTxsHandlerFn(cliCtx context.CLIContext, cdc *codec.Codec) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { - var output []byte var typesQuerySlice []string vars := mux.Vars(r) delegatorAddr := vars["delegatorAddr"] - w.Header().Set("Content-Type", "application/json") - _, err := sdk.AccAddressFromBech32(delegatorAddr) if err != nil { utils.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) @@ -134,8 +101,7 @@ func delegatorTxsHandlerFn(cliCtx context.CLIContext, cdc *codec.Codec) http.Han node, err := cliCtx.GetNode() if err != nil { - w.WriteHeader(http.StatusInternalServerError) - w.Write([]byte(err.Error())) + utils.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) return } @@ -182,198 +148,52 @@ func delegatorTxsHandlerFn(cliCtx context.CLIContext, cdc *codec.Codec) http.Han txs = append(txs, foundTxs...) } - output, err = cdc.MarshalJSON(txs) + res, err := cdc.MarshalJSON(txs) if err != nil { utils.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) return } - w.Write(output) + utils.PostProcessResponse(w, cdc, res, cliCtx.Indent) } } // HTTP request handler to query an unbonding-delegation func unbondingDelegationHandlerFn(cliCtx context.CLIContext, cdc *codec.Codec) http.HandlerFunc { - return func(w http.ResponseWriter, r *http.Request) { - vars := mux.Vars(r) - bech32delegator := vars["delegatorAddr"] - bech32validator := vars["validatorAddr"] - - w.Header().Set("Content-Type", "application/json") - - delegatorAddr, err := sdk.AccAddressFromBech32(bech32delegator) - if err != nil { - utils.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) - return - } - - validatorAddr, err := sdk.ValAddressFromBech32(bech32validator) - if err != nil { - utils.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) - return - } - - params := stake.QueryBondsParams{ - DelegatorAddr: delegatorAddr, - ValidatorAddr: validatorAddr, - } - - bz, err := cdc.MarshalJSON(params) - if err != nil { - utils.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) - return - } - - res, err := cliCtx.QueryWithData("custom/stake/unbondingDelegation", bz) - if err != nil { - utils.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) - return - } - - w.Write(res) - } + return queryBonds(cliCtx, cdc, "custom/stake/unbondingDelegation") } -// HTTP request handler to query a bonded validator +// HTTP request handler to query a delegation func delegationHandlerFn(cliCtx context.CLIContext, cdc *codec.Codec) http.HandlerFunc { - return func(w http.ResponseWriter, r *http.Request) { - // read parameters - vars := mux.Vars(r) - bech32delegator := vars["delegatorAddr"] - bech32validator := vars["validatorAddr"] - - w.Header().Set("Content-Type", "application/json") - - delegatorAddr, err := sdk.AccAddressFromBech32(bech32delegator) - if err != nil { - utils.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) - return - } - - validatorAddr, err := sdk.ValAddressFromBech32(bech32validator) - if err != nil { - utils.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) - return - } - - params := stake.QueryBondsParams{ - DelegatorAddr: delegatorAddr, - ValidatorAddr: validatorAddr, - } - - bz, err := cdc.MarshalJSON(params) - if err != nil { - utils.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) - return - } - - res, err := cliCtx.QueryWithData("custom/stake/delegation", bz) - if err != nil { - utils.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) - return - } - - w.Write(res) - } + return queryBonds(cliCtx, cdc, "custom/stake/delegation") } // HTTP request handler to query all delegator bonded validators func delegatorValidatorsHandlerFn(cliCtx context.CLIContext, cdc *codec.Codec) http.HandlerFunc { - return func(w http.ResponseWriter, r *http.Request) { - // read parameters - vars := mux.Vars(r) - bech32delegator := vars["delegatorAddr"] - - w.Header().Set("Content-Type", "application/json") - - delegatorAddr, err := sdk.AccAddressFromBech32(bech32delegator) - if err != nil { - utils.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) - return - } - - params := stake.QueryDelegatorParams{ - DelegatorAddr: delegatorAddr, - } - - bz, err := cdc.MarshalJSON(params) - if err != nil { - utils.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) - return - } - - res, err := cliCtx.QueryWithData("custom/stake/delegatorValidators", bz) - if err != nil { - utils.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) - return - } - - w.Write(res) - } + return queryDelegator(cliCtx, cdc, "custom/stake/delegatorValidators") } // HTTP request handler to get information from a currently bonded validator func delegatorValidatorHandlerFn(cliCtx context.CLIContext, cdc *codec.Codec) http.HandlerFunc { - return func(w http.ResponseWriter, r *http.Request) { - - vars := mux.Vars(r) - bech32delegator := vars["delegatorAddr"] - bech32validator := vars["validatorAddr"] - - w.Header().Set("Content-Type", "application/json") - - delegatorAddr, err := sdk.AccAddressFromBech32(bech32delegator) - validatorAddr, err := sdk.ValAddressFromBech32(bech32validator) - if err != nil { - utils.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) - return - } - - params := stake.QueryBondsParams{ - DelegatorAddr: delegatorAddr, - ValidatorAddr: validatorAddr, - } - - bz, err := cdc.MarshalJSON(params) - if err != nil { - utils.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) - return - } - - res, err := cliCtx.QueryWithData("custom/stake/delegatorValidator", bz) - if err != nil { - utils.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) - return - } - - w.Write(res) - } + return queryBonds(cliCtx, cdc, "custom/stake/delegatorValidator") } // HTTP request handler to query list of validators -func validatorsHandlerFn(cliCtx context.CLIContext) http.HandlerFunc { +func validatorsHandlerFn(cliCtx context.CLIContext, cdc *codec.Codec) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { - - w.Header().Set("Content-Type", "application/json") - res, err := cliCtx.QueryWithData("custom/stake/validators", nil) if err != nil { utils.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) return } - - w.Header().Set("Content-Type", "application/json") - w.Write(res) + utils.PostProcessResponse(w, cdc, res, cliCtx.Indent) } } // HTTP request handler to query the validator information from a given validator address func validatorHandlerFn(cliCtx context.CLIContext, cdc *codec.Codec) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { - vars := mux.Vars(r) - bech32validatorAddr := vars["addr"] - - w.Header().Set("Content-Type", "application/json") + bech32validatorAddr := vars["validatorAddr"] validatorAddr, err := sdk.ValAddressFromBech32(bech32validatorAddr) if err != nil { @@ -396,39 +216,30 @@ func validatorHandlerFn(cliCtx context.CLIContext, cdc *codec.Codec) http.Handle utils.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) return } - - w.Write(res) + utils.PostProcessResponse(w, cdc, res, cliCtx.Indent) } } // HTTP request handler to query the pool information -func poolHandlerFn(cliCtx context.CLIContext) http.HandlerFunc { +func poolHandlerFn(cliCtx context.CLIContext, cdc *codec.Codec) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { - - w.Header().Set("Content-Type", "application/json") - res, err := cliCtx.QueryWithData("custom/stake/pool", nil) if err != nil { utils.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) return } - - w.Write(res) + utils.PostProcessResponse(w, cdc, res, cliCtx.Indent) } } // HTTP request handler to query the staking params values -func paramsHandlerFn(cliCtx context.CLIContext) http.HandlerFunc { +func paramsHandlerFn(cliCtx context.CLIContext, cdc *codec.Codec) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { - - w.Header().Set("Content-Type", "application/json") - res, err := cliCtx.QueryWithData("custom/stake/parameters", nil) if err != nil { utils.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) return } - - w.Write(res) + utils.PostProcessResponse(w, cdc, res, cliCtx.Indent) } } diff --git a/x/stake/client/rest/tx.go b/x/stake/client/rest/tx.go index ae991dd4b..e4fd6a1a3 100644 --- a/x/stake/client/rest/tx.go +++ b/x/stake/client/rest/tx.go @@ -262,12 +262,12 @@ func delegationsRequestHandlerFn(cdc *codec.Codec, kb keys.Keybase, cliCtx conte results[i] = res } - output, err := codec.MarshalJSONIndent(cdc, results[:]) + res, err := codec.MarshalJSONIndent(cdc, results[:]) if err != nil { utils.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) return } - w.Write(output) + utils.PostProcessResponse(w, cdc, res, cliCtx.Indent) } } diff --git a/x/stake/client/rest/utils.go b/x/stake/client/rest/utils.go index e7b8891ea..477432032 100644 --- a/x/stake/client/rest/utils.go +++ b/x/stake/client/rest/utils.go @@ -2,11 +2,16 @@ package rest import ( "fmt" + "net/http" "github.com/cosmos/cosmos-sdk/client/context" "github.com/cosmos/cosmos-sdk/client/tx" + "github.com/cosmos/cosmos-sdk/client/utils" "github.com/cosmos/cosmos-sdk/codec" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/stake" "github.com/cosmos/cosmos-sdk/x/stake/tags" + "github.com/gorilla/mux" rpcclient "github.com/tendermint/tendermint/rpc/client" ) @@ -43,3 +48,66 @@ func queryTxs(node rpcclient.Client, cliCtx context.CLIContext, cdc *codec.Codec return tx.FormatTxResults(cdc, res.Txs) } + +func queryBonds(cliCtx context.CLIContext, cdc *codec.Codec, endpoint string) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + vars := mux.Vars(r) + bech32delegator := vars["delegatorAddr"] + bech32validator := vars["validatorAddr"] + + delegatorAddr, err := sdk.AccAddressFromBech32(bech32delegator) + validatorAddr, err := sdk.ValAddressFromBech32(bech32validator) + if err != nil { + utils.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) + return + } + + params := stake.QueryBondsParams{ + DelegatorAddr: delegatorAddr, + ValidatorAddr: validatorAddr, + } + + bz, err := cdc.MarshalJSON(params) + if err != nil { + utils.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) + return + } + + res, err := cliCtx.QueryWithData(endpoint, bz) + if err != nil { + utils.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) + return + } + utils.PostProcessResponse(w, cdc, res, cliCtx.Indent) + } +} + +func queryDelegator(cliCtx context.CLIContext, cdc *codec.Codec, endpoint string) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + vars := mux.Vars(r) + bech32delegator := vars["delegatorAddr"] + + delegatorAddr, err := sdk.AccAddressFromBech32(bech32delegator) + if err != nil { + utils.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) + return + } + + params := stake.QueryDelegatorParams{ + DelegatorAddr: delegatorAddr, + } + + bz, err := cdc.MarshalJSON(params) + if err != nil { + utils.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) + return + } + + res, err := cliCtx.QueryWithData(endpoint, bz) + if err != nil { + utils.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) + return + } + utils.PostProcessResponse(w, cdc, res, cliCtx.Indent) + } +} diff --git a/x/stake/querier/queryable_test.go b/x/stake/querier/queryable_test.go index d950f90bf..1d76da90d 100644 --- a/x/stake/querier/queryable_test.go +++ b/x/stake/querier/queryable_test.go @@ -36,6 +36,53 @@ func newTestBondQuery(delegatorAddr sdk.AccAddress, validatorAddr sdk.ValAddress } } +func TestNewQuerier(t *testing.T) { + cdc := codec.New() + ctx, _, keeper := keep.CreateTestInput(t, false, 1000) + pool := keeper.GetPool(ctx) + // Create Validators + amts := []sdk.Int{sdk.NewInt(9), sdk.NewInt(8)} + var validators [2]types.Validator + for i, amt := range amts { + validators[i] = types.NewValidator(sdk.ValAddress(keep.Addrs[i]), keep.PKs[i], types.Description{}) + validators[i], pool, _ = validators[i].AddTokensFromDel(pool, amt) + validators[i].BondIntraTxCounter = int16(i) + keeper.SetValidator(ctx, validators[i]) + keeper.SetValidatorByPowerIndex(ctx, validators[i], pool) + } + keeper.SetPool(ctx, pool) + + query := abci.RequestQuery{ + Path: "", + Data: []byte{}, + } + + querier := NewQuerier(keeper, cdc) + + bz, err := querier(ctx, []string{"other"}, query) + require.NotNil(t, err) + require.Nil(t, bz) + + _, err = querier(ctx, []string{"validators"}, query) + require.Nil(t, err) + + _, err = querier(ctx, []string{"pool"}, query) + require.Nil(t, err) + + _, err = querier(ctx, []string{"parameters"}, query) + require.Nil(t, err) + + queryParams := newTestValidatorQuery(addrVal1) + bz, errRes := cdc.MarshalJSON(queryParams) + require.Nil(t, errRes) + + query.Path = "/custom/stake/validator" + query.Data = bz + + _, err = querier(ctx, []string{"validator"}, query) + require.Nil(t, err) +} + func TestQueryParametersPool(t *testing.T) { cdc := codec.New() ctx, _, keeper := keep.CreateTestInput(t, false, 1000) @@ -144,6 +191,12 @@ func TestQueryDelegation(t *testing.T) { require.Equal(t, len(delValidators), len(validatorsResp)) require.ElementsMatch(t, delValidators, validatorsResp) + // error unknown request + query.Data = bz[:len(bz)-1] + + _, err = queryDelegatorValidators(ctx, cdc, query, keeper) + require.NotNil(t, err) + // Query bonded validator queryBondParams := newTestBondQuery(addrAcc2, addrVal1) bz, errRes = cdc.MarshalJSON(queryBondParams) @@ -163,6 +216,12 @@ func TestQueryDelegation(t *testing.T) { require.Equal(t, delValidators[0], validator) + // error unknown request + query.Data = bz[:len(bz)-1] + + _, err = queryDelegatorValidator(ctx, cdc, query, keeper) + require.NotNil(t, err) + // Query delegation query = abci.RequestQuery{ @@ -182,6 +241,12 @@ func TestQueryDelegation(t *testing.T) { require.Equal(t, delegation, delegationRes) + // error unknown request + query.Data = bz[:len(bz)-1] + + _, err = queryDelegation(ctx, cdc, query, keeper) + require.NotNil(t, err) + // Query unbonging delegation keeper.BeginUnbonding(ctx, addrAcc2, val1.OperatorAddr, sdk.NewDec(10)) @@ -202,6 +267,12 @@ func TestQueryDelegation(t *testing.T) { require.Equal(t, unbond, unbondRes) + // error unknown request + query.Data = bz[:len(bz)-1] + + _, err = queryUnbondingDelegation(ctx, cdc, query, keeper) + require.NotNil(t, err) + // Query Delegator Summary query = abci.RequestQuery{ @@ -217,4 +288,10 @@ func TestQueryDelegation(t *testing.T) { require.Nil(t, errRes) require.Equal(t, unbond, summary.UnbondingDelegations[0]) + + // error unknown request + query.Data = bz[:len(bz)-1] + + _, err = queryDelegator(ctx, cdc, query, keeper) + require.NotNil(t, err) } diff --git a/x/stake/types/delegation.go b/x/stake/types/delegation.go index d38911521..1b51e00ac 100644 --- a/x/stake/types/delegation.go +++ b/x/stake/types/delegation.go @@ -115,7 +115,7 @@ func (d Delegation) HumanReadableString() (string, error) { resp := "Delegation \n" resp += fmt.Sprintf("Delegator: %s\n", d.DelegatorAddr) resp += fmt.Sprintf("Validator: %s\n", d.ValidatorAddr) - resp += fmt.Sprintf("Shares: %s", d.Shares.String()) + resp += fmt.Sprintf("Shares: %s\n", d.Shares.String()) resp += fmt.Sprintf("Height: %d", d.Height) return resp, nil @@ -297,7 +297,7 @@ func (d Redelegation) HumanReadableString() (string, error) { resp += fmt.Sprintf("Destination Validator: %s\n", d.ValidatorDstAddr) resp += fmt.Sprintf("Creation height: %v\n", d.CreationHeight) resp += fmt.Sprintf("Min time to unbond (unix): %v\n", d.MinTime) - resp += fmt.Sprintf("Source shares: %s", d.SharesSrc.String()) + resp += fmt.Sprintf("Source shares: %s\n", d.SharesSrc.String()) resp += fmt.Sprintf("Destination shares: %s", d.SharesDst.String()) return resp, nil