From 725ed7969accb44e7d6004169ed7ede46d6d53df Mon Sep 17 00:00:00 2001 From: Ismail Khoffi Date: Thu, 29 Nov 2018 23:03:41 +0100 Subject: [PATCH 01/12] Add some ProposerPriority tests (#2946) * WIP: tests for #2785 * rebase onto develop * add Bucky's test without changing ValidatorSet.Update * make TestValidatorSetBasic fail * add ProposerPriority preserving fix to ValidatorSet.Update to fix TestValidatorSetBasic * fix randValidator_ to stay in bounds of MaxTotalVotingPower * check for expected proposer and remove some duplicate code * actually limit the voting power of random validator ... * fix test --- types/validator_set_test.go | 186 ++++++++++++++++++++++++++++++++++-- 1 file changed, 179 insertions(+), 7 deletions(-) diff --git a/types/validator_set_test.go b/types/validator_set_test.go index f0e41a1d..26793cc1 100644 --- a/types/validator_set_test.go +++ b/types/validator_set_test.go @@ -45,7 +45,8 @@ func TestValidatorSetBasic(t *testing.T) { assert.Nil(t, vset.Hash()) // add - val = randValidator_() + + val = randValidator_(vset.TotalVotingPower()) assert.True(t, vset.Add(val)) assert.True(t, vset.HasAddress(val.Address)) idx, val2 := vset.GetByAddress(val.Address) @@ -61,7 +62,7 @@ func TestValidatorSetBasic(t *testing.T) { assert.NotPanics(t, func() { vset.IncrementProposerPriority(1) }) // update - assert.False(t, vset.Update(randValidator_())) + assert.False(t, vset.Update(randValidator_(vset.TotalVotingPower()))) _, val = vset.GetByAddress(val.Address) val.VotingPower += 100 proposerPriority := val.ProposerPriority @@ -73,7 +74,7 @@ func TestValidatorSetBasic(t *testing.T) { assert.Equal(t, proposerPriority, val.ProposerPriority) // remove - val2, removed := vset.Remove(randValidator_().Address) + val2, removed := vset.Remove(randValidator_(vset.TotalVotingPower()).Address) assert.Nil(t, val2) assert.False(t, removed) val2, removed = vset.Remove(val.Address) @@ -280,16 +281,20 @@ func randPubKey() crypto.PubKey { return ed25519.PubKeyEd25519(pubKey) } -func randValidator_() *Validator { - val := NewValidator(randPubKey(), cmn.RandInt64()) - val.ProposerPriority = cmn.RandInt64() % MaxTotalVotingPower +func randValidator_(totalVotingPower int64) *Validator { + // this modulo limits the ProposerPriority/VotingPower to stay in the + // bounds of MaxTotalVotingPower minus the already existing voting power: + val := NewValidator(randPubKey(), cmn.RandInt64()%(MaxTotalVotingPower-totalVotingPower)) + val.ProposerPriority = cmn.RandInt64() % (MaxTotalVotingPower - totalVotingPower) return val } func randValidatorSet(numValidators int) *ValidatorSet { validators := make([]*Validator, numValidators) + totalVotingPower := int64(0) for i := 0; i < numValidators; i++ { - validators[i] = randValidator_() + validators[i] = randValidator_(totalVotingPower) + totalVotingPower += validators[i].VotingPower } return NewValidatorSet(validators) } @@ -342,7 +347,174 @@ func TestAvgProposerPriority(t *testing.T) { got := tc.vs.computeAvgProposerPriority() assert.Equal(t, tc.want, got, "test case: %v", i) } +} +func TestAveragingInIncrementProposerPriority(t *testing.T) { + // Test that the averaging works as expected inside of IncrementProposerPriority. + // Each validator comes with zero voting power which simplifies reasoning about + // the expected ProposerPriority. + tcs := []struct { + vs ValidatorSet + times int + avg int64 + }{ + 0: {ValidatorSet{ + Validators: []*Validator{ + {Address: []byte("a"), ProposerPriority: 1}, + {Address: []byte("b"), ProposerPriority: 2}, + {Address: []byte("c"), ProposerPriority: 3}}}, + 1, 2}, + 1: {ValidatorSet{ + Validators: []*Validator{ + {Address: []byte("a"), ProposerPriority: 10}, + {Address: []byte("b"), ProposerPriority: -10}, + {Address: []byte("c"), ProposerPriority: 1}}}, + // this should average twice but the average should be 0 after the first iteration + // (voting power is 0 -> no changes) + 11, 1 / 3}, + 2: {ValidatorSet{ + Validators: []*Validator{ + {Address: []byte("a"), ProposerPriority: 100}, + {Address: []byte("b"), ProposerPriority: -10}, + {Address: []byte("c"), ProposerPriority: 1}}}, + 1, 91 / 3}, + } + for i, tc := range tcs { + // work on copy to have the old ProposerPriorities: + newVset := tc.vs.CopyIncrementProposerPriority(tc.times) + for _, val := range tc.vs.Validators { + _, updatedVal := newVset.GetByAddress(val.Address) + assert.Equal(t, updatedVal.ProposerPriority, val.ProposerPriority-tc.avg, "test case: %v", i) + } + } +} + +func TestAveragingInIncrementProposerPriorityWithVotingPower(t *testing.T) { + // Other than TestAveragingInIncrementProposerPriority this is a more complete test showing + // how each ProposerPriority changes in relation to the validator's voting power respectively. + vals := ValidatorSet{Validators: []*Validator{ + {Address: []byte{0}, ProposerPriority: 0, VotingPower: 10}, + {Address: []byte{1}, ProposerPriority: 0, VotingPower: 1}, + {Address: []byte{2}, ProposerPriority: 0, VotingPower: 1}}} + tcs := []struct { + vals *ValidatorSet + wantProposerPrioritys []int64 + times int + wantProposer *Validator + }{ + + 0: { + vals.Copy(), + []int64{ + // Acumm+VotingPower-Avg: + 0 + 10 - 12 - 4, // mostest will be subtracted by total voting power (12) + 0 + 1 - 4, + 0 + 1 - 4}, + 1, + vals.Validators[0]}, + 1: { + vals.Copy(), + []int64{ + (0 + 10 - 12 - 4) + 10 - 12 + 4, // this will be mostest on 2nd iter, too + (0 + 1 - 4) + 1 + 4, + (0 + 1 - 4) + 1 + 4}, + 2, + vals.Validators[0]}, // increment twice -> expect average to be subtracted twice + 2: { + vals.Copy(), + []int64{ + ((0 + 10 - 12 - 4) + 10 - 12) + 10 - 12 + 4, // still mostest + ((0 + 1 - 4) + 1) + 1 + 4, + ((0 + 1 - 4) + 1) + 1 + 4}, + 3, + vals.Validators[0]}, + 3: { + vals.Copy(), + []int64{ + 0 + 4*(10-12) + 4 - 4, // still mostest + 0 + 4*1 + 4 - 4, + 0 + 4*1 + 4 - 4}, + 4, + vals.Validators[0]}, + 4: { + vals.Copy(), + []int64{ + 0 + 4*(10-12) + 10 + 4 - 4, // 4 iters was mostest + 0 + 5*1 - 12 + 4 - 4, // now this val is mostest for the 1st time (hence -12==totalVotingPower) + 0 + 5*1 + 4 - 4}, + 5, + vals.Validators[1]}, + 5: { + vals.Copy(), + []int64{ + 0 + 6*10 - 5*12 + 4 - 4, // mostest again + 0 + 6*1 - 12 + 4 - 4, // mostest once up to here + 0 + 6*1 + 4 - 4}, + 6, + vals.Validators[0]}, + 6: { + vals.Copy(), + []int64{ + 0 + 7*10 - 6*12 + 4 - 4, // in 7 iters this val is mostest 6 times + 0 + 7*1 - 12 + 4 - 4, // in 7 iters this val is mostest 1 time + 0 + 7*1 + 4 - 4}, + 7, + vals.Validators[0]}, + 7: { + vals.Copy(), + []int64{ + 0 + 8*10 - 7*12 + 4 - 4, // mostest + 0 + 8*1 - 12 + 4 - 4, + 0 + 8*1 + 4 - 4}, + 8, + vals.Validators[0]}, + 8: { + vals.Copy(), + []int64{ + 0 + 9*10 - 7*12 + 4 - 4, + 0 + 9*1 - 12 + 4 - 4, + 0 + 9*1 - 12 + 4 - 4}, // mostest + 9, + vals.Validators[2]}, + 9: { + vals.Copy(), + []int64{ + 0 + 10*10 - 8*12 + 4 - 4, // after 10 iters this is mostest again + 0 + 10*1 - 12 + 4 - 4, // after 6 iters this val is "mostest" once and not in between + 0 + 10*1 - 12 + 4 - 4}, // in between 10 iters this val is "mostest" once + 10, + vals.Validators[0]}, + 10: { + vals.Copy(), + []int64{ + // shift twice inside incrementProposerPriority (shift every 10th iter); + // don't shift at the end of IncremenctProposerPriority + // last avg should be zero because + // ProposerPriority of validator 0: (0 + 11*10 - 8*12 - 4) == 10 + // ProposerPriority of validator 1 and 2: (0 + 11*1 - 12 - 4) == -5 + // and (10 + 5 - 5) / 3 == 0 + 0 + 11*10 - 8*12 - 4 - 12 - 0, + 0 + 11*1 - 12 - 4 - 0, // after 6 iters this val is "mostest" once and not in between + 0 + 11*1 - 12 - 4 - 0}, // after 10 iters this val is "mostest" once + 11, + vals.Validators[0]}, + } + for i, tc := range tcs { + tc.vals.IncrementProposerPriority(tc.times) + + assert.Equal(t, tc.wantProposer.Address, tc.vals.GetProposer().Address, + "test case: %v", + i) + + for valIdx, val := range tc.vals.Validators { + assert.Equal(t, + tc.wantProposerPrioritys[valIdx], + val.ProposerPriority, + "test case: %v, validator: %v", + i, + valIdx) + } + } } func TestSafeAdd(t *testing.T) { From dc2a338d96ed7129dd7f8ef03e7a9d1423c5c76e Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Fri, 30 Nov 2018 14:05:16 -0500 Subject: [PATCH 02/12] Bucky/v0.27.0 (#2950) * update changelog * changelog, upgrading, version --- CHANGELOG.md | 62 +++++++++++++++++++++++++++++++++++++++++--- CHANGELOG_PENDING.md | 27 +------------------ UPGRADING.md | 29 ++++++++++++++++++++- version/version.go | 6 ++--- 4 files changed, 91 insertions(+), 33 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c506a229..ce6f2f6d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,13 +1,69 @@ # Changelog +## v0.27.0 + +*November 29th, 2018* + +Special thanks to external contributors on this release: +@danil-lashin, @srmo + +Special thanks to @dlguddus for discovering a [major +issue](https://github.com/tendermint/tendermint/issues/2718#issuecomment-440888677) +in the proposer selection algorithm. + +Friendly reminder, we have a [bug bounty +program](https://hackerone.com/tendermint). + +This release is primarily about fixes to the proposer selection algorithm +in preparation for the [Cosmos Game of +Stakes](https://blog.cosmos.network/the-game-of-stakes-is-open-for-registration-83a404746ee6). +It also makes use of the `ConsensusParams.Validator.PubKeyTypes` to restrict the +key types that can be used by validators. + +### BREAKING CHANGES: + +* CLI/RPC/Config + - [rpc] [\#2932](https://github.com/tendermint/tendermint/issues/2932) Rename `accum` to `proposer_priority` + +* Go API + - [db] [\#2913](https://github.com/tendermint/tendermint/pull/2913) + ReverseIterator API change: start < end, and end is exclusive. + - [types] [\#2932](https://github.com/tendermint/tendermint/issues/2932) Rename `Validator.Accum` to `Validator.ProposerPriority` + +* Blockchain Protocol + - [state] [\#2714](https://github.com/tendermint/tendermint/issues/2714) Validators can now only use pubkeys allowed within + ConsensusParams.Validator.PubKeyTypes + +* P2P Protocol + - [consensus] [\#2871](https://github.com/tendermint/tendermint/issues/2871) + Remove *ProposalHeartbeat* message as it serves no real purpose (@srmo) + - [state] Fixes for proposer selection: + - [\#2785](https://github.com/tendermint/tendermint/issues/2785) Accum for new validators is `-1.125*totalVotingPower` instead of 0 + - [\#2941](https://github.com/tendermint/tendermint/issues/2941) val.Accum is preserved during ValidatorSet.Update to avoid being + reset to 0 + +### IMPROVEMENTS: + +- [state] [\#2929](https://github.com/tendermint/tendermint/issues/2929) Minor refactor of updateState logic (@danil-lashin) + +### BUG FIXES: + +- [types] [\#2938](https://github.com/tendermint/tendermint/issues/2938) Fix regression in v0.26.4 where we panic on empty + genDoc.Validators +- [state] [\#2785](https://github.com/tendermint/tendermint/issues/2785) Fix accum for new validators to be `-1.125*totalVotingPower` + instead of 0, forcing them to wait before becoming the proposer. Also: + - do not batch clip + - keep accums averaged near 0 +- [types] [\#2941](https://github.com/tendermint/tendermint/issues/2941) Preserve val.Accum during ValidatorSet.Update to avoid it being + reset to 0 every time a validator is updated + ## v0.26.4 *November 27th, 2018* Special thanks to external contributors on this release: -ackratos, goolAdapter, james-ray, joe-bowman, kostko, -nagarajmanjunath, tomtau - +@ackratos, @goolAdapter, @james-ray, @joe-bowman, @kostko, +@nagarajmanjunath, @tomtau Friendly reminder, we have a [bug bounty program](https://hackerone.com/tendermint). diff --git a/CHANGELOG_PENDING.md b/CHANGELOG_PENDING.md index b9a5454c..7063efc3 100644 --- a/CHANGELOG_PENDING.md +++ b/CHANGELOG_PENDING.md @@ -1,48 +1,23 @@ -# Pending - -## v0.27.0 +## v0.27.1 *TBD* Special thanks to external contributors on this release: -Friendly reminder, we have a [bug bounty -program](https://hackerone.com/tendermint). - ### BREAKING CHANGES: * CLI/RPC/Config - - [rpc] \#2932 Rename `accum` to `proposer_priority` * Apps * Go API - - [db] [\#2913](https://github.com/tendermint/tendermint/pull/2913) - ReverseIterator API change -- start < end, and end is exclusive. - - [types] \#2932 Rename `Validator.Accum` to `Validator.ProposerPriority` * Blockchain Protocol - - [state] \#2714 Validators can now only use pubkeys allowed within - ConsensusParams.ValidatorParams * P2P Protocol - - [consensus] [\#2871](https://github.com/tendermint/tendermint/issues/2871) - Remove *ProposalHeartbeat* message as it serves no real purpose - - [state] Fixes for proposer selection: - - \#2785 Accum for new validators is `-1.125*totalVotingPower` instead of 0 - - \#2941 val.Accum is preserved during ValidatorSet.Update to avoid being - reset to 0 ### FEATURES: ### IMPROVEMENTS: ### BUG FIXES: -- [types] \#2938 Fix regression in v0.26.4 where we panic on empty - genDoc.Validators -- [state] \#2785 Fix accum for new validators to be `-1.125*totalVotingPower` - instead of 0, forcing them to wait before becoming the proposer. Also: - - do not batch clip - - keep accums averaged near 0 -- [types] \#2941 Preserve val.Accum during ValidatorSet.Update to avoid it being - reset to 0 every time a validator is updated diff --git a/UPGRADING.md b/UPGRADING.md index 055dbec4..f55058a8 100644 --- a/UPGRADING.md +++ b/UPGRADING.md @@ -3,6 +3,33 @@ This guide provides steps to be followed when you upgrade your applications to a newer version of Tendermint Core. +## v0.27.0 + +### Go API Changes + +#### libs/db + +The ReverseIterator API has changed the meaning of `start` and `end`. +Before, iteration was from `start` to `end`, where +`start > end`. Now, iteration is from `end` to `start`, where `start < end`. +The iterator also excludes `end`. This change allows a simplified and more +intuitive logic, aligning the semantic meaning of `start` and `end` in the +`Iterator` and `ReverseIterator`. + +### Applications + +This release enforces a new consensus parameter, the +ValidatorParams.PubKeyTypes. Applications must ensure that they only return +validator updates with the allowed PubKeyTypes. If a validator update includes a +pubkey type that is not included in the ConsensusParams.Validator.PubKeyTypes, +block execution will fail and the consensus will halt. + +By default, only Ed25519 pubkeys may be used for validators. Enabling +Secp256k1 requires explicit modification of the ConsensusParams. +Please update your application accordingly (ie. restrict validators to only be +able to use Ed25519 keys, or explicitly add additional key types to the genesis +file). + ## v0.26.0 New 0.26.0 release contains a lot of changes to core data types and protocols. It is not @@ -67,7 +94,7 @@ For more information, see: ### Go API Changes -#### crypto.merkle +#### crypto/merkle The `merkle.Hasher` interface was removed. Functions which used to take `Hasher` now simply take `[]byte`. This means that any objects being Merklized should be diff --git a/version/version.go b/version/version.go index 933328a6..921e1430 100644 --- a/version/version.go +++ b/version/version.go @@ -18,7 +18,7 @@ const ( // TMCoreSemVer is the current version of Tendermint Core. // It's the Semantic Version of the software. // Must be a string because scripts like dist.sh read this file. - TMCoreSemVer = "0.26.4" + TMCoreSemVer = "0.27.0" // ABCISemVer is the semantic version of the ABCI library ABCISemVer = "0.15.0" @@ -36,10 +36,10 @@ func (p Protocol) Uint64() uint64 { var ( // P2PProtocol versions all p2p behaviour and msgs. - P2PProtocol Protocol = 4 + P2PProtocol Protocol = 5 // BlockProtocol versions all block data structures and processing. - BlockProtocol Protocol = 7 + BlockProtocol Protocol = 8 ) //------------------------------------------------------------------------ From c4d93fd27b5b2b9785f47a71edf5eba569411a72 Mon Sep 17 00:00:00 2001 From: Ismail Khoffi Date: Fri, 30 Nov 2018 20:43:16 +0100 Subject: [PATCH 03/12] explicitly type MaxTotalVotingPower to int64 (#2953) --- types/validator_set.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/types/validator_set.go b/types/validator_set.go index c62261d2..8b2d71b8 100644 --- a/types/validator_set.go +++ b/types/validator_set.go @@ -19,7 +19,7 @@ import ( // x + (x >> 3) = x + x/8 = x * (1 + 0.125). // MaxTotalVotingPower is the largest int64 `x` with the property that `x + (x >> 3)` is // still in the bounds of int64. -const MaxTotalVotingPower = 8198552921648689607 +const MaxTotalVotingPower = int64(8198552921648689607) // ValidatorSet represent a set of *Validator at a given height. // The validators can be fetched by address or index. From 8ef0c2681d2a20e45b056baf1efb40cf89bfa3df Mon Sep 17 00:00:00 2001 From: Anton Kaliaev Date: Mon, 3 Dec 2018 16:15:36 +0400 Subject: [PATCH 04/12] check if deliverTxResCh is still open, return an err otherwise (#2947) deliverTxResCh, like any other eventBus (pubsub) channel, is closed when eventBus is stopped. We must check if the channel is still open. The alternative approach is to not close any channels, which seems a bit odd. Fixes #2408 --- CHANGELOG_PENDING.md | 1 + rpc/core/mempool.go | 5 ++++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG_PENDING.md b/CHANGELOG_PENDING.md index 7063efc3..3c26f213 100644 --- a/CHANGELOG_PENDING.md +++ b/CHANGELOG_PENDING.md @@ -21,3 +21,4 @@ Special thanks to external contributors on this release: ### IMPROVEMENTS: ### BUG FIXES: +- [rpc] \#2408 `/broadcast_tx_commit`: Fix "interface conversion: interface {} in nil, not EventDataTx" panic (could happen if somebody sent a tx using /broadcast_tx_commit while Tendermint was being stopped) \ No newline at end of file diff --git a/rpc/core/mempool.go b/rpc/core/mempool.go index 7b3c368a..2e32790b 100644 --- a/rpc/core/mempool.go +++ b/rpc/core/mempool.go @@ -198,7 +198,10 @@ func BroadcastTxCommit(tx types.Tx) (*ctypes.ResultBroadcastTxCommit, error) { // TODO: configurable? var deliverTxTimeout = rpcserver.WriteTimeout / 2 select { - case deliverTxResMsg := <-deliverTxResCh: // The tx was included in a block. + case deliverTxResMsg, ok := <-deliverTxResCh: // The tx was included in a block. + if !ok { + return nil, errors.New("Error on broadcastTxCommit: expected DeliverTxResult, got nil. Did the Tendermint stop?") + } deliverTxRes := deliverTxResMsg.(types.EventDataTx) return &ctypes.ResultBroadcastTxCommit{ CheckTx: *checkTxRes, From d9a1aad5c559ebd4b3382ad54290ecb16079b7ea Mon Sep 17 00:00:00 2001 From: Anton Kaliaev Date: Mon, 3 Dec 2018 16:17:06 +0400 Subject: [PATCH 05/12] docs: add client#Start/Stop to examples in RPC docs (#2939) follow-up on https://github.com/tendermint/tendermint/pull/2936 --- rpc/core/abci.go | 10 ++++++++++ rpc/core/blocks.go | 20 ++++++++++++++++++++ rpc/core/consensus.go | 20 ++++++++++++++++++++ rpc/core/events.go | 12 ++++++++++++ rpc/core/health.go | 5 +++++ rpc/core/mempool.go | 25 +++++++++++++++++++++++++ rpc/core/net.go | 10 ++++++++++ rpc/core/status.go | 5 +++++ rpc/core/tx.go | 10 ++++++++++ 9 files changed, 117 insertions(+) diff --git a/rpc/core/abci.go b/rpc/core/abci.go index 2468a5f0..c9d516f9 100644 --- a/rpc/core/abci.go +++ b/rpc/core/abci.go @@ -15,6 +15,11 @@ import ( // // ```go // client := client.NewHTTP("tcp://0.0.0.0:26657", "/websocket") +// err := client.Start() +// if err != nil { +// // handle error +// } +// defer client.Stop() // result, err := client.ABCIQuery("", "abcd", true) // ``` // @@ -69,6 +74,11 @@ func ABCIQuery(path string, data cmn.HexBytes, height int64, prove bool) (*ctype // // ```go // client := client.NewHTTP("tcp://0.0.0.0:26657", "/websocket") +// err := client.Start() +// if err != nil { +// // handle error +// } +// defer client.Stop() // info, err := client.ABCIInfo() // ``` // diff --git a/rpc/core/blocks.go b/rpc/core/blocks.go index a9252f55..ee4009e5 100644 --- a/rpc/core/blocks.go +++ b/rpc/core/blocks.go @@ -18,6 +18,11 @@ import ( // // ```go // client := client.NewHTTP("tcp://0.0.0.0:26657", "/websocket") +// err := client.Start() +// if err != nil { +// // handle error +// } +// defer client.Stop() // info, err := client.BlockchainInfo(10, 10) // ``` // @@ -123,6 +128,11 @@ func filterMinMax(height, min, max, limit int64) (int64, int64, error) { // // ```go // client := client.NewHTTP("tcp://0.0.0.0:26657", "/websocket") +// err := client.Start() +// if err != nil { +// // handle error +// } +// defer client.Stop() // info, err := client.Block(10) // ``` // @@ -235,6 +245,11 @@ func Block(heightPtr *int64) (*ctypes.ResultBlock, error) { // // ```go // client := client.NewHTTP("tcp://0.0.0.0:26657", "/websocket") +// err := client.Start() +// if err != nil { +// // handle error +// } +// defer client.Stop() // info, err := client.Commit(11) // ``` // @@ -329,6 +344,11 @@ func Commit(heightPtr *int64) (*ctypes.ResultCommit, error) { // // ```go // client := client.NewHTTP("tcp://0.0.0.0:26657", "/websocket") +// err := client.Start() +// if err != nil { +// // handle error +// } +// defer client.Stop() // info, err := client.BlockResults(10) // ``` // diff --git a/rpc/core/consensus.go b/rpc/core/consensus.go index 63a4dfe0..9968a1b2 100644 --- a/rpc/core/consensus.go +++ b/rpc/core/consensus.go @@ -16,6 +16,11 @@ import ( // // ```go // client := client.NewHTTP("tcp://0.0.0.0:26657", "/websocket") +// err := client.Start() +// if err != nil { +// // handle error +// } +// defer client.Stop() // state, err := client.Validators() // ``` // @@ -67,6 +72,11 @@ func Validators(heightPtr *int64) (*ctypes.ResultValidators, error) { // // ```go // client := client.NewHTTP("tcp://0.0.0.0:26657", "/websocket") +// err := client.Start() +// if err != nil { +// // handle error +// } +// defer client.Stop() // state, err := client.DumpConsensusState() // ``` // @@ -225,6 +235,11 @@ func DumpConsensusState() (*ctypes.ResultDumpConsensusState, error) { // // ```go // client := client.NewHTTP("tcp://0.0.0.0:26657", "/websocket") +// err := client.Start() +// if err != nil { +// // handle error +// } +// defer client.Stop() // state, err := client.ConsensusState() // ``` // @@ -273,6 +288,11 @@ func ConsensusState() (*ctypes.ResultConsensusState, error) { // // ```go // client := client.NewHTTP("tcp://0.0.0.0:26657", "/websocket") +// err := client.Start() +// if err != nil { +// // handle error +// } +// defer client.Stop() // state, err := client.ConsensusParams() // ``` // diff --git a/rpc/core/events.go b/rpc/core/events.go index 98c81fac..e4fd2041 100644 --- a/rpc/core/events.go +++ b/rpc/core/events.go @@ -55,6 +55,10 @@ import ( // // client := client.NewHTTP("tcp://0.0.0.0:26657", "/websocket") // err := client.Start() +// if err != nil { +// // handle error +// } +// defer client.Stop() // ctx, cancel := context.WithTimeout(context.Background(), timeout) // defer cancel() // query := query.MustParse("tm.event = 'Tx' AND tx.height = 3") @@ -118,6 +122,10 @@ func Subscribe(wsCtx rpctypes.WSRPCContext, query string) (*ctypes.ResultSubscri // ```go // client := client.NewHTTP("tcp://0.0.0.0:26657", "/websocket") // err := client.Start() +// if err != nil { +// // handle error +// } +// defer client.Stop() // err = client.Unsubscribe("test-client", query) // ``` // @@ -158,6 +166,10 @@ func Unsubscribe(wsCtx rpctypes.WSRPCContext, query string) (*ctypes.ResultUnsub // ```go // client := client.NewHTTP("tcp://0.0.0.0:26657", "/websocket") // err := client.Start() +// if err != nil { +// // handle error +// } +// defer client.Stop() // err = client.UnsubscribeAll("test-client") // ``` // diff --git a/rpc/core/health.go b/rpc/core/health.go index 0ec4b5b4..eeb8686b 100644 --- a/rpc/core/health.go +++ b/rpc/core/health.go @@ -13,6 +13,11 @@ import ( // // ```go // client := client.NewHTTP("tcp://0.0.0.0:26657", "/websocket") +// err := client.Start() +// if err != nil { +// // handle error +// } +// defer client.Stop() // result, err := client.Health() // ``` // diff --git a/rpc/core/mempool.go b/rpc/core/mempool.go index 2e32790b..ff6b029c 100644 --- a/rpc/core/mempool.go +++ b/rpc/core/mempool.go @@ -24,6 +24,11 @@ import ( // // ```go // client := client.NewHTTP("tcp://0.0.0.0:26657", "/websocket") +// err := client.Start() +// if err != nil { +// // handle error +// } +// defer client.Stop() // result, err := client.BroadcastTxAsync("123") // ``` // @@ -64,6 +69,11 @@ func BroadcastTxAsync(tx types.Tx) (*ctypes.ResultBroadcastTx, error) { // // ```go // client := client.NewHTTP("tcp://0.0.0.0:26657", "/websocket") +// err := client.Start() +// if err != nil { +// // handle error +// } +// defer client.Stop() // result, err := client.BroadcastTxSync("456") // ``` // @@ -118,6 +128,11 @@ func BroadcastTxSync(tx types.Tx) (*ctypes.ResultBroadcastTx, error) { // // ```go // client := client.NewHTTP("tcp://0.0.0.0:26657", "/websocket") +// err := client.Start() +// if err != nil { +// // handle error +// } +// defer client.Stop() // result, err := client.BroadcastTxCommit("789") // ``` // @@ -228,6 +243,11 @@ func BroadcastTxCommit(tx types.Tx) (*ctypes.ResultBroadcastTxCommit, error) { // // ```go // client := client.NewHTTP("tcp://0.0.0.0:26657", "/websocket") +// err := client.Start() +// if err != nil { +// // handle error +// } +// defer client.Stop() // result, err := client.UnconfirmedTxs() // ``` // @@ -266,6 +286,11 @@ func UnconfirmedTxs(limit int) (*ctypes.ResultUnconfirmedTxs, error) { // // ```go // client := client.NewHTTP("tcp://0.0.0.0:26657", "/websocket") +// err := client.Start() +// if err != nil { +// // handle error +// } +// defer client.Stop() // result, err := client.UnconfirmedTxs() // ``` // diff --git a/rpc/core/net.go b/rpc/core/net.go index dbd4d8c0..b80902da 100644 --- a/rpc/core/net.go +++ b/rpc/core/net.go @@ -17,6 +17,11 @@ import ( // // ```go // client := client.NewHTTP("tcp://0.0.0.0:26657", "/websocket") +// err := client.Start() +// if err != nil { +// // handle error +// } +// defer client.Stop() // info, err := client.NetInfo() // ``` // @@ -95,6 +100,11 @@ func UnsafeDialPeers(peers []string, persistent bool) (*ctypes.ResultDialPeers, // // ```go // client := client.NewHTTP("tcp://0.0.0.0:26657", "/websocket") +// err := client.Start() +// if err != nil { +// // handle error +// } +// defer client.Stop() // genesis, err := client.Genesis() // ``` // diff --git a/rpc/core/status.go b/rpc/core/status.go index 793e1ade..224857d0 100644 --- a/rpc/core/status.go +++ b/rpc/core/status.go @@ -20,6 +20,11 @@ import ( // // ```go // client := client.NewHTTP("tcp://0.0.0.0:26657", "/websocket") +// err := client.Start() +// if err != nil { +// // handle error +// } +// defer client.Stop() // result, err := client.Status() // ``` // diff --git a/rpc/core/tx.go b/rpc/core/tx.go index ba632001..3bb0f28e 100644 --- a/rpc/core/tx.go +++ b/rpc/core/tx.go @@ -21,6 +21,11 @@ import ( // // ```go // client := client.NewHTTP("tcp://0.0.0.0:26657", "/websocket") +// err := client.Start() +// if err != nil { +// // handle error +// } +// defer client.Stop() // tx, err := client.Tx([]byte("2B8EC32BA2579B3B8606E42C06DE2F7AFA2556EF"), true) // ``` // @@ -115,6 +120,11 @@ func Tx(hash []byte, prove bool) (*ctypes.ResultTx, error) { // // ```go // client := client.NewHTTP("tcp://0.0.0.0:26657", "/websocket") +// err := client.Start() +// if err != nil { +// // handle error +// } +// defer client.Stop() // q, err := tmquery.New("account.owner='Ivan'") // tx, err := client.TxSearch(q, true) // ``` From 222b8978c8e0c72dfcb7c8c389cbcdec3489fde8 Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Tue, 4 Dec 2018 08:30:29 -0500 Subject: [PATCH 06/12] Minor log changes (#2959) * node: allow state and code to have diff block versions * node: pex is a log module --- node/node.go | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/node/node.go b/node/node.go index 8e41dfd1..b56a3594 100644 --- a/node/node.go +++ b/node/node.go @@ -210,13 +210,18 @@ func NewNode(config *cfg.Config, // what happened during block replay). state = sm.LoadState(stateDB) - // Ensure the state's block version matches that of the software. + // Log the version info. + logger.Info("Version info", + "software", version.TMCoreSemVer, + "block", version.BlockProtocol, + "p2p", version.P2PProtocol, + ) + + // If the state and software differ in block version, at least log it. if state.Version.Consensus.Block != version.BlockProtocol { - return nil, fmt.Errorf( - "Block version of the software does not match that of the state.\n"+ - "Got version.BlockProtocol=%v, state.Version.Consensus.Block=%v", - version.BlockProtocol, - state.Version.Consensus.Block, + logger.Info("Software and state have different block protocols", + "software", version.BlockProtocol, + "state", state.Version.Consensus.Block, ) } @@ -454,7 +459,7 @@ func NewNode(config *cfg.Config, Seeds: splitAndTrimEmpty(config.P2P.Seeds, ",", " "), SeedMode: config.P2P.SeedMode, }) - pexReactor.SetLogger(p2pLogger) + pexReactor.SetLogger(logger.With("module", "pex")) sw.AddReactor("PEX", pexReactor) } From 1bb7e31d63e72e1017ac68a61a498b22a137a028 Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Tue, 4 Dec 2018 19:16:06 -0500 Subject: [PATCH 07/12] p2p: panic on transport error (#2968) * p2p: panic on transport error Addresses #2823. Currently, the acceptRoutine exits if the transport returns an error trying to accept a new connection. Once this happens, the node can't accept any new connections. So here, we panic instead. While we could potentially be more intelligent by rerunning the acceptRoutine, the error may indicate something more fundamental (eg. file desriptor limit) that requires a restart anyways. We can leave it to process managers to handle that restart, and notify operators about the panic. * changelog --- CHANGELOG.md | 2 ++ p2p/switch.go | 6 ++++++ 2 files changed, 8 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index ce6f2f6d..47528bce 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -56,6 +56,8 @@ key types that can be used by validators. - keep accums averaged near 0 - [types] [\#2941](https://github.com/tendermint/tendermint/issues/2941) Preserve val.Accum during ValidatorSet.Update to avoid it being reset to 0 every time a validator is updated +- [p2p] \#2968 Panic on transport error rather than continuing to run but not + accept new connections ## v0.26.4 diff --git a/p2p/switch.go b/p2p/switch.go index 4996ebd9..eece7131 100644 --- a/p2p/switch.go +++ b/p2p/switch.go @@ -505,6 +505,12 @@ func (sw *Switch) acceptRoutine() { "err", err, "numPeers", sw.peers.Size(), ) + // We could instead have a retry loop around the acceptRoutine, + // but that would need to stop and let the node shutdown eventually. + // So might as well panic and let process managers restart the node. + // There's no point in letting the node run without the acceptRoutine, + // since it won't be able to accept new connections. + panic(fmt.Errorf("accept routine exited: %v", err)) } break From a14fd8eba03147050396a5d3972d32350e5f3dd8 Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Wed, 5 Dec 2018 07:32:27 -0500 Subject: [PATCH 08/12] p2p: fix peer count mismatch #2332 (#2969) * p2p: test case for peer count mismatch #2332 * p2p: fix peer count mismatch #2332 * changelog * use httptest.Server to scrape Prometheus metrics --- CHANGELOG.md | 3 +++ p2p/peer_set.go | 9 +++++--- p2p/peer_set_test.go | 12 ++++++---- p2p/switch.go | 9 +++++--- p2p/switch_test.go | 55 ++++++++++++++++++++++++++++++++++++++++++++ p2p/test_util.go | 2 +- 6 files changed, 79 insertions(+), 11 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 47528bce..64d37e68 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -58,6 +58,9 @@ key types that can be used by validators. reset to 0 every time a validator is updated - [p2p] \#2968 Panic on transport error rather than continuing to run but not accept new connections +- [p2p] \#2969 Fix mismatch in peer count between `/net_info` and the prometheus + metrics + ## v0.26.4 diff --git a/p2p/peer_set.go b/p2p/peer_set.go index 25785615..87cf61da 100644 --- a/p2p/peer_set.go +++ b/p2p/peer_set.go @@ -98,13 +98,15 @@ func (ps *PeerSet) Get(peerKey ID) Peer { } // Remove discards peer by its Key, if the peer was previously memoized. -func (ps *PeerSet) Remove(peer Peer) { +// Returns true if the peer was removed, and false if it was not found. +// in the set. +func (ps *PeerSet) Remove(peer Peer) bool { ps.mtx.Lock() defer ps.mtx.Unlock() item := ps.lookup[peer.ID()] if item == nil { - return + return false } index := item.index @@ -116,7 +118,7 @@ func (ps *PeerSet) Remove(peer Peer) { if index == len(ps.list)-1 { ps.list = newList delete(ps.lookup, peer.ID()) - return + return true } // Replace the popped item with the last item in the old list. @@ -127,6 +129,7 @@ func (ps *PeerSet) Remove(peer Peer) { lastPeerItem.index = index ps.list = newList delete(ps.lookup, peer.ID()) + return true } // Size returns the number of unique items in the peerSet. diff --git a/p2p/peer_set_test.go b/p2p/peer_set_test.go index 04b877b0..3eb5357d 100644 --- a/p2p/peer_set_test.go +++ b/p2p/peer_set_test.go @@ -60,13 +60,15 @@ func TestPeerSetAddRemoveOne(t *testing.T) { n := len(peerList) // 1. Test removing from the front for i, peerAtFront := range peerList { - peerSet.Remove(peerAtFront) + removed := peerSet.Remove(peerAtFront) + assert.True(t, removed) wantSize := n - i - 1 for j := 0; j < 2; j++ { assert.Equal(t, false, peerSet.Has(peerAtFront.ID()), "#%d Run #%d: failed to remove peer", i, j) assert.Equal(t, wantSize, peerSet.Size(), "#%d Run #%d: failed to remove peer and decrement size", i, j) // Test the route of removing the now non-existent element - peerSet.Remove(peerAtFront) + removed := peerSet.Remove(peerAtFront) + assert.False(t, removed) } } @@ -81,7 +83,8 @@ func TestPeerSetAddRemoveOne(t *testing.T) { // b) In reverse, remove each element for i := n - 1; i >= 0; i-- { peerAtEnd := peerList[i] - peerSet.Remove(peerAtEnd) + removed := peerSet.Remove(peerAtEnd) + assert.True(t, removed) assert.Equal(t, false, peerSet.Has(peerAtEnd.ID()), "#%d: failed to remove item at end", i) assert.Equal(t, i, peerSet.Size(), "#%d: differing sizes after peerSet.Remove(atEndPeer)", i) } @@ -105,7 +108,8 @@ func TestPeerSetAddRemoveMany(t *testing.T) { } for i, peer := range peers { - peerSet.Remove(peer) + removed := peerSet.Remove(peer) + assert.True(t, removed) if peerSet.Has(peer.ID()) { t.Errorf("Failed to remove peer") } diff --git a/p2p/switch.go b/p2p/switch.go index eece7131..0490eebb 100644 --- a/p2p/switch.go +++ b/p2p/switch.go @@ -211,7 +211,9 @@ func (sw *Switch) OnStop() { // Stop peers for _, p := range sw.peers.List() { p.Stop() - sw.peers.Remove(p) + if sw.peers.Remove(p) { + sw.metrics.Peers.Add(float64(-1)) + } } // Stop reactors @@ -299,8 +301,9 @@ func (sw *Switch) StopPeerGracefully(peer Peer) { } func (sw *Switch) stopAndRemovePeer(peer Peer, reason interface{}) { - sw.peers.Remove(peer) - sw.metrics.Peers.Add(float64(-1)) + if sw.peers.Remove(peer) { + sw.metrics.Peers.Add(float64(-1)) + } peer.Stop() for _, reactor := range sw.reactors { reactor.RemovePeer(peer, reason) diff --git a/p2p/switch_test.go b/p2p/switch_test.go index f52e47f0..6c515be0 100644 --- a/p2p/switch_test.go +++ b/p2p/switch_test.go @@ -3,10 +3,17 @@ package p2p import ( "bytes" "fmt" + "io/ioutil" + "net/http" + "net/http/httptest" + "regexp" + "strconv" "sync" "testing" "time" + stdprometheus "github.com/prometheus/client_golang/prometheus" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -335,6 +342,54 @@ func TestSwitchStopsNonPersistentPeerOnError(t *testing.T) { assert.False(p.IsRunning()) } +func TestSwitchStopPeerForError(t *testing.T) { + s := httptest.NewServer(stdprometheus.UninstrumentedHandler()) + defer s.Close() + + scrapeMetrics := func() string { + resp, _ := http.Get(s.URL) + buf, _ := ioutil.ReadAll(resp.Body) + return string(buf) + } + + namespace, subsystem, name := config.TestInstrumentationConfig().Namespace, MetricsSubsystem, "peers" + re := regexp.MustCompile(namespace + `_` + subsystem + `_` + name + ` ([0-9\.]+)`) + peersMetricValue := func() float64 { + matches := re.FindStringSubmatch(scrapeMetrics()) + f, _ := strconv.ParseFloat(matches[1], 64) + return f + } + + p2pMetrics := PrometheusMetrics(namespace) + + // make two connected switches + sw1, sw2 := MakeSwitchPair(t, func(i int, sw *Switch) *Switch { + // set metrics on sw1 + if i == 0 { + opt := WithMetrics(p2pMetrics) + opt(sw) + } + return initSwitchFunc(i, sw) + }) + + assert.Equal(t, len(sw1.Peers().List()), 1) + assert.EqualValues(t, 1, peersMetricValue()) + + // send messages to the peer from sw1 + p := sw1.Peers().List()[0] + p.Send(0x1, []byte("here's a message to send")) + + // stop sw2. this should cause the p to fail, + // which results in calling StopPeerForError internally + sw2.Stop() + + // now call StopPeerForError explicitly, eg. from a reactor + sw1.StopPeerForError(p, fmt.Errorf("some err")) + + assert.Equal(t, len(sw1.Peers().List()), 0) + assert.EqualValues(t, 0, peersMetricValue()) +} + func TestSwitchReconnectsToPersistentPeer(t *testing.T) { assert, require := assert.New(t), require.New(t) diff --git a/p2p/test_util.go b/p2p/test_util.go index ac29ecb4..44f27be4 100644 --- a/p2p/test_util.go +++ b/p2p/test_util.go @@ -184,7 +184,7 @@ func MakeSwitch( // TODO: let the config be passed in? sw := initSwitch(i, NewSwitch(cfg, t, opts...)) - sw.SetLogger(log.TestingLogger()) + sw.SetLogger(log.TestingLogger().With("switch", i)) sw.SetNodeKey(&nodeKey) ni := nodeInfo.(DefaultNodeInfo) From 5413c11150809e7dd8683293c92281fb459fd68f Mon Sep 17 00:00:00 2001 From: Anton Kaliaev Date: Wed, 5 Dec 2018 23:21:46 +0400 Subject: [PATCH 09/12] kv indexer: add separator to start key when matching ranges (#2925) * kv indexer: add separator to start key when matching ranges to avoid including false positives Refs #2908 * refactor code * add a test case --- state/txindex/kv/kv.go | 51 ++++++++++++++++++++++++------------- state/txindex/kv/kv_test.go | 13 +++++++++- 2 files changed, 45 insertions(+), 19 deletions(-) diff --git a/state/txindex/kv/kv.go b/state/txindex/kv/kv.go index a5913d5b..1e3733ba 100644 --- a/state/txindex/kv/kv.go +++ b/state/txindex/kv/kv.go @@ -172,10 +172,10 @@ func (txi *TxIndex) Search(q *query.Query) ([]*types.TxResult, error) { for _, r := range ranges { if !hashesInitialized { - hashes = txi.matchRange(r, []byte(r.key)) + hashes = txi.matchRange(r, startKey(r.key)) hashesInitialized = true } else { - hashes = intersect(hashes, txi.matchRange(r, []byte(r.key))) + hashes = intersect(hashes, txi.matchRange(r, startKey(r.key))) } } } @@ -190,10 +190,10 @@ func (txi *TxIndex) Search(q *query.Query) ([]*types.TxResult, error) { } if !hashesInitialized { - hashes = txi.match(c, startKey(c, height)) + hashes = txi.match(c, startKeyForCondition(c, height)) hashesInitialized = true } else { - hashes = intersect(hashes, txi.match(c, startKey(c, height))) + hashes = intersect(hashes, txi.match(c, startKeyForCondition(c, height))) } } @@ -359,14 +359,14 @@ func (txi *TxIndex) match(c query.Condition, startKey []byte) (hashes [][]byte) return } -func (txi *TxIndex) matchRange(r queryRange, prefix []byte) (hashes [][]byte) { +func (txi *TxIndex) matchRange(r queryRange, startKey []byte) (hashes [][]byte) { // create a map to prevent duplicates hashesMap := make(map[string][]byte) lowerBound := r.lowerBoundValue() upperBound := r.upperBoundValue() - it := dbm.IteratePrefix(txi.store, prefix) + it := dbm.IteratePrefix(txi.store, startKey) defer it.Close() LOOP: for ; it.Valid(); it.Next() { @@ -409,16 +409,6 @@ LOOP: /////////////////////////////////////////////////////////////////////////////// // Keys -func startKey(c query.Condition, height int64) []byte { - var key string - if height > 0 { - key = fmt.Sprintf("%s/%v/%d/", c.Tag, c.Operand, height) - } else { - key = fmt.Sprintf("%s/%v/", c.Tag, c.Operand) - } - return []byte(key) -} - func isTagKey(key []byte) bool { return strings.Count(string(key), tagKeySeparator) == 3 } @@ -429,11 +419,36 @@ func extractValueFromKey(key []byte) string { } func keyForTag(tag cmn.KVPair, result *types.TxResult) []byte { - return []byte(fmt.Sprintf("%s/%s/%d/%d", tag.Key, tag.Value, result.Height, result.Index)) + return []byte(fmt.Sprintf("%s/%s/%d/%d", + tag.Key, + tag.Value, + result.Height, + result.Index, + )) } func keyForHeight(result *types.TxResult) []byte { - return []byte(fmt.Sprintf("%s/%d/%d/%d", types.TxHeightKey, result.Height, result.Height, result.Index)) + return []byte(fmt.Sprintf("%s/%d/%d/%d", + types.TxHeightKey, + result.Height, + result.Height, + result.Index, + )) +} + +func startKeyForCondition(c query.Condition, height int64) []byte { + if height > 0 { + return startKey(c.Tag, c.Operand, height) + } + return startKey(c.Tag, c.Operand) +} + +func startKey(fields ...interface{}) []byte { + var b bytes.Buffer + for _, f := range fields { + b.Write([]byte(fmt.Sprintf("%v", f) + tagKeySeparator)) + } + return b.Bytes() } /////////////////////////////////////////////////////////////////////////////// diff --git a/state/txindex/kv/kv_test.go b/state/txindex/kv/kv_test.go index 7cf16dc5..343cfbb5 100644 --- a/state/txindex/kv/kv_test.go +++ b/state/txindex/kv/kv_test.go @@ -126,7 +126,7 @@ func TestTxSearchOneTxWithMultipleSameTagsButDifferentValues(t *testing.T) { } func TestTxSearchMultipleTxs(t *testing.T) { - allowedTags := []string{"account.number"} + allowedTags := []string{"account.number", "account.number.id"} indexer := NewTxIndex(db.NewMemDB(), IndexTags(allowedTags)) // indexed first, but bigger height (to test the order of transactions) @@ -160,6 +160,17 @@ func TestTxSearchMultipleTxs(t *testing.T) { err = indexer.Index(txResult3) require.NoError(t, err) + // indexed fourth (to test we don't include txs with similar tags) + // https://github.com/tendermint/tendermint/issues/2908 + txResult4 := txResultWithTags([]cmn.KVPair{ + {Key: []byte("account.number.id"), Value: []byte("1")}, + }) + txResult4.Tx = types.Tx("Mike's account") + txResult4.Height = 2 + txResult4.Index = 2 + err = indexer.Index(txResult4) + require.NoError(t, err) + results, err := indexer.Search(query.MustParse("account.number >= 1")) assert.NoError(t, err) From 9f8761d105c8ae661c0139756dbb0b80ba7b2e98 Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Wed, 5 Dec 2018 15:19:30 -0500 Subject: [PATCH 10/12] update changelog and upgrading (#2974) --- CHANGELOG.md | 27 ++++++++++++++++----------- CHANGELOG_PENDING.md | 1 - UPGRADING.md | 16 +++++++++++++++- 3 files changed, 31 insertions(+), 13 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 64d37e68..2ec6b1d9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,7 @@ ## v0.27.0 -*November 29th, 2018* +*December 5th, 2018* Special thanks to external contributors on this release: @danil-lashin, @srmo @@ -18,7 +18,8 @@ This release is primarily about fixes to the proposer selection algorithm in preparation for the [Cosmos Game of Stakes](https://blog.cosmos.network/the-game-of-stakes-is-open-for-registration-83a404746ee6). It also makes use of the `ConsensusParams.Validator.PubKeyTypes` to restrict the -key types that can be used by validators. +key types that can be used by validators, and removes the `Heartbeat` consensus +message. ### BREAKING CHANGES: @@ -45,22 +46,26 @@ key types that can be used by validators. ### IMPROVEMENTS: - [state] [\#2929](https://github.com/tendermint/tendermint/issues/2929) Minor refactor of updateState logic (@danil-lashin) +- [node] \#2959 Allow node to start even if software's BlockProtocol is + different from state's BlockProtocol +- [pex] \#2959 Pex reactor logger uses `module=pex` ### BUG FIXES: -- [types] [\#2938](https://github.com/tendermint/tendermint/issues/2938) Fix regression in v0.26.4 where we panic on empty - genDoc.Validators -- [state] [\#2785](https://github.com/tendermint/tendermint/issues/2785) Fix accum for new validators to be `-1.125*totalVotingPower` - instead of 0, forcing them to wait before becoming the proposer. Also: - - do not batch clip - - keep accums averaged near 0 -- [types] [\#2941](https://github.com/tendermint/tendermint/issues/2941) Preserve val.Accum during ValidatorSet.Update to avoid it being - reset to 0 every time a validator is updated - [p2p] \#2968 Panic on transport error rather than continuing to run but not accept new connections - [p2p] \#2969 Fix mismatch in peer count between `/net_info` and the prometheus metrics - +- [rpc] \#2408 `/broadcast_tx_commit`: Fix "interface conversion: interface {} in nil, not EventDataTx" panic (could happen if somebody sent a tx using `/broadcast_tx_commit` while Tendermint was being stopped) +- [state] [\#2785](https://github.com/tendermint/tendermint/issues/2785) Fix accum for new validators to be `-1.125*totalVotingPower` + instead of 0, forcing them to wait before becoming the proposer. Also: + - do not batch clip + - keep accums averaged near 0 +- [txindex/kv] [\#2925](https://github.com/tendermint/tendermint/issues/2925) Don't return false positives when range searching for a prefix of a tag value +- [types] [\#2938](https://github.com/tendermint/tendermint/issues/2938) Fix regression in v0.26.4 where we panic on empty + genDoc.Validators +- [types] [\#2941](https://github.com/tendermint/tendermint/issues/2941) Preserve val.Accum during ValidatorSet.Update to avoid it being + reset to 0 every time a validator is updated ## v0.26.4 diff --git a/CHANGELOG_PENDING.md b/CHANGELOG_PENDING.md index 3c26f213..7063efc3 100644 --- a/CHANGELOG_PENDING.md +++ b/CHANGELOG_PENDING.md @@ -21,4 +21,3 @@ Special thanks to external contributors on this release: ### IMPROVEMENTS: ### BUG FIXES: -- [rpc] \#2408 `/broadcast_tx_commit`: Fix "interface conversion: interface {} in nil, not EventDataTx" panic (could happen if somebody sent a tx using /broadcast_tx_commit while Tendermint was being stopped) \ No newline at end of file diff --git a/UPGRADING.md b/UPGRADING.md index f55058a8..63f000f5 100644 --- a/UPGRADING.md +++ b/UPGRADING.md @@ -5,6 +5,20 @@ a newer version of Tendermint Core. ## v0.27.0 +This release contains some breaking changes to the block and p2p protocols, +but does not change any core data structures, so it should be compatible with +existing blockchains from the v0.26 series that only used Ed25519 validator keys. +Blockchains using Secp256k1 for validators will not be compatible. This is due +to the fact that we now enforce which key types validators can use as a +consensus param. The default is Ed25519, and Secp256k1 must be activated +explicitly. + +It is recommended to upgrade all nodes at once to avoid incompatibilities at the +peer layer - namely, the heartbeat consensus message has been removed (only +relevant if `create_empty_blocks=false` or `create_empty_blocks_interval > 0`), +and the proposer selection algorithm has changed. Since proposer information is +never included in the blockchain, this change only affects the peer layer. + ### Go API Changes #### libs/db @@ -32,7 +46,7 @@ file). ## v0.26.0 -New 0.26.0 release contains a lot of changes to core data types and protocols. It is not +This release contains a lot of changes to core data types and protocols. It is not compatible to the old versions and there is no straight forward way to update old data to be compatible with the new version. From c4a1cfc5c29f2f3f89bdb995184a34cdf3484c91 Mon Sep 17 00:00:00 2001 From: Anton Kaliaev Date: Fri, 7 Dec 2018 12:28:02 +0400 Subject: [PATCH 11/12] don't ignore key when executing CONTAINS (#2924) Fixes #2912 --- CHANGELOG_PENDING.md | 1 + state/txindex/kv/kv.go | 12 ++++++------ state/txindex/kv/kv_test.go | 4 +++- 3 files changed, 10 insertions(+), 7 deletions(-) diff --git a/CHANGELOG_PENDING.md b/CHANGELOG_PENDING.md index 7063efc3..74d81bd5 100644 --- a/CHANGELOG_PENDING.md +++ b/CHANGELOG_PENDING.md @@ -21,3 +21,4 @@ Special thanks to external contributors on this release: ### IMPROVEMENTS: ### BUG FIXES: +- [kv indexer] \#2912 don't ignore key when executing CONTAINS \ No newline at end of file diff --git a/state/txindex/kv/kv.go b/state/txindex/kv/kv.go index 1e3733ba..93249b7f 100644 --- a/state/txindex/kv/kv.go +++ b/state/txindex/kv/kv.go @@ -332,18 +332,18 @@ func isRangeOperation(op query.Operator) bool { } } -func (txi *TxIndex) match(c query.Condition, startKey []byte) (hashes [][]byte) { +func (txi *TxIndex) match(c query.Condition, startKeyBz []byte) (hashes [][]byte) { if c.Op == query.OpEqual { - it := dbm.IteratePrefix(txi.store, startKey) + it := dbm.IteratePrefix(txi.store, startKeyBz) defer it.Close() for ; it.Valid(); it.Next() { hashes = append(hashes, it.Value()) } } else if c.Op == query.OpContains { - // XXX: doing full scan because startKey does not apply here - // For example, if startKey = "account.owner=an" and search query = "accoutn.owner CONSISTS an" - // we can't iterate with prefix "account.owner=an" because we might miss keys like "account.owner=Ulan" - it := txi.store.Iterator(nil, nil) + // XXX: startKey does not apply here. + // For example, if startKey = "account.owner/an/" and search query = "accoutn.owner CONTAINS an" + // we can't iterate with prefix "account.owner/an/" because we might miss keys like "account.owner/Ulan/" + it := dbm.IteratePrefix(txi.store, startKey(c.Tag)) defer it.Close() for ; it.Valid(); it.Next() { if !isTagKey(it.Key()) { diff --git a/state/txindex/kv/kv_test.go b/state/txindex/kv/kv_test.go index 343cfbb5..0f206514 100644 --- a/state/txindex/kv/kv_test.go +++ b/state/txindex/kv/kv_test.go @@ -89,8 +89,10 @@ func TestTxSearch(t *testing.T) { {"account.date >= TIME 2013-05-03T14:45:00Z", 0}, // search using CONTAINS {"account.owner CONTAINS 'an'", 1}, - // search using CONTAINS + // search for non existing value using CONTAINS {"account.owner CONTAINS 'Vlad'", 0}, + // search using the wrong tag (of numeric type) using CONTAINS + {"account.number CONTAINS 'Iv'", 0}, } for _, tc := range testCases { From 2f64717bb5ae3108ed938648291ba577e70fa8c6 Mon Sep 17 00:00:00 2001 From: Leo Wang Date: Fri, 7 Dec 2018 16:30:58 +0800 Subject: [PATCH 12/12] return an error if validator set is empty in genesis file and after InitChain (#2971) Fixes #2951 --- consensus/replay.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/consensus/replay.go b/consensus/replay.go index c9a779e3..ba118e66 100644 --- a/consensus/replay.go +++ b/consensus/replay.go @@ -303,7 +303,13 @@ func (h *Handshaker) ReplayBlocks( } state.Validators = types.NewValidatorSet(vals) state.NextValidators = types.NewValidatorSet(vals) + } else { + // If validator set is not set in genesis and still empty after InitChain, exit. + if len(h.genDoc.Validators) == 0 { + return nil, fmt.Errorf("Validator set is nil in genesis and still empty after InitChain") + } } + if res.ConsensusParams != nil { state.ConsensusParams = types.PB2TM.ConsensusParams(res.ConsensusParams) }