From 7b48ea1788878d02c84b714c06640d595aa0b90e Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Thu, 11 Oct 2018 13:55:36 -0400 Subject: [PATCH 001/267] privval: set deadline in readMsg (#2548) * privval: set deadline in readMsg * fixes from review --- privval/socket.go | 32 +++++++++++++++++--------------- 1 file changed, 17 insertions(+), 15 deletions(-) diff --git a/privval/socket.go b/privval/socket.go index da95f8fb..64d4c46d 100644 --- a/privval/socket.go +++ b/privval/socket.go @@ -18,11 +18,11 @@ import ( ) const ( - defaultAcceptDeadlineSeconds = 3 - defaultConnDeadlineSeconds = 3 - defaultConnHeartBeatSeconds = 30 - defaultConnWaitSeconds = 60 - defaultDialRetries = 10 + defaultAcceptDeadlineSeconds = 30 // tendermint waits this long for remote val to connect + defaultConnDeadlineSeconds = 3 // must be set before each read + defaultConnHeartBeatSeconds = 30 // tcp keep-alive period + defaultConnWaitSeconds = 60 // XXX: is this redundant with the accept deadline? + defaultDialRetries = 10 // try to connect to tendermint this many times ) // Socket errors. @@ -33,12 +33,6 @@ var ( ErrUnexpectedResponse = errors.New("received unexpected response") ) -var ( - acceptDeadline = time.Second * defaultAcceptDeadlineSeconds - connDeadline = time.Second * defaultConnDeadlineSeconds - connHeartbeat = time.Second * defaultConnHeartBeatSeconds -) - // SocketPVOption sets an optional parameter on the SocketPV. type SocketPVOption func(*SocketPV) @@ -93,9 +87,9 @@ func NewSocketPV( ) *SocketPV { sc := &SocketPV{ addr: socketAddr, - acceptDeadline: acceptDeadline, - connDeadline: connDeadline, - connHeartbeat: connHeartbeat, + acceptDeadline: time.Second * defaultAcceptDeadlineSeconds, + connDeadline: time.Second * defaultConnDeadlineSeconds, + connHeartbeat: time.Second * defaultConnHeartBeatSeconds, connWaitTimeout: time.Second * defaultConnWaitSeconds, privKey: privKey, } @@ -441,7 +435,7 @@ func (rs *RemoteSigner) connect() (net.Conn, error) { continue } - if err := conn.SetDeadline(time.Now().Add(connDeadline)); err != nil { + if err := conn.SetDeadline(time.Now().Add(time.Second * defaultConnDeadlineSeconds)); err != nil { err = cmn.ErrorWrap(err, "setting connection timeout failed") rs.Logger.Error( "connect", @@ -587,6 +581,14 @@ type RemoteSignerError struct { func readMsg(r io.Reader) (msg SocketPVMsg, err error) { const maxSocketPVMsgSize = 1024 * 10 + + // set deadline before trying to read + conn := r.(net.Conn) + if err := conn.SetDeadline(time.Now().Add(time.Second * defaultConnDeadlineSeconds)); err != nil { + err = cmn.ErrorWrap(err, "setting connection timeout failed in readMsg") + return msg, err + } + _, err = cdc.UnmarshalBinaryReader(r, &msg, maxSocketPVMsgSize) if _, ok := err.(timeoutError); ok { err = cmn.ErrorWrap(ErrConnTimeout, err.Error()) From 3744e8271d664a89cc453a546d7ab827fd43d9a4 Mon Sep 17 00:00:00 2001 From: Alessio Treglia Date: Thu, 11 Oct 2018 20:37:21 -0700 Subject: [PATCH 002/267] [R4R] Pass nil to NewValidatorSet() when genesis file's Validators field is nil (#2617) * Pass nil to NewValidatorSet() when genesis file's Validators field is nil Closes: #2616 * Update CHANGELOG_PENDING.md --- CHANGELOG_PENDING.md | 1 + state/state.go | 30 +++++++++++++++++++----------- state/state_test.go | 13 +++++++++++++ 3 files changed, 33 insertions(+), 11 deletions(-) diff --git a/CHANGELOG_PENDING.md b/CHANGELOG_PENDING.md index bdc2a731..dace33f1 100644 --- a/CHANGELOG_PENDING.md +++ b/CHANGELOG_PENDING.md @@ -57,3 +57,4 @@ timeoutPrecommit before starting next round block - [p2p] \#2555 fix p2p switch FlushThrottle value (@goolAdapter) - [libs/event] \#2518 fix event concurrency flaw (@goolAdapter) +- [state] \#2616 Pass nil to NewValidatorSet() when genesis file's Validators field is nil diff --git a/state/state.go b/state/state.go index 26510816..1f60fd65 100644 --- a/state/state.go +++ b/state/state.go @@ -198,17 +198,25 @@ func MakeGenesisState(genDoc *types.GenesisDoc) (State, error) { } // Make validators slice - validators := make([]*types.Validator, len(genDoc.Validators)) - for i, val := range genDoc.Validators { - pubKey := val.PubKey - address := pubKey.Address() + var validatorSet, nextValidatorSet *types.ValidatorSet + if genDoc.Validators == nil { + validatorSet = types.NewValidatorSet(nil) + nextValidatorSet = types.NewValidatorSet(nil) + } else { + validators := make([]*types.Validator, len(genDoc.Validators)) + for i, val := range genDoc.Validators { + pubKey := val.PubKey + address := pubKey.Address() - // Make validator - validators[i] = &types.Validator{ - Address: address, - PubKey: pubKey, - VotingPower: val.Power, + // Make validator + validators[i] = &types.Validator{ + Address: address, + PubKey: pubKey, + VotingPower: val.Power, + } } + validatorSet = types.NewValidatorSet(validators) + nextValidatorSet = types.NewValidatorSet(validators).CopyIncrementAccum(1) } return State{ @@ -219,8 +227,8 @@ func MakeGenesisState(genDoc *types.GenesisDoc) (State, error) { LastBlockID: types.BlockID{}, LastBlockTime: genDoc.GenesisTime, - NextValidators: types.NewValidatorSet(validators).CopyIncrementAccum(1), - Validators: types.NewValidatorSet(validators), + NextValidators: nextValidatorSet, + Validators: validatorSet, LastValidators: types.NewValidatorSet(nil), LastHeightValidatorsChanged: 1, diff --git a/state/state_test.go b/state/state_test.go index 1ab470b0..2c777307 100644 --- a/state/state_test.go +++ b/state/state_test.go @@ -48,6 +48,19 @@ func TestStateCopy(t *testing.T) { %v`, state)) } +//TestMakeGenesisStateNilValidators tests state's consistency when genesis file's validators field is nil. +func TestMakeGenesisStateNilValidators(t *testing.T) { + doc := types.GenesisDoc{ + ChainID: "dummy", + Validators: nil, + } + require.Nil(t, doc.ValidateAndComplete()) + state, err := MakeGenesisState(&doc) + require.Nil(t, err) + require.Equal(t, 0, len(state.Validators.Validators)) + require.Equal(t, 0, len(state.NextValidators.Validators)) +} + // TestStateSaveLoad tests saving and loading State from a db. func TestStateSaveLoad(t *testing.T) { tearDown, stateDB, state := setupTestCase(t) From e1538bf67ea5122b0add392e93bd2a3ea73dee69 Mon Sep 17 00:00:00 2001 From: Anton Kaliaev Date: Fri, 12 Oct 2018 09:03:58 +0400 Subject: [PATCH 003/267] state: require block.Time of the fist block to be genesis time (#2594) * require block.Time of the fist block to be genesis time Refs #2587: ``` We only start validating block.Time when Height > 1, because there is no commit to compute the median timestamp from for the first block. This means a faulty proposer could make the first block with whatever time they want. Instead, we should require the timestamp of block 1 to match the genesis time. I discovered this while refactoring the ValidateBlock tests to be table-driven while working on tests for #2560. ``` * do not accept blocks with negative height * update changelog and spec * nanos precision for test genesis time * Fix failing test (#2607) --- CHANGELOG_PENDING.md | 1 + cmd/tendermint/commands/lite.go | 14 +++++++------- config/toml.go | 2 +- consensus/state_test.go | 7 ++++--- docs/spec/blockchain/blockchain.md | 9 +++++++++ p2p/netaddress.go | 1 + state/state.go | 5 +---- state/validation.go | 9 +++++++++ state/validation_test.go | 11 ++++++----- types/block.go | 7 +++++++ types/block_test.go | 3 ++- 11 files changed, 48 insertions(+), 21 deletions(-) diff --git a/CHANGELOG_PENDING.md b/CHANGELOG_PENDING.md index dace33f1..f1268402 100644 --- a/CHANGELOG_PENDING.md +++ b/CHANGELOG_PENDING.md @@ -28,6 +28,7 @@ BREAKING CHANGES: * Blockchain Protocol * [types] \#2459 `Vote`/`Proposal`/`Heartbeat` use amino encoding instead of JSON in `SignBytes`. * [types] \#2512 Remove the pubkey field from the validator hash + * [state] \#2587 require block.Time of the fist block to be genesis time * P2P Protocol diff --git a/cmd/tendermint/commands/lite.go b/cmd/tendermint/commands/lite.go index bc51d7de..eb2817b6 100644 --- a/cmd/tendermint/commands/lite.go +++ b/cmd/tendermint/commands/lite.go @@ -26,12 +26,12 @@ just with added trust and running locally.`, } var ( - listenAddr string - nodeAddr string - chainID string - home string - maxOpenConnections int - cacheSize int + listenAddr string + nodeAddr string + chainID string + home string + maxOpenConnections int + cacheSize int ) func init() { @@ -39,7 +39,7 @@ func init() { LiteCmd.Flags().StringVar(&nodeAddr, "node", "tcp://localhost:26657", "Connect to a Tendermint node at this address") LiteCmd.Flags().StringVar(&chainID, "chain-id", "tendermint", "Specify the Tendermint chain ID") LiteCmd.Flags().StringVar(&home, "home-dir", ".tendermint-lite", "Specify the home directory") - LiteCmd.Flags().IntVar(&maxOpenConnections,"max-open-connections",900,"Maximum number of simultaneous connections (including WebSocket).") + LiteCmd.Flags().IntVar(&maxOpenConnections, "max-open-connections", 900, "Maximum number of simultaneous connections (including WebSocket).") LiteCmd.Flags().IntVar(&cacheSize, "cache-size", 10, "Specify the memory trust store cache size") } diff --git a/config/toml.go b/config/toml.go index ddfe5f05..62e5fa97 100644 --- a/config/toml.go +++ b/config/toml.go @@ -342,7 +342,7 @@ func ResetTestRoot(testName string) *Config { } var testGenesis = `{ - "genesis_time": "0001-01-01T00:00:00.000Z", + "genesis_time": "2017-10-10T08:20:13.695936996Z", "chain_id": "tendermint_test", "validators": [ { diff --git a/consensus/state_test.go b/consensus/state_test.go index 831f77f4..e7d4b4fa 100644 --- a/consensus/state_test.go +++ b/consensus/state_test.go @@ -455,8 +455,9 @@ func TestStateLockNoPOL(t *testing.T) { ensureNewTimeout(timeoutWaitCh, cs1.config.TimeoutPrecommit.Nanoseconds()) + cs2, _ := randConsensusState(2) // needed so generated block is different than locked block // before we time out into new round, set next proposal block - prop, propBlock := decideProposal(cs1, vs2, vs2.Height, vs2.Round+1) + prop, propBlock := decideProposal(cs2, vs2, vs2.Height, vs2.Round+1) if prop == nil || propBlock == nil { t.Fatal("Failed to create proposal block with vs2") } @@ -479,7 +480,7 @@ func TestStateLockNoPOL(t *testing.T) { ensureNewVote(voteCh) // prevote // prevote for locked block (not proposal) - validatePrevote(t, cs1, 0, vss[0], cs1.LockedBlock.Hash()) + validatePrevote(t, cs1, 3, vss[0], cs1.LockedBlock.Hash()) signAddVotes(cs1, types.VoteTypePrevote, propBlock.Hash(), propBlock.MakePartSet(partSize).Header(), vs2) ensureNewVote(voteCh) @@ -487,7 +488,7 @@ func TestStateLockNoPOL(t *testing.T) { ensureNewTimeout(timeoutWaitCh, cs1.config.TimeoutPrevote.Nanoseconds()) ensureNewVote(voteCh) - validatePrecommit(t, cs1, 2, 0, vss[0], nil, theBlockHash) // precommit nil but locked on proposal + validatePrecommit(t, cs1, 3, 0, vss[0], nil, theBlockHash) // precommit nil but locked on proposal signAddVotes(cs1, types.VoteTypePrecommit, propBlock.Hash(), propBlock.MakePartSet(partSize).Header(), vs2) // NOTE: conflicting precommits at same height ensureNewVote(voteCh) diff --git a/docs/spec/blockchain/blockchain.md b/docs/spec/blockchain/blockchain.md index bd0af70a..4a433b5d 100644 --- a/docs/spec/blockchain/blockchain.md +++ b/docs/spec/blockchain/blockchain.md @@ -230,6 +230,15 @@ It must equal the weighted median of the timestamps of the valid votes in the bl Note: the timestamp of a vote must be greater by at least one millisecond than that of the block being voted on. +The timestamp of the first block must be equal to the genesis time (since +there's no votes to compute the median). + +``` +if block.Header.Height == 1 { + block.Header.Timestamp == genesisTime +} +``` + See the section on [BFT time](../consensus/bft-time.md) for more details. ### NumTxs diff --git a/p2p/netaddress.go b/p2p/netaddress.go index f848b7a5..ec9a0ea7 100644 --- a/p2p/netaddress.go +++ b/p2p/netaddress.go @@ -14,6 +14,7 @@ import ( "time" "errors" + cmn "github.com/tendermint/tendermint/libs/common" ) diff --git a/state/state.go b/state/state.go index 1f60fd65..23c0d632 100644 --- a/state/state.go +++ b/state/state.go @@ -118,10 +118,7 @@ func (state State) MakeBlock( // Set time if height == 1 { - block.Time = tmtime.Now() - if block.Time.Before(state.LastBlockTime) { - block.Time = state.LastBlockTime // state.LastBlockTime for height == 1 is genesis time - } + block.Time = state.LastBlockTime // genesis time } else { block.Time = MedianTime(commit, state.LastValidators) } diff --git a/state/validation.go b/state/validation.go index 9d8ef97a..a308870e 100644 --- a/state/validation.go +++ b/state/validation.go @@ -123,6 +123,15 @@ func validateBlock(stateDB dbm.DB, state State, block *types.Block) error { block.Time, ) } + } else if block.Height == 1 { + genesisTime := state.LastBlockTime + if !block.Time.Equal(genesisTime) { + return fmt.Errorf( + "Block time %v is not equal to genesis time %v", + block.Time, + genesisTime, + ) + } } // Limit the amount of evidence diff --git a/state/validation_test.go b/state/validation_test.go index e5f45166..3c58c713 100644 --- a/state/validation_test.go +++ b/state/validation_test.go @@ -2,6 +2,7 @@ package state import ( "testing" + "time" "github.com/stretchr/testify/require" "github.com/tendermint/tendermint/crypto/ed25519" @@ -32,11 +33,11 @@ func TestValidateBlockHeader(t *testing.T) { name string malleateBlock func(block *types.Block) }{ - {"ChainID wrong", func(block *types.Block) { block.ChainID = "not-the-real-one" }}, // wrong chain id - {"Height wrong", func(block *types.Block) { block.Height += 10 }}, // wrong height - // TODO(#2589) (#2587) : {"Time", func(block *types.Block) { block.Time.Add(-time.Second * 3600 * 24) }}, // wrong time - {"NumTxs wrong", func(block *types.Block) { block.NumTxs += 10 }}, // wrong num txs - {"TotalTxs wrong", func(block *types.Block) { block.TotalTxs += 10 }}, // wrong total txs + {"ChainID wrong", func(block *types.Block) { block.ChainID = "not-the-real-one" }}, + {"Height wrong", func(block *types.Block) { block.Height += 10 }}, + {"Time wrong", func(block *types.Block) { block.Time = block.Time.Add(-time.Second * 3600 * 24) }}, + {"NumTxs wrong", func(block *types.Block) { block.NumTxs += 10 }}, + {"TotalTxs wrong", func(block *types.Block) { block.TotalTxs += 10 }}, {"LastBlockID wrong", func(block *types.Block) { block.LastBlockID.PartsHeader.Total += 10 }}, {"LastCommitHash wrong", func(block *types.Block) { block.LastCommitHash = wrongHash }}, diff --git a/types/block.go b/types/block.go index 07a71ca8..bd9092f4 100644 --- a/types/block.go +++ b/types/block.go @@ -64,6 +64,13 @@ func (b *Block) ValidateBasic() error { b.mtx.Lock() defer b.mtx.Unlock() + if b.Height < 0 { + return fmt.Errorf( + "Negative Block.Header.Height: %v", + b.Height, + ) + } + newTxs := int64(len(b.Data.Txs)) if b.NumTxs != newTxs { return fmt.Errorf( diff --git a/types/block_test.go b/types/block_test.go index 887f35a1..962aa002 100644 --- a/types/block_test.go +++ b/types/block_test.go @@ -60,6 +60,7 @@ func TestBlockValidateBasic(t *testing.T) { }{ {"Make Block", func(blk *Block) {}, false}, {"Make Block w/ proposer Addr", func(blk *Block) { blk.ProposerAddress = valSet.GetProposer().Address }, false}, + {"Negative Height", func(blk *Block) { blk.Height = -1 }, true}, {"Increase NumTxs", func(blk *Block) { blk.NumTxs++ }, true}, {"Remove 1/2 the commits", func(blk *Block) { blk.LastCommit.Precommits = commit.Precommits[:commit.Size()/2] @@ -81,7 +82,7 @@ func TestBlockValidateBasic(t *testing.T) { t.Run(tc.testName, func(t *testing.T) { block := MakeBlock(h, txs, commit, evList) tc.malleateBlock(block) - assert.Equal(t, tc.expErr, block.ValidateBasic() != nil, "Validate Basic had an unexpected result") + assert.Equal(t, tc.expErr, block.ValidateBasic() != nil, "ValidateBasic had an unexpected result") }) } } From 2363d8897949919fafec4992047ef803bc5b8ee4 Mon Sep 17 00:00:00 2001 From: Zarko Milosevic Date: Fri, 12 Oct 2018 22:13:01 +0200 Subject: [PATCH 004/267] consensus: Wait for proposal or timeout before prevote (#2540) * Fix termination issues and improve tests * Improve formatting and tests based on reviewer feedback --- CHANGELOG_PENDING.md | 2 + config/config.go | 2 +- consensus/common_test.go | 187 ++++++++++--- consensus/mempool_test.go | 36 ++- consensus/state.go | 52 ++-- consensus/state_test.go | 567 ++++++++++++++++++++------------------ node/node.go | 2 +- p2p/metrics.go | 1 - state/execution.go | 32 +-- state/metrics.go | 2 +- 10 files changed, 516 insertions(+), 367 deletions(-) diff --git a/CHANGELOG_PENDING.md b/CHANGELOG_PENDING.md index f1268402..0f919bda 100644 --- a/CHANGELOG_PENDING.md +++ b/CHANGELOG_PENDING.md @@ -50,6 +50,8 @@ BUG FIXES: - [node] \#2434 Make node respond to signal interrupts while sleeping for genesis time - [consensus] [\#1690](https://github.com/tendermint/tendermint/issues/1690) wait for timeoutPrecommit before starting next round +- [consensus] [\#1745](https://github.com/tendermint/tendermint/issues/1745) wait for +Proposal or timeoutProposal before entering prevote - [evidence] \#2515 fix db iter leak (@goolAdapter) - [common/bit_array] Fixed a bug in the `Or` function - [common/bit_array] Fixed a bug in the `Sub` function (@james-ray) diff --git a/config/config.go b/config/config.go index f2bac5c6..ede57207 100644 --- a/config/config.go +++ b/config/config.go @@ -565,7 +565,7 @@ func DefaultConsensusConfig() *ConsensusConfig { // TestConsensusConfig returns a configuration for testing the consensus service func TestConsensusConfig() *ConsensusConfig { cfg := DefaultConsensusConfig() - cfg.TimeoutPropose = 100 * time.Millisecond + cfg.TimeoutPropose = 40 * time.Millisecond cfg.TimeoutProposeDelta = 1 * time.Millisecond cfg.TimeoutPrevote = 10 * time.Millisecond cfg.TimeoutPrevoteDelta = 1 * time.Millisecond diff --git a/consensus/common_test.go b/consensus/common_test.go index 2a5cc8e7..cf76e924 100644 --- a/consensus/common_test.go +++ b/consensus/common_test.go @@ -39,8 +39,8 @@ const ( ) // genesis, chain_id, priv_val -var config *cfg.Config // NOTE: must be reset for each _test.go file -var ensureTimeout = time.Second * 1 // must be in seconds because CreateEmptyBlocksInterval is +var config *cfg.Config // NOTE: must be reset for each _test.go file +var ensureTimeout = time.Millisecond * 100 func ensureDir(dir string, mode os.FileMode) { if err := cmn.EnsureDir(dir, mode); err != nil { @@ -317,67 +317,156 @@ func ensureNoNewEvent(ch <-chan interface{}, timeout time.Duration, } } -func ensureNoNewStep(stepCh <-chan interface{}) { - ensureNoNewEvent(stepCh, ensureTimeout, "We should be stuck waiting, "+ - "not moving to the next step") +func ensureNoNewEventOnChannel(ch <-chan interface{}) { + ensureNoNewEvent( + ch, + ensureTimeout, + "We should be stuck waiting, not receiving new event on the channel") +} + +func ensureNoNewRoundStep(stepCh <-chan interface{}) { + ensureNoNewEvent( + stepCh, + ensureTimeout, + "We should be stuck waiting, not receiving NewRoundStep event") +} + +func ensureNoNewUnlock(unlockCh <-chan interface{}) { + ensureNoNewEvent( + unlockCh, + ensureTimeout, + "We should be stuck waiting, not receiving Unlock event") } func ensureNoNewTimeout(stepCh <-chan interface{}, timeout int64) { timeoutDuration := time.Duration(timeout*5) * time.Nanosecond - ensureNoNewEvent(stepCh, timeoutDuration, "We should be stuck waiting, "+ - "not moving to the next step") + ensureNoNewEvent( + stepCh, + timeoutDuration, + "We should be stuck waiting, not receiving NewTimeout event") } -func ensureNewEvent(ch <-chan interface{}, timeout time.Duration, errorMessage string) { +func ensureNewEvent( + ch <-chan interface{}, + height int64, + round int, + timeout time.Duration, + errorMessage string) { + select { case <-time.After(timeout): panic(errorMessage) - case <-ch: - break + case ev := <-ch: + rs, ok := ev.(types.EventDataRoundState) + if !ok { + panic( + fmt.Sprintf( + "expected a EventDataRoundState, got %v.Wrong subscription channel?", + reflect.TypeOf(rs))) + } + if rs.Height != height { + panic(fmt.Sprintf("expected height %v, got %v", height, rs.Height)) + } + if rs.Round != round { + panic(fmt.Sprintf("expected round %v, got %v", round, rs.Round)) + } + // TODO: We could check also for a step at this point! } } -func ensureNewStep(stepCh <-chan interface{}) { - ensureNewEvent(stepCh, ensureTimeout, +func ensureNewRoundStep(stepCh <-chan interface{}, height int64, round int) { + ensureNewEvent( + stepCh, + height, + round, + ensureTimeout, "Timeout expired while waiting for NewStep event") } -func ensureNewRound(roundCh <-chan interface{}) { - ensureNewEvent(roundCh, ensureTimeout, - "Timeout expired while waiting for NewRound event") -} - -func ensureNewTimeout(timeoutCh <-chan interface{}, timeout int64) { - timeoutDuration := time.Duration(timeout*5) * time.Nanosecond - ensureNewEvent(timeoutCh, timeoutDuration, - "Timeout expired while waiting for NewTimeout event") -} - -func ensureNewProposal(proposalCh <-chan interface{}) { - ensureNewEvent(proposalCh, ensureTimeout, - "Timeout expired while waiting for NewProposal event") -} - -func ensureNewBlock(blockCh <-chan interface{}) { - ensureNewEvent(blockCh, ensureTimeout, - "Timeout expired while waiting for NewBlock event") -} - -func ensureNewVote(voteCh <-chan interface{}) { - ensureNewEvent(voteCh, ensureTimeout, - "Timeout expired while waiting for NewVote event") -} - -func ensureNewUnlock(unlockCh <-chan interface{}) { - ensureNewEvent(unlockCh, ensureTimeout, - "Timeout expired while waiting for NewUnlock event") -} - -func ensureVote(voteCh chan interface{}, height int64, round int, - voteType byte) { +func ensureNewVote(voteCh <-chan interface{}, height int64, round int) { select { case <-time.After(ensureTimeout): break + case v := <-voteCh: + edv, ok := v.(types.EventDataVote) + if !ok { + panic(fmt.Sprintf("expected a *types.Vote, "+ + "got %v. wrong subscription channel?", + reflect.TypeOf(v))) + } + vote := edv.Vote + if vote.Height != height { + panic(fmt.Sprintf("expected height %v, got %v", height, vote.Height)) + } + if vote.Round != round { + panic(fmt.Sprintf("expected round %v, got %v", round, vote.Round)) + } + } +} + +func ensureNewRound(roundCh <-chan interface{}, height int64, round int) { + ensureNewEvent(roundCh, height, round, ensureTimeout, + "Timeout expired while waiting for NewRound event") +} + +func ensureNewTimeout(timeoutCh <-chan interface{}, height int64, round int, timeout int64) { + timeoutDuration := time.Duration(timeout*3) * time.Nanosecond + ensureNewEvent(timeoutCh, height, round, timeoutDuration, + "Timeout expired while waiting for NewTimeout event") +} + +func ensureNewProposal(proposalCh <-chan interface{}, height int64, round int) { + ensureNewEvent(proposalCh, height, round, ensureTimeout, + "Timeout expired while waiting for NewProposal event") +} + +func ensureNewBlock(blockCh <-chan interface{}, height int64) { + select { + case <-time.After(ensureTimeout): + panic("Timeout expired while waiting for NewBlock event") + case ev := <-blockCh: + block, ok := ev.(types.EventDataNewBlock) + if !ok { + panic(fmt.Sprintf("expected a *types.EventDataNewBlock, "+ + "got %v. wrong subscription channel?", + reflect.TypeOf(block))) + } + if block.Block.Height != height { + panic(fmt.Sprintf("expected height %v, got %v", height, block.Block.Height)) + } + } +} + +func ensureNewBlockHeader(blockCh <-chan interface{}, height int64, blockHash cmn.HexBytes) { + select { + case <-time.After(ensureTimeout): + panic("Timeout expired while waiting for NewBlockHeader event") + case ev := <-blockCh: + blockHeader, ok := ev.(types.EventDataNewBlockHeader) + if !ok { + panic(fmt.Sprintf("expected a *types.EventDataNewBlockHeader, "+ + "got %v. wrong subscription channel?", + reflect.TypeOf(blockHeader))) + } + if blockHeader.Header.Height != height { + panic(fmt.Sprintf("expected height %v, got %v", height, blockHeader.Header.Height)) + } + if !bytes.Equal(blockHeader.Header.Hash(), blockHash) { + panic(fmt.Sprintf("expected header %X, got %X", blockHash, blockHeader.Header.Hash())) + } + } +} + +func ensureNewUnlock(unlockCh <-chan interface{}, height int64, round int) { + ensureNewEvent(unlockCh, height, round, ensureTimeout, + "Timeout expired while waiting for NewUnlock event") +} + +func ensureVote(voteCh <-chan interface{}, height int64, round int, + voteType byte) { + select { + case <-time.After(ensureTimeout): + panic("Timeout expired while waiting for NewVote event") case v := <-voteCh: edv, ok := v.(types.EventDataVote) if !ok { @@ -398,6 +487,14 @@ func ensureVote(voteCh chan interface{}, height int64, round int, } } +func ensureNewEventOnChannel(ch <-chan interface{}) { + select { + case <-time.After(ensureTimeout): + panic("Timeout expired while waiting for new activity on the channel") + case <-ch: + } +} + //------------------------------------------------------------------------------- // consensus nets diff --git a/consensus/mempool_test.go b/consensus/mempool_test.go index 179766fd..ed97ae68 100644 --- a/consensus/mempool_test.go +++ b/consensus/mempool_test.go @@ -28,12 +28,12 @@ func TestMempoolNoProgressUntilTxsAvailable(t *testing.T) { newBlockCh := subscribe(cs.eventBus, types.EventQueryNewBlock) startTestRound(cs, height, round) - ensureNewStep(newBlockCh) // first block gets committed - ensureNoNewStep(newBlockCh) + ensureNewEventOnChannel(newBlockCh) // first block gets committed + ensureNoNewEventOnChannel(newBlockCh) deliverTxsRange(cs, 0, 1) - ensureNewStep(newBlockCh) // commit txs - ensureNewStep(newBlockCh) // commit updated app hash - ensureNoNewStep(newBlockCh) + ensureNewEventOnChannel(newBlockCh) // commit txs + ensureNewEventOnChannel(newBlockCh) // commit updated app hash + ensureNoNewEventOnChannel(newBlockCh) } func TestMempoolProgressAfterCreateEmptyBlocksInterval(t *testing.T) { @@ -46,9 +46,9 @@ func TestMempoolProgressAfterCreateEmptyBlocksInterval(t *testing.T) { newBlockCh := subscribe(cs.eventBus, types.EventQueryNewBlock) startTestRound(cs, height, round) - ensureNewStep(newBlockCh) // first block gets committed - ensureNoNewStep(newBlockCh) // then we dont make a block ... - ensureNewStep(newBlockCh) // until the CreateEmptyBlocksInterval has passed + ensureNewEventOnChannel(newBlockCh) // first block gets committed + ensureNoNewEventOnChannel(newBlockCh) // then we dont make a block ... + ensureNewEventOnChannel(newBlockCh) // until the CreateEmptyBlocksInterval has passed } func TestMempoolProgressInHigherRound(t *testing.T) { @@ -72,13 +72,19 @@ func TestMempoolProgressInHigherRound(t *testing.T) { } startTestRound(cs, height, round) - ensureNewStep(newRoundCh) // first round at first height - ensureNewStep(newBlockCh) // first block gets committed - ensureNewStep(newRoundCh) // first round at next height - deliverTxsRange(cs, 0, 1) // we deliver txs, but dont set a proposal so we get the next round - <-timeoutCh - ensureNewStep(newRoundCh) // wait for the next round - ensureNewStep(newBlockCh) // now we can commit the block + ensureNewRoundStep(newRoundCh, height, round) // first round at first height + ensureNewEventOnChannel(newBlockCh) // first block gets committed + + height = height + 1 // moving to the next height + round = 0 + + ensureNewRoundStep(newRoundCh, height, round) // first round at next height + deliverTxsRange(cs, 0, 1) // we deliver txs, but dont set a proposal so we get the next round + ensureNewTimeout(timeoutCh, height, round, cs.config.TimeoutPropose.Nanoseconds()) + + round = round + 1 // moving to the next round + ensureNewRoundStep(newRoundCh, height, round) // wait for the next round + ensureNewEventOnChannel(newBlockCh) // now we can commit the block } func deliverTxsRange(cs *ConsensusState, start, end int) { diff --git a/consensus/state.go b/consensus/state.go index 0100a150..022023ae 100644 --- a/consensus/state.go +++ b/consensus/state.go @@ -83,7 +83,8 @@ type ConsensusState struct { // internal state mtx sync.RWMutex cstypes.RoundState - state sm.State // State until height-1. + triggeredTimeoutPrecommit bool + state sm.State // State until height-1. // state changes may be triggered by: msgs from peers, // msgs from ourself, or by timeouts @@ -711,6 +712,7 @@ func (cs *ConsensusState) handleTimeout(ti timeoutInfo, rs cstypes.RoundState) { cs.enterPrecommit(ti.Height, ti.Round) case cstypes.RoundStepPrecommitWait: cs.eventBus.PublishEventTimeoutWait(cs.RoundStateEvent()) + cs.enterPrecommit(ti.Height, ti.Round) cs.enterNewRound(ti.Height, ti.Round+1) default: panic(fmt.Sprintf("Invalid timeout step: %v", ti.Step)) @@ -772,6 +774,7 @@ func (cs *ConsensusState) enterNewRound(height int64, round int) { cs.ProposalBlockParts = nil } cs.Votes.SetRound(round + 1) // also track next round (round+1) to allow round-skipping + cs.triggeredTimeoutPrecommit = false cs.eventBus.PublishEventNewRound(cs.RoundStateEvent()) cs.metrics.Rounds.Set(float64(round)) @@ -782,7 +785,8 @@ func (cs *ConsensusState) enterNewRound(height int64, round int) { waitForTxs := cs.config.WaitForTxs() && round == 0 && !cs.needProofBlock(height) if waitForTxs { if cs.config.CreateEmptyBlocksInterval > 0 { - cs.scheduleTimeout(cs.config.CreateEmptyBlocksInterval, height, round, cstypes.RoundStepNewRound) + cs.scheduleTimeout(cs.config.CreateEmptyBlocksInterval, height, round, + cstypes.RoundStepNewRound) } go cs.proposalHeartbeat(height, round) } else { @@ -1013,6 +1017,7 @@ func (cs *ConsensusState) enterPrevote(height int64, round int) { func (cs *ConsensusState) defaultDoPrevote(height int64, round int) { logger := cs.Logger.With("height", height, "round", round) + // If a block is locked, prevote that. if cs.LockedBlock != nil { logger.Info("enterPrevote: Block was locked") @@ -1171,8 +1176,12 @@ func (cs *ConsensusState) enterPrecommit(height int64, round int) { func (cs *ConsensusState) enterPrecommitWait(height int64, round int) { logger := cs.Logger.With("height", height, "round", round) - if cs.Height != height || round < cs.Round || (cs.Round == round && cstypes.RoundStepPrecommitWait <= cs.Step) { - logger.Debug(fmt.Sprintf("enterPrecommitWait(%v/%v): Invalid args. Current step: %v/%v/%v", height, round, cs.Height, cs.Round, cs.Step)) + if cs.Height != height || round < cs.Round || (cs.Round == round && cs.triggeredTimeoutPrecommit) { + logger.Debug( + fmt.Sprintf( + "enterPrecommitWait(%v/%v): Invalid args. "+ + "Current state is Height/Round: %v/%v/, triggeredTimeoutPrecommit:%v", + height, round, cs.Height, cs.Round, cs.triggeredTimeoutPrecommit)) return } if !cs.Votes.Precommits(round).HasTwoThirdsAny() { @@ -1182,7 +1191,7 @@ func (cs *ConsensusState) enterPrecommitWait(height int64, round int) { defer func() { // Done enterPrecommitWait: - cs.updateRoundStep(round, cstypes.RoundStepPrecommitWait) + cs.triggeredTimeoutPrecommit = true cs.newStep() }() @@ -1495,6 +1504,9 @@ func (cs *ConsensusState) addProposalBlockPart(msg *BlockPartMessage, peerID p2p if cs.Step <= cstypes.RoundStepPropose && cs.isProposalComplete() { // Move onto the next step cs.enterPrevote(height, cs.Round) + if hasTwoThirds { // this is optimisation as this will be triggered when prevote is added + cs.enterPrecommit(height, cs.Round) + } } else if cs.Step == cstypes.RoundStepCommit { // If we're waiting on the proposal block... cs.tryFinalizeCommit(height) @@ -1609,7 +1621,7 @@ func (cs *ConsensusState) addVote(vote *types.Vote, peerID p2p.ID) (added bool, // Update Valid* if we can. // NOTE: our proposal block may be nil or not what received a polka.. // TODO: we may want to still update the ValidBlock and obtain it via gossipping - if !blockID.IsZero() && + if len(blockID.Hash) != 0 && (cs.ValidRound < vote.Round) && (vote.Round <= cs.Round) && cs.ProposalBlock.HashesTo(blockID.Hash) { @@ -1621,14 +1633,14 @@ func (cs *ConsensusState) addVote(vote *types.Vote, peerID p2p.ID) (added bool, } } - // If +2/3 prevotes for *anything* for this or future round: - if cs.Round <= vote.Round && prevotes.HasTwoThirdsAny() { - // Round-skip over to PrevoteWait or goto Precommit. - cs.enterNewRound(height, vote.Round) // if the vote is ahead of us + // If +2/3 prevotes for *anything* for future round: + if cs.Round < vote.Round && prevotes.HasTwoThirdsAny() { + // Round-skip if there is any 2/3+ of votes ahead of us + cs.enterNewRound(height, vote.Round) + } else if cs.Round == vote.Round && cstypes.RoundStepPrevote <= cs.Step { // current round if prevotes.HasTwoThirdsMajority() { cs.enterPrecommit(height, vote.Round) - } else { - cs.enterPrevote(height, vote.Round) // if the vote is ahead of us + } else if prevotes.HasTwoThirdsAny() { cs.enterPrevoteWait(height, vote.Round) } } else if cs.Proposal != nil && 0 <= cs.Proposal.POLRound && cs.Proposal.POLRound == vote.Round { @@ -1641,21 +1653,25 @@ func (cs *ConsensusState) addVote(vote *types.Vote, peerID p2p.ID) (added bool, case types.VoteTypePrecommit: precommits := cs.Votes.Precommits(vote.Round) cs.Logger.Info("Added to precommit", "vote", vote, "precommits", precommits.StringShort()) + blockID, ok := precommits.TwoThirdsMajority() - if ok && len(blockID.Hash) != 0 { + if ok { // Executed as TwoThirdsMajority could be from a higher round cs.enterNewRound(height, vote.Round) cs.enterPrecommit(height, vote.Round) - cs.enterCommit(height, vote.Round) - - if cs.config.SkipTimeoutCommit && precommits.HasAll() { - cs.enterNewRound(cs.Height, 0) + if len(blockID.Hash) != 0 { + cs.enterCommit(height, vote.Round) + if cs.config.SkipTimeoutCommit && precommits.HasAll() { + cs.enterNewRound(cs.Height, 0) + } + } else { + cs.enterPrecommitWait(height, vote.Round) } } else if cs.Round <= vote.Round && precommits.HasTwoThirdsAny() { cs.enterNewRound(height, vote.Round) - cs.enterPrecommit(height, vote.Round) cs.enterPrecommitWait(height, vote.Round) } + default: panic(fmt.Sprintf("Unexpected vote type %X", vote.Type)) // go-wire should prevent this. } diff --git a/consensus/state_test.go b/consensus/state_test.go index e7d4b4fa..d80b0c8a 100644 --- a/consensus/state_test.go +++ b/consensus/state_test.go @@ -7,6 +7,7 @@ import ( "testing" "time" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" cstypes "github.com/tendermint/tendermint/consensus/types" @@ -68,7 +69,7 @@ func TestStateProposerSelection0(t *testing.T) { startTestRound(cs1, height, round) // Wait for new round so proposer is set. - ensureNewRound(newRoundCh) + ensureNewRound(newRoundCh, height, round) // Commit a block and ensure proposer for the next height is correct. prop := cs1.GetRoundState().Validators.GetProposer() @@ -77,13 +78,13 @@ func TestStateProposerSelection0(t *testing.T) { } // Wait for complete proposal. - ensureNewProposal(proposalCh) + ensureNewProposal(proposalCh, height, round) rs := cs1.GetRoundState() signAddVotes(cs1, types.VoteTypePrecommit, rs.ProposalBlock.Hash(), rs.ProposalBlockParts.Header(), vss[1:]...) // Wait for new round so next validator is set. - ensureNewRound(newRoundCh) + ensureNewRound(newRoundCh, height+1, 0) prop = cs1.GetRoundState().Validators.GetProposer() if !bytes.Equal(prop.Address, vss[1].GetAddress()) { @@ -94,27 +95,29 @@ func TestStateProposerSelection0(t *testing.T) { // Now let's do it all again, but starting from round 2 instead of 0 func TestStateProposerSelection2(t *testing.T) { cs1, vss := randConsensusState(4) // test needs more work for more than 3 validators - + height := cs1.Height newRoundCh := subscribe(cs1.eventBus, types.EventQueryNewRound) // this time we jump in at round 2 incrementRound(vss[1:]...) incrementRound(vss[1:]...) - startTestRound(cs1, cs1.Height, 2) - ensureNewRound(newRoundCh) // wait for the new round + round := 2 + startTestRound(cs1, height, round) + + ensureNewRound(newRoundCh, height, round) // wait for the new round // everyone just votes nil. we get a new proposer each round for i := 0; i < len(vss); i++ { prop := cs1.GetRoundState().Validators.GetProposer() - correctProposer := vss[(i+2)%len(vss)].GetAddress() + correctProposer := vss[(i+round)%len(vss)].GetAddress() if !bytes.Equal(prop.Address, correctProposer) { panic(fmt.Sprintf("expected RoundState.Validators.GetProposer() to be validator %d. Got %X", (i+2)%len(vss), prop.Address)) } rs := cs1.GetRoundState() signAddVotes(cs1, types.VoteTypePrecommit, nil, rs.ProposalBlockParts.Header(), vss[1:]...) - ensureNewRound(newRoundCh) // wait for the new round event each round + ensureNewRound(newRoundCh, height, i+round+1) // wait for the new round event each round incrementRound(vss[1:]...) } @@ -132,7 +135,7 @@ func TestStateEnterProposeNoPrivValidator(t *testing.T) { startTestRound(cs, height, round) // if we're not a validator, EnterPropose should timeout - ensureNewTimeout(timeoutCh, cs.config.TimeoutPropose.Nanoseconds()) + ensureNewTimeout(timeoutCh, height, round, cs.config.TimeoutPropose.Nanoseconds()) if cs.GetRoundState().Proposal != nil { t.Error("Expected to make no proposal, since no privValidator") @@ -152,7 +155,7 @@ func TestStateEnterProposeYesPrivValidator(t *testing.T) { cs.enterNewRound(height, round) cs.startRoutines(3) - ensureNewProposal(proposalCh) + ensureNewProposal(proposalCh, height, round) // Check that Proposal, ProposalBlock, ProposalBlockParts are set. rs := cs.GetRoundState() @@ -208,22 +211,19 @@ func TestStateBadProposal(t *testing.T) { startTestRound(cs1, height, round) // wait for proposal - ensureNewProposal(proposalCh) + ensureNewProposal(proposalCh, height, round) // wait for prevote - ensureNewVote(voteCh) - + ensureVote(voteCh, height, round, types.VoteTypePrevote) validatePrevote(t, cs1, round, vss[0], nil) // add bad prevote from vs2 and wait for it signAddVotes(cs1, types.VoteTypePrevote, propBlock.Hash(), propBlock.MakePartSet(partSize).Header(), vs2) - ensureNewVote(voteCh) + ensureVote(voteCh, height, round, types.VoteTypePrevote) // wait for precommit - ensureNewVote(voteCh) - + ensureVote(voteCh, height, round, types.VoteTypePrecommit) validatePrecommit(t, cs1, round, 0, vss[0], nil, nil) - signAddVotes(cs1, types.VoteTypePrecommit, propBlock.Hash(), propBlock.MakePartSet(partSize).Header(), vs2) } //---------------------------------------------------------------------------------------------------- @@ -246,21 +246,21 @@ func TestStateFullRound1(t *testing.T) { propCh := subscribe(cs.eventBus, types.EventQueryCompleteProposal) newRoundCh := subscribe(cs.eventBus, types.EventQueryNewRound) + // Maybe it would be better to call explicitly startRoutines(4) startTestRound(cs, height, round) - ensureNewRound(newRoundCh) + ensureNewRound(newRoundCh, height, round) - // grab proposal - re := <-propCh - propBlockHash := re.(types.EventDataRoundState).RoundState.(*cstypes.RoundState).ProposalBlock.Hash() + ensureNewProposal(propCh, height, round) + propBlockHash := cs.GetRoundState().ProposalBlock.Hash() - ensureNewVote(voteCh) // wait for prevote + ensureVote(voteCh, height, round, types.VoteTypePrevote) // wait for prevote validatePrevote(t, cs, round, vss[0], propBlockHash) - ensureNewVote(voteCh) // wait for precommit + ensureVote(voteCh, height, round, types.VoteTypePrecommit) // wait for precommit // we're going to roll right into new height - ensureNewRound(newRoundCh) + ensureNewRound(newRoundCh, height+1, 0) validateLastPrecommit(t, cs, vss[0], propBlockHash) } @@ -275,8 +275,8 @@ func TestStateFullRoundNil(t *testing.T) { cs.enterPrevote(height, round) cs.startRoutines(4) - ensureNewVote(voteCh) // prevote - ensureNewVote(voteCh) // precommit + ensureVote(voteCh, height, round, types.VoteTypePrevote) // prevote + ensureVote(voteCh, height, round, types.VoteTypePrecommit) // precommit // should prevote and precommit nil validatePrevoteAndPrecommit(t, cs, round, 0, vss[0], nil, nil) @@ -295,7 +295,7 @@ func TestStateFullRound2(t *testing.T) { // start round and wait for propose and prevote startTestRound(cs1, height, round) - ensureNewVote(voteCh) // prevote + ensureVote(voteCh, height, round, types.VoteTypePrevote) // prevote // we should be stuck in limbo waiting for more prevotes rs := cs1.GetRoundState() @@ -303,10 +303,9 @@ func TestStateFullRound2(t *testing.T) { // prevote arrives from vs2: signAddVotes(cs1, types.VoteTypePrevote, propBlockHash, propPartsHeader, vs2) - ensureNewVote(voteCh) - - ensureNewVote(voteCh) //precommit + ensureVote(voteCh, height, round, types.VoteTypePrevote) // prevote + ensureVote(voteCh, height, round, types.VoteTypePrecommit) //precommit // the proposed block should now be locked and our precommit added validatePrecommit(t, cs1, 0, 0, vss[0], propBlockHash, propBlockHash) @@ -314,10 +313,10 @@ func TestStateFullRound2(t *testing.T) { // precommit arrives from vs2: signAddVotes(cs1, types.VoteTypePrecommit, propBlockHash, propPartsHeader, vs2) - ensureNewVote(voteCh) + ensureVote(voteCh, height, round, types.VoteTypePrecommit) // wait to finish commit, propose in next height - ensureNewBlock(newBlockCh) + ensureNewBlock(newBlockCh, height) } //------------------------------------------------------------------------------------------ @@ -328,7 +327,7 @@ func TestStateFullRound2(t *testing.T) { func TestStateLockNoPOL(t *testing.T) { cs1, vss := randConsensusState(2) vs2 := vss[1] - height := cs1.Height + height, round := cs1.Height, cs1.Round partSize := types.BlockPartSizeBytes @@ -343,41 +342,43 @@ func TestStateLockNoPOL(t *testing.T) { */ // start round and wait for prevote - cs1.enterNewRound(height, 0) + cs1.enterNewRound(height, round) cs1.startRoutines(0) - re := <-proposalCh - rs := re.(types.EventDataRoundState).RoundState.(*cstypes.RoundState) - theBlockHash := rs.ProposalBlock.Hash() + ensureNewRound(newRoundCh, height, round) - ensureNewVote(voteCh) // prevote + ensureNewProposal(proposalCh, height, round) + roundState := cs1.GetRoundState() + theBlockHash := roundState.ProposalBlock.Hash() + thePartSetHeader := roundState.ProposalBlockParts.Header() + + ensureVote(voteCh, height, round, types.VoteTypePrevote) // prevote // we should now be stuck in limbo forever, waiting for more prevotes // prevote arrives from vs2: - signAddVotes(cs1, types.VoteTypePrevote, cs1.ProposalBlock.Hash(), cs1.ProposalBlockParts.Header(), vs2) - ensureNewVote(voteCh) // prevote - - ensureNewVote(voteCh) // precommit + signAddVotes(cs1, types.VoteTypePrevote, theBlockHash, thePartSetHeader, vs2) + ensureVote(voteCh, height, round, types.VoteTypePrevote) // prevote + ensureVote(voteCh, height, round, types.VoteTypePrecommit) // precommit // the proposed block should now be locked and our precommit added - validatePrecommit(t, cs1, 0, 0, vss[0], theBlockHash, theBlockHash) + validatePrecommit(t, cs1, round, round, vss[0], theBlockHash, theBlockHash) // we should now be stuck in limbo forever, waiting for more precommits // lets add one for a different block - // NOTE: in practice we should never get to a point where there are precommits for different blocks at the same round hash := make([]byte, len(theBlockHash)) copy(hash, theBlockHash) hash[0] = byte((hash[0] + 1) % 255) - signAddVotes(cs1, types.VoteTypePrecommit, hash, rs.ProposalBlock.MakePartSet(partSize).Header(), vs2) - ensureNewVote(voteCh) // precommit + signAddVotes(cs1, types.VoteTypePrecommit, hash, thePartSetHeader, vs2) + ensureVote(voteCh, height, round, types.VoteTypePrecommit) // precommit // (note we're entering precommit for a second time this round) // but with invalid args. then we enterPrecommitWait, and the timeout to new round - ensureNewTimeout(timeoutWaitCh, cs1.config.TimeoutPrecommit.Nanoseconds()) + ensureNewTimeout(timeoutWaitCh, height, round, cs1.config.TimeoutPrecommit.Nanoseconds()) /// - ensureNewRound(newRoundCh) + round = round + 1 // moving to the next round + ensureNewRound(newRoundCh, height, round) t.Log("#### ONTO ROUND 1") /* Round2 (cs1, B) // B B2 @@ -386,43 +387,42 @@ func TestStateLockNoPOL(t *testing.T) { incrementRound(vs2) // now we're on a new round and not the proposer, so wait for timeout - re = <-timeoutProposeCh - rs = re.(types.EventDataRoundState).RoundState.(*cstypes.RoundState) + ensureNewTimeout(timeoutProposeCh, height, round, cs1.config.TimeoutPropose.Nanoseconds()) + + rs := cs1.GetRoundState() if rs.ProposalBlock != nil { panic("Expected proposal block to be nil") } // wait to finish prevote - ensureNewVote(voteCh) - + ensureVote(voteCh, height, round, types.VoteTypePrevote) // we should have prevoted our locked block - validatePrevote(t, cs1, 1, vss[0], rs.LockedBlock.Hash()) + validatePrevote(t, cs1, round, vss[0], rs.LockedBlock.Hash()) // add a conflicting prevote from the other validator signAddVotes(cs1, types.VoteTypePrevote, hash, rs.LockedBlock.MakePartSet(partSize).Header(), vs2) - ensureNewVote(voteCh) + ensureVote(voteCh, height, round, types.VoteTypePrevote) // now we're going to enter prevote again, but with invalid args // and then prevote wait, which should timeout. then wait for precommit - ensureNewTimeout(timeoutWaitCh, cs1.config.TimeoutPrevote.Nanoseconds()) - - ensureNewVote(voteCh) // precommit + ensureNewTimeout(timeoutWaitCh, height, round, cs1.config.TimeoutPrevote.Nanoseconds()) + ensureVote(voteCh, height, round, types.VoteTypePrecommit) // precommit // the proposed block should still be locked and our precommit added // we should precommit nil and be locked on the proposal - validatePrecommit(t, cs1, 1, 0, vss[0], nil, theBlockHash) + validatePrecommit(t, cs1, round, 0, vss[0], nil, theBlockHash) // add conflicting precommit from vs2 - // NOTE: in practice we should never get to a point where there are precommits for different blocks at the same round signAddVotes(cs1, types.VoteTypePrecommit, hash, rs.LockedBlock.MakePartSet(partSize).Header(), vs2) - ensureNewVote(voteCh) + ensureVote(voteCh, height, round, types.VoteTypePrecommit) // (note we're entering precommit for a second time this round, but with invalid args // then we enterPrecommitWait and timeout into NewRound - ensureNewTimeout(timeoutWaitCh, cs1.config.TimeoutPrecommit.Nanoseconds()) + ensureNewTimeout(timeoutWaitCh, height, round, cs1.config.TimeoutPrecommit.Nanoseconds()) - ensureNewRound(newRoundCh) + round = round + 1 // entering new round + ensureNewRound(newRoundCh, height, round) t.Log("#### ONTO ROUND 2") /* Round3 (vs2, _) // B, B2 @@ -430,30 +430,29 @@ func TestStateLockNoPOL(t *testing.T) { incrementRound(vs2) - re = <-proposalCh - rs = re.(types.EventDataRoundState).RoundState.(*cstypes.RoundState) + ensureNewProposal(proposalCh, height, round) + rs = cs1.GetRoundState() // now we're on a new round and are the proposer if !bytes.Equal(rs.ProposalBlock.Hash(), rs.LockedBlock.Hash()) { panic(fmt.Sprintf("Expected proposal block to be locked block. Got %v, Expected %v", rs.ProposalBlock, rs.LockedBlock)) } - ensureNewVote(voteCh) // prevote - - validatePrevote(t, cs1, 2, vss[0], rs.LockedBlock.Hash()) + ensureVote(voteCh, height, round, types.VoteTypePrevote) // prevote + validatePrevote(t, cs1, round, vss[0], rs.LockedBlock.Hash()) signAddVotes(cs1, types.VoteTypePrevote, hash, rs.ProposalBlock.MakePartSet(partSize).Header(), vs2) - ensureNewVote(voteCh) + ensureVote(voteCh, height, round, types.VoteTypePrevote) - ensureNewTimeout(timeoutWaitCh, cs1.config.TimeoutPrevote.Nanoseconds()) - ensureNewVote(voteCh) // precommit + ensureNewTimeout(timeoutWaitCh, height, round, cs1.config.TimeoutPrevote.Nanoseconds()) + ensureVote(voteCh, height, round, types.VoteTypePrecommit) // precommit - validatePrecommit(t, cs1, 2, 0, vss[0], nil, theBlockHash) // precommit nil but be locked on proposal + validatePrecommit(t, cs1, round, 0, vss[0], nil, theBlockHash) // precommit nil but be locked on proposal signAddVotes(cs1, types.VoteTypePrecommit, hash, rs.ProposalBlock.MakePartSet(partSize).Header(), vs2) // NOTE: conflicting precommits at same height - ensureNewVote(voteCh) + ensureVote(voteCh, height, round, types.VoteTypePrecommit) - ensureNewTimeout(timeoutWaitCh, cs1.config.TimeoutPrecommit.Nanoseconds()) + ensureNewTimeout(timeoutWaitCh, height, round, cs1.config.TimeoutPrecommit.Nanoseconds()) cs2, _ := randConsensusState(2) // needed so generated block is different than locked block // before we time out into new round, set next proposal block @@ -464,7 +463,8 @@ func TestStateLockNoPOL(t *testing.T) { incrementRound(vs2) - ensureNewRound(newRoundCh) + round = round + 1 // entering new round + ensureNewRound(newRoundCh, height, round) t.Log("#### ONTO ROUND 3") /* Round4 (vs2, C) // B C // B C @@ -476,35 +476,34 @@ func TestStateLockNoPOL(t *testing.T) { t.Fatal(err) } - ensureNewProposal(proposalCh) - ensureNewVote(voteCh) // prevote - + ensureNewProposal(proposalCh, height, round) + ensureVote(voteCh, height, round, types.VoteTypePrevote) // prevote // prevote for locked block (not proposal) validatePrevote(t, cs1, 3, vss[0], cs1.LockedBlock.Hash()) + // prevote for proposed block signAddVotes(cs1, types.VoteTypePrevote, propBlock.Hash(), propBlock.MakePartSet(partSize).Header(), vs2) - ensureNewVote(voteCh) + ensureVote(voteCh, height, round, types.VoteTypePrevote) - ensureNewTimeout(timeoutWaitCh, cs1.config.TimeoutPrevote.Nanoseconds()) - ensureNewVote(voteCh) - - validatePrecommit(t, cs1, 3, 0, vss[0], nil, theBlockHash) // precommit nil but locked on proposal + ensureNewTimeout(timeoutWaitCh, height, round, cs1.config.TimeoutPrevote.Nanoseconds()) + ensureVote(voteCh, height, round, types.VoteTypePrecommit) + validatePrecommit(t, cs1, round, 0, vss[0], nil, theBlockHash) // precommit nil but locked on proposal signAddVotes(cs1, types.VoteTypePrecommit, propBlock.Hash(), propBlock.MakePartSet(partSize).Header(), vs2) // NOTE: conflicting precommits at same height - ensureNewVote(voteCh) + ensureVote(voteCh, height, round, types.VoteTypePrecommit) } // 4 vals, one precommits, other 3 polka at next round, so we unlock and precomit the polka func TestStateLockPOLRelock(t *testing.T) { cs1, vss := randConsensusState(4) vs2, vs3, vs4 := vss[1], vss[2], vss[3] + height, round := cs1.Height, cs1.Round partSize := types.BlockPartSizeBytes - timeoutProposeCh := subscribe(cs1.eventBus, types.EventQueryTimeoutPropose) timeoutWaitCh := subscribe(cs1.eventBus, types.EventQueryTimeoutWait) proposalCh := subscribe(cs1.eventBus, types.EventQueryCompleteProposal) - voteCh := subscribe(cs1.eventBus, types.EventQueryVote) + voteCh := subscribeToVoter(cs1, cs1.privValidator.GetAddress()) newRoundCh := subscribe(cs1.eventBus, types.EventQueryNewRound) newBlockCh := subscribe(cs1.eventBus, types.EventQueryNewBlockHeader) @@ -517,28 +516,25 @@ func TestStateLockPOLRelock(t *testing.T) { */ // start round and wait for propose and prevote - startTestRound(cs1, cs1.Height, 0) + startTestRound(cs1, height, round) - ensureNewRound(newRoundCh) - re := <-proposalCh - rs := re.(types.EventDataRoundState).RoundState.(*cstypes.RoundState) + ensureNewRound(newRoundCh, height, round) + ensureNewProposal(proposalCh, height, round) + rs := cs1.GetRoundState() theBlockHash := rs.ProposalBlock.Hash() + theBlockParts := rs.ProposalBlockParts.Header() - ensureNewVote(voteCh) // prevote + ensureVote(voteCh, height, round, types.VoteTypePrevote) // prevote - signAddVotes(cs1, types.VoteTypePrevote, cs1.ProposalBlock.Hash(), cs1.ProposalBlockParts.Header(), vs2, vs3, vs4) - // prevotes - discardFromChan(voteCh, 3) + signAddVotes(cs1, types.VoteTypePrevote, theBlockHash, theBlockParts, vs2, vs3, vs4) - ensureNewVote(voteCh) // our precommit + ensureVote(voteCh, height, round, types.VoteTypePrecommit) // our precommit // the proposed block should now be locked and our precommit added - validatePrecommit(t, cs1, 0, 0, vss[0], theBlockHash, theBlockHash) + validatePrecommit(t, cs1, round, round, vss[0], theBlockHash, theBlockHash) // add precommits from the rest signAddVotes(cs1, types.VoteTypePrecommit, nil, types.PartSetHeader{}, vs2, vs4) - signAddVotes(cs1, types.VoteTypePrecommit, cs1.ProposalBlock.Hash(), cs1.ProposalBlockParts.Header(), vs3) - // precommites - discardFromChan(voteCh, 3) + signAddVotes(cs1, types.VoteTypePrecommit, theBlockHash, theBlockParts, vs3) // before we timeout to the new round set the new proposal prop, propBlock := decideProposal(cs1, vs2, vs2.Height, vs2.Round+1) @@ -548,14 +544,15 @@ func TestStateLockPOLRelock(t *testing.T) { incrementRound(vs2, vs3, vs4) // timeout to new round - ensureNewTimeout(timeoutWaitCh, cs1.config.TimeoutPrecommit.Nanoseconds()) + ensureNewTimeout(timeoutWaitCh, height, round, cs1.config.TimeoutPrecommit.Nanoseconds()) + round = round + 1 // moving to the next round //XXX: this isnt guaranteed to get there before the timeoutPropose ... if err := cs1.SetProposalAndBlock(prop, propBlock, propBlockParts, "some peer"); err != nil { t.Fatal(err) } - ensureNewRound(newRoundCh) + ensureNewRound(newRoundCh, height, round) t.Log("### ONTO ROUND 1") /* @@ -566,60 +563,34 @@ func TestStateLockPOLRelock(t *testing.T) { // now we're on a new round and not the proposer // but we should receive the proposal - select { - case <-proposalCh: - case <-timeoutProposeCh: - <-proposalCh - } + ensureNewProposal(proposalCh, height, round) // go to prevote, prevote for locked block (not proposal), move on - ensureNewVote(voteCh) - validatePrevote(t, cs1, 0, vss[0], theBlockHash) + ensureVote(voteCh, height, round, types.VoteTypePrevote) + validatePrevote(t, cs1, round, vss[0], theBlockHash) // now lets add prevotes from everyone else for the new block signAddVotes(cs1, types.VoteTypePrevote, propBlockHash, propBlockParts.Header(), vs2, vs3, vs4) - // prevotes - discardFromChan(voteCh, 3) - - // now either we go to PrevoteWait or Precommit - select { - case <-timeoutWaitCh: // we're in PrevoteWait, go to Precommit - // XXX: there's no guarantee we see the polka, this might be a precommit for nil, - // in which case the test fails! - <-voteCh - case <-voteCh: // we went straight to Precommit - } + ensureVote(voteCh, height, round, types.VoteTypePrecommit) // we should have unlocked and locked on the new block - validatePrecommit(t, cs1, 1, 1, vss[0], propBlockHash, propBlockHash) + validatePrecommit(t, cs1, round, round, vss[0], propBlockHash, propBlockHash) signAddVotes(cs1, types.VoteTypePrecommit, propBlockHash, propBlockParts.Header(), vs2, vs3) - discardFromChan(voteCh, 2) + ensureNewBlockHeader(newBlockCh, height, propBlockHash) - be := <-newBlockCh - b := be.(types.EventDataNewBlockHeader) - re = <-newRoundCh - rs = re.(types.EventDataRoundState).RoundState.(*cstypes.RoundState) - if rs.Height != 2 { - panic("Expected height to increment") - } - - if !bytes.Equal(b.Header.Hash(), propBlockHash) { - panic("Expected new block to be proposal block") - } + ensureNewRound(newRoundCh, height+1, 0) } // 4 vals, one precommits, other 3 polka at next round, so we unlock and precomit the polka func TestStateLockPOLUnlock(t *testing.T) { cs1, vss := randConsensusState(4) vs2, vs3, vs4 := vss[1], vss[2], vss[3] - h := cs1.GetRoundState().Height - r := cs1.GetRoundState().Round + height, round := cs1.Height, cs1.Round partSize := types.BlockPartSizeBytes proposalCh := subscribe(cs1.eventBus, types.EventQueryCompleteProposal) - timeoutProposeCh := subscribe(cs1.eventBus, types.EventQueryTimeoutPropose) timeoutWaitCh := subscribe(cs1.eventBus, types.EventQueryTimeoutWait) newRoundCh := subscribe(cs1.eventBus, types.EventQueryNewRound) unlockCh := subscribe(cs1.eventBus, types.EventQueryUnlock) @@ -634,75 +605,72 @@ func TestStateLockPOLUnlock(t *testing.T) { */ // start round and wait for propose and prevote - startTestRound(cs1, h, r) - ensureNewRound(newRoundCh) - re := <-proposalCh - rs := re.(types.EventDataRoundState).RoundState.(*cstypes.RoundState) + startTestRound(cs1, height, round) + ensureNewRound(newRoundCh, height, round) + + ensureNewProposal(proposalCh, height, round) + rs := cs1.GetRoundState() theBlockHash := rs.ProposalBlock.Hash() + theBlockParts := rs.ProposalBlockParts.Header() - ensureVote(voteCh, h, r, types.VoteTypePrevote) + ensureVote(voteCh, height, round, types.VoteTypePrevote) + validatePrevote(t, cs1, round, vss[0], theBlockHash) - signAddVotes(cs1, types.VoteTypePrevote, cs1.ProposalBlock.Hash(), cs1.ProposalBlockParts.Header(), vs2, vs3, vs4) - - ensureVote(voteCh, h, r, types.VoteTypePrecommit) + signAddVotes(cs1, types.VoteTypePrevote, theBlockHash, theBlockParts, vs2, vs3, vs4) + ensureVote(voteCh, height, round, types.VoteTypePrecommit) // the proposed block should now be locked and our precommit added - validatePrecommit(t, cs1, r, 0, vss[0], theBlockHash, theBlockHash) + validatePrecommit(t, cs1, round, round, vss[0], theBlockHash, theBlockHash) rs = cs1.GetRoundState() // add precommits from the rest signAddVotes(cs1, types.VoteTypePrecommit, nil, types.PartSetHeader{}, vs2, vs4) - signAddVotes(cs1, types.VoteTypePrecommit, cs1.ProposalBlock.Hash(), cs1.ProposalBlockParts.Header(), vs3) + signAddVotes(cs1, types.VoteTypePrecommit, theBlockHash, theBlockParts, vs3) // before we time out into new round, set next proposal block prop, propBlock := decideProposal(cs1, vs2, vs2.Height, vs2.Round+1) propBlockParts := propBlock.MakePartSet(partSize) - incrementRound(vs2, vs3, vs4) - // timeout to new round - re = <-timeoutWaitCh - rs = re.(types.EventDataRoundState).RoundState.(*cstypes.RoundState) + ensureNewTimeout(timeoutWaitCh, height, round, cs1.config.TimeoutPrecommit.Nanoseconds()) + rs = cs1.GetRoundState() lockedBlockHash := rs.LockedBlock.Hash() - //XXX: this isnt guaranteed to get there before the timeoutPropose ... - if err := cs1.SetProposalAndBlock(prop, propBlock, propBlockParts, "some peer"); err != nil { - t.Fatal(err) - } + incrementRound(vs2, vs3, vs4) + round = round + 1 // moving to the next round - ensureNewRound(newRoundCh) + ensureNewRound(newRoundCh, height, round) t.Log("#### ONTO ROUND 1") /* Round2 (vs2, C) // B nil nil nil // nil nil nil _ cs1 unlocks! */ - - // now we're on a new round and not the proposer, - // but we should receive the proposal - select { - case <-proposalCh: - case <-timeoutProposeCh: - <-proposalCh + //XXX: this isnt guaranteed to get there before the timeoutPropose ... + if err := cs1.SetProposalAndBlock(prop, propBlock, propBlockParts, "some peer"); err != nil { + t.Fatal(err) } + ensureNewProposal(proposalCh, height, round) + // go to prevote, prevote for locked block (not proposal) - ensureVote(voteCh, h, r+1, types.VoteTypePrevote) - validatePrevote(t, cs1, 0, vss[0], lockedBlockHash) + ensureVote(voteCh, height, round, types.VoteTypePrevote) + validatePrevote(t, cs1, round, vss[0], lockedBlockHash) + // now lets add prevotes from everyone else for nil (a polka!) signAddVotes(cs1, types.VoteTypePrevote, nil, types.PartSetHeader{}, vs2, vs3, vs4) // the polka makes us unlock and precommit nil - ensureNewUnlock(unlockCh) - ensureVote(voteCh, h, r+1, types.VoteTypePrecommit) + ensureNewUnlock(unlockCh, height, round) + ensureVote(voteCh, height, round, types.VoteTypePrecommit) // we should have unlocked and committed nil // NOTE: since we don't relock on nil, the lock round is 0 - validatePrecommit(t, cs1, r+1, 0, vss[0], nil, nil) + validatePrecommit(t, cs1, round, 0, vss[0], nil, nil) signAddVotes(cs1, types.VoteTypePrecommit, nil, types.PartSetHeader{}, vs2, vs3) - ensureNewRound(newRoundCh) + ensureNewRound(newRoundCh, height, round+1) } // 4 vals @@ -712,8 +680,7 @@ func TestStateLockPOLUnlock(t *testing.T) { func TestStateLockPOLSafety1(t *testing.T) { cs1, vss := randConsensusState(4) vs2, vs3, vs4 := vss[1], vss[2], vss[3] - h := cs1.GetRoundState().Height - r := cs1.GetRoundState().Round + height, round := cs1.Height, cs1.Round partSize := types.BlockPartSizeBytes @@ -724,38 +691,28 @@ func TestStateLockPOLSafety1(t *testing.T) { voteCh := subscribeToVoter(cs1, cs1.privValidator.GetAddress()) // start round and wait for propose and prevote - startTestRound(cs1, cs1.Height, 0) - ensureNewRound(newRoundCh) - re := <-proposalCh - rs := re.(types.EventDataRoundState).RoundState.(*cstypes.RoundState) + startTestRound(cs1, cs1.Height, round) + ensureNewRound(newRoundCh, height, round) + + ensureNewProposal(proposalCh, height, round) + rs := cs1.GetRoundState() propBlock := rs.ProposalBlock - ensureVote(voteCh, h, r, types.VoteTypePrevote) - - validatePrevote(t, cs1, 0, vss[0], propBlock.Hash()) + ensureVote(voteCh, height, round, types.VoteTypePrevote) + validatePrevote(t, cs1, round, vss[0], propBlock.Hash()) // the others sign a polka but we don't see it prevotes := signVotes(types.VoteTypePrevote, propBlock.Hash(), propBlock.MakePartSet(partSize).Header(), vs2, vs3, vs4) - // before we time out into new round, set next proposer - // and next proposal block - - //TODO: Should we remove this? - /* - _, v1 := cs1.Validators.GetByAddress(vss[0].Address) - v1.VotingPower = 1 - if updated := cs1.Validators.Update(v1); !updated { - panic("failed to update validator") - }*/ - t.Logf("old prop hash %v", fmt.Sprintf("%X", propBlock.Hash())) // we do see them precommit nil signAddVotes(cs1, types.VoteTypePrecommit, nil, types.PartSetHeader{}, vs2, vs3, vs4) - ensureVote(voteCh, h, r, types.VoteTypePrecommit) + // cs1 precommit nil + ensureVote(voteCh, height, round, types.VoteTypePrecommit) + ensureNewTimeout(timeoutWaitCh, height, round, cs1.config.TimeoutPrecommit.Nanoseconds()) - ensureNewRound(newRoundCh) t.Log("### ONTO ROUND 1") prop, propBlock := decideProposal(cs1, vs2, vs2.Height, vs2.Round+1) @@ -764,6 +721,9 @@ func TestStateLockPOLSafety1(t *testing.T) { incrementRound(vs2, vs3, vs4) + round = round + 1 // moving to the next round + ensureNewRound(newRoundCh, height, round) + //XXX: this isnt guaranteed to get there before the timeoutPropose ... if err := cs1.SetProposalAndBlock(prop, propBlock, propBlockParts, "some peer"); err != nil { t.Fatal(err) @@ -773,39 +733,34 @@ func TestStateLockPOLSafety1(t *testing.T) { // a polka happened but we didn't see it! */ - // now we're on a new round and not the proposer, - // but we should receive the proposal - select { - case re = <-proposalCh: - case <-timeoutProposeCh: - re = <-proposalCh - } + ensureNewProposal(proposalCh, height, round) - rs = re.(types.EventDataRoundState).RoundState.(*cstypes.RoundState) + rs = cs1.GetRoundState() if rs.LockedBlock != nil { panic("we should not be locked!") } t.Logf("new prop hash %v", fmt.Sprintf("%X", propBlockHash)) + // go to prevote, prevote for proposal block - ensureVote(voteCh, h, r+1, types.VoteTypePrevote) - validatePrevote(t, cs1, 1, vss[0], propBlockHash) + ensureVote(voteCh, height, round, types.VoteTypePrevote) + validatePrevote(t, cs1, round, vss[0], propBlockHash) // now we see the others prevote for it, so we should lock on it signAddVotes(cs1, types.VoteTypePrevote, propBlockHash, propBlockParts.Header(), vs2, vs3, vs4) - ensureVote(voteCh, h, r+1, types.VoteTypePrecommit) - + ensureVote(voteCh, height, round, types.VoteTypePrecommit) // we should have precommitted - validatePrecommit(t, cs1, 1, 1, vss[0], propBlockHash, propBlockHash) + validatePrecommit(t, cs1, round, round, vss[0], propBlockHash, propBlockHash) - signAddVotes(cs1, types.VoteTypePrecommit, nil, types.PartSetHeader{}, vs2, vs3) + signAddVotes(cs1, types.VoteTypePrecommit, nil, types.PartSetHeader{}, vs2, vs3, vs4) - ensureNewTimeout(timeoutWaitCh, cs1.config.TimeoutPrecommit.Nanoseconds()) + ensureNewTimeout(timeoutWaitCh, height, round, cs1.config.TimeoutPrecommit.Nanoseconds()) incrementRound(vs2, vs3, vs4) + round = round + 1 // moving to the next round - ensureNewRound(newRoundCh) + ensureNewRound(newRoundCh, height, round) t.Log("### ONTO ROUND 2") /*Round3 @@ -813,22 +768,22 @@ func TestStateLockPOLSafety1(t *testing.T) { */ // timeout of propose - ensureNewTimeout(timeoutProposeCh, cs1.config.TimeoutPropose.Nanoseconds()) + ensureNewTimeout(timeoutProposeCh, height, round, cs1.config.TimeoutPropose.Nanoseconds()) // finish prevote - ensureVote(voteCh, h, r+2, types.VoteTypePrevote) - + ensureVote(voteCh, height, round, types.VoteTypePrevote) // we should prevote what we're locked on - validatePrevote(t, cs1, 2, vss[0], propBlockHash) + validatePrevote(t, cs1, round, vss[0], propBlockHash) newStepCh := subscribe(cs1.eventBus, types.EventQueryNewRoundStep) + // before prevotes from the previous round are added // add prevotes from the earlier round addVotes(cs1, prevotes...) t.Log("Done adding prevotes!") - ensureNoNewStep(newStepCh) + ensureNoNewRoundStep(newStepCh) } // 4 vals. @@ -841,13 +796,11 @@ func TestStateLockPOLSafety1(t *testing.T) { func TestStateLockPOLSafety2(t *testing.T) { cs1, vss := randConsensusState(4) vs2, vs3, vs4 := vss[1], vss[2], vss[3] - h := cs1.GetRoundState().Height - r := cs1.GetRoundState().Round + height, round := cs1.Height, cs1.Round partSize := types.BlockPartSizeBytes proposalCh := subscribe(cs1.eventBus, types.EventQueryCompleteProposal) - timeoutProposeCh := subscribe(cs1.eventBus, types.EventQueryTimeoutPropose) timeoutWaitCh := subscribe(cs1.eventBus, types.EventQueryTimeoutWait) newRoundCh := subscribe(cs1.eventBus, types.EventQueryNewRound) unlockCh := subscribe(cs1.eventBus, types.EventQueryUnlock) @@ -855,7 +808,7 @@ func TestStateLockPOLSafety2(t *testing.T) { // the block for R0: gets polkad but we miss it // (even though we signed it, shhh) - _, propBlock0 := decideProposal(cs1, vss[0], cs1.Height, cs1.Round) + _, propBlock0 := decideProposal(cs1, vss[0], height, round) propBlockHash0 := propBlock0.Hash() propBlockParts0 := propBlock0.MakePartSet(partSize) @@ -870,25 +823,25 @@ func TestStateLockPOLSafety2(t *testing.T) { incrementRound(vs2, vs3, vs4) - cs1.updateRoundStep(0, cstypes.RoundStepPrecommitWait) - + round = round + 1 // moving to the next round t.Log("### ONTO Round 1") // jump in at round 1 - startTestRound(cs1, h, r+1) - ensureNewRound(newRoundCh) + startTestRound(cs1, height, round) + ensureNewRound(newRoundCh, height, round) if err := cs1.SetProposalAndBlock(prop1, propBlock1, propBlockParts1, "some peer"); err != nil { t.Fatal(err) } - ensureNewProposal(proposalCh) + ensureNewProposal(proposalCh, height, round) - ensureVote(voteCh, h, r+1, types.VoteTypePrevote) + ensureVote(voteCh, height, round, types.VoteTypePrevote) + validatePrevote(t, cs1, round, vss[0], propBlockHash1) signAddVotes(cs1, types.VoteTypePrevote, propBlockHash1, propBlockParts1.Header(), vs2, vs3, vs4) - ensureVote(voteCh, h, r+1, types.VoteTypePrecommit) + ensureVote(voteCh, height, round, types.VoteTypePrecommit) // the proposed block should now be locked and our precommit added - validatePrecommit(t, cs1, 1, 1, vss[0], propBlockHash1, propBlockHash1) + validatePrecommit(t, cs1, round, round, vss[0], propBlockHash1, propBlockHash1) // add precommits from the rest signAddVotes(cs1, types.VoteTypePrecommit, nil, types.PartSetHeader{}, vs2, vs4) @@ -897,10 +850,11 @@ func TestStateLockPOLSafety2(t *testing.T) { incrementRound(vs2, vs3, vs4) // timeout of precommit wait to new round - ensureNewTimeout(timeoutWaitCh, cs1.config.TimeoutPrecommit.Nanoseconds()) + ensureNewTimeout(timeoutWaitCh, height, round, cs1.config.TimeoutPrecommit.Nanoseconds()) + round = round + 1 // moving to the next round // in round 2 we see the polkad block from round 0 - newProp := types.NewProposal(h, 2, propBlockParts0.Header(), 0, propBlockID1) + newProp := types.NewProposal(height, round, propBlockParts0.Header(), 0, propBlockID1) if err := vs3.SignProposal(config.ChainID(), newProp); err != nil { t.Fatal(err) } @@ -911,25 +865,16 @@ func TestStateLockPOLSafety2(t *testing.T) { // Add the pol votes addVotes(cs1, prevotes...) - ensureNewRound(newRoundCh) + ensureNewRound(newRoundCh, height, round) t.Log("### ONTO Round 2") /*Round2 // now we see the polka from round 1, but we shouldnt unlock */ + ensureNewProposal(proposalCh, height, round) - select { - case <-timeoutProposeCh: - <-proposalCh - case <-proposalCh: - } - - select { - case <-unlockCh: - panic("validator unlocked using an old polka") - case <-voteCh: - // prevote our locked block - } - validatePrevote(t, cs1, 2, vss[0], propBlockHash1) + ensureNoNewUnlock(unlockCh) + ensureVote(voteCh, height, round, types.VoteTypePrevote) + validatePrevote(t, cs1, round, vss[0], propBlockHash1) } @@ -939,18 +884,110 @@ func TestStateLockPOLSafety2(t *testing.T) { func TestWaitingTimeoutOnNilPolka(t *testing.T) { cs1, vss := randConsensusState(4) vs2, vs3, vs4 := vss[1], vss[2], vss[3] + height, round := cs1.Height, cs1.Round timeoutWaitCh := subscribe(cs1.eventBus, types.EventQueryTimeoutWait) newRoundCh := subscribe(cs1.eventBus, types.EventQueryNewRound) // start round - startTestRound(cs1, cs1.Height, 0) - ensureNewRound(newRoundCh) + startTestRound(cs1, height, round) + ensureNewRound(newRoundCh, height, round) signAddVotes(cs1, types.VoteTypePrecommit, nil, types.PartSetHeader{}, vs2, vs3, vs4) - ensureNewTimeout(timeoutWaitCh, cs1.config.TimeoutPrecommit.Nanoseconds()) - ensureNewRound(newRoundCh) + ensureNewTimeout(timeoutWaitCh, height, round, cs1.config.TimeoutPrecommit.Nanoseconds()) + ensureNewRound(newRoundCh, height, round+1) +} + +// 4 vals, 3 Prevotes for nil from the higher round. +// What we want: +// P0 waits for timeoutPropose in the next round before entering prevote +func TestWaitingTimeoutProposeOnNewRound(t *testing.T) { + cs1, vss := randConsensusState(4) + vs2, vs3, vs4 := vss[1], vss[2], vss[3] + height, round := cs1.Height, cs1.Round + + timeoutWaitCh := subscribe(cs1.eventBus, types.EventQueryTimeoutPropose) + newRoundCh := subscribe(cs1.eventBus, types.EventQueryNewRound) + voteCh := subscribeToVoter(cs1, cs1.privValidator.GetAddress()) + + // start round + startTestRound(cs1, height, round) + ensureNewRound(newRoundCh, height, round) + + ensureVote(voteCh, height, round, types.VoteTypePrevote) + + incrementRound(vss[1:]...) + signAddVotes(cs1, types.VoteTypePrevote, nil, types.PartSetHeader{}, vs2, vs3, vs4) + + round = round + 1 // moving to the next round + ensureNewRound(newRoundCh, height, round) + + rs := cs1.GetRoundState() + assert.True(t, rs.Step == cstypes.RoundStepPropose) // P0 does not prevote before timeoutPropose expires + + ensureNewTimeout(timeoutWaitCh, height, round, cs1.config.TimeoutPropose.Nanoseconds()) + + ensureVote(voteCh, height, round, types.VoteTypePrevote) + validatePrevote(t, cs1, round, vss[0], nil) +} + +// 4 vals, 3 Precommits for nil from the higher round. +// What we want: +// P0 jump to higher round, precommit and start precommit wait +func TestRoundSkipOnNilPolkaFromHigherRound(t *testing.T) { + cs1, vss := randConsensusState(4) + vs2, vs3, vs4 := vss[1], vss[2], vss[3] + height, round := cs1.Height, cs1.Round + + timeoutWaitCh := subscribe(cs1.eventBus, types.EventQueryTimeoutWait) + newRoundCh := subscribe(cs1.eventBus, types.EventQueryNewRound) + voteCh := subscribeToVoter(cs1, cs1.privValidator.GetAddress()) + + // start round + startTestRound(cs1, height, round) + ensureNewRound(newRoundCh, height, round) + + ensureVote(voteCh, height, round, types.VoteTypePrevote) + + incrementRound(vss[1:]...) + signAddVotes(cs1, types.VoteTypePrecommit, nil, types.PartSetHeader{}, vs2, vs3, vs4) + + round = round + 1 // moving to the next round + ensureNewRound(newRoundCh, height, round) + + ensureVote(voteCh, height, round, types.VoteTypePrecommit) + validatePrecommit(t, cs1, round, 0, vss[0], nil, nil) + + ensureNewTimeout(timeoutWaitCh, height, round, cs1.config.TimeoutPrecommit.Nanoseconds()) + + round = round + 1 // moving to the next round + ensureNewRound(newRoundCh, height, round) +} + +// 4 vals, 3 Prevotes for nil in the current round. +// What we want: +// P0 wait for timeoutPropose to expire before sending prevote. +func TestWaitTimeoutProposeOnNilPolkaForTheCurrentRound(t *testing.T) { + cs1, vss := randConsensusState(4) + vs2, vs3, vs4 := vss[1], vss[2], vss[3] + height, round := cs1.Height, 1 + + timeoutProposeCh := subscribe(cs1.eventBus, types.EventQueryTimeoutPropose) + newRoundCh := subscribe(cs1.eventBus, types.EventQueryNewRound) + voteCh := subscribeToVoter(cs1, cs1.privValidator.GetAddress()) + + // start round in which PO is not proposer + startTestRound(cs1, height, round) + ensureNewRound(newRoundCh, height, round) + + incrementRound(vss[1:]...) + signAddVotes(cs1, types.VoteTypePrevote, nil, types.PartSetHeader{}, vs2, vs3, vs4) + + ensureNewTimeout(timeoutProposeCh, height, round, cs1.config.TimeoutPropose.Nanoseconds()) + + ensureVote(voteCh, height, round, types.VoteTypePrevote) + validatePrevote(t, cs1, round, vss[0], nil) } //------------------------------------------------------------------------------------------ @@ -1041,8 +1078,7 @@ func TestStateSlashingPrecommits(t *testing.T) { func TestStateHalt1(t *testing.T) { cs1, vss := randConsensusState(4) vs2, vs3, vs4 := vss[1], vss[2], vss[3] - h := cs1.GetRoundState().Height - r := cs1.GetRoundState().Round + height, round := cs1.Height, cs1.Round partSize := types.BlockPartSizeBytes proposalCh := subscribe(cs1.eventBus, types.EventQueryCompleteProposal) @@ -1052,20 +1088,21 @@ func TestStateHalt1(t *testing.T) { voteCh := subscribeToVoter(cs1, cs1.privValidator.GetAddress()) // start round and wait for propose and prevote - startTestRound(cs1, cs1.Height, 0) - ensureNewRound(newRoundCh) - re := <-proposalCh - rs := re.(types.EventDataRoundState).RoundState.(*cstypes.RoundState) + startTestRound(cs1, height, round) + ensureNewRound(newRoundCh, height, round) + + ensureNewProposal(proposalCh, height, round) + rs := cs1.GetRoundState() propBlock := rs.ProposalBlock propBlockParts := propBlock.MakePartSet(partSize) - ensureVote(voteCh, h, r, types.VoteTypePrevote) + ensureVote(voteCh, height, round, types.VoteTypePrevote) - signAddVotes(cs1, types.VoteTypePrevote, propBlock.Hash(), propBlockParts.Header(), vs3, vs4) - ensureVote(voteCh, h, r, types.VoteTypePrecommit) + signAddVotes(cs1, types.VoteTypePrevote, propBlock.Hash(), propBlockParts.Header(), vs2, vs3, vs4) + ensureVote(voteCh, height, round, types.VoteTypePrecommit) // the proposed block should now be locked and our precommit added - validatePrecommit(t, cs1, 0, 0, vss[0], propBlock.Hash(), propBlock.Hash()) + validatePrecommit(t, cs1, round, round, vss[0], propBlock.Hash(), propBlock.Hash()) // add precommits from the rest signAddVotes(cs1, types.VoteTypePrecommit, nil, types.PartSetHeader{}, vs2) // didnt receive proposal @@ -1076,9 +1113,12 @@ func TestStateHalt1(t *testing.T) { incrementRound(vs2, vs3, vs4) // timeout to new round - ensureNewTimeout(timeoutWaitCh, cs1.config.TimeoutPrecommit.Nanoseconds()) - re = <-newRoundCh - rs = re.(types.EventDataRoundState).RoundState.(*cstypes.RoundState) + ensureNewTimeout(timeoutWaitCh, height, round, cs1.config.TimeoutPrecommit.Nanoseconds()) + + round = round + 1 // moving to the next round + + ensureNewRound(newRoundCh, height, round) + rs = cs1.GetRoundState() t.Log("### ONTO ROUND 1") /*Round2 @@ -1087,20 +1127,16 @@ func TestStateHalt1(t *testing.T) { */ // go to prevote, prevote for locked block - ensureVote(voteCh, h, r+1, types.VoteTypePrevote) - validatePrevote(t, cs1, 0, vss[0], rs.LockedBlock.Hash()) + ensureVote(voteCh, height, round, types.VoteTypePrevote) + validatePrevote(t, cs1, round, vss[0], rs.LockedBlock.Hash()) // now we receive the precommit from the previous round addVotes(cs1, precommit4) // receiving that precommit should take us straight to commit - ensureNewBlock(newBlockCh) - re = <-newRoundCh - rs = re.(types.EventDataRoundState).RoundState.(*cstypes.RoundState) + ensureNewBlock(newBlockCh, height) - if rs.Height != 2 { - panic("expected height to increment") - } + ensureNewRound(newRoundCh, height+1, 0) } func TestStateOutputsBlockPartsStats(t *testing.T) { @@ -1186,10 +1222,3 @@ func subscribe(eventBus *types.EventBus, q tmpubsub.Query) <-chan interface{} { } return out } - -// discardFromChan reads n values from the channel. -func discardFromChan(ch <-chan interface{}, n int) { - for i := 0; i < n; i++ { - <-ch - } -} diff --git a/node/node.go b/node/node.go index d1ab0f86..9c409787 100644 --- a/node/node.go +++ b/node/node.go @@ -106,7 +106,7 @@ func DefaultMetricsProvider(config *cfg.InstrumentationConfig) MetricsProvider { return func() (*cs.Metrics, *p2p.Metrics, *mempl.Metrics, *sm.Metrics) { if config.Prometheus { return cs.PrometheusMetrics(config.Namespace), p2p.PrometheusMetrics(config.Namespace), - mempl.PrometheusMetrics(config.Namespace), sm.PrometheusMetrics(config.Namespace) + mempl.PrometheusMetrics(config.Namespace), sm.PrometheusMetrics(config.Namespace) } return cs.NopMetrics(), p2p.NopMetrics(), mempl.NopMetrics(), sm.NopMetrics() } diff --git a/p2p/metrics.go b/p2p/metrics.go index 86a20505..b066fb31 100644 --- a/p2p/metrics.go +++ b/p2p/metrics.go @@ -56,7 +56,6 @@ func PrometheusMetrics(namespace string) *Metrics { Name: "num_txs", Help: "Number of transactions submitted by each peer.", }, []string{"peer_id"}), - } } diff --git a/state/execution.go b/state/execution.go index d5a1a161..611efa51 100644 --- a/state/execution.go +++ b/state/execution.go @@ -49,7 +49,7 @@ func BlockExecutorWithMetrics(metrics *Metrics) BlockExecutorOption { // NewBlockExecutor returns a new BlockExecutor with a NopEventBus. // Call SetEventBus to provide one. func NewBlockExecutor(db dbm.DB, logger log.Logger, proxyApp proxy.AppConnConsensus, - mempool Mempool, evpool EvidencePool, options ...BlockExecutorOption) *BlockExecutor { + mempool Mempool, evpool EvidencePool, options ...BlockExecutorOption) *BlockExecutor { res := &BlockExecutor{ db: db, proxyApp: proxyApp, @@ -95,7 +95,7 @@ func (blockExec *BlockExecutor) ApplyBlock(state State, blockID types.BlockID, b startTime := time.Now().UnixNano() abciResponses, err := execBlockOnProxyApp(blockExec.logger, blockExec.proxyApp, block, state.LastValidators, blockExec.db) endTime := time.Now().UnixNano() - blockExec.metrics.BlockProcessingTime.Observe(float64(endTime - startTime) / 1000000) + blockExec.metrics.BlockProcessingTime.Observe(float64(endTime-startTime) / 1000000) if err != nil { return state, ErrProxyAppConn(err) } @@ -198,11 +198,11 @@ func (blockExec *BlockExecutor) Commit( // Executes block's transactions on proxyAppConn. // Returns a list of transaction results and updates to the validator set func execBlockOnProxyApp( - logger log.Logger, - proxyAppConn proxy.AppConnConsensus, - block *types.Block, - lastValSet *types.ValidatorSet, - stateDB dbm.DB, + logger log.Logger, + proxyAppConn proxy.AppConnConsensus, + block *types.Block, + lastValSet *types.ValidatorSet, + stateDB dbm.DB, ) (*ABCIResponses, error) { var validTxs, invalidTxs = 0, 0 @@ -360,10 +360,10 @@ func updateValidators(currentSet *types.ValidatorSet, abciUpdates []abci.Validat // updateState returns a new State updated according to the header and responses. func updateState( - state State, - blockID types.BlockID, - header *types.Header, - abciResponses *ABCIResponses, + state State, + blockID types.BlockID, + header *types.Header, + abciResponses *ABCIResponses, ) (State, error) { // Copy the valset so we can apply changes from EndBlock @@ -448,11 +448,11 @@ func fireEvents(logger log.Logger, eventBus types.BlockEventPublisher, block *ty // ExecCommitBlock executes and commits a block on the proxyApp without validating or mutating the state. // It returns the application root hash (result of abci.Commit). func ExecCommitBlock( - appConnConsensus proxy.AppConnConsensus, - block *types.Block, - logger log.Logger, - lastValSet *types.ValidatorSet, - stateDB dbm.DB, + appConnConsensus proxy.AppConnConsensus, + block *types.Block, + logger log.Logger, + lastValSet *types.ValidatorSet, + stateDB dbm.DB, ) ([]byte, error) { _, err := execBlockOnProxyApp(logger, appConnConsensus, block, lastValSet, stateDB) if err != nil { diff --git a/state/metrics.go b/state/metrics.go index 7acbafa3..4e99753f 100644 --- a/state/metrics.go +++ b/state/metrics.go @@ -2,9 +2,9 @@ package state import ( "github.com/go-kit/kit/metrics" + "github.com/go-kit/kit/metrics/discard" "github.com/go-kit/kit/metrics/prometheus" stdprometheus "github.com/prometheus/client_golang/prometheus" - "github.com/go-kit/kit/metrics/discard" ) const MetricsSubsystem = "state" From 1b51cf3f4692f6c9157fcc5f5a71f203002a0a07 Mon Sep 17 00:00:00 2001 From: Dev Ojha Date: Fri, 12 Oct 2018 14:48:00 -0700 Subject: [PATCH 005/267] Remove unnecessary layer of indirection / unnecessary allocation of hashes (#2620) --- crypto/merkle/simple_tree.go | 31 +++++++++---------------------- 1 file changed, 9 insertions(+), 22 deletions(-) diff --git a/crypto/merkle/simple_tree.go b/crypto/merkle/simple_tree.go index 9677aef4..45e0c5c5 100644 --- a/crypto/merkle/simple_tree.go +++ b/crypto/merkle/simple_tree.go @@ -21,12 +21,16 @@ func SimpleHashFromTwoHashes(left, right []byte) []byte { // SimpleHashFromByteSlices computes a Merkle tree where the leaves are the byte slice, // in the provided order. func SimpleHashFromByteSlices(items [][]byte) []byte { - hashes := make([][]byte, len(items)) - for i, item := range items { - hash := tmhash.Sum(item) - hashes[i] = hash + switch len(items) { + case 0: + return nil + case 1: + return tmhash.Sum(items[0]) + default: + left := SimpleHashFromByteSlices(items[:(len(items)+1)/2]) + right := SimpleHashFromByteSlices(items[(len(items)+1)/2:]) + return SimpleHashFromTwoHashes(left, right) } - return simpleHashFromHashes(hashes) } // SimpleHashFromMap computes a Merkle tree from sorted map. @@ -40,20 +44,3 @@ func SimpleHashFromMap(m map[string][]byte) []byte { } return sm.Hash() } - -//---------------------------------------------------------------- - -// Expects hashes! -func simpleHashFromHashes(hashes [][]byte) []byte { - // Recursive impl. - switch len(hashes) { - case 0: - return nil - case 1: - return hashes[0] - default: - left := simpleHashFromHashes(hashes[:(len(hashes)+1)/2]) - right := simpleHashFromHashes(hashes[(len(hashes)+1)/2:]) - return SimpleHashFromTwoHashes(left, right) - } -} From 8888595b94d9b15df548b6ab7a08840fc43265d8 Mon Sep 17 00:00:00 2001 From: Ismail Khoffi Date: Sat, 13 Oct 2018 01:21:46 +0200 Subject: [PATCH 006/267] [R4R] Fixed sized and reordered fields for Vote/Proposal/Heartbeat SignBytes (#2598) * WIP: switching to fixed offsets for SignBytes * add version field to sign bytes and update order * more comments on test-cases and add a tc with a chainID * remove amino:"write_empty" tag - it doesn't affect if default fixed size fields ((u)int64) are written or not - add comment about int->int64 casting * update CHANGELOG_PENDING * update documentation * add back link to issue #1622 in documentation * remove JSON tags and add (failing test-case) * fix failing test * update test-vectors due to added `Type` field * change Type field from string to byte and add new type alias - SignedMsgType replaces VoteTypePrevote, VoteTypePrecommit and adds new ProposalType to separate votes from proposal when signed - update test-vectors * fix remains from rebasing * use SignMessageType instead of byte everywhere * fixes from review --- CHANGELOG_PENDING.md | 20 ++- consensus/byzantine_test.go | 4 +- consensus/common_test.go | 10 +- consensus/reactor.go | 44 +++--- consensus/replay_test.go | 2 +- consensus/state.go | 30 ++-- consensus/state_test.go | 196 ++++++++++++------------ consensus/types/height_vote_set.go | 18 +-- consensus/types/height_vote_set_test.go | 2 +- docs/spec/blockchain/blockchain.md | 5 +- docs/spec/blockchain/encoding.md | 21 +-- lite/helpers.go | 2 +- privval/priv_validator.go | 4 +- privval/priv_validator_test.go | 6 +- privval/socket_test.go | 4 +- state/execution_test.go | 4 +- types/block.go | 6 +- types/block_test.go | 16 +- types/canonical.go | 80 +++++----- types/evidence_test.go | 2 +- types/signed_msg_type.go | 27 ++++ types/test_util.go | 2 +- types/validator_set.go | 4 +- types/validator_set_test.go | 2 +- types/vote.go | 38 ++--- types/vote_set.go | 12 +- types/vote_set_test.go | 30 ++-- types/vote_test.go | 108 ++++++++++++- 28 files changed, 407 insertions(+), 292 deletions(-) create mode 100644 types/signed_msg_type.go diff --git a/CHANGELOG_PENDING.md b/CHANGELOG_PENDING.md index 0f919bda..f82ddbc2 100644 --- a/CHANGELOG_PENDING.md +++ b/CHANGELOG_PENDING.md @@ -24,11 +24,16 @@ BREAKING CHANGES: * [types] \#2298 Remove `Index` and `Total` fields from `TxProof`. * [crypto/merkle & lite] \#2298 Various changes to accomodate General Merkle trees * [crypto/merkle] \#2595 Remove all Hasher objects in favor of byte slices + * [types] \#2598 `VoteTypeXxx` are now * Blockchain Protocol - * [types] \#2459 `Vote`/`Proposal`/`Heartbeat` use amino encoding instead of JSON in `SignBytes`. + * [types] Update SignBytes for `Vote`/`Proposal`/`Heartbeat`: + * \#2459 Use amino encoding instead of JSON in `SignBytes`. + * \#2598 Reorder fields and use fixed sized encoding. + * \#2598 Change `Type` field fromt `string` to `byte` and use new + `SignedMsgType` to enumerate. * [types] \#2512 Remove the pubkey field from the validator hash - * [state] \#2587 require block.Time of the fist block to be genesis time + * [state] \#2587 Require block.Time of the fist block to be genesis time * P2P Protocol @@ -37,9 +42,10 @@ FEATURES: - [abci] \#2557 Add `Codespace` field to `Response{CheckTx, DeliverTx, Query}` IMPROVEMENTS: -- [consensus] [\#2169](https://github.com/cosmos/cosmos-sdk/issues/2169) add additional metrics -- [p2p] [\#2169](https://github.com/cosmos/cosmos-sdk/issues/2169) add additional metrics -- [config] \#2232 added ValidateBasic method, which performs basic checks +- Additional Metrics + - [consensus] [\#2169](https://github.com/cosmos/cosmos-sdk/issues/2169) + - [p2p] [\#2169](https://github.com/cosmos/cosmos-sdk/issues/2169) +- [config] \#2232 Added ValidateBasic method, which performs basic checks - [crypto/ed25519] \#2558 Switch to use latest `golang.org/x/crypto` through our fork at github.com/tendermint/crypto - [tools] \#2238 Binary dependencies are now locked to a specific git commit @@ -50,8 +56,8 @@ BUG FIXES: - [node] \#2434 Make node respond to signal interrupts while sleeping for genesis time - [consensus] [\#1690](https://github.com/tendermint/tendermint/issues/1690) wait for timeoutPrecommit before starting next round -- [consensus] [\#1745](https://github.com/tendermint/tendermint/issues/1745) wait for -Proposal or timeoutProposal before entering prevote +- [consensus] [\#1745](https://github.com/tendermint/tendermint/issues/1745) wait for +Proposal or timeoutProposal before entering prevote - [evidence] \#2515 fix db iter leak (@goolAdapter) - [common/bit_array] Fixed a bug in the `Or` function - [common/bit_array] Fixed a bug in the `Sub` function (@james-ray) diff --git a/consensus/byzantine_test.go b/consensus/byzantine_test.go index 3903e6b9..60c2b0db 100644 --- a/consensus/byzantine_test.go +++ b/consensus/byzantine_test.go @@ -226,8 +226,8 @@ func sendProposalAndParts(height int64, round int, cs *ConsensusState, peer p2p. // votes cs.mtx.Lock() - prevote, _ := cs.signVote(types.VoteTypePrevote, blockHash, parts.Header()) - precommit, _ := cs.signVote(types.VoteTypePrecommit, blockHash, parts.Header()) + prevote, _ := cs.signVote(types.PrevoteType, blockHash, parts.Header()) + precommit, _ := cs.signVote(types.PrecommitType, blockHash, parts.Header()) cs.mtx.Unlock() peer.Send(VoteChannel, cdc.MustMarshalBinaryBare(&VoteMessage{prevote})) diff --git a/consensus/common_test.go b/consensus/common_test.go index cf76e924..26f8e3e5 100644 --- a/consensus/common_test.go +++ b/consensus/common_test.go @@ -71,7 +71,7 @@ func NewValidatorStub(privValidator types.PrivValidator, valIndex int) *validato } } -func (vs *validatorStub) signVote(voteType byte, hash []byte, header types.PartSetHeader) (*types.Vote, error) { +func (vs *validatorStub) signVote(voteType types.SignedMsgType, hash []byte, header types.PartSetHeader) (*types.Vote, error) { vote := &types.Vote{ ValidatorIndex: vs.Index, ValidatorAddress: vs.PrivValidator.GetAddress(), @@ -86,7 +86,7 @@ func (vs *validatorStub) signVote(voteType byte, hash []byte, header types.PartS } // Sign vote for type/hash/header -func signVote(vs *validatorStub, voteType byte, hash []byte, header types.PartSetHeader) *types.Vote { +func signVote(vs *validatorStub, voteType types.SignedMsgType, hash []byte, header types.PartSetHeader) *types.Vote { v, err := vs.signVote(voteType, hash, header) if err != nil { panic(fmt.Errorf("failed to sign vote: %v", err)) @@ -94,7 +94,7 @@ func signVote(vs *validatorStub, voteType byte, hash []byte, header types.PartSe return v } -func signVotes(voteType byte, hash []byte, header types.PartSetHeader, vss ...*validatorStub) []*types.Vote { +func signVotes(voteType types.SignedMsgType, hash []byte, header types.PartSetHeader, vss ...*validatorStub) []*types.Vote { votes := make([]*types.Vote, len(vss)) for i, vs := range vss { votes[i] = signVote(vs, voteType, hash, header) @@ -144,7 +144,7 @@ func addVotes(to *ConsensusState, votes ...*types.Vote) { } } -func signAddVotes(to *ConsensusState, voteType byte, hash []byte, header types.PartSetHeader, vss ...*validatorStub) { +func signAddVotes(to *ConsensusState, voteType types.SignedMsgType, hash []byte, header types.PartSetHeader, vss ...*validatorStub) { votes := signVotes(voteType, hash, header, vss...) addVotes(to, votes...) } @@ -463,7 +463,7 @@ func ensureNewUnlock(unlockCh <-chan interface{}, height int64, round int) { } func ensureVote(voteCh <-chan interface{}, height int64, round int, - voteType byte) { + voteType types.SignedMsgType) { select { case <-time.After(ensureTimeout): panic("Timeout expired while waiting for NewVote event") diff --git a/consensus/reactor.go b/consensus/reactor.go index ca63e899..6643273c 100644 --- a/consensus/reactor.go +++ b/consensus/reactor.go @@ -237,9 +237,9 @@ func (conR *ConsensusReactor) Receive(chID byte, src p2p.Peer, msgBytes []byte) // (and consequently shows which we don't have) var ourVotes *cmn.BitArray switch msg.Type { - case types.VoteTypePrevote: + case types.PrevoteType: ourVotes = votes.Prevotes(msg.Round).BitArrayByBlockID(msg.BlockID) - case types.VoteTypePrecommit: + case types.PrecommitType: ourVotes = votes.Precommits(msg.Round).BitArrayByBlockID(msg.BlockID) default: conR.Logger.Error("Bad VoteSetBitsMessage field Type") @@ -317,9 +317,9 @@ func (conR *ConsensusReactor) Receive(chID byte, src p2p.Peer, msgBytes []byte) if height == msg.Height { var ourVotes *cmn.BitArray switch msg.Type { - case types.VoteTypePrevote: + case types.PrevoteType: ourVotes = votes.Prevotes(msg.Round).BitArrayByBlockID(msg.BlockID) - case types.VoteTypePrecommit: + case types.PrecommitType: ourVotes = votes.Precommits(msg.Round).BitArrayByBlockID(msg.BlockID) default: conR.Logger.Error("Bad VoteSetBitsMessage field Type") @@ -739,7 +739,7 @@ OUTER_LOOP: peer.TrySend(StateChannel, cdc.MustMarshalBinaryBare(&VoteSetMaj23Message{ Height: prs.Height, Round: prs.Round, - Type: types.VoteTypePrevote, + Type: types.PrevoteType, BlockID: maj23, })) time.Sleep(conR.conS.config.PeerQueryMaj23SleepDuration) @@ -756,7 +756,7 @@ OUTER_LOOP: peer.TrySend(StateChannel, cdc.MustMarshalBinaryBare(&VoteSetMaj23Message{ Height: prs.Height, Round: prs.Round, - Type: types.VoteTypePrecommit, + Type: types.PrecommitType, BlockID: maj23, })) time.Sleep(conR.conS.config.PeerQueryMaj23SleepDuration) @@ -773,7 +773,7 @@ OUTER_LOOP: peer.TrySend(StateChannel, cdc.MustMarshalBinaryBare(&VoteSetMaj23Message{ Height: prs.Height, Round: prs.ProposalPOLRound, - Type: types.VoteTypePrevote, + Type: types.PrevoteType, BlockID: maj23, })) time.Sleep(conR.conS.config.PeerQueryMaj23SleepDuration) @@ -792,7 +792,7 @@ OUTER_LOOP: peer.TrySend(StateChannel, cdc.MustMarshalBinaryBare(&VoteSetMaj23Message{ Height: prs.Height, Round: commit.Round(), - Type: types.VoteTypePrecommit, + Type: types.PrecommitType, BlockID: commit.BlockID, })) time.Sleep(conR.conS.config.PeerQueryMaj23SleepDuration) @@ -1022,7 +1022,7 @@ func (ps *PeerState) PickVoteToSend(votes types.VoteSetReader) (vote *types.Vote return nil, false } - height, round, type_, size := votes.Height(), votes.Round(), votes.Type(), votes.Size() + height, round, type_, size := votes.Height(), votes.Round(), types.SignedMsgType(votes.Type()), votes.Size() // Lazily set data using 'votes'. if votes.IsCommit() { @@ -1041,7 +1041,7 @@ func (ps *PeerState) PickVoteToSend(votes types.VoteSetReader) (vote *types.Vote return nil, false } -func (ps *PeerState) getVoteBitArray(height int64, round int, type_ byte) *cmn.BitArray { +func (ps *PeerState) getVoteBitArray(height int64, round int, type_ types.SignedMsgType) *cmn.BitArray { if !types.IsVoteTypeValid(type_) { return nil } @@ -1049,25 +1049,25 @@ func (ps *PeerState) getVoteBitArray(height int64, round int, type_ byte) *cmn.B if ps.PRS.Height == height { if ps.PRS.Round == round { switch type_ { - case types.VoteTypePrevote: + case types.PrevoteType: return ps.PRS.Prevotes - case types.VoteTypePrecommit: + case types.PrecommitType: return ps.PRS.Precommits } } if ps.PRS.CatchupCommitRound == round { switch type_ { - case types.VoteTypePrevote: + case types.PrevoteType: return nil - case types.VoteTypePrecommit: + case types.PrecommitType: return ps.PRS.CatchupCommit } } if ps.PRS.ProposalPOLRound == round { switch type_ { - case types.VoteTypePrevote: + case types.PrevoteType: return ps.PRS.ProposalPOL - case types.VoteTypePrecommit: + case types.PrecommitType: return nil } } @@ -1076,9 +1076,9 @@ func (ps *PeerState) getVoteBitArray(height int64, round int, type_ byte) *cmn.B if ps.PRS.Height == height+1 { if ps.PRS.LastCommitRound == round { switch type_ { - case types.VoteTypePrevote: + case types.PrevoteType: return nil - case types.VoteTypePrecommit: + case types.PrecommitType: return ps.PRS.LastCommit } } @@ -1187,7 +1187,7 @@ func (ps *PeerState) SetHasVote(vote *types.Vote) { ps.setHasVote(vote.Height, vote.Round, vote.Type, vote.ValidatorIndex) } -func (ps *PeerState) setHasVote(height int64, round int, type_ byte, index int) { +func (ps *PeerState) setHasVote(height int64, round int, type_ types.SignedMsgType, index int) { logger := ps.logger.With("peerH/R", fmt.Sprintf("%d/%d", ps.PRS.Height, ps.PRS.Round), "H/R", fmt.Sprintf("%d/%d", height, round)) logger.Debug("setHasVote", "type", type_, "index", index) @@ -1453,7 +1453,7 @@ func (m *VoteMessage) String() string { type HasVoteMessage struct { Height int64 Round int - Type byte + Type types.SignedMsgType Index int } @@ -1468,7 +1468,7 @@ func (m *HasVoteMessage) String() string { type VoteSetMaj23Message struct { Height int64 Round int - Type byte + Type types.SignedMsgType BlockID types.BlockID } @@ -1483,7 +1483,7 @@ func (m *VoteSetMaj23Message) String() string { type VoteSetBitsMessage struct { Height int64 Round int - Type byte + Type types.SignedMsgType BlockID types.BlockID Votes *cmn.BitArray } diff --git a/consensus/replay_test.go b/consensus/replay_test.go index 7a828da6..160e777c 100644 --- a/consensus/replay_test.go +++ b/consensus/replay_test.go @@ -542,7 +542,7 @@ func makeBlockchainFromWAL(wal WAL) ([]*types.Block, []*types.Commit, error) { return nil, nil, err } case *types.Vote: - if p.Type == types.VoteTypePrecommit { + if p.Type == types.PrecommitType { thisBlockCommit = &types.Commit{ BlockID: p.BlockID, Precommits: []*types.Vote{p}, diff --git a/consensus/state.go b/consensus/state.go index 022023ae..37047aa3 100644 --- a/consensus/state.go +++ b/consensus/state.go @@ -460,7 +460,7 @@ func (cs *ConsensusState) reconstructLastCommit(state sm.State) { return } seenCommit := cs.blockStore.LoadSeenCommit(state.LastBlockHeight) - lastPrecommits := types.NewVoteSet(state.ChainID, state.LastBlockHeight, seenCommit.Round(), types.VoteTypePrecommit, state.LastValidators) + lastPrecommits := types.NewVoteSet(state.ChainID, state.LastBlockHeight, seenCommit.Round(), types.PrecommitType, state.LastValidators) for _, precommit := range seenCommit.Precommits { if precommit == nil { continue @@ -1021,14 +1021,14 @@ func (cs *ConsensusState) defaultDoPrevote(height int64, round int) { // If a block is locked, prevote that. if cs.LockedBlock != nil { logger.Info("enterPrevote: Block was locked") - cs.signAddVote(types.VoteTypePrevote, cs.LockedBlock.Hash(), cs.LockedBlockParts.Header()) + cs.signAddVote(types.PrevoteType, cs.LockedBlock.Hash(), cs.LockedBlockParts.Header()) return } // If ProposalBlock is nil, prevote nil. if cs.ProposalBlock == nil { logger.Info("enterPrevote: ProposalBlock is nil") - cs.signAddVote(types.VoteTypePrevote, nil, types.PartSetHeader{}) + cs.signAddVote(types.PrevoteType, nil, types.PartSetHeader{}) return } @@ -1037,7 +1037,7 @@ func (cs *ConsensusState) defaultDoPrevote(height int64, round int) { if err != nil { // ProposalBlock is invalid, prevote nil. logger.Error("enterPrevote: ProposalBlock is invalid", "err", err) - cs.signAddVote(types.VoteTypePrevote, nil, types.PartSetHeader{}) + cs.signAddVote(types.PrevoteType, nil, types.PartSetHeader{}) return } @@ -1045,7 +1045,7 @@ func (cs *ConsensusState) defaultDoPrevote(height int64, round int) { // NOTE: the proposal signature is validated when it is received, // and the proposal block parts are validated as they are received (against the merkle hash in the proposal) logger.Info("enterPrevote: ProposalBlock is valid") - cs.signAddVote(types.VoteTypePrevote, cs.ProposalBlock.Hash(), cs.ProposalBlockParts.Header()) + cs.signAddVote(types.PrevoteType, cs.ProposalBlock.Hash(), cs.ProposalBlockParts.Header()) } // Enter: any +2/3 prevotes at next round. @@ -1103,7 +1103,7 @@ func (cs *ConsensusState) enterPrecommit(height int64, round int) { } else { logger.Info("enterPrecommit: No +2/3 prevotes during enterPrecommit. Precommitting nil.") } - cs.signAddVote(types.VoteTypePrecommit, nil, types.PartSetHeader{}) + cs.signAddVote(types.PrecommitType, nil, types.PartSetHeader{}) return } @@ -1127,7 +1127,7 @@ func (cs *ConsensusState) enterPrecommit(height int64, round int) { cs.LockedBlockParts = nil cs.eventBus.PublishEventUnlock(cs.RoundStateEvent()) } - cs.signAddVote(types.VoteTypePrecommit, nil, types.PartSetHeader{}) + cs.signAddVote(types.PrecommitType, nil, types.PartSetHeader{}) return } @@ -1138,7 +1138,7 @@ func (cs *ConsensusState) enterPrecommit(height int64, round int) { logger.Info("enterPrecommit: +2/3 prevoted locked block. Relocking") cs.LockedRound = round cs.eventBus.PublishEventRelock(cs.RoundStateEvent()) - cs.signAddVote(types.VoteTypePrecommit, blockID.Hash, blockID.PartsHeader) + cs.signAddVote(types.PrecommitType, blockID.Hash, blockID.PartsHeader) return } @@ -1153,7 +1153,7 @@ func (cs *ConsensusState) enterPrecommit(height int64, round int) { cs.LockedBlock = cs.ProposalBlock cs.LockedBlockParts = cs.ProposalBlockParts cs.eventBus.PublishEventLock(cs.RoundStateEvent()) - cs.signAddVote(types.VoteTypePrecommit, blockID.Hash, blockID.PartsHeader) + cs.signAddVote(types.PrecommitType, blockID.Hash, blockID.PartsHeader) return } @@ -1169,7 +1169,7 @@ func (cs *ConsensusState) enterPrecommit(height int64, round int) { cs.ProposalBlockParts = types.NewPartSetFromHeader(blockID.PartsHeader) } cs.eventBus.PublishEventUnlock(cs.RoundStateEvent()) - cs.signAddVote(types.VoteTypePrecommit, nil, types.PartSetHeader{}) + cs.signAddVote(types.PrecommitType, nil, types.PartSetHeader{}) } // Enter: any +2/3 precommits for next round. @@ -1550,7 +1550,7 @@ func (cs *ConsensusState) addVote(vote *types.Vote, peerID p2p.ID) (added bool, // A precommit for the previous height? // These come in while we wait timeoutCommit if vote.Height+1 == cs.Height { - if !(cs.Step == cstypes.RoundStepNewHeight && vote.Type == types.VoteTypePrecommit) { + if !(cs.Step == cstypes.RoundStepNewHeight && vote.Type == types.PrecommitType) { // TODO: give the reason .. // fmt.Errorf("tryAddVote: Wrong height, not a LastCommit straggler commit.") return added, ErrVoteHeightMismatch @@ -1593,7 +1593,7 @@ func (cs *ConsensusState) addVote(vote *types.Vote, peerID p2p.ID) (added bool, cs.evsw.FireEvent(types.EventVote, vote) switch vote.Type { - case types.VoteTypePrevote: + case types.PrevoteType: prevotes := cs.Votes.Prevotes(vote.Round) cs.Logger.Info("Added to prevote", "vote", vote, "prevotes", prevotes.StringShort()) @@ -1650,7 +1650,7 @@ func (cs *ConsensusState) addVote(vote *types.Vote, peerID p2p.ID) (added bool, } } - case types.VoteTypePrecommit: + case types.PrecommitType: precommits := cs.Votes.Precommits(vote.Round) cs.Logger.Info("Added to precommit", "vote", vote, "precommits", precommits.StringShort()) @@ -1679,7 +1679,7 @@ func (cs *ConsensusState) addVote(vote *types.Vote, peerID p2p.ID) (added bool, return } -func (cs *ConsensusState) signVote(type_ byte, hash []byte, header types.PartSetHeader) (*types.Vote, error) { +func (cs *ConsensusState) signVote(type_ types.SignedMsgType, hash []byte, header types.PartSetHeader) (*types.Vote, error) { addr := cs.privValidator.GetAddress() valIndex, _ := cs.Validators.GetByAddress(addr) @@ -1714,7 +1714,7 @@ func (cs *ConsensusState) voteTime() time.Time { } // sign the vote and publish on internalMsgQueue -func (cs *ConsensusState) signAddVote(type_ byte, hash []byte, header types.PartSetHeader) *types.Vote { +func (cs *ConsensusState) signAddVote(type_ types.SignedMsgType, hash []byte, header types.PartSetHeader) *types.Vote { // if we don't have a key or we're not in the validator set, do nothing if cs.privValidator == nil || !cs.Validators.HasAddress(cs.privValidator.GetAddress()) { return nil diff --git a/consensus/state_test.go b/consensus/state_test.go index d80b0c8a..229d7e7b 100644 --- a/consensus/state_test.go +++ b/consensus/state_test.go @@ -81,7 +81,7 @@ func TestStateProposerSelection0(t *testing.T) { ensureNewProposal(proposalCh, height, round) rs := cs1.GetRoundState() - signAddVotes(cs1, types.VoteTypePrecommit, rs.ProposalBlock.Hash(), rs.ProposalBlockParts.Header(), vss[1:]...) + signAddVotes(cs1, types.PrecommitType, rs.ProposalBlock.Hash(), rs.ProposalBlockParts.Header(), vss[1:]...) // Wait for new round so next validator is set. ensureNewRound(newRoundCh, height+1, 0) @@ -116,7 +116,7 @@ func TestStateProposerSelection2(t *testing.T) { } rs := cs1.GetRoundState() - signAddVotes(cs1, types.VoteTypePrecommit, nil, rs.ProposalBlockParts.Header(), vss[1:]...) + signAddVotes(cs1, types.PrecommitType, nil, rs.ProposalBlockParts.Header(), vss[1:]...) ensureNewRound(newRoundCh, height, i+round+1) // wait for the new round event each round incrementRound(vss[1:]...) } @@ -214,16 +214,17 @@ func TestStateBadProposal(t *testing.T) { ensureNewProposal(proposalCh, height, round) // wait for prevote - ensureVote(voteCh, height, round, types.VoteTypePrevote) + ensureVote(voteCh, height, round, types.PrevoteType) validatePrevote(t, cs1, round, vss[0], nil) // add bad prevote from vs2 and wait for it - signAddVotes(cs1, types.VoteTypePrevote, propBlock.Hash(), propBlock.MakePartSet(partSize).Header(), vs2) - ensureVote(voteCh, height, round, types.VoteTypePrevote) + signAddVotes(cs1, types.PrevoteType, propBlock.Hash(), propBlock.MakePartSet(partSize).Header(), vs2) + ensureVote(voteCh, height, round, types.PrevoteType) // wait for precommit - ensureVote(voteCh, height, round, types.VoteTypePrecommit) + ensureVote(voteCh, height, round, types.PrecommitType) validatePrecommit(t, cs1, round, 0, vss[0], nil, nil) + signAddVotes(cs1, types.PrecommitType, propBlock.Hash(), propBlock.MakePartSet(partSize).Header(), vs2) } //---------------------------------------------------------------------------------------------------- @@ -254,10 +255,10 @@ func TestStateFullRound1(t *testing.T) { ensureNewProposal(propCh, height, round) propBlockHash := cs.GetRoundState().ProposalBlock.Hash() - ensureVote(voteCh, height, round, types.VoteTypePrevote) // wait for prevote + ensureVote(voteCh, height, round, types.PrevoteType) // wait for prevote validatePrevote(t, cs, round, vss[0], propBlockHash) - ensureVote(voteCh, height, round, types.VoteTypePrecommit) // wait for precommit + ensureVote(voteCh, height, round, types.PrecommitType) // wait for precommit // we're going to roll right into new height ensureNewRound(newRoundCh, height+1, 0) @@ -275,8 +276,8 @@ func TestStateFullRoundNil(t *testing.T) { cs.enterPrevote(height, round) cs.startRoutines(4) - ensureVote(voteCh, height, round, types.VoteTypePrevote) // prevote - ensureVote(voteCh, height, round, types.VoteTypePrecommit) // precommit + ensureVote(voteCh, height, round, types.PrevoteType) // prevote + ensureVote(voteCh, height, round, types.PrecommitType) // precommit // should prevote and precommit nil validatePrevoteAndPrecommit(t, cs, round, 0, vss[0], nil, nil) @@ -295,25 +296,25 @@ func TestStateFullRound2(t *testing.T) { // start round and wait for propose and prevote startTestRound(cs1, height, round) - ensureVote(voteCh, height, round, types.VoteTypePrevote) // prevote + ensureVote(voteCh, height, round, types.PrevoteType) // prevote // we should be stuck in limbo waiting for more prevotes rs := cs1.GetRoundState() propBlockHash, propPartsHeader := rs.ProposalBlock.Hash(), rs.ProposalBlockParts.Header() // prevote arrives from vs2: - signAddVotes(cs1, types.VoteTypePrevote, propBlockHash, propPartsHeader, vs2) - ensureVote(voteCh, height, round, types.VoteTypePrevote) // prevote + signAddVotes(cs1, types.PrevoteType, propBlockHash, propPartsHeader, vs2) + ensureVote(voteCh, height, round, types.PrevoteType) // prevote - ensureVote(voteCh, height, round, types.VoteTypePrecommit) //precommit + ensureVote(voteCh, height, round, types.PrecommitType) //precommit // the proposed block should now be locked and our precommit added validatePrecommit(t, cs1, 0, 0, vss[0], propBlockHash, propBlockHash) // we should be stuck in limbo waiting for more precommits // precommit arrives from vs2: - signAddVotes(cs1, types.VoteTypePrecommit, propBlockHash, propPartsHeader, vs2) - ensureVote(voteCh, height, round, types.VoteTypePrecommit) + signAddVotes(cs1, types.PrecommitType, propBlockHash, propPartsHeader, vs2) + ensureVote(voteCh, height, round, types.PrecommitType) // wait to finish commit, propose in next height ensureNewBlock(newBlockCh, height) @@ -352,14 +353,14 @@ func TestStateLockNoPOL(t *testing.T) { theBlockHash := roundState.ProposalBlock.Hash() thePartSetHeader := roundState.ProposalBlockParts.Header() - ensureVote(voteCh, height, round, types.VoteTypePrevote) // prevote + ensureVote(voteCh, height, round, types.PrevoteType) // prevote // we should now be stuck in limbo forever, waiting for more prevotes // prevote arrives from vs2: - signAddVotes(cs1, types.VoteTypePrevote, theBlockHash, thePartSetHeader, vs2) - ensureVote(voteCh, height, round, types.VoteTypePrevote) // prevote + signAddVotes(cs1, types.PrevoteType, theBlockHash, thePartSetHeader, vs2) + ensureVote(voteCh, height, round, types.PrevoteType) // prevote - ensureVote(voteCh, height, round, types.VoteTypePrecommit) // precommit + ensureVote(voteCh, height, round, types.PrecommitType) // precommit // the proposed block should now be locked and our precommit added validatePrecommit(t, cs1, round, round, vss[0], theBlockHash, theBlockHash) @@ -368,8 +369,8 @@ func TestStateLockNoPOL(t *testing.T) { hash := make([]byte, len(theBlockHash)) copy(hash, theBlockHash) hash[0] = byte((hash[0] + 1) % 255) - signAddVotes(cs1, types.VoteTypePrecommit, hash, thePartSetHeader, vs2) - ensureVote(voteCh, height, round, types.VoteTypePrecommit) // precommit + signAddVotes(cs1, types.PrecommitType, hash, thePartSetHeader, vs2) + ensureVote(voteCh, height, round, types.PrecommitType) // precommit // (note we're entering precommit for a second time this round) // but with invalid args. then we enterPrecommitWait, and the timeout to new round @@ -396,26 +397,26 @@ func TestStateLockNoPOL(t *testing.T) { } // wait to finish prevote - ensureVote(voteCh, height, round, types.VoteTypePrevote) + ensureVote(voteCh, height, round, types.PrevoteType) // we should have prevoted our locked block validatePrevote(t, cs1, round, vss[0], rs.LockedBlock.Hash()) // add a conflicting prevote from the other validator - signAddVotes(cs1, types.VoteTypePrevote, hash, rs.LockedBlock.MakePartSet(partSize).Header(), vs2) - ensureVote(voteCh, height, round, types.VoteTypePrevote) + signAddVotes(cs1, types.PrevoteType, hash, rs.LockedBlock.MakePartSet(partSize).Header(), vs2) + ensureVote(voteCh, height, round, types.PrevoteType) // now we're going to enter prevote again, but with invalid args // and then prevote wait, which should timeout. then wait for precommit ensureNewTimeout(timeoutWaitCh, height, round, cs1.config.TimeoutPrevote.Nanoseconds()) - ensureVote(voteCh, height, round, types.VoteTypePrecommit) // precommit + ensureVote(voteCh, height, round, types.PrecommitType) // precommit // the proposed block should still be locked and our precommit added // we should precommit nil and be locked on the proposal validatePrecommit(t, cs1, round, 0, vss[0], nil, theBlockHash) // add conflicting precommit from vs2 - signAddVotes(cs1, types.VoteTypePrecommit, hash, rs.LockedBlock.MakePartSet(partSize).Header(), vs2) - ensureVote(voteCh, height, round, types.VoteTypePrecommit) + signAddVotes(cs1, types.PrecommitType, hash, rs.LockedBlock.MakePartSet(partSize).Header(), vs2) + ensureVote(voteCh, height, round, types.PrecommitType) // (note we're entering precommit for a second time this round, but with invalid args // then we enterPrecommitWait and timeout into NewRound @@ -438,19 +439,19 @@ func TestStateLockNoPOL(t *testing.T) { panic(fmt.Sprintf("Expected proposal block to be locked block. Got %v, Expected %v", rs.ProposalBlock, rs.LockedBlock)) } - ensureVote(voteCh, height, round, types.VoteTypePrevote) // prevote + ensureVote(voteCh, height, round, types.PrevoteType) // prevote validatePrevote(t, cs1, round, vss[0], rs.LockedBlock.Hash()) - signAddVotes(cs1, types.VoteTypePrevote, hash, rs.ProposalBlock.MakePartSet(partSize).Header(), vs2) - ensureVote(voteCh, height, round, types.VoteTypePrevote) + signAddVotes(cs1, types.PrevoteType, hash, rs.ProposalBlock.MakePartSet(partSize).Header(), vs2) + ensureVote(voteCh, height, round, types.PrevoteType) ensureNewTimeout(timeoutWaitCh, height, round, cs1.config.TimeoutPrevote.Nanoseconds()) - ensureVote(voteCh, height, round, types.VoteTypePrecommit) // precommit + ensureVote(voteCh, height, round, types.PrecommitType) // precommit validatePrecommit(t, cs1, round, 0, vss[0], nil, theBlockHash) // precommit nil but be locked on proposal - signAddVotes(cs1, types.VoteTypePrecommit, hash, rs.ProposalBlock.MakePartSet(partSize).Header(), vs2) // NOTE: conflicting precommits at same height - ensureVote(voteCh, height, round, types.VoteTypePrecommit) + signAddVotes(cs1, types.PrecommitType, hash, rs.ProposalBlock.MakePartSet(partSize).Header(), vs2) // NOTE: conflicting precommits at same height + ensureVote(voteCh, height, round, types.PrecommitType) ensureNewTimeout(timeoutWaitCh, height, round, cs1.config.TimeoutPrecommit.Nanoseconds()) @@ -477,20 +478,20 @@ func TestStateLockNoPOL(t *testing.T) { } ensureNewProposal(proposalCh, height, round) - ensureVote(voteCh, height, round, types.VoteTypePrevote) // prevote + ensureVote(voteCh, height, round, types.PrevoteType) // prevote // prevote for locked block (not proposal) validatePrevote(t, cs1, 3, vss[0], cs1.LockedBlock.Hash()) // prevote for proposed block - signAddVotes(cs1, types.VoteTypePrevote, propBlock.Hash(), propBlock.MakePartSet(partSize).Header(), vs2) - ensureVote(voteCh, height, round, types.VoteTypePrevote) + signAddVotes(cs1, types.PrevoteType, propBlock.Hash(), propBlock.MakePartSet(partSize).Header(), vs2) + ensureVote(voteCh, height, round, types.PrevoteType) ensureNewTimeout(timeoutWaitCh, height, round, cs1.config.TimeoutPrevote.Nanoseconds()) - ensureVote(voteCh, height, round, types.VoteTypePrecommit) + ensureVote(voteCh, height, round, types.PrecommitType) validatePrecommit(t, cs1, round, 0, vss[0], nil, theBlockHash) // precommit nil but locked on proposal - signAddVotes(cs1, types.VoteTypePrecommit, propBlock.Hash(), propBlock.MakePartSet(partSize).Header(), vs2) // NOTE: conflicting precommits at same height - ensureVote(voteCh, height, round, types.VoteTypePrecommit) + signAddVotes(cs1, types.PrecommitType, propBlock.Hash(), propBlock.MakePartSet(partSize).Header(), vs2) // NOTE: conflicting precommits at same height + ensureVote(voteCh, height, round, types.PrecommitType) } // 4 vals, one precommits, other 3 polka at next round, so we unlock and precomit the polka @@ -524,17 +525,17 @@ func TestStateLockPOLRelock(t *testing.T) { theBlockHash := rs.ProposalBlock.Hash() theBlockParts := rs.ProposalBlockParts.Header() - ensureVote(voteCh, height, round, types.VoteTypePrevote) // prevote + ensureVote(voteCh, height, round, types.PrevoteType) // prevote - signAddVotes(cs1, types.VoteTypePrevote, theBlockHash, theBlockParts, vs2, vs3, vs4) + signAddVotes(cs1, types.PrevoteType, theBlockHash, theBlockParts, vs2, vs3, vs4) - ensureVote(voteCh, height, round, types.VoteTypePrecommit) // our precommit + ensureVote(voteCh, height, round, types.PrecommitType) // our precommit // the proposed block should now be locked and our precommit added validatePrecommit(t, cs1, round, round, vss[0], theBlockHash, theBlockHash) // add precommits from the rest - signAddVotes(cs1, types.VoteTypePrecommit, nil, types.PartSetHeader{}, vs2, vs4) - signAddVotes(cs1, types.VoteTypePrecommit, theBlockHash, theBlockParts, vs3) + signAddVotes(cs1, types.PrecommitType, nil, types.PartSetHeader{}, vs2, vs4) + signAddVotes(cs1, types.PrecommitType, theBlockHash, theBlockParts, vs3) // before we timeout to the new round set the new proposal prop, propBlock := decideProposal(cs1, vs2, vs2.Height, vs2.Round+1) @@ -566,17 +567,17 @@ func TestStateLockPOLRelock(t *testing.T) { ensureNewProposal(proposalCh, height, round) // go to prevote, prevote for locked block (not proposal), move on - ensureVote(voteCh, height, round, types.VoteTypePrevote) + ensureVote(voteCh, height, round, types.PrevoteType) validatePrevote(t, cs1, round, vss[0], theBlockHash) // now lets add prevotes from everyone else for the new block - signAddVotes(cs1, types.VoteTypePrevote, propBlockHash, propBlockParts.Header(), vs2, vs3, vs4) + signAddVotes(cs1, types.PrevoteType, propBlockHash, propBlockParts.Header(), vs2, vs3, vs4) - ensureVote(voteCh, height, round, types.VoteTypePrecommit) + ensureVote(voteCh, height, round, types.PrecommitType) // we should have unlocked and locked on the new block validatePrecommit(t, cs1, round, round, vss[0], propBlockHash, propBlockHash) - signAddVotes(cs1, types.VoteTypePrecommit, propBlockHash, propBlockParts.Header(), vs2, vs3) + signAddVotes(cs1, types.PrecommitType, propBlockHash, propBlockParts.Header(), vs2, vs3) ensureNewBlockHeader(newBlockCh, height, propBlockHash) ensureNewRound(newRoundCh, height+1, 0) @@ -613,20 +614,20 @@ func TestStateLockPOLUnlock(t *testing.T) { theBlockHash := rs.ProposalBlock.Hash() theBlockParts := rs.ProposalBlockParts.Header() - ensureVote(voteCh, height, round, types.VoteTypePrevote) + ensureVote(voteCh, height, round, types.PrevoteType) validatePrevote(t, cs1, round, vss[0], theBlockHash) - signAddVotes(cs1, types.VoteTypePrevote, theBlockHash, theBlockParts, vs2, vs3, vs4) + signAddVotes(cs1, types.PrevoteType, theBlockHash, theBlockParts, vs2, vs3, vs4) - ensureVote(voteCh, height, round, types.VoteTypePrecommit) + ensureVote(voteCh, height, round, types.PrecommitType) // the proposed block should now be locked and our precommit added validatePrecommit(t, cs1, round, round, vss[0], theBlockHash, theBlockHash) rs = cs1.GetRoundState() // add precommits from the rest - signAddVotes(cs1, types.VoteTypePrecommit, nil, types.PartSetHeader{}, vs2, vs4) - signAddVotes(cs1, types.VoteTypePrecommit, theBlockHash, theBlockParts, vs3) + signAddVotes(cs1, types.PrecommitType, nil, types.PartSetHeader{}, vs2, vs4) + signAddVotes(cs1, types.PrecommitType, theBlockHash, theBlockParts, vs3) // before we time out into new round, set next proposal block prop, propBlock := decideProposal(cs1, vs2, vs2.Height, vs2.Round+1) @@ -655,21 +656,20 @@ func TestStateLockPOLUnlock(t *testing.T) { ensureNewProposal(proposalCh, height, round) // go to prevote, prevote for locked block (not proposal) - ensureVote(voteCh, height, round, types.VoteTypePrevote) + ensureVote(voteCh, height, round, types.PrevoteType) validatePrevote(t, cs1, round, vss[0], lockedBlockHash) - // now lets add prevotes from everyone else for nil (a polka!) - signAddVotes(cs1, types.VoteTypePrevote, nil, types.PartSetHeader{}, vs2, vs3, vs4) + signAddVotes(cs1, types.PrevoteType, nil, types.PartSetHeader{}, vs2, vs3, vs4) // the polka makes us unlock and precommit nil ensureNewUnlock(unlockCh, height, round) - ensureVote(voteCh, height, round, types.VoteTypePrecommit) + ensureVote(voteCh, height, round, types.PrecommitType) // we should have unlocked and committed nil // NOTE: since we don't relock on nil, the lock round is 0 validatePrecommit(t, cs1, round, 0, vss[0], nil, nil) - signAddVotes(cs1, types.VoteTypePrecommit, nil, types.PartSetHeader{}, vs2, vs3) + signAddVotes(cs1, types.PrecommitType, nil, types.PartSetHeader{}, vs2, vs3) ensureNewRound(newRoundCh, height, round+1) } @@ -698,19 +698,19 @@ func TestStateLockPOLSafety1(t *testing.T) { rs := cs1.GetRoundState() propBlock := rs.ProposalBlock - ensureVote(voteCh, height, round, types.VoteTypePrevote) + ensureVote(voteCh, height, round, types.PrevoteType) validatePrevote(t, cs1, round, vss[0], propBlock.Hash()) // the others sign a polka but we don't see it - prevotes := signVotes(types.VoteTypePrevote, propBlock.Hash(), propBlock.MakePartSet(partSize).Header(), vs2, vs3, vs4) + prevotes := signVotes(types.PrevoteType, propBlock.Hash(), propBlock.MakePartSet(partSize).Header(), vs2, vs3, vs4) t.Logf("old prop hash %v", fmt.Sprintf("%X", propBlock.Hash())) // we do see them precommit nil - signAddVotes(cs1, types.VoteTypePrecommit, nil, types.PartSetHeader{}, vs2, vs3, vs4) + signAddVotes(cs1, types.PrecommitType, nil, types.PartSetHeader{}, vs2, vs3, vs4) // cs1 precommit nil - ensureVote(voteCh, height, round, types.VoteTypePrecommit) + ensureVote(voteCh, height, round, types.PrecommitType) ensureNewTimeout(timeoutWaitCh, height, round, cs1.config.TimeoutPrecommit.Nanoseconds()) t.Log("### ONTO ROUND 1") @@ -743,17 +743,17 @@ func TestStateLockPOLSafety1(t *testing.T) { t.Logf("new prop hash %v", fmt.Sprintf("%X", propBlockHash)) // go to prevote, prevote for proposal block - ensureVote(voteCh, height, round, types.VoteTypePrevote) + ensureVote(voteCh, height, round, types.PrevoteType) validatePrevote(t, cs1, round, vss[0], propBlockHash) // now we see the others prevote for it, so we should lock on it - signAddVotes(cs1, types.VoteTypePrevote, propBlockHash, propBlockParts.Header(), vs2, vs3, vs4) + signAddVotes(cs1, types.PrevoteType, propBlockHash, propBlockParts.Header(), vs2, vs3, vs4) - ensureVote(voteCh, height, round, types.VoteTypePrecommit) + ensureVote(voteCh, height, round, types.PrecommitType) // we should have precommitted validatePrecommit(t, cs1, round, round, vss[0], propBlockHash, propBlockHash) - signAddVotes(cs1, types.VoteTypePrecommit, nil, types.PartSetHeader{}, vs2, vs3, vs4) + signAddVotes(cs1, types.PrecommitType, nil, types.PartSetHeader{}, vs2, vs3, vs4) ensureNewTimeout(timeoutWaitCh, height, round, cs1.config.TimeoutPrecommit.Nanoseconds()) @@ -771,7 +771,7 @@ func TestStateLockPOLSafety1(t *testing.T) { ensureNewTimeout(timeoutProposeCh, height, round, cs1.config.TimeoutPropose.Nanoseconds()) // finish prevote - ensureVote(voteCh, height, round, types.VoteTypePrevote) + ensureVote(voteCh, height, round, types.PrevoteType) // we should prevote what we're locked on validatePrevote(t, cs1, round, vss[0], propBlockHash) @@ -813,7 +813,7 @@ func TestStateLockPOLSafety2(t *testing.T) { propBlockParts0 := propBlock0.MakePartSet(partSize) // the others sign a polka but we don't see it - prevotes := signVotes(types.VoteTypePrevote, propBlockHash0, propBlockParts0.Header(), vs2, vs3, vs4) + prevotes := signVotes(types.PrevoteType, propBlockHash0, propBlockParts0.Header(), vs2, vs3, vs4) // the block for round 1 prop1, propBlock1 := decideProposal(cs1, vs2, vs2.Height, vs2.Round+1) @@ -834,18 +834,18 @@ func TestStateLockPOLSafety2(t *testing.T) { } ensureNewProposal(proposalCh, height, round) - ensureVote(voteCh, height, round, types.VoteTypePrevote) + ensureVote(voteCh, height, round, types.PrevoteType) validatePrevote(t, cs1, round, vss[0], propBlockHash1) - signAddVotes(cs1, types.VoteTypePrevote, propBlockHash1, propBlockParts1.Header(), vs2, vs3, vs4) + signAddVotes(cs1, types.PrevoteType, propBlockHash1, propBlockParts1.Header(), vs2, vs3, vs4) - ensureVote(voteCh, height, round, types.VoteTypePrecommit) + ensureVote(voteCh, height, round, types.PrecommitType) // the proposed block should now be locked and our precommit added validatePrecommit(t, cs1, round, round, vss[0], propBlockHash1, propBlockHash1) // add precommits from the rest - signAddVotes(cs1, types.VoteTypePrecommit, nil, types.PartSetHeader{}, vs2, vs4) - signAddVotes(cs1, types.VoteTypePrecommit, propBlockHash1, propBlockParts1.Header(), vs3) + signAddVotes(cs1, types.PrecommitType, nil, types.PartSetHeader{}, vs2, vs4) + signAddVotes(cs1, types.PrecommitType, propBlockHash1, propBlockParts1.Header(), vs3) incrementRound(vs2, vs3, vs4) @@ -873,7 +873,7 @@ func TestStateLockPOLSafety2(t *testing.T) { ensureNewProposal(proposalCh, height, round) ensureNoNewUnlock(unlockCh) - ensureVote(voteCh, height, round, types.VoteTypePrevote) + ensureVote(voteCh, height, round, types.PrevoteType) validatePrevote(t, cs1, round, vss[0], propBlockHash1) } @@ -893,7 +893,7 @@ func TestWaitingTimeoutOnNilPolka(t *testing.T) { startTestRound(cs1, height, round) ensureNewRound(newRoundCh, height, round) - signAddVotes(cs1, types.VoteTypePrecommit, nil, types.PartSetHeader{}, vs2, vs3, vs4) + signAddVotes(cs1, types.PrecommitType, nil, types.PartSetHeader{}, vs2, vs3, vs4) ensureNewTimeout(timeoutWaitCh, height, round, cs1.config.TimeoutPrecommit.Nanoseconds()) ensureNewRound(newRoundCh, height, round+1) @@ -915,10 +915,10 @@ func TestWaitingTimeoutProposeOnNewRound(t *testing.T) { startTestRound(cs1, height, round) ensureNewRound(newRoundCh, height, round) - ensureVote(voteCh, height, round, types.VoteTypePrevote) + ensureVote(voteCh, height, round, types.PrevoteType) incrementRound(vss[1:]...) - signAddVotes(cs1, types.VoteTypePrevote, nil, types.PartSetHeader{}, vs2, vs3, vs4) + signAddVotes(cs1, types.PrevoteType, nil, types.PartSetHeader{}, vs2, vs3, vs4) round = round + 1 // moving to the next round ensureNewRound(newRoundCh, height, round) @@ -928,7 +928,7 @@ func TestWaitingTimeoutProposeOnNewRound(t *testing.T) { ensureNewTimeout(timeoutWaitCh, height, round, cs1.config.TimeoutPropose.Nanoseconds()) - ensureVote(voteCh, height, round, types.VoteTypePrevote) + ensureVote(voteCh, height, round, types.PrevoteType) validatePrevote(t, cs1, round, vss[0], nil) } @@ -948,15 +948,15 @@ func TestRoundSkipOnNilPolkaFromHigherRound(t *testing.T) { startTestRound(cs1, height, round) ensureNewRound(newRoundCh, height, round) - ensureVote(voteCh, height, round, types.VoteTypePrevote) + ensureVote(voteCh, height, round, types.PrevoteType) incrementRound(vss[1:]...) - signAddVotes(cs1, types.VoteTypePrecommit, nil, types.PartSetHeader{}, vs2, vs3, vs4) + signAddVotes(cs1, types.PrecommitType, nil, types.PartSetHeader{}, vs2, vs3, vs4) round = round + 1 // moving to the next round ensureNewRound(newRoundCh, height, round) - ensureVote(voteCh, height, round, types.VoteTypePrecommit) + ensureVote(voteCh, height, round, types.PrecommitType) validatePrecommit(t, cs1, round, 0, vss[0], nil, nil) ensureNewTimeout(timeoutWaitCh, height, round, cs1.config.TimeoutPrecommit.Nanoseconds()) @@ -982,11 +982,11 @@ func TestWaitTimeoutProposeOnNilPolkaForTheCurrentRound(t *testing.T) { ensureNewRound(newRoundCh, height, round) incrementRound(vss[1:]...) - signAddVotes(cs1, types.VoteTypePrevote, nil, types.PartSetHeader{}, vs2, vs3, vs4) + signAddVotes(cs1, types.PrevoteType, nil, types.PartSetHeader{}, vs2, vs3, vs4) ensureNewTimeout(timeoutProposeCh, height, round, cs1.config.TimeoutPropose.Nanoseconds()) - ensureVote(voteCh, height, round, types.VoteTypePrevote) + ensureVote(voteCh, height, round, types.PrevoteType) validatePrevote(t, cs1, round, vss[0], nil) } @@ -1017,7 +1017,7 @@ func TestStateSlashingPrevotes(t *testing.T) { // add one for a different block should cause us to go into prevote wait hash := rs.ProposalBlock.Hash() hash[0] = byte(hash[0]+1) % 255 - signAddVotes(cs1, types.VoteTypePrevote, hash, rs.ProposalBlockParts.Header(), vs2) + signAddVotes(cs1, types.PrevoteType, hash, rs.ProposalBlockParts.Header(), vs2) <-timeoutWaitCh @@ -1025,7 +1025,7 @@ func TestStateSlashingPrevotes(t *testing.T) { // away and ignore more prevotes (and thus fail to slash!) // add the conflicting vote - signAddVotes(cs1, types.VoteTypePrevote, rs.ProposalBlock.Hash(), rs.ProposalBlockParts.Header(), vs2) + signAddVotes(cs1, types.PrevoteType, rs.ProposalBlock.Hash(), rs.ProposalBlockParts.Header(), vs2) // XXX: Check for existence of Dupeout info } @@ -1047,7 +1047,7 @@ func TestStateSlashingPrecommits(t *testing.T) { <-voteCh // prevote // add prevote from vs2 - signAddVotes(cs1, types.VoteTypePrevote, rs.ProposalBlock.Hash(), rs.ProposalBlockParts.Header(), vs2) + signAddVotes(cs1, types.PrevoteType, rs.ProposalBlock.Hash(), rs.ProposalBlockParts.Header(), vs2) <-voteCh // precommit @@ -1055,13 +1055,13 @@ func TestStateSlashingPrecommits(t *testing.T) { // add one for a different block should cause us to go into prevote wait hash := rs.ProposalBlock.Hash() hash[0] = byte(hash[0]+1) % 255 - signAddVotes(cs1, types.VoteTypePrecommit, hash, rs.ProposalBlockParts.Header(), vs2) + signAddVotes(cs1, types.PrecommitType, hash, rs.ProposalBlockParts.Header(), vs2) // NOTE: we have to send the vote for different block first so we don't just go into precommit round right // away and ignore more prevotes (and thus fail to slash!) // add precommit from vs2 - signAddVotes(cs1, types.VoteTypePrecommit, rs.ProposalBlock.Hash(), rs.ProposalBlockParts.Header(), vs2) + signAddVotes(cs1, types.PrecommitType, rs.ProposalBlock.Hash(), rs.ProposalBlockParts.Header(), vs2) // XXX: Check for existence of Dupeout info } @@ -1096,19 +1096,19 @@ func TestStateHalt1(t *testing.T) { propBlock := rs.ProposalBlock propBlockParts := propBlock.MakePartSet(partSize) - ensureVote(voteCh, height, round, types.VoteTypePrevote) + ensureVote(voteCh, height, round, types.PrevoteType) - signAddVotes(cs1, types.VoteTypePrevote, propBlock.Hash(), propBlockParts.Header(), vs2, vs3, vs4) + signAddVotes(cs1, types.PrevoteType, propBlock.Hash(), propBlockParts.Header(), vs2, vs3, vs4) - ensureVote(voteCh, height, round, types.VoteTypePrecommit) + ensureVote(voteCh, height, round, types.PrecommitType) // the proposed block should now be locked and our precommit added validatePrecommit(t, cs1, round, round, vss[0], propBlock.Hash(), propBlock.Hash()) // add precommits from the rest - signAddVotes(cs1, types.VoteTypePrecommit, nil, types.PartSetHeader{}, vs2) // didnt receive proposal - signAddVotes(cs1, types.VoteTypePrecommit, propBlock.Hash(), propBlockParts.Header(), vs3) + signAddVotes(cs1, types.PrecommitType, nil, types.PartSetHeader{}, vs2) // didnt receive proposal + signAddVotes(cs1, types.PrecommitType, propBlock.Hash(), propBlockParts.Header(), vs3) // we receive this later, but vs3 might receive it earlier and with ours will go to commit! - precommit4 := signVote(vs4, types.VoteTypePrecommit, propBlock.Hash(), propBlockParts.Header()) + precommit4 := signVote(vs4, types.PrecommitType, propBlock.Hash(), propBlockParts.Header()) incrementRound(vs2, vs3, vs4) @@ -1127,7 +1127,7 @@ func TestStateHalt1(t *testing.T) { */ // go to prevote, prevote for locked block - ensureVote(voteCh, height, round, types.VoteTypePrevote) + ensureVote(voteCh, height, round, types.PrevoteType) validatePrevote(t, cs1, round, vss[0], rs.LockedBlock.Hash()) // now we receive the precommit from the previous round @@ -1187,7 +1187,7 @@ func TestStateOutputVoteStats(t *testing.T) { // create dummy peer peer := p2pdummy.NewPeer() - vote := signVote(vss[1], types.VoteTypePrecommit, []byte("test"), types.PartSetHeader{}) + vote := signVote(vss[1], types.PrecommitType, []byte("test"), types.PartSetHeader{}) voteMessage := &VoteMessage{vote} cs.handleMsg(msgInfo{voteMessage, peer.ID()}) @@ -1201,7 +1201,7 @@ func TestStateOutputVoteStats(t *testing.T) { // sending the vote for the bigger height incrementHeight(vss[1]) - vote = signVote(vss[1], types.VoteTypePrecommit, []byte("test"), types.PartSetHeader{}) + vote = signVote(vss[1], types.PrecommitType, []byte("test"), types.PartSetHeader{}) cs.handleMsg(msgInfo{&VoteMessage{vote}, peer.ID()}) diff --git a/consensus/types/height_vote_set.go b/consensus/types/height_vote_set.go index 1c8ac67c..eee013ee 100644 --- a/consensus/types/height_vote_set.go +++ b/consensus/types/height_vote_set.go @@ -99,8 +99,8 @@ func (hvs *HeightVoteSet) addRound(round int) { cmn.PanicSanity("addRound() for an existing round") } // log.Debug("addRound(round)", "round", round) - prevotes := types.NewVoteSet(hvs.chainID, hvs.height, round, types.VoteTypePrevote, hvs.valSet) - precommits := types.NewVoteSet(hvs.chainID, hvs.height, round, types.VoteTypePrecommit, hvs.valSet) + prevotes := types.NewVoteSet(hvs.chainID, hvs.height, round, types.PrevoteType, hvs.valSet) + precommits := types.NewVoteSet(hvs.chainID, hvs.height, round, types.PrecommitType, hvs.valSet) hvs.roundVoteSets[round] = RoundVoteSet{ Prevotes: prevotes, Precommits: precommits, @@ -134,13 +134,13 @@ func (hvs *HeightVoteSet) AddVote(vote *types.Vote, peerID p2p.ID) (added bool, func (hvs *HeightVoteSet) Prevotes(round int) *types.VoteSet { hvs.mtx.Lock() defer hvs.mtx.Unlock() - return hvs.getVoteSet(round, types.VoteTypePrevote) + return hvs.getVoteSet(round, types.PrevoteType) } func (hvs *HeightVoteSet) Precommits(round int) *types.VoteSet { hvs.mtx.Lock() defer hvs.mtx.Unlock() - return hvs.getVoteSet(round, types.VoteTypePrecommit) + return hvs.getVoteSet(round, types.PrecommitType) } // Last round and blockID that has +2/3 prevotes for a particular block or nil. @@ -149,7 +149,7 @@ func (hvs *HeightVoteSet) POLInfo() (polRound int, polBlockID types.BlockID) { hvs.mtx.Lock() defer hvs.mtx.Unlock() for r := hvs.round; r >= 0; r-- { - rvs := hvs.getVoteSet(r, types.VoteTypePrevote) + rvs := hvs.getVoteSet(r, types.PrevoteType) polBlockID, ok := rvs.TwoThirdsMajority() if ok { return r, polBlockID @@ -158,15 +158,15 @@ func (hvs *HeightVoteSet) POLInfo() (polRound int, polBlockID types.BlockID) { return -1, types.BlockID{} } -func (hvs *HeightVoteSet) getVoteSet(round int, type_ byte) *types.VoteSet { +func (hvs *HeightVoteSet) getVoteSet(round int, type_ types.SignedMsgType) *types.VoteSet { rvs, ok := hvs.roundVoteSets[round] if !ok { return nil } switch type_ { - case types.VoteTypePrevote: + case types.PrevoteType: return rvs.Prevotes - case types.VoteTypePrecommit: + case types.PrecommitType: return rvs.Precommits default: cmn.PanicSanity(fmt.Sprintf("Unexpected vote type %X", type_)) @@ -178,7 +178,7 @@ func (hvs *HeightVoteSet) getVoteSet(round int, type_ byte) *types.VoteSet { // NOTE: if there are too many peers, or too much peer churn, // this can cause memory issues. // TODO: implement ability to remove peers too -func (hvs *HeightVoteSet) SetPeerMaj23(round int, type_ byte, peerID p2p.ID, blockID types.BlockID) error { +func (hvs *HeightVoteSet) SetPeerMaj23(round int, type_ types.SignedMsgType, peerID p2p.ID, blockID types.BlockID) error { hvs.mtx.Lock() defer hvs.mtx.Unlock() if !types.IsVoteTypeValid(type_) { diff --git a/consensus/types/height_vote_set_test.go b/consensus/types/height_vote_set_test.go index 5f469221..e2298cef 100644 --- a/consensus/types/height_vote_set_test.go +++ b/consensus/types/height_vote_set_test.go @@ -56,7 +56,7 @@ func makeVoteHR(t *testing.T, height int64, round int, privVals []types.PrivVali Height: height, Round: round, Timestamp: tmtime.Now(), - Type: types.VoteTypePrecommit, + Type: types.PrecommitType, BlockID: types.BlockID{[]byte("fakehash"), types.PartSetHeader{}}, } chainID := config.ChainID() diff --git a/docs/spec/blockchain/blockchain.md b/docs/spec/blockchain/blockchain.md index 4a433b5d..89ab1b4f 100644 --- a/docs/spec/blockchain/blockchain.md +++ b/docs/spec/blockchain/blockchain.md @@ -410,8 +410,9 @@ must be greater than 2/3 of the total voting power of the complete validator set A vote is a signed message broadcast in the consensus for a particular block at a particular height and round. When stored in the blockchain or propagated over the network, votes are encoded in Amino. -For signing, votes are represented via `CanonicalVote` and also encoded using amino (protobuf compatible) via -`Vote.SignBytes` which includes the `ChainID`. +For signing, votes are represented via `CanonicalVote` and also encoded using amino (protobuf compatible) via +`Vote.SignBytes` which includes the `ChainID`, and uses a different ordering of +the fields. We define a method `Verify` that returns `true` if the signature verifies against the pubkey for the `SignBytes` using the given ChainID: diff --git a/docs/spec/blockchain/encoding.md b/docs/spec/blockchain/encoding.md index 2ff024ce..ed92739d 100644 --- a/docs/spec/blockchain/encoding.md +++ b/docs/spec/blockchain/encoding.md @@ -300,20 +300,23 @@ Where the `"value"` is the base64 encoding of the raw pubkey bytes, and the Signed messages (eg. votes, proposals) in the consensus are encoded using Amino. -When signing, the elements of a message are sorted alphabetically by key and prepended with -a `chain_id` and `type` field. +When signing, the elements of a message are re-ordered so the fixed-length fields +are first, making it easy to quickly check the version, height, round, and type. +The `ChainID` is also appended to the end. We call this encoding the SignBytes. For instance, SignBytes for a vote is the Amino encoding of the following struct: ```go type CanonicalVote struct { - ChainID string - Type string - BlockID CanonicalBlockID - Height int64 - Round int - Timestamp time.Time + Version uint64 `binary:"fixed64"` + Height int64 `binary:"fixed64"` + Round int64 `binary:"fixed64"` VoteType byte + Timestamp time.Time + BlockID CanonicalBlockID + ChainID string } ``` -NOTE: see [#1622](https://github.com/tendermint/tendermint/issues/1622) for how field ordering will change +The field ordering and the fixed sized encoding for the first three fields is optimized to ease parsing of SignBytes +in HSMs. It creates fixed offsets for relevant fields that need to be read in this context. +See [#1622](https://github.com/tendermint/tendermint/issues/1622) for more details. diff --git a/lite/helpers.go b/lite/helpers.go index 16d22e70..5177ee50 100644 --- a/lite/helpers.go +++ b/lite/helpers.go @@ -97,7 +97,7 @@ func makeVote(header *types.Header, valset *types.ValidatorSet, key crypto.PrivK Height: header.Height, Round: 1, Timestamp: tmtime.Now(), - Type: types.VoteTypePrecommit, + Type: types.PrecommitType, BlockID: types.BlockID{Hash: header.Hash()}, } // Sign it diff --git a/privval/priv_validator.go b/privval/priv_validator.go index e606b826..c5fba509 100644 --- a/privval/priv_validator.go +++ b/privval/priv_validator.go @@ -25,9 +25,9 @@ const ( func voteToStep(vote *types.Vote) int8 { switch vote.Type { - case types.VoteTypePrevote: + case types.PrevoteType: return stepPrevote - case types.VoteTypePrecommit: + case types.PrecommitType: return stepPrecommit default: cmn.PanicSanity("Unknown vote type") diff --git a/privval/priv_validator_test.go b/privval/priv_validator_test.go index 404ff770..90796ddf 100644 --- a/privval/priv_validator_test.go +++ b/privval/priv_validator_test.go @@ -101,7 +101,7 @@ func TestSignVote(t *testing.T) { block1 := types.BlockID{[]byte{1, 2, 3}, types.PartSetHeader{}} block2 := types.BlockID{[]byte{3, 2, 1}, types.PartSetHeader{}} height, round := int64(10), 1 - voteType := types.VoteTypePrevote + voteType := byte(types.PrevoteType) // sign a vote for first time vote := newVote(privVal.Address, 0, height, round, voteType, block1) @@ -206,7 +206,7 @@ func TestDifferByTimestamp(t *testing.T) { // test vote { - voteType := types.VoteTypePrevote + voteType := byte(types.PrevoteType) blockID := types.BlockID{[]byte{1, 2, 3}, types.PartSetHeader{}} vote := newVote(privVal.Address, 0, height, round, voteType, blockID) err := privVal.SignVote("mychainid", vote) @@ -235,7 +235,7 @@ func newVote(addr types.Address, idx int, height int64, round int, typ byte, blo ValidatorIndex: idx, Height: height, Round: round, - Type: typ, + Type: types.SignedMsgType(typ), Timestamp: tmtime.Now(), BlockID: blockID, } diff --git a/privval/socket_test.go b/privval/socket_test.go index 84e721be..aa2e15fa 100644 --- a/privval/socket_test.go +++ b/privval/socket_test.go @@ -79,7 +79,7 @@ func TestSocketPVVote(t *testing.T) { sc, rs = testSetupSocketPair(t, chainID, types.NewMockPV()) ts = time.Now() - vType = types.VoteTypePrecommit + vType = types.PrecommitType want = &types.Vote{Timestamp: ts, Type: vType} have = &types.Vote{Timestamp: ts, Type: vType} ) @@ -237,7 +237,7 @@ func TestRemoteSignVoteErrors(t *testing.T) { sc, rs = testSetupSocketPair(t, chainID, types.NewErroringMockPV()) ts = time.Now() - vType = types.VoteTypePrecommit + vType = types.PrecommitType vote = &types.Vote{Timestamp: ts, Type: vType} ) defer sc.Stop() diff --git a/state/execution_test.go b/state/execution_test.go index e93c9bfd..273e9ebe 100644 --- a/state/execution_test.go +++ b/state/execution_test.go @@ -64,7 +64,7 @@ func TestBeginBlockValidators(t *testing.T) { prevBlockID := types.BlockID{prevHash, prevParts} now := tmtime.Now() - vote0 := &types.Vote{ValidatorIndex: 0, Timestamp: now, Type: types.VoteTypePrecommit} + vote0 := &types.Vote{ValidatorIndex: 0, Timestamp: now, Type: types.PrecommitType} vote1 := &types.Vote{ValidatorIndex: 1, Timestamp: now} testCases := []struct { @@ -135,7 +135,7 @@ func TestBeginBlockByzantineValidators(t *testing.T) { types.TM2PB.Evidence(ev2, valSet, now)}}, } - vote0 := &types.Vote{ValidatorIndex: 0, Timestamp: now, Type: types.VoteTypePrecommit} + vote0 := &types.Vote{ValidatorIndex: 0, Timestamp: now, Type: types.PrecommitType} vote1 := &types.Vote{ValidatorIndex: 1, Timestamp: now} votes := []*types.Vote{vote0, vote1} lastCommit := &types.Commit{BlockID: prevBlockID, Precommits: votes} diff --git a/types/block.go b/types/block.go index bd9092f4..fe3b1725 100644 --- a/types/block.go +++ b/types/block.go @@ -388,7 +388,7 @@ func (commit *Commit) FirstPrecommit() *Vote { } } return &Vote{ - Type: VoteTypePrecommit, + Type: PrecommitType, } } @@ -410,7 +410,7 @@ func (commit *Commit) Round() int { // Type returns the vote type of the commit, which is always VoteTypePrecommit func (commit *Commit) Type() byte { - return VoteTypePrecommit + return byte(PrecommitType) } // Size returns the number of votes in the commit @@ -462,7 +462,7 @@ func (commit *Commit) ValidateBasic() error { continue } // Ensure that all votes are precommits. - if precommit.Type != VoteTypePrecommit { + if precommit.Type != PrecommitType { return fmt.Errorf("Invalid commit vote. Expected precommit, got %v", precommit.Type) } diff --git a/types/block_test.go b/types/block_test.go index 962aa002..7abd79d7 100644 --- a/types/block_test.go +++ b/types/block_test.go @@ -26,7 +26,7 @@ func TestBlockAddEvidence(t *testing.T) { lastID := makeBlockIDRandom() h := int64(3) - voteSet, valSet, vals := randVoteSet(h-1, 1, VoteTypePrecommit, 10, 1) + voteSet, valSet, vals := randVoteSet(h-1, 1, PrecommitType, 10, 1) commit, err := MakeCommit(lastID, h-1, 1, voteSet, vals) require.NoError(t, err) @@ -46,7 +46,7 @@ func TestBlockValidateBasic(t *testing.T) { lastID := makeBlockIDRandom() h := int64(3) - voteSet, valSet, vals := randVoteSet(h-1, 1, VoteTypePrecommit, 10, 1) + voteSet, valSet, vals := randVoteSet(h-1, 1, PrecommitType, 10, 1) commit, err := MakeCommit(lastID, h-1, 1, voteSet, vals) require.NoError(t, err) @@ -106,7 +106,7 @@ func TestBlockMakePartSetWithEvidence(t *testing.T) { lastID := makeBlockIDRandom() h := int64(3) - voteSet, valSet, vals := randVoteSet(h-1, 1, VoteTypePrecommit, 10, 1) + voteSet, valSet, vals := randVoteSet(h-1, 1, PrecommitType, 10, 1) commit, err := MakeCommit(lastID, h-1, 1, voteSet, vals) require.NoError(t, err) @@ -123,7 +123,7 @@ func TestBlockHashesTo(t *testing.T) { lastID := makeBlockIDRandom() h := int64(3) - voteSet, valSet, vals := randVoteSet(h-1, 1, VoteTypePrecommit, 10, 1) + voteSet, valSet, vals := randVoteSet(h-1, 1, PrecommitType, 10, 1) commit, err := MakeCommit(lastID, h-1, 1, voteSet, vals) require.NoError(t, err) @@ -190,14 +190,14 @@ func TestNilDataHashDoesntCrash(t *testing.T) { func TestCommit(t *testing.T) { lastID := makeBlockIDRandom() h := int64(3) - voteSet, _, vals := randVoteSet(h-1, 1, VoteTypePrecommit, 10, 1) + voteSet, _, vals := randVoteSet(h-1, 1, PrecommitType, 10, 1) commit, err := MakeCommit(lastID, h-1, 1, voteSet, vals) require.NoError(t, err) assert.NotNil(t, commit.FirstPrecommit()) assert.Equal(t, h-1, commit.Height()) assert.Equal(t, 1, commit.Round()) - assert.Equal(t, VoteTypePrecommit, commit.Type()) + assert.Equal(t, PrecommitType, SignedMsgType(commit.Type())) if commit.Size() <= 0 { t.Fatalf("commit %v has a zero or negative size: %d", commit, commit.Size()) } @@ -218,7 +218,7 @@ func TestCommitValidateBasic(t *testing.T) { {"Random Commit", func(com *Commit) {}, false}, {"Nil precommit", func(com *Commit) { com.Precommits[0] = nil }, false}, {"Incorrect signature", func(com *Commit) { com.Precommits[0].Signature = []byte{0} }, false}, - {"Incorrect type", func(com *Commit) { com.Precommits[0].Type = VoteTypePrevote }, true}, + {"Incorrect type", func(com *Commit) { com.Precommits[0].Type = PrevoteType }, true}, {"Incorrect height", func(com *Commit) { com.Precommits[0].Height = int64(100) }, true}, {"Incorrect round", func(com *Commit) { com.Precommits[0].Round = 100 }, true}, } @@ -268,7 +268,7 @@ func TestMaxHeaderBytes(t *testing.T) { func randCommit() *Commit { lastID := makeBlockIDRandom() h := int64(3) - voteSet, _, vals := randVoteSet(h-1, 1, VoteTypePrecommit, 10, 1) + voteSet, _, vals := randVoteSet(h-1, 1, PrecommitType, 10, 1) commit, err := MakeCommit(lastID, h-1, 1, voteSet, vals) if err != nil { panic(err) diff --git a/types/canonical.go b/types/canonical.go index cdf0bd7b..8a33debd 100644 --- a/types/canonical.go +++ b/types/canonical.go @@ -13,44 +13,46 @@ import ( const TimeFormat = time.RFC3339Nano type CanonicalBlockID struct { - Hash cmn.HexBytes `json:"hash,omitempty"` - PartsHeader CanonicalPartSetHeader `json:"parts,omitempty"` + Hash cmn.HexBytes + PartsHeader CanonicalPartSetHeader } type CanonicalPartSetHeader struct { - Hash cmn.HexBytes `json:"hash,omitempty"` - Total int `json:"total,omitempty"` + Hash cmn.HexBytes + Total int } type CanonicalProposal struct { - ChainID string `json:"@chain_id"` - Type string `json:"@type"` - BlockPartsHeader CanonicalPartSetHeader `json:"block_parts_header"` - Height int64 `json:"height"` - POLBlockID CanonicalBlockID `json:"pol_block_id"` - POLRound int `json:"pol_round"` - Round int `json:"round"` - Timestamp time.Time `json:"timestamp"` + Version uint64 `binary:"fixed64"` + Height int64 `binary:"fixed64"` + Round int64 `binary:"fixed64"` + Type SignedMsgType // type alias for byte + POLRound int64 `binary:"fixed64"` + Timestamp time.Time + BlockPartsHeader CanonicalPartSetHeader + POLBlockID CanonicalBlockID + ChainID string } type CanonicalVote struct { - ChainID string `json:"@chain_id"` - Type string `json:"@type"` - BlockID CanonicalBlockID `json:"block_id"` - Height int64 `json:"height"` - Round int `json:"round"` - Timestamp time.Time `json:"timestamp"` - VoteType byte `json:"type"` + Version uint64 `binary:"fixed64"` + Height int64 `binary:"fixed64"` + Round int64 `binary:"fixed64"` + Type SignedMsgType // type alias for byte + Timestamp time.Time + BlockID CanonicalBlockID + ChainID string } type CanonicalHeartbeat struct { - ChainID string `json:"@chain_id"` - Type string `json:"@type"` - Height int64 `json:"height"` - Round int `json:"round"` - Sequence int `json:"sequence"` - ValidatorAddress Address `json:"validator_address"` - ValidatorIndex int `json:"validator_index"` + Version uint64 `binary:"fixed64"` + Height int64 `binary:"fixed64"` + Round int `binary:"fixed64"` + Type byte + Sequence int `binary:"fixed64"` + ValidatorAddress Address + ValidatorIndex int + ChainID string } //----------------------------------- @@ -72,38 +74,40 @@ func CanonicalizePartSetHeader(psh PartSetHeader) CanonicalPartSetHeader { func CanonicalizeProposal(chainID string, proposal *Proposal) CanonicalProposal { return CanonicalProposal{ - ChainID: chainID, - Type: "proposal", - BlockPartsHeader: CanonicalizePartSetHeader(proposal.BlockPartsHeader), + Version: 0, // TODO Height: proposal.Height, + Round: int64(proposal.Round), // cast int->int64 to make amino encode it fixed64 (does not work for int) + Type: ProposalType, + POLRound: int64(proposal.POLRound), Timestamp: proposal.Timestamp, + BlockPartsHeader: CanonicalizePartSetHeader(proposal.BlockPartsHeader), POLBlockID: CanonicalizeBlockID(proposal.POLBlockID), - POLRound: proposal.POLRound, - Round: proposal.Round, + ChainID: chainID, } } func CanonicalizeVote(chainID string, vote *Vote) CanonicalVote { return CanonicalVote{ - ChainID: chainID, - Type: "vote", - BlockID: CanonicalizeBlockID(vote.BlockID), + Version: 0, // TODO Height: vote.Height, - Round: vote.Round, + Round: int64(vote.Round), // cast int->int64 to make amino encode it fixed64 (does not work for int) + Type: vote.Type, Timestamp: vote.Timestamp, - VoteType: vote.Type, + BlockID: CanonicalizeBlockID(vote.BlockID), + ChainID: chainID, } } func CanonicalizeHeartbeat(chainID string, heartbeat *Heartbeat) CanonicalHeartbeat { return CanonicalHeartbeat{ - ChainID: chainID, - Type: "heartbeat", + Version: 0, // TODO Height: heartbeat.Height, Round: heartbeat.Round, + Type: byte(HeartbeatType), Sequence: heartbeat.Sequence, ValidatorAddress: heartbeat.ValidatorAddress, ValidatorIndex: heartbeat.ValidatorIndex, + ChainID: chainID, } } diff --git a/types/evidence_test.go b/types/evidence_test.go index 1a7e9ea5..79805691 100644 --- a/types/evidence_test.go +++ b/types/evidence_test.go @@ -22,7 +22,7 @@ func makeVote(val PrivValidator, chainID string, valIndex int, height int64, rou ValidatorIndex: valIndex, Height: height, Round: round, - Type: byte(step), + Type: SignedMsgType(step), BlockID: blockID, } err := val.SignVote(chainID, v) diff --git a/types/signed_msg_type.go b/types/signed_msg_type.go new file mode 100644 index 00000000..cc3ddbdc --- /dev/null +++ b/types/signed_msg_type.go @@ -0,0 +1,27 @@ +package types + +// SignedMsgType is a type of signed message in the consensus. +type SignedMsgType byte + +const ( + // Votes + PrevoteType SignedMsgType = 0x01 + PrecommitType SignedMsgType = 0x02 + + // Proposals + ProposalType SignedMsgType = 0x20 + + // Heartbeat + HeartbeatType SignedMsgType = 0x30 +) + +func IsVoteTypeValid(type_ SignedMsgType) bool { + switch type_ { + case PrevoteType: + return true + case PrecommitType: + return true + default: + return false + } +} diff --git a/types/test_util.go b/types/test_util.go index e20ea212..80f0c787 100644 --- a/types/test_util.go +++ b/types/test_util.go @@ -16,7 +16,7 @@ func MakeCommit(blockID BlockID, height int64, round int, ValidatorIndex: i, Height: height, Round: round, - Type: VoteTypePrecommit, + Type: PrecommitType, BlockID: blockID, Timestamp: tmtime.Now(), } diff --git a/types/validator_set.go b/types/validator_set.go index 72ab68c0..ab030d1b 100644 --- a/types/validator_set.go +++ b/types/validator_set.go @@ -282,7 +282,7 @@ func (vals *ValidatorSet) VerifyCommit(chainID string, blockID BlockID, height i if precommit.Round != round { return fmt.Errorf("Invalid commit -- wrong round: want %v got %v", round, precommit.Round) } - if precommit.Type != VoteTypePrecommit { + if precommit.Type != PrecommitType { return fmt.Errorf("Invalid commit -- not precommit @ index %v", idx) } _, val := vals.GetByIndex(idx) @@ -361,7 +361,7 @@ func (vals *ValidatorSet) VerifyFutureCommit(newSet *ValidatorSet, chainID strin if precommit.Round != round { return cmn.NewError("Invalid commit -- wrong round: %v vs %v", round, precommit.Round) } - if precommit.Type != VoteTypePrecommit { + if precommit.Type != PrecommitType { return cmn.NewError("Invalid commit -- not precommit @ index %v", idx) } // See if this validator is in oldVals. diff --git a/types/validator_set_test.go b/types/validator_set_test.go index e4111707..d886b419 100644 --- a/types/validator_set_test.go +++ b/types/validator_set_test.go @@ -385,7 +385,7 @@ func TestValidatorSetVerifyCommit(t *testing.T) { Height: height, Round: 0, Timestamp: tmtime.Now(), - Type: VoteTypePrecommit, + Type: PrecommitType, BlockID: blockID, } sig, err := privKey.Sign(vote.SignBytes(chainID)) diff --git a/types/vote.go b/types/vote.go index 5a31f0e2..2d70e21b 100644 --- a/types/vote.go +++ b/types/vote.go @@ -43,37 +43,19 @@ func NewConflictingVoteError(val *Validator, voteA, voteB *Vote) *ErrVoteConflic } } -// Types of votes -// TODO Make a new type "VoteType" -const ( - VoteTypePrevote = byte(0x01) - VoteTypePrecommit = byte(0x02) -) - -func IsVoteTypeValid(type_ byte) bool { - switch type_ { - case VoteTypePrevote: - return true - case VoteTypePrecommit: - return true - default: - return false - } -} - // Address is hex bytes. type Address = crypto.Address // Represents a prevote, precommit, or commit vote from validators for consensus. type Vote struct { - ValidatorAddress Address `json:"validator_address"` - ValidatorIndex int `json:"validator_index"` - Height int64 `json:"height"` - Round int `json:"round"` - Timestamp time.Time `json:"timestamp"` - Type byte `json:"type"` - BlockID BlockID `json:"block_id"` // zero if vote is nil. - Signature []byte `json:"signature"` + ValidatorAddress Address `json:"validator_address"` + ValidatorIndex int `json:"validator_index"` + Height int64 `json:"height"` + Round int `json:"round"` + Timestamp time.Time `json:"timestamp"` + Type SignedMsgType `json:"type"` + BlockID BlockID `json:"block_id"` // zero if vote is nil. + Signature []byte `json:"signature"` } func (vote *Vote) SignBytes(chainID string) []byte { @@ -95,9 +77,9 @@ func (vote *Vote) String() string { } var typeString string switch vote.Type { - case VoteTypePrevote: + case PrevoteType: typeString = "Prevote" - case VoteTypePrecommit: + case PrecommitType: typeString = "Precommit" default: cmn.PanicSanity("Unknown vote type") diff --git a/types/vote_set.go b/types/vote_set.go index dbcacbbd..cdfa3d40 100644 --- a/types/vote_set.go +++ b/types/vote_set.go @@ -55,7 +55,7 @@ type VoteSet struct { chainID string height int64 round int - type_ byte + type_ SignedMsgType valSet *ValidatorSet mtx sync.Mutex @@ -68,7 +68,7 @@ type VoteSet struct { } // Constructs a new VoteSet struct used to accumulate votes for given height/round. -func NewVoteSet(chainID string, height int64, round int, type_ byte, valSet *ValidatorSet) *VoteSet { +func NewVoteSet(chainID string, height int64, round int, type_ SignedMsgType, valSet *ValidatorSet) *VoteSet { if height == 0 { cmn.PanicSanity("Cannot make VoteSet for height == 0, doesn't make sense.") } @@ -109,7 +109,7 @@ func (voteSet *VoteSet) Type() byte { if voteSet == nil { return 0x00 } - return voteSet.type_ + return byte(voteSet.type_) } func (voteSet *VoteSet) Size() int { @@ -381,7 +381,7 @@ func (voteSet *VoteSet) IsCommit() bool { if voteSet == nil { return false } - if voteSet.type_ != VoteTypePrecommit { + if voteSet.type_ != PrecommitType { return false } voteSet.mtx.Lock() @@ -529,8 +529,8 @@ func (voteSet *VoteSet) sumTotalFrac() (int64, int64, float64) { // Commit func (voteSet *VoteSet) MakeCommit() *Commit { - if voteSet.type_ != VoteTypePrecommit { - cmn.PanicSanity("Cannot MakeCommit() unless VoteSet.Type is VoteTypePrecommit") + if voteSet.type_ != PrecommitType { + cmn.PanicSanity("Cannot MakeCommit() unless VoteSet.Type is PrecommitType") } voteSet.mtx.Lock() defer voteSet.mtx.Unlock() diff --git a/types/vote_set_test.go b/types/vote_set_test.go index 995fb94b..64187292 100644 --- a/types/vote_set_test.go +++ b/types/vote_set_test.go @@ -11,7 +11,7 @@ import ( ) // NOTE: privValidators are in order -func randVoteSet(height int64, round int, type_ byte, numValidators int, votingPower int64) (*VoteSet, *ValidatorSet, []PrivValidator) { +func randVoteSet(height int64, round int, type_ SignedMsgType, numValidators int, votingPower int64) (*VoteSet, *ValidatorSet, []PrivValidator) { valSet, privValidators := RandValidatorSet(numValidators, votingPower) return NewVoteSet("test_chain_id", height, round, type_, valSet), valSet, privValidators } @@ -41,7 +41,7 @@ func withRound(vote *Vote, round int) *Vote { // Convenience: Return new vote with different type func withType(vote *Vote, type_ byte) *Vote { vote = vote.Copy() - vote.Type = type_ + vote.Type = SignedMsgType(type_) return vote } @@ -61,7 +61,7 @@ func withBlockPartsHeader(vote *Vote, blockPartsHeader PartSetHeader) *Vote { func TestAddVote(t *testing.T) { height, round := int64(1), 0 - voteSet, _, privValidators := randVoteSet(height, round, VoteTypePrevote, 10, 1) + voteSet, _, privValidators := randVoteSet(height, round, PrevoteType, 10, 1) val0 := privValidators[0] // t.Logf(">> %v", voteSet) @@ -82,7 +82,7 @@ func TestAddVote(t *testing.T) { ValidatorIndex: 0, // since privValidators are in order Height: height, Round: round, - Type: VoteTypePrevote, + Type: PrevoteType, Timestamp: tmtime.Now(), BlockID: BlockID{nil, PartSetHeader{}}, } @@ -105,14 +105,14 @@ func TestAddVote(t *testing.T) { func Test2_3Majority(t *testing.T) { height, round := int64(1), 0 - voteSet, _, privValidators := randVoteSet(height, round, VoteTypePrevote, 10, 1) + voteSet, _, privValidators := randVoteSet(height, round, PrevoteType, 10, 1) voteProto := &Vote{ ValidatorAddress: nil, // NOTE: must fill in ValidatorIndex: -1, // NOTE: must fill in Height: height, Round: round, - Type: VoteTypePrevote, + Type: PrevoteType, Timestamp: tmtime.Now(), BlockID: BlockID{nil, PartSetHeader{}}, } @@ -158,7 +158,7 @@ func Test2_3Majority(t *testing.T) { func Test2_3MajorityRedux(t *testing.T) { height, round := int64(1), 0 - voteSet, _, privValidators := randVoteSet(height, round, VoteTypePrevote, 100, 1) + voteSet, _, privValidators := randVoteSet(height, round, PrevoteType, 100, 1) blockHash := crypto.CRandBytes(32) blockPartsTotal := 123 @@ -170,7 +170,7 @@ func Test2_3MajorityRedux(t *testing.T) { Height: height, Round: round, Timestamp: tmtime.Now(), - Type: VoteTypePrevote, + Type: PrevoteType, BlockID: BlockID{blockHash, blockPartsHeader}, } @@ -257,7 +257,7 @@ func Test2_3MajorityRedux(t *testing.T) { func TestBadVotes(t *testing.T) { height, round := int64(1), 0 - voteSet, _, privValidators := randVoteSet(height, round, VoteTypePrevote, 10, 1) + voteSet, _, privValidators := randVoteSet(height, round, PrevoteType, 10, 1) voteProto := &Vote{ ValidatorAddress: nil, @@ -265,7 +265,7 @@ func TestBadVotes(t *testing.T) { Height: height, Round: round, Timestamp: tmtime.Now(), - Type: VoteTypePrevote, + Type: PrevoteType, BlockID: BlockID{nil, PartSetHeader{}}, } @@ -308,7 +308,7 @@ func TestBadVotes(t *testing.T) { // val3 votes of another type. { vote := withValidator(voteProto, privValidators[3].GetAddress(), 3) - added, err := signAddVote(privValidators[3], withType(vote, VoteTypePrecommit), voteSet) + added, err := signAddVote(privValidators[3], withType(vote, byte(PrecommitType)), voteSet) if added || err == nil { t.Errorf("Expected VoteSet.Add to fail, wrong type") } @@ -317,7 +317,7 @@ func TestBadVotes(t *testing.T) { func TestConflicts(t *testing.T) { height, round := int64(1), 0 - voteSet, _, privValidators := randVoteSet(height, round, VoteTypePrevote, 4, 1) + voteSet, _, privValidators := randVoteSet(height, round, PrevoteType, 4, 1) blockHash1 := cmn.RandBytes(32) blockHash2 := cmn.RandBytes(32) @@ -327,7 +327,7 @@ func TestConflicts(t *testing.T) { Height: height, Round: round, Timestamp: tmtime.Now(), - Type: VoteTypePrevote, + Type: PrevoteType, BlockID: BlockID{nil, PartSetHeader{}}, } @@ -447,7 +447,7 @@ func TestConflicts(t *testing.T) { func TestMakeCommit(t *testing.T) { height, round := int64(1), 0 - voteSet, _, privValidators := randVoteSet(height, round, VoteTypePrecommit, 10, 1) + voteSet, _, privValidators := randVoteSet(height, round, PrecommitType, 10, 1) blockHash, blockPartsHeader := crypto.CRandBytes(32), PartSetHeader{123, crypto.CRandBytes(32)} voteProto := &Vote{ @@ -456,7 +456,7 @@ func TestMakeCommit(t *testing.T) { Height: height, Round: round, Timestamp: tmtime.Now(), - Type: VoteTypePrecommit, + Type: PrecommitType, BlockID: BlockID{blockHash, blockPartsHeader}, } diff --git a/types/vote_test.go b/types/vote_test.go index d0c41a06..282953f4 100644 --- a/types/vote_test.go +++ b/types/vote_test.go @@ -13,11 +13,11 @@ import ( ) func examplePrevote() *Vote { - return exampleVote(VoteTypePrevote) + return exampleVote(byte(PrevoteType)) } func examplePrecommit() *Vote { - return exampleVote(VoteTypePrecommit) + return exampleVote(byte(PrecommitType)) } func exampleVote(t byte) *Vote { @@ -32,7 +32,7 @@ func exampleVote(t byte) *Vote { Height: 12345, Round: 2, Timestamp: stamp, - Type: t, + Type: SignedMsgType(t), BlockID: BlockID{ Hash: tmhash.Sum([]byte("blockID_hash")), PartsHeader: PartSetHeader{ @@ -53,6 +53,98 @@ func TestVoteSignable(t *testing.T) { require.Equal(t, expected, signBytes, "Got unexpected sign bytes for Vote.") } +func TestVoteSignableTestVectors(t *testing.T) { + voteWithVersion := CanonicalizeVote("", &Vote{Height: 1, Round: 1}) + voteWithVersion.Version = 123 + + tests := []struct { + canonicalVote CanonicalVote + want []byte + }{ + { + CanonicalizeVote("", &Vote{}), + // NOTE: Height and Round are skipped here. This case needs to be considered while parsing. + []byte{0xb, 0x2a, 0x9, 0x9, 0x0, 0x9, 0x6e, 0x88, 0xf1, 0xff, 0xff, 0xff}, + }, + // with proper (fixed size) height and round (PreCommit): + { + CanonicalizeVote("", &Vote{Height: 1, Round: 1, Type: PrecommitType}), + []byte{ + 0x1f, // total length + 0x11, // (field_number << 3) | wire_type (version is missing) + 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, // height + 0x19, // (field_number << 3) | wire_type + 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, // round + 0x20, // (field_number << 3) | wire_type + 0x2, // PrecommitType + 0x2a, // (field_number << 3) | wire_type + // remaining fields (timestamp): + 0x9, 0x9, 0x0, 0x9, 0x6e, 0x88, 0xf1, 0xff, 0xff, 0xff}, + }, + // with proper (fixed size) height and round (PreVote): + { + CanonicalizeVote("", &Vote{Height: 1, Round: 1, Type: PrevoteType}), + []byte{ + 0x1f, // total length + 0x11, // (field_number << 3) | wire_type (version is missing) + 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, // height + 0x19, // (field_number << 3) | wire_type + 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, // round + 0x20, // (field_number << 3) | wire_type + 0x1, // PrevoteType + 0x2a, // (field_number << 3) | wire_type + // remaining fields (timestamp): + 0x9, 0x9, 0x0, 0x9, 0x6e, 0x88, 0xf1, 0xff, 0xff, 0xff}, + }, + // containing version (empty type) + { + voteWithVersion, + []byte{ + 0x26, // total length + 0x9, // (field_number << 3) | wire_type + 0x7b, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, // version (123) + 0x11, // (field_number << 3) | wire_type + 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, // height + 0x19, // (field_number << 3) | wire_type + 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, // round + // remaining fields (timestamp): + 0x2a, + 0x9, 0x9, 0x0, 0x9, 0x6e, 0x88, 0xf1, 0xff, 0xff, 0xff}, + }, + // containing non-empty chain_id: + { + CanonicalizeVote("test_chain_id", &Vote{Height: 1, Round: 1}), + []byte{ + 0x2c, // total length + 0x11, // (field_number << 3) | wire_type + 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, // height + 0x19, // (field_number << 3) | wire_type + 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, // round + // remaining fields: + 0x2a, // (field_number << 3) | wire_type + 0x9, 0x9, 0x0, 0x9, 0x6e, 0x88, 0xf1, 0xff, 0xff, 0xff, // timestamp + 0x3a, // (field_number << 3) | wire_type + 0xd, 0x74, 0x65, 0x73, 0x74, 0x5f, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x5f, 0x69, 0x64}, // chainID + }, + } + for i, tc := range tests { + got, err := cdc.MarshalBinary(tc.canonicalVote) + require.NoError(t, err) + + require.Equal(t, tc.want, got, "test case #%v: got unexpected sign bytes for Vote.", i) + } +} + +func TestVoteProposalNotEq(t *testing.T) { + cv := CanonicalizeVote("", &Vote{Height: 1, Round: 1}) + p := CanonicalizeProposal("", &Proposal{Height: 1, Round: 1}) + vb, err := cdc.MarshalBinary(cv) + require.NoError(t, err) + pb, err := cdc.MarshalBinary(p) + require.NoError(t, err) + require.NotEqual(t, vb, pb) +} + func TestVoteVerifySignature(t *testing.T) { privVal := NewMockPV() pubkey := privVal.GetPubKey() @@ -85,12 +177,12 @@ func TestVoteVerifySignature(t *testing.T) { func TestIsVoteTypeValid(t *testing.T) { tc := []struct { name string - in byte + in SignedMsgType out bool }{ - {"Prevote", VoteTypePrevote, true}, - {"Precommit", VoteTypePrecommit, true}, - {"InvalidType", byte(3), false}, + {"Prevote", PrevoteType, true}, + {"Precommit", PrecommitType, true}, + {"InvalidType", SignedMsgType(0x3), false}, } for _, tt := range tc { @@ -128,7 +220,7 @@ func TestMaxVoteBytes(t *testing.T) { Height: math.MaxInt64, Round: math.MaxInt64, Timestamp: tmtime.Now(), - Type: VoteTypePrevote, + Type: PrevoteType, BlockID: BlockID{ Hash: tmhash.Sum([]byte("blockID_hash")), PartsHeader: PartSetHeader{ From 0baa7588c278c43cd19fbf435f1de17ece9923a7 Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Fri, 12 Oct 2018 19:25:33 -0400 Subject: [PATCH 007/267] p2p: NodeInfo is an interface; General cleanup (#2556) * p2p: NodeInfo is an interface * (squash) fixes from review * (squash) more fixes from review * p2p: remove peerConn.HandshakeTimeout * p2p: NodeInfo is two interfaces. Remove String() * fixes from review * remove test code from peer.RemoteIP() * p2p: remove peer.OriginalAddr(). See #2618 * use a mockPeer in peer_set_test.go * p2p: fix testNodeInfo naming * p2p: remove unused var * remove testRandNodeInfo * fix linter * fix retry dialing self * fix rpc --- benchmarks/codec_test.go | 50 +++++---------- blockchain/reactor_test.go | 3 +- consensus/common_test.go | 2 +- node/node.go | 6 +- p2p/dummy/peer.go | 7 +- p2p/errors.go | 5 +- p2p/node_info.go | 76 +++++++++++++--------- p2p/peer.go | 107 ++++++++----------------------- p2p/peer_set_test.go | 45 +++++++------ p2p/peer_test.go | 36 +++++------ p2p/pex/pex_reactor_test.go | 7 +- p2p/switch.go | 13 ++-- p2p/switch_test.go | 1 + p2p/test_util.go | 89 +++++++++++++------------ p2p/transport.go | 35 +++++----- p2p/transport_test.go | 83 +++++++++--------------- rpc/core/consensus.go | 3 +- rpc/core/net.go | 9 ++- rpc/core/status.go | 3 +- rpc/core/types/responses.go | 8 +-- rpc/core/types/responses_test.go | 12 ++-- 21 files changed, 269 insertions(+), 331 deletions(-) diff --git a/benchmarks/codec_test.go b/benchmarks/codec_test.go index c0e13d16..71d7a83b 100644 --- a/benchmarks/codec_test.go +++ b/benchmarks/codec_test.go @@ -12,23 +12,27 @@ import ( ctypes "github.com/tendermint/tendermint/rpc/core/types" ) +func testNodeInfo(id p2p.ID) p2p.DefaultNodeInfo { + return p2p.DefaultNodeInfo{ + ID_: id, + Moniker: "SOMENAME", + Network: "SOMENAME", + ListenAddr: "SOMEADDR", + Version: "SOMEVER", + Other: p2p.DefaultNodeInfoOther{ + AminoVersion: "SOMESTRING", + P2PVersion: "OTHERSTRING", + }, + } +} + func BenchmarkEncodeStatusWire(b *testing.B) { b.StopTimer() cdc := amino.NewCodec() ctypes.RegisterAmino(cdc) nodeKey := p2p.NodeKey{PrivKey: ed25519.GenPrivKey()} status := &ctypes.ResultStatus{ - NodeInfo: p2p.NodeInfo{ - ID: nodeKey.ID(), - Moniker: "SOMENAME", - Network: "SOMENAME", - ListenAddr: "SOMEADDR", - Version: "SOMEVER", - Other: p2p.NodeInfoOther{ - AminoVersion: "SOMESTRING", - P2PVersion: "OTHERSTRING", - }, - }, + NodeInfo: testNodeInfo(nodeKey.ID()), SyncInfo: ctypes.SyncInfo{ LatestBlockHash: []byte("SOMEBYTES"), LatestBlockHeight: 123, @@ -56,17 +60,7 @@ func BenchmarkEncodeNodeInfoWire(b *testing.B) { cdc := amino.NewCodec() ctypes.RegisterAmino(cdc) nodeKey := p2p.NodeKey{PrivKey: ed25519.GenPrivKey()} - nodeInfo := p2p.NodeInfo{ - ID: nodeKey.ID(), - Moniker: "SOMENAME", - Network: "SOMENAME", - ListenAddr: "SOMEADDR", - Version: "SOMEVER", - Other: p2p.NodeInfoOther{ - AminoVersion: "SOMESTRING", - P2PVersion: "OTHERSTRING", - }, - } + nodeInfo := testNodeInfo(nodeKey.ID()) b.StartTimer() counter := 0 @@ -84,17 +78,7 @@ func BenchmarkEncodeNodeInfoBinary(b *testing.B) { cdc := amino.NewCodec() ctypes.RegisterAmino(cdc) nodeKey := p2p.NodeKey{PrivKey: ed25519.GenPrivKey()} - nodeInfo := p2p.NodeInfo{ - ID: nodeKey.ID(), - Moniker: "SOMENAME", - Network: "SOMENAME", - ListenAddr: "SOMEADDR", - Version: "SOMEVER", - Other: p2p.NodeInfoOther{ - AminoVersion: "SOMESTRING", - P2PVersion: "OTHERSTRING", - }, - } + nodeInfo := testNodeInfo(nodeKey.ID()) b.StartTimer() counter := 0 diff --git a/blockchain/reactor_test.go b/blockchain/reactor_test.go index b63a057e..7fc7ffb7 100644 --- a/blockchain/reactor_test.go +++ b/blockchain/reactor_test.go @@ -198,7 +198,7 @@ func (tp *bcrTestPeer) TrySend(chID byte, msgBytes []byte) bool { } func (tp *bcrTestPeer) Send(chID byte, msgBytes []byte) bool { return tp.TrySend(chID, msgBytes) } -func (tp *bcrTestPeer) NodeInfo() p2p.NodeInfo { return p2p.NodeInfo{} } +func (tp *bcrTestPeer) NodeInfo() p2p.NodeInfo { return p2p.DefaultNodeInfo{} } func (tp *bcrTestPeer) Status() p2p.ConnectionStatus { return p2p.ConnectionStatus{} } func (tp *bcrTestPeer) ID() p2p.ID { return tp.id } func (tp *bcrTestPeer) IsOutbound() bool { return false } @@ -206,4 +206,3 @@ func (tp *bcrTestPeer) IsPersistent() bool { return true } func (tp *bcrTestPeer) Get(s string) interface{} { return s } func (tp *bcrTestPeer) Set(string, interface{}) {} func (tp *bcrTestPeer) RemoteIP() net.IP { return []byte{127, 0, 0, 1} } -func (tp *bcrTestPeer) OriginalAddr() *p2p.NetAddress { return nil } diff --git a/consensus/common_test.go b/consensus/common_test.go index 26f8e3e5..ddce6914 100644 --- a/consensus/common_test.go +++ b/consensus/common_test.go @@ -568,7 +568,7 @@ func randConsensusNetWithPeers(nValidators, nPeers int, testName string, tickerF func getSwitchIndex(switches []*p2p.Switch, peer p2p.Peer) int { for i, s := range switches { - if peer.NodeInfo().ID == s.NodeInfo().ID { + if peer.NodeInfo().ID() == s.NodeInfo().ID() { return i } } diff --git a/node/node.go b/node/node.go index 9c409787..ed0fa119 100644 --- a/node/node.go +++ b/node/node.go @@ -761,8 +761,8 @@ func makeNodeInfo( if _, ok := txIndexer.(*null.TxIndex); ok { txIndexerStatus = "off" } - nodeInfo := p2p.NodeInfo{ - ID: nodeID, + nodeInfo := p2p.DefaultNodeInfo{ + ID_: nodeID, Network: chainID, Version: version.Version, Channels: []byte{ @@ -772,7 +772,7 @@ func makeNodeInfo( evidence.EvidenceChannel, }, Moniker: config.Moniker, - Other: p2p.NodeInfoOther{ + Other: p2p.DefaultNodeInfoOther{ AminoVersion: amino.Version, P2PVersion: p2p.Version, ConsensusVersion: cs.Version, diff --git a/p2p/dummy/peer.go b/p2p/dummy/peer.go index bb6e822f..4871719d 100644 --- a/p2p/dummy/peer.go +++ b/p2p/dummy/peer.go @@ -42,7 +42,7 @@ func (p *peer) IsPersistent() bool { // NodeInfo always returns empty node info. func (p *peer) NodeInfo() p2p.NodeInfo { - return p2p.NodeInfo{} + return p2p.DefaultNodeInfo{} } // RemoteIP always returns localhost. @@ -78,8 +78,3 @@ func (p *peer) Get(key string) interface{} { } return nil } - -// OriginalAddr always returns nil. -func (p *peer) OriginalAddr() *p2p.NetAddress { - return nil -} diff --git a/p2p/errors.go b/p2p/errors.go index 902d2203..70615094 100644 --- a/p2p/errors.go +++ b/p2p/errors.go @@ -40,13 +40,12 @@ func (e ErrRejected) Error() string { if e.isDuplicate { if e.conn != nil { return fmt.Sprintf( - "duplicate CONN<%s>: %s", + "duplicate CONN<%s>", e.conn.RemoteAddr().String(), - e.err, ) } if e.id != "" { - return fmt.Sprintf("duplicate ID<%v>: %s", e.id, e.err) + return fmt.Sprintf("duplicate ID<%v>", e.id) } } diff --git a/p2p/node_info.go b/p2p/node_info.go index a1653594..a468443d 100644 --- a/p2p/node_info.go +++ b/p2p/node_info.go @@ -2,6 +2,7 @@ package p2p import ( "fmt" + "reflect" "strings" cmn "github.com/tendermint/tendermint/libs/common" @@ -17,12 +18,32 @@ func MaxNodeInfoSize() int { return maxNodeInfoSize } -// NodeInfo is the basic node information exchanged +// NodeInfo exposes basic info of a node +// and determines if we're compatible +type NodeInfo interface { + nodeInfoAddress + nodeInfoTransport +} + +// nodeInfoAddress exposes just the core info of a node. +type nodeInfoAddress interface { + ID() ID + NetAddress() *NetAddress +} + +// nodeInfoTransport is validates a nodeInfo and checks +// our compatibility with it. It's for use in the handshake. +type nodeInfoTransport interface { + ValidateBasic() error + CompatibleWith(other NodeInfo) error +} + +// DefaultNodeInfo is the basic node information exchanged // between two peers during the Tendermint P2P handshake. -type NodeInfo struct { +type DefaultNodeInfo struct { // Authenticate // TODO: replace with NetAddress - ID ID `json:"id"` // authenticated identifier + ID_ ID `json:"id"` // authenticated identifier ListenAddr string `json:"listen_addr"` // accepting incoming // Check compatibility. @@ -32,12 +53,12 @@ type NodeInfo struct { Channels cmn.HexBytes `json:"channels"` // channels this node knows about // ASCIIText fields - Moniker string `json:"moniker"` // arbitrary moniker - Other NodeInfoOther `json:"other"` // other application specific data + Moniker string `json:"moniker"` // arbitrary moniker + Other DefaultNodeInfoOther `json:"other"` // other application specific data } -// NodeInfoOther is the misc. applcation specific data -type NodeInfoOther struct { +// DefaultNodeInfoOther is the misc. applcation specific data +type DefaultNodeInfoOther struct { AminoVersion string `json:"amino_version"` P2PVersion string `json:"p2p_version"` ConsensusVersion string `json:"consensus_version"` @@ -46,19 +67,12 @@ type NodeInfoOther struct { RPCAddress string `json:"rpc_address"` } -func (o NodeInfoOther) String() string { - return fmt.Sprintf( - "{amino_version: %v, p2p_version: %v, consensus_version: %v, rpc_version: %v, tx_index: %v, rpc_address: %v}", - o.AminoVersion, - o.P2PVersion, - o.ConsensusVersion, - o.RPCVersion, - o.TxIndex, - o.RPCAddress, - ) +// ID returns the node's peer ID. +func (info DefaultNodeInfo) ID() ID { + return info.ID_ } -// Validate checks the self-reported NodeInfo is safe. +// ValidateBasic checks the self-reported DefaultNodeInfo is safe. // It returns an error if there // are too many Channels, if there are any duplicate Channels, // if the ListenAddr is malformed, or if the ListenAddr is a host name @@ -71,7 +85,7 @@ func (o NodeInfoOther) String() string { // International clients could then use punycode (or we could use // url-encoding), and we just need to be careful with how we handle that in our // clients. (e.g. off by default). -func (info NodeInfo) Validate() error { +func (info DefaultNodeInfo) ValidateBasic() error { if len(info.Channels) > maxNumChannels { return fmt.Errorf("info.Channels is too long (%v). Max is %v", len(info.Channels), maxNumChannels) } @@ -111,14 +125,19 @@ func (info NodeInfo) Validate() error { } // ensure ListenAddr is good - _, err := NewNetAddressString(IDAddressString(info.ID, info.ListenAddr)) + _, err := NewNetAddressString(IDAddressString(info.ID(), info.ListenAddr)) return err } -// CompatibleWith checks if two NodeInfo are compatible with eachother. +// CompatibleWith checks if two DefaultNodeInfo are compatible with eachother. // CONTRACT: two nodes are compatible if the major version matches and network match // and they have at least one channel in common. -func (info NodeInfo) CompatibleWith(other NodeInfo) error { +func (info DefaultNodeInfo) CompatibleWith(other_ NodeInfo) error { + other, ok := other_.(DefaultNodeInfo) + if !ok { + return fmt.Errorf("wrong NodeInfo type. Expected DefaultNodeInfo, got %v", reflect.TypeOf(other_)) + } + iMajor, _, _, iErr := splitVersion(info.Version) oMajor, _, _, oErr := splitVersion(other.Version) @@ -164,18 +183,18 @@ OUTER_LOOP: return nil } -// NetAddress returns a NetAddress derived from the NodeInfo - +// NetAddress returns a NetAddress derived from the DefaultNodeInfo - // it includes the authenticated peer ID and the self-reported // ListenAddr. Note that the ListenAddr is not authenticated and // may not match that address actually dialed if its an outbound peer. -func (info NodeInfo) NetAddress() *NetAddress { - netAddr, err := NewNetAddressString(IDAddressString(info.ID, info.ListenAddr)) +func (info DefaultNodeInfo) NetAddress() *NetAddress { + netAddr, err := NewNetAddressString(IDAddressString(info.ID(), info.ListenAddr)) if err != nil { switch err.(type) { case ErrNetAddressLookup: // XXX If the peer provided a host name and the lookup fails here // we're out of luck. - // TODO: use a NetAddress in NodeInfo + // TODO: use a NetAddress in DefaultNodeInfo default: panic(err) // everything should be well formed by now } @@ -183,11 +202,6 @@ func (info NodeInfo) NetAddress() *NetAddress { return netAddr } -func (info NodeInfo) String() string { - return fmt.Sprintf("NodeInfo{id: %v, moniker: %v, network: %v [listen %v], version: %v (%v)}", - info.ID, info.Moniker, info.Network, info.ListenAddr, info.Version, info.Other) -} - func splitVersion(version string) (string, string, string, error) { spl := strings.Split(version, ".") if len(spl) != 3 { diff --git a/p2p/peer.go b/p2p/peer.go index ba22695e..00931314 100644 --- a/p2p/peer.go +++ b/p2p/peer.go @@ -3,7 +3,6 @@ package p2p import ( "fmt" "net" - "sync/atomic" "time" cmn "github.com/tendermint/tendermint/libs/common" @@ -15,19 +14,18 @@ import ( const metricsTickerDuration = 10 * time.Second -var testIPSuffix uint32 - // Peer is an interface representing a peer connected on a reactor. type Peer interface { cmn.Service - ID() ID // peer's cryptographic ID - RemoteIP() net.IP // remote IP of the connection + ID() ID // peer's cryptographic ID + RemoteIP() net.IP // remote IP of the connection + IsOutbound() bool // did we dial the peer IsPersistent() bool // do we redial this peer when we disconnect + NodeInfo() NodeInfo // peer's info Status() tmconn.ConnectionStatus - OriginalAddr() *NetAddress Send(byte, []byte) bool TrySend(byte, []byte) bool @@ -40,12 +38,13 @@ type Peer interface { // peerConn contains the raw connection and its config. type peerConn struct { - outbound bool - persistent bool - config *config.P2PConfig - conn net.Conn // source connection - ip net.IP - originalAddr *NetAddress // nil for inbound connections + outbound bool + persistent bool + config *config.P2PConfig + conn net.Conn // source connection + + // cached RemoteIP() + ip net.IP } // ID only exists for SecretConnection. @@ -60,14 +59,6 @@ func (pc peerConn) RemoteIP() net.IP { return pc.ip } - // In test cases a conn could not be present at all or be an in-memory - // implementation where we want to return a fake ip. - if pc.conn == nil || pc.conn.RemoteAddr().String() == "pipe" { - pc.ip = net.IP{172, 16, 0, byte(atomic.AddUint32(&testIPSuffix, 1))} - - return pc.ip - } - host, _, err := net.SplitHostPort(pc.conn.RemoteAddr().String()) if err != nil { panic(err) @@ -120,7 +111,7 @@ func newPeer( p := &peer{ peerConn: pc, nodeInfo: nodeInfo, - channels: nodeInfo.Channels, + channels: nodeInfo.(DefaultNodeInfo).Channels, // TODO Data: cmn.NewCMap(), metricsTicker: time.NewTicker(metricsTickerDuration), metrics: NopMetrics(), @@ -142,6 +133,15 @@ func newPeer( return p } +// String representation. +func (p *peer) String() string { + if p.outbound { + return fmt.Sprintf("Peer{%v %v out}", p.mconn, p.ID()) + } + + return fmt.Sprintf("Peer{%v %v in}", p.mconn, p.ID()) +} + //--------------------------------------------------- // Implements cmn.Service @@ -177,7 +177,7 @@ func (p *peer) OnStop() { // ID returns the peer's ID - the hex encoded hash of its pubkey. func (p *peer) ID() ID { - return p.nodeInfo.ID + return p.nodeInfo.ID() } // IsOutbound returns true if the connection is outbound, false otherwise. @@ -195,15 +195,6 @@ func (p *peer) NodeInfo() NodeInfo { return p.nodeInfo } -// OriginalAddr returns the original address, which was used to connect with -// the peer. Returns nil for inbound peers. -func (p *peer) OriginalAddr() *NetAddress { - if p.peerConn.outbound { - return p.peerConn.originalAddr - } - return nil -} - // Status returns the peer's ConnectionStatus. func (p *peer) Status() tmconn.ConnectionStatus { return p.mconn.Status() @@ -272,53 +263,14 @@ func (p *peer) hasChannel(chID byte) bool { } //--------------------------------------------------- -// methods used by the Switch +// methods only used for testing +// TODO: can we remove these? -// CloseConn should be called by the Switch if the peer was created but never -// started. +// CloseConn closes the underlying connection func (pc *peerConn) CloseConn() { pc.conn.Close() // nolint: errcheck } -// HandshakeTimeout performs the Tendermint P2P handshake between a given node -// and the peer by exchanging their NodeInfo. It sets the received nodeInfo on -// the peer. -// NOTE: blocking -func (pc *peerConn) HandshakeTimeout( - ourNodeInfo NodeInfo, - timeout time.Duration, -) (peerNodeInfo NodeInfo, err error) { - // Set deadline for handshake so we don't block forever on conn.ReadFull - if err := pc.conn.SetDeadline(time.Now().Add(timeout)); err != nil { - return peerNodeInfo, cmn.ErrorWrap(err, "Error setting deadline") - } - - var trs, _ = cmn.Parallel( - func(_ int) (val interface{}, err error, abort bool) { - _, err = cdc.MarshalBinaryWriter(pc.conn, ourNodeInfo) - return - }, - func(_ int) (val interface{}, err error, abort bool) { - _, err = cdc.UnmarshalBinaryReader( - pc.conn, - &peerNodeInfo, - int64(MaxNodeInfoSize()), - ) - return - }, - ) - if err := trs.FirstError(); err != nil { - return peerNodeInfo, cmn.ErrorWrap(err, "Error during handshake") - } - - // Remove deadline - if err := pc.conn.SetDeadline(time.Time{}); err != nil { - return peerNodeInfo, cmn.ErrorWrap(err, "Error removing deadline") - } - - return peerNodeInfo, nil -} - // Addr returns peer's remote network address. func (p *peer) Addr() net.Addr { return p.peerConn.conn.RemoteAddr() @@ -332,14 +284,7 @@ func (p *peer) CanSend(chID byte) bool { return p.mconn.CanSend(chID) } -// String representation. -func (p *peer) String() string { - if p.outbound { - return fmt.Sprintf("Peer{%v %v out}", p.mconn, p.ID()) - } - - return fmt.Sprintf("Peer{%v %v in}", p.mconn, p.ID()) -} +//--------------------------------------------------- func PeerMetrics(metrics *Metrics) PeerOption { return func(p *peer) { diff --git a/p2p/peer_set_test.go b/p2p/peer_set_test.go index ee1c52ea..c0ad8000 100644 --- a/p2p/peer_set_test.go +++ b/p2p/peer_set_test.go @@ -1,7 +1,6 @@ package p2p import ( - "fmt" "net" "sync" "testing" @@ -12,24 +11,34 @@ import ( cmn "github.com/tendermint/tendermint/libs/common" ) -// Returns an empty kvstore peer -func randPeer(ip net.IP) *peer { +// mockPeer for testing the PeerSet +type mockPeer struct { + cmn.BaseService + ip net.IP + id ID +} + +func (mp *mockPeer) TrySend(chID byte, msgBytes []byte) bool { return true } +func (mp *mockPeer) Send(chID byte, msgBytes []byte) bool { return true } +func (mp *mockPeer) NodeInfo() NodeInfo { return DefaultNodeInfo{} } +func (mp *mockPeer) Status() ConnectionStatus { return ConnectionStatus{} } +func (mp *mockPeer) ID() ID { return mp.id } +func (mp *mockPeer) IsOutbound() bool { return false } +func (mp *mockPeer) IsPersistent() bool { return true } +func (mp *mockPeer) Get(s string) interface{} { return s } +func (mp *mockPeer) Set(string, interface{}) {} +func (mp *mockPeer) RemoteIP() net.IP { return mp.ip } + +// Returns a mock peer +func newMockPeer(ip net.IP) *mockPeer { if ip == nil { ip = net.IP{127, 0, 0, 1} } - nodeKey := NodeKey{PrivKey: ed25519.GenPrivKey()} - p := &peer{ - nodeInfo: NodeInfo{ - ID: nodeKey.ID(), - ListenAddr: fmt.Sprintf("%v.%v.%v.%v:26656", cmn.RandInt()%256, cmn.RandInt()%256, cmn.RandInt()%256, cmn.RandInt()%256), - }, - metrics: NopMetrics(), + return &mockPeer{ + ip: ip, + id: nodeKey.ID(), } - - p.ip = ip - - return p } func TestPeerSetAddRemoveOne(t *testing.T) { @@ -39,7 +48,7 @@ func TestPeerSetAddRemoveOne(t *testing.T) { var peerList []Peer for i := 0; i < 5; i++ { - p := randPeer(net.IP{127, 0, 0, byte(i)}) + p := newMockPeer(net.IP{127, 0, 0, byte(i)}) if err := peerSet.Add(p); err != nil { t.Error(err) } @@ -83,7 +92,7 @@ func TestPeerSetAddRemoveMany(t *testing.T) { peers := []Peer{} N := 100 for i := 0; i < N; i++ { - peer := randPeer(net.IP{127, 0, 0, byte(i)}) + peer := newMockPeer(net.IP{127, 0, 0, byte(i)}) if err := peerSet.Add(peer); err != nil { t.Errorf("Failed to add new peer") } @@ -107,7 +116,7 @@ func TestPeerSetAddRemoveMany(t *testing.T) { func TestPeerSetAddDuplicate(t *testing.T) { t.Parallel() peerSet := NewPeerSet() - peer := randPeer(nil) + peer := newMockPeer(nil) n := 20 errsChan := make(chan error) @@ -149,7 +158,7 @@ func TestPeerSetGet(t *testing.T) { var ( peerSet = NewPeerSet() - peer = randPeer(nil) + peer = newMockPeer(nil) ) assert.Nil(t, peerSet.Get(peer.ID()), "expecting a nil lookup, before .Add") diff --git a/p2p/peer_test.go b/p2p/peer_test.go index a2a2946a..fecf7f1c 100644 --- a/p2p/peer_test.go +++ b/p2p/peer_test.go @@ -19,8 +19,6 @@ import ( tmconn "github.com/tendermint/tendermint/p2p/conn" ) -const testCh = 0x01 - func TestPeerBasic(t *testing.T) { assert, require := assert.New(t), require.New(t) @@ -81,18 +79,14 @@ func createOutboundPeerAndPerformHandshake( if err != nil { return nil, err } - nodeInfo, err := pc.HandshakeTimeout(NodeInfo{ - ID: addr.ID, - Moniker: "host_peer", - Network: "testing", - Version: "123.123.123", - Channels: []byte{testCh}, - }, 1*time.Second) + timeout := 1 * time.Second + ourNodeInfo := testNodeInfo(addr.ID, "host_peer") + peerNodeInfo, err := handshake(pc.conn, timeout, ourNodeInfo) if err != nil { return nil, err } - p := newPeer(pc, mConfig, nodeInfo, reactorsByCh, chDescs, func(p Peer, r interface{}) {}) + p := newPeer(pc, mConfig, peerNodeInfo, reactorsByCh, chDescs, func(p Peer, r interface{}) {}) p.SetLogger(log.TestingLogger().With("peer", addr)) return p, nil } @@ -120,7 +114,7 @@ func testOutboundPeerConn( return peerConn{}, cmn.ErrorWrap(err, "Error creating peer") } - pc, err := testPeerConn(conn, config, true, persistent, ourNodePrivKey, addr) + pc, err := testPeerConn(conn, config, true, persistent, ourNodePrivKey) if err != nil { if cerr := conn.Close(); cerr != nil { return peerConn{}, cmn.ErrorWrap(err, cerr.Error()) @@ -191,14 +185,7 @@ func (rp *remotePeer) accept(l net.Listener) { golog.Fatalf("Failed to create a peer: %+v", err) } - _, err = handshake(pc.conn, time.Second, NodeInfo{ - ID: rp.Addr().ID, - Moniker: "remote_peer", - Network: "testing", - Version: "123.123.123", - ListenAddr: l.Addr().String(), - Channels: rp.channels, - }) + _, err = handshake(pc.conn, time.Second, rp.nodeInfo(l)) if err != nil { golog.Fatalf("Failed to perform handshake: %+v", err) } @@ -217,3 +204,14 @@ func (rp *remotePeer) accept(l net.Listener) { } } } + +func (rp *remotePeer) nodeInfo(l net.Listener) NodeInfo { + return DefaultNodeInfo{ + ID_: rp.Addr().ID, + Moniker: "remote_peer", + Network: "testing", + Version: "123.123.123", + ListenAddr: l.Addr().String(), + Channels: rp.channels, + } +} diff --git a/p2p/pex/pex_reactor_test.go b/p2p/pex/pex_reactor_test.go index c22eabdc..b0338c3c 100644 --- a/p2p/pex/pex_reactor_test.go +++ b/p2p/pex/pex_reactor_test.go @@ -320,7 +320,7 @@ func TestPEXReactorDoesNotAddPrivatePeersToAddrBook(t *testing.T) { peer := p2p.CreateRandomPeer(false) pexR, book := createReactor(&PEXReactorConfig{}) - book.AddPrivateIDs([]string{string(peer.NodeInfo().ID)}) + book.AddPrivateIDs([]string{string(peer.NodeInfo().ID())}) defer teardownReactor(book) // we have to send a request to receive responses @@ -391,8 +391,8 @@ func (mp mockPeer) ID() p2p.ID { return mp.addr.ID } func (mp mockPeer) IsOutbound() bool { return mp.outbound } func (mp mockPeer) IsPersistent() bool { return mp.persistent } func (mp mockPeer) NodeInfo() p2p.NodeInfo { - return p2p.NodeInfo{ - ID: mp.addr.ID, + return p2p.DefaultNodeInfo{ + ID_: mp.addr.ID, ListenAddr: mp.addr.DialString(), } } @@ -402,7 +402,6 @@ func (mockPeer) Send(byte, []byte) bool { return false } func (mockPeer) TrySend(byte, []byte) bool { return false } func (mockPeer) Set(string, interface{}) {} func (mockPeer) Get(string) interface{} { return nil } -func (mockPeer) OriginalAddr() *p2p.NetAddress { return nil } func assertPeersWithTimeout( t *testing.T, diff --git a/p2p/switch.go b/p2p/switch.go index 8325d7e8..64e248fc 100644 --- a/p2p/switch.go +++ b/p2p/switch.go @@ -280,12 +280,9 @@ func (sw *Switch) StopPeerForError(peer Peer, reason interface{}) { sw.stopAndRemovePeer(peer, reason) if peer.IsPersistent() { - addr := peer.OriginalAddr() - if addr == nil { - // FIXME: persistent peers can't be inbound right now. - // self-reported address for inbound persistent peers - addr = peer.NodeInfo().NetAddress() - } + // TODO: use the original address dialed, not the self reported one + // See #2618. + addr := peer.NodeInfo().NetAddress() go sw.reconnectToPeer(addr) } } @@ -560,9 +557,13 @@ func (sw *Switch) addOutboundPeerWithConfig( // to avoid dialing in the future. sw.addrBook.RemoveAddress(addr) sw.addrBook.AddOurAddress(addr) + + return err } } + // retry persistent peers after + // any dial error besides IsSelf() if persistent { go sw.reconnectToPeer(addr) } diff --git a/p2p/switch_test.go b/p2p/switch_test.go index 4fea3cfe..f52e47f0 100644 --- a/p2p/switch_test.go +++ b/p2p/switch_test.go @@ -143,6 +143,7 @@ func assertMsgReceivedWithTimeout(t *testing.T, msgBytes []byte, channel byte, r } return } + case <-time.After(timeout): t.Fatalf("Expected to have received 1 message in channel #%v, got zero", channel) } diff --git a/p2p/test_util.go b/p2p/test_util.go index e35e0989..2859dc64 100644 --- a/p2p/test_util.go +++ b/p2p/test_util.go @@ -14,6 +14,19 @@ import ( "github.com/tendermint/tendermint/p2p/conn" ) +const testCh = 0x01 + +//------------------------------------------------ + +type mockNodeInfo struct { + addr *NetAddress +} + +func (ni mockNodeInfo) ID() ID { return ni.addr.ID } +func (ni mockNodeInfo) NetAddress() *NetAddress { return ni.addr } +func (ni mockNodeInfo) ValidateBasic() error { return nil } +func (ni mockNodeInfo) CompatibleWith(other NodeInfo) error { return nil } + func AddPeerToSwitch(sw *Switch, peer Peer) { sw.peers.Add(peer) } @@ -24,12 +37,9 @@ func CreateRandomPeer(outbound bool) *peer { peerConn: peerConn{ outbound: outbound, }, - nodeInfo: NodeInfo{ - ID: netAddr.ID, - ListenAddr: netAddr.DialString(), - }, - mconn: &conn.MConnection{}, - metrics: NopMetrics(), + nodeInfo: mockNodeInfo{netAddr}, + mconn: &conn.MConnection{}, + metrics: NopMetrics(), } p.SetLogger(log.TestingLogger().With("peer", addr)) return p @@ -159,36 +169,15 @@ func MakeSwitch( initSwitch func(int, *Switch) *Switch, opts ...SwitchOption, ) *Switch { - var ( - nodeKey = NodeKey{ - PrivKey: ed25519.GenPrivKey(), - } - ni = NodeInfo{ - ID: nodeKey.ID(), - Moniker: fmt.Sprintf("switch%d", i), - Network: network, - Version: version, - ListenAddr: fmt.Sprintf("127.0.0.1:%d", cmn.RandIntn(64512)+1023), - Other: NodeInfoOther{ - AminoVersion: "1.0", - P2PVersion: "1.0", - ConsensusVersion: "1.0", - RPCVersion: "1.0", - TxIndex: "off", - RPCAddress: fmt.Sprintf("127.0.0.1:%d", cmn.RandIntn(64512)+1023), - }, - } - ) - addr, err := NewNetAddressStringWithOptionalID( - IDAddressString(nodeKey.ID(), ni.ListenAddr), - ) - if err != nil { - panic(err) + nodeKey := NodeKey{ + PrivKey: ed25519.GenPrivKey(), } + nodeInfo := testNodeInfo(nodeKey.ID(), fmt.Sprintf("node%d", i)) - t := NewMultiplexTransport(ni, nodeKey) + t := NewMultiplexTransport(nodeInfo, nodeKey) + addr := nodeInfo.NetAddress() if err := t.Listen(*addr); err != nil { panic(err) } @@ -198,14 +187,16 @@ func MakeSwitch( sw.SetLogger(log.TestingLogger()) sw.SetNodeKey(&nodeKey) + ni := nodeInfo.(DefaultNodeInfo) for ch := range sw.reactorsByCh { ni.Channels = append(ni.Channels, ch) } + nodeInfo = ni // TODO: We need to setup reactors ahead of time so the NodeInfo is properly // populated and we don't have to do those awkward overrides and setters. - t.nodeInfo = ni - sw.SetNodeInfo(ni) + t.nodeInfo = nodeInfo + sw.SetNodeInfo(nodeInfo) return sw } @@ -215,7 +206,7 @@ func testInboundPeerConn( config *config.P2PConfig, ourNodePrivKey crypto.PrivKey, ) (peerConn, error) { - return testPeerConn(conn, config, false, false, ourNodePrivKey, nil) + return testPeerConn(conn, config, false, false, ourNodePrivKey) } func testPeerConn( @@ -223,7 +214,6 @@ func testPeerConn( cfg *config.P2PConfig, outbound, persistent bool, ourNodePrivKey crypto.PrivKey, - originalAddr *NetAddress, ) (pc peerConn, err error) { conn := rawConn @@ -241,10 +231,27 @@ func testPeerConn( // Only the information we already have return peerConn{ - config: cfg, - outbound: outbound, - persistent: persistent, - conn: conn, - originalAddr: originalAddr, + config: cfg, + outbound: outbound, + persistent: persistent, + conn: conn, }, nil } + +//---------------------------------------------------------------- +// rand node info + +func testNodeInfo(id ID, name string) NodeInfo { + return testNodeInfoWithNetwork(id, name, "testing") +} + +func testNodeInfoWithNetwork(id ID, name, network string) NodeInfo { + return DefaultNodeInfo{ + ID_: id, + ListenAddr: fmt.Sprintf("127.0.0.1:%d", cmn.RandIntn(64512)+1023), + Moniker: name, + Network: network, + Version: "123.123.123", + Channels: []byte{testCh}, + } +} diff --git a/p2p/transport.go b/p2p/transport.go index 6f097b4f..b20f32f3 100644 --- a/p2p/transport.go +++ b/p2p/transport.go @@ -335,7 +335,7 @@ func (mt *MultiplexTransport) upgrade( secretConn, err = upgradeSecretConn(c, mt.handshakeTimeout, mt.nodeKey.PrivKey) if err != nil { - return nil, NodeInfo{}, ErrRejected{ + return nil, nil, ErrRejected{ conn: c, err: fmt.Errorf("secrect conn failed: %v", err), isAuthFailure: true, @@ -344,15 +344,15 @@ func (mt *MultiplexTransport) upgrade( nodeInfo, err = handshake(secretConn, mt.handshakeTimeout, mt.nodeInfo) if err != nil { - return nil, NodeInfo{}, ErrRejected{ + return nil, nil, ErrRejected{ conn: c, err: fmt.Errorf("handshake failed: %v", err), isAuthFailure: true, } } - if err := nodeInfo.Validate(); err != nil { - return nil, NodeInfo{}, ErrRejected{ + if err := nodeInfo.ValidateBasic(); err != nil { + return nil, nil, ErrRejected{ conn: c, err: err, isNodeInfoInvalid: true, @@ -360,34 +360,34 @@ func (mt *MultiplexTransport) upgrade( } // Ensure connection key matches self reported key. - if connID := PubKeyToID(secretConn.RemotePubKey()); connID != nodeInfo.ID { - return nil, NodeInfo{}, ErrRejected{ + if connID := PubKeyToID(secretConn.RemotePubKey()); connID != nodeInfo.ID() { + return nil, nil, ErrRejected{ conn: c, id: connID, err: fmt.Errorf( "conn.ID (%v) NodeInfo.ID (%v) missmatch", connID, - nodeInfo.ID, + nodeInfo.ID(), ), isAuthFailure: true, } } // Reject self. - if mt.nodeInfo.ID == nodeInfo.ID { - return nil, NodeInfo{}, ErrRejected{ - addr: *NewNetAddress(nodeInfo.ID, c.RemoteAddr()), + if mt.nodeInfo.ID() == nodeInfo.ID() { + return nil, nil, ErrRejected{ + addr: *NewNetAddress(nodeInfo.ID(), c.RemoteAddr()), conn: c, - id: nodeInfo.ID, + id: nodeInfo.ID(), isSelf: true, } } if err := mt.nodeInfo.CompatibleWith(nodeInfo); err != nil { - return nil, NodeInfo{}, ErrRejected{ + return nil, nil, ErrRejected{ conn: c, err: err, - id: nodeInfo.ID, + id: nodeInfo.ID(), isIncompatible: true, } } @@ -430,17 +430,18 @@ func handshake( nodeInfo NodeInfo, ) (NodeInfo, error) { if err := c.SetDeadline(time.Now().Add(timeout)); err != nil { - return NodeInfo{}, err + return nil, err } var ( errc = make(chan error, 2) - peerNodeInfo NodeInfo + peerNodeInfo DefaultNodeInfo + ourNodeInfo = nodeInfo.(DefaultNodeInfo) ) go func(errc chan<- error, c net.Conn) { - _, err := cdc.MarshalBinaryWriter(c, nodeInfo) + _, err := cdc.MarshalBinaryWriter(c, ourNodeInfo) errc <- err }(errc, c) go func(errc chan<- error, c net.Conn) { @@ -455,7 +456,7 @@ func handshake( for i := 0; i < cap(errc); i++ { err := <-errc if err != nil { - return NodeInfo{}, err + return nil, err } } diff --git a/p2p/transport_test.go b/p2p/transport_test.go index 9e3cc467..cce223a3 100644 --- a/p2p/transport_test.go +++ b/p2p/transport_test.go @@ -11,9 +11,15 @@ import ( "github.com/tendermint/tendermint/crypto/ed25519" ) +var defaultNodeName = "host_peer" + +func emptyNodeInfo() NodeInfo { + return DefaultNodeInfo{} +} + func TestTransportMultiplexConnFilter(t *testing.T) { mt := NewMultiplexTransport( - NodeInfo{}, + emptyNodeInfo(), NodeKey{ PrivKey: ed25519.GenPrivKey(), }, @@ -70,7 +76,7 @@ func TestTransportMultiplexConnFilter(t *testing.T) { func TestTransportMultiplexConnFilterTimeout(t *testing.T) { mt := NewMultiplexTransport( - NodeInfo{}, + emptyNodeInfo(), NodeKey{ PrivKey: ed25519.GenPrivKey(), }, @@ -120,6 +126,7 @@ func TestTransportMultiplexConnFilterTimeout(t *testing.T) { t.Errorf("expected ErrFilterTimeout") } } + func TestTransportMultiplexAcceptMultiple(t *testing.T) { mt := testSetupMultiplexTransport(t) @@ -134,12 +141,7 @@ func TestTransportMultiplexAcceptMultiple(t *testing.T) { var ( pv = ed25519.GenPrivKey() dialer = NewMultiplexTransport( - NodeInfo{ - ID: PubKeyToID(pv.PubKey()), - ListenAddr: "127.0.0.1:0", - Moniker: "dialer", - Version: "1.0.0", - }, + testNodeInfo(PubKeyToID(pv.PubKey()), defaultNodeName), NodeKey{ PrivKey: pv, }, @@ -207,15 +209,10 @@ func TestTransportMultiplexAcceptNonBlocking(t *testing.T) { var ( fastNodePV = ed25519.GenPrivKey() - fastNodeInfo = NodeInfo{ - ID: PubKeyToID(fastNodePV.PubKey()), - ListenAddr: "127.0.0.1:0", - Moniker: "fastNode", - Version: "1.0.0", - } - errc = make(chan error) - fastc = make(chan struct{}) - slowc = make(chan struct{}) + fastNodeInfo = testNodeInfo(PubKeyToID(fastNodePV.PubKey()), "fastnode") + errc = make(chan error) + fastc = make(chan struct{}) + slowc = make(chan struct{}) ) // Simulate slow Peer. @@ -248,11 +245,11 @@ func TestTransportMultiplexAcceptNonBlocking(t *testing.T) { return } - _, err = handshake(sc, 20*time.Millisecond, NodeInfo{ - ID: PubKeyToID(ed25519.GenPrivKey().PubKey()), - ListenAddr: "127.0.0.1:0", - Moniker: "slow_peer", - }) + _, err = handshake(sc, 20*time.Millisecond, + testNodeInfo( + PubKeyToID(ed25519.GenPrivKey().PubKey()), + "slow_peer", + )) if err != nil { errc <- err return @@ -311,12 +308,7 @@ func TestTransportMultiplexValidateNodeInfo(t *testing.T) { var ( pv = ed25519.GenPrivKey() dialer = NewMultiplexTransport( - NodeInfo{ - ID: PubKeyToID(pv.PubKey()), - ListenAddr: "127.0.0.1:0", - Moniker: "", // Should not be empty. - Version: "1.0.0", - }, + testNodeInfo(PubKeyToID(pv.PubKey()), ""), // Should not be empty NodeKey{ PrivKey: pv, }, @@ -359,12 +351,9 @@ func TestTransportMultiplexRejectMissmatchID(t *testing.T) { go func() { dialer := NewMultiplexTransport( - NodeInfo{ - ID: PubKeyToID(ed25519.GenPrivKey().PubKey()), - ListenAddr: "127.0.0.1:0", - Moniker: "dialer", - Version: "1.0.0", - }, + testNodeInfo( + PubKeyToID(ed25519.GenPrivKey().PubKey()), "dialer", + ), NodeKey{ PrivKey: ed25519.GenPrivKey(), }, @@ -408,12 +397,7 @@ func TestTransportMultiplexRejectIncompatible(t *testing.T) { var ( pv = ed25519.GenPrivKey() dialer = NewMultiplexTransport( - NodeInfo{ - ID: PubKeyToID(pv.PubKey()), - ListenAddr: "127.0.0.1:0", - Moniker: "dialer", - Version: "2.0.0", - }, + testNodeInfoWithNetwork(PubKeyToID(pv.PubKey()), "dialer", "incompatible-network"), NodeKey{ PrivKey: pv, }, @@ -521,9 +505,7 @@ func TestTransportHandshake(t *testing.T) { var ( peerPV = ed25519.GenPrivKey() - peerNodeInfo = NodeInfo{ - ID: PubKeyToID(peerPV.PubKey()), - } + peerNodeInfo = testNodeInfo(PubKeyToID(peerPV.PubKey()), defaultNodeName) ) go func() { @@ -534,13 +516,13 @@ func TestTransportHandshake(t *testing.T) { } go func(c net.Conn) { - _, err := cdc.MarshalBinaryWriter(c, peerNodeInfo) + _, err := cdc.MarshalBinaryWriter(c, peerNodeInfo.(DefaultNodeInfo)) if err != nil { t.Error(err) } }(c) go func(c net.Conn) { - ni := NodeInfo{} + var ni DefaultNodeInfo _, err := cdc.UnmarshalBinaryReader( c, @@ -558,7 +540,7 @@ func TestTransportHandshake(t *testing.T) { t.Fatal(err) } - ni, err := handshake(c, 20*time.Millisecond, NodeInfo{}) + ni, err := handshake(c, 20*time.Millisecond, emptyNodeInfo()) if err != nil { t.Fatal(err) } @@ -572,12 +554,9 @@ func testSetupMultiplexTransport(t *testing.T) *MultiplexTransport { var ( pv = ed25519.GenPrivKey() mt = NewMultiplexTransport( - NodeInfo{ - ID: PubKeyToID(pv.PubKey()), - ListenAddr: "127.0.0.1:0", - Moniker: "transport", - Version: "1.0.0", - }, + testNodeInfo( + PubKeyToID(pv.PubKey()), "transport", + ), NodeKey{ PrivKey: pv, }, diff --git a/rpc/core/consensus.go b/rpc/core/consensus.go index 1d5f9275..1c2619d5 100644 --- a/rpc/core/consensus.go +++ b/rpc/core/consensus.go @@ -2,7 +2,6 @@ package core import ( cm "github.com/tendermint/tendermint/consensus" - "github.com/tendermint/tendermint/p2p" ctypes "github.com/tendermint/tendermint/rpc/core/types" sm "github.com/tendermint/tendermint/state" "github.com/tendermint/tendermint/types" @@ -201,7 +200,7 @@ func DumpConsensusState() (*ctypes.ResultDumpConsensusState, error) { } peerStates[i] = ctypes.PeerStateInfo{ // Peer basic info. - NodeAddress: p2p.IDAddressString(peer.ID(), peer.NodeInfo().ListenAddr), + NodeAddress: peer.NodeInfo().NetAddress().String(), // Peer consensus state. PeerState: peerStateJSON, } diff --git a/rpc/core/net.go b/rpc/core/net.go index 9816d2f6..dbd4d8c0 100644 --- a/rpc/core/net.go +++ b/rpc/core/net.go @@ -1,8 +1,11 @@ package core import ( + "fmt" + "github.com/pkg/errors" + "github.com/tendermint/tendermint/p2p" ctypes "github.com/tendermint/tendermint/rpc/core/types" ) @@ -37,8 +40,12 @@ import ( func NetInfo() (*ctypes.ResultNetInfo, error) { peers := []ctypes.Peer{} for _, peer := range p2pPeers.Peers().List() { + nodeInfo, ok := peer.NodeInfo().(p2p.DefaultNodeInfo) + if !ok { + return nil, fmt.Errorf("peer.NodeInfo() is not DefaultNodeInfo") + } peers = append(peers, ctypes.Peer{ - NodeInfo: peer.NodeInfo(), + NodeInfo: nodeInfo, IsOutbound: peer.IsOutbound(), ConnectionStatus: peer.Status(), }) diff --git a/rpc/core/status.go b/rpc/core/status.go index 17fb2f34..c26b06b8 100644 --- a/rpc/core/status.go +++ b/rpc/core/status.go @@ -5,6 +5,7 @@ import ( "time" cmn "github.com/tendermint/tendermint/libs/common" + "github.com/tendermint/tendermint/p2p" ctypes "github.com/tendermint/tendermint/rpc/core/types" sm "github.com/tendermint/tendermint/state" "github.com/tendermint/tendermint/types" @@ -91,7 +92,7 @@ func Status() (*ctypes.ResultStatus, error) { } result := &ctypes.ResultStatus{ - NodeInfo: p2pTransport.NodeInfo(), + NodeInfo: p2pTransport.NodeInfo().(p2p.DefaultNodeInfo), SyncInfo: ctypes.SyncInfo{ LatestBlockHash: latestBlockHash, LatestAppHash: latestAppHash, diff --git a/rpc/core/types/responses.go b/rpc/core/types/responses.go index a6dcf2b9..07628d1c 100644 --- a/rpc/core/types/responses.go +++ b/rpc/core/types/responses.go @@ -74,9 +74,9 @@ type ValidatorInfo struct { // Node Status type ResultStatus struct { - NodeInfo p2p.NodeInfo `json:"node_info"` - SyncInfo SyncInfo `json:"sync_info"` - ValidatorInfo ValidatorInfo `json:"validator_info"` + NodeInfo p2p.DefaultNodeInfo `json:"node_info"` + SyncInfo SyncInfo `json:"sync_info"` + ValidatorInfo ValidatorInfo `json:"validator_info"` } // Is TxIndexing enabled @@ -107,7 +107,7 @@ type ResultDialPeers struct { // A peer type Peer struct { - p2p.NodeInfo `json:"node_info"` + NodeInfo p2p.DefaultNodeInfo `json:"node_info"` IsOutbound bool `json:"is_outbound"` ConnectionStatus p2p.ConnectionStatus `json:"connection_status"` } diff --git a/rpc/core/types/responses_test.go b/rpc/core/types/responses_test.go index c6c86e1f..796299d3 100644 --- a/rpc/core/types/responses_test.go +++ b/rpc/core/types/responses_test.go @@ -15,17 +15,17 @@ func TestStatusIndexer(t *testing.T) { status = &ResultStatus{} assert.False(t, status.TxIndexEnabled()) - status.NodeInfo = p2p.NodeInfo{} + status.NodeInfo = p2p.DefaultNodeInfo{} assert.False(t, status.TxIndexEnabled()) cases := []struct { expected bool - other p2p.NodeInfoOther + other p2p.DefaultNodeInfoOther }{ - {false, p2p.NodeInfoOther{}}, - {false, p2p.NodeInfoOther{TxIndex: "aa"}}, - {false, p2p.NodeInfoOther{TxIndex: "off"}}, - {true, p2p.NodeInfoOther{TxIndex: "on"}}, + {false, p2p.DefaultNodeInfoOther{}}, + {false, p2p.DefaultNodeInfoOther{TxIndex: "aa"}}, + {false, p2p.DefaultNodeInfoOther{TxIndex: "off"}}, + {true, p2p.DefaultNodeInfoOther{TxIndex: "on"}}, } for _, tc := range cases { From 0790223518b2f4c75f98244c59396f25c40100ab Mon Sep 17 00:00:00 2001 From: Dev Ojha Date: Sat, 13 Oct 2018 17:01:21 -0700 Subject: [PATCH 008/267] Comment about ed25519 private key format on Sign (#2632) Closes #2001 --- crypto/ed25519/ed25519.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/crypto/ed25519/ed25519.go b/crypto/ed25519/ed25519.go index c2bed6ab..61872d98 100644 --- a/crypto/ed25519/ed25519.go +++ b/crypto/ed25519/ed25519.go @@ -46,6 +46,12 @@ func (privKey PrivKeyEd25519) Bytes() []byte { } // Sign produces a signature on the provided message. +// This assumes the privkey is wellformed in the golang format. +// The first 32 bytes should be random, +// corresponding to the normal ed25519 private key. +// The latter 32 bytes should be the compressed public key. +// If these conditions aren't met, Sign will panic or produce an +// incorrect signature. func (privKey PrivKeyEd25519) Sign(msg []byte) ([]byte, error) { signatureBytes := ed25519.Sign(privKey[:], msg) return signatureBytes[:], nil From 37928cb9907a60ae79d5b387d1d89ffc43dc07d8 Mon Sep 17 00:00:00 2001 From: Anton Kaliaev Date: Mon, 15 Oct 2018 06:28:41 +0400 Subject: [PATCH 009/267] set next validators along with validators while replay (#2637) Closes #2634 --- consensus/replay.go | 1 + 1 file changed, 1 insertion(+) diff --git a/consensus/replay.go b/consensus/replay.go index c92654f2..af6369c3 100644 --- a/consensus/replay.go +++ b/consensus/replay.go @@ -287,6 +287,7 @@ func (h *Handshaker) ReplayBlocks(state sm.State, appHash []byte, appBlockHeight return nil, err } state.Validators = types.NewValidatorSet(vals) + state.NextValidators = types.NewValidatorSet(vals) } if res.ConsensusParams != nil { state.ConsensusParams = types.PB2TM.ConsensusParams(res.ConsensusParams) From 287b25a0592ad572e5e184d1f3e995b68454699d Mon Sep 17 00:00:00 2001 From: Zarko Milosevic Date: Mon, 15 Oct 2018 22:05:13 +0200 Subject: [PATCH 010/267] Align with spec (#2642) --- consensus/common_test.go | 8 ++ consensus/reactor.go | 6 +- consensus/state.go | 18 ++-- consensus/state_test.go | 198 ++++++++++++++++++++++++++++----------- p2p/metrics.go | 2 +- types/evidence_test.go | 2 +- 6 files changed, 162 insertions(+), 72 deletions(-) diff --git a/consensus/common_test.go b/consensus/common_test.go index ddce6914..ca14a292 100644 --- a/consensus/common_test.go +++ b/consensus/common_test.go @@ -487,6 +487,14 @@ func ensureVote(voteCh <-chan interface{}, height int64, round int, } } +func ensurePrecommit(voteCh <-chan interface{}, height int64, round int) { + ensureVote(voteCh, height, round, types.PrecommitType) +} + +func ensurePrevote(voteCh <-chan interface{}, height int64, round int) { + ensureVote(voteCh, height, round, types.PrevoteType) +} + func ensureNewEventOnChannel(ch <-chan interface{}) { select { case <-time.After(ensureTimeout): diff --git a/consensus/reactor.go b/consensus/reactor.go index 6643273c..bcf77fb3 100644 --- a/consensus/reactor.go +++ b/consensus/reactor.go @@ -429,9 +429,9 @@ func (conR *ConsensusReactor) broadcastHasVoteMessage(vote *types.Vote) { func makeRoundStepMessages(rs *cstypes.RoundState) (nrsMsg *NewRoundStepMessage, csMsg *CommitStepMessage) { nrsMsg = &NewRoundStepMessage{ - Height: rs.Height, - Round: rs.Round, - Step: rs.Step, + Height: rs.Height, + Round: rs.Round, + Step: rs.Step, SecondsSinceStartTime: int(time.Since(rs.StartTime).Seconds()), LastCommitRound: rs.LastCommit.Round(), } diff --git a/consensus/state.go b/consensus/state.go index 37047aa3..37567400 100644 --- a/consensus/state.go +++ b/consensus/state.go @@ -532,10 +532,10 @@ func (cs *ConsensusState) updateToState(state sm.State) { cs.Proposal = nil cs.ProposalBlock = nil cs.ProposalBlockParts = nil - cs.LockedRound = 0 + cs.LockedRound = -1 cs.LockedBlock = nil cs.LockedBlockParts = nil - cs.ValidRound = 0 + cs.ValidRound = -1 cs.ValidBlock = nil cs.ValidBlockParts = nil cs.Votes = cstypes.NewHeightVoteSet(state.ChainID, height, validators) @@ -889,10 +889,7 @@ func (cs *ConsensusState) defaultDecideProposal(height int64, round int) { var blockParts *types.PartSet // Decide on block - if cs.LockedBlock != nil { - // If we're locked onto a block, just choose that. - block, blockParts = cs.LockedBlock, cs.LockedBlockParts - } else if cs.ValidBlock != nil { + if cs.ValidBlock != nil { // If there is valid block, choose that. block, blockParts = cs.ValidBlock, cs.ValidBlockParts } else { @@ -983,7 +980,6 @@ func (cs *ConsensusState) createProposalBlock() (block *types.Block, blockParts // Enter: `timeoutPropose` after entering Propose. // Enter: proposal block and POL is ready. -// Enter: any +2/3 prevotes for future round. // Prevote for LockedBlock if we're locked, or ProposalBlock if valid. // Otherwise vote nil. func (cs *ConsensusState) enterPrevote(height int64, round int) { @@ -1072,8 +1068,8 @@ func (cs *ConsensusState) enterPrevoteWait(height int64, round int) { } // Enter: `timeoutPrevote` after any +2/3 prevotes. +// Enter: `timeoutPrecommit` after any +2/3 precommits. // Enter: +2/3 precomits for block or nil. -// Enter: any +2/3 precommits for next round. // Lock & precommit the ProposalBlock if we have enough prevotes for it (a POL in this round) // else, unlock an existing lock and precommit nil if +2/3 of prevotes were nil, // else, precommit nil otherwise. @@ -1122,7 +1118,7 @@ func (cs *ConsensusState) enterPrecommit(height int64, round int) { logger.Info("enterPrecommit: +2/3 prevoted for nil.") } else { logger.Info("enterPrecommit: +2/3 prevoted for nil. Unlocking") - cs.LockedRound = 0 + cs.LockedRound = -1 cs.LockedBlock = nil cs.LockedBlockParts = nil cs.eventBus.PublishEventUnlock(cs.RoundStateEvent()) @@ -1161,7 +1157,7 @@ func (cs *ConsensusState) enterPrecommit(height int64, round int) { // Fetch that block, unlock, and precommit nil. // The +2/3 prevotes for this round is the POL for our unlock. // TODO: In the future save the POL prevotes for justification. - cs.LockedRound = 0 + cs.LockedRound = -1 cs.LockedBlock = nil cs.LockedBlockParts = nil if !cs.ProposalBlockParts.HasHeader(blockID.PartsHeader) { @@ -1612,7 +1608,7 @@ func (cs *ConsensusState) addVote(vote *types.Vote, peerID p2p.ID) (added bool, !cs.LockedBlock.HashesTo(blockID.Hash) { cs.Logger.Info("Unlocking because of POL.", "lockedRound", cs.LockedRound, "POLRound", vote.Round) - cs.LockedRound = 0 + cs.LockedRound = -1 cs.LockedBlock = nil cs.LockedBlockParts = nil cs.eventBus.PublishEventUnlock(cs.RoundStateEvent()) diff --git a/consensus/state_test.go b/consensus/state_test.go index 229d7e7b..c4fc11c3 100644 --- a/consensus/state_test.go +++ b/consensus/state_test.go @@ -214,16 +214,16 @@ func TestStateBadProposal(t *testing.T) { ensureNewProposal(proposalCh, height, round) // wait for prevote - ensureVote(voteCh, height, round, types.PrevoteType) + ensurePrevote(voteCh, height, round) validatePrevote(t, cs1, round, vss[0], nil) // add bad prevote from vs2 and wait for it signAddVotes(cs1, types.PrevoteType, propBlock.Hash(), propBlock.MakePartSet(partSize).Header(), vs2) - ensureVote(voteCh, height, round, types.PrevoteType) + ensurePrevote(voteCh, height, round) // wait for precommit - ensureVote(voteCh, height, round, types.PrecommitType) - validatePrecommit(t, cs1, round, 0, vss[0], nil, nil) + ensurePrecommit(voteCh, height, round) + validatePrecommit(t, cs1, round, -1, vss[0], nil, nil) signAddVotes(cs1, types.PrecommitType, propBlock.Hash(), propBlock.MakePartSet(partSize).Header(), vs2) } @@ -255,10 +255,10 @@ func TestStateFullRound1(t *testing.T) { ensureNewProposal(propCh, height, round) propBlockHash := cs.GetRoundState().ProposalBlock.Hash() - ensureVote(voteCh, height, round, types.PrevoteType) // wait for prevote + ensurePrevote(voteCh, height, round) // wait for prevote validatePrevote(t, cs, round, vss[0], propBlockHash) - ensureVote(voteCh, height, round, types.PrecommitType) // wait for precommit + ensurePrecommit(voteCh, height, round) // wait for precommit // we're going to roll right into new height ensureNewRound(newRoundCh, height+1, 0) @@ -276,11 +276,11 @@ func TestStateFullRoundNil(t *testing.T) { cs.enterPrevote(height, round) cs.startRoutines(4) - ensureVote(voteCh, height, round, types.PrevoteType) // prevote - ensureVote(voteCh, height, round, types.PrecommitType) // precommit + ensurePrevote(voteCh, height, round) // prevote + ensurePrecommit(voteCh, height, round) // precommit // should prevote and precommit nil - validatePrevoteAndPrecommit(t, cs, round, 0, vss[0], nil, nil) + validatePrevoteAndPrecommit(t, cs, round, -1, vss[0], nil, nil) } // run through propose, prevote, precommit commit with two validators @@ -296,7 +296,7 @@ func TestStateFullRound2(t *testing.T) { // start round and wait for propose and prevote startTestRound(cs1, height, round) - ensureVote(voteCh, height, round, types.PrevoteType) // prevote + ensurePrevote(voteCh, height, round) // prevote // we should be stuck in limbo waiting for more prevotes rs := cs1.GetRoundState() @@ -304,9 +304,9 @@ func TestStateFullRound2(t *testing.T) { // prevote arrives from vs2: signAddVotes(cs1, types.PrevoteType, propBlockHash, propPartsHeader, vs2) - ensureVote(voteCh, height, round, types.PrevoteType) // prevote + ensurePrevote(voteCh, height, round) // prevote - ensureVote(voteCh, height, round, types.PrecommitType) //precommit + ensurePrecommit(voteCh, height, round) //precommit // the proposed block should now be locked and our precommit added validatePrecommit(t, cs1, 0, 0, vss[0], propBlockHash, propBlockHash) @@ -314,7 +314,7 @@ func TestStateFullRound2(t *testing.T) { // precommit arrives from vs2: signAddVotes(cs1, types.PrecommitType, propBlockHash, propPartsHeader, vs2) - ensureVote(voteCh, height, round, types.PrecommitType) + ensurePrecommit(voteCh, height, round) // wait to finish commit, propose in next height ensureNewBlock(newBlockCh, height) @@ -353,14 +353,14 @@ func TestStateLockNoPOL(t *testing.T) { theBlockHash := roundState.ProposalBlock.Hash() thePartSetHeader := roundState.ProposalBlockParts.Header() - ensureVote(voteCh, height, round, types.PrevoteType) // prevote + ensurePrevote(voteCh, height, round) // prevote // we should now be stuck in limbo forever, waiting for more prevotes // prevote arrives from vs2: signAddVotes(cs1, types.PrevoteType, theBlockHash, thePartSetHeader, vs2) - ensureVote(voteCh, height, round, types.PrevoteType) // prevote + ensurePrevote(voteCh, height, round) // prevote - ensureVote(voteCh, height, round, types.PrecommitType) // precommit + ensurePrecommit(voteCh, height, round) // precommit // the proposed block should now be locked and our precommit added validatePrecommit(t, cs1, round, round, vss[0], theBlockHash, theBlockHash) @@ -370,7 +370,7 @@ func TestStateLockNoPOL(t *testing.T) { copy(hash, theBlockHash) hash[0] = byte((hash[0] + 1) % 255) signAddVotes(cs1, types.PrecommitType, hash, thePartSetHeader, vs2) - ensureVote(voteCh, height, round, types.PrecommitType) // precommit + ensurePrecommit(voteCh, height, round) // precommit // (note we're entering precommit for a second time this round) // but with invalid args. then we enterPrecommitWait, and the timeout to new round @@ -397,26 +397,26 @@ func TestStateLockNoPOL(t *testing.T) { } // wait to finish prevote - ensureVote(voteCh, height, round, types.PrevoteType) + ensurePrevote(voteCh, height, round) // we should have prevoted our locked block validatePrevote(t, cs1, round, vss[0], rs.LockedBlock.Hash()) // add a conflicting prevote from the other validator signAddVotes(cs1, types.PrevoteType, hash, rs.LockedBlock.MakePartSet(partSize).Header(), vs2) - ensureVote(voteCh, height, round, types.PrevoteType) + ensurePrevote(voteCh, height, round) // now we're going to enter prevote again, but with invalid args // and then prevote wait, which should timeout. then wait for precommit ensureNewTimeout(timeoutWaitCh, height, round, cs1.config.TimeoutPrevote.Nanoseconds()) - ensureVote(voteCh, height, round, types.PrecommitType) // precommit + ensurePrecommit(voteCh, height, round) // precommit // the proposed block should still be locked and our precommit added // we should precommit nil and be locked on the proposal validatePrecommit(t, cs1, round, 0, vss[0], nil, theBlockHash) // add conflicting precommit from vs2 signAddVotes(cs1, types.PrecommitType, hash, rs.LockedBlock.MakePartSet(partSize).Header(), vs2) - ensureVote(voteCh, height, round, types.PrecommitType) + ensurePrecommit(voteCh, height, round) // (note we're entering precommit for a second time this round, but with invalid args // then we enterPrecommitWait and timeout into NewRound @@ -439,19 +439,19 @@ func TestStateLockNoPOL(t *testing.T) { panic(fmt.Sprintf("Expected proposal block to be locked block. Got %v, Expected %v", rs.ProposalBlock, rs.LockedBlock)) } - ensureVote(voteCh, height, round, types.PrevoteType) // prevote + ensurePrevote(voteCh, height, round) // prevote validatePrevote(t, cs1, round, vss[0], rs.LockedBlock.Hash()) signAddVotes(cs1, types.PrevoteType, hash, rs.ProposalBlock.MakePartSet(partSize).Header(), vs2) - ensureVote(voteCh, height, round, types.PrevoteType) + ensurePrevote(voteCh, height, round) ensureNewTimeout(timeoutWaitCh, height, round, cs1.config.TimeoutPrevote.Nanoseconds()) - ensureVote(voteCh, height, round, types.PrecommitType) // precommit + ensurePrecommit(voteCh, height, round) // precommit validatePrecommit(t, cs1, round, 0, vss[0], nil, theBlockHash) // precommit nil but be locked on proposal signAddVotes(cs1, types.PrecommitType, hash, rs.ProposalBlock.MakePartSet(partSize).Header(), vs2) // NOTE: conflicting precommits at same height - ensureVote(voteCh, height, round, types.PrecommitType) + ensurePrecommit(voteCh, height, round) ensureNewTimeout(timeoutWaitCh, height, round, cs1.config.TimeoutPrecommit.Nanoseconds()) @@ -478,20 +478,20 @@ func TestStateLockNoPOL(t *testing.T) { } ensureNewProposal(proposalCh, height, round) - ensureVote(voteCh, height, round, types.PrevoteType) // prevote + ensurePrevote(voteCh, height, round) // prevote // prevote for locked block (not proposal) validatePrevote(t, cs1, 3, vss[0], cs1.LockedBlock.Hash()) // prevote for proposed block signAddVotes(cs1, types.PrevoteType, propBlock.Hash(), propBlock.MakePartSet(partSize).Header(), vs2) - ensureVote(voteCh, height, round, types.PrevoteType) + ensurePrevote(voteCh, height, round) ensureNewTimeout(timeoutWaitCh, height, round, cs1.config.TimeoutPrevote.Nanoseconds()) - ensureVote(voteCh, height, round, types.PrecommitType) + ensurePrecommit(voteCh, height, round) validatePrecommit(t, cs1, round, 0, vss[0], nil, theBlockHash) // precommit nil but locked on proposal signAddVotes(cs1, types.PrecommitType, propBlock.Hash(), propBlock.MakePartSet(partSize).Header(), vs2) // NOTE: conflicting precommits at same height - ensureVote(voteCh, height, round, types.PrecommitType) + ensurePrecommit(voteCh, height, round) } // 4 vals, one precommits, other 3 polka at next round, so we unlock and precomit the polka @@ -525,11 +525,11 @@ func TestStateLockPOLRelock(t *testing.T) { theBlockHash := rs.ProposalBlock.Hash() theBlockParts := rs.ProposalBlockParts.Header() - ensureVote(voteCh, height, round, types.PrevoteType) // prevote + ensurePrevote(voteCh, height, round) // prevote signAddVotes(cs1, types.PrevoteType, theBlockHash, theBlockParts, vs2, vs3, vs4) - ensureVote(voteCh, height, round, types.PrecommitType) // our precommit + ensurePrecommit(voteCh, height, round) // our precommit // the proposed block should now be locked and our precommit added validatePrecommit(t, cs1, round, round, vss[0], theBlockHash, theBlockHash) @@ -567,13 +567,13 @@ func TestStateLockPOLRelock(t *testing.T) { ensureNewProposal(proposalCh, height, round) // go to prevote, prevote for locked block (not proposal), move on - ensureVote(voteCh, height, round, types.PrevoteType) + ensurePrevote(voteCh, height, round) validatePrevote(t, cs1, round, vss[0], theBlockHash) // now lets add prevotes from everyone else for the new block signAddVotes(cs1, types.PrevoteType, propBlockHash, propBlockParts.Header(), vs2, vs3, vs4) - ensureVote(voteCh, height, round, types.PrecommitType) + ensurePrecommit(voteCh, height, round) // we should have unlocked and locked on the new block validatePrecommit(t, cs1, round, round, vss[0], propBlockHash, propBlockHash) @@ -614,12 +614,12 @@ func TestStateLockPOLUnlock(t *testing.T) { theBlockHash := rs.ProposalBlock.Hash() theBlockParts := rs.ProposalBlockParts.Header() - ensureVote(voteCh, height, round, types.PrevoteType) + ensurePrevote(voteCh, height, round) validatePrevote(t, cs1, round, vss[0], theBlockHash) signAddVotes(cs1, types.PrevoteType, theBlockHash, theBlockParts, vs2, vs3, vs4) - ensureVote(voteCh, height, round, types.PrecommitType) + ensurePrecommit(voteCh, height, round) // the proposed block should now be locked and our precommit added validatePrecommit(t, cs1, round, round, vss[0], theBlockHash, theBlockHash) @@ -656,18 +656,18 @@ func TestStateLockPOLUnlock(t *testing.T) { ensureNewProposal(proposalCh, height, round) // go to prevote, prevote for locked block (not proposal) - ensureVote(voteCh, height, round, types.PrevoteType) + ensurePrevote(voteCh, height, round) validatePrevote(t, cs1, round, vss[0], lockedBlockHash) // now lets add prevotes from everyone else for nil (a polka!) signAddVotes(cs1, types.PrevoteType, nil, types.PartSetHeader{}, vs2, vs3, vs4) // the polka makes us unlock and precommit nil ensureNewUnlock(unlockCh, height, round) - ensureVote(voteCh, height, round, types.PrecommitType) + ensurePrecommit(voteCh, height, round) // we should have unlocked and committed nil - // NOTE: since we don't relock on nil, the lock round is 0 - validatePrecommit(t, cs1, round, 0, vss[0], nil, nil) + // NOTE: since we don't relock on nil, the lock round is -1 + validatePrecommit(t, cs1, round, -1, vss[0], nil, nil) signAddVotes(cs1, types.PrecommitType, nil, types.PartSetHeader{}, vs2, vs3) ensureNewRound(newRoundCh, height, round+1) @@ -698,7 +698,7 @@ func TestStateLockPOLSafety1(t *testing.T) { rs := cs1.GetRoundState() propBlock := rs.ProposalBlock - ensureVote(voteCh, height, round, types.PrevoteType) + ensurePrevote(voteCh, height, round) validatePrevote(t, cs1, round, vss[0], propBlock.Hash()) // the others sign a polka but we don't see it @@ -710,7 +710,7 @@ func TestStateLockPOLSafety1(t *testing.T) { signAddVotes(cs1, types.PrecommitType, nil, types.PartSetHeader{}, vs2, vs3, vs4) // cs1 precommit nil - ensureVote(voteCh, height, round, types.PrecommitType) + ensurePrecommit(voteCh, height, round) ensureNewTimeout(timeoutWaitCh, height, round, cs1.config.TimeoutPrecommit.Nanoseconds()) t.Log("### ONTO ROUND 1") @@ -743,13 +743,13 @@ func TestStateLockPOLSafety1(t *testing.T) { t.Logf("new prop hash %v", fmt.Sprintf("%X", propBlockHash)) // go to prevote, prevote for proposal block - ensureVote(voteCh, height, round, types.PrevoteType) + ensurePrevote(voteCh, height, round) validatePrevote(t, cs1, round, vss[0], propBlockHash) // now we see the others prevote for it, so we should lock on it signAddVotes(cs1, types.PrevoteType, propBlockHash, propBlockParts.Header(), vs2, vs3, vs4) - ensureVote(voteCh, height, round, types.PrecommitType) + ensurePrecommit(voteCh, height, round) // we should have precommitted validatePrecommit(t, cs1, round, round, vss[0], propBlockHash, propBlockHash) @@ -771,7 +771,7 @@ func TestStateLockPOLSafety1(t *testing.T) { ensureNewTimeout(timeoutProposeCh, height, round, cs1.config.TimeoutPropose.Nanoseconds()) // finish prevote - ensureVote(voteCh, height, round, types.PrevoteType) + ensurePrevote(voteCh, height, round) // we should prevote what we're locked on validatePrevote(t, cs1, round, vss[0], propBlockHash) @@ -834,12 +834,12 @@ func TestStateLockPOLSafety2(t *testing.T) { } ensureNewProposal(proposalCh, height, round) - ensureVote(voteCh, height, round, types.PrevoteType) + ensurePrevote(voteCh, height, round) validatePrevote(t, cs1, round, vss[0], propBlockHash1) signAddVotes(cs1, types.PrevoteType, propBlockHash1, propBlockParts1.Header(), vs2, vs3, vs4) - ensureVote(voteCh, height, round, types.PrecommitType) + ensurePrecommit(voteCh, height, round) // the proposed block should now be locked and our precommit added validatePrecommit(t, cs1, round, round, vss[0], propBlockHash1, propBlockHash1) @@ -873,11 +873,97 @@ func TestStateLockPOLSafety2(t *testing.T) { ensureNewProposal(proposalCh, height, round) ensureNoNewUnlock(unlockCh) - ensureVote(voteCh, height, round, types.PrevoteType) + ensurePrevote(voteCh, height, round) validatePrevote(t, cs1, round, vss[0], propBlockHash1) } +// 4 vals. +// polka P0 at R0 for B0. We lock B0 on P0 at R0. P0 unlocks value at R1. + +// What we want: +// P0 proposes B0 at R3. +func TestProposeValidBlock(t *testing.T) { + cs1, vss := randConsensusState(4) + vs2, vs3, vs4 := vss[1], vss[2], vss[3] + height, round := cs1.Height, cs1.Round + + partSize := types.BlockPartSizeBytes + + proposalCh := subscribe(cs1.eventBus, types.EventQueryCompleteProposal) + timeoutWaitCh := subscribe(cs1.eventBus, types.EventQueryTimeoutWait) + timeoutProposeCh := subscribe(cs1.eventBus, types.EventQueryTimeoutPropose) + newRoundCh := subscribe(cs1.eventBus, types.EventQueryNewRound) + unlockCh := subscribe(cs1.eventBus, types.EventQueryUnlock) + voteCh := subscribeToVoter(cs1, cs1.privValidator.GetAddress()) + + // start round and wait for propose and prevote + startTestRound(cs1, cs1.Height, round) + ensureNewRound(newRoundCh, height, round) + + ensureNewProposal(proposalCh, height, round) + rs := cs1.GetRoundState() + propBlock := rs.ProposalBlock + propBlockHash := propBlock.Hash() + + ensurePrevote(voteCh, height, round) + validatePrevote(t, cs1, round, vss[0], propBlockHash) + + // the others sign a polka but we don't see it + signAddVotes(cs1, types.PrevoteType, propBlockHash, propBlock.MakePartSet(partSize).Header(), vs2, vs3, vs4) + + ensurePrecommit(voteCh, height, round) + // we should have precommitted + validatePrecommit(t, cs1, round, round, vss[0], propBlockHash, propBlockHash) + + signAddVotes(cs1, types.PrecommitType, nil, types.PartSetHeader{}, vs2, vs3, vs4) + + ensureNewTimeout(timeoutWaitCh, height, round, cs1.config.TimeoutPrecommit.Nanoseconds()) + + incrementRound(vs2, vs3, vs4) + round = round + 1 // moving to the next round + + ensureNewRound(newRoundCh, height, round) + + t.Log("### ONTO ROUND 2") + + // timeout of propose + ensureNewTimeout(timeoutProposeCh, height, round, cs1.config.TimeoutPropose.Nanoseconds()) + + ensurePrevote(voteCh, height, round) + validatePrevote(t, cs1, round, vss[0], propBlockHash) + + signAddVotes(cs1, types.PrevoteType, nil, types.PartSetHeader{}, vs2, vs3, vs4) + + ensureNewUnlock(unlockCh, height, round) + + ensurePrecommit(voteCh, height, round) + // we should have precommitted + validatePrecommit(t, cs1, round, -1, vss[0], nil, nil) + + incrementRound(vs2, vs3, vs4) + incrementRound(vs2, vs3, vs4) + + signAddVotes(cs1, types.PrecommitType, nil, types.PartSetHeader{}, vs2, vs3, vs4) + + round = round + 2 // moving to the next round + + ensureNewRound(newRoundCh, height, round) + ensureNewTimeout(timeoutWaitCh, height, round, cs1.config.TimeoutPrecommit.Nanoseconds()) + + round = round + 1 // moving to the next round + + ensureNewRound(newRoundCh, height, round) + + t.Log("### ONTO ROUND 4") + + ensureNewProposal(proposalCh, height, round) + + rs = cs1.GetRoundState() + assert.True(t, bytes.Equal(rs.ProposalBlock.Hash(), propBlockHash)) + assert.True(t, bytes.Equal(rs.ProposalBlock.Hash(), rs.ValidBlock.Hash())) +} + // 4 vals, 3 Nil Precommits at P0 // What we want: // P0 waits for timeoutPrecommit before starting next round @@ -915,7 +1001,7 @@ func TestWaitingTimeoutProposeOnNewRound(t *testing.T) { startTestRound(cs1, height, round) ensureNewRound(newRoundCh, height, round) - ensureVote(voteCh, height, round, types.PrevoteType) + ensurePrevote(voteCh, height, round) incrementRound(vss[1:]...) signAddVotes(cs1, types.PrevoteType, nil, types.PartSetHeader{}, vs2, vs3, vs4) @@ -928,7 +1014,7 @@ func TestWaitingTimeoutProposeOnNewRound(t *testing.T) { ensureNewTimeout(timeoutWaitCh, height, round, cs1.config.TimeoutPropose.Nanoseconds()) - ensureVote(voteCh, height, round, types.PrevoteType) + ensurePrevote(voteCh, height, round) validatePrevote(t, cs1, round, vss[0], nil) } @@ -948,7 +1034,7 @@ func TestRoundSkipOnNilPolkaFromHigherRound(t *testing.T) { startTestRound(cs1, height, round) ensureNewRound(newRoundCh, height, round) - ensureVote(voteCh, height, round, types.PrevoteType) + ensurePrevote(voteCh, height, round) incrementRound(vss[1:]...) signAddVotes(cs1, types.PrecommitType, nil, types.PartSetHeader{}, vs2, vs3, vs4) @@ -956,8 +1042,8 @@ func TestRoundSkipOnNilPolkaFromHigherRound(t *testing.T) { round = round + 1 // moving to the next round ensureNewRound(newRoundCh, height, round) - ensureVote(voteCh, height, round, types.PrecommitType) - validatePrecommit(t, cs1, round, 0, vss[0], nil, nil) + ensurePrecommit(voteCh, height, round) + validatePrecommit(t, cs1, round, -1, vss[0], nil, nil) ensureNewTimeout(timeoutWaitCh, height, round, cs1.config.TimeoutPrecommit.Nanoseconds()) @@ -986,7 +1072,7 @@ func TestWaitTimeoutProposeOnNilPolkaForTheCurrentRound(t *testing.T) { ensureNewTimeout(timeoutProposeCh, height, round, cs1.config.TimeoutPropose.Nanoseconds()) - ensureVote(voteCh, height, round, types.PrevoteType) + ensurePrevote(voteCh, height, round) validatePrevote(t, cs1, round, vss[0], nil) } @@ -1096,11 +1182,11 @@ func TestStateHalt1(t *testing.T) { propBlock := rs.ProposalBlock propBlockParts := propBlock.MakePartSet(partSize) - ensureVote(voteCh, height, round, types.PrevoteType) + ensurePrevote(voteCh, height, round) signAddVotes(cs1, types.PrevoteType, propBlock.Hash(), propBlockParts.Header(), vs2, vs3, vs4) - ensureVote(voteCh, height, round, types.PrecommitType) + ensurePrecommit(voteCh, height, round) // the proposed block should now be locked and our precommit added validatePrecommit(t, cs1, round, round, vss[0], propBlock.Hash(), propBlock.Hash()) @@ -1127,7 +1213,7 @@ func TestStateHalt1(t *testing.T) { */ // go to prevote, prevote for locked block - ensureVote(voteCh, height, round, types.PrevoteType) + ensurePrevote(voteCh, height, round) validatePrevote(t, cs1, round, vss[0], rs.LockedBlock.Hash()) // now we receive the precommit from the previous round diff --git a/p2p/metrics.go b/p2p/metrics.go index b066fb31..ed26d119 100644 --- a/p2p/metrics.go +++ b/p2p/metrics.go @@ -62,7 +62,7 @@ func PrometheusMetrics(namespace string) *Metrics { // NopMetrics returns no-op Metrics. func NopMetrics() *Metrics { return &Metrics{ - Peers: discard.NewGauge(), + Peers: discard.NewGauge(), PeerReceiveBytesTotal: discard.NewCounter(), PeerSendBytesTotal: discard.NewCounter(), PeerPendingSendBytes: discard.NewGauge(), diff --git a/types/evidence_test.go b/types/evidence_test.go index 79805691..a8d7efff 100644 --- a/types/evidence_test.go +++ b/types/evidence_test.go @@ -61,7 +61,7 @@ func TestEvidence(t *testing.T) { {vote1, makeVote(val, chainID, 0, 10, 3, 1, blockID2), false}, // wrong round {vote1, makeVote(val, chainID, 0, 10, 2, 2, blockID2), false}, // wrong step {vote1, makeVote(val2, chainID, 0, 10, 2, 1, blockID), false}, // wrong validator - {vote1, badVote, false}, // signed by wrong key + {vote1, badVote, false}, // signed by wrong key } pubKey := val.GetPubKey() From 26462025bc327b238d9972ce9ff0fc07175a1ab4 Mon Sep 17 00:00:00 2001 From: Joon Date: Tue, 16 Oct 2018 05:28:49 +0900 Subject: [PATCH 011/267] standardize header.Hash() (#2610) --- types/block.go | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/types/block.go b/types/block.go index fe3b1725..f41f4c1f 100644 --- a/types/block.go +++ b/types/block.go @@ -297,21 +297,21 @@ func (h *Header) Hash() cmn.HexBytes { return nil } return merkle.SimpleHashFromMap(map[string][]byte{ - "ChainID": cdcEncode(h.ChainID), - "Height": cdcEncode(h.Height), - "Time": cdcEncode(h.Time), - "NumTxs": cdcEncode(h.NumTxs), - "TotalTxs": cdcEncode(h.TotalTxs), - "LastBlockID": cdcEncode(h.LastBlockID), - "LastCommit": cdcEncode(h.LastCommitHash), - "Data": cdcEncode(h.DataHash), - "Validators": cdcEncode(h.ValidatorsHash), - "NextValidators": cdcEncode(h.NextValidatorsHash), - "App": cdcEncode(h.AppHash), - "Consensus": cdcEncode(h.ConsensusHash), - "Results": cdcEncode(h.LastResultsHash), - "Evidence": cdcEncode(h.EvidenceHash), - "Proposer": cdcEncode(h.ProposerAddress), + "ChainID": cdcEncode(h.ChainID), + "Height": cdcEncode(h.Height), + "Time": cdcEncode(h.Time), + "NumTxs": cdcEncode(h.NumTxs), + "TotalTxs": cdcEncode(h.TotalTxs), + "LastBlockID": cdcEncode(h.LastBlockID), + "LastCommitHash": cdcEncode(h.LastCommitHash), + "DataHash": cdcEncode(h.DataHash), + "ValidatorsHash": cdcEncode(h.ValidatorsHash), + "NextValidatorsHash": cdcEncode(h.NextValidatorsHash), + "AppHash": cdcEncode(h.AppHash), + "ConsensusHash": cdcEncode(h.ConsensusHash), + "LastResultsHash": cdcEncode(h.LastResultsHash), + "EvidenceHash": cdcEncode(h.EvidenceHash), + "ProposerAddress": cdcEncode(h.ProposerAddress), }) } From 4ab7dcf3acdad757c96ec0553d6542aa3a93ee2f Mon Sep 17 00:00:00 2001 From: Joon Date: Tue, 16 Oct 2018 05:31:27 +0900 Subject: [PATCH 012/267] [R4R] Unmerklize ConsensusParams.Hash() (#2609) * Hash() uses tmhash instead of merkle.SimpleHashFromMap * marshal whole struct * update comments * update docs --- docs/spec/blockchain/blockchain.md | 4 ++-- types/params.go | 19 ++++++++++++------- types/params_test.go | 5 +++++ 3 files changed, 19 insertions(+), 9 deletions(-) diff --git a/docs/spec/blockchain/blockchain.md b/docs/spec/blockchain/blockchain.md index 89ab1b4f..029b64fa 100644 --- a/docs/spec/blockchain/blockchain.md +++ b/docs/spec/blockchain/blockchain.md @@ -320,10 +320,10 @@ next validator sets Merkle root. ### ConsensusParamsHash ```go -block.ConsensusParamsHash == SimpleMerkleRoot(state.ConsensusParams) +block.ConsensusParamsHash == tmhash(amino(state.ConsensusParams)) ``` -Simple Merkle root of the consensus parameters. +Hash of the amino-encoded consensus parameters. ### AppHash diff --git a/types/params.go b/types/params.go index 129d4762..ed1e7963 100644 --- a/types/params.go +++ b/types/params.go @@ -2,7 +2,7 @@ package types import ( abci "github.com/tendermint/tendermint/abci/types" - "github.com/tendermint/tendermint/crypto/merkle" + "github.com/tendermint/tendermint/crypto/tmhash" cmn "github.com/tendermint/tendermint/libs/common" ) @@ -80,13 +80,18 @@ func (params *ConsensusParams) Validate() error { return nil } -// Hash returns a merkle hash of the parameters to store in the block header +// Hash returns a hash of the parameters to store in the block header +// No Merkle tree here, only three values are hashed here +// thus benefit from saving space < drawbacks from proofs' overhead +// Revisit this function if new fields are added to ConsensusParams func (params *ConsensusParams) Hash() []byte { - return merkle.SimpleHashFromMap(map[string][]byte{ - "block_size_max_bytes": cdcEncode(params.BlockSize.MaxBytes), - "block_size_max_gas": cdcEncode(params.BlockSize.MaxGas), - "evidence_params_max_age": cdcEncode(params.EvidenceParams.MaxAge), - }) + hasher := tmhash.New() + bz := cdcEncode(params) + if bz == nil { + panic("cannot fail to encode ConsensusParams") + } + hasher.Write(bz) + return hasher.Sum(nil) } // Update returns a copy of the params with updates from the non-zero fields of p2. diff --git a/types/params_test.go b/types/params_test.go index 888b678b..2936e5a4 100644 --- a/types/params_test.go +++ b/types/params_test.go @@ -53,6 +53,11 @@ func TestConsensusParamsHash(t *testing.T) { makeParams(4, 2, 3), makeParams(1, 4, 3), makeParams(1, 2, 4), + makeParams(2, 5, 7), + makeParams(1, 7, 6), + makeParams(9, 5, 4), + makeParams(7, 8, 9), + makeParams(4, 6, 5), } hashes := make([][]byte, len(params)) From 124d0db1e092e1f7d6a3747cee29c370d998e61c Mon Sep 17 00:00:00 2001 From: Dev Ojha Date: Mon, 15 Oct 2018 15:42:47 -0500 Subject: [PATCH 013/267] Make txs and evidencelist use merkle.SimpleHashFromBytes to create hash (#2635) This is a performance regression, but will also spare the types directory from knowing about RFC 6962, which is a more correct abstraction. For txs this performance hit will be fixed soon with #2603. For evidence, the performance impact is negligible due to it being capped at a small number. --- CHANGELOG_PENDING.md | 1 + crypto/merkle/simple_proof.go | 6 +++--- crypto/merkle/simple_tree.go | 6 +++--- types/block.go | 2 -- types/evidence.go | 31 ++++++++++++++++++++----------- types/tx.go | 17 ++++++----------- 6 files changed, 33 insertions(+), 30 deletions(-) diff --git a/CHANGELOG_PENDING.md b/CHANGELOG_PENDING.md index f82ddbc2..05369ea6 100644 --- a/CHANGELOG_PENDING.md +++ b/CHANGELOG_PENDING.md @@ -24,6 +24,7 @@ BREAKING CHANGES: * [types] \#2298 Remove `Index` and `Total` fields from `TxProof`. * [crypto/merkle & lite] \#2298 Various changes to accomodate General Merkle trees * [crypto/merkle] \#2595 Remove all Hasher objects in favor of byte slices + * [crypto/merkle] \#2635 merkle.SimpleHashFromTwoHashes is no longer exported * [types] \#2598 `VoteTypeXxx` are now * Blockchain Protocol diff --git a/crypto/merkle/simple_proof.go b/crypto/merkle/simple_proof.go index d2cbb126..fd6d07b8 100644 --- a/crypto/merkle/simple_proof.go +++ b/crypto/merkle/simple_proof.go @@ -134,13 +134,13 @@ func computeHashFromAunts(index int, total int, leafHash []byte, innerHashes [][ if leftHash == nil { return nil } - return SimpleHashFromTwoHashes(leftHash, innerHashes[len(innerHashes)-1]) + return simpleHashFromTwoHashes(leftHash, innerHashes[len(innerHashes)-1]) } rightHash := computeHashFromAunts(index-numLeft, total-numLeft, leafHash, innerHashes[:len(innerHashes)-1]) if rightHash == nil { return nil } - return SimpleHashFromTwoHashes(innerHashes[len(innerHashes)-1], rightHash) + return simpleHashFromTwoHashes(innerHashes[len(innerHashes)-1], rightHash) } } @@ -187,7 +187,7 @@ func trailsFromByteSlices(items [][]byte) (trails []*SimpleProofNode, root *Simp default: lefts, leftRoot := trailsFromByteSlices(items[:(len(items)+1)/2]) rights, rightRoot := trailsFromByteSlices(items[(len(items)+1)/2:]) - rootHash := SimpleHashFromTwoHashes(leftRoot.Hash, rightRoot.Hash) + rootHash := simpleHashFromTwoHashes(leftRoot.Hash, rightRoot.Hash) root := &SimpleProofNode{rootHash, nil, nil, nil} leftRoot.Parent = root leftRoot.Right = rightRoot diff --git a/crypto/merkle/simple_tree.go b/crypto/merkle/simple_tree.go index 45e0c5c5..7aacb088 100644 --- a/crypto/merkle/simple_tree.go +++ b/crypto/merkle/simple_tree.go @@ -4,8 +4,8 @@ import ( "github.com/tendermint/tendermint/crypto/tmhash" ) -// SimpleHashFromTwoHashes is the basic operation of the Merkle tree: Hash(left | right). -func SimpleHashFromTwoHashes(left, right []byte) []byte { +// simpleHashFromTwoHashes is the basic operation of the Merkle tree: Hash(left | right). +func simpleHashFromTwoHashes(left, right []byte) []byte { var hasher = tmhash.New() err := encodeByteSlice(hasher, left) if err != nil { @@ -29,7 +29,7 @@ func SimpleHashFromByteSlices(items [][]byte) []byte { default: left := SimpleHashFromByteSlices(items[:(len(items)+1)/2]) right := SimpleHashFromByteSlices(items[(len(items)+1)/2:]) - return SimpleHashFromTwoHashes(left, right) + return simpleHashFromTwoHashes(left, right) } } diff --git a/types/block.go b/types/block.go index f41f4c1f..45a5b8c3 100644 --- a/types/block.go +++ b/types/block.go @@ -576,7 +576,6 @@ func (sh SignedHeader) StringIndented(indent string) string { indent, sh.Header.StringIndented(indent+" "), indent, sh.Commit.StringIndented(indent+" "), indent) - return "" } //----------------------------------------------------------------------------- @@ -660,7 +659,6 @@ func (data *EvidenceData) StringIndented(indent string) string { %s}#%v`, indent, strings.Join(evStrings, "\n"+indent+" "), indent, data.hash) - return "" } //-------------------------------------------------------------------------------- diff --git a/types/evidence.go b/types/evidence.go index 00c46c59..57523ab1 100644 --- a/types/evidence.go +++ b/types/evidence.go @@ -55,6 +55,7 @@ func (err *ErrEvidenceOverflow) Error() string { type Evidence interface { Height() int64 // height of the equivocation Address() []byte // address of the equivocating validator + Bytes() []byte // bytes which compromise the evidence Hash() []byte // hash of the evidence Verify(chainID string, pubKey crypto.PubKey) error // verify the evidence Equal(Evidence) bool // check equality of evidence @@ -88,6 +89,8 @@ type DuplicateVoteEvidence struct { VoteB *Vote } +var _ Evidence = &DuplicateVoteEvidence{} + // String returns a string representation of the evidence. func (dve *DuplicateVoteEvidence) String() string { return fmt.Sprintf("VoteA: %v; VoteB: %v", dve.VoteA, dve.VoteB) @@ -104,6 +107,11 @@ func (dve *DuplicateVoteEvidence) Address() []byte { return dve.PubKey.Address() } +// Hash returns the hash of the evidence. +func (dve *DuplicateVoteEvidence) Bytes() []byte { + return cdcEncode(dve) +} + // Hash returns the hash of the evidence. func (dve *DuplicateVoteEvidence) Hash() []byte { return tmhash.Sum(cdcEncode(dve)) @@ -172,6 +180,8 @@ type MockGoodEvidence struct { Address_ []byte } +var _ Evidence = &MockGoodEvidence{} + // UNSTABLE func NewMockGoodEvidence(height int64, idx int, address []byte) MockGoodEvidence { return MockGoodEvidence{height, address} @@ -182,6 +192,9 @@ func (e MockGoodEvidence) Address() []byte { return e.Address_ } func (e MockGoodEvidence) Hash() []byte { return []byte(fmt.Sprintf("%d-%x", e.Height_, e.Address_)) } +func (e MockGoodEvidence) Bytes() []byte { + return []byte(fmt.Sprintf("%d-%x", e.Height_, e.Address_)) +} func (e MockGoodEvidence) Verify(chainID string, pubKey crypto.PubKey) error { return nil } func (e MockGoodEvidence) Equal(ev Evidence) bool { e2 := ev.(MockGoodEvidence) @@ -216,18 +229,14 @@ type EvidenceList []Evidence // Hash returns the simple merkle root hash of the EvidenceList. func (evl EvidenceList) Hash() []byte { - // Recursive impl. - // Copied from crypto/merkle to avoid allocations - switch len(evl) { - case 0: - return nil - case 1: - return evl[0].Hash() - default: - left := EvidenceList(evl[:(len(evl)+1)/2]).Hash() - right := EvidenceList(evl[(len(evl)+1)/2:]).Hash() - return merkle.SimpleHashFromTwoHashes(left, right) + // These allocations are required because Evidence is not of type Bytes, and + // golang slices can't be typed cast. This shouldn't be a performance problem since + // the Evidence size is capped. + evidenceBzs := make([][]byte, len(evl)) + for i := 0; i < len(evl); i++ { + evidenceBzs[i] = evl[i].Bytes() } + return merkle.SimpleHashFromByteSlices(evidenceBzs) } func (evl EvidenceList) String() string { diff --git a/types/tx.go b/types/tx.go index ec42f3f1..10c097e3 100644 --- a/types/tx.go +++ b/types/tx.go @@ -31,18 +31,13 @@ type Txs []Tx // Hash returns the simple Merkle root hash of the transactions. func (txs Txs) Hash() []byte { - // Recursive impl. - // Copied from tendermint/crypto/merkle to avoid allocations - switch len(txs) { - case 0: - return nil - case 1: - return txs[0].Hash() - default: - left := Txs(txs[:(len(txs)+1)/2]).Hash() - right := Txs(txs[(len(txs)+1)/2:]).Hash() - return merkle.SimpleHashFromTwoHashes(left, right) + // These allocations will be removed once Txs is switched to [][]byte, + // ref #2603. This is because golang does not allow type casting slices without unsafe + txBzs := make([][]byte, len(txs)) + for i := 0; i < len(txs); i++ { + txBzs[i] = txs[i] } + return merkle.SimpleHashFromByteSlices(txBzs) } // Index returns the index of this transaction in the list, or -1 if not found From 55362ed76630f3e1ebec159a598f6a9fb5892cb1 Mon Sep 17 00:00:00 2001 From: Anton Kaliaev Date: Tue, 16 Oct 2018 10:09:24 +0400 Subject: [PATCH 014/267] [pubsub] document design shortcomings (#2641) Refs https://github.com/tendermint/tendermint/issues/1811#issuecomment-427825250 --- libs/pubsub/pubsub.go | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/libs/pubsub/pubsub.go b/libs/pubsub/pubsub.go index c104439f..18f098d8 100644 --- a/libs/pubsub/pubsub.go +++ b/libs/pubsub/pubsub.go @@ -9,6 +9,39 @@ // When some message is published, we match it with all queries. If there is a // match, this message will be pushed to all clients, subscribed to that query. // See query subpackage for our implementation. +// +// Due to the blocking send implementation, a single subscriber can freeze an +// entire server by not reading messages before it unsubscribes. To avoid such +// scenario, subscribers must either: +// +// a) make sure they continue to read from the out channel until +// Unsubscribe(All) is called +// +// s.Subscribe(ctx, sub, qry, out) +// go func() { +// for msg := range out { +// // handle msg +// // will exit automatically when out is closed by Unsubscribe(All) +// } +// }() +// s.UnsubscribeAll(ctx, sub) +// +// b) drain the out channel before calling Unsubscribe(All) +// +// s.Subscribe(ctx, sub, qry, out) +// defer func() { +// for range out { +// // drain out to make sure we don't block +// } +// s.UnsubscribeAll(ctx, sub) +// }() +// for msg := range out { +// // handle msg +// if err != nil { +// return err +// } +// } +// package pubsub import ( From 80562669bfc0ad56d9d37662407c60661fdf476b Mon Sep 17 00:00:00 2001 From: Peng Zhong <172531+nylira@users.noreply.github.com> Date: Tue, 16 Oct 2018 14:10:52 +0800 Subject: [PATCH 015/267] add Google Analytics for documentation pages (#2645) We need Google Analytics to start measuring how many developers are viewing our documentation. That way we can gauge how successful an/or useful various pages are. VuePress supports GA and all we have to provide is the tracking code. This PR also renames the static docs site to "Tendermint Documentation", which is a better representation of the contents than only "Tendermint Core". --- docs/.vuepress/config.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/docs/.vuepress/config.js b/docs/.vuepress/config.js index 342c5eac..9f8ddbc7 100644 --- a/docs/.vuepress/config.js +++ b/docs/.vuepress/config.js @@ -1,6 +1,7 @@ module.exports = { - title: "Tendermint Core", - description: "Documentation for Tendermint Core", + title: "Tendermint Documentation", + description: "Documentation for Tendermint Core.", + ga: "UA-51029217-1", dest: "./dist/docs", base: "/docs/", markdown: { From ed107d0e845144e5f6a819a4da6afbde95f05042 Mon Sep 17 00:00:00 2001 From: Uzair1995 Date: Wed, 17 Oct 2018 11:12:31 +0500 Subject: [PATCH 016/267] [scripts/install_tendermint_ubuntu] change /root to local user (#2647) --- scripts/install/install_tendermint_ubuntu.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/install/install_tendermint_ubuntu.sh b/scripts/install/install_tendermint_ubuntu.sh index 0e1de117..b9605de0 100644 --- a/scripts/install/install_tendermint_ubuntu.sh +++ b/scripts/install/install_tendermint_ubuntu.sh @@ -22,12 +22,12 @@ curl -O https://storage.googleapis.com/golang/go1.10.linux-amd64.tar.gz tar -xvf go1.10.linux-amd64.tar.gz # move go binary and add to path -mv go /usr/local +sudo mv go /usr/local echo "export PATH=\$PATH:/usr/local/go/bin" >> ~/.profile # create the goApps directory, set GOPATH, and put it on PATH mkdir goApps -echo "export GOPATH=/root/goApps" >> ~/.profile +echo "export GOPATH=$HOME/goApps" >> ~/.profile echo "export PATH=\$PATH:\$GOPATH/bin" >> ~/.profile source ~/.profile From f60713bca86a49464c1e10bae7cfe636c88b8bc8 Mon Sep 17 00:00:00 2001 From: Hendrik Hofstadt Date: Wed, 17 Oct 2018 10:26:14 +0200 Subject: [PATCH 017/267] privval: Add IPCPV and fix SocketPV (#2568) Ref: #2563 I added IPC as an unencrypted alternative to SocketPV. Besides I fixed the following aspects of SocketPV: Added locking since we are operating on a single socket The connection deadline is extended every time a successful packet exchange happens; otherwise the connection would always die permanently x seconds after the connection was established. Added a ping/heartbeat mechanism to keep the connection alive; native TCP keepalives do not work in this use-case * Extend the SecureConn socket to extend its deadline * Add locking & ping/heartbeat packets to SocketPV * Implement IPC PV and abstract socket signing * Refactored IPC and SocketPV * Implement @melekes comments * Fixes to rebase --- node/node.go | 4 +- privval/ipc.go | 120 ++++ privval/ipc_server.go | 131 ++++ privval/ipc_test.go | 147 +++++ privval/remote_signer.go | 303 +++++++++ privval/socket.go | 605 ------------------ privval/tcp.go | 214 +++++++ privval/tcp_server.go | 160 +++++ privval/{socket_tcp.go => tcp_socket.go} | 48 +- ...{socket_tcp_test.go => tcp_socket_test.go} | 5 +- privval/{socket_test.go => tcp_test.go} | 110 ++-- privval/wire.go | 2 +- 12 files changed, 1180 insertions(+), 669 deletions(-) create mode 100644 privval/ipc.go create mode 100644 privval/ipc_server.go create mode 100644 privval/ipc_test.go create mode 100644 privval/remote_signer.go delete mode 100644 privval/socket.go create mode 100644 privval/tcp.go create mode 100644 privval/tcp_server.go rename privval/{socket_tcp.go => tcp_socket.go} (59%) rename privval/{socket_tcp_test.go => tcp_socket_test.go} (91%) rename privval/{socket_test.go => tcp_test.go} (82%) diff --git a/node/node.go b/node/node.go index ed0fa119..9939f1c6 100644 --- a/node/node.go +++ b/node/node.go @@ -215,7 +215,7 @@ func NewNode(config *cfg.Config, // TODO: persist this key so external signer // can actually authenticate us privKey = ed25519.GenPrivKey() - pvsc = privval.NewSocketPV( + pvsc = privval.NewTCPVal( logger.With("module", "privval"), config.PrivValidatorListenAddr, privKey, @@ -579,7 +579,7 @@ func (n *Node) OnStop() { } } - if pvsc, ok := n.privValidator.(*privval.SocketPV); ok { + if pvsc, ok := n.privValidator.(*privval.TCPVal); ok { if err := pvsc.Stop(); err != nil { n.Logger.Error("Error stopping priv validator socket client", "err", err) } diff --git a/privval/ipc.go b/privval/ipc.go new file mode 100644 index 00000000..eda23fe6 --- /dev/null +++ b/privval/ipc.go @@ -0,0 +1,120 @@ +package privval + +import ( + "net" + "time" + + cmn "github.com/tendermint/tendermint/libs/common" + "github.com/tendermint/tendermint/libs/log" + "github.com/tendermint/tendermint/types" +) + +// IPCValOption sets an optional parameter on the SocketPV. +type IPCValOption func(*IPCVal) + +// IPCValConnTimeout sets the read and write timeout for connections +// from external signing processes. +func IPCValConnTimeout(timeout time.Duration) IPCValOption { + return func(sc *IPCVal) { sc.connTimeout = timeout } +} + +// IPCValHeartbeat sets the period on which to check the liveness of the +// connected Signer connections. +func IPCValHeartbeat(period time.Duration) IPCValOption { + return func(sc *IPCVal) { sc.connHeartbeat = period } +} + +// IPCVal implements PrivValidator, it uses a unix socket to request signatures +// from an external process. +type IPCVal struct { + cmn.BaseService + *RemoteSignerClient + + addr string + + connTimeout time.Duration + connHeartbeat time.Duration + + conn net.Conn + cancelPing chan struct{} + pingTicker *time.Ticker +} + +// Check that IPCVal implements PrivValidator. +var _ types.PrivValidator = (*IPCVal)(nil) + +// NewIPCVal returns an instance of IPCVal. +func NewIPCVal( + logger log.Logger, + socketAddr string, +) *IPCVal { + sc := &IPCVal{ + addr: socketAddr, + connTimeout: connTimeout, + connHeartbeat: connHeartbeat, + } + + sc.BaseService = *cmn.NewBaseService(logger, "IPCVal", sc) + + return sc +} + +// OnStart implements cmn.Service. +func (sc *IPCVal) OnStart() error { + err := sc.connect() + if err != nil { + sc.Logger.Error("OnStart", "err", err) + return err + } + + sc.RemoteSignerClient = NewRemoteSignerClient(sc.conn) + + // Start a routine to keep the connection alive + sc.cancelPing = make(chan struct{}, 1) + sc.pingTicker = time.NewTicker(sc.connHeartbeat) + go func() { + for { + select { + case <-sc.pingTicker.C: + err := sc.Ping() + if err != nil { + sc.Logger.Error("Ping", "err", err) + } + case <-sc.cancelPing: + sc.pingTicker.Stop() + return + } + } + }() + + return nil +} + +// OnStop implements cmn.Service. +func (sc *IPCVal) OnStop() { + if sc.cancelPing != nil { + close(sc.cancelPing) + } + + if sc.conn != nil { + if err := sc.conn.Close(); err != nil { + sc.Logger.Error("OnStop", "err", err) + } + } +} + +func (sc *IPCVal) connect() error { + la, err := net.ResolveUnixAddr("unix", sc.addr) + if err != nil { + return err + } + + conn, err := net.DialUnix("unix", nil, la) + if err != nil { + return err + } + + sc.conn = newTimeoutConn(conn, sc.connTimeout) + + return nil +} diff --git a/privval/ipc_server.go b/privval/ipc_server.go new file mode 100644 index 00000000..d3907cbd --- /dev/null +++ b/privval/ipc_server.go @@ -0,0 +1,131 @@ +package privval + +import ( + "io" + "net" + "time" + + cmn "github.com/tendermint/tendermint/libs/common" + "github.com/tendermint/tendermint/libs/log" + "github.com/tendermint/tendermint/types" +) + +// IPCRemoteSignerOption sets an optional parameter on the IPCRemoteSigner. +type IPCRemoteSignerOption func(*IPCRemoteSigner) + +// IPCRemoteSignerConnDeadline sets the read and write deadline for connections +// from external signing processes. +func IPCRemoteSignerConnDeadline(deadline time.Duration) IPCRemoteSignerOption { + return func(ss *IPCRemoteSigner) { ss.connDeadline = deadline } +} + +// IPCRemoteSignerConnRetries sets the amount of attempted retries to connect. +func IPCRemoteSignerConnRetries(retries int) IPCRemoteSignerOption { + return func(ss *IPCRemoteSigner) { ss.connRetries = retries } +} + +// IPCRemoteSigner is a RPC implementation of PrivValidator that listens on a unix socket. +type IPCRemoteSigner struct { + cmn.BaseService + + addr string + chainID string + connDeadline time.Duration + connRetries int + privVal types.PrivValidator + + listener *net.UnixListener +} + +// NewIPCRemoteSigner returns an instance of IPCRemoteSigner. +func NewIPCRemoteSigner( + logger log.Logger, + chainID, socketAddr string, + privVal types.PrivValidator, +) *IPCRemoteSigner { + rs := &IPCRemoteSigner{ + addr: socketAddr, + chainID: chainID, + connDeadline: time.Second * defaultConnDeadlineSeconds, + connRetries: defaultDialRetries, + privVal: privVal, + } + + rs.BaseService = *cmn.NewBaseService(logger, "IPCRemoteSigner", rs) + + return rs +} + +// OnStart implements cmn.Service. +func (rs *IPCRemoteSigner) OnStart() error { + err := rs.listen() + if err != nil { + err = cmn.ErrorWrap(err, "listen") + rs.Logger.Error("OnStart", "err", err) + return err + } + + go func() { + for { + conn, err := rs.listener.AcceptUnix() + if err != nil { + return + } + go rs.handleConnection(conn) + } + }() + + return nil +} + +// OnStop implements cmn.Service. +func (rs *IPCRemoteSigner) OnStop() { + if rs.listener != nil { + if err := rs.listener.Close(); err != nil { + rs.Logger.Error("OnStop", "err", cmn.ErrorWrap(err, "closing listener failed")) + } + } +} + +func (rs *IPCRemoteSigner) listen() error { + la, err := net.ResolveUnixAddr("unix", rs.addr) + if err != nil { + return err + } + + rs.listener, err = net.ListenUnix("unix", la) + + return err +} + +func (rs *IPCRemoteSigner) handleConnection(conn net.Conn) { + for { + if !rs.IsRunning() { + return // Ignore error from listener closing. + } + + // Reset the connection deadline + conn.SetDeadline(time.Now().Add(rs.connDeadline)) + + req, err := readMsg(conn) + if err != nil { + if err != io.EOF { + rs.Logger.Error("handleConnection", "err", err) + } + return + } + + res, err := handleRequest(req, rs.chainID, rs.privVal) + + if err != nil { + // only log the error; we'll reply with an error in res + rs.Logger.Error("handleConnection", "err", err) + } + + err = writeMsg(conn, res) + if err != nil { + rs.Logger.Error("handleConnection", "err", err) + return + } + } +} diff --git a/privval/ipc_test.go b/privval/ipc_test.go new file mode 100644 index 00000000..c8d6dfc7 --- /dev/null +++ b/privval/ipc_test.go @@ -0,0 +1,147 @@ +package privval + +import ( + "io/ioutil" + "os" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + cmn "github.com/tendermint/tendermint/libs/common" + "github.com/tendermint/tendermint/libs/log" + "github.com/tendermint/tendermint/types" +) + +func TestIPCPVVote(t *testing.T) { + var ( + chainID = cmn.RandStr(12) + sc, rs = testSetupIPCSocketPair(t, chainID, types.NewMockPV()) + + ts = time.Now() + vType = types.PrecommitType + want = &types.Vote{Timestamp: ts, Type: vType} + have = &types.Vote{Timestamp: ts, Type: vType} + ) + defer sc.Stop() + defer rs.Stop() + + require.NoError(t, rs.privVal.SignVote(chainID, want)) + require.NoError(t, sc.SignVote(chainID, have)) + assert.Equal(t, want.Signature, have.Signature) +} + +func TestIPCPVVoteResetDeadline(t *testing.T) { + var ( + chainID = cmn.RandStr(12) + sc, rs = testSetupIPCSocketPair(t, chainID, types.NewMockPV()) + + ts = time.Now() + vType = types.PrecommitType + want = &types.Vote{Timestamp: ts, Type: vType} + have = &types.Vote{Timestamp: ts, Type: vType} + ) + defer sc.Stop() + defer rs.Stop() + + time.Sleep(3 * time.Millisecond) + + require.NoError(t, rs.privVal.SignVote(chainID, want)) + require.NoError(t, sc.SignVote(chainID, have)) + assert.Equal(t, want.Signature, have.Signature) + + // This would exceed the deadline if it was not extended by the previous message + time.Sleep(3 * time.Millisecond) + + require.NoError(t, rs.privVal.SignVote(chainID, want)) + require.NoError(t, sc.SignVote(chainID, have)) + assert.Equal(t, want.Signature, have.Signature) +} + +func TestIPCPVVoteKeepalive(t *testing.T) { + var ( + chainID = cmn.RandStr(12) + sc, rs = testSetupIPCSocketPair(t, chainID, types.NewMockPV()) + + ts = time.Now() + vType = types.PrecommitType + want = &types.Vote{Timestamp: ts, Type: vType} + have = &types.Vote{Timestamp: ts, Type: vType} + ) + defer sc.Stop() + defer rs.Stop() + + time.Sleep(10 * time.Millisecond) + + require.NoError(t, rs.privVal.SignVote(chainID, want)) + require.NoError(t, sc.SignVote(chainID, have)) + assert.Equal(t, want.Signature, have.Signature) +} + +func testSetupIPCSocketPair( + t *testing.T, + chainID string, + privValidator types.PrivValidator, +) (*IPCVal, *IPCRemoteSigner) { + addr, err := testUnixAddr() + require.NoError(t, err) + + var ( + logger = log.TestingLogger() + privVal = privValidator + readyc = make(chan struct{}) + rs = NewIPCRemoteSigner( + logger, + chainID, + addr, + privVal, + ) + sc = NewIPCVal( + logger, + addr, + ) + ) + + IPCValConnTimeout(5 * time.Millisecond)(sc) + IPCValHeartbeat(time.Millisecond)(sc) + + IPCRemoteSignerConnDeadline(time.Millisecond * 5)(rs) + + testStartIPCRemoteSigner(t, readyc, rs) + + <-readyc + + require.NoError(t, sc.Start()) + assert.True(t, sc.IsRunning()) + + return sc, rs +} + +func testStartIPCRemoteSigner(t *testing.T, readyc chan struct{}, rs *IPCRemoteSigner) { + go func(rs *IPCRemoteSigner) { + require.NoError(t, rs.Start()) + assert.True(t, rs.IsRunning()) + + readyc <- struct{}{} + }(rs) +} + +func testUnixAddr() (string, error) { + f, err := ioutil.TempFile("/tmp", "nettest") + if err != nil { + return "", err + } + + addr := f.Name() + err = f.Close() + if err != nil { + return "", err + } + err = os.Remove(addr) + if err != nil { + return "", err + } + + return addr, nil +} diff --git a/privval/remote_signer.go b/privval/remote_signer.go new file mode 100644 index 00000000..399ee790 --- /dev/null +++ b/privval/remote_signer.go @@ -0,0 +1,303 @@ +package privval + +import ( + "fmt" + "io" + "net" + "sync" + + "github.com/tendermint/go-amino" + "github.com/tendermint/tendermint/crypto" + cmn "github.com/tendermint/tendermint/libs/common" + "github.com/tendermint/tendermint/types" +) + +// RemoteSignerClient implements PrivValidator, it uses a socket to request signatures +// from an external process. +type RemoteSignerClient struct { + conn net.Conn + lock sync.Mutex +} + +// Check that RemoteSignerClient implements PrivValidator. +var _ types.PrivValidator = (*RemoteSignerClient)(nil) + +// NewRemoteSignerClient returns an instance of RemoteSignerClient. +func NewRemoteSignerClient( + conn net.Conn, +) *RemoteSignerClient { + sc := &RemoteSignerClient{ + conn: conn, + } + return sc +} + +// GetAddress implements PrivValidator. +func (sc *RemoteSignerClient) GetAddress() types.Address { + pubKey, err := sc.getPubKey() + if err != nil { + panic(err) + } + + return pubKey.Address() +} + +// GetPubKey implements PrivValidator. +func (sc *RemoteSignerClient) GetPubKey() crypto.PubKey { + pubKey, err := sc.getPubKey() + if err != nil { + panic(err) + } + + return pubKey +} + +func (sc *RemoteSignerClient) getPubKey() (crypto.PubKey, error) { + sc.lock.Lock() + defer sc.lock.Unlock() + + err := writeMsg(sc.conn, &PubKeyMsg{}) + if err != nil { + return nil, err + } + + res, err := readMsg(sc.conn) + if err != nil { + return nil, err + } + + return res.(*PubKeyMsg).PubKey, nil +} + +// SignVote implements PrivValidator. +func (sc *RemoteSignerClient) SignVote(chainID string, vote *types.Vote) error { + sc.lock.Lock() + defer sc.lock.Unlock() + + err := writeMsg(sc.conn, &SignVoteRequest{Vote: vote}) + if err != nil { + return err + } + + res, err := readMsg(sc.conn) + if err != nil { + return err + } + + resp, ok := res.(*SignedVoteResponse) + if !ok { + return ErrUnexpectedResponse + } + if resp.Error != nil { + return resp.Error + } + *vote = *resp.Vote + + return nil +} + +// SignProposal implements PrivValidator. +func (sc *RemoteSignerClient) SignProposal( + chainID string, + proposal *types.Proposal, +) error { + sc.lock.Lock() + defer sc.lock.Unlock() + + err := writeMsg(sc.conn, &SignProposalRequest{Proposal: proposal}) + if err != nil { + return err + } + + res, err := readMsg(sc.conn) + if err != nil { + return err + } + resp, ok := res.(*SignedProposalResponse) + if !ok { + return ErrUnexpectedResponse + } + if resp.Error != nil { + return resp.Error + } + *proposal = *resp.Proposal + + return nil +} + +// SignHeartbeat implements PrivValidator. +func (sc *RemoteSignerClient) SignHeartbeat( + chainID string, + heartbeat *types.Heartbeat, +) error { + sc.lock.Lock() + defer sc.lock.Unlock() + + err := writeMsg(sc.conn, &SignHeartbeatRequest{Heartbeat: heartbeat}) + if err != nil { + return err + } + + res, err := readMsg(sc.conn) + if err != nil { + return err + } + resp, ok := res.(*SignedHeartbeatResponse) + if !ok { + return ErrUnexpectedResponse + } + if resp.Error != nil { + return resp.Error + } + *heartbeat = *resp.Heartbeat + + return nil +} + +// Ping is used to check connection health. +func (sc *RemoteSignerClient) Ping() error { + sc.lock.Lock() + defer sc.lock.Unlock() + + err := writeMsg(sc.conn, &PingRequest{}) + if err != nil { + return err + } + + res, err := readMsg(sc.conn) + if err != nil { + return err + } + _, ok := res.(*PingResponse) + if !ok { + return ErrUnexpectedResponse + } + + return nil +} + +// RemoteSignerMsg is sent between RemoteSigner and the RemoteSigner client. +type RemoteSignerMsg interface{} + +func RegisterRemoteSignerMsg(cdc *amino.Codec) { + cdc.RegisterInterface((*RemoteSignerMsg)(nil), nil) + cdc.RegisterConcrete(&PubKeyMsg{}, "tendermint/remotesigner/PubKeyMsg", nil) + cdc.RegisterConcrete(&SignVoteRequest{}, "tendermint/remotesigner/SignVoteRequest", nil) + cdc.RegisterConcrete(&SignedVoteResponse{}, "tendermint/remotesigner/SignedVoteResponse", nil) + cdc.RegisterConcrete(&SignProposalRequest{}, "tendermint/remotesigner/SignProposalRequest", nil) + cdc.RegisterConcrete(&SignedProposalResponse{}, "tendermint/remotesigner/SignedProposalResponse", nil) + cdc.RegisterConcrete(&SignHeartbeatRequest{}, "tendermint/remotesigner/SignHeartbeatRequest", nil) + cdc.RegisterConcrete(&SignedHeartbeatResponse{}, "tendermint/remotesigner/SignedHeartbeatResponse", nil) + cdc.RegisterConcrete(&PingRequest{}, "tendermint/remotesigner/PingRequest", nil) + cdc.RegisterConcrete(&PingResponse{}, "tendermint/remotesigner/PingResponse", nil) +} + +// PubKeyMsg is a PrivValidatorSocket message containing the public key. +type PubKeyMsg struct { + PubKey crypto.PubKey +} + +// SignVoteRequest is a PrivValidatorSocket message containing a vote. +type SignVoteRequest struct { + Vote *types.Vote +} + +// SignedVoteResponse is a PrivValidatorSocket message containing a signed vote along with a potenial error message. +type SignedVoteResponse struct { + Vote *types.Vote + Error *RemoteSignerError +} + +// SignProposalRequest is a PrivValidatorSocket message containing a Proposal. +type SignProposalRequest struct { + Proposal *types.Proposal +} + +type SignedProposalResponse struct { + Proposal *types.Proposal + Error *RemoteSignerError +} + +// SignHeartbeatRequest is a PrivValidatorSocket message containing a Heartbeat. +type SignHeartbeatRequest struct { + Heartbeat *types.Heartbeat +} + +type SignedHeartbeatResponse struct { + Heartbeat *types.Heartbeat + Error *RemoteSignerError +} + +// PingRequest is a PrivValidatorSocket message to keep the connection alive. +type PingRequest struct { +} + +type PingResponse struct { +} + +// RemoteSignerError allows (remote) validators to include meaningful error descriptions in their reply. +type RemoteSignerError struct { + // TODO(ismail): create an enum of known errors + Code int + Description string +} + +func (e *RemoteSignerError) Error() string { + return fmt.Sprintf("RemoteSigner returned error #%d: %s", e.Code, e.Description) +} + +func readMsg(r io.Reader) (msg RemoteSignerMsg, err error) { + const maxRemoteSignerMsgSize = 1024 * 10 + _, err = cdc.UnmarshalBinaryReader(r, &msg, maxRemoteSignerMsgSize) + if _, ok := err.(timeoutError); ok { + err = cmn.ErrorWrap(ErrConnTimeout, err.Error()) + } + return +} + +func writeMsg(w io.Writer, msg interface{}) (err error) { + _, err = cdc.MarshalBinaryWriter(w, msg) + if _, ok := err.(timeoutError); ok { + err = cmn.ErrorWrap(ErrConnTimeout, err.Error()) + } + return +} + +func handleRequest(req RemoteSignerMsg, chainID string, privVal types.PrivValidator) (RemoteSignerMsg, error) { + var res RemoteSignerMsg + var err error + + switch r := req.(type) { + case *PubKeyMsg: + var p crypto.PubKey + p = privVal.GetPubKey() + res = &PubKeyMsg{p} + case *SignVoteRequest: + err = privVal.SignVote(chainID, r.Vote) + if err != nil { + res = &SignedVoteResponse{nil, &RemoteSignerError{0, err.Error()}} + } else { + res = &SignedVoteResponse{r.Vote, nil} + } + case *SignProposalRequest: + err = privVal.SignProposal(chainID, r.Proposal) + if err != nil { + res = &SignedProposalResponse{nil, &RemoteSignerError{0, err.Error()}} + } else { + res = &SignedProposalResponse{r.Proposal, nil} + } + case *SignHeartbeatRequest: + err = privVal.SignHeartbeat(chainID, r.Heartbeat) + if err != nil { + res = &SignedHeartbeatResponse{nil, &RemoteSignerError{0, err.Error()}} + } else { + res = &SignedHeartbeatResponse{r.Heartbeat, nil} + } + case *PingRequest: + res = &PingResponse{} + default: + err = fmt.Errorf("unknown msg: %v", r) + } + + return res, err +} diff --git a/privval/socket.go b/privval/socket.go deleted file mode 100644 index 64d4c46d..00000000 --- a/privval/socket.go +++ /dev/null @@ -1,605 +0,0 @@ -package privval - -import ( - "errors" - "fmt" - "io" - "net" - "time" - - "github.com/tendermint/go-amino" - - "github.com/tendermint/tendermint/crypto" - "github.com/tendermint/tendermint/crypto/ed25519" - cmn "github.com/tendermint/tendermint/libs/common" - "github.com/tendermint/tendermint/libs/log" - p2pconn "github.com/tendermint/tendermint/p2p/conn" - "github.com/tendermint/tendermint/types" -) - -const ( - defaultAcceptDeadlineSeconds = 30 // tendermint waits this long for remote val to connect - defaultConnDeadlineSeconds = 3 // must be set before each read - defaultConnHeartBeatSeconds = 30 // tcp keep-alive period - defaultConnWaitSeconds = 60 // XXX: is this redundant with the accept deadline? - defaultDialRetries = 10 // try to connect to tendermint this many times -) - -// Socket errors. -var ( - ErrDialRetryMax = errors.New("dialed maximum retries") - ErrConnWaitTimeout = errors.New("waited for remote signer for too long") - ErrConnTimeout = errors.New("remote signer timed out") - ErrUnexpectedResponse = errors.New("received unexpected response") -) - -// SocketPVOption sets an optional parameter on the SocketPV. -type SocketPVOption func(*SocketPV) - -// SocketPVAcceptDeadline sets the deadline for the SocketPV listener. -// A zero time value disables the deadline. -func SocketPVAcceptDeadline(deadline time.Duration) SocketPVOption { - return func(sc *SocketPV) { sc.acceptDeadline = deadline } -} - -// SocketPVConnDeadline sets the read and write deadline for connections -// from external signing processes. -func SocketPVConnDeadline(deadline time.Duration) SocketPVOption { - return func(sc *SocketPV) { sc.connDeadline = deadline } -} - -// SocketPVHeartbeat sets the period on which to check the liveness of the -// connected Signer connections. -func SocketPVHeartbeat(period time.Duration) SocketPVOption { - return func(sc *SocketPV) { sc.connHeartbeat = period } -} - -// SocketPVConnWait sets the timeout duration before connection of external -// signing processes are considered to be unsuccessful. -func SocketPVConnWait(timeout time.Duration) SocketPVOption { - return func(sc *SocketPV) { sc.connWaitTimeout = timeout } -} - -// SocketPV implements PrivValidator, it uses a socket to request signatures -// from an external process. -type SocketPV struct { - cmn.BaseService - - addr string - acceptDeadline time.Duration - connDeadline time.Duration - connHeartbeat time.Duration - connWaitTimeout time.Duration - privKey ed25519.PrivKeyEd25519 - - conn net.Conn - listener net.Listener -} - -// Check that SocketPV implements PrivValidator. -var _ types.PrivValidator = (*SocketPV)(nil) - -// NewSocketPV returns an instance of SocketPV. -func NewSocketPV( - logger log.Logger, - socketAddr string, - privKey ed25519.PrivKeyEd25519, -) *SocketPV { - sc := &SocketPV{ - addr: socketAddr, - acceptDeadline: time.Second * defaultAcceptDeadlineSeconds, - connDeadline: time.Second * defaultConnDeadlineSeconds, - connHeartbeat: time.Second * defaultConnHeartBeatSeconds, - connWaitTimeout: time.Second * defaultConnWaitSeconds, - privKey: privKey, - } - - sc.BaseService = *cmn.NewBaseService(logger, "SocketPV", sc) - - return sc -} - -// GetAddress implements PrivValidator. -func (sc *SocketPV) GetAddress() types.Address { - addr, err := sc.getAddress() - if err != nil { - panic(err) - } - - return addr -} - -// Address is an alias for PubKey().Address(). -func (sc *SocketPV) getAddress() (cmn.HexBytes, error) { - p, err := sc.getPubKey() - if err != nil { - return nil, err - } - - return p.Address(), nil -} - -// GetPubKey implements PrivValidator. -func (sc *SocketPV) GetPubKey() crypto.PubKey { - pubKey, err := sc.getPubKey() - if err != nil { - panic(err) - } - - return pubKey -} - -func (sc *SocketPV) getPubKey() (crypto.PubKey, error) { - err := writeMsg(sc.conn, &PubKeyMsg{}) - if err != nil { - return nil, err - } - - res, err := readMsg(sc.conn) - if err != nil { - return nil, err - } - - return res.(*PubKeyMsg).PubKey, nil -} - -// SignVote implements PrivValidator. -func (sc *SocketPV) SignVote(chainID string, vote *types.Vote) error { - err := writeMsg(sc.conn, &SignVoteRequest{Vote: vote}) - if err != nil { - return err - } - - res, err := readMsg(sc.conn) - if err != nil { - return err - } - - resp, ok := res.(*SignedVoteResponse) - if !ok { - return ErrUnexpectedResponse - } - if resp.Error != nil { - return fmt.Errorf("remote error occurred: code: %v, description: %s", - resp.Error.Code, - resp.Error.Description) - } - *vote = *resp.Vote - - return nil -} - -// SignProposal implements PrivValidator. -func (sc *SocketPV) SignProposal( - chainID string, - proposal *types.Proposal, -) error { - err := writeMsg(sc.conn, &SignProposalRequest{Proposal: proposal}) - if err != nil { - return err - } - - res, err := readMsg(sc.conn) - if err != nil { - return err - } - resp, ok := res.(*SignedProposalResponse) - if !ok { - return ErrUnexpectedResponse - } - if resp.Error != nil { - return fmt.Errorf("remote error occurred: code: %v, description: %s", - resp.Error.Code, - resp.Error.Description) - } - *proposal = *resp.Proposal - - return nil -} - -// SignHeartbeat implements PrivValidator. -func (sc *SocketPV) SignHeartbeat( - chainID string, - heartbeat *types.Heartbeat, -) error { - err := writeMsg(sc.conn, &SignHeartbeatRequest{Heartbeat: heartbeat}) - if err != nil { - return err - } - - res, err := readMsg(sc.conn) - if err != nil { - return err - } - resp, ok := res.(*SignedHeartbeatResponse) - if !ok { - return ErrUnexpectedResponse - } - if resp.Error != nil { - return fmt.Errorf("remote error occurred: code: %v, description: %s", - resp.Error.Code, - resp.Error.Description) - } - *heartbeat = *resp.Heartbeat - - return nil -} - -// OnStart implements cmn.Service. -func (sc *SocketPV) OnStart() error { - if err := sc.listen(); err != nil { - err = cmn.ErrorWrap(err, "failed to listen") - sc.Logger.Error( - "OnStart", - "err", err, - ) - return err - } - - conn, err := sc.waitConnection() - if err != nil { - err = cmn.ErrorWrap(err, "failed to accept connection") - sc.Logger.Error( - "OnStart", - "err", err, - ) - - return err - } - - sc.conn = conn - - return nil -} - -// OnStop implements cmn.Service. -func (sc *SocketPV) OnStop() { - if sc.conn != nil { - if err := sc.conn.Close(); err != nil { - err = cmn.ErrorWrap(err, "failed to close connection") - sc.Logger.Error( - "OnStop", - "err", err, - ) - } - } - - if sc.listener != nil { - if err := sc.listener.Close(); err != nil { - err = cmn.ErrorWrap(err, "failed to close listener") - sc.Logger.Error( - "OnStop", - "err", err, - ) - } - } -} - -func (sc *SocketPV) acceptConnection() (net.Conn, error) { - conn, err := sc.listener.Accept() - if err != nil { - if !sc.IsRunning() { - return nil, nil // Ignore error from listener closing. - } - return nil, err - - } - - conn, err = p2pconn.MakeSecretConnection(conn, sc.privKey) - if err != nil { - return nil, err - } - - return conn, nil -} - -func (sc *SocketPV) listen() error { - ln, err := net.Listen(cmn.ProtocolAndAddress(sc.addr)) - if err != nil { - return err - } - - sc.listener = newTCPTimeoutListener( - ln, - sc.acceptDeadline, - sc.connDeadline, - sc.connHeartbeat, - ) - - return nil -} - -// waitConnection uses the configured wait timeout to error if no external -// process connects in the time period. -func (sc *SocketPV) waitConnection() (net.Conn, error) { - var ( - connc = make(chan net.Conn, 1) - errc = make(chan error, 1) - ) - - go func(connc chan<- net.Conn, errc chan<- error) { - conn, err := sc.acceptConnection() - if err != nil { - errc <- err - return - } - - connc <- conn - }(connc, errc) - - select { - case conn := <-connc: - return conn, nil - case err := <-errc: - if _, ok := err.(timeoutError); ok { - return nil, cmn.ErrorWrap(ErrConnWaitTimeout, err.Error()) - } - return nil, err - case <-time.After(sc.connWaitTimeout): - return nil, ErrConnWaitTimeout - } -} - -//--------------------------------------------------------- - -// RemoteSignerOption sets an optional parameter on the RemoteSigner. -type RemoteSignerOption func(*RemoteSigner) - -// RemoteSignerConnDeadline sets the read and write deadline for connections -// from external signing processes. -func RemoteSignerConnDeadline(deadline time.Duration) RemoteSignerOption { - return func(ss *RemoteSigner) { ss.connDeadline = deadline } -} - -// RemoteSignerConnRetries sets the amount of attempted retries to connect. -func RemoteSignerConnRetries(retries int) RemoteSignerOption { - return func(ss *RemoteSigner) { ss.connRetries = retries } -} - -// RemoteSigner implements PrivValidator by dialing to a socket. -type RemoteSigner struct { - cmn.BaseService - - addr string - chainID string - connDeadline time.Duration - connRetries int - privKey ed25519.PrivKeyEd25519 - privVal types.PrivValidator - - conn net.Conn -} - -// NewRemoteSigner returns an instance of RemoteSigner. -func NewRemoteSigner( - logger log.Logger, - chainID, socketAddr string, - privVal types.PrivValidator, - privKey ed25519.PrivKeyEd25519, -) *RemoteSigner { - rs := &RemoteSigner{ - addr: socketAddr, - chainID: chainID, - connDeadline: time.Second * defaultConnDeadlineSeconds, - connRetries: defaultDialRetries, - privKey: privKey, - privVal: privVal, - } - - rs.BaseService = *cmn.NewBaseService(logger, "RemoteSigner", rs) - - return rs -} - -// OnStart implements cmn.Service. -func (rs *RemoteSigner) OnStart() error { - conn, err := rs.connect() - if err != nil { - err = cmn.ErrorWrap(err, "connect") - rs.Logger.Error("OnStart", "err", err) - return err - } - - go rs.handleConnection(conn) - - return nil -} - -// OnStop implements cmn.Service. -func (rs *RemoteSigner) OnStop() { - if rs.conn == nil { - return - } - - if err := rs.conn.Close(); err != nil { - rs.Logger.Error("OnStop", "err", cmn.ErrorWrap(err, "closing listener failed")) - } -} - -func (rs *RemoteSigner) connect() (net.Conn, error) { - for retries := rs.connRetries; retries > 0; retries-- { - // Don't sleep if it is the first retry. - if retries != rs.connRetries { - time.Sleep(rs.connDeadline) - } - - conn, err := cmn.Connect(rs.addr) - if err != nil { - err = cmn.ErrorWrap(err, "connection failed") - rs.Logger.Error( - "connect", - "addr", rs.addr, - "err", err, - ) - - continue - } - - if err := conn.SetDeadline(time.Now().Add(time.Second * defaultConnDeadlineSeconds)); err != nil { - err = cmn.ErrorWrap(err, "setting connection timeout failed") - rs.Logger.Error( - "connect", - "err", err, - ) - continue - } - - conn, err = p2pconn.MakeSecretConnection(conn, rs.privKey) - if err != nil { - err = cmn.ErrorWrap(err, "encrypting connection failed") - rs.Logger.Error( - "connect", - "err", err, - ) - - continue - } - - return conn, nil - } - - return nil, ErrDialRetryMax -} - -func (rs *RemoteSigner) handleConnection(conn net.Conn) { - for { - if !rs.IsRunning() { - return // Ignore error from listener closing. - } - - req, err := readMsg(conn) - if err != nil { - if err != io.EOF { - rs.Logger.Error("handleConnection", "err", err) - } - return - } - - var res SocketPVMsg - - switch r := req.(type) { - case *PubKeyMsg: - var p crypto.PubKey - p = rs.privVal.GetPubKey() - res = &PubKeyMsg{p} - case *SignVoteRequest: - err = rs.privVal.SignVote(rs.chainID, r.Vote) - if err != nil { - res = &SignedVoteResponse{nil, &RemoteSignerError{0, err.Error()}} - } else { - res = &SignedVoteResponse{r.Vote, nil} - } - case *SignProposalRequest: - err = rs.privVal.SignProposal(rs.chainID, r.Proposal) - if err != nil { - res = &SignedProposalResponse{nil, &RemoteSignerError{0, err.Error()}} - } else { - res = &SignedProposalResponse{r.Proposal, nil} - } - case *SignHeartbeatRequest: - err = rs.privVal.SignHeartbeat(rs.chainID, r.Heartbeat) - if err != nil { - res = &SignedHeartbeatResponse{nil, &RemoteSignerError{0, err.Error()}} - } else { - res = &SignedHeartbeatResponse{r.Heartbeat, nil} - } - default: - err = fmt.Errorf("unknown msg: %v", r) - } - - if err != nil { - // only log the error; we'll reply with an error in res - rs.Logger.Error("handleConnection", "err", err) - } - - err = writeMsg(conn, res) - if err != nil { - rs.Logger.Error("handleConnection", "err", err) - return - } - } -} - -//--------------------------------------------------------- - -// SocketPVMsg is sent between RemoteSigner and SocketPV. -type SocketPVMsg interface{} - -func RegisterSocketPVMsg(cdc *amino.Codec) { - cdc.RegisterInterface((*SocketPVMsg)(nil), nil) - cdc.RegisterConcrete(&PubKeyMsg{}, "tendermint/socketpv/PubKeyMsg", nil) - cdc.RegisterConcrete(&SignVoteRequest{}, "tendermint/socketpv/SignVoteRequest", nil) - cdc.RegisterConcrete(&SignedVoteResponse{}, "tendermint/socketpv/SignedVoteResponse", nil) - cdc.RegisterConcrete(&SignProposalRequest{}, "tendermint/socketpv/SignProposalRequest", nil) - cdc.RegisterConcrete(&SignedProposalResponse{}, "tendermint/socketpv/SignedProposalResponse", nil) - cdc.RegisterConcrete(&SignHeartbeatRequest{}, "tendermint/socketpv/SignHeartbeatRequest", nil) - cdc.RegisterConcrete(&SignedHeartbeatResponse{}, "tendermint/socketpv/SignedHeartbeatResponse", nil) -} - -// PubKeyMsg is a PrivValidatorSocket message containing the public key. -type PubKeyMsg struct { - PubKey crypto.PubKey -} - -// SignVoteRequest is a PrivValidatorSocket message containing a vote. -type SignVoteRequest struct { - Vote *types.Vote -} - -// SignedVoteResponse is a PrivValidatorSocket message containing a signed vote along with a potenial error message. -type SignedVoteResponse struct { - Vote *types.Vote - Error *RemoteSignerError -} - -// SignProposalRequest is a PrivValidatorSocket message containing a Proposal. -type SignProposalRequest struct { - Proposal *types.Proposal -} - -type SignedProposalResponse struct { - Proposal *types.Proposal - Error *RemoteSignerError -} - -// SignHeartbeatRequest is a PrivValidatorSocket message containing a Heartbeat. -type SignHeartbeatRequest struct { - Heartbeat *types.Heartbeat -} - -type SignedHeartbeatResponse struct { - Heartbeat *types.Heartbeat - Error *RemoteSignerError -} - -// RemoteSignerError allows (remote) validators to include meaningful error descriptions in their reply. -type RemoteSignerError struct { - // TODO(ismail): create an enum of known errors - Code int - Description string -} - -func readMsg(r io.Reader) (msg SocketPVMsg, err error) { - const maxSocketPVMsgSize = 1024 * 10 - - // set deadline before trying to read - conn := r.(net.Conn) - if err := conn.SetDeadline(time.Now().Add(time.Second * defaultConnDeadlineSeconds)); err != nil { - err = cmn.ErrorWrap(err, "setting connection timeout failed in readMsg") - return msg, err - } - - _, err = cdc.UnmarshalBinaryReader(r, &msg, maxSocketPVMsgSize) - if _, ok := err.(timeoutError); ok { - err = cmn.ErrorWrap(ErrConnTimeout, err.Error()) - } - return -} - -func writeMsg(w io.Writer, msg interface{}) (err error) { - _, err = cdc.MarshalBinaryWriter(w, msg) - if _, ok := err.(timeoutError); ok { - err = cmn.ErrorWrap(ErrConnTimeout, err.Error()) - } - return -} diff --git a/privval/tcp.go b/privval/tcp.go new file mode 100644 index 00000000..11bd833c --- /dev/null +++ b/privval/tcp.go @@ -0,0 +1,214 @@ +package privval + +import ( + "errors" + "net" + "time" + + "github.com/tendermint/tendermint/crypto/ed25519" + cmn "github.com/tendermint/tendermint/libs/common" + "github.com/tendermint/tendermint/libs/log" + p2pconn "github.com/tendermint/tendermint/p2p/conn" + "github.com/tendermint/tendermint/types" +) + +const ( + defaultAcceptDeadlineSeconds = 3 + defaultConnDeadlineSeconds = 3 + defaultConnHeartBeatSeconds = 2 + defaultDialRetries = 10 +) + +// Socket errors. +var ( + ErrDialRetryMax = errors.New("dialed maximum retries") + ErrConnTimeout = errors.New("remote signer timed out") + ErrUnexpectedResponse = errors.New("received unexpected response") +) + +var ( + acceptDeadline = time.Second * defaultAcceptDeadlineSeconds + connTimeout = time.Second * defaultConnDeadlineSeconds + connHeartbeat = time.Second * defaultConnHeartBeatSeconds +) + +// TCPValOption sets an optional parameter on the SocketPV. +type TCPValOption func(*TCPVal) + +// TCPValAcceptDeadline sets the deadline for the TCPVal listener. +// A zero time value disables the deadline. +func TCPValAcceptDeadline(deadline time.Duration) TCPValOption { + return func(sc *TCPVal) { sc.acceptDeadline = deadline } +} + +// TCPValConnTimeout sets the read and write timeout for connections +// from external signing processes. +func TCPValConnTimeout(timeout time.Duration) TCPValOption { + return func(sc *TCPVal) { sc.connTimeout = timeout } +} + +// TCPValHeartbeat sets the period on which to check the liveness of the +// connected Signer connections. +func TCPValHeartbeat(period time.Duration) TCPValOption { + return func(sc *TCPVal) { sc.connHeartbeat = period } +} + +// TCPVal implements PrivValidator, it uses a socket to request signatures +// from an external process. +type TCPVal struct { + cmn.BaseService + *RemoteSignerClient + + addr string + acceptDeadline time.Duration + connTimeout time.Duration + connHeartbeat time.Duration + privKey ed25519.PrivKeyEd25519 + + conn net.Conn + listener net.Listener + cancelPing chan struct{} + pingTicker *time.Ticker +} + +// Check that TCPVal implements PrivValidator. +var _ types.PrivValidator = (*TCPVal)(nil) + +// NewTCPVal returns an instance of TCPVal. +func NewTCPVal( + logger log.Logger, + socketAddr string, + privKey ed25519.PrivKeyEd25519, +) *TCPVal { + sc := &TCPVal{ + addr: socketAddr, + acceptDeadline: acceptDeadline, + connTimeout: connTimeout, + connHeartbeat: connHeartbeat, + privKey: privKey, + } + + sc.BaseService = *cmn.NewBaseService(logger, "TCPVal", sc) + + return sc +} + +// OnStart implements cmn.Service. +func (sc *TCPVal) OnStart() error { + if err := sc.listen(); err != nil { + sc.Logger.Error("OnStart", "err", err) + return err + } + + conn, err := sc.waitConnection() + if err != nil { + sc.Logger.Error("OnStart", "err", err) + return err + } + + sc.conn = conn + + sc.RemoteSignerClient = NewRemoteSignerClient(sc.conn) + + // Start a routine to keep the connection alive + sc.cancelPing = make(chan struct{}, 1) + sc.pingTicker = time.NewTicker(sc.connHeartbeat) + go func() { + for { + select { + case <-sc.pingTicker.C: + err := sc.Ping() + if err != nil { + sc.Logger.Error( + "Ping", + "err", err, + ) + } + case <-sc.cancelPing: + sc.pingTicker.Stop() + return + } + } + }() + + return nil +} + +// OnStop implements cmn.Service. +func (sc *TCPVal) OnStop() { + if sc.cancelPing != nil { + close(sc.cancelPing) + } + + if sc.conn != nil { + if err := sc.conn.Close(); err != nil { + sc.Logger.Error("OnStop", "err", err) + } + } + + if sc.listener != nil { + if err := sc.listener.Close(); err != nil { + sc.Logger.Error("OnStop", "err", err) + } + } +} + +func (sc *TCPVal) acceptConnection() (net.Conn, error) { + conn, err := sc.listener.Accept() + if err != nil { + if !sc.IsRunning() { + return nil, nil // Ignore error from listener closing. + } + return nil, err + + } + + conn, err = p2pconn.MakeSecretConnection(conn, sc.privKey) + if err != nil { + return nil, err + } + + return conn, nil +} + +func (sc *TCPVal) listen() error { + ln, err := net.Listen(cmn.ProtocolAndAddress(sc.addr)) + if err != nil { + return err + } + + sc.listener = newTCPTimeoutListener( + ln, + sc.acceptDeadline, + sc.connTimeout, + sc.connHeartbeat, + ) + + return nil +} + +// waitConnection uses the configured wait timeout to error if no external +// process connects in the time period. +func (sc *TCPVal) waitConnection() (net.Conn, error) { + var ( + connc = make(chan net.Conn, 1) + errc = make(chan error, 1) + ) + + go func(connc chan<- net.Conn, errc chan<- error) { + conn, err := sc.acceptConnection() + if err != nil { + errc <- err + return + } + + connc <- conn + }(connc, errc) + + select { + case conn := <-connc: + return conn, nil + case err := <-errc: + return nil, err + } +} diff --git a/privval/tcp_server.go b/privval/tcp_server.go new file mode 100644 index 00000000..694023d7 --- /dev/null +++ b/privval/tcp_server.go @@ -0,0 +1,160 @@ +package privval + +import ( + "io" + "net" + "time" + + "github.com/tendermint/tendermint/crypto/ed25519" + cmn "github.com/tendermint/tendermint/libs/common" + "github.com/tendermint/tendermint/libs/log" + p2pconn "github.com/tendermint/tendermint/p2p/conn" + "github.com/tendermint/tendermint/types" +) + +// RemoteSignerOption sets an optional parameter on the RemoteSigner. +type RemoteSignerOption func(*RemoteSigner) + +// RemoteSignerConnDeadline sets the read and write deadline for connections +// from external signing processes. +func RemoteSignerConnDeadline(deadline time.Duration) RemoteSignerOption { + return func(ss *RemoteSigner) { ss.connDeadline = deadline } +} + +// RemoteSignerConnRetries sets the amount of attempted retries to connect. +func RemoteSignerConnRetries(retries int) RemoteSignerOption { + return func(ss *RemoteSigner) { ss.connRetries = retries } +} + +// RemoteSigner implements PrivValidator by dialing to a socket. +type RemoteSigner struct { + cmn.BaseService + + addr string + chainID string + connDeadline time.Duration + connRetries int + privKey ed25519.PrivKeyEd25519 + privVal types.PrivValidator + + conn net.Conn +} + +// NewRemoteSigner returns an instance of RemoteSigner. +func NewRemoteSigner( + logger log.Logger, + chainID, socketAddr string, + privVal types.PrivValidator, + privKey ed25519.PrivKeyEd25519, +) *RemoteSigner { + rs := &RemoteSigner{ + addr: socketAddr, + chainID: chainID, + connDeadline: time.Second * defaultConnDeadlineSeconds, + connRetries: defaultDialRetries, + privKey: privKey, + privVal: privVal, + } + + rs.BaseService = *cmn.NewBaseService(logger, "RemoteSigner", rs) + + return rs +} + +// OnStart implements cmn.Service. +func (rs *RemoteSigner) OnStart() error { + conn, err := rs.connect() + if err != nil { + rs.Logger.Error("OnStart", "err", err) + return err + } + + go rs.handleConnection(conn) + + return nil +} + +// OnStop implements cmn.Service. +func (rs *RemoteSigner) OnStop() { + if rs.conn == nil { + return + } + + if err := rs.conn.Close(); err != nil { + rs.Logger.Error("OnStop", "err", cmn.ErrorWrap(err, "closing listener failed")) + } +} + +func (rs *RemoteSigner) connect() (net.Conn, error) { + for retries := rs.connRetries; retries > 0; retries-- { + // Don't sleep if it is the first retry. + if retries != rs.connRetries { + time.Sleep(rs.connDeadline) + } + + conn, err := cmn.Connect(rs.addr) + if err != nil { + rs.Logger.Error( + "connect", + "addr", rs.addr, + "err", err, + ) + + continue + } + + if err := conn.SetDeadline(time.Now().Add(connTimeout)); err != nil { + rs.Logger.Error( + "connect", + "err", err, + ) + continue + } + + conn, err = p2pconn.MakeSecretConnection(conn, rs.privKey) + if err != nil { + rs.Logger.Error( + "connect", + "err", err, + ) + + continue + } + + return conn, nil + } + + return nil, ErrDialRetryMax +} + +func (rs *RemoteSigner) handleConnection(conn net.Conn) { + for { + if !rs.IsRunning() { + return // Ignore error from listener closing. + } + + // Reset the connection deadline + conn.SetDeadline(time.Now().Add(rs.connDeadline)) + + req, err := readMsg(conn) + if err != nil { + if err != io.EOF { + rs.Logger.Error("handleConnection", "err", err) + } + return + } + + res, err := handleRequest(req, rs.chainID, rs.privVal) + + if err != nil { + // only log the error; we'll reply with an error in res + rs.Logger.Error("handleConnection", "err", err) + } + + err = writeMsg(conn, res) + if err != nil { + rs.Logger.Error("handleConnection", "err", err) + return + } + } +} diff --git a/privval/socket_tcp.go b/privval/tcp_socket.go similarity index 59% rename from privval/socket_tcp.go rename to privval/tcp_socket.go index b26db00c..2b17bf26 100644 --- a/privval/socket_tcp.go +++ b/privval/tcp_socket.go @@ -24,6 +24,13 @@ type tcpTimeoutListener struct { period time.Duration } +// timeoutConn wraps a net.Conn to standardise protocol timeouts / deadline resets. +type timeoutConn struct { + net.Conn + + connDeadline time.Duration +} + // newTCPTimeoutListener returns an instance of tcpTimeoutListener. func newTCPTimeoutListener( ln net.Listener, @@ -38,6 +45,16 @@ func newTCPTimeoutListener( } } +// newTimeoutConn returns an instance of newTCPTimeoutConn. +func newTimeoutConn( + conn net.Conn, + connDeadline time.Duration) *timeoutConn { + return &timeoutConn{ + conn, + connDeadline, + } +} + // Accept implements net.Listener. func (ln tcpTimeoutListener) Accept() (net.Conn, error) { err := ln.SetDeadline(time.Now().Add(ln.acceptDeadline)) @@ -50,17 +67,24 @@ func (ln tcpTimeoutListener) Accept() (net.Conn, error) { return nil, err } - if err := tc.SetDeadline(time.Now().Add(ln.connDeadline)); err != nil { - return nil, err - } + // Wrap the conn in our timeout wrapper + conn := newTimeoutConn(tc, ln.connDeadline) - if err := tc.SetKeepAlive(true); err != nil { - return nil, err - } - - if err := tc.SetKeepAlivePeriod(ln.period); err != nil { - return nil, err - } - - return tc, nil + return conn, nil +} + +// Read implements net.Listener. +func (c timeoutConn) Read(b []byte) (n int, err error) { + // Reset deadline + c.Conn.SetReadDeadline(time.Now().Add(c.connDeadline)) + + return c.Conn.Read(b) +} + +// Write implements net.Listener. +func (c timeoutConn) Write(b []byte) (n int, err error) { + // Reset deadline + c.Conn.SetWriteDeadline(time.Now().Add(c.connDeadline)) + + return c.Conn.Write(b) } diff --git a/privval/socket_tcp_test.go b/privval/tcp_socket_test.go similarity index 91% rename from privval/socket_tcp_test.go rename to privval/tcp_socket_test.go index 44a673c0..285e73ed 100644 --- a/privval/socket_tcp_test.go +++ b/privval/tcp_socket_test.go @@ -44,13 +44,14 @@ func TestTCPTimeoutListenerConnDeadline(t *testing.T) { time.Sleep(2 * time.Millisecond) - _, err = c.Write([]byte("foo")) + msg := make([]byte, 200) + _, err = c.Read(msg) opErr, ok := err.(*net.OpError) if !ok { t.Fatalf("have %v, want *net.OpError", err) } - if have, want := opErr.Op, "write"; have != want { + if have, want := opErr.Op, "read"; have != want { t.Errorf("have %v, want %v", have, want) } }(ln) diff --git a/privval/socket_test.go b/privval/tcp_test.go similarity index 82% rename from privval/socket_test.go rename to privval/tcp_test.go index aa2e15fa..6549759d 100644 --- a/privval/socket_test.go +++ b/privval/tcp_test.go @@ -27,8 +27,7 @@ func TestSocketPVAddress(t *testing.T) { serverAddr := rs.privVal.GetAddress() - clientAddr, err := sc.getAddress() - require.NoError(t, err) + clientAddr := sc.GetAddress() assert.Equal(t, serverAddr, clientAddr) @@ -91,6 +90,53 @@ func TestSocketPVVote(t *testing.T) { assert.Equal(t, want.Signature, have.Signature) } +func TestSocketPVVoteResetDeadline(t *testing.T) { + var ( + chainID = cmn.RandStr(12) + sc, rs = testSetupSocketPair(t, chainID, types.NewMockPV()) + + ts = time.Now() + vType = types.PrecommitType + want = &types.Vote{Timestamp: ts, Type: vType} + have = &types.Vote{Timestamp: ts, Type: vType} + ) + defer sc.Stop() + defer rs.Stop() + + time.Sleep(3 * time.Millisecond) + + require.NoError(t, rs.privVal.SignVote(chainID, want)) + require.NoError(t, sc.SignVote(chainID, have)) + assert.Equal(t, want.Signature, have.Signature) + + // This would exceed the deadline if it was not extended by the previous message + time.Sleep(3 * time.Millisecond) + + require.NoError(t, rs.privVal.SignVote(chainID, want)) + require.NoError(t, sc.SignVote(chainID, have)) + assert.Equal(t, want.Signature, have.Signature) +} + +func TestSocketPVVoteKeepalive(t *testing.T) { + var ( + chainID = cmn.RandStr(12) + sc, rs = testSetupSocketPair(t, chainID, types.NewMockPV()) + + ts = time.Now() + vType = types.PrecommitType + want = &types.Vote{Timestamp: ts, Type: vType} + have = &types.Vote{Timestamp: ts, Type: vType} + ) + defer sc.Stop() + defer rs.Stop() + + time.Sleep(10 * time.Millisecond) + + require.NoError(t, rs.privVal.SignVote(chainID, want)) + require.NoError(t, sc.SignVote(chainID, have)) + assert.Equal(t, want.Signature, have.Signature) +} + func TestSocketPVHeartbeat(t *testing.T) { var ( chainID = cmn.RandStr(12) @@ -107,36 +153,20 @@ func TestSocketPVHeartbeat(t *testing.T) { assert.Equal(t, want.Signature, have.Signature) } -func TestSocketPVAcceptDeadline(t *testing.T) { - var ( - sc = NewSocketPV( - log.TestingLogger(), - "127.0.0.1:0", - ed25519.GenPrivKey(), - ) - ) - defer sc.Stop() - - SocketPVAcceptDeadline(time.Millisecond)(sc) - - assert.Equal(t, sc.Start().(cmn.Error).Data(), ErrConnWaitTimeout) -} - func TestSocketPVDeadline(t *testing.T) { var ( addr = testFreeAddr(t) listenc = make(chan struct{}) - sc = NewSocketPV( + sc = NewTCPVal( log.TestingLogger(), addr, ed25519.GenPrivKey(), ) ) - SocketPVConnDeadline(100 * time.Millisecond)(sc) - SocketPVConnWait(500 * time.Millisecond)(sc) + TCPValConnTimeout(100 * time.Millisecond)(sc) - go func(sc *SocketPV) { + go func(sc *TCPVal) { defer close(listenc) require.NoError(t, sc.Start()) @@ -161,26 +191,10 @@ func TestSocketPVDeadline(t *testing.T) { <-listenc - // Sleep to guarantee deadline has been hit. - time.Sleep(20 * time.Microsecond) - _, err := sc.getPubKey() assert.Equal(t, err.(cmn.Error).Data(), ErrConnTimeout) } -func TestSocketPVWait(t *testing.T) { - sc := NewSocketPV( - log.TestingLogger(), - "127.0.0.1:0", - ed25519.GenPrivKey(), - ) - defer sc.Stop() - - SocketPVConnWait(time.Millisecond)(sc) - - assert.Equal(t, sc.Start().(cmn.Error).Data(), ErrConnWaitTimeout) -} - func TestRemoteSignerRetry(t *testing.T) { var ( attemptc = make(chan int) @@ -221,7 +235,7 @@ func TestRemoteSignerRetry(t *testing.T) { RemoteSignerConnDeadline(time.Millisecond)(rs) RemoteSignerConnRetries(retries)(rs) - assert.Equal(t, rs.Start().(cmn.Error).Data(), ErrDialRetryMax) + assert.Equal(t, rs.Start(), ErrDialRetryMax) select { case attempts := <-attemptc: @@ -328,7 +342,7 @@ func TestErrUnexpectedResponse(t *testing.T) { types.NewMockPV(), ed25519.GenPrivKey(), ) - sc = NewSocketPV( + sc = NewTCPVal( logger, addr, ed25519.GenPrivKey(), @@ -383,7 +397,7 @@ func testSetupSocketPair( t *testing.T, chainID string, privValidator types.PrivValidator, -) (*SocketPV, *RemoteSigner) { +) (*TCPVal, *RemoteSigner) { var ( addr = testFreeAddr(t) logger = log.TestingLogger() @@ -396,18 +410,20 @@ func testSetupSocketPair( privVal, ed25519.GenPrivKey(), ) - sc = NewSocketPV( + sc = NewTCPVal( logger, addr, ed25519.GenPrivKey(), ) ) - testStartSocketPV(t, readyc, sc) - - RemoteSignerConnDeadline(time.Millisecond)(rs) + TCPValConnTimeout(5 * time.Millisecond)(sc) + TCPValHeartbeat(2 * time.Millisecond)(sc) + RemoteSignerConnDeadline(5 * time.Millisecond)(rs) RemoteSignerConnRetries(1e6)(rs) + testStartSocketPV(t, readyc, sc) + require.NoError(t, rs.Start()) assert.True(t, rs.IsRunning()) @@ -416,7 +432,7 @@ func testSetupSocketPair( return sc, rs } -func testReadWriteResponse(t *testing.T, resp SocketPVMsg, rsConn net.Conn) { +func testReadWriteResponse(t *testing.T, resp RemoteSignerMsg, rsConn net.Conn) { _, err := readMsg(rsConn) require.NoError(t, err) @@ -424,8 +440,8 @@ func testReadWriteResponse(t *testing.T, resp SocketPVMsg, rsConn net.Conn) { require.NoError(t, err) } -func testStartSocketPV(t *testing.T, readyc chan struct{}, sc *SocketPV) { - go func(sc *SocketPV) { +func testStartSocketPV(t *testing.T, readyc chan struct{}, sc *TCPVal) { + go func(sc *TCPVal) { require.NoError(t, sc.Start()) assert.True(t, sc.IsRunning()) diff --git a/privval/wire.go b/privval/wire.go index 50660ff3..2e11677e 100644 --- a/privval/wire.go +++ b/privval/wire.go @@ -9,5 +9,5 @@ var cdc = amino.NewCodec() func init() { cryptoAmino.RegisterAmino(cdc) - RegisterSocketPVMsg(cdc) + RegisterRemoteSignerMsg(cdc) } From d20693fb16ad79c8313f6e590d187c9fb22f0d92 Mon Sep 17 00:00:00 2001 From: Anton Kaliaev Date: Wed, 17 Oct 2018 20:47:56 +0400 Subject: [PATCH 018/267] install scripts: update go version, remove upgrade cmd (#2650) * [install scripts] update go version, remove upgrade - upgrading OS is out of scope of installation scripts * add missing log statement Refs https://github.com/tendermint/tendermint/pull/2642#discussion_r225786794 --- consensus/state_test.go | 2 ++ scripts/install/install_tendermint_bsd.sh | 11 +++++------ scripts/install/install_tendermint_ubuntu.sh | 10 ++++------ 3 files changed, 11 insertions(+), 12 deletions(-) diff --git a/consensus/state_test.go b/consensus/state_test.go index c4fc11c3..83c4bb14 100644 --- a/consensus/state_test.go +++ b/consensus/state_test.go @@ -949,6 +949,8 @@ func TestProposeValidBlock(t *testing.T) { round = round + 2 // moving to the next round ensureNewRound(newRoundCh, height, round) + t.Log("### ONTO ROUND 3") + ensureNewTimeout(timeoutWaitCh, height, round, cs1.config.TimeoutPrecommit.Nanoseconds()) round = round + 1 // moving to the next round diff --git a/scripts/install/install_tendermint_bsd.sh b/scripts/install/install_tendermint_bsd.sh index aba584f2..5b30eab3 100644 --- a/scripts/install/install_tendermint_bsd.sh +++ b/scripts/install/install_tendermint_bsd.sh @@ -17,16 +17,15 @@ set BRANCH=master sudo pkg update -sudo pkg upgrade -y sudo pkg install -y gmake sudo pkg install -y git # get and unpack golang -curl -O https://storage.googleapis.com/golang/go1.10.freebsd-amd64.tar.gz -tar -xvf go1.10.freebsd-amd64.tar.gz +curl -O https://storage.googleapis.com/golang/go1.11.freebsd-amd64.tar.gz +tar -xvf go1.11.freebsd-amd64.tar.gz -# move go binary and add to path -mv go /usr/local +# move go folder and add go binary to path +sudo mv go /usr/local set path=($path /usr/local/go/bin) @@ -41,7 +40,7 @@ source ~/.tcshrc # get the code and move into repo set REPO=github.com/tendermint/tendermint go get $REPO -cd $GOPATH/src/$REPO +cd "$GOPATH/src/$REPO" # build & install master git checkout $BRANCH diff --git a/scripts/install/install_tendermint_ubuntu.sh b/scripts/install/install_tendermint_ubuntu.sh index b9605de0..29e97508 100644 --- a/scripts/install/install_tendermint_ubuntu.sh +++ b/scripts/install/install_tendermint_ubuntu.sh @@ -14,14 +14,13 @@ REPO=github.com/tendermint/tendermint BRANCH=master sudo apt-get update -y -sudo apt-get upgrade -y sudo apt-get install -y make # get and unpack golang -curl -O https://storage.googleapis.com/golang/go1.10.linux-amd64.tar.gz -tar -xvf go1.10.linux-amd64.tar.gz +curl -O https://storage.googleapis.com/golang/go1.11.linux-amd64.tar.gz +tar -xvf go1.11.linux-amd64.tar.gz -# move go binary and add to path +# move go folder and add go binary to path sudo mv go /usr/local echo "export PATH=\$PATH:/usr/local/go/bin" >> ~/.profile @@ -29,12 +28,11 @@ echo "export PATH=\$PATH:/usr/local/go/bin" >> ~/.profile mkdir goApps echo "export GOPATH=$HOME/goApps" >> ~/.profile echo "export PATH=\$PATH:\$GOPATH/bin" >> ~/.profile - source ~/.profile # get the code and move into repo go get $REPO -cd $GOPATH/src/$REPO +cd "$GOPATH/src/$REPO" # build & install git checkout $BRANCH From 6a07f415e96b6311a1e1626a888cfecab47d43b5 Mon Sep 17 00:00:00 2001 From: Jack Zampolin Date: Wed, 17 Oct 2018 11:26:14 -0700 Subject: [PATCH 019/267] Change error output format for better SDK and Voyager UX (#2648) * Change error output format * Update tests * :facepalm: * apply suggestion --- libs/common/errors.go | 2 +- libs/common/errors_test.go | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/libs/common/errors.go b/libs/common/errors.go index 1dc909e8..10e40ebd 100644 --- a/libs/common/errors.go +++ b/libs/common/errors.go @@ -146,7 +146,7 @@ func (err *cmnError) Format(s fmt.State, verb rune) { s.Write([]byte("--= /Error =--\n")) } else { // Write msg. - s.Write([]byte(fmt.Sprintf("Error{%v}", err.data))) // TODO tick-esc? + s.Write([]byte(fmt.Sprintf("%v", err.data))) } } } diff --git a/libs/common/errors_test.go b/libs/common/errors_test.go index 52c78a76..326468c9 100644 --- a/libs/common/errors_test.go +++ b/libs/common/errors_test.go @@ -24,7 +24,7 @@ func TestErrorPanic(t *testing.T) { var err = capturePanic() assert.Equal(t, pnk{"something"}, err.Data()) - assert.Equal(t, "Error{{something}}", fmt.Sprintf("%v", err)) +assert.Equal(t, "{something}", fmt.Sprintf("%v", err)) assert.Contains(t, fmt.Sprintf("%#v", err), "This is the message in ErrorWrap(r, message).") assert.Contains(t, fmt.Sprintf("%#v", err), "Stack Trace:\n 0") } @@ -34,7 +34,7 @@ func TestErrorWrapSomething(t *testing.T) { var err = ErrorWrap("something", "formatter%v%v", 0, 1) assert.Equal(t, "something", err.Data()) - assert.Equal(t, "Error{something}", fmt.Sprintf("%v", err)) + assert.Equal(t, "something", fmt.Sprintf("%v", err)) assert.Regexp(t, `formatter01\n`, fmt.Sprintf("%#v", err)) assert.Contains(t, fmt.Sprintf("%#v", err), "Stack Trace:\n 0") } @@ -46,7 +46,7 @@ func TestErrorWrapNothing(t *testing.T) { assert.Equal(t, FmtError{"formatter%v%v", []interface{}{0, 1}}, err.Data()) - assert.Equal(t, "Error{formatter01}", fmt.Sprintf("%v", err)) + assert.Equal(t, "formatter01", fmt.Sprintf("%v", err)) assert.Contains(t, fmt.Sprintf("%#v", err), `Data: common.FmtError{format:"formatter%v%v", args:[]interface {}{0, 1}}`) assert.Contains(t, fmt.Sprintf("%#v", err), "Stack Trace:\n 0") } @@ -58,7 +58,7 @@ func TestErrorNewError(t *testing.T) { assert.Equal(t, FmtError{"formatter%v%v", []interface{}{0, 1}}, err.Data()) - assert.Equal(t, "Error{formatter01}", fmt.Sprintf("%v", err)) + assert.Equal(t, "formatter01", fmt.Sprintf("%v", err)) assert.Contains(t, fmt.Sprintf("%#v", err), `Data: common.FmtError{format:"formatter%v%v", args:[]interface {}{0, 1}}`) assert.NotContains(t, fmt.Sprintf("%#v", err), "Stack Trace") } @@ -70,7 +70,7 @@ func TestErrorNewErrorWithStacktrace(t *testing.T) { assert.Equal(t, FmtError{"formatter%v%v", []interface{}{0, 1}}, err.Data()) - assert.Equal(t, "Error{formatter01}", fmt.Sprintf("%v", err)) + assert.Equal(t, "formatter01", fmt.Sprintf("%v", err)) assert.Contains(t, fmt.Sprintf("%#v", err), `Data: common.FmtError{format:"formatter%v%v", args:[]interface {}{0, 1}}`) assert.Contains(t, fmt.Sprintf("%#v", err), "Stack Trace:\n 0") } @@ -85,7 +85,7 @@ func TestErrorNewErrorWithTrace(t *testing.T) { assert.Equal(t, FmtError{"formatter%v%v", []interface{}{0, 1}}, err.Data()) - assert.Equal(t, "Error{formatter01}", fmt.Sprintf("%v", err)) + assert.Equal(t, "formatter01", fmt.Sprintf("%v", err)) assert.Contains(t, fmt.Sprintf("%#v", err), `Data: common.FmtError{format:"formatter%v%v", args:[]interface {}{0, 1}}`) dump := fmt.Sprintf("%#v", err) assert.NotContains(t, dump, "Stack Trace") From 455d34134cc53c334ebd3195ac22ea444c4b59bb Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Wed, 17 Oct 2018 15:30:53 -0400 Subject: [PATCH 020/267] ADR-016: Add versions to Block and State (#2644) * types: add Version to Header * abci: add Version to Header * state: add Version to State * node: check software and state protocol versions match * update changelog * docs/spec: update for versions * state: more tests * remove TODOs * remove empty test --- CHANGELOG_PENDING.md | 8 +- abci/types/types.pb.go | 897 +++++++++++++++++++---------- abci/types/types.proto | 37 +- abci/types/typespb_test.go | 124 ++++ docs/spec/abci/abci.md | 10 + docs/spec/blockchain/blockchain.md | 24 + docs/spec/blockchain/state.md | 1 + node/node.go | 9 + state/execution.go | 4 + state/state.go | 26 +- state/state_test.go | 4 +- state/validation.go | 7 + state/validation_test.go | 8 + types/block.go | 20 +- types/block_test.go | 14 +- types/proto3/block.pb.go | 274 ++++----- types/proto3/block.proto | 36 +- 17 files changed, 999 insertions(+), 504 deletions(-) diff --git a/CHANGELOG_PENDING.md b/CHANGELOG_PENDING.md index 05369ea6..5fcbbb7b 100644 --- a/CHANGELOG_PENDING.md +++ b/CHANGELOG_PENDING.md @@ -12,10 +12,13 @@ BREAKING CHANGES: * [rpc] \#2298 `/abci_query` takes `prove` argument instead of `trusted` and switches the default behaviour to `prove=false` * [privval] \#2459 Split `SocketPVMsg`s implementations into Request and Response, where the Response may contain a error message (returned by the remote signer) + * [state] \#2644 Add Version field to State, breaking the format of State as + encoded on disk. * Apps * [abci] \#2298 ResponseQuery.Proof is now a structured merkle.Proof, not just arbitrary bytes + * [abci] \#2644 Add Version to Header and shift all fields by one * Go API * [node] Remove node.RunForever @@ -25,7 +28,8 @@ BREAKING CHANGES: * [crypto/merkle & lite] \#2298 Various changes to accomodate General Merkle trees * [crypto/merkle] \#2595 Remove all Hasher objects in favor of byte slices * [crypto/merkle] \#2635 merkle.SimpleHashFromTwoHashes is no longer exported - * [types] \#2598 `VoteTypeXxx` are now + * [types] \#2598 `VoteTypeXxx` are now of type `SignedMsgType byte` and named `XxxType`, eg. `PrevoteType`, + `PrecommitType`. * Blockchain Protocol * [types] Update SignBytes for `Vote`/`Proposal`/`Heartbeat`: @@ -34,7 +38,9 @@ BREAKING CHANGES: * \#2598 Change `Type` field fromt `string` to `byte` and use new `SignedMsgType` to enumerate. * [types] \#2512 Remove the pubkey field from the validator hash + * [types] \#2644 Add Version struct to Header * [state] \#2587 Require block.Time of the fist block to be genesis time + * [state] \#2644 Require block.Version to match state.Version * P2P Protocol diff --git a/abci/types/types.pb.go b/abci/types/types.pb.go index 1ec51602..81fb74b4 100644 --- a/abci/types/types.pb.go +++ b/abci/types/types.pb.go @@ -61,7 +61,7 @@ func (m *Request) Reset() { *m = Request{} } func (m *Request) String() string { return proto.CompactTextString(m) } func (*Request) ProtoMessage() {} func (*Request) Descriptor() ([]byte, []int) { - return fileDescriptor_types_4a7ab597ee120b05, []int{0} + return fileDescriptor_types_07d64ea985a686e2, []int{0} } func (m *Request) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -483,7 +483,7 @@ func (m *RequestEcho) Reset() { *m = RequestEcho{} } func (m *RequestEcho) String() string { return proto.CompactTextString(m) } func (*RequestEcho) ProtoMessage() {} func (*RequestEcho) Descriptor() ([]byte, []int) { - return fileDescriptor_types_4a7ab597ee120b05, []int{1} + return fileDescriptor_types_07d64ea985a686e2, []int{1} } func (m *RequestEcho) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -529,7 +529,7 @@ func (m *RequestFlush) Reset() { *m = RequestFlush{} } func (m *RequestFlush) String() string { return proto.CompactTextString(m) } func (*RequestFlush) ProtoMessage() {} func (*RequestFlush) Descriptor() ([]byte, []int) { - return fileDescriptor_types_4a7ab597ee120b05, []int{2} + return fileDescriptor_types_07d64ea985a686e2, []int{2} } func (m *RequestFlush) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -569,7 +569,7 @@ func (m *RequestInfo) Reset() { *m = RequestInfo{} } func (m *RequestInfo) String() string { return proto.CompactTextString(m) } func (*RequestInfo) ProtoMessage() {} func (*RequestInfo) Descriptor() ([]byte, []int) { - return fileDescriptor_types_4a7ab597ee120b05, []int{3} + return fileDescriptor_types_07d64ea985a686e2, []int{3} } func (m *RequestInfo) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -618,7 +618,7 @@ func (m *RequestSetOption) Reset() { *m = RequestSetOption{} } func (m *RequestSetOption) String() string { return proto.CompactTextString(m) } func (*RequestSetOption) ProtoMessage() {} func (*RequestSetOption) Descriptor() ([]byte, []int) { - return fileDescriptor_types_4a7ab597ee120b05, []int{4} + return fileDescriptor_types_07d64ea985a686e2, []int{4} } func (m *RequestSetOption) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -676,7 +676,7 @@ func (m *RequestInitChain) Reset() { *m = RequestInitChain{} } func (m *RequestInitChain) String() string { return proto.CompactTextString(m) } func (*RequestInitChain) ProtoMessage() {} func (*RequestInitChain) Descriptor() ([]byte, []int) { - return fileDescriptor_types_4a7ab597ee120b05, []int{5} + return fileDescriptor_types_07d64ea985a686e2, []int{5} } func (m *RequestInitChain) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -754,7 +754,7 @@ func (m *RequestQuery) Reset() { *m = RequestQuery{} } func (m *RequestQuery) String() string { return proto.CompactTextString(m) } func (*RequestQuery) ProtoMessage() {} func (*RequestQuery) Descriptor() ([]byte, []int) { - return fileDescriptor_types_4a7ab597ee120b05, []int{6} + return fileDescriptor_types_07d64ea985a686e2, []int{6} } func (m *RequestQuery) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -826,7 +826,7 @@ func (m *RequestBeginBlock) Reset() { *m = RequestBeginBlock{} } func (m *RequestBeginBlock) String() string { return proto.CompactTextString(m) } func (*RequestBeginBlock) ProtoMessage() {} func (*RequestBeginBlock) Descriptor() ([]byte, []int) { - return fileDescriptor_types_4a7ab597ee120b05, []int{7} + return fileDescriptor_types_07d64ea985a686e2, []int{7} } func (m *RequestBeginBlock) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -894,7 +894,7 @@ func (m *RequestCheckTx) Reset() { *m = RequestCheckTx{} } func (m *RequestCheckTx) String() string { return proto.CompactTextString(m) } func (*RequestCheckTx) ProtoMessage() {} func (*RequestCheckTx) Descriptor() ([]byte, []int) { - return fileDescriptor_types_4a7ab597ee120b05, []int{8} + return fileDescriptor_types_07d64ea985a686e2, []int{8} } func (m *RequestCheckTx) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -941,7 +941,7 @@ func (m *RequestDeliverTx) Reset() { *m = RequestDeliverTx{} } func (m *RequestDeliverTx) String() string { return proto.CompactTextString(m) } func (*RequestDeliverTx) ProtoMessage() {} func (*RequestDeliverTx) Descriptor() ([]byte, []int) { - return fileDescriptor_types_4a7ab597ee120b05, []int{9} + return fileDescriptor_types_07d64ea985a686e2, []int{9} } func (m *RequestDeliverTx) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -988,7 +988,7 @@ func (m *RequestEndBlock) Reset() { *m = RequestEndBlock{} } func (m *RequestEndBlock) String() string { return proto.CompactTextString(m) } func (*RequestEndBlock) ProtoMessage() {} func (*RequestEndBlock) Descriptor() ([]byte, []int) { - return fileDescriptor_types_4a7ab597ee120b05, []int{10} + return fileDescriptor_types_07d64ea985a686e2, []int{10} } func (m *RequestEndBlock) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -1034,7 +1034,7 @@ func (m *RequestCommit) Reset() { *m = RequestCommit{} } func (m *RequestCommit) String() string { return proto.CompactTextString(m) } func (*RequestCommit) ProtoMessage() {} func (*RequestCommit) Descriptor() ([]byte, []int) { - return fileDescriptor_types_4a7ab597ee120b05, []int{11} + return fileDescriptor_types_07d64ea985a686e2, []int{11} } func (m *RequestCommit) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -1087,7 +1087,7 @@ func (m *Response) Reset() { *m = Response{} } func (m *Response) String() string { return proto.CompactTextString(m) } func (*Response) ProtoMessage() {} func (*Response) Descriptor() ([]byte, []int) { - return fileDescriptor_types_4a7ab597ee120b05, []int{12} + return fileDescriptor_types_07d64ea985a686e2, []int{12} } func (m *Response) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -1540,7 +1540,7 @@ func (m *ResponseException) Reset() { *m = ResponseException{} } func (m *ResponseException) String() string { return proto.CompactTextString(m) } func (*ResponseException) ProtoMessage() {} func (*ResponseException) Descriptor() ([]byte, []int) { - return fileDescriptor_types_4a7ab597ee120b05, []int{13} + return fileDescriptor_types_07d64ea985a686e2, []int{13} } func (m *ResponseException) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -1587,7 +1587,7 @@ func (m *ResponseEcho) Reset() { *m = ResponseEcho{} } func (m *ResponseEcho) String() string { return proto.CompactTextString(m) } func (*ResponseEcho) ProtoMessage() {} func (*ResponseEcho) Descriptor() ([]byte, []int) { - return fileDescriptor_types_4a7ab597ee120b05, []int{14} + return fileDescriptor_types_07d64ea985a686e2, []int{14} } func (m *ResponseEcho) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -1633,7 +1633,7 @@ func (m *ResponseFlush) Reset() { *m = ResponseFlush{} } func (m *ResponseFlush) String() string { return proto.CompactTextString(m) } func (*ResponseFlush) ProtoMessage() {} func (*ResponseFlush) Descriptor() ([]byte, []int) { - return fileDescriptor_types_4a7ab597ee120b05, []int{15} + return fileDescriptor_types_07d64ea985a686e2, []int{15} } func (m *ResponseFlush) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -1676,7 +1676,7 @@ func (m *ResponseInfo) Reset() { *m = ResponseInfo{} } func (m *ResponseInfo) String() string { return proto.CompactTextString(m) } func (*ResponseInfo) ProtoMessage() {} func (*ResponseInfo) Descriptor() ([]byte, []int) { - return fileDescriptor_types_4a7ab597ee120b05, []int{16} + return fileDescriptor_types_07d64ea985a686e2, []int{16} } func (m *ResponseInfo) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -1748,7 +1748,7 @@ func (m *ResponseSetOption) Reset() { *m = ResponseSetOption{} } func (m *ResponseSetOption) String() string { return proto.CompactTextString(m) } func (*ResponseSetOption) ProtoMessage() {} func (*ResponseSetOption) Descriptor() ([]byte, []int) { - return fileDescriptor_types_4a7ab597ee120b05, []int{17} + return fileDescriptor_types_07d64ea985a686e2, []int{17} } func (m *ResponseSetOption) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -1810,7 +1810,7 @@ func (m *ResponseInitChain) Reset() { *m = ResponseInitChain{} } func (m *ResponseInitChain) String() string { return proto.CompactTextString(m) } func (*ResponseInitChain) ProtoMessage() {} func (*ResponseInitChain) Descriptor() ([]byte, []int) { - return fileDescriptor_types_4a7ab597ee120b05, []int{18} + return fileDescriptor_types_07d64ea985a686e2, []int{18} } func (m *ResponseInitChain) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -1873,7 +1873,7 @@ func (m *ResponseQuery) Reset() { *m = ResponseQuery{} } func (m *ResponseQuery) String() string { return proto.CompactTextString(m) } func (*ResponseQuery) ProtoMessage() {} func (*ResponseQuery) Descriptor() ([]byte, []int) { - return fileDescriptor_types_4a7ab597ee120b05, []int{19} + return fileDescriptor_types_07d64ea985a686e2, []int{19} } func (m *ResponseQuery) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -1976,7 +1976,7 @@ func (m *ResponseBeginBlock) Reset() { *m = ResponseBeginBlock{} } func (m *ResponseBeginBlock) String() string { return proto.CompactTextString(m) } func (*ResponseBeginBlock) ProtoMessage() {} func (*ResponseBeginBlock) Descriptor() ([]byte, []int) { - return fileDescriptor_types_4a7ab597ee120b05, []int{20} + return fileDescriptor_types_07d64ea985a686e2, []int{20} } func (m *ResponseBeginBlock) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -2030,7 +2030,7 @@ func (m *ResponseCheckTx) Reset() { *m = ResponseCheckTx{} } func (m *ResponseCheckTx) String() string { return proto.CompactTextString(m) } func (*ResponseCheckTx) ProtoMessage() {} func (*ResponseCheckTx) Descriptor() ([]byte, []int) { - return fileDescriptor_types_4a7ab597ee120b05, []int{21} + return fileDescriptor_types_07d64ea985a686e2, []int{21} } func (m *ResponseCheckTx) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -2133,7 +2133,7 @@ func (m *ResponseDeliverTx) Reset() { *m = ResponseDeliverTx{} } func (m *ResponseDeliverTx) String() string { return proto.CompactTextString(m) } func (*ResponseDeliverTx) ProtoMessage() {} func (*ResponseDeliverTx) Descriptor() ([]byte, []int) { - return fileDescriptor_types_4a7ab597ee120b05, []int{22} + return fileDescriptor_types_07d64ea985a686e2, []int{22} } func (m *ResponseDeliverTx) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -2231,7 +2231,7 @@ func (m *ResponseEndBlock) Reset() { *m = ResponseEndBlock{} } func (m *ResponseEndBlock) String() string { return proto.CompactTextString(m) } func (*ResponseEndBlock) ProtoMessage() {} func (*ResponseEndBlock) Descriptor() ([]byte, []int) { - return fileDescriptor_types_4a7ab597ee120b05, []int{23} + return fileDescriptor_types_07d64ea985a686e2, []int{23} } func (m *ResponseEndBlock) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -2293,7 +2293,7 @@ func (m *ResponseCommit) Reset() { *m = ResponseCommit{} } func (m *ResponseCommit) String() string { return proto.CompactTextString(m) } func (*ResponseCommit) ProtoMessage() {} func (*ResponseCommit) Descriptor() ([]byte, []int) { - return fileDescriptor_types_4a7ab597ee120b05, []int{24} + return fileDescriptor_types_07d64ea985a686e2, []int{24} } func (m *ResponseCommit) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -2343,7 +2343,7 @@ func (m *ConsensusParams) Reset() { *m = ConsensusParams{} } func (m *ConsensusParams) String() string { return proto.CompactTextString(m) } func (*ConsensusParams) ProtoMessage() {} func (*ConsensusParams) Descriptor() ([]byte, []int) { - return fileDescriptor_types_4a7ab597ee120b05, []int{25} + return fileDescriptor_types_07d64ea985a686e2, []int{25} } func (m *ConsensusParams) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -2401,7 +2401,7 @@ func (m *BlockSize) Reset() { *m = BlockSize{} } func (m *BlockSize) String() string { return proto.CompactTextString(m) } func (*BlockSize) ProtoMessage() {} func (*BlockSize) Descriptor() ([]byte, []int) { - return fileDescriptor_types_4a7ab597ee120b05, []int{26} + return fileDescriptor_types_07d64ea985a686e2, []int{26} } func (m *BlockSize) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -2457,7 +2457,7 @@ func (m *EvidenceParams) Reset() { *m = EvidenceParams{} } func (m *EvidenceParams) String() string { return proto.CompactTextString(m) } func (*EvidenceParams) ProtoMessage() {} func (*EvidenceParams) Descriptor() ([]byte, []int) { - return fileDescriptor_types_4a7ab597ee120b05, []int{27} + return fileDescriptor_types_07d64ea985a686e2, []int{27} } func (m *EvidenceParams) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -2505,7 +2505,7 @@ func (m *LastCommitInfo) Reset() { *m = LastCommitInfo{} } func (m *LastCommitInfo) String() string { return proto.CompactTextString(m) } func (*LastCommitInfo) ProtoMessage() {} func (*LastCommitInfo) Descriptor() ([]byte, []int) { - return fileDescriptor_types_4a7ab597ee120b05, []int{28} + return fileDescriptor_types_07d64ea985a686e2, []int{28} } func (m *LastCommitInfo) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -2550,25 +2550,26 @@ func (m *LastCommitInfo) GetVotes() []VoteInfo { type Header struct { // basic block info - ChainID string `protobuf:"bytes,1,opt,name=chain_id,json=chainId,proto3" json:"chain_id,omitempty"` - Height int64 `protobuf:"varint,2,opt,name=height,proto3" json:"height,omitempty"` - Time time.Time `protobuf:"bytes,3,opt,name=time,stdtime" json:"time"` - NumTxs int64 `protobuf:"varint,4,opt,name=num_txs,json=numTxs,proto3" json:"num_txs,omitempty"` - TotalTxs int64 `protobuf:"varint,5,opt,name=total_txs,json=totalTxs,proto3" json:"total_txs,omitempty"` + Version Version `protobuf:"bytes,1,opt,name=version" json:"version"` + ChainID string `protobuf:"bytes,2,opt,name=chain_id,json=chainId,proto3" json:"chain_id,omitempty"` + Height int64 `protobuf:"varint,3,opt,name=height,proto3" json:"height,omitempty"` + Time time.Time `protobuf:"bytes,4,opt,name=time,stdtime" json:"time"` + NumTxs int64 `protobuf:"varint,5,opt,name=num_txs,json=numTxs,proto3" json:"num_txs,omitempty"` + TotalTxs int64 `protobuf:"varint,6,opt,name=total_txs,json=totalTxs,proto3" json:"total_txs,omitempty"` // prev block info - LastBlockId BlockID `protobuf:"bytes,6,opt,name=last_block_id,json=lastBlockId" json:"last_block_id"` + LastBlockId BlockID `protobuf:"bytes,7,opt,name=last_block_id,json=lastBlockId" json:"last_block_id"` // hashes of block data - LastCommitHash []byte `protobuf:"bytes,7,opt,name=last_commit_hash,json=lastCommitHash,proto3" json:"last_commit_hash,omitempty"` - DataHash []byte `protobuf:"bytes,8,opt,name=data_hash,json=dataHash,proto3" json:"data_hash,omitempty"` + LastCommitHash []byte `protobuf:"bytes,8,opt,name=last_commit_hash,json=lastCommitHash,proto3" json:"last_commit_hash,omitempty"` + DataHash []byte `protobuf:"bytes,9,opt,name=data_hash,json=dataHash,proto3" json:"data_hash,omitempty"` // hashes from the app output from the prev block - ValidatorsHash []byte `protobuf:"bytes,9,opt,name=validators_hash,json=validatorsHash,proto3" json:"validators_hash,omitempty"` - NextValidatorsHash []byte `protobuf:"bytes,10,opt,name=next_validators_hash,json=nextValidatorsHash,proto3" json:"next_validators_hash,omitempty"` - ConsensusHash []byte `protobuf:"bytes,11,opt,name=consensus_hash,json=consensusHash,proto3" json:"consensus_hash,omitempty"` - AppHash []byte `protobuf:"bytes,12,opt,name=app_hash,json=appHash,proto3" json:"app_hash,omitempty"` - LastResultsHash []byte `protobuf:"bytes,13,opt,name=last_results_hash,json=lastResultsHash,proto3" json:"last_results_hash,omitempty"` + ValidatorsHash []byte `protobuf:"bytes,10,opt,name=validators_hash,json=validatorsHash,proto3" json:"validators_hash,omitempty"` + NextValidatorsHash []byte `protobuf:"bytes,11,opt,name=next_validators_hash,json=nextValidatorsHash,proto3" json:"next_validators_hash,omitempty"` + ConsensusHash []byte `protobuf:"bytes,12,opt,name=consensus_hash,json=consensusHash,proto3" json:"consensus_hash,omitempty"` + AppHash []byte `protobuf:"bytes,13,opt,name=app_hash,json=appHash,proto3" json:"app_hash,omitempty"` + LastResultsHash []byte `protobuf:"bytes,14,opt,name=last_results_hash,json=lastResultsHash,proto3" json:"last_results_hash,omitempty"` // consensus info - EvidenceHash []byte `protobuf:"bytes,14,opt,name=evidence_hash,json=evidenceHash,proto3" json:"evidence_hash,omitempty"` - ProposerAddress []byte `protobuf:"bytes,15,opt,name=proposer_address,json=proposerAddress,proto3" json:"proposer_address,omitempty"` + EvidenceHash []byte `protobuf:"bytes,15,opt,name=evidence_hash,json=evidenceHash,proto3" json:"evidence_hash,omitempty"` + ProposerAddress []byte `protobuf:"bytes,16,opt,name=proposer_address,json=proposerAddress,proto3" json:"proposer_address,omitempty"` XXX_NoUnkeyedLiteral struct{} `json:"-"` XXX_unrecognized []byte `json:"-"` XXX_sizecache int32 `json:"-"` @@ -2578,7 +2579,7 @@ func (m *Header) Reset() { *m = Header{} } func (m *Header) String() string { return proto.CompactTextString(m) } func (*Header) ProtoMessage() {} func (*Header) Descriptor() ([]byte, []int) { - return fileDescriptor_types_4a7ab597ee120b05, []int{29} + return fileDescriptor_types_07d64ea985a686e2, []int{29} } func (m *Header) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -2607,6 +2608,13 @@ func (m *Header) XXX_DiscardUnknown() { var xxx_messageInfo_Header proto.InternalMessageInfo +func (m *Header) GetVersion() Version { + if m != nil { + return m.Version + } + return Version{} +} + func (m *Header) GetChainID() string { if m != nil { return m.ChainID @@ -2712,6 +2720,61 @@ func (m *Header) GetProposerAddress() []byte { return nil } +type Version struct { + Block uint64 `protobuf:"varint,1,opt,name=Block,proto3" json:"Block,omitempty"` + App uint64 `protobuf:"varint,2,opt,name=App,proto3" json:"App,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *Version) Reset() { *m = Version{} } +func (m *Version) String() string { return proto.CompactTextString(m) } +func (*Version) ProtoMessage() {} +func (*Version) Descriptor() ([]byte, []int) { + return fileDescriptor_types_07d64ea985a686e2, []int{30} +} +func (m *Version) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *Version) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_Version.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalTo(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (dst *Version) XXX_Merge(src proto.Message) { + xxx_messageInfo_Version.Merge(dst, src) +} +func (m *Version) XXX_Size() int { + return m.Size() +} +func (m *Version) XXX_DiscardUnknown() { + xxx_messageInfo_Version.DiscardUnknown(m) +} + +var xxx_messageInfo_Version proto.InternalMessageInfo + +func (m *Version) GetBlock() uint64 { + if m != nil { + return m.Block + } + return 0 +} + +func (m *Version) GetApp() uint64 { + if m != nil { + return m.App + } + return 0 +} + type BlockID struct { Hash []byte `protobuf:"bytes,1,opt,name=hash,proto3" json:"hash,omitempty"` PartsHeader PartSetHeader `protobuf:"bytes,2,opt,name=parts_header,json=partsHeader" json:"parts_header"` @@ -2724,7 +2787,7 @@ func (m *BlockID) Reset() { *m = BlockID{} } func (m *BlockID) String() string { return proto.CompactTextString(m) } func (*BlockID) ProtoMessage() {} func (*BlockID) Descriptor() ([]byte, []int) { - return fileDescriptor_types_4a7ab597ee120b05, []int{30} + return fileDescriptor_types_07d64ea985a686e2, []int{31} } func (m *BlockID) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -2779,7 +2842,7 @@ func (m *PartSetHeader) Reset() { *m = PartSetHeader{} } func (m *PartSetHeader) String() string { return proto.CompactTextString(m) } func (*PartSetHeader) ProtoMessage() {} func (*PartSetHeader) Descriptor() ([]byte, []int) { - return fileDescriptor_types_4a7ab597ee120b05, []int{31} + return fileDescriptor_types_07d64ea985a686e2, []int{32} } func (m *PartSetHeader) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -2836,7 +2899,7 @@ func (m *Validator) Reset() { *m = Validator{} } func (m *Validator) String() string { return proto.CompactTextString(m) } func (*Validator) ProtoMessage() {} func (*Validator) Descriptor() ([]byte, []int) { - return fileDescriptor_types_4a7ab597ee120b05, []int{32} + return fileDescriptor_types_07d64ea985a686e2, []int{33} } func (m *Validator) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -2892,7 +2955,7 @@ func (m *ValidatorUpdate) Reset() { *m = ValidatorUpdate{} } func (m *ValidatorUpdate) String() string { return proto.CompactTextString(m) } func (*ValidatorUpdate) ProtoMessage() {} func (*ValidatorUpdate) Descriptor() ([]byte, []int) { - return fileDescriptor_types_4a7ab597ee120b05, []int{33} + return fileDescriptor_types_07d64ea985a686e2, []int{34} } func (m *ValidatorUpdate) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -2948,7 +3011,7 @@ func (m *VoteInfo) Reset() { *m = VoteInfo{} } func (m *VoteInfo) String() string { return proto.CompactTextString(m) } func (*VoteInfo) ProtoMessage() {} func (*VoteInfo) Descriptor() ([]byte, []int) { - return fileDescriptor_types_4a7ab597ee120b05, []int{34} + return fileDescriptor_types_07d64ea985a686e2, []int{35} } func (m *VoteInfo) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -3003,7 +3066,7 @@ func (m *PubKey) Reset() { *m = PubKey{} } func (m *PubKey) String() string { return proto.CompactTextString(m) } func (*PubKey) ProtoMessage() {} func (*PubKey) Descriptor() ([]byte, []int) { - return fileDescriptor_types_4a7ab597ee120b05, []int{35} + return fileDescriptor_types_07d64ea985a686e2, []int{36} } func (m *PubKey) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -3061,7 +3124,7 @@ func (m *Evidence) Reset() { *m = Evidence{} } func (m *Evidence) String() string { return proto.CompactTextString(m) } func (*Evidence) ProtoMessage() {} func (*Evidence) Descriptor() ([]byte, []int) { - return fileDescriptor_types_4a7ab597ee120b05, []int{36} + return fileDescriptor_types_07d64ea985a686e2, []int{37} } func (m *Evidence) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -3186,6 +3249,8 @@ func init() { golang_proto.RegisterType((*LastCommitInfo)(nil), "types.LastCommitInfo") proto.RegisterType((*Header)(nil), "types.Header") golang_proto.RegisterType((*Header)(nil), "types.Header") + proto.RegisterType((*Version)(nil), "types.Version") + golang_proto.RegisterType((*Version)(nil), "types.Version") proto.RegisterType((*BlockID)(nil), "types.BlockID") golang_proto.RegisterType((*BlockID)(nil), "types.BlockID") proto.RegisterType((*PartSetHeader)(nil), "types.PartSetHeader") @@ -4735,6 +4800,9 @@ func (this *Header) Equal(that interface{}) bool { } else if this == nil { return false } + if !this.Version.Equal(&that1.Version) { + return false + } if this.ChainID != that1.ChainID { return false } @@ -4785,6 +4853,36 @@ func (this *Header) Equal(that interface{}) bool { } return true } +func (this *Version) Equal(that interface{}) bool { + if that == nil { + return this == nil + } + + that1, ok := that.(*Version) + if !ok { + that2, ok := that.(Version) + if ok { + that1 = &that2 + } else { + return false + } + } + if that1 == nil { + return this == nil + } else if this == nil { + return false + } + if this.Block != that1.Block { + return false + } + if this.App != that1.App { + return false + } + if !bytes.Equal(this.XXX_unrecognized, that1.XXX_unrecognized) { + return false + } + return true +} func (this *BlockID) Equal(that interface{}) bool { if that == nil { return this == nil @@ -6848,93 +6946,103 @@ func (m *Header) MarshalTo(dAtA []byte) (int, error) { _ = i var l int _ = l + dAtA[i] = 0xa + i++ + i = encodeVarintTypes(dAtA, i, uint64(m.Version.Size())) + n35, err := m.Version.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n35 if len(m.ChainID) > 0 { - dAtA[i] = 0xa + dAtA[i] = 0x12 i++ i = encodeVarintTypes(dAtA, i, uint64(len(m.ChainID))) i += copy(dAtA[i:], m.ChainID) } if m.Height != 0 { - dAtA[i] = 0x10 + dAtA[i] = 0x18 i++ i = encodeVarintTypes(dAtA, i, uint64(m.Height)) } - dAtA[i] = 0x1a + dAtA[i] = 0x22 i++ i = encodeVarintTypes(dAtA, i, uint64(github_com_gogo_protobuf_types.SizeOfStdTime(m.Time))) - n35, err := github_com_gogo_protobuf_types.StdTimeMarshalTo(m.Time, dAtA[i:]) - if err != nil { - return 0, err - } - i += n35 - if m.NumTxs != 0 { - dAtA[i] = 0x20 - i++ - i = encodeVarintTypes(dAtA, i, uint64(m.NumTxs)) - } - if m.TotalTxs != 0 { - dAtA[i] = 0x28 - i++ - i = encodeVarintTypes(dAtA, i, uint64(m.TotalTxs)) - } - dAtA[i] = 0x32 - i++ - i = encodeVarintTypes(dAtA, i, uint64(m.LastBlockId.Size())) - n36, err := m.LastBlockId.MarshalTo(dAtA[i:]) + n36, err := github_com_gogo_protobuf_types.StdTimeMarshalTo(m.Time, dAtA[i:]) if err != nil { return 0, err } i += n36 + if m.NumTxs != 0 { + dAtA[i] = 0x28 + i++ + i = encodeVarintTypes(dAtA, i, uint64(m.NumTxs)) + } + if m.TotalTxs != 0 { + dAtA[i] = 0x30 + i++ + i = encodeVarintTypes(dAtA, i, uint64(m.TotalTxs)) + } + dAtA[i] = 0x3a + i++ + i = encodeVarintTypes(dAtA, i, uint64(m.LastBlockId.Size())) + n37, err := m.LastBlockId.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n37 if len(m.LastCommitHash) > 0 { - dAtA[i] = 0x3a + dAtA[i] = 0x42 i++ i = encodeVarintTypes(dAtA, i, uint64(len(m.LastCommitHash))) i += copy(dAtA[i:], m.LastCommitHash) } if len(m.DataHash) > 0 { - dAtA[i] = 0x42 + dAtA[i] = 0x4a i++ i = encodeVarintTypes(dAtA, i, uint64(len(m.DataHash))) i += copy(dAtA[i:], m.DataHash) } if len(m.ValidatorsHash) > 0 { - dAtA[i] = 0x4a + dAtA[i] = 0x52 i++ i = encodeVarintTypes(dAtA, i, uint64(len(m.ValidatorsHash))) i += copy(dAtA[i:], m.ValidatorsHash) } if len(m.NextValidatorsHash) > 0 { - dAtA[i] = 0x52 + dAtA[i] = 0x5a i++ i = encodeVarintTypes(dAtA, i, uint64(len(m.NextValidatorsHash))) i += copy(dAtA[i:], m.NextValidatorsHash) } if len(m.ConsensusHash) > 0 { - dAtA[i] = 0x5a + dAtA[i] = 0x62 i++ i = encodeVarintTypes(dAtA, i, uint64(len(m.ConsensusHash))) i += copy(dAtA[i:], m.ConsensusHash) } if len(m.AppHash) > 0 { - dAtA[i] = 0x62 + dAtA[i] = 0x6a i++ i = encodeVarintTypes(dAtA, i, uint64(len(m.AppHash))) i += copy(dAtA[i:], m.AppHash) } if len(m.LastResultsHash) > 0 { - dAtA[i] = 0x6a + dAtA[i] = 0x72 i++ i = encodeVarintTypes(dAtA, i, uint64(len(m.LastResultsHash))) i += copy(dAtA[i:], m.LastResultsHash) } if len(m.EvidenceHash) > 0 { - dAtA[i] = 0x72 + dAtA[i] = 0x7a i++ i = encodeVarintTypes(dAtA, i, uint64(len(m.EvidenceHash))) i += copy(dAtA[i:], m.EvidenceHash) } if len(m.ProposerAddress) > 0 { - dAtA[i] = 0x7a + dAtA[i] = 0x82 + i++ + dAtA[i] = 0x1 i++ i = encodeVarintTypes(dAtA, i, uint64(len(m.ProposerAddress))) i += copy(dAtA[i:], m.ProposerAddress) @@ -6945,6 +7053,37 @@ func (m *Header) MarshalTo(dAtA []byte) (int, error) { return i, nil } +func (m *Version) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalTo(dAtA) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *Version) MarshalTo(dAtA []byte) (int, error) { + var i int + _ = i + var l int + _ = l + if m.Block != 0 { + dAtA[i] = 0x8 + i++ + i = encodeVarintTypes(dAtA, i, uint64(m.Block)) + } + if m.App != 0 { + dAtA[i] = 0x10 + i++ + i = encodeVarintTypes(dAtA, i, uint64(m.App)) + } + if m.XXX_unrecognized != nil { + i += copy(dAtA[i:], m.XXX_unrecognized) + } + return i, nil +} + func (m *BlockID) Marshal() (dAtA []byte, err error) { size := m.Size() dAtA = make([]byte, size) @@ -6969,11 +7108,11 @@ func (m *BlockID) MarshalTo(dAtA []byte) (int, error) { dAtA[i] = 0x12 i++ i = encodeVarintTypes(dAtA, i, uint64(m.PartsHeader.Size())) - n37, err := m.PartsHeader.MarshalTo(dAtA[i:]) + n38, err := m.PartsHeader.MarshalTo(dAtA[i:]) if err != nil { return 0, err } - i += n37 + i += n38 if m.XXX_unrecognized != nil { i += copy(dAtA[i:], m.XXX_unrecognized) } @@ -7062,11 +7201,11 @@ func (m *ValidatorUpdate) MarshalTo(dAtA []byte) (int, error) { dAtA[i] = 0xa i++ i = encodeVarintTypes(dAtA, i, uint64(m.PubKey.Size())) - n38, err := m.PubKey.MarshalTo(dAtA[i:]) + n39, err := m.PubKey.MarshalTo(dAtA[i:]) if err != nil { return 0, err } - i += n38 + i += n39 if m.Power != 0 { dAtA[i] = 0x10 i++ @@ -7096,11 +7235,11 @@ func (m *VoteInfo) MarshalTo(dAtA []byte) (int, error) { dAtA[i] = 0xa i++ i = encodeVarintTypes(dAtA, i, uint64(m.Validator.Size())) - n39, err := m.Validator.MarshalTo(dAtA[i:]) + n40, err := m.Validator.MarshalTo(dAtA[i:]) if err != nil { return 0, err } - i += n39 + i += n40 if m.SignedLastBlock { dAtA[i] = 0x10 i++ @@ -7174,11 +7313,11 @@ func (m *Evidence) MarshalTo(dAtA []byte) (int, error) { dAtA[i] = 0x12 i++ i = encodeVarintTypes(dAtA, i, uint64(m.Validator.Size())) - n40, err := m.Validator.MarshalTo(dAtA[i:]) + n41, err := m.Validator.MarshalTo(dAtA[i:]) if err != nil { return 0, err } - i += n40 + i += n41 if m.Height != 0 { dAtA[i] = 0x18 i++ @@ -7187,11 +7326,11 @@ func (m *Evidence) MarshalTo(dAtA []byte) (int, error) { dAtA[i] = 0x22 i++ i = encodeVarintTypes(dAtA, i, uint64(github_com_gogo_protobuf_types.SizeOfStdTime(m.Time))) - n41, err := github_com_gogo_protobuf_types.StdTimeMarshalTo(m.Time, dAtA[i:]) + n42, err := github_com_gogo_protobuf_types.StdTimeMarshalTo(m.Time, dAtA[i:]) if err != nil { return 0, err } - i += n41 + i += n42 if m.TotalVotingPower != 0 { dAtA[i] = 0x28 i++ @@ -7842,13 +7981,15 @@ func NewPopulatedLastCommitInfo(r randyTypes, easy bool) *LastCommitInfo { func NewPopulatedHeader(r randyTypes, easy bool) *Header { this := &Header{} + v33 := NewPopulatedVersion(r, easy) + this.Version = *v33 this.ChainID = string(randStringTypes(r)) this.Height = int64(r.Int63()) if r.Intn(2) == 0 { this.Height *= -1 } - v33 := github_com_gogo_protobuf_types.NewPopulatedStdTime(r, easy) - this.Time = *v33 + v34 := github_com_gogo_protobuf_types.NewPopulatedStdTime(r, easy) + this.Time = *v34 this.NumTxs = int64(r.Int63()) if r.Intn(2) == 0 { this.NumTxs *= -1 @@ -7857,68 +7998,78 @@ func NewPopulatedHeader(r randyTypes, easy bool) *Header { if r.Intn(2) == 0 { this.TotalTxs *= -1 } - v34 := NewPopulatedBlockID(r, easy) - this.LastBlockId = *v34 - v35 := r.Intn(100) - this.LastCommitHash = make([]byte, v35) - for i := 0; i < v35; i++ { + v35 := NewPopulatedBlockID(r, easy) + this.LastBlockId = *v35 + v36 := r.Intn(100) + this.LastCommitHash = make([]byte, v36) + for i := 0; i < v36; i++ { this.LastCommitHash[i] = byte(r.Intn(256)) } - v36 := r.Intn(100) - this.DataHash = make([]byte, v36) - for i := 0; i < v36; i++ { + v37 := r.Intn(100) + this.DataHash = make([]byte, v37) + for i := 0; i < v37; i++ { this.DataHash[i] = byte(r.Intn(256)) } - v37 := r.Intn(100) - this.ValidatorsHash = make([]byte, v37) - for i := 0; i < v37; i++ { + v38 := r.Intn(100) + this.ValidatorsHash = make([]byte, v38) + for i := 0; i < v38; i++ { this.ValidatorsHash[i] = byte(r.Intn(256)) } - v38 := r.Intn(100) - this.NextValidatorsHash = make([]byte, v38) - for i := 0; i < v38; i++ { + v39 := r.Intn(100) + this.NextValidatorsHash = make([]byte, v39) + for i := 0; i < v39; i++ { this.NextValidatorsHash[i] = byte(r.Intn(256)) } - v39 := r.Intn(100) - this.ConsensusHash = make([]byte, v39) - for i := 0; i < v39; i++ { + v40 := r.Intn(100) + this.ConsensusHash = make([]byte, v40) + for i := 0; i < v40; i++ { this.ConsensusHash[i] = byte(r.Intn(256)) } - v40 := r.Intn(100) - this.AppHash = make([]byte, v40) - for i := 0; i < v40; i++ { + v41 := r.Intn(100) + this.AppHash = make([]byte, v41) + for i := 0; i < v41; i++ { this.AppHash[i] = byte(r.Intn(256)) } - v41 := r.Intn(100) - this.LastResultsHash = make([]byte, v41) - for i := 0; i < v41; i++ { + v42 := r.Intn(100) + this.LastResultsHash = make([]byte, v42) + for i := 0; i < v42; i++ { this.LastResultsHash[i] = byte(r.Intn(256)) } - v42 := r.Intn(100) - this.EvidenceHash = make([]byte, v42) - for i := 0; i < v42; i++ { + v43 := r.Intn(100) + this.EvidenceHash = make([]byte, v43) + for i := 0; i < v43; i++ { this.EvidenceHash[i] = byte(r.Intn(256)) } - v43 := r.Intn(100) - this.ProposerAddress = make([]byte, v43) - for i := 0; i < v43; i++ { + v44 := r.Intn(100) + this.ProposerAddress = make([]byte, v44) + for i := 0; i < v44; i++ { this.ProposerAddress[i] = byte(r.Intn(256)) } if !easy && r.Intn(10) != 0 { - this.XXX_unrecognized = randUnrecognizedTypes(r, 16) + this.XXX_unrecognized = randUnrecognizedTypes(r, 17) + } + return this +} + +func NewPopulatedVersion(r randyTypes, easy bool) *Version { + this := &Version{} + this.Block = uint64(uint64(r.Uint32())) + this.App = uint64(uint64(r.Uint32())) + if !easy && r.Intn(10) != 0 { + this.XXX_unrecognized = randUnrecognizedTypes(r, 3) } return this } func NewPopulatedBlockID(r randyTypes, easy bool) *BlockID { this := &BlockID{} - v44 := r.Intn(100) - this.Hash = make([]byte, v44) - for i := 0; i < v44; i++ { + v45 := r.Intn(100) + this.Hash = make([]byte, v45) + for i := 0; i < v45; i++ { this.Hash[i] = byte(r.Intn(256)) } - v45 := NewPopulatedPartSetHeader(r, easy) - this.PartsHeader = *v45 + v46 := NewPopulatedPartSetHeader(r, easy) + this.PartsHeader = *v46 if !easy && r.Intn(10) != 0 { this.XXX_unrecognized = randUnrecognizedTypes(r, 3) } @@ -7931,9 +8082,9 @@ func NewPopulatedPartSetHeader(r randyTypes, easy bool) *PartSetHeader { if r.Intn(2) == 0 { this.Total *= -1 } - v46 := r.Intn(100) - this.Hash = make([]byte, v46) - for i := 0; i < v46; i++ { + v47 := r.Intn(100) + this.Hash = make([]byte, v47) + for i := 0; i < v47; i++ { this.Hash[i] = byte(r.Intn(256)) } if !easy && r.Intn(10) != 0 { @@ -7944,9 +8095,9 @@ func NewPopulatedPartSetHeader(r randyTypes, easy bool) *PartSetHeader { func NewPopulatedValidator(r randyTypes, easy bool) *Validator { this := &Validator{} - v47 := r.Intn(100) - this.Address = make([]byte, v47) - for i := 0; i < v47; i++ { + v48 := r.Intn(100) + this.Address = make([]byte, v48) + for i := 0; i < v48; i++ { this.Address[i] = byte(r.Intn(256)) } this.Power = int64(r.Int63()) @@ -7961,8 +8112,8 @@ func NewPopulatedValidator(r randyTypes, easy bool) *Validator { func NewPopulatedValidatorUpdate(r randyTypes, easy bool) *ValidatorUpdate { this := &ValidatorUpdate{} - v48 := NewPopulatedPubKey(r, easy) - this.PubKey = *v48 + v49 := NewPopulatedPubKey(r, easy) + this.PubKey = *v49 this.Power = int64(r.Int63()) if r.Intn(2) == 0 { this.Power *= -1 @@ -7975,8 +8126,8 @@ func NewPopulatedValidatorUpdate(r randyTypes, easy bool) *ValidatorUpdate { func NewPopulatedVoteInfo(r randyTypes, easy bool) *VoteInfo { this := &VoteInfo{} - v49 := NewPopulatedValidator(r, easy) - this.Validator = *v49 + v50 := NewPopulatedValidator(r, easy) + this.Validator = *v50 this.SignedLastBlock = bool(bool(r.Intn(2) == 0)) if !easy && r.Intn(10) != 0 { this.XXX_unrecognized = randUnrecognizedTypes(r, 3) @@ -7987,9 +8138,9 @@ func NewPopulatedVoteInfo(r randyTypes, easy bool) *VoteInfo { func NewPopulatedPubKey(r randyTypes, easy bool) *PubKey { this := &PubKey{} this.Type = string(randStringTypes(r)) - v50 := r.Intn(100) - this.Data = make([]byte, v50) - for i := 0; i < v50; i++ { + v51 := r.Intn(100) + this.Data = make([]byte, v51) + for i := 0; i < v51; i++ { this.Data[i] = byte(r.Intn(256)) } if !easy && r.Intn(10) != 0 { @@ -8001,14 +8152,14 @@ func NewPopulatedPubKey(r randyTypes, easy bool) *PubKey { func NewPopulatedEvidence(r randyTypes, easy bool) *Evidence { this := &Evidence{} this.Type = string(randStringTypes(r)) - v51 := NewPopulatedValidator(r, easy) - this.Validator = *v51 + v52 := NewPopulatedValidator(r, easy) + this.Validator = *v52 this.Height = int64(r.Int63()) if r.Intn(2) == 0 { this.Height *= -1 } - v52 := github_com_gogo_protobuf_types.NewPopulatedStdTime(r, easy) - this.Time = *v52 + v53 := github_com_gogo_protobuf_types.NewPopulatedStdTime(r, easy) + this.Time = *v53 this.TotalVotingPower = int64(r.Int63()) if r.Intn(2) == 0 { this.TotalVotingPower *= -1 @@ -8038,9 +8189,9 @@ func randUTF8RuneTypes(r randyTypes) rune { return rune(ru + 61) } func randStringTypes(r randyTypes) string { - v53 := r.Intn(100) - tmps := make([]rune, v53) - for i := 0; i < v53; i++ { + v54 := r.Intn(100) + tmps := make([]rune, v54) + for i := 0; i < v54; i++ { tmps[i] = randUTF8RuneTypes(r) } return string(tmps) @@ -8062,11 +8213,11 @@ func randFieldTypes(dAtA []byte, r randyTypes, fieldNumber int, wire int) []byte switch wire { case 0: dAtA = encodeVarintPopulateTypes(dAtA, uint64(key)) - v54 := r.Int63() + v55 := r.Int63() if r.Intn(2) == 0 { - v54 *= -1 + v55 *= -1 } - dAtA = encodeVarintPopulateTypes(dAtA, uint64(v54)) + dAtA = encodeVarintPopulateTypes(dAtA, uint64(v55)) case 1: dAtA = encodeVarintPopulateTypes(dAtA, uint64(key)) dAtA = append(dAtA, byte(r.Intn(256)), byte(r.Intn(256)), byte(r.Intn(256)), byte(r.Intn(256)), byte(r.Intn(256)), byte(r.Intn(256)), byte(r.Intn(256)), byte(r.Intn(256))) @@ -8834,6 +8985,8 @@ func (m *LastCommitInfo) Size() (n int) { func (m *Header) Size() (n int) { var l int _ = l + l = m.Version.Size() + n += 1 + l + sovTypes(uint64(l)) l = len(m.ChainID) if l > 0 { n += 1 + l + sovTypes(uint64(l)) @@ -8885,7 +9038,22 @@ func (m *Header) Size() (n int) { } l = len(m.ProposerAddress) if l > 0 { - n += 1 + l + sovTypes(uint64(l)) + n += 2 + l + sovTypes(uint64(l)) + } + if m.XXX_unrecognized != nil { + n += len(m.XXX_unrecognized) + } + return n +} + +func (m *Version) Size() (n int) { + var l int + _ = l + if m.Block != 0 { + n += 1 + sovTypes(uint64(m.Block)) + } + if m.App != 0 { + n += 1 + sovTypes(uint64(m.App)) } if m.XXX_unrecognized != nil { n += len(m.XXX_unrecognized) @@ -13126,6 +13294,36 @@ func (m *Header) Unmarshal(dAtA []byte) error { } switch fieldNum { case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Version", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTypes + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthTypes + } + postIndex := iNdEx + msglen + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.Version.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 2: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field ChainID", wireType) } @@ -13154,7 +13352,7 @@ func (m *Header) Unmarshal(dAtA []byte) error { } m.ChainID = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex - case 2: + case 3: if wireType != 0 { return fmt.Errorf("proto: wrong wireType = %d for field Height", wireType) } @@ -13173,7 +13371,7 @@ func (m *Header) Unmarshal(dAtA []byte) error { break } } - case 3: + case 4: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field Time", wireType) } @@ -13203,7 +13401,7 @@ func (m *Header) Unmarshal(dAtA []byte) error { return err } iNdEx = postIndex - case 4: + case 5: if wireType != 0 { return fmt.Errorf("proto: wrong wireType = %d for field NumTxs", wireType) } @@ -13222,7 +13420,7 @@ func (m *Header) Unmarshal(dAtA []byte) error { break } } - case 5: + case 6: if wireType != 0 { return fmt.Errorf("proto: wrong wireType = %d for field TotalTxs", wireType) } @@ -13241,7 +13439,7 @@ func (m *Header) Unmarshal(dAtA []byte) error { break } } - case 6: + case 7: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field LastBlockId", wireType) } @@ -13271,7 +13469,7 @@ func (m *Header) Unmarshal(dAtA []byte) error { return err } iNdEx = postIndex - case 7: + case 8: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field LastCommitHash", wireType) } @@ -13302,7 +13500,7 @@ func (m *Header) Unmarshal(dAtA []byte) error { m.LastCommitHash = []byte{} } iNdEx = postIndex - case 8: + case 9: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field DataHash", wireType) } @@ -13333,7 +13531,7 @@ func (m *Header) Unmarshal(dAtA []byte) error { m.DataHash = []byte{} } iNdEx = postIndex - case 9: + case 10: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field ValidatorsHash", wireType) } @@ -13364,7 +13562,7 @@ func (m *Header) Unmarshal(dAtA []byte) error { m.ValidatorsHash = []byte{} } iNdEx = postIndex - case 10: + case 11: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field NextValidatorsHash", wireType) } @@ -13395,7 +13593,7 @@ func (m *Header) Unmarshal(dAtA []byte) error { m.NextValidatorsHash = []byte{} } iNdEx = postIndex - case 11: + case 12: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field ConsensusHash", wireType) } @@ -13426,7 +13624,7 @@ func (m *Header) Unmarshal(dAtA []byte) error { m.ConsensusHash = []byte{} } iNdEx = postIndex - case 12: + case 13: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field AppHash", wireType) } @@ -13457,7 +13655,7 @@ func (m *Header) Unmarshal(dAtA []byte) error { m.AppHash = []byte{} } iNdEx = postIndex - case 13: + case 14: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field LastResultsHash", wireType) } @@ -13488,7 +13686,7 @@ func (m *Header) Unmarshal(dAtA []byte) error { m.LastResultsHash = []byte{} } iNdEx = postIndex - case 14: + case 15: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field EvidenceHash", wireType) } @@ -13519,7 +13717,7 @@ func (m *Header) Unmarshal(dAtA []byte) error { m.EvidenceHash = []byte{} } iNdEx = postIndex - case 15: + case 16: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field ProposerAddress", wireType) } @@ -13572,6 +13770,95 @@ func (m *Header) Unmarshal(dAtA []byte) error { } return nil } +func (m *Version) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTypes + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: Version: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: Version: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Block", wireType) + } + m.Block = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTypes + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.Block |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + case 2: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field App", wireType) + } + m.App = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTypes + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.App |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + default: + iNdEx = preIndex + skippy, err := skipTypes(dAtA[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthTypes + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + m.XXX_unrecognized = append(m.XXX_unrecognized, dAtA[iNdEx:iNdEx+skippy]...) + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} func (m *BlockID) Unmarshal(dAtA []byte) error { l := len(dAtA) iNdEx := 0 @@ -14481,143 +14768,145 @@ var ( ErrIntOverflowTypes = fmt.Errorf("proto: integer overflow") ) -func init() { proto.RegisterFile("abci/types/types.proto", fileDescriptor_types_4a7ab597ee120b05) } +func init() { proto.RegisterFile("abci/types/types.proto", fileDescriptor_types_07d64ea985a686e2) } func init() { - golang_proto.RegisterFile("abci/types/types.proto", fileDescriptor_types_4a7ab597ee120b05) + golang_proto.RegisterFile("abci/types/types.proto", fileDescriptor_types_07d64ea985a686e2) } -var fileDescriptor_types_4a7ab597ee120b05 = []byte{ - // 2107 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xdc, 0x58, 0xcd, 0x73, 0x23, 0x47, - 0x15, 0xf7, 0x48, 0xb2, 0xa4, 0x79, 0xb6, 0x25, 0xa7, 0xd7, 0x6b, 0x6b, 0x45, 0xb0, 0xb7, 0x26, - 0x90, 0xd8, 0xc4, 0x91, 0x53, 0x0e, 0xa1, 0xbc, 0xd9, 0x90, 0x2a, 0x6b, 0x77, 0xc1, 0xae, 0x04, - 0x30, 0xb3, 0xbb, 0xe6, 0x42, 0xd5, 0x54, 0x4b, 0xd3, 0x96, 0xa6, 0x56, 0x9a, 0x99, 0xcc, 0xb4, - 0x1c, 0x79, 0x8f, 0x9c, 0x73, 0xc8, 0x81, 0x2a, 0xfe, 0x05, 0xfe, 0x04, 0x8e, 0x9c, 0xa8, 0x1c, - 0x29, 0x8a, 0xf3, 0x02, 0xa6, 0x38, 0xc0, 0x95, 0xa2, 0x8a, 0x23, 0xd5, 0xaf, 0xbb, 0xe7, 0xcb, - 0xa3, 0x25, 0x1b, 0x6e, 0x5c, 0xa4, 0xee, 0xf7, 0xd1, 0x1f, 0x6f, 0xde, 0x7b, 0xbf, 0xf7, 0x1a, - 0x36, 0xe9, 0x60, 0xe8, 0x1d, 0xf0, 0xab, 0x90, 0xc5, 0xf2, 0xb7, 0x17, 0x46, 0x01, 0x0f, 0xc8, - 0x32, 0x4e, 0xba, 0xef, 0x8c, 0x3c, 0x3e, 0x9e, 0x0d, 0x7a, 0xc3, 0x60, 0x7a, 0x30, 0x0a, 0x46, - 0xc1, 0x01, 0x72, 0x07, 0xb3, 0x0b, 0x9c, 0xe1, 0x04, 0x47, 0x52, 0xab, 0xbb, 0x33, 0x0a, 0x82, - 0xd1, 0x84, 0xa5, 0x52, 0xdc, 0x9b, 0xb2, 0x98, 0xd3, 0x69, 0xa8, 0x04, 0x8e, 0x32, 0xeb, 0x71, - 0xe6, 0xbb, 0x2c, 0x9a, 0x7a, 0x3e, 0xcf, 0x0e, 0x27, 0xde, 0x20, 0x3e, 0x18, 0x06, 0xd3, 0x69, - 0xe0, 0x67, 0x0f, 0xd4, 0xbd, 0xff, 0x5f, 0x35, 0x87, 0xd1, 0x55, 0xc8, 0x83, 0x83, 0x29, 0x8b, - 0x9e, 0x4d, 0x98, 0xfa, 0x93, 0xca, 0xd6, 0xef, 0x6a, 0xd0, 0xb0, 0xd9, 0xa7, 0x33, 0x16, 0x73, - 0xb2, 0x0b, 0x35, 0x36, 0x1c, 0x07, 0x9d, 0xca, 0x5d, 0x63, 0x77, 0xe5, 0x90, 0xf4, 0xe4, 0x26, - 0x8a, 0xfb, 0x68, 0x38, 0x0e, 0x4e, 0x96, 0x6c, 0x94, 0x20, 0x6f, 0xc3, 0xf2, 0xc5, 0x64, 0x16, - 0x8f, 0x3b, 0x55, 0x14, 0xbd, 0x95, 0x17, 0xfd, 0x81, 0x60, 0x9d, 0x2c, 0xd9, 0x52, 0x46, 0x2c, - 0xeb, 0xf9, 0x17, 0x41, 0xa7, 0x56, 0xb6, 0xec, 0xa9, 0x7f, 0x81, 0xcb, 0x0a, 0x09, 0x72, 0x04, - 0x10, 0x33, 0xee, 0x04, 0x21, 0xf7, 0x02, 0xbf, 0xb3, 0x8c, 0xf2, 0x5b, 0x79, 0xf9, 0xc7, 0x8c, - 0xff, 0x04, 0xd9, 0x27, 0x4b, 0xb6, 0x19, 0xeb, 0x89, 0xd0, 0xf4, 0x7c, 0x8f, 0x3b, 0xc3, 0x31, - 0xf5, 0xfc, 0x4e, 0xbd, 0x4c, 0xf3, 0xd4, 0xf7, 0xf8, 0x03, 0xc1, 0x16, 0x9a, 0x9e, 0x9e, 0x88, - 0xab, 0x7c, 0x3a, 0x63, 0xd1, 0x55, 0xa7, 0x51, 0x76, 0x95, 0x9f, 0x0a, 0x96, 0xb8, 0x0a, 0xca, - 0x90, 0xfb, 0xb0, 0x32, 0x60, 0x23, 0xcf, 0x77, 0x06, 0x93, 0x60, 0xf8, 0xac, 0xd3, 0x44, 0x95, - 0x4e, 0x5e, 0xa5, 0x2f, 0x04, 0xfa, 0x82, 0x7f, 0xb2, 0x64, 0xc3, 0x20, 0x99, 0x91, 0x43, 0x68, - 0x0e, 0xc7, 0x6c, 0xf8, 0xcc, 0xe1, 0xf3, 0x8e, 0x89, 0x9a, 0xb7, 0xf3, 0x9a, 0x0f, 0x04, 0xf7, - 0xc9, 0xfc, 0x64, 0xc9, 0x6e, 0x0c, 0xe5, 0x90, 0xbc, 0x0f, 0x26, 0xf3, 0x5d, 0xb5, 0xdd, 0x0a, - 0x2a, 0x6d, 0x16, 0xbe, 0x8b, 0xef, 0xea, 0xcd, 0x9a, 0x4c, 0x8d, 0x49, 0x0f, 0xea, 0xc2, 0x51, - 0x3c, 0xde, 0x59, 0x45, 0x9d, 0x8d, 0xc2, 0x46, 0xc8, 0x3b, 0x59, 0xb2, 0x95, 0x94, 0x30, 0x9f, - 0xcb, 0x26, 0xde, 0x25, 0x8b, 0xc4, 0xe1, 0x6e, 0x95, 0x99, 0xef, 0xa1, 0xe4, 0xe3, 0xf1, 0x4c, - 0x57, 0x4f, 0xfa, 0x0d, 0x58, 0xbe, 0xa4, 0x93, 0x19, 0xb3, 0xde, 0x82, 0x95, 0x8c, 0xa7, 0x90, - 0x0e, 0x34, 0xa6, 0x2c, 0x8e, 0xe9, 0x88, 0x75, 0x8c, 0xbb, 0xc6, 0xae, 0x69, 0xeb, 0xa9, 0xd5, - 0x82, 0xd5, 0xac, 0x9f, 0x64, 0x14, 0x85, 0x2f, 0x08, 0xc5, 0x4b, 0x16, 0xc5, 0xc2, 0x01, 0x94, - 0xa2, 0x9a, 0x5a, 0x1f, 0xc0, 0x7a, 0xd1, 0x09, 0xc8, 0x3a, 0x54, 0x9f, 0xb1, 0x2b, 0x25, 0x29, - 0x86, 0x64, 0x43, 0x1d, 0x08, 0xbd, 0xd8, 0xb4, 0xd5, 0xe9, 0xbe, 0xa8, 0x24, 0xca, 0x89, 0x1f, - 0x90, 0x23, 0xa8, 0x89, 0x28, 0x44, 0xed, 0x95, 0xc3, 0x6e, 0x4f, 0x86, 0x68, 0x4f, 0x87, 0x68, - 0xef, 0x89, 0x0e, 0xd1, 0x7e, 0xf3, 0xcb, 0x17, 0x3b, 0x4b, 0x5f, 0xfc, 0x69, 0xc7, 0xb0, 0x51, - 0x83, 0xdc, 0x11, 0x9f, 0x92, 0x7a, 0xbe, 0xe3, 0xb9, 0x6a, 0x9f, 0x06, 0xce, 0x4f, 0x5d, 0x72, - 0x0c, 0xeb, 0xc3, 0xc0, 0x8f, 0x99, 0x1f, 0xcf, 0x62, 0x27, 0xa4, 0x11, 0x9d, 0xc6, 0x2a, 0x4a, - 0xf4, 0x87, 0x7b, 0xa0, 0xd9, 0x67, 0xc8, 0xb5, 0xdb, 0xc3, 0x3c, 0x81, 0x7c, 0x08, 0x70, 0x49, - 0x27, 0x9e, 0x4b, 0x79, 0x10, 0xc5, 0x9d, 0xda, 0xdd, 0x6a, 0x46, 0xf9, 0x5c, 0x33, 0x9e, 0x86, - 0x2e, 0xe5, 0xac, 0x5f, 0x13, 0x27, 0xb3, 0x33, 0xf2, 0xe4, 0x4d, 0x68, 0xd3, 0x30, 0x74, 0x62, - 0x4e, 0x39, 0x73, 0x06, 0x57, 0x9c, 0xc5, 0x18, 0x49, 0xab, 0xf6, 0x1a, 0x0d, 0xc3, 0xc7, 0x82, - 0xda, 0x17, 0x44, 0xcb, 0x4d, 0xbe, 0x03, 0x3a, 0x39, 0x21, 0x50, 0x73, 0x29, 0xa7, 0x68, 0x8d, - 0x55, 0x1b, 0xc7, 0x82, 0x16, 0x52, 0x3e, 0x56, 0x77, 0xc4, 0x31, 0xd9, 0x84, 0xfa, 0x98, 0x79, - 0xa3, 0x31, 0xc7, 0x6b, 0x55, 0x6d, 0x35, 0x13, 0x86, 0x0f, 0xa3, 0xe0, 0x92, 0x61, 0x9c, 0x37, - 0x6d, 0x39, 0xb1, 0xfe, 0x66, 0xc0, 0x6b, 0x37, 0x02, 0x43, 0xac, 0x3b, 0xa6, 0xf1, 0x58, 0xef, - 0x25, 0xc6, 0xe4, 0x6d, 0xb1, 0x2e, 0x75, 0x59, 0xa4, 0xf2, 0xcf, 0x9a, 0xba, 0xf1, 0x09, 0x12, - 0xd5, 0x45, 0x95, 0x08, 0x79, 0x04, 0xeb, 0x13, 0x1a, 0x73, 0x47, 0xfa, 0xaf, 0x83, 0xf9, 0xa5, - 0x9a, 0x8b, 0xa9, 0x4f, 0xa8, 0xf6, 0x73, 0xe1, 0x56, 0x4a, 0xbd, 0x35, 0xc9, 0x51, 0xc9, 0x09, - 0x6c, 0x0c, 0xae, 0x9e, 0x53, 0x9f, 0x7b, 0x3e, 0x73, 0x6e, 0xd8, 0xbc, 0xad, 0x96, 0x7a, 0x74, - 0xe9, 0xb9, 0xcc, 0x1f, 0x6a, 0x63, 0xdf, 0x4a, 0x54, 0x92, 0x8f, 0x11, 0x5b, 0x77, 0xa1, 0x95, - 0x8f, 0x62, 0xd2, 0x82, 0x0a, 0x9f, 0xab, 0x1b, 0x56, 0xf8, 0xdc, 0xb2, 0x12, 0x0f, 0x4c, 0x42, - 0xe9, 0x86, 0xcc, 0x1e, 0xb4, 0x0b, 0x61, 0x9d, 0x31, 0xb7, 0x91, 0x35, 0xb7, 0xd5, 0x86, 0xb5, - 0x5c, 0x34, 0x5b, 0x9f, 0x2f, 0x43, 0xd3, 0x66, 0x71, 0x28, 0x9c, 0x89, 0x1c, 0x81, 0xc9, 0xe6, - 0x43, 0x26, 0x13, 0xa9, 0x51, 0x48, 0x53, 0x52, 0xe6, 0x91, 0xe6, 0x8b, 0x80, 0x4e, 0x84, 0xc9, - 0x5e, 0x0e, 0x04, 0x6e, 0x15, 0x95, 0xb2, 0x28, 0xb0, 0x9f, 0x47, 0x81, 0x8d, 0x82, 0x6c, 0x01, - 0x06, 0xf6, 0x72, 0x30, 0x50, 0x5c, 0x38, 0x87, 0x03, 0xf7, 0x4a, 0x70, 0xa0, 0x78, 0xfc, 0x05, - 0x40, 0x70, 0xaf, 0x04, 0x08, 0x3a, 0x37, 0xf6, 0x2a, 0x45, 0x82, 0xfd, 0x3c, 0x12, 0x14, 0xaf, - 0x53, 0x80, 0x82, 0x0f, 0xcb, 0xa0, 0xe0, 0x4e, 0x41, 0x67, 0x21, 0x16, 0xbc, 0x77, 0x03, 0x0b, - 0x36, 0x0b, 0xaa, 0x25, 0x60, 0x70, 0x2f, 0x97, 0xa5, 0xa1, 0xf4, 0x6e, 0xe5, 0x69, 0x9a, 0x7c, - 0xef, 0x26, 0x8e, 0x6c, 0x15, 0x3f, 0x6d, 0x19, 0x90, 0x1c, 0x14, 0x80, 0xe4, 0x76, 0xf1, 0x94, - 0x05, 0x24, 0x49, 0xf1, 0x60, 0x4f, 0xc4, 0x7d, 0xc1, 0xd3, 0x44, 0x8e, 0x60, 0x51, 0x14, 0x44, - 0x2a, 0x61, 0xcb, 0x89, 0xb5, 0x2b, 0x32, 0x51, 0xea, 0x5f, 0x2f, 0xc1, 0x0e, 0x74, 0xfa, 0x8c, - 0x77, 0x59, 0xbf, 0x32, 0x52, 0x5d, 0x8c, 0xe8, 0x6c, 0x16, 0x33, 0x55, 0x16, 0xcb, 0x40, 0x4a, - 0x25, 0x07, 0x29, 0xe4, 0x3b, 0xf0, 0x1a, 0xa6, 0x11, 0xb4, 0x8b, 0x93, 0x4b, 0x6b, 0x6d, 0xc1, - 0x90, 0x06, 0x91, 0xf9, 0xed, 0x1d, 0xb8, 0x95, 0x91, 0x15, 0x29, 0x16, 0x53, 0x58, 0x0d, 0x83, - 0x77, 0x3d, 0x91, 0x3e, 0x0e, 0xc3, 0x13, 0x1a, 0x8f, 0xad, 0x1f, 0xa5, 0xf7, 0x4f, 0xe1, 0x8a, - 0x40, 0x6d, 0x18, 0xb8, 0xf2, 0x5a, 0x6b, 0x36, 0x8e, 0x05, 0x84, 0x4d, 0x82, 0x11, 0xee, 0x6a, - 0xda, 0x62, 0x28, 0xa4, 0x92, 0x48, 0x31, 0x65, 0x48, 0x58, 0xbf, 0x34, 0xd2, 0xf5, 0x52, 0x04, - 0x2b, 0x03, 0x1b, 0xe3, 0x7f, 0x01, 0x9b, 0xca, 0xab, 0x81, 0x8d, 0x75, 0x6d, 0xa4, 0x5f, 0x24, - 0x81, 0x91, 0xaf, 0x77, 0x45, 0xe1, 0x1c, 0x9e, 0xef, 0xb2, 0x39, 0x06, 0x7c, 0xd5, 0x96, 0x13, - 0x8d, 0xf0, 0x75, 0x34, 0x73, 0x1e, 0xe1, 0x1b, 0x48, 0x93, 0x13, 0xf2, 0x06, 0xc2, 0x4f, 0x70, - 0xa1, 0x22, 0x71, 0xad, 0xa7, 0xca, 0xdc, 0x33, 0x41, 0xb4, 0x25, 0x2f, 0x93, 0x4c, 0xcd, 0x1c, - 0x76, 0xbd, 0x0e, 0xa6, 0x38, 0x68, 0x1c, 0xd2, 0x21, 0xc3, 0xc0, 0x32, 0xed, 0x94, 0x60, 0x9d, - 0x01, 0xb9, 0x19, 0xd0, 0xe4, 0x03, 0xa8, 0x71, 0x3a, 0x12, 0xf6, 0x16, 0x26, 0x6b, 0xf5, 0x64, - 0x65, 0xde, 0xfb, 0xf8, 0xfc, 0x8c, 0x7a, 0x51, 0x7f, 0x53, 0x98, 0xea, 0x1f, 0x2f, 0x76, 0x5a, - 0x42, 0x66, 0x3f, 0x98, 0x7a, 0x9c, 0x4d, 0x43, 0x7e, 0x65, 0xa3, 0x8e, 0xf5, 0x4f, 0x43, 0x24, - 0xfa, 0x5c, 0xa0, 0x97, 0x1a, 0x4e, 0x7b, 0x73, 0x25, 0x83, 0xc9, 0x5f, 0xcd, 0x98, 0xdf, 0x04, - 0x18, 0xd1, 0xd8, 0xf9, 0x8c, 0xfa, 0x9c, 0xb9, 0xca, 0xa2, 0xe6, 0x88, 0xc6, 0x3f, 0x43, 0x82, - 0x28, 0x60, 0x04, 0x7b, 0x16, 0x33, 0x17, 0x4d, 0x5b, 0xb5, 0x1b, 0x23, 0x1a, 0x3f, 0x8d, 0x99, - 0x9b, 0xdc, 0xab, 0xf1, 0xea, 0xf7, 0xca, 0xdb, 0xb1, 0x59, 0xb4, 0xe3, 0xbf, 0x32, 0x3e, 0x9c, - 0x62, 0xe0, 0xff, 0xff, 0xbd, 0xff, 0x6e, 0x08, 0xe8, 0xcf, 0x67, 0x59, 0x72, 0x0a, 0xaf, 0x25, - 0x71, 0xe4, 0xcc, 0x30, 0xbe, 0xb4, 0x2f, 0xbd, 0x3c, 0xfc, 0xd6, 0x2f, 0xf3, 0xe4, 0x98, 0xfc, - 0x18, 0xb6, 0x0a, 0x59, 0x20, 0x59, 0xb0, 0xf2, 0xd2, 0x64, 0x70, 0x3b, 0x9f, 0x0c, 0xf4, 0x7a, - 0xda, 0x12, 0xd5, 0xaf, 0xe1, 0xd9, 0xdf, 0x12, 0x75, 0x50, 0x16, 0x1b, 0xca, 0xbe, 0xa5, 0xf5, - 0x0b, 0x03, 0xda, 0x85, 0xc3, 0x90, 0x03, 0x00, 0x99, 0x5a, 0x63, 0xef, 0xb9, 0xae, 0xc9, 0xd7, - 0xd5, 0xc1, 0xd1, 0x64, 0x8f, 0xbd, 0xe7, 0xcc, 0x36, 0x07, 0x7a, 0x48, 0x3e, 0x82, 0x36, 0x53, - 0x95, 0x99, 0xce, 0x7d, 0x95, 0x1c, 0x48, 0xe9, 0xba, 0x4d, 0xdd, 0xb6, 0xc5, 0x72, 0x73, 0xeb, - 0x18, 0xcc, 0x64, 0x5d, 0xf2, 0x0d, 0x30, 0xa7, 0x74, 0xae, 0xea, 0x65, 0x59, 0x69, 0x35, 0xa7, - 0x74, 0x8e, 0xa5, 0x32, 0xd9, 0x82, 0x86, 0x60, 0x8e, 0xa8, 0xdc, 0xa1, 0x6a, 0xd7, 0xa7, 0x74, - 0xfe, 0x43, 0x1a, 0x5b, 0x7b, 0xd0, 0xca, 0x6f, 0xa2, 0x45, 0x35, 0x76, 0x49, 0xd1, 0xe3, 0x11, - 0xb3, 0x1e, 0x43, 0x2b, 0x5f, 0x92, 0x8a, 0x3c, 0x16, 0x05, 0x33, 0xdf, 0x45, 0xc1, 0x65, 0x5b, - 0x4e, 0x44, 0x3f, 0x7a, 0x19, 0xc8, 0x4f, 0x97, 0xad, 0x41, 0xcf, 0x03, 0xce, 0x32, 0x85, 0xac, - 0x94, 0xb1, 0xfe, 0x50, 0x83, 0xba, 0xac, 0x8f, 0xc9, 0x9b, 0x99, 0x96, 0x04, 0xc1, 0xaf, 0xbf, - 0x72, 0xfd, 0x62, 0xa7, 0x81, 0x38, 0x71, 0xfa, 0x30, 0xed, 0x4f, 0xd2, 0x14, 0x58, 0xc9, 0xa5, - 0x40, 0xdd, 0x0c, 0x55, 0x5f, 0xb9, 0x19, 0xda, 0x82, 0x86, 0x3f, 0x9b, 0x3a, 0x7c, 0x1e, 0x63, - 0x24, 0x56, 0xed, 0xba, 0x3f, 0x9b, 0x3e, 0x99, 0xc7, 0xc2, 0xa6, 0x3c, 0xe0, 0x74, 0x82, 0x2c, - 0x19, 0x8a, 0x4d, 0x24, 0x08, 0xe6, 0x11, 0xac, 0x65, 0xe0, 0xd4, 0x73, 0x55, 0xad, 0xd6, 0xca, - 0x7e, 0xf1, 0xd3, 0x87, 0xea, 0xba, 0x2b, 0x09, 0xbc, 0x9e, 0xba, 0x64, 0x37, 0x5f, 0xfb, 0x23, - 0x0a, 0x4b, 0x28, 0xc8, 0x94, 0xf7, 0x02, 0x83, 0xc5, 0x01, 0x84, 0xbb, 0x49, 0x91, 0x26, 0x8a, - 0x34, 0x05, 0x01, 0x99, 0x6f, 0x41, 0x3b, 0x05, 0x32, 0x29, 0x62, 0xca, 0x55, 0x52, 0x32, 0x0a, - 0xbe, 0x0b, 0x1b, 0x3e, 0x9b, 0x73, 0xa7, 0x28, 0x0d, 0x28, 0x4d, 0x04, 0xef, 0x3c, 0xaf, 0xf1, - 0x6d, 0x68, 0xa5, 0x01, 0x89, 0xb2, 0x2b, 0xb2, 0x03, 0x4b, 0xa8, 0x28, 0x76, 0x07, 0x9a, 0x49, - 0x19, 0xb1, 0x8a, 0x02, 0x0d, 0x2a, 0xab, 0x87, 0xa4, 0x30, 0x89, 0x58, 0x3c, 0x9b, 0x70, 0xb5, - 0xc8, 0x1a, 0xca, 0x60, 0x61, 0x62, 0x4b, 0x3a, 0xca, 0xbe, 0x01, 0x6b, 0x49, 0x1c, 0xa0, 0x5c, - 0x0b, 0xe5, 0x56, 0x35, 0x11, 0x85, 0xf6, 0x60, 0x3d, 0x8c, 0x82, 0x30, 0x88, 0x59, 0xe4, 0x50, - 0xd7, 0x8d, 0x58, 0x1c, 0x77, 0xda, 0x72, 0x3d, 0x4d, 0x3f, 0x96, 0x64, 0xeb, 0xe7, 0xd0, 0x50, - 0xd6, 0x2f, 0xed, 0xd3, 0xbe, 0x0f, 0xab, 0x21, 0x8d, 0xc4, 0x99, 0xb2, 0xdd, 0x9a, 0xae, 0x96, - 0xcf, 0x68, 0x24, 0xda, 0xf3, 0x5c, 0xd3, 0xb6, 0x82, 0xf2, 0x92, 0x64, 0xdd, 0x83, 0xb5, 0x9c, - 0x8c, 0x08, 0x03, 0x74, 0x0a, 0x1d, 0x06, 0x38, 0x49, 0x76, 0xae, 0xa4, 0x3b, 0x5b, 0xf7, 0xc1, - 0x4c, 0x0c, 0x2d, 0x8a, 0x3a, 0x7d, 0x0f, 0x43, 0xd9, 0x4e, 0x4e, 0xb1, 0x11, 0x0d, 0x3e, 0x63, - 0x91, 0x2a, 0xe4, 0xe4, 0xc4, 0x7a, 0x0a, 0xed, 0x42, 0x3e, 0x25, 0xfb, 0xd0, 0x08, 0x67, 0x03, - 0x47, 0x3f, 0x20, 0xa4, 0x2d, 0xe7, 0xd9, 0x6c, 0xf0, 0x31, 0xbb, 0xd2, 0x2d, 0x67, 0x88, 0xb3, - 0x74, 0xd9, 0x4a, 0x76, 0xd9, 0x09, 0x34, 0x75, 0x68, 0x92, 0xef, 0x82, 0x99, 0xf8, 0x48, 0x21, - 0x81, 0x25, 0x5b, 0xab, 0x45, 0x53, 0x41, 0xf1, 0xa9, 0x63, 0x6f, 0xe4, 0x33, 0xd7, 0x49, 0xe3, - 0x01, 0xf7, 0x68, 0xda, 0x6d, 0xc9, 0xf8, 0x44, 0x3b, 0xbf, 0xf5, 0x2e, 0xd4, 0xe5, 0xd9, 0x84, - 0x7d, 0xc4, 0xca, 0xba, 0xce, 0x15, 0xe3, 0xd2, 0x4c, 0xfb, 0x47, 0x03, 0x9a, 0x3a, 0x45, 0x95, - 0x2a, 0xe5, 0x0e, 0x5d, 0xf9, 0xaa, 0x87, 0x5e, 0xf4, 0x08, 0xa0, 0xb3, 0x48, 0xed, 0x95, 0xb3, - 0xc8, 0x3e, 0x10, 0x99, 0x2c, 0x2e, 0x03, 0xee, 0xf9, 0x23, 0x47, 0xda, 0x5a, 0x66, 0x8d, 0x75, - 0xe4, 0x9c, 0x23, 0xe3, 0x4c, 0xd0, 0x0f, 0x3f, 0x5f, 0x86, 0xf6, 0x71, 0xff, 0xc1, 0xe9, 0x71, - 0x18, 0x4e, 0xbc, 0x21, 0xc5, 0xe2, 0xfa, 0x00, 0x6a, 0xd8, 0x3e, 0x94, 0x3c, 0x5c, 0x76, 0xcb, - 0xfa, 0x58, 0x72, 0x08, 0xcb, 0xd8, 0x45, 0x90, 0xb2, 0xf7, 0xcb, 0x6e, 0x69, 0x3b, 0x2b, 0x36, - 0x91, 0x7d, 0xc6, 0xcd, 0x67, 0xcc, 0x6e, 0x59, 0x4f, 0x4b, 0x3e, 0x02, 0x33, 0xad, 0xff, 0x17, - 0x3d, 0x66, 0x76, 0x17, 0x76, 0xb7, 0x42, 0x3f, 0xad, 0x95, 0x16, 0xbd, 0xc9, 0x75, 0x17, 0xb6, - 0x81, 0xe4, 0x08, 0x1a, 0xba, 0xc2, 0x2c, 0x7f, 0x6e, 0xec, 0x2e, 0xe8, 0x3c, 0x85, 0x79, 0x64, - 0x49, 0x5f, 0xf6, 0x26, 0xda, 0x2d, 0x6d, 0x8f, 0xc9, 0xfb, 0x50, 0x57, 0xb0, 0x5f, 0xfa, 0xe4, - 0xd8, 0x2d, 0xef, 0x1f, 0xc5, 0x25, 0xd3, 0xa6, 0x66, 0xd1, 0xbb, 0x6d, 0x77, 0x61, 0x1f, 0x4f, - 0x8e, 0x01, 0x32, 0x95, 0xf9, 0xc2, 0x07, 0xd9, 0xee, 0xe2, 0xfe, 0x9c, 0xdc, 0x87, 0x66, 0xfa, - 0xe6, 0x52, 0xfe, 0xc4, 0xda, 0x5d, 0xd4, 0x32, 0xf7, 0x5f, 0xff, 0xf7, 0x5f, 0xb6, 0x8d, 0x5f, - 0x5f, 0x6f, 0x1b, 0xbf, 0xb9, 0xde, 0x36, 0xbe, 0xbc, 0xde, 0x36, 0x7e, 0x7f, 0xbd, 0x6d, 0xfc, - 0xf9, 0x7a, 0xdb, 0xf8, 0xed, 0x5f, 0xb7, 0x8d, 0x41, 0x1d, 0xdd, 0xff, 0xbd, 0xff, 0x04, 0x00, - 0x00, 0xff, 0xff, 0x2c, 0x0a, 0x65, 0x88, 0x52, 0x18, 0x00, 0x00, +var fileDescriptor_types_07d64ea985a686e2 = []byte{ + // 2133 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xdc, 0x58, 0xcd, 0x93, 0x1b, 0x47, + 0x15, 0xdf, 0xd1, 0x6a, 0x25, 0xcd, 0xdb, 0x5d, 0x49, 0x6e, 0x7f, 0xc9, 0x22, 0xac, 0x5d, 0x13, + 0x48, 0xbc, 0xc4, 0xd1, 0x06, 0x87, 0x50, 0xeb, 0x38, 0xa4, 0x6a, 0x65, 0x1b, 0x76, 0x2b, 0x01, + 0x96, 0xf1, 0x07, 0x17, 0xaa, 0xa6, 0x5a, 0x9a, 0xb6, 0x34, 0x65, 0x69, 0x66, 0x32, 0xd3, 0xda, + 0x68, 0x7d, 0xcc, 0x39, 0x87, 0x1c, 0xa8, 0xe2, 0x5f, 0xe0, 0x4f, 0xe0, 0xc8, 0x89, 0xca, 0x91, + 0x03, 0x67, 0x03, 0x4b, 0x71, 0x80, 0x2b, 0x45, 0x15, 0x47, 0xea, 0xbd, 0xee, 0xf9, 0xdc, 0x91, + 0x89, 0x03, 0x27, 0x2e, 0xd2, 0xf4, 0xfb, 0xe8, 0x8f, 0xd7, 0xef, 0xbd, 0xdf, 0x7b, 0x0d, 0x57, + 0xf8, 0x68, 0xec, 0xed, 0xc9, 0xd3, 0x50, 0xc4, 0xea, 0x77, 0x10, 0x46, 0x81, 0x0c, 0xd8, 0x06, + 0x0d, 0xfa, 0x6f, 0x4f, 0x3c, 0x39, 0x5d, 0x8c, 0x06, 0xe3, 0x60, 0xbe, 0x37, 0x09, 0x26, 0xc1, + 0x1e, 0x71, 0x47, 0x8b, 0xa7, 0x34, 0xa2, 0x01, 0x7d, 0x29, 0xad, 0xfe, 0xf5, 0x49, 0x10, 0x4c, + 0x66, 0x22, 0x93, 0x92, 0xde, 0x5c, 0xc4, 0x92, 0xcf, 0x43, 0x2d, 0xb0, 0x9f, 0x9b, 0x4f, 0x0a, + 0xdf, 0x15, 0xd1, 0xdc, 0xf3, 0x65, 0xfe, 0x73, 0xe6, 0x8d, 0xe2, 0xbd, 0x71, 0x30, 0x9f, 0x07, + 0x7e, 0x7e, 0x43, 0xfd, 0xbb, 0xff, 0x51, 0x73, 0x1c, 0x9d, 0x86, 0x32, 0xd8, 0x9b, 0x8b, 0xe8, + 0xd9, 0x4c, 0xe8, 0x3f, 0xa5, 0x6c, 0xfd, 0xae, 0x0e, 0x4d, 0x5b, 0x7c, 0xb2, 0x10, 0xb1, 0x64, + 0x37, 0xa1, 0x2e, 0xc6, 0xd3, 0xa0, 0x57, 0xbb, 0x61, 0xdc, 0xdc, 0xbc, 0xcd, 0x06, 0x6a, 0x11, + 0xcd, 0x7d, 0x30, 0x9e, 0x06, 0x87, 0x6b, 0x36, 0x49, 0xb0, 0xb7, 0x60, 0xe3, 0xe9, 0x6c, 0x11, + 0x4f, 0x7b, 0xeb, 0x24, 0x7a, 0xb1, 0x28, 0xfa, 0x43, 0x64, 0x1d, 0xae, 0xd9, 0x4a, 0x06, 0xa7, + 0xf5, 0xfc, 0xa7, 0x41, 0xaf, 0x5e, 0x35, 0xed, 0x91, 0xff, 0x94, 0xa6, 0x45, 0x09, 0xb6, 0x0f, + 0x10, 0x0b, 0xe9, 0x04, 0xa1, 0xf4, 0x02, 0xbf, 0xb7, 0x41, 0xf2, 0x57, 0x8b, 0xf2, 0x0f, 0x85, + 0xfc, 0x29, 0xb1, 0x0f, 0xd7, 0x6c, 0x33, 0x4e, 0x06, 0xa8, 0xe9, 0xf9, 0x9e, 0x74, 0xc6, 0x53, + 0xee, 0xf9, 0xbd, 0x46, 0x95, 0xe6, 0x91, 0xef, 0xc9, 0x7b, 0xc8, 0x46, 0x4d, 0x2f, 0x19, 0xe0, + 0x51, 0x3e, 0x59, 0x88, 0xe8, 0xb4, 0xd7, 0xac, 0x3a, 0xca, 0xcf, 0x90, 0x85, 0x47, 0x21, 0x19, + 0x76, 0x17, 0x36, 0x47, 0x62, 0xe2, 0xf9, 0xce, 0x68, 0x16, 0x8c, 0x9f, 0xf5, 0x5a, 0xa4, 0xd2, + 0x2b, 0xaa, 0x0c, 0x51, 0x60, 0x88, 0xfc, 0xc3, 0x35, 0x1b, 0x46, 0xe9, 0x88, 0xdd, 0x86, 0xd6, + 0x78, 0x2a, 0xc6, 0xcf, 0x1c, 0xb9, 0xec, 0x99, 0xa4, 0x79, 0xb9, 0xa8, 0x79, 0x0f, 0xb9, 0x8f, + 0x96, 0x87, 0x6b, 0x76, 0x73, 0xac, 0x3e, 0xd9, 0x7b, 0x60, 0x0a, 0xdf, 0xd5, 0xcb, 0x6d, 0x92, + 0xd2, 0x95, 0xd2, 0xbd, 0xf8, 0x6e, 0xb2, 0x58, 0x4b, 0xe8, 0x6f, 0x36, 0x80, 0x06, 0x3a, 0x8a, + 0x27, 0x7b, 0x5b, 0xa4, 0x73, 0xa9, 0xb4, 0x10, 0xf1, 0x0e, 0xd7, 0x6c, 0x2d, 0x85, 0xe6, 0x73, + 0xc5, 0xcc, 0x3b, 0x11, 0x11, 0x6e, 0xee, 0x62, 0x95, 0xf9, 0xee, 0x2b, 0x3e, 0x6d, 0xcf, 0x74, + 0x93, 0xc1, 0xb0, 0x09, 0x1b, 0x27, 0x7c, 0xb6, 0x10, 0xd6, 0x9b, 0xb0, 0x99, 0xf3, 0x14, 0xd6, + 0x83, 0xe6, 0x5c, 0xc4, 0x31, 0x9f, 0x88, 0x9e, 0x71, 0xc3, 0xb8, 0x69, 0xda, 0xc9, 0xd0, 0x6a, + 0xc3, 0x56, 0xde, 0x4f, 0x72, 0x8a, 0xe8, 0x0b, 0xa8, 0x78, 0x22, 0xa2, 0x18, 0x1d, 0x40, 0x2b, + 0xea, 0xa1, 0xf5, 0x3e, 0x74, 0xcb, 0x4e, 0xc0, 0xba, 0xb0, 0xfe, 0x4c, 0x9c, 0x6a, 0x49, 0xfc, + 0x64, 0x97, 0xf4, 0x86, 0xc8, 0x8b, 0x4d, 0x5b, 0xef, 0xee, 0x8b, 0x5a, 0xaa, 0x9c, 0xfa, 0x01, + 0xdb, 0x87, 0x3a, 0x46, 0x21, 0x69, 0x6f, 0xde, 0xee, 0x0f, 0x54, 0x88, 0x0e, 0x92, 0x10, 0x1d, + 0x3c, 0x4a, 0x42, 0x74, 0xd8, 0xfa, 0xf2, 0xc5, 0xf5, 0xb5, 0x2f, 0xfe, 0x78, 0xdd, 0xb0, 0x49, + 0x83, 0x5d, 0xc3, 0xab, 0xe4, 0x9e, 0xef, 0x78, 0xae, 0x5e, 0xa7, 0x49, 0xe3, 0x23, 0x97, 0x1d, + 0x40, 0x77, 0x1c, 0xf8, 0xb1, 0xf0, 0xe3, 0x45, 0xec, 0x84, 0x3c, 0xe2, 0xf3, 0x58, 0x47, 0x49, + 0x72, 0x71, 0xf7, 0x12, 0xf6, 0x31, 0x71, 0xed, 0xce, 0xb8, 0x48, 0x60, 0x1f, 0x00, 0x9c, 0xf0, + 0x99, 0xe7, 0x72, 0x19, 0x44, 0x71, 0xaf, 0x7e, 0x63, 0x3d, 0xa7, 0xfc, 0x24, 0x61, 0x3c, 0x0e, + 0x5d, 0x2e, 0xc5, 0xb0, 0x8e, 0x3b, 0xb3, 0x73, 0xf2, 0xec, 0x0d, 0xe8, 0xf0, 0x30, 0x74, 0x62, + 0xc9, 0xa5, 0x70, 0x46, 0xa7, 0x52, 0xc4, 0x14, 0x49, 0x5b, 0xf6, 0x36, 0x0f, 0xc3, 0x87, 0x48, + 0x1d, 0x22, 0xd1, 0x72, 0xd3, 0x7b, 0x20, 0x27, 0x67, 0x0c, 0xea, 0x2e, 0x97, 0x9c, 0xac, 0xb1, + 0x65, 0xd3, 0x37, 0xd2, 0x42, 0x2e, 0xa7, 0xfa, 0x8c, 0xf4, 0xcd, 0xae, 0x40, 0x63, 0x2a, 0xbc, + 0xc9, 0x54, 0xd2, 0xb1, 0xd6, 0x6d, 0x3d, 0x42, 0xc3, 0x87, 0x51, 0x70, 0x22, 0x28, 0xce, 0x5b, + 0xb6, 0x1a, 0x58, 0x7f, 0x35, 0xe0, 0xc2, 0xb9, 0xc0, 0xc0, 0x79, 0xa7, 0x3c, 0x9e, 0x26, 0x6b, + 0xe1, 0x37, 0x7b, 0x0b, 0xe7, 0xe5, 0xae, 0x88, 0x74, 0xfe, 0xd9, 0xd6, 0x27, 0x3e, 0x24, 0xa2, + 0x3e, 0xa8, 0x16, 0x61, 0x0f, 0xa0, 0x3b, 0xe3, 0xb1, 0x74, 0x94, 0xff, 0x3a, 0x94, 0x5f, 0xd6, + 0x0b, 0x31, 0xf5, 0x31, 0x4f, 0xfc, 0x1c, 0xdd, 0x4a, 0xab, 0xb7, 0x67, 0x05, 0x2a, 0x3b, 0x84, + 0x4b, 0xa3, 0xd3, 0xe7, 0xdc, 0x97, 0x9e, 0x2f, 0x9c, 0x73, 0x36, 0xef, 0xe8, 0xa9, 0x1e, 0x9c, + 0x78, 0xae, 0xf0, 0xc7, 0x89, 0xb1, 0x2f, 0xa6, 0x2a, 0xe9, 0x65, 0xc4, 0xd6, 0x0d, 0x68, 0x17, + 0xa3, 0x98, 0xb5, 0xa1, 0x26, 0x97, 0xfa, 0x84, 0x35, 0xb9, 0xb4, 0xac, 0xd4, 0x03, 0xd3, 0x50, + 0x3a, 0x27, 0xb3, 0x0b, 0x9d, 0x52, 0x58, 0xe7, 0xcc, 0x6d, 0xe4, 0xcd, 0x6d, 0x75, 0x60, 0xbb, + 0x10, 0xcd, 0xd6, 0xe7, 0x1b, 0xd0, 0xb2, 0x45, 0x1c, 0xa2, 0x33, 0xb1, 0x7d, 0x30, 0xc5, 0x72, + 0x2c, 0x54, 0x22, 0x35, 0x4a, 0x69, 0x4a, 0xc9, 0x3c, 0x48, 0xf8, 0x18, 0xd0, 0xa9, 0x30, 0xdb, + 0x2d, 0x80, 0xc0, 0xc5, 0xb2, 0x52, 0x1e, 0x05, 0x6e, 0x15, 0x51, 0xe0, 0x52, 0x49, 0xb6, 0x04, + 0x03, 0xbb, 0x05, 0x18, 0x28, 0x4f, 0x5c, 0xc0, 0x81, 0x3b, 0x15, 0x38, 0x50, 0xde, 0xfe, 0x0a, + 0x20, 0xb8, 0x53, 0x01, 0x04, 0xbd, 0x73, 0x6b, 0x55, 0x22, 0xc1, 0xad, 0x22, 0x12, 0x94, 0x8f, + 0x53, 0x82, 0x82, 0x0f, 0xaa, 0xa0, 0xe0, 0x5a, 0x49, 0x67, 0x25, 0x16, 0xbc, 0x7b, 0x0e, 0x0b, + 0xae, 0x94, 0x54, 0x2b, 0xc0, 0xe0, 0x4e, 0x21, 0x4b, 0x43, 0xe5, 0xd9, 0xaa, 0xd3, 0x34, 0xfb, + 0xfe, 0x79, 0x1c, 0xb9, 0x5a, 0xbe, 0xda, 0x2a, 0x20, 0xd9, 0x2b, 0x01, 0xc9, 0xe5, 0xf2, 0x2e, + 0x4b, 0x48, 0x92, 0xe1, 0xc1, 0x2e, 0xc6, 0x7d, 0xc9, 0xd3, 0x30, 0x47, 0x88, 0x28, 0x0a, 0x22, + 0x9d, 0xb0, 0xd5, 0xc0, 0xba, 0x89, 0x99, 0x28, 0xf3, 0xaf, 0x97, 0x60, 0x07, 0x39, 0x7d, 0xce, + 0xbb, 0xac, 0x5f, 0x19, 0x99, 0x2e, 0x45, 0x74, 0x3e, 0x8b, 0x99, 0x3a, 0x8b, 0xe5, 0x20, 0xa5, + 0x56, 0x80, 0x14, 0xf6, 0x1d, 0xb8, 0x40, 0x69, 0x84, 0xec, 0xe2, 0x14, 0xd2, 0x5a, 0x07, 0x19, + 0xca, 0x20, 0x2a, 0xbf, 0xbd, 0x0d, 0x17, 0x73, 0xb2, 0x98, 0x62, 0x29, 0x85, 0xd5, 0x29, 0x78, + 0xbb, 0xa9, 0xf4, 0x41, 0x18, 0x1e, 0xf2, 0x78, 0x6a, 0xfd, 0x38, 0x3b, 0x7f, 0x06, 0x57, 0x0c, + 0xea, 0xe3, 0xc0, 0x55, 0xc7, 0xda, 0xb6, 0xe9, 0x1b, 0x21, 0x6c, 0x16, 0x4c, 0x68, 0x55, 0xd3, + 0xc6, 0x4f, 0x94, 0x4a, 0x23, 0xc5, 0x54, 0x21, 0x61, 0xfd, 0xd2, 0xc8, 0xe6, 0xcb, 0x10, 0xac, + 0x0a, 0x6c, 0x8c, 0xff, 0x06, 0x6c, 0x6a, 0xaf, 0x06, 0x36, 0xd6, 0x99, 0x91, 0xdd, 0x48, 0x0a, + 0x23, 0x5f, 0xef, 0x88, 0xe8, 0x1c, 0x9e, 0xef, 0x8a, 0x25, 0x05, 0xfc, 0xba, 0xad, 0x06, 0x09, + 0xc2, 0x37, 0xc8, 0xcc, 0x45, 0x84, 0x6f, 0x12, 0x4d, 0x0d, 0xd8, 0xeb, 0x04, 0x3f, 0xc1, 0x53, + 0x1d, 0x89, 0xdb, 0x03, 0x5d, 0xe6, 0x1e, 0x23, 0xd1, 0x56, 0xbc, 0x5c, 0x32, 0x35, 0x0b, 0xd8, + 0xf5, 0x1a, 0x98, 0xb8, 0xd1, 0x38, 0xe4, 0x63, 0x41, 0x81, 0x65, 0xda, 0x19, 0xc1, 0x3a, 0x06, + 0x76, 0x3e, 0xa0, 0xd9, 0xfb, 0x50, 0x97, 0x7c, 0x82, 0xf6, 0x46, 0x93, 0xb5, 0x07, 0xaa, 0x32, + 0x1f, 0x7c, 0xf4, 0xe4, 0x98, 0x7b, 0xd1, 0xf0, 0x0a, 0x9a, 0xea, 0xef, 0x2f, 0xae, 0xb7, 0x51, + 0xe6, 0x56, 0x30, 0xf7, 0xa4, 0x98, 0x87, 0xf2, 0xd4, 0x26, 0x1d, 0xeb, 0x1f, 0x06, 0x26, 0xfa, + 0x42, 0xa0, 0x57, 0x1a, 0x2e, 0xf1, 0xe6, 0x5a, 0x0e, 0x93, 0xbf, 0x9a, 0x31, 0xbf, 0x09, 0x30, + 0xe1, 0xb1, 0xf3, 0x29, 0xf7, 0xa5, 0x70, 0xb5, 0x45, 0xcd, 0x09, 0x8f, 0x7f, 0x4e, 0x04, 0x2c, + 0x60, 0x90, 0xbd, 0x88, 0x85, 0x4b, 0xa6, 0x5d, 0xb7, 0x9b, 0x13, 0x1e, 0x3f, 0x8e, 0x85, 0x9b, + 0x9e, 0xab, 0xf9, 0xea, 0xe7, 0x2a, 0xda, 0xb1, 0x55, 0xb6, 0xe3, 0x3f, 0x73, 0x3e, 0x9c, 0x61, + 0xe0, 0xff, 0xff, 0xb9, 0xff, 0x66, 0x20, 0xf4, 0x17, 0xb3, 0x2c, 0x3b, 0x82, 0x0b, 0x69, 0x1c, + 0x39, 0x0b, 0x8a, 0xaf, 0xc4, 0x97, 0x5e, 0x1e, 0x7e, 0xdd, 0x93, 0x22, 0x39, 0x66, 0x3f, 0x81, + 0xab, 0xa5, 0x2c, 0x90, 0x4e, 0x58, 0x7b, 0x69, 0x32, 0xb8, 0x5c, 0x4c, 0x06, 0xc9, 0x7c, 0x89, + 0x25, 0xd6, 0xbf, 0x86, 0x67, 0x7f, 0x0b, 0xeb, 0xa0, 0x3c, 0x36, 0x54, 0xdd, 0xa5, 0xf5, 0x99, + 0x01, 0x9d, 0xd2, 0x66, 0xd8, 0x1e, 0x80, 0x4a, 0xad, 0xb1, 0xf7, 0x3c, 0xa9, 0xc9, 0xbb, 0x7a, + 0xe3, 0x64, 0xb2, 0x87, 0xde, 0x73, 0x61, 0x9b, 0xa3, 0xe4, 0x93, 0x7d, 0x08, 0x1d, 0xa1, 0x2b, + 0xb3, 0x24, 0xf7, 0xd5, 0x0a, 0x20, 0x95, 0xd4, 0x6d, 0xfa, 0xb4, 0x6d, 0x51, 0x18, 0x5b, 0x07, + 0x60, 0xa6, 0xf3, 0xb2, 0x6f, 0x80, 0x39, 0xe7, 0x4b, 0x5d, 0x2f, 0xab, 0x4a, 0xab, 0x35, 0xe7, + 0x4b, 0x2a, 0x95, 0xd9, 0x55, 0x68, 0x22, 0x73, 0xc2, 0xd5, 0x0a, 0xeb, 0x76, 0x63, 0xce, 0x97, + 0x3f, 0xe2, 0xb1, 0xb5, 0x0b, 0xed, 0xe2, 0x22, 0x89, 0x68, 0x82, 0x5d, 0x4a, 0xf4, 0x60, 0x22, + 0xac, 0x87, 0xd0, 0x2e, 0x96, 0xa4, 0x98, 0xc7, 0xa2, 0x60, 0xe1, 0xbb, 0x24, 0xb8, 0x61, 0xab, + 0x01, 0xf6, 0xa3, 0x27, 0x81, 0xba, 0xba, 0x7c, 0x0d, 0xfa, 0x24, 0x90, 0x22, 0x57, 0xc8, 0x2a, + 0x19, 0xeb, 0xb3, 0x0d, 0x68, 0xa8, 0xfa, 0x98, 0x0d, 0x8a, 0x7d, 0x13, 0xde, 0x9b, 0xd6, 0x54, + 0x54, 0xad, 0x98, 0x42, 0xdf, 0x1b, 0xe5, 0x16, 0x66, 0xb8, 0x79, 0xf6, 0xe2, 0x7a, 0x93, 0x70, + 0xe5, 0xe8, 0x7e, 0xd6, 0xcf, 0xac, 0x2a, 0xf7, 0x93, 0xe6, 0xa9, 0xfe, 0xca, 0xcd, 0xd3, 0x55, + 0x68, 0xfa, 0x8b, 0xb9, 0x23, 0x97, 0xb1, 0x8e, 0xcf, 0x86, 0xbf, 0x98, 0x3f, 0x5a, 0xc6, 0x78, + 0x07, 0x32, 0x90, 0x7c, 0x46, 0x2c, 0x15, 0x9d, 0x2d, 0x22, 0x20, 0x73, 0x1f, 0xb6, 0x73, 0xf0, + 0xeb, 0xb9, 0xba, 0x4a, 0x6b, 0xe7, 0x3d, 0xe4, 0xe8, 0xbe, 0x3e, 0xe5, 0x66, 0x0a, 0xc7, 0x47, + 0x2e, 0xbb, 0x59, 0xec, 0x15, 0x08, 0xb5, 0x5b, 0xe4, 0x8c, 0xb9, 0x76, 0x00, 0x31, 0x1b, 0x37, + 0x80, 0xee, 0xa9, 0x44, 0x4c, 0x12, 0x69, 0x21, 0x81, 0x98, 0x6f, 0x42, 0x27, 0x03, 0x3e, 0x25, + 0x02, 0x6a, 0x96, 0x8c, 0x4c, 0x82, 0xef, 0xc0, 0x25, 0x5f, 0x2c, 0xa5, 0x53, 0x96, 0xde, 0x24, + 0x69, 0x86, 0xbc, 0x27, 0x45, 0x8d, 0x6f, 0x43, 0x3b, 0x0b, 0x60, 0x92, 0xdd, 0x52, 0x1d, 0x5b, + 0x4a, 0x25, 0xb1, 0x6b, 0xd0, 0x4a, 0xcb, 0x8e, 0x6d, 0x12, 0x68, 0x72, 0x55, 0x6d, 0xa4, 0x85, + 0x4c, 0x24, 0xe2, 0xc5, 0x4c, 0xea, 0x49, 0xda, 0x24, 0x43, 0x85, 0x8c, 0xad, 0xe8, 0x24, 0xfb, + 0x3a, 0x6c, 0xa7, 0x71, 0x43, 0x72, 0x1d, 0x92, 0xdb, 0x4a, 0x88, 0x24, 0xb4, 0x0b, 0xdd, 0x30, + 0x0a, 0xc2, 0x20, 0x16, 0x91, 0xc3, 0x5d, 0x37, 0x12, 0x71, 0xdc, 0xeb, 0xaa, 0xf9, 0x12, 0xfa, + 0x81, 0x22, 0x5b, 0xdf, 0x85, 0xa6, 0xf6, 0x31, 0x74, 0x69, 0xb2, 0x3a, 0xb9, 0x60, 0xdd, 0x56, + 0x03, 0xcc, 0xdc, 0x07, 0x61, 0x48, 0x5e, 0x56, 0xb7, 0xf1, 0xd3, 0xfa, 0x05, 0x34, 0xf5, 0x85, + 0x55, 0xb6, 0x82, 0x3f, 0x80, 0xad, 0x90, 0x47, 0x78, 0x8c, 0x7c, 0x43, 0x98, 0x14, 0xe4, 0xc7, + 0x3c, 0x92, 0x0f, 0x85, 0x2c, 0xf4, 0x85, 0x9b, 0x24, 0xaf, 0x48, 0xd6, 0x1d, 0xd8, 0x2e, 0xc8, + 0xe0, 0xb6, 0xc8, 0x8f, 0x92, 0x48, 0xa3, 0x41, 0xba, 0x72, 0x2d, 0x5b, 0xd9, 0xba, 0x0b, 0x66, + 0x7a, 0x37, 0x58, 0x37, 0x26, 0x47, 0x37, 0xb4, 0xb9, 0xd5, 0x90, 0x7a, 0xdd, 0xe0, 0x53, 0x11, + 0xe9, 0x98, 0x50, 0x03, 0xeb, 0x31, 0x74, 0x4a, 0x29, 0x9b, 0xdd, 0x82, 0x66, 0xb8, 0x18, 0x39, + 0xc9, 0x1b, 0x45, 0xd6, 0xd5, 0x1e, 0x2f, 0x46, 0x1f, 0x89, 0xd3, 0xa4, 0xab, 0x0d, 0x69, 0x94, + 0x4d, 0x5b, 0xcb, 0x4f, 0x3b, 0x83, 0x56, 0x12, 0xfd, 0xec, 0x7b, 0x60, 0xa6, 0x6e, 0x55, 0xca, + 0x91, 0xe9, 0xd2, 0x7a, 0xd2, 0x4c, 0x10, 0xbd, 0x23, 0xf6, 0x26, 0xbe, 0x70, 0x9d, 0x2c, 0x84, + 0x68, 0x8d, 0x96, 0xdd, 0x51, 0x8c, 0x8f, 0x93, 0x78, 0xb1, 0xde, 0x81, 0x86, 0xda, 0x1b, 0xda, + 0x07, 0x67, 0x4e, 0x4a, 0x69, 0xfc, 0xae, 0x4c, 0xe6, 0x7f, 0x30, 0xa0, 0x95, 0x64, 0xc1, 0x4a, + 0xa5, 0xc2, 0xa6, 0x6b, 0x5f, 0x75, 0xd3, 0xff, 0xfb, 0xc4, 0x73, 0x0b, 0x98, 0xca, 0x2f, 0x27, + 0x81, 0xf4, 0xfc, 0x89, 0xa3, 0x6c, 0xad, 0x72, 0x50, 0x97, 0x38, 0x4f, 0x88, 0x71, 0x8c, 0xf4, + 0xdb, 0x9f, 0x6f, 0x40, 0xe7, 0x60, 0x78, 0xef, 0xe8, 0x20, 0x0c, 0x67, 0xde, 0x98, 0x53, 0xfd, + 0xbe, 0x07, 0x75, 0xea, 0x50, 0x2a, 0xde, 0x46, 0xfb, 0x55, 0xad, 0x32, 0xbb, 0x0d, 0x1b, 0xd4, + 0xa8, 0xb0, 0xaa, 0x27, 0xd2, 0x7e, 0x65, 0xc7, 0x8c, 0x8b, 0xa8, 0x56, 0xe6, 0xfc, 0x4b, 0x69, + 0xbf, 0xaa, 0x6d, 0x66, 0x1f, 0x82, 0x99, 0xb5, 0x18, 0xab, 0xde, 0x4b, 0xfb, 0x2b, 0x1b, 0x68, + 0xd4, 0xcf, 0xca, 0xb1, 0x55, 0xcf, 0x7e, 0xfd, 0x95, 0x9d, 0x26, 0xdb, 0x87, 0x66, 0x52, 0xc4, + 0x56, 0xbf, 0x68, 0xf6, 0x57, 0x34, 0xb7, 0x68, 0x1e, 0xd5, 0x35, 0x54, 0x3d, 0xbb, 0xf6, 0x2b, + 0x3b, 0x70, 0xf6, 0x1e, 0x34, 0x74, 0x65, 0x51, 0xf9, 0xaa, 0xd9, 0xaf, 0x6e, 0x51, 0xf1, 0x90, + 0x59, 0xdf, 0xb4, 0xea, 0x69, 0xb8, 0xbf, 0xf2, 0xa9, 0x80, 0x1d, 0x00, 0xe4, 0x8a, 0xff, 0x95, + 0x6f, 0xbe, 0xfd, 0xd5, 0x4f, 0x00, 0xec, 0x2e, 0xb4, 0xb2, 0x67, 0x9d, 0xea, 0x57, 0xdc, 0xfe, + 0xaa, 0xae, 0x7c, 0xf8, 0xda, 0xbf, 0xfe, 0xbc, 0x63, 0xfc, 0xfa, 0x6c, 0xc7, 0xf8, 0xcd, 0xd9, + 0x8e, 0xf1, 0xe5, 0xd9, 0x8e, 0xf1, 0xfb, 0xb3, 0x1d, 0xe3, 0x4f, 0x67, 0x3b, 0xc6, 0x6f, 0xff, + 0xb2, 0x63, 0x8c, 0x1a, 0xe4, 0xfe, 0xef, 0xfe, 0x3b, 0x00, 0x00, 0xff, 0xff, 0xf4, 0x43, 0x2c, + 0xa9, 0xb5, 0x18, 0x00, 0x00, } diff --git a/abci/types/types.proto b/abci/types/types.proto index 39c96e0e..517369b1 100644 --- a/abci/types/types.proto +++ b/abci/types/types.proto @@ -231,31 +231,38 @@ message LastCommitInfo { message Header { // basic block info - string chain_id = 1 [(gogoproto.customname)="ChainID"]; - int64 height = 2; - google.protobuf.Timestamp time = 3 [(gogoproto.nullable)=false, (gogoproto.stdtime)=true]; - int64 num_txs = 4; - int64 total_txs = 5; + Version version = 1 [(gogoproto.nullable)=false]; + string chain_id = 2 [(gogoproto.customname)="ChainID"]; + int64 height = 3; + google.protobuf.Timestamp time = 4 [(gogoproto.nullable)=false, (gogoproto.stdtime)=true]; + int64 num_txs = 5; + int64 total_txs = 6; // prev block info - BlockID last_block_id = 6 [(gogoproto.nullable)=false]; + BlockID last_block_id = 7 [(gogoproto.nullable)=false]; // hashes of block data - bytes last_commit_hash = 7; // commit from validators from the last block - bytes data_hash = 8; // transactions + bytes last_commit_hash = 8; // commit from validators from the last block + bytes data_hash = 9; // transactions // hashes from the app output from the prev block - bytes validators_hash = 9; // validators for the current block - bytes next_validators_hash = 10; // validators for the next block - bytes consensus_hash = 11; // consensus params for current block - bytes app_hash = 12; // state after txs from the previous block - bytes last_results_hash = 13;// root hash of all results from the txs from the previous block + bytes validators_hash = 10; // validators for the current block + bytes next_validators_hash = 11; // validators for the next block + bytes consensus_hash = 12; // consensus params for current block + bytes app_hash = 13; // state after txs from the previous block + bytes last_results_hash = 14;// root hash of all results from the txs from the previous block // consensus info - bytes evidence_hash = 14; // evidence included in the block - bytes proposer_address = 15; // original proposer of the block + bytes evidence_hash = 15; // evidence included in the block + bytes proposer_address = 16; // original proposer of the block } +message Version { + uint64 Block = 1; + uint64 App = 2; +} + + message BlockID { bytes hash = 1; PartSetHeader parts_header = 2 [(gogoproto.nullable)=false]; diff --git a/abci/types/typespb_test.go b/abci/types/typespb_test.go index 0ae0fea0..53c5cd94 100644 --- a/abci/types/typespb_test.go +++ b/abci/types/typespb_test.go @@ -1703,6 +1703,62 @@ func TestHeaderMarshalTo(t *testing.T) { } } +func TestVersionProto(t *testing.T) { + seed := time.Now().UnixNano() + popr := math_rand.New(math_rand.NewSource(seed)) + p := NewPopulatedVersion(popr, false) + dAtA, err := github_com_gogo_protobuf_proto.Marshal(p) + if err != nil { + t.Fatalf("seed = %d, err = %v", seed, err) + } + msg := &Version{} + if err := github_com_gogo_protobuf_proto.Unmarshal(dAtA, msg); err != nil { + t.Fatalf("seed = %d, err = %v", seed, err) + } + littlefuzz := make([]byte, len(dAtA)) + copy(littlefuzz, dAtA) + for i := range dAtA { + dAtA[i] = byte(popr.Intn(256)) + } + if !p.Equal(msg) { + t.Fatalf("seed = %d, %#v !Proto %#v", seed, msg, p) + } + if len(littlefuzz) > 0 { + fuzzamount := 100 + for i := 0; i < fuzzamount; i++ { + littlefuzz[popr.Intn(len(littlefuzz))] = byte(popr.Intn(256)) + littlefuzz = append(littlefuzz, byte(popr.Intn(256))) + } + // shouldn't panic + _ = github_com_gogo_protobuf_proto.Unmarshal(littlefuzz, msg) + } +} + +func TestVersionMarshalTo(t *testing.T) { + seed := time.Now().UnixNano() + popr := math_rand.New(math_rand.NewSource(seed)) + p := NewPopulatedVersion(popr, false) + size := p.Size() + dAtA := make([]byte, size) + for i := range dAtA { + dAtA[i] = byte(popr.Intn(256)) + } + _, err := p.MarshalTo(dAtA) + if err != nil { + t.Fatalf("seed = %d, err = %v", seed, err) + } + msg := &Version{} + if err := github_com_gogo_protobuf_proto.Unmarshal(dAtA, msg); err != nil { + t.Fatalf("seed = %d, err = %v", seed, err) + } + for i := range dAtA { + dAtA[i] = byte(popr.Intn(256)) + } + if !p.Equal(msg) { + t.Fatalf("seed = %d, %#v !Proto %#v", seed, msg, p) + } +} + func TestBlockIDProto(t *testing.T) { seed := time.Now().UnixNano() popr := math_rand.New(math_rand.NewSource(seed)) @@ -2635,6 +2691,24 @@ func TestHeaderJSON(t *testing.T) { t.Fatalf("seed = %d, %#v !Json Equal %#v", seed, msg, p) } } +func TestVersionJSON(t *testing.T) { + seed := time.Now().UnixNano() + popr := math_rand.New(math_rand.NewSource(seed)) + p := NewPopulatedVersion(popr, true) + marshaler := github_com_gogo_protobuf_jsonpb.Marshaler{} + jsondata, err := marshaler.MarshalToString(p) + if err != nil { + t.Fatalf("seed = %d, err = %v", seed, err) + } + msg := &Version{} + err = github_com_gogo_protobuf_jsonpb.UnmarshalString(jsondata, msg) + if err != nil { + t.Fatalf("seed = %d, err = %v", seed, err) + } + if !p.Equal(msg) { + t.Fatalf("seed = %d, %#v !Json Equal %#v", seed, msg, p) + } +} func TestBlockIDJSON(t *testing.T) { seed := time.Now().UnixNano() popr := math_rand.New(math_rand.NewSource(seed)) @@ -3601,6 +3675,34 @@ func TestHeaderProtoCompactText(t *testing.T) { } } +func TestVersionProtoText(t *testing.T) { + seed := time.Now().UnixNano() + popr := math_rand.New(math_rand.NewSource(seed)) + p := NewPopulatedVersion(popr, true) + dAtA := github_com_gogo_protobuf_proto.MarshalTextString(p) + msg := &Version{} + if err := github_com_gogo_protobuf_proto.UnmarshalText(dAtA, msg); err != nil { + t.Fatalf("seed = %d, err = %v", seed, err) + } + if !p.Equal(msg) { + t.Fatalf("seed = %d, %#v !Proto %#v", seed, msg, p) + } +} + +func TestVersionProtoCompactText(t *testing.T) { + seed := time.Now().UnixNano() + popr := math_rand.New(math_rand.NewSource(seed)) + p := NewPopulatedVersion(popr, true) + dAtA := github_com_gogo_protobuf_proto.CompactTextString(p) + msg := &Version{} + if err := github_com_gogo_protobuf_proto.UnmarshalText(dAtA, msg); err != nil { + t.Fatalf("seed = %d, err = %v", seed, err) + } + if !p.Equal(msg) { + t.Fatalf("seed = %d, %#v !Proto %#v", seed, msg, p) + } +} + func TestBlockIDProtoText(t *testing.T) { seed := time.Now().UnixNano() popr := math_rand.New(math_rand.NewSource(seed)) @@ -4457,6 +4559,28 @@ func TestHeaderSize(t *testing.T) { } } +func TestVersionSize(t *testing.T) { + seed := time.Now().UnixNano() + popr := math_rand.New(math_rand.NewSource(seed)) + p := NewPopulatedVersion(popr, true) + size2 := github_com_gogo_protobuf_proto.Size(p) + dAtA, err := github_com_gogo_protobuf_proto.Marshal(p) + if err != nil { + t.Fatalf("seed = %d, err = %v", seed, err) + } + size := p.Size() + if len(dAtA) != size { + t.Errorf("seed = %d, size %v != marshalled size %v", seed, size, len(dAtA)) + } + if size2 != size { + t.Errorf("seed = %d, size %v != before marshal proto.Size %v", seed, size, size2) + } + size3 := github_com_gogo_protobuf_proto.Size(p) + if size3 != size { + t.Errorf("seed = %d, size %v != after marshal proto.Size %v", seed, size, size3) + } +} + func TestBlockIDSize(t *testing.T) { seed := time.Now().UnixNano() popr := math_rand.New(math_rand.NewSource(seed)) diff --git a/docs/spec/abci/abci.md b/docs/spec/abci/abci.md index 15e24624..54b7c899 100644 --- a/docs/spec/abci/abci.md +++ b/docs/spec/abci/abci.md @@ -338,6 +338,7 @@ Commit are included in the header of the next block. ### Header - **Fields**: + - `Version (Version)`: Version of the blockchain and the application - `ChainID (string)`: ID of the blockchain - `Height (int64)`: Height of the block in the chain - `Time (google.protobuf.Timestamp)`: Time of the block. It is the proposer's @@ -363,6 +364,15 @@ Commit are included in the header of the next block. - Provides the proposer of the current block, for use in proposer-based reward mechanisms. +### Version + +- **Fields**: + - `Block (uint64)`: Protocol version of the blockchain data structures. + - `App (uint64)`: Protocol version of the application. +- **Usage**: + - Block version should be static in the life of a blockchain. + - App version may be updated over time by the application. + ### Validator - **Fields**: diff --git a/docs/spec/blockchain/blockchain.md b/docs/spec/blockchain/blockchain.md index 029b64fa..c5291ed4 100644 --- a/docs/spec/blockchain/blockchain.md +++ b/docs/spec/blockchain/blockchain.md @@ -8,6 +8,7 @@ The Tendermint blockchains consists of a short list of basic data types: - `Block` - `Header` +- `Version` - `BlockID` - `Time` - `Data` (for transactions) @@ -38,6 +39,7 @@ the data in the current block, the previous block, and the results returned by t ```go type Header struct { // basic block info + Version Version ChainID string Height int64 Time Time @@ -65,6 +67,19 @@ type Header struct { Further details on each of these fields is described below. +## Version + +The `Version` contains the protocol version for the blockchain and the +application as two `uint64` values: + +```go +type Version struct { + Block uint64 + App uint64 +} +``` + + ## BlockID The `BlockID` contains two distinct Merkle roots of the block. @@ -200,6 +215,15 @@ See [here](https://github.com/tendermint/tendermint/blob/master/docs/spec/blockc A Header is valid if its corresponding fields are valid. +### Version + +``` +block.Version.Block == state.Version.Block +block.Version.App == state.Version.App +``` + +The block version must match the state version. + ### ChainID ``` diff --git a/docs/spec/blockchain/state.md b/docs/spec/blockchain/state.md index e904bb33..a0badd71 100644 --- a/docs/spec/blockchain/state.md +++ b/docs/spec/blockchain/state.md @@ -15,6 +15,7 @@ validation. ```go type State struct { + Version Version LastResults []Result AppHash []byte diff --git a/node/node.go b/node/node.go index 9939f1c6..12e0b8e6 100644 --- a/node/node.go +++ b/node/node.go @@ -208,6 +208,15 @@ func NewNode(config *cfg.Config, // reload the state (it may have been updated by the handshake) state = sm.LoadState(stateDB) + 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, + ) + } + // If an address is provided, listen on the socket for a // connection from an external signing process. if config.PrivValidatorListenAddr != "" { diff --git a/state/execution.go b/state/execution.go index 611efa51..68298a8d 100644 --- a/state/execution.go +++ b/state/execution.go @@ -398,9 +398,13 @@ func updateState( lastHeightParamsChanged = header.Height + 1 } + // TODO: allow app to upgrade version + nextVersion := state.Version + // NOTE: the AppHash has not been populated. // It will be filled on state.Save. return State{ + Version: nextVersion, ChainID: state.ChainID, LastBlockHeight: header.Height, LastBlockTotalTx: state.LastBlockTotalTx + header.NumTxs, diff --git a/state/state.go b/state/state.go index 23c0d632..aedb2b00 100644 --- a/state/state.go +++ b/state/state.go @@ -8,6 +8,7 @@ import ( "github.com/tendermint/tendermint/types" tmtime "github.com/tendermint/tendermint/types/time" + "github.com/tendermint/tendermint/version" ) // database keys @@ -17,6 +18,25 @@ var ( //----------------------------------------------------------------------------- +// Version is for versioning the State. +// It holds the Block and App version needed for making blocks, +// and the software version to support upgrades to the format of +// the State as stored on disk. +type Version struct { + Consensus version.Consensus + Software string +} + +var initStateVersion = Version{ + Consensus: version.Consensus{ + Block: version.BlockProtocol, + App: 0, + }, + Software: version.TMCoreSemVer, +} + +//----------------------------------------------------------------------------- + // State is a short description of the latest committed block of the Tendermint consensus. // It keeps all information necessary to validate new blocks, // including the last validator set and the consensus params. @@ -25,6 +45,8 @@ var ( // Instead, use state.Copy() or state.NextState(...). // NOTE: not goroutine-safe. type State struct { + Version Version + // immutable ChainID string @@ -59,6 +81,7 @@ type State struct { // Copy makes a copy of the State for mutating. func (state State) Copy() State { return State{ + Version: state.Version, ChainID: state.ChainID, LastBlockHeight: state.LastBlockHeight, @@ -114,6 +137,7 @@ func (state State) MakeBlock( block := types.MakeBlock(height, txs, commit, evidence) // Fill rest of header with state data. + block.Version = state.Version.Consensus block.ChainID = state.ChainID // Set time @@ -217,7 +241,7 @@ func MakeGenesisState(genDoc *types.GenesisDoc) (State, error) { } return State{ - + Version: initStateVersion, ChainID: genDoc.ChainID, LastBlockHeight: 0, diff --git a/state/state_test.go b/state/state_test.go index 2c777307..b1f24d30 100644 --- a/state/state_test.go +++ b/state/state_test.go @@ -319,9 +319,11 @@ func TestStateMakeBlock(t *testing.T) { defer tearDown(t) proposerAddress := state.Validators.GetProposer().Address + stateVersion := state.Version.Consensus block := makeBlock(state, 2) - // test we set proposer address + // test we set some fields + assert.Equal(t, stateVersion, block.Version) assert.Equal(t, proposerAddress, block.ProposerAddress) } diff --git a/state/validation.go b/state/validation.go index a308870e..ff1791e2 100644 --- a/state/validation.go +++ b/state/validation.go @@ -20,6 +20,13 @@ func validateBlock(stateDB dbm.DB, state State, block *types.Block) error { } // Validate basic info. + if block.Version != state.Version.Consensus { + return fmt.Errorf( + "Wrong Block.Header.Version. Expected %v, got %v", + state.Version.Consensus, + block.Version, + ) + } if block.ChainID != state.ChainID { return fmt.Errorf( "Wrong Block.Header.ChainID. Expected %v, got %v", diff --git a/state/validation_test.go b/state/validation_test.go index 3c58c713..f89fbdea 100644 --- a/state/validation_test.go +++ b/state/validation_test.go @@ -5,6 +5,7 @@ import ( "time" "github.com/stretchr/testify/require" + "github.com/tendermint/tendermint/crypto/ed25519" "github.com/tendermint/tendermint/crypto/tmhash" "github.com/tendermint/tendermint/libs/log" @@ -26,13 +27,20 @@ func TestValidateBlockHeader(t *testing.T) { err := blockExec.ValidateBlock(state, block) require.NoError(t, err) + // some bad values wrongHash := tmhash.Sum([]byte("this hash is wrong")) + wrongVersion1 := state.Version.Consensus + wrongVersion1.Block += 1 + wrongVersion2 := state.Version.Consensus + wrongVersion2.App += 1 // Manipulation of any header field causes failure. testCases := []struct { name string malleateBlock func(block *types.Block) }{ + {"Version wrong1", func(block *types.Block) { block.Version = wrongVersion1 }}, + {"Version wrong2", func(block *types.Block) { block.Version = wrongVersion2 }}, {"ChainID wrong", func(block *types.Block) { block.ChainID = "not-the-real-one" }}, {"Height wrong", func(block *types.Block) { block.Height += 10 }}, {"Time wrong", func(block *types.Block) { block.Time = block.Time.Add(-time.Second * 3600 * 24) }}, diff --git a/types/block.go b/types/block.go index 45a5b8c3..06ad55fc 100644 --- a/types/block.go +++ b/types/block.go @@ -10,11 +10,12 @@ import ( "github.com/tendermint/tendermint/crypto/merkle" cmn "github.com/tendermint/tendermint/libs/common" + "github.com/tendermint/tendermint/version" ) const ( // MaxHeaderBytes is a maximum header size (including amino overhead). - MaxHeaderBytes int64 = 511 + MaxHeaderBytes int64 = 534 // MaxAminoOverheadForBlock - maximum amino overhead to encode a block (up to // MaxBlockSizeBytes in size) not including it's parts except Data. @@ -27,7 +28,6 @@ const ( ) // Block defines the atomic unit of a Tendermint blockchain. -// TODO: add Version byte type Block struct { mtx sync.Mutex Header `json:"header"` @@ -258,16 +258,16 @@ func MaxDataBytesUnknownEvidence(maxBytes int64, valsCount int) int64 { //----------------------------------------------------------------------------- // Header defines the structure of a Tendermint block header -// TODO: limit header size // NOTE: changes to the Header should be duplicated in the abci Header // and in /docs/spec/blockchain/blockchain.md type Header struct { // basic block info - ChainID string `json:"chain_id"` - Height int64 `json:"height"` - Time time.Time `json:"time"` - NumTxs int64 `json:"num_txs"` - TotalTxs int64 `json:"total_txs"` + Version version.Consensus `json:"version"` + ChainID string `json:"chain_id"` + Height int64 `json:"height"` + Time time.Time `json:"time"` + NumTxs int64 `json:"num_txs"` + TotalTxs int64 `json:"total_txs"` // prev block info LastBlockID BlockID `json:"last_block_id"` @@ -297,6 +297,7 @@ func (h *Header) Hash() cmn.HexBytes { return nil } return merkle.SimpleHashFromMap(map[string][]byte{ + "Version": cdcEncode(h.Version), "ChainID": cdcEncode(h.ChainID), "Height": cdcEncode(h.Height), "Time": cdcEncode(h.Time), @@ -321,6 +322,7 @@ func (h *Header) StringIndented(indent string) string { return "nil-Header" } return fmt.Sprintf(`Header{ +%s Version: %v %s ChainID: %v %s Height: %v %s Time: %v @@ -337,6 +339,7 @@ func (h *Header) StringIndented(indent string) string { %s Evidence: %v %s Proposer: %v %s}#%v`, + indent, h.Version, indent, h.ChainID, indent, h.Height, indent, h.Time, @@ -538,6 +541,7 @@ func (sh SignedHeader) ValidateBasic(chainID string) error { if sh.Commit == nil { return errors.New("SignedHeader missing commit (precommit votes).") } + // Check ChainID. if sh.ChainID != chainID { return fmt.Errorf("Header belongs to another chain '%s' not '%s'", diff --git a/types/block_test.go b/types/block_test.go index 7abd79d7..d268e411 100644 --- a/types/block_test.go +++ b/types/block_test.go @@ -12,6 +12,7 @@ import ( "github.com/tendermint/tendermint/crypto/tmhash" cmn "github.com/tendermint/tendermint/libs/common" + "github.com/tendermint/tendermint/version" ) func TestMain(m *testing.M) { @@ -242,6 +243,7 @@ func TestMaxHeaderBytes(t *testing.T) { } h := Header{ + Version: version.Consensus{math.MaxInt64, math.MaxInt64}, ChainID: maxChainID, Height: math.MaxInt64, Time: time.Now().UTC(), @@ -286,9 +288,9 @@ func TestBlockMaxDataBytes(t *testing.T) { }{ 0: {-10, 1, 0, true, 0}, 1: {10, 1, 0, true, 0}, - 2: {721, 1, 0, true, 0}, - 3: {722, 1, 0, false, 0}, - 4: {723, 1, 0, false, 1}, + 2: {744, 1, 0, true, 0}, + 3: {745, 1, 0, false, 0}, + 4: {746, 1, 0, false, 1}, } for i, tc := range testCases { @@ -314,9 +316,9 @@ func TestBlockMaxDataBytesUnknownEvidence(t *testing.T) { }{ 0: {-10, 1, true, 0}, 1: {10, 1, true, 0}, - 2: {801, 1, true, 0}, - 3: {802, 1, false, 0}, - 4: {803, 1, false, 1}, + 2: {826, 1, true, 0}, + 3: {827, 1, false, 0}, + 4: {828, 1, false, 1}, } for i, tc := range testCases { diff --git a/types/proto3/block.pb.go b/types/proto3/block.pb.go index ab1c66cf..446b3919 100644 --- a/types/proto3/block.pb.go +++ b/types/proto3/block.pb.go @@ -1,7 +1,19 @@ // Code generated by protoc-gen-go. DO NOT EDIT. -// source: types/proto3/block.proto +// source: block.proto -//nolint +/* +Package proto3 is a generated protocol buffer package. + +It is generated from these files: + block.proto + +It has these top-level messages: + PartSetHeader + BlockID + Header + Version + Timestamp +*/ package proto3 import proto "github.com/golang/protobuf/proto" @@ -20,36 +32,14 @@ var _ = math.Inf const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package type PartSetHeader struct { - Total int32 `protobuf:"zigzag32,1,opt,name=Total,proto3" json:"Total,omitempty"` - Hash []byte `protobuf:"bytes,2,opt,name=Hash,proto3" json:"Hash,omitempty"` - XXX_NoUnkeyedLiteral struct{} `json:"-"` - XXX_unrecognized []byte `json:"-"` - XXX_sizecache int32 `json:"-"` + Total int32 `protobuf:"zigzag32,1,opt,name=Total" json:"Total,omitempty"` + Hash []byte `protobuf:"bytes,2,opt,name=Hash,proto3" json:"Hash,omitempty"` } -func (m *PartSetHeader) Reset() { *m = PartSetHeader{} } -func (m *PartSetHeader) String() string { return proto.CompactTextString(m) } -func (*PartSetHeader) ProtoMessage() {} -func (*PartSetHeader) Descriptor() ([]byte, []int) { - return fileDescriptor_block_c8c1dcbe91697ccd, []int{0} -} -func (m *PartSetHeader) XXX_Unmarshal(b []byte) error { - return xxx_messageInfo_PartSetHeader.Unmarshal(m, b) -} -func (m *PartSetHeader) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { - return xxx_messageInfo_PartSetHeader.Marshal(b, m, deterministic) -} -func (dst *PartSetHeader) XXX_Merge(src proto.Message) { - xxx_messageInfo_PartSetHeader.Merge(dst, src) -} -func (m *PartSetHeader) XXX_Size() int { - return xxx_messageInfo_PartSetHeader.Size(m) -} -func (m *PartSetHeader) XXX_DiscardUnknown() { - xxx_messageInfo_PartSetHeader.DiscardUnknown(m) -} - -var xxx_messageInfo_PartSetHeader proto.InternalMessageInfo +func (m *PartSetHeader) Reset() { *m = PartSetHeader{} } +func (m *PartSetHeader) String() string { return proto.CompactTextString(m) } +func (*PartSetHeader) ProtoMessage() {} +func (*PartSetHeader) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{0} } func (m *PartSetHeader) GetTotal() int32 { if m != nil { @@ -66,36 +56,14 @@ func (m *PartSetHeader) GetHash() []byte { } type BlockID struct { - Hash []byte `protobuf:"bytes,1,opt,name=Hash,proto3" json:"Hash,omitempty"` - PartsHeader *PartSetHeader `protobuf:"bytes,2,opt,name=PartsHeader,proto3" json:"PartsHeader,omitempty"` - XXX_NoUnkeyedLiteral struct{} `json:"-"` - XXX_unrecognized []byte `json:"-"` - XXX_sizecache int32 `json:"-"` + Hash []byte `protobuf:"bytes,1,opt,name=Hash,proto3" json:"Hash,omitempty"` + PartsHeader *PartSetHeader `protobuf:"bytes,2,opt,name=PartsHeader" json:"PartsHeader,omitempty"` } -func (m *BlockID) Reset() { *m = BlockID{} } -func (m *BlockID) String() string { return proto.CompactTextString(m) } -func (*BlockID) ProtoMessage() {} -func (*BlockID) Descriptor() ([]byte, []int) { - return fileDescriptor_block_c8c1dcbe91697ccd, []int{1} -} -func (m *BlockID) XXX_Unmarshal(b []byte) error { - return xxx_messageInfo_BlockID.Unmarshal(m, b) -} -func (m *BlockID) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { - return xxx_messageInfo_BlockID.Marshal(b, m, deterministic) -} -func (dst *BlockID) XXX_Merge(src proto.Message) { - xxx_messageInfo_BlockID.Merge(dst, src) -} -func (m *BlockID) XXX_Size() int { - return xxx_messageInfo_BlockID.Size(m) -} -func (m *BlockID) XXX_DiscardUnknown() { - xxx_messageInfo_BlockID.DiscardUnknown(m) -} - -var xxx_messageInfo_BlockID proto.InternalMessageInfo +func (m *BlockID) Reset() { *m = BlockID{} } +func (m *BlockID) String() string { return proto.CompactTextString(m) } +func (*BlockID) ProtoMessage() {} +func (*BlockID) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{1} } func (m *BlockID) GetHash() []byte { if m != nil { @@ -113,52 +81,39 @@ func (m *BlockID) GetPartsHeader() *PartSetHeader { type Header struct { // basic block info - ChainID string `protobuf:"bytes,1,opt,name=ChainID,proto3" json:"ChainID,omitempty"` - Height int64 `protobuf:"zigzag64,2,opt,name=Height,proto3" json:"Height,omitempty"` - Time *Timestamp `protobuf:"bytes,3,opt,name=Time,proto3" json:"Time,omitempty"` - NumTxs int64 `protobuf:"zigzag64,4,opt,name=NumTxs,proto3" json:"NumTxs,omitempty"` - TotalTxs int64 `protobuf:"zigzag64,5,opt,name=TotalTxs,proto3" json:"TotalTxs,omitempty"` + Version *Version `protobuf:"bytes,1,opt,name=Version" json:"Version,omitempty"` + ChainID string `protobuf:"bytes,2,opt,name=ChainID" json:"ChainID,omitempty"` + Height int64 `protobuf:"zigzag64,3,opt,name=Height" json:"Height,omitempty"` + Time *Timestamp `protobuf:"bytes,4,opt,name=Time" json:"Time,omitempty"` + NumTxs int64 `protobuf:"zigzag64,5,opt,name=NumTxs" json:"NumTxs,omitempty"` + TotalTxs int64 `protobuf:"zigzag64,6,opt,name=TotalTxs" json:"TotalTxs,omitempty"` // prev block info - LastBlockID *BlockID `protobuf:"bytes,6,opt,name=LastBlockID,proto3" json:"LastBlockID,omitempty"` + LastBlockID *BlockID `protobuf:"bytes,7,opt,name=LastBlockID" json:"LastBlockID,omitempty"` // hashes of block data - LastCommitHash []byte `protobuf:"bytes,7,opt,name=LastCommitHash,proto3" json:"LastCommitHash,omitempty"` - DataHash []byte `protobuf:"bytes,8,opt,name=DataHash,proto3" json:"DataHash,omitempty"` + LastCommitHash []byte `protobuf:"bytes,8,opt,name=LastCommitHash,proto3" json:"LastCommitHash,omitempty"` + DataHash []byte `protobuf:"bytes,9,opt,name=DataHash,proto3" json:"DataHash,omitempty"` // hashes from the app output from the prev block - ValidatorsHash []byte `protobuf:"bytes,9,opt,name=ValidatorsHash,proto3" json:"ValidatorsHash,omitempty"` - ConsensusHash []byte `protobuf:"bytes,10,opt,name=ConsensusHash,proto3" json:"ConsensusHash,omitempty"` - AppHash []byte `protobuf:"bytes,11,opt,name=AppHash,proto3" json:"AppHash,omitempty"` - LastResultsHash []byte `protobuf:"bytes,12,opt,name=LastResultsHash,proto3" json:"LastResultsHash,omitempty"` + ValidatorsHash []byte `protobuf:"bytes,10,opt,name=ValidatorsHash,proto3" json:"ValidatorsHash,omitempty"` + NextValidatorsHash []byte `protobuf:"bytes,11,opt,name=NextValidatorsHash,proto3" json:"NextValidatorsHash,omitempty"` + ConsensusHash []byte `protobuf:"bytes,12,opt,name=ConsensusHash,proto3" json:"ConsensusHash,omitempty"` + AppHash []byte `protobuf:"bytes,13,opt,name=AppHash,proto3" json:"AppHash,omitempty"` + LastResultsHash []byte `protobuf:"bytes,14,opt,name=LastResultsHash,proto3" json:"LastResultsHash,omitempty"` // consensus info - EvidenceHash []byte `protobuf:"bytes,13,opt,name=EvidenceHash,proto3" json:"EvidenceHash,omitempty"` - ProposerAddress []byte `protobuf:"bytes,14,opt,name=ProposerAddress,proto3" json:"ProposerAddress,omitempty"` - XXX_NoUnkeyedLiteral struct{} `json:"-"` - XXX_unrecognized []byte `json:"-"` - XXX_sizecache int32 `json:"-"` + EvidenceHash []byte `protobuf:"bytes,15,opt,name=EvidenceHash,proto3" json:"EvidenceHash,omitempty"` + ProposerAddress []byte `protobuf:"bytes,16,opt,name=ProposerAddress,proto3" json:"ProposerAddress,omitempty"` } -func (m *Header) Reset() { *m = Header{} } -func (m *Header) String() string { return proto.CompactTextString(m) } -func (*Header) ProtoMessage() {} -func (*Header) Descriptor() ([]byte, []int) { - return fileDescriptor_block_c8c1dcbe91697ccd, []int{2} -} -func (m *Header) XXX_Unmarshal(b []byte) error { - return xxx_messageInfo_Header.Unmarshal(m, b) -} -func (m *Header) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { - return xxx_messageInfo_Header.Marshal(b, m, deterministic) -} -func (dst *Header) XXX_Merge(src proto.Message) { - xxx_messageInfo_Header.Merge(dst, src) -} -func (m *Header) XXX_Size() int { - return xxx_messageInfo_Header.Size(m) -} -func (m *Header) XXX_DiscardUnknown() { - xxx_messageInfo_Header.DiscardUnknown(m) -} +func (m *Header) Reset() { *m = Header{} } +func (m *Header) String() string { return proto.CompactTextString(m) } +func (*Header) ProtoMessage() {} +func (*Header) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{2} } -var xxx_messageInfo_Header proto.InternalMessageInfo +func (m *Header) GetVersion() *Version { + if m != nil { + return m.Version + } + return nil +} func (m *Header) GetChainID() string { if m != nil { @@ -223,6 +178,13 @@ func (m *Header) GetValidatorsHash() []byte { return nil } +func (m *Header) GetNextValidatorsHash() []byte { + if m != nil { + return m.NextValidatorsHash + } + return nil +} + func (m *Header) GetConsensusHash() []byte { if m != nil { return m.ConsensusHash @@ -258,41 +220,43 @@ func (m *Header) GetProposerAddress() []byte { return nil } +type Version struct { + Block uint64 `protobuf:"varint,1,opt,name=Block" json:"Block,omitempty"` + App uint64 `protobuf:"varint,2,opt,name=App" json:"App,omitempty"` +} + +func (m *Version) Reset() { *m = Version{} } +func (m *Version) String() string { return proto.CompactTextString(m) } +func (*Version) ProtoMessage() {} +func (*Version) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{3} } + +func (m *Version) GetBlock() uint64 { + if m != nil { + return m.Block + } + return 0 +} + +func (m *Version) GetApp() uint64 { + if m != nil { + return m.App + } + return 0 +} + // Timestamp wraps how amino encodes time. Note that this is different from the protobuf well-known type // protobuf/timestamp.proto in the sense that there seconds and nanos are varint encoded. See: // https://github.com/google/protobuf/blob/d2980062c859649523d5fd51d6b55ab310e47482/src/google/protobuf/timestamp.proto#L123-L135 // Also nanos do not get skipped if they are zero in amino. type Timestamp struct { - Seconds int64 `protobuf:"fixed64,1,opt,name=seconds,proto3" json:"seconds,omitempty"` - Nanos int32 `protobuf:"fixed32,2,opt,name=nanos,proto3" json:"nanos,omitempty"` - XXX_NoUnkeyedLiteral struct{} `json:"-"` - XXX_unrecognized []byte `json:"-"` - XXX_sizecache int32 `json:"-"` + Seconds int64 `protobuf:"fixed64,1,opt,name=seconds" json:"seconds,omitempty"` + Nanos int32 `protobuf:"fixed32,2,opt,name=nanos" json:"nanos,omitempty"` } -func (m *Timestamp) Reset() { *m = Timestamp{} } -func (m *Timestamp) String() string { return proto.CompactTextString(m) } -func (*Timestamp) ProtoMessage() {} -func (*Timestamp) Descriptor() ([]byte, []int) { - return fileDescriptor_block_c8c1dcbe91697ccd, []int{3} -} -func (m *Timestamp) XXX_Unmarshal(b []byte) error { - return xxx_messageInfo_Timestamp.Unmarshal(m, b) -} -func (m *Timestamp) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { - return xxx_messageInfo_Timestamp.Marshal(b, m, deterministic) -} -func (dst *Timestamp) XXX_Merge(src proto.Message) { - xxx_messageInfo_Timestamp.Merge(dst, src) -} -func (m *Timestamp) XXX_Size() int { - return xxx_messageInfo_Timestamp.Size(m) -} -func (m *Timestamp) XXX_DiscardUnknown() { - xxx_messageInfo_Timestamp.DiscardUnknown(m) -} - -var xxx_messageInfo_Timestamp proto.InternalMessageInfo +func (m *Timestamp) Reset() { *m = Timestamp{} } +func (m *Timestamp) String() string { return proto.CompactTextString(m) } +func (*Timestamp) ProtoMessage() {} +func (*Timestamp) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{4} } func (m *Timestamp) GetSeconds() int64 { if m != nil { @@ -312,36 +276,40 @@ func init() { proto.RegisterType((*PartSetHeader)(nil), "proto3.PartSetHeader") proto.RegisterType((*BlockID)(nil), "proto3.BlockID") proto.RegisterType((*Header)(nil), "proto3.Header") + proto.RegisterType((*Version)(nil), "proto3.Version") proto.RegisterType((*Timestamp)(nil), "proto3.Timestamp") } -func init() { proto.RegisterFile("types/proto3/block.proto", fileDescriptor_block_c8c1dcbe91697ccd) } +func init() { proto.RegisterFile("block.proto", fileDescriptor0) } -var fileDescriptor_block_c8c1dcbe91697ccd = []byte{ - // 395 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x5c, 0x52, 0x4b, 0x8b, 0xdb, 0x30, - 0x10, 0xc6, 0xcd, 0x7b, 0x9c, 0x47, 0x23, 0xda, 0x22, 0x7a, 0x0a, 0xa6, 0x2d, 0x39, 0x25, 0xb4, - 0x39, 0x94, 0xd2, 0x53, 0x9a, 0x14, 0x12, 0x28, 0x25, 0x68, 0x43, 0xee, 0x4a, 0x2c, 0x36, 0x66, - 0x6d, 0xcb, 0x78, 0x94, 0x65, 0xf7, 0x3f, 0xef, 0x8f, 0x58, 0x34, 0xb2, 0xbd, 0x71, 0x6e, 0xfe, - 0x1e, 0xfa, 0x3e, 0x79, 0x46, 0xc0, 0xcd, 0x73, 0xa6, 0x70, 0x9e, 0xe5, 0xda, 0xe8, 0xc5, 0xfc, - 0x18, 0xeb, 0xd3, 0xc3, 0x8c, 0x00, 0x6b, 0x3b, 0x2e, 0xf8, 0x05, 0x83, 0x9d, 0xcc, 0xcd, 0x9d, - 0x32, 0x1b, 0x25, 0x43, 0x95, 0xb3, 0x0f, 0xd0, 0xda, 0x6b, 0x23, 0x63, 0xee, 0x4d, 0xbc, 0xe9, - 0x58, 0x38, 0xc0, 0x18, 0x34, 0x37, 0x12, 0xcf, 0xfc, 0xdd, 0xc4, 0x9b, 0xf6, 0x05, 0x7d, 0x07, - 0x07, 0xe8, 0xfc, 0xb1, 0x89, 0xdb, 0x75, 0x25, 0x7b, 0x6f, 0x32, 0xfb, 0x09, 0xbe, 0x4d, 0x46, - 0x97, 0x4b, 0x27, 0xfd, 0x1f, 0x1f, 0x5d, 0xfd, 0x62, 0x56, 0x2b, 0x15, 0xd7, 0xce, 0xe0, 0xa5, - 0x01, 0xed, 0xe2, 0x32, 0x1c, 0x3a, 0xab, 0xb3, 0x8c, 0xd2, 0xed, 0x9a, 0xa2, 0x7b, 0xa2, 0x84, - 0xec, 0x93, 0xf5, 0x44, 0xf7, 0x67, 0x43, 0xc1, 0x4c, 0x14, 0x88, 0x7d, 0x85, 0xe6, 0x3e, 0x4a, - 0x14, 0x6f, 0x50, 0xdd, 0xb8, 0xac, 0xb3, 0x1c, 0x1a, 0x99, 0x64, 0x82, 0x64, 0x7b, 0xfc, 0xff, - 0x25, 0xd9, 0x3f, 0x21, 0x6f, 0xba, 0xe3, 0x0e, 0xb1, 0xcf, 0xd0, 0xa5, 0x1f, 0xb6, 0x4a, 0x8b, - 0x94, 0x0a, 0xb3, 0xef, 0xe0, 0xff, 0x93, 0x68, 0x8a, 0x7f, 0xe6, 0x6d, 0x6a, 0x18, 0x95, 0x0d, - 0x05, 0x2d, 0xae, 0x3d, 0xec, 0x1b, 0x0c, 0x2d, 0x5c, 0xe9, 0x24, 0x89, 0x0c, 0x4d, 0xa8, 0x43, - 0x13, 0xba, 0x61, 0x6d, 0xed, 0x5a, 0x1a, 0x49, 0x8e, 0x2e, 0x39, 0x2a, 0x6c, 0x33, 0x0e, 0x32, - 0x8e, 0x42, 0x69, 0x74, 0x8e, 0xe4, 0xe8, 0xb9, 0x8c, 0x3a, 0xcb, 0xbe, 0xc0, 0x60, 0xa5, 0x53, - 0x54, 0x29, 0x5e, 0x9c, 0x0d, 0xc8, 0x56, 0x27, 0xed, 0x44, 0x97, 0x59, 0x46, 0xba, 0x4f, 0x7a, - 0x09, 0xd9, 0x14, 0x46, 0xf6, 0x56, 0x42, 0xe1, 0x25, 0x36, 0x2e, 0xa1, 0x4f, 0x8e, 0x5b, 0x9a, - 0x05, 0xd0, 0xff, 0xfb, 0x18, 0x85, 0x2a, 0x3d, 0x29, 0xb2, 0x0d, 0xc8, 0x56, 0xe3, 0x6c, 0xda, - 0x2e, 0xd7, 0x99, 0x46, 0x95, 0x2f, 0xc3, 0x30, 0x57, 0x88, 0x7c, 0xe8, 0xd2, 0x6e, 0xe8, 0xe0, - 0x37, 0xf4, 0xaa, 0xed, 0xd8, 0xeb, 0xa1, 0x3a, 0xe9, 0x34, 0x44, 0x5a, 0xf8, 0x7b, 0x51, 0x42, - 0xfb, 0x2e, 0x53, 0x99, 0x6a, 0xa4, 0x7d, 0x8f, 0x84, 0x03, 0xc7, 0xe2, 0x19, 0xbf, 0x06, 0x00, - 0x00, 0xff, 0xff, 0xde, 0x29, 0x34, 0x75, 0xe9, 0x02, 0x00, 0x00, +var fileDescriptor0 = []byte{ + // 443 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x6c, 0x53, 0xcd, 0x6a, 0xdb, 0x40, + 0x10, 0x46, 0x8d, 0x62, 0xc7, 0x23, 0x3b, 0x76, 0x86, 0xb6, 0x88, 0x9e, 0x8c, 0x68, 0x8b, 0x7b, + 0x31, 0x24, 0x39, 0x94, 0xd2, 0x93, 0x6b, 0x17, 0x12, 0x28, 0x21, 0x6c, 0x8d, 0xef, 0x1b, 0x6b, + 0xa9, 0x45, 0x2d, 0xad, 0xd0, 0xac, 0x4b, 0xde, 0xb0, 0xaf, 0x55, 0x66, 0x56, 0x52, 0x2d, 0x93, + 0x93, 0xf7, 0xfb, 0x99, 0x6f, 0x76, 0xc7, 0x23, 0x88, 0x9e, 0xf6, 0x76, 0xfb, 0x7b, 0x5e, 0x56, + 0xd6, 0x59, 0xec, 0xc9, 0xcf, 0x6d, 0xf2, 0x05, 0x46, 0x8f, 0xba, 0x72, 0x3f, 0x8d, 0xbb, 0x33, + 0x3a, 0x35, 0x15, 0xbe, 0x86, 0xf3, 0xb5, 0x75, 0x7a, 0x1f, 0x07, 0xd3, 0x60, 0x76, 0xa5, 0x3c, + 0x40, 0x84, 0xf0, 0x4e, 0xd3, 0x2e, 0x7e, 0x35, 0x0d, 0x66, 0x43, 0x25, 0xe7, 0x64, 0x03, 0xfd, + 0x6f, 0x9c, 0x78, 0xbf, 0x6a, 0xe5, 0xe0, 0xbf, 0x8c, 0x9f, 0x21, 0xe2, 0x64, 0xf2, 0xb9, 0x52, + 0x19, 0xdd, 0xbc, 0xf1, 0xed, 0x6f, 0xe7, 0x9d, 0xa6, 0xea, 0xd8, 0x99, 0xfc, 0x0d, 0xa1, 0x57, + 0x5f, 0xe6, 0x13, 0xf4, 0x37, 0xa6, 0xa2, 0xcc, 0x16, 0x12, 0x1d, 0xdd, 0x8c, 0x9b, 0xfa, 0x9a, + 0x56, 0x8d, 0x8e, 0x31, 0xf4, 0x97, 0x3b, 0x9d, 0x15, 0xf7, 0x2b, 0x69, 0x35, 0x50, 0x0d, 0xc4, + 0xb7, 0x1c, 0x97, 0xfd, 0xda, 0xb9, 0xf8, 0x6c, 0x1a, 0xcc, 0x50, 0xd5, 0x08, 0x3f, 0x40, 0xb8, + 0xce, 0x72, 0x13, 0x87, 0x92, 0x7c, 0xd5, 0x24, 0x33, 0x47, 0x4e, 0xe7, 0xa5, 0x12, 0x99, 0xcb, + 0x1f, 0x0e, 0xf9, 0xfa, 0x99, 0xe2, 0x73, 0x5f, 0xee, 0x11, 0xbe, 0x83, 0x0b, 0x99, 0x0d, 0x2b, + 0x3d, 0x51, 0x5a, 0x8c, 0xd7, 0x10, 0xfd, 0xd0, 0xe4, 0xea, 0xf1, 0xc4, 0xfd, 0xee, 0xdd, 0x6b, + 0x5a, 0x1d, 0x7b, 0xf0, 0x23, 0x5c, 0x32, 0x5c, 0xda, 0x3c, 0xcf, 0x9c, 0x0c, 0xf3, 0x42, 0x86, + 0x79, 0xc2, 0x72, 0xdb, 0x95, 0x76, 0x5a, 0x1c, 0x03, 0x71, 0xb4, 0x98, 0x33, 0x36, 0x7a, 0x9f, + 0xa5, 0xda, 0xd9, 0x8a, 0xc4, 0x01, 0x3e, 0xa3, 0xcb, 0xe2, 0x1c, 0xf0, 0xc1, 0x3c, 0xbb, 0x13, + 0x6f, 0x24, 0xde, 0x17, 0x14, 0x7c, 0x0f, 0xa3, 0xa5, 0x2d, 0xc8, 0x14, 0x74, 0xf0, 0xd6, 0xa1, + 0x58, 0xbb, 0x24, 0xff, 0x03, 0x8b, 0xb2, 0x14, 0x7d, 0x24, 0x7a, 0x03, 0x71, 0x06, 0x63, 0x7e, + 0x85, 0x32, 0x74, 0xd8, 0x3b, 0x9f, 0x70, 0x29, 0x8e, 0x53, 0x1a, 0x13, 0x18, 0x7e, 0xff, 0x93, + 0xa5, 0xa6, 0xd8, 0x1a, 0xb1, 0x8d, 0xc5, 0xd6, 0xe1, 0x38, 0xed, 0xb1, 0xb2, 0xa5, 0x25, 0x53, + 0x2d, 0xd2, 0xb4, 0x32, 0x44, 0xf1, 0xc4, 0xa7, 0x9d, 0xd0, 0xc9, 0x75, 0xbb, 0x3e, 0xbc, 0xd6, + 0x32, 0x69, 0xd9, 0xa3, 0x50, 0x79, 0x80, 0x13, 0x38, 0x5b, 0x94, 0xa5, 0x2c, 0x4c, 0xa8, 0xf8, + 0x98, 0x7c, 0x85, 0x41, 0xbb, 0x00, 0xfc, 0x22, 0x32, 0x5b, 0x5b, 0xa4, 0x24, 0x65, 0x13, 0xd5, + 0x40, 0x8e, 0x2b, 0x74, 0x61, 0x49, 0x4a, 0xc7, 0xca, 0x83, 0xa7, 0xfa, 0xa3, 0xfa, 0x17, 0x00, + 0x00, 0xff, 0xff, 0xd5, 0x8b, 0x28, 0x26, 0x6a, 0x03, 0x00, 0x00, } diff --git a/types/proto3/block.proto b/types/proto3/block.proto index 835d6b74..dd64a9e9 100644 --- a/types/proto3/block.proto +++ b/types/proto3/block.proto @@ -15,29 +15,35 @@ message BlockID { message Header { // basic block info - string ChainID = 1; - sint64 Height = 2; - Timestamp Time = 3; - sint64 NumTxs = 4; - sint64 TotalTxs = 5; + Version Version = 1; + string ChainID = 2; + sint64 Height = 3; + Timestamp Time = 4; + sint64 NumTxs = 5; + sint64 TotalTxs = 6; // prev block info - BlockID LastBlockID = 6; + BlockID LastBlockID = 7; // hashes of block data - bytes LastCommitHash = 7; // commit from validators from the last block - bytes DataHash = 8; // transactions + bytes LastCommitHash = 8; // commit from validators from the last block + bytes DataHash = 9; // transactions // hashes from the app output from the prev block - bytes ValidatorsHash = 9; // validators for the current block - bytes NextValidatorsHash = 10; // validators for the next block - bytes ConsensusHash = 11; // consensus params for current block - bytes AppHash = 12; // state after txs from the previous block - bytes LastResultsHash = 13; // root hash of all results from the txs from the previous block + bytes ValidatorsHash = 10; // validators for the current block + bytes NextValidatorsHash = 11; // validators for the next block + bytes ConsensusHash = 12; // consensus params for current block + bytes AppHash = 13; // state after txs from the previous block + bytes LastResultsHash = 14; // root hash of all results from the txs from the previous block // consensus info - bytes EvidenceHash = 14; // evidence included in the block - bytes ProposerAddress = 15; // original proposer of the block + bytes EvidenceHash = 15; // evidence included in the block + bytes ProposerAddress = 16; // original proposer of the block +} + +message Version { + uint64 Block = 1; + uint64 App = 2; } // Timestamp wraps how amino encodes time. Note that this is different from the protobuf well-known type From 14c1baeb246055743c857fe8866eac0f17e325ea Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Thu, 18 Oct 2018 10:29:59 -0400 Subject: [PATCH 021/267] ADR-016: Add protocol Version to NodeInfo (#2654) * p2p: add protocol Version to NodeInfo * update node pkg. remove extraneous version files * update changelog and docs * fix test * p2p: Version -> ProtocolVersion; more ValidateBasic and tests --- CHANGELOG_PENDING.md | 5 ++ benchmarks/codec_test.go | 15 +++-- consensus/version.go | 11 ---- docs/spec/p2p/peer.md | 16 +++-- node/node.go | 16 ++--- p2p/node_info.go | 135 +++++++++++++++++++++------------------ p2p/node_info_test.go | 123 +++++++++++++++++++++++++++++++++++ p2p/peer_test.go | 13 ++-- p2p/test_util.go | 17 +++-- p2p/version.go | 3 - rpc/core/status.go | 9 +-- rpc/core/version.go | 5 -- rpc/lib/version.go | 7 -- 13 files changed, 246 insertions(+), 129 deletions(-) delete mode 100644 consensus/version.go create mode 100644 p2p/node_info_test.go delete mode 100644 p2p/version.go delete mode 100644 rpc/core/version.go delete mode 100644 rpc/lib/version.go diff --git a/CHANGELOG_PENDING.md b/CHANGELOG_PENDING.md index 5fcbbb7b..f4858d95 100644 --- a/CHANGELOG_PENDING.md +++ b/CHANGELOG_PENDING.md @@ -14,6 +14,8 @@ BREAKING CHANGES: * [privval] \#2459 Split `SocketPVMsg`s implementations into Request and Response, where the Response may contain a error message (returned by the remote signer) * [state] \#2644 Add Version field to State, breaking the format of State as encoded on disk. + * [rpc] \#2654 Remove all `node_info.other.*_version` fields in `/status` and + `/net_info` * Apps * [abci] \#2298 ResponseQuery.Proof is now a structured merkle.Proof, not just @@ -43,6 +45,9 @@ BREAKING CHANGES: * [state] \#2644 Require block.Version to match state.Version * P2P Protocol + * [p2p] \#2654 Add `ProtocolVersion` struct with protocol versions to top of + DefaultNodeInfo and require `ProtocolVersion.Block` to match during peer handshake + FEATURES: - [crypto/merkle] \#2298 General Merkle Proof scheme for chaining various types of Merkle trees together diff --git a/benchmarks/codec_test.go b/benchmarks/codec_test.go index 71d7a83b..2be1db15 100644 --- a/benchmarks/codec_test.go +++ b/benchmarks/codec_test.go @@ -14,14 +14,15 @@ import ( func testNodeInfo(id p2p.ID) p2p.DefaultNodeInfo { return p2p.DefaultNodeInfo{ - ID_: id, - Moniker: "SOMENAME", - Network: "SOMENAME", - ListenAddr: "SOMEADDR", - Version: "SOMEVER", + ProtocolVersion: p2p.InitProtocolVersion, + ID_: id, + Moniker: "SOMENAME", + Network: "SOMENAME", + ListenAddr: "SOMEADDR", + Version: "SOMEVER", Other: p2p.DefaultNodeInfoOther{ - AminoVersion: "SOMESTRING", - P2PVersion: "OTHERSTRING", + TxIndex: "on", + RPCAddress: "0.0.0.0:26657", }, } } diff --git a/consensus/version.go b/consensus/version.go deleted file mode 100644 index c04d2ac7..00000000 --- a/consensus/version.go +++ /dev/null @@ -1,11 +0,0 @@ -package consensus - -import "fmt" - -// kind of arbitrary -var Spec = "1" // async -var Major = "0" // -var Minor = "2" // replay refactor -var Revision = "2" // validation -> commit - -var Version = fmt.Sprintf("v%s/%s.%s.%s", Spec, Major, Minor, Revision) diff --git a/docs/spec/p2p/peer.md b/docs/spec/p2p/peer.md index a1ff25d8..f5c2e7bf 100644 --- a/docs/spec/p2p/peer.md +++ b/docs/spec/p2p/peer.md @@ -75,22 +75,25 @@ The Tendermint Version Handshake allows the peers to exchange their NodeInfo: ```golang type NodeInfo struct { + Version p2p.Version ID p2p.ID ListenAddr string Network string - Version string + SoftwareVersion string Channels []int8 Moniker string Other NodeInfoOther } +type Version struct { + P2P uint64 + Block uint64 + App uint64 +} + type NodeInfoOther struct { - AminoVersion string - P2PVersion string - ConsensusVersion string - RPCVersion string TxIndex string RPCAddress string } @@ -99,8 +102,7 @@ type NodeInfoOther struct { The connection is disconnected if: - `peer.NodeInfo.ID` is not equal `peerConn.ID` -- `peer.NodeInfo.Version` is not formatted as `X.X.X` where X are integers known as Major, Minor, and Revision -- `peer.NodeInfo.Version` Major is not the same as ours +- `peer.NodeInfo.Version.Block` does not match ours - `peer.NodeInfo.Network` is not the same as ours - `peer.Channels` does not intersect with our known Channels. - `peer.NodeInfo.ListenAddr` is malformed or is a DNS host that cannot be diff --git a/node/node.go b/node/node.go index 12e0b8e6..97de2473 100644 --- a/node/node.go +++ b/node/node.go @@ -32,7 +32,6 @@ import ( rpccore "github.com/tendermint/tendermint/rpc/core" ctypes "github.com/tendermint/tendermint/rpc/core/types" grpccore "github.com/tendermint/tendermint/rpc/grpc" - "github.com/tendermint/tendermint/rpc/lib" "github.com/tendermint/tendermint/rpc/lib/server" sm "github.com/tendermint/tendermint/state" "github.com/tendermint/tendermint/state/txindex" @@ -771,9 +770,10 @@ func makeNodeInfo( txIndexerStatus = "off" } nodeInfo := p2p.DefaultNodeInfo{ - ID_: nodeID, - Network: chainID, - Version: version.Version, + ProtocolVersion: p2p.InitProtocolVersion, + ID_: nodeID, + Network: chainID, + Version: version.TMCoreSemVer, Channels: []byte{ bc.BlockchainChannel, cs.StateChannel, cs.DataChannel, cs.VoteChannel, cs.VoteSetBitsChannel, @@ -782,12 +782,8 @@ func makeNodeInfo( }, Moniker: config.Moniker, Other: p2p.DefaultNodeInfoOther{ - AminoVersion: amino.Version, - P2PVersion: p2p.Version, - ConsensusVersion: cs.Version, - RPCVersion: fmt.Sprintf("%v/%v", rpc.Version, rpccore.Version), - TxIndex: txIndexerStatus, - RPCAddress: config.RPC.ListenAddress, + TxIndex: txIndexerStatus, + RPCAddress: config.RPC.ListenAddress, }, } diff --git a/p2p/node_info.go b/p2p/node_info.go index a468443d..5874dc85 100644 --- a/p2p/node_info.go +++ b/p2p/node_info.go @@ -3,9 +3,9 @@ package p2p import ( "fmt" "reflect" - "strings" cmn "github.com/tendermint/tendermint/libs/common" + "github.com/tendermint/tendermint/version" ) const ( @@ -18,8 +18,10 @@ func MaxNodeInfoSize() int { return maxNodeInfoSize } +//------------------------------------------------------------- + // NodeInfo exposes basic info of a node -// and determines if we're compatible +// and determines if we're compatible. type NodeInfo interface { nodeInfoAddress nodeInfoTransport @@ -31,16 +33,38 @@ type nodeInfoAddress interface { NetAddress() *NetAddress } -// nodeInfoTransport is validates a nodeInfo and checks +// nodeInfoTransport validates a nodeInfo and checks // our compatibility with it. It's for use in the handshake. type nodeInfoTransport interface { ValidateBasic() error CompatibleWith(other NodeInfo) error } +//------------------------------------------------------------- + +// ProtocolVersion contains the protocol versions for the software. +type ProtocolVersion struct { + P2P version.Protocol `json:"p2p"` + Block version.Protocol `json:"block"` + App version.Protocol `json:"app"` +} + +var InitProtocolVersion = ProtocolVersion{ + P2P: version.P2PProtocol, + Block: version.BlockProtocol, + App: 0, +} + +//------------------------------------------------------------- + +// Assert DefaultNodeInfo satisfies NodeInfo +var _ NodeInfo = DefaultNodeInfo{} + // DefaultNodeInfo is the basic node information exchanged // between two peers during the Tendermint P2P handshake. type DefaultNodeInfo struct { + ProtocolVersion ProtocolVersion `json:"protocol_version"` + // Authenticate // TODO: replace with NetAddress ID_ ID `json:"id"` // authenticated identifier @@ -59,12 +83,8 @@ type DefaultNodeInfo struct { // DefaultNodeInfoOther is the misc. applcation specific data type DefaultNodeInfoOther struct { - AminoVersion string `json:"amino_version"` - P2PVersion string `json:"p2p_version"` - ConsensusVersion string `json:"consensus_version"` - RPCVersion string `json:"rpc_version"` - TxIndex string `json:"tx_index"` - RPCAddress string `json:"rpc_address"` + TxIndex string `json:"tx_index"` + RPCAddress string `json:"rpc_address"` } // ID returns the node's peer ID. @@ -86,35 +106,28 @@ func (info DefaultNodeInfo) ID() ID { // url-encoding), and we just need to be careful with how we handle that in our // clients. (e.g. off by default). func (info DefaultNodeInfo) ValidateBasic() error { + + // ID is already validated. + + // Validate ListenAddr. + _, err := NewNetAddressString(IDAddressString(info.ID(), info.ListenAddr)) + if err != nil { + return err + } + + // Network is validated in CompatibleWith. + + // Validate Version + if len(info.Version) > 0 && + (!cmn.IsASCIIText(info.Version) || cmn.ASCIITrim(info.Version) == "") { + + return fmt.Errorf("info.Version must be valid ASCII text without tabs, but got %v", info.Version) + } + + // Validate Channels - ensure max and check for duplicates. if len(info.Channels) > maxNumChannels { return fmt.Errorf("info.Channels is too long (%v). Max is %v", len(info.Channels), maxNumChannels) } - - // Sanitize ASCII text fields. - if !cmn.IsASCIIText(info.Moniker) || cmn.ASCIITrim(info.Moniker) == "" { - return fmt.Errorf("info.Moniker must be valid non-empty ASCII text without tabs, but got %v", info.Moniker) - } - - // Sanitize versions - // XXX: Should we be more strict about version and address formats? - other := info.Other - versions := []string{ - other.AminoVersion, - other.P2PVersion, - other.ConsensusVersion, - other.RPCVersion} - for i, v := range versions { - if cmn.ASCIITrim(v) != "" && !cmn.IsASCIIText(v) { - return fmt.Errorf("info.Other[%d]=%v must be valid non-empty ASCII text without tabs", i, v) - } - } - if cmn.ASCIITrim(other.TxIndex) != "" && (other.TxIndex != "on" && other.TxIndex != "off") { - return fmt.Errorf("info.Other.TxIndex should be either 'on' or 'off', got '%v'", other.TxIndex) - } - if cmn.ASCIITrim(other.RPCAddress) != "" && !cmn.IsASCIIText(other.RPCAddress) { - return fmt.Errorf("info.Other.RPCAddress=%v must be valid non-empty ASCII text without tabs", other.RPCAddress) - } - channels := make(map[byte]struct{}) for _, ch := range info.Channels { _, ok := channels[ch] @@ -124,13 +137,30 @@ func (info DefaultNodeInfo) ValidateBasic() error { channels[ch] = struct{}{} } - // ensure ListenAddr is good - _, err := NewNetAddressString(IDAddressString(info.ID(), info.ListenAddr)) - return err + // Validate Moniker. + if !cmn.IsASCIIText(info.Moniker) || cmn.ASCIITrim(info.Moniker) == "" { + return fmt.Errorf("info.Moniker must be valid non-empty ASCII text without tabs, but got %v", info.Moniker) + } + + // Validate Other. + other := info.Other + txIndex := other.TxIndex + switch txIndex { + case "", "on", "off": + default: + return fmt.Errorf("info.Other.TxIndex should be either 'on' or 'off', got '%v'", txIndex) + } + // XXX: Should we be more strict about address formats? + rpcAddr := other.RPCAddress + if len(rpcAddr) > 0 && (!cmn.IsASCIIText(rpcAddr) || cmn.ASCIITrim(rpcAddr) == "") { + return fmt.Errorf("info.Other.RPCAddress=%v must be valid ASCII text without tabs", rpcAddr) + } + + return nil } // CompatibleWith checks if two DefaultNodeInfo are compatible with eachother. -// CONTRACT: two nodes are compatible if the major version matches and network match +// CONTRACT: two nodes are compatible if the Block version and network match // and they have at least one channel in common. func (info DefaultNodeInfo) CompatibleWith(other_ NodeInfo) error { other, ok := other_.(DefaultNodeInfo) @@ -138,22 +168,9 @@ func (info DefaultNodeInfo) CompatibleWith(other_ NodeInfo) error { return fmt.Errorf("wrong NodeInfo type. Expected DefaultNodeInfo, got %v", reflect.TypeOf(other_)) } - iMajor, _, _, iErr := splitVersion(info.Version) - oMajor, _, _, oErr := splitVersion(other.Version) - - // if our own version number is not formatted right, we messed up - if iErr != nil { - return iErr - } - - // version number must be formatted correctly ("x.x.x") - if oErr != nil { - return oErr - } - - // major version must match - if iMajor != oMajor { - return fmt.Errorf("Peer is on a different major version. Got %v, expected %v", oMajor, iMajor) + if info.ProtocolVersion.Block != other.ProtocolVersion.Block { + return fmt.Errorf("Peer is on a different Block version. Got %v, expected %v", + other.ProtocolVersion.Block, info.ProtocolVersion.Block) } // nodes must be on the same network @@ -201,11 +218,3 @@ func (info DefaultNodeInfo) NetAddress() *NetAddress { } return netAddr } - -func splitVersion(version string) (string, string, string, error) { - spl := strings.Split(version, ".") - if len(spl) != 3 { - return "", "", "", fmt.Errorf("Invalid version format %v", version) - } - return spl[0], spl[1], spl[2], nil -} diff --git a/p2p/node_info_test.go b/p2p/node_info_test.go new file mode 100644 index 00000000..c9a72dbc --- /dev/null +++ b/p2p/node_info_test.go @@ -0,0 +1,123 @@ +package p2p + +import ( + "fmt" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/tendermint/tendermint/crypto/ed25519" +) + +func TestNodeInfoValidate(t *testing.T) { + + // empty fails + ni := DefaultNodeInfo{} + assert.Error(t, ni.ValidateBasic()) + + channels := make([]byte, maxNumChannels) + for i := 0; i < maxNumChannels; i++ { + channels[i] = byte(i) + } + dupChannels := make([]byte, 5) + copy(dupChannels[:], channels[:5]) + dupChannels = append(dupChannels, testCh) + + nonAscii := "¢§µ" + emptyTab := fmt.Sprintf("\t") + emptySpace := fmt.Sprintf(" ") + + testCases := []struct { + testName string + malleateNodeInfo func(*DefaultNodeInfo) + expectErr bool + }{ + {"Too Many Channels", func(ni *DefaultNodeInfo) { ni.Channels = append(channels, byte(maxNumChannels)) }, true}, + {"Duplicate Channel", func(ni *DefaultNodeInfo) { ni.Channels = dupChannels }, true}, + {"Good Channels", func(ni *DefaultNodeInfo) { ni.Channels = ni.Channels[:5] }, false}, + + {"Invalid NetAddress", func(ni *DefaultNodeInfo) { ni.ListenAddr = "not-an-address" }, true}, + {"Good NetAddress", func(ni *DefaultNodeInfo) { ni.ListenAddr = "0.0.0.0:26656" }, false}, + + {"Non-ASCII Version", func(ni *DefaultNodeInfo) { ni.Version = nonAscii }, true}, + {"Empty tab Version", func(ni *DefaultNodeInfo) { ni.Version = emptyTab }, true}, + {"Empty space Version", func(ni *DefaultNodeInfo) { ni.Version = emptySpace }, true}, + {"Empty Version", func(ni *DefaultNodeInfo) { ni.Version = "" }, false}, + + {"Non-ASCII Moniker", func(ni *DefaultNodeInfo) { ni.Moniker = nonAscii }, true}, + {"Empty tab Moniker", func(ni *DefaultNodeInfo) { ni.Moniker = emptyTab }, true}, + {"Empty space Moniker", func(ni *DefaultNodeInfo) { ni.Moniker = emptySpace }, true}, + {"Empty Moniker", func(ni *DefaultNodeInfo) { ni.Moniker = "" }, true}, + {"Good Moniker", func(ni *DefaultNodeInfo) { ni.Moniker = "hey its me" }, false}, + + {"Non-ASCII TxIndex", func(ni *DefaultNodeInfo) { ni.Other.TxIndex = nonAscii }, true}, + {"Empty tab TxIndex", func(ni *DefaultNodeInfo) { ni.Other.TxIndex = emptyTab }, true}, + {"Empty space TxIndex", func(ni *DefaultNodeInfo) { ni.Other.TxIndex = emptySpace }, true}, + {"Empty TxIndex", func(ni *DefaultNodeInfo) { ni.Other.TxIndex = "" }, false}, + {"Off TxIndex", func(ni *DefaultNodeInfo) { ni.Other.TxIndex = "off" }, false}, + + {"Non-ASCII RPCAddress", func(ni *DefaultNodeInfo) { ni.Other.RPCAddress = nonAscii }, true}, + {"Empty tab RPCAddress", func(ni *DefaultNodeInfo) { ni.Other.RPCAddress = emptyTab }, true}, + {"Empty space RPCAddress", func(ni *DefaultNodeInfo) { ni.Other.RPCAddress = emptySpace }, true}, + {"Empty RPCAddress", func(ni *DefaultNodeInfo) { ni.Other.RPCAddress = "" }, false}, + {"Good RPCAddress", func(ni *DefaultNodeInfo) { ni.Other.RPCAddress = "0.0.0.0:26657" }, false}, + } + + nodeKey := NodeKey{PrivKey: ed25519.GenPrivKey()} + name := "testing" + + // test case passes + ni = testNodeInfo(nodeKey.ID(), name).(DefaultNodeInfo) + ni.Channels = channels + assert.NoError(t, ni.ValidateBasic()) + + for _, tc := range testCases { + ni := testNodeInfo(nodeKey.ID(), name).(DefaultNodeInfo) + ni.Channels = channels + tc.malleateNodeInfo(&ni) + err := ni.ValidateBasic() + if tc.expectErr { + assert.Error(t, err, tc.testName) + } else { + assert.NoError(t, err, tc.testName) + } + } + +} + +func TestNodeInfoCompatible(t *testing.T) { + + nodeKey1 := NodeKey{PrivKey: ed25519.GenPrivKey()} + nodeKey2 := NodeKey{PrivKey: ed25519.GenPrivKey()} + name := "testing" + + var newTestChannel byte = 0x2 + + // test NodeInfo is compatible + ni1 := testNodeInfo(nodeKey1.ID(), name).(DefaultNodeInfo) + ni2 := testNodeInfo(nodeKey2.ID(), name).(DefaultNodeInfo) + assert.NoError(t, ni1.CompatibleWith(ni2)) + + // add another channel; still compatible + ni2.Channels = []byte{newTestChannel, testCh} + assert.NoError(t, ni1.CompatibleWith(ni2)) + + // wrong NodeInfo type is not compatible + _, netAddr := CreateRoutableAddr() + ni3 := mockNodeInfo{netAddr} + assert.Error(t, ni1.CompatibleWith(ni3)) + + testCases := []struct { + testName string + malleateNodeInfo func(*DefaultNodeInfo) + }{ + {"Wrong block version", func(ni *DefaultNodeInfo) { ni.ProtocolVersion.Block += 1 }}, + {"Wrong network", func(ni *DefaultNodeInfo) { ni.Network += "-wrong" }}, + {"No common channels", func(ni *DefaultNodeInfo) { ni.Channels = []byte{newTestChannel} }}, + } + + for _, tc := range testCases { + ni := testNodeInfo(nodeKey2.ID(), name).(DefaultNodeInfo) + tc.malleateNodeInfo(&ni) + assert.Error(t, ni1.CompatibleWith(ni)) + } +} diff --git a/p2p/peer_test.go b/p2p/peer_test.go index fecf7f1c..9c330ee5 100644 --- a/p2p/peer_test.go +++ b/p2p/peer_test.go @@ -207,11 +207,12 @@ func (rp *remotePeer) accept(l net.Listener) { func (rp *remotePeer) nodeInfo(l net.Listener) NodeInfo { return DefaultNodeInfo{ - ID_: rp.Addr().ID, - Moniker: "remote_peer", - Network: "testing", - Version: "123.123.123", - ListenAddr: l.Addr().String(), - Channels: rp.channels, + ProtocolVersion: InitProtocolVersion, + ID_: rp.Addr().ID, + ListenAddr: l.Addr().String(), + Network: "testing", + Version: "1.2.3-rc0-deadbeef", + Channels: rp.channels, + Moniker: "remote_peer", } } diff --git a/p2p/test_util.go b/p2p/test_util.go index 2859dc64..4d43175b 100644 --- a/p2p/test_util.go +++ b/p2p/test_util.go @@ -247,11 +247,16 @@ func testNodeInfo(id ID, name string) NodeInfo { func testNodeInfoWithNetwork(id ID, name, network string) NodeInfo { return DefaultNodeInfo{ - ID_: id, - ListenAddr: fmt.Sprintf("127.0.0.1:%d", cmn.RandIntn(64512)+1023), - Moniker: name, - Network: network, - Version: "123.123.123", - Channels: []byte{testCh}, + ProtocolVersion: InitProtocolVersion, + ID_: id, + ListenAddr: fmt.Sprintf("127.0.0.1:%d", cmn.RandIntn(64512)+1023), + Network: network, + Version: "1.2.3-rc0-deadbeef", + Channels: []byte{testCh}, + Moniker: name, + Other: DefaultNodeInfoOther{ + TxIndex: "on", + RPCAddress: fmt.Sprintf("127.0.0.1:%d", cmn.RandIntn(64512)+1023), + }, } } diff --git a/p2p/version.go b/p2p/version.go deleted file mode 100644 index 9a4c7bba..00000000 --- a/p2p/version.go +++ /dev/null @@ -1,3 +0,0 @@ -package p2p - -const Version = "0.5.0" diff --git a/rpc/core/status.go b/rpc/core/status.go index c26b06b8..793e1ade 100644 --- a/rpc/core/status.go +++ b/rpc/core/status.go @@ -31,6 +31,11 @@ import ( // "id": "", // "result": { // "node_info": { +// "protocol_version": { +// "p2p": "4", +// "block": "7", +// "app": "0" +// }, // "id": "53729852020041b956e86685e24394e0bee4373f", // "listen_addr": "10.0.2.15:26656", // "network": "test-chain-Y1OHx6", @@ -38,10 +43,6 @@ import ( // "channels": "4020212223303800", // "moniker": "ubuntu-xenial", // "other": { -// "amino_version": "0.12.0", -// "p2p_version": "0.5.0", -// "consensus_version": "v1/0.2.2", -// "rpc_version": "0.7.0/3", // "tx_index": "on", // "rpc_addr": "tcp://0.0.0.0:26657" // } diff --git a/rpc/core/version.go b/rpc/core/version.go deleted file mode 100644 index e283de47..00000000 --- a/rpc/core/version.go +++ /dev/null @@ -1,5 +0,0 @@ -package core - -// a single integer is sufficient here - -const Version = "3" // rpc routes for profiling, setting config diff --git a/rpc/lib/version.go b/rpc/lib/version.go deleted file mode 100644 index 8828f260..00000000 --- a/rpc/lib/version.go +++ /dev/null @@ -1,7 +0,0 @@ -package rpc - -const Maj = "0" -const Min = "7" -const Fix = "0" - -const Version = Maj + "." + Min + "." + Fix From 055d7adffbb58b676841eb48d436ad3ead0e391d Mon Sep 17 00:00:00 2001 From: Anton Kaliaev Date: Thu, 18 Oct 2018 20:03:45 +0400 Subject: [PATCH 022/267] add tm-abci python ABCI server (fork of py-abci) (#2658) It utilises async IO -> greater performance. --- docs/app-dev/ecosystem.json | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/docs/app-dev/ecosystem.json b/docs/app-dev/ecosystem.json index 67aca2ef..1c2bc2b2 100644 --- a/docs/app-dev/ecosystem.json +++ b/docs/app-dev/ecosystem.json @@ -163,6 +163,12 @@ "language": "Python", "author": "Dave Bryson" }, + { + "name": "tm-abci", + "url": "https://github.com/SoftblocksCo/tm-abci", + "language": "Python", + "author": "Softblocks" + }, { "name": "Spearmint", "url": "https://github.com/dennismckinnon/spearmint", From ed4ce5ff6cc455114d749bd2121096c81098a84f Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Thu, 18 Oct 2018 16:51:17 -0400 Subject: [PATCH 023/267] ADR-016: Update ABCI Info method for versions (#2662) * abci: update RequestInfo for versions * abci: update ResponseInfo for versions * compile fix * fix test * software_version -> version * comment fix * update spec * add test * comments and fix test --- CHANGELOG_PENDING.md | 3 + abci/example/kvstore/kvstore.go | 9 +- abci/types/types.pb.go | 484 ++++++++++++++++++++------------ abci/types/types.proto | 9 +- consensus/replay.go | 14 +- consensus/replay_test.go | 12 +- consensus/wal_generator.go | 4 +- docs/spec/abci/abci.md | 8 +- node/node.go | 20 +- node/node_test.go | 22 ++ p2p/node_info.go | 17 +- proxy/app_conn_test.go | 2 +- proxy/version.go | 15 + rpc/client/mock/abci.go | 4 +- rpc/core/abci.go | 4 +- state/state.go | 4 + version/version.go | 6 + 17 files changed, 425 insertions(+), 212 deletions(-) create mode 100644 proxy/version.go diff --git a/CHANGELOG_PENDING.md b/CHANGELOG_PENDING.md index f4858d95..99c38997 100644 --- a/CHANGELOG_PENDING.md +++ b/CHANGELOG_PENDING.md @@ -21,6 +21,8 @@ BREAKING CHANGES: * [abci] \#2298 ResponseQuery.Proof is now a structured merkle.Proof, not just arbitrary bytes * [abci] \#2644 Add Version to Header and shift all fields by one + * [abci] \#2662 Bump the field numbers for some `ResponseInfo` fields to make room for + `AppVersion` * Go API * [node] Remove node.RunForever @@ -52,6 +54,7 @@ BREAKING CHANGES: FEATURES: - [crypto/merkle] \#2298 General Merkle Proof scheme for chaining various types of Merkle trees together - [abci] \#2557 Add `Codespace` field to `Response{CheckTx, DeliverTx, Query}` +- [abci] \#2662 Add `BlockVersion` and `P2PVersion` to `RequestInfo` IMPROVEMENTS: - Additional Metrics diff --git a/abci/example/kvstore/kvstore.go b/abci/example/kvstore/kvstore.go index 9523bf74..955baefb 100644 --- a/abci/example/kvstore/kvstore.go +++ b/abci/example/kvstore/kvstore.go @@ -10,11 +10,14 @@ import ( "github.com/tendermint/tendermint/abci/types" cmn "github.com/tendermint/tendermint/libs/common" dbm "github.com/tendermint/tendermint/libs/db" + "github.com/tendermint/tendermint/version" ) var ( stateKey = []byte("stateKey") kvPairPrefixKey = []byte("kvPairKey:") + + ProtocolVersion version.Protocol = 0x1 ) type State struct { @@ -65,7 +68,11 @@ func NewKVStoreApplication() *KVStoreApplication { } func (app *KVStoreApplication) Info(req types.RequestInfo) (resInfo types.ResponseInfo) { - return types.ResponseInfo{Data: fmt.Sprintf("{\"size\":%v}", app.state.Size)} + return types.ResponseInfo{ + Data: fmt.Sprintf("{\"size\":%v}", app.state.Size), + Version: version.ABCIVersion, + AppVersion: ProtocolVersion.Uint64(), + } } // tx is either "key=value" or just arbitrary bytes diff --git a/abci/types/types.pb.go b/abci/types/types.pb.go index 81fb74b4..6a70bb97 100644 --- a/abci/types/types.pb.go +++ b/abci/types/types.pb.go @@ -61,7 +61,7 @@ func (m *Request) Reset() { *m = Request{} } func (m *Request) String() string { return proto.CompactTextString(m) } func (*Request) ProtoMessage() {} func (*Request) Descriptor() ([]byte, []int) { - return fileDescriptor_types_07d64ea985a686e2, []int{0} + return fileDescriptor_types_4449c1011851ea19, []int{0} } func (m *Request) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -483,7 +483,7 @@ func (m *RequestEcho) Reset() { *m = RequestEcho{} } func (m *RequestEcho) String() string { return proto.CompactTextString(m) } func (*RequestEcho) ProtoMessage() {} func (*RequestEcho) Descriptor() ([]byte, []int) { - return fileDescriptor_types_07d64ea985a686e2, []int{1} + return fileDescriptor_types_4449c1011851ea19, []int{1} } func (m *RequestEcho) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -529,7 +529,7 @@ func (m *RequestFlush) Reset() { *m = RequestFlush{} } func (m *RequestFlush) String() string { return proto.CompactTextString(m) } func (*RequestFlush) ProtoMessage() {} func (*RequestFlush) Descriptor() ([]byte, []int) { - return fileDescriptor_types_07d64ea985a686e2, []int{2} + return fileDescriptor_types_4449c1011851ea19, []int{2} } func (m *RequestFlush) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -560,6 +560,8 @@ var xxx_messageInfo_RequestFlush proto.InternalMessageInfo type RequestInfo struct { Version string `protobuf:"bytes,1,opt,name=version,proto3" json:"version,omitempty"` + BlockVersion uint64 `protobuf:"varint,2,opt,name=block_version,json=blockVersion,proto3" json:"block_version,omitempty"` + P2PVersion uint64 `protobuf:"varint,3,opt,name=p2p_version,json=p2pVersion,proto3" json:"p2p_version,omitempty"` XXX_NoUnkeyedLiteral struct{} `json:"-"` XXX_unrecognized []byte `json:"-"` XXX_sizecache int32 `json:"-"` @@ -569,7 +571,7 @@ func (m *RequestInfo) Reset() { *m = RequestInfo{} } func (m *RequestInfo) String() string { return proto.CompactTextString(m) } func (*RequestInfo) ProtoMessage() {} func (*RequestInfo) Descriptor() ([]byte, []int) { - return fileDescriptor_types_07d64ea985a686e2, []int{3} + return fileDescriptor_types_4449c1011851ea19, []int{3} } func (m *RequestInfo) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -605,6 +607,20 @@ func (m *RequestInfo) GetVersion() string { return "" } +func (m *RequestInfo) GetBlockVersion() uint64 { + if m != nil { + return m.BlockVersion + } + return 0 +} + +func (m *RequestInfo) GetP2PVersion() uint64 { + if m != nil { + return m.P2PVersion + } + return 0 +} + // nondeterministic type RequestSetOption struct { Key string `protobuf:"bytes,1,opt,name=key,proto3" json:"key,omitempty"` @@ -618,7 +634,7 @@ func (m *RequestSetOption) Reset() { *m = RequestSetOption{} } func (m *RequestSetOption) String() string { return proto.CompactTextString(m) } func (*RequestSetOption) ProtoMessage() {} func (*RequestSetOption) Descriptor() ([]byte, []int) { - return fileDescriptor_types_07d64ea985a686e2, []int{4} + return fileDescriptor_types_4449c1011851ea19, []int{4} } func (m *RequestSetOption) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -676,7 +692,7 @@ func (m *RequestInitChain) Reset() { *m = RequestInitChain{} } func (m *RequestInitChain) String() string { return proto.CompactTextString(m) } func (*RequestInitChain) ProtoMessage() {} func (*RequestInitChain) Descriptor() ([]byte, []int) { - return fileDescriptor_types_07d64ea985a686e2, []int{5} + return fileDescriptor_types_4449c1011851ea19, []int{5} } func (m *RequestInitChain) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -754,7 +770,7 @@ func (m *RequestQuery) Reset() { *m = RequestQuery{} } func (m *RequestQuery) String() string { return proto.CompactTextString(m) } func (*RequestQuery) ProtoMessage() {} func (*RequestQuery) Descriptor() ([]byte, []int) { - return fileDescriptor_types_07d64ea985a686e2, []int{6} + return fileDescriptor_types_4449c1011851ea19, []int{6} } func (m *RequestQuery) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -826,7 +842,7 @@ func (m *RequestBeginBlock) Reset() { *m = RequestBeginBlock{} } func (m *RequestBeginBlock) String() string { return proto.CompactTextString(m) } func (*RequestBeginBlock) ProtoMessage() {} func (*RequestBeginBlock) Descriptor() ([]byte, []int) { - return fileDescriptor_types_07d64ea985a686e2, []int{7} + return fileDescriptor_types_4449c1011851ea19, []int{7} } func (m *RequestBeginBlock) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -894,7 +910,7 @@ func (m *RequestCheckTx) Reset() { *m = RequestCheckTx{} } func (m *RequestCheckTx) String() string { return proto.CompactTextString(m) } func (*RequestCheckTx) ProtoMessage() {} func (*RequestCheckTx) Descriptor() ([]byte, []int) { - return fileDescriptor_types_07d64ea985a686e2, []int{8} + return fileDescriptor_types_4449c1011851ea19, []int{8} } func (m *RequestCheckTx) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -941,7 +957,7 @@ func (m *RequestDeliverTx) Reset() { *m = RequestDeliverTx{} } func (m *RequestDeliverTx) String() string { return proto.CompactTextString(m) } func (*RequestDeliverTx) ProtoMessage() {} func (*RequestDeliverTx) Descriptor() ([]byte, []int) { - return fileDescriptor_types_07d64ea985a686e2, []int{9} + return fileDescriptor_types_4449c1011851ea19, []int{9} } func (m *RequestDeliverTx) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -988,7 +1004,7 @@ func (m *RequestEndBlock) Reset() { *m = RequestEndBlock{} } func (m *RequestEndBlock) String() string { return proto.CompactTextString(m) } func (*RequestEndBlock) ProtoMessage() {} func (*RequestEndBlock) Descriptor() ([]byte, []int) { - return fileDescriptor_types_07d64ea985a686e2, []int{10} + return fileDescriptor_types_4449c1011851ea19, []int{10} } func (m *RequestEndBlock) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -1034,7 +1050,7 @@ func (m *RequestCommit) Reset() { *m = RequestCommit{} } func (m *RequestCommit) String() string { return proto.CompactTextString(m) } func (*RequestCommit) ProtoMessage() {} func (*RequestCommit) Descriptor() ([]byte, []int) { - return fileDescriptor_types_07d64ea985a686e2, []int{11} + return fileDescriptor_types_4449c1011851ea19, []int{11} } func (m *RequestCommit) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -1087,7 +1103,7 @@ func (m *Response) Reset() { *m = Response{} } func (m *Response) String() string { return proto.CompactTextString(m) } func (*Response) ProtoMessage() {} func (*Response) Descriptor() ([]byte, []int) { - return fileDescriptor_types_07d64ea985a686e2, []int{12} + return fileDescriptor_types_4449c1011851ea19, []int{12} } func (m *Response) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -1540,7 +1556,7 @@ func (m *ResponseException) Reset() { *m = ResponseException{} } func (m *ResponseException) String() string { return proto.CompactTextString(m) } func (*ResponseException) ProtoMessage() {} func (*ResponseException) Descriptor() ([]byte, []int) { - return fileDescriptor_types_07d64ea985a686e2, []int{13} + return fileDescriptor_types_4449c1011851ea19, []int{13} } func (m *ResponseException) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -1587,7 +1603,7 @@ func (m *ResponseEcho) Reset() { *m = ResponseEcho{} } func (m *ResponseEcho) String() string { return proto.CompactTextString(m) } func (*ResponseEcho) ProtoMessage() {} func (*ResponseEcho) Descriptor() ([]byte, []int) { - return fileDescriptor_types_07d64ea985a686e2, []int{14} + return fileDescriptor_types_4449c1011851ea19, []int{14} } func (m *ResponseEcho) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -1633,7 +1649,7 @@ func (m *ResponseFlush) Reset() { *m = ResponseFlush{} } func (m *ResponseFlush) String() string { return proto.CompactTextString(m) } func (*ResponseFlush) ProtoMessage() {} func (*ResponseFlush) Descriptor() ([]byte, []int) { - return fileDescriptor_types_07d64ea985a686e2, []int{15} + return fileDescriptor_types_4449c1011851ea19, []int{15} } func (m *ResponseFlush) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -1665,8 +1681,9 @@ var xxx_messageInfo_ResponseFlush proto.InternalMessageInfo type ResponseInfo struct { Data string `protobuf:"bytes,1,opt,name=data,proto3" json:"data,omitempty"` Version string `protobuf:"bytes,2,opt,name=version,proto3" json:"version,omitempty"` - LastBlockHeight int64 `protobuf:"varint,3,opt,name=last_block_height,json=lastBlockHeight,proto3" json:"last_block_height,omitempty"` - LastBlockAppHash []byte `protobuf:"bytes,4,opt,name=last_block_app_hash,json=lastBlockAppHash,proto3" json:"last_block_app_hash,omitempty"` + AppVersion uint64 `protobuf:"varint,3,opt,name=app_version,json=appVersion,proto3" json:"app_version,omitempty"` + LastBlockHeight int64 `protobuf:"varint,4,opt,name=last_block_height,json=lastBlockHeight,proto3" json:"last_block_height,omitempty"` + LastBlockAppHash []byte `protobuf:"bytes,5,opt,name=last_block_app_hash,json=lastBlockAppHash,proto3" json:"last_block_app_hash,omitempty"` XXX_NoUnkeyedLiteral struct{} `json:"-"` XXX_unrecognized []byte `json:"-"` XXX_sizecache int32 `json:"-"` @@ -1676,7 +1693,7 @@ func (m *ResponseInfo) Reset() { *m = ResponseInfo{} } func (m *ResponseInfo) String() string { return proto.CompactTextString(m) } func (*ResponseInfo) ProtoMessage() {} func (*ResponseInfo) Descriptor() ([]byte, []int) { - return fileDescriptor_types_07d64ea985a686e2, []int{16} + return fileDescriptor_types_4449c1011851ea19, []int{16} } func (m *ResponseInfo) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -1719,6 +1736,13 @@ func (m *ResponseInfo) GetVersion() string { return "" } +func (m *ResponseInfo) GetAppVersion() uint64 { + if m != nil { + return m.AppVersion + } + return 0 +} + func (m *ResponseInfo) GetLastBlockHeight() int64 { if m != nil { return m.LastBlockHeight @@ -1748,7 +1772,7 @@ func (m *ResponseSetOption) Reset() { *m = ResponseSetOption{} } func (m *ResponseSetOption) String() string { return proto.CompactTextString(m) } func (*ResponseSetOption) ProtoMessage() {} func (*ResponseSetOption) Descriptor() ([]byte, []int) { - return fileDescriptor_types_07d64ea985a686e2, []int{17} + return fileDescriptor_types_4449c1011851ea19, []int{17} } func (m *ResponseSetOption) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -1810,7 +1834,7 @@ func (m *ResponseInitChain) Reset() { *m = ResponseInitChain{} } func (m *ResponseInitChain) String() string { return proto.CompactTextString(m) } func (*ResponseInitChain) ProtoMessage() {} func (*ResponseInitChain) Descriptor() ([]byte, []int) { - return fileDescriptor_types_07d64ea985a686e2, []int{18} + return fileDescriptor_types_4449c1011851ea19, []int{18} } func (m *ResponseInitChain) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -1873,7 +1897,7 @@ func (m *ResponseQuery) Reset() { *m = ResponseQuery{} } func (m *ResponseQuery) String() string { return proto.CompactTextString(m) } func (*ResponseQuery) ProtoMessage() {} func (*ResponseQuery) Descriptor() ([]byte, []int) { - return fileDescriptor_types_07d64ea985a686e2, []int{19} + return fileDescriptor_types_4449c1011851ea19, []int{19} } func (m *ResponseQuery) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -1976,7 +2000,7 @@ func (m *ResponseBeginBlock) Reset() { *m = ResponseBeginBlock{} } func (m *ResponseBeginBlock) String() string { return proto.CompactTextString(m) } func (*ResponseBeginBlock) ProtoMessage() {} func (*ResponseBeginBlock) Descriptor() ([]byte, []int) { - return fileDescriptor_types_07d64ea985a686e2, []int{20} + return fileDescriptor_types_4449c1011851ea19, []int{20} } func (m *ResponseBeginBlock) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -2030,7 +2054,7 @@ func (m *ResponseCheckTx) Reset() { *m = ResponseCheckTx{} } func (m *ResponseCheckTx) String() string { return proto.CompactTextString(m) } func (*ResponseCheckTx) ProtoMessage() {} func (*ResponseCheckTx) Descriptor() ([]byte, []int) { - return fileDescriptor_types_07d64ea985a686e2, []int{21} + return fileDescriptor_types_4449c1011851ea19, []int{21} } func (m *ResponseCheckTx) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -2133,7 +2157,7 @@ func (m *ResponseDeliverTx) Reset() { *m = ResponseDeliverTx{} } func (m *ResponseDeliverTx) String() string { return proto.CompactTextString(m) } func (*ResponseDeliverTx) ProtoMessage() {} func (*ResponseDeliverTx) Descriptor() ([]byte, []int) { - return fileDescriptor_types_07d64ea985a686e2, []int{22} + return fileDescriptor_types_4449c1011851ea19, []int{22} } func (m *ResponseDeliverTx) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -2231,7 +2255,7 @@ func (m *ResponseEndBlock) Reset() { *m = ResponseEndBlock{} } func (m *ResponseEndBlock) String() string { return proto.CompactTextString(m) } func (*ResponseEndBlock) ProtoMessage() {} func (*ResponseEndBlock) Descriptor() ([]byte, []int) { - return fileDescriptor_types_07d64ea985a686e2, []int{23} + return fileDescriptor_types_4449c1011851ea19, []int{23} } func (m *ResponseEndBlock) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -2293,7 +2317,7 @@ func (m *ResponseCommit) Reset() { *m = ResponseCommit{} } func (m *ResponseCommit) String() string { return proto.CompactTextString(m) } func (*ResponseCommit) ProtoMessage() {} func (*ResponseCommit) Descriptor() ([]byte, []int) { - return fileDescriptor_types_07d64ea985a686e2, []int{24} + return fileDescriptor_types_4449c1011851ea19, []int{24} } func (m *ResponseCommit) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -2343,7 +2367,7 @@ func (m *ConsensusParams) Reset() { *m = ConsensusParams{} } func (m *ConsensusParams) String() string { return proto.CompactTextString(m) } func (*ConsensusParams) ProtoMessage() {} func (*ConsensusParams) Descriptor() ([]byte, []int) { - return fileDescriptor_types_07d64ea985a686e2, []int{25} + return fileDescriptor_types_4449c1011851ea19, []int{25} } func (m *ConsensusParams) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -2401,7 +2425,7 @@ func (m *BlockSize) Reset() { *m = BlockSize{} } func (m *BlockSize) String() string { return proto.CompactTextString(m) } func (*BlockSize) ProtoMessage() {} func (*BlockSize) Descriptor() ([]byte, []int) { - return fileDescriptor_types_07d64ea985a686e2, []int{26} + return fileDescriptor_types_4449c1011851ea19, []int{26} } func (m *BlockSize) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -2457,7 +2481,7 @@ func (m *EvidenceParams) Reset() { *m = EvidenceParams{} } func (m *EvidenceParams) String() string { return proto.CompactTextString(m) } func (*EvidenceParams) ProtoMessage() {} func (*EvidenceParams) Descriptor() ([]byte, []int) { - return fileDescriptor_types_07d64ea985a686e2, []int{27} + return fileDescriptor_types_4449c1011851ea19, []int{27} } func (m *EvidenceParams) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -2505,7 +2529,7 @@ func (m *LastCommitInfo) Reset() { *m = LastCommitInfo{} } func (m *LastCommitInfo) String() string { return proto.CompactTextString(m) } func (*LastCommitInfo) ProtoMessage() {} func (*LastCommitInfo) Descriptor() ([]byte, []int) { - return fileDescriptor_types_07d64ea985a686e2, []int{28} + return fileDescriptor_types_4449c1011851ea19, []int{28} } func (m *LastCommitInfo) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -2579,7 +2603,7 @@ func (m *Header) Reset() { *m = Header{} } func (m *Header) String() string { return proto.CompactTextString(m) } func (*Header) ProtoMessage() {} func (*Header) Descriptor() ([]byte, []int) { - return fileDescriptor_types_07d64ea985a686e2, []int{29} + return fileDescriptor_types_4449c1011851ea19, []int{29} } func (m *Header) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -2732,7 +2756,7 @@ func (m *Version) Reset() { *m = Version{} } func (m *Version) String() string { return proto.CompactTextString(m) } func (*Version) ProtoMessage() {} func (*Version) Descriptor() ([]byte, []int) { - return fileDescriptor_types_07d64ea985a686e2, []int{30} + return fileDescriptor_types_4449c1011851ea19, []int{30} } func (m *Version) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -2787,7 +2811,7 @@ func (m *BlockID) Reset() { *m = BlockID{} } func (m *BlockID) String() string { return proto.CompactTextString(m) } func (*BlockID) ProtoMessage() {} func (*BlockID) Descriptor() ([]byte, []int) { - return fileDescriptor_types_07d64ea985a686e2, []int{31} + return fileDescriptor_types_4449c1011851ea19, []int{31} } func (m *BlockID) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -2842,7 +2866,7 @@ func (m *PartSetHeader) Reset() { *m = PartSetHeader{} } func (m *PartSetHeader) String() string { return proto.CompactTextString(m) } func (*PartSetHeader) ProtoMessage() {} func (*PartSetHeader) Descriptor() ([]byte, []int) { - return fileDescriptor_types_07d64ea985a686e2, []int{32} + return fileDescriptor_types_4449c1011851ea19, []int{32} } func (m *PartSetHeader) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -2899,7 +2923,7 @@ func (m *Validator) Reset() { *m = Validator{} } func (m *Validator) String() string { return proto.CompactTextString(m) } func (*Validator) ProtoMessage() {} func (*Validator) Descriptor() ([]byte, []int) { - return fileDescriptor_types_07d64ea985a686e2, []int{33} + return fileDescriptor_types_4449c1011851ea19, []int{33} } func (m *Validator) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -2955,7 +2979,7 @@ func (m *ValidatorUpdate) Reset() { *m = ValidatorUpdate{} } func (m *ValidatorUpdate) String() string { return proto.CompactTextString(m) } func (*ValidatorUpdate) ProtoMessage() {} func (*ValidatorUpdate) Descriptor() ([]byte, []int) { - return fileDescriptor_types_07d64ea985a686e2, []int{34} + return fileDescriptor_types_4449c1011851ea19, []int{34} } func (m *ValidatorUpdate) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -3011,7 +3035,7 @@ func (m *VoteInfo) Reset() { *m = VoteInfo{} } func (m *VoteInfo) String() string { return proto.CompactTextString(m) } func (*VoteInfo) ProtoMessage() {} func (*VoteInfo) Descriptor() ([]byte, []int) { - return fileDescriptor_types_07d64ea985a686e2, []int{35} + return fileDescriptor_types_4449c1011851ea19, []int{35} } func (m *VoteInfo) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -3066,7 +3090,7 @@ func (m *PubKey) Reset() { *m = PubKey{} } func (m *PubKey) String() string { return proto.CompactTextString(m) } func (*PubKey) ProtoMessage() {} func (*PubKey) Descriptor() ([]byte, []int) { - return fileDescriptor_types_07d64ea985a686e2, []int{36} + return fileDescriptor_types_4449c1011851ea19, []int{36} } func (m *PubKey) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -3124,7 +3148,7 @@ func (m *Evidence) Reset() { *m = Evidence{} } func (m *Evidence) String() string { return proto.CompactTextString(m) } func (*Evidence) ProtoMessage() {} func (*Evidence) Descriptor() ([]byte, []int) { - return fileDescriptor_types_07d64ea985a686e2, []int{37} + return fileDescriptor_types_4449c1011851ea19, []int{37} } func (m *Evidence) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -3636,6 +3660,12 @@ func (this *RequestInfo) Equal(that interface{}) bool { if this.Version != that1.Version { return false } + if this.BlockVersion != that1.BlockVersion { + return false + } + if this.P2PVersion != that1.P2PVersion { + return false + } if !bytes.Equal(this.XXX_unrecognized, that1.XXX_unrecognized) { return false } @@ -4321,6 +4351,9 @@ func (this *ResponseInfo) Equal(that interface{}) bool { if this.Version != that1.Version { return false } + if this.AppVersion != that1.AppVersion { + return false + } if this.LastBlockHeight != that1.LastBlockHeight { return false } @@ -5758,6 +5791,16 @@ func (m *RequestInfo) MarshalTo(dAtA []byte) (int, error) { i = encodeVarintTypes(dAtA, i, uint64(len(m.Version))) i += copy(dAtA[i:], m.Version) } + if m.BlockVersion != 0 { + dAtA[i] = 0x10 + i++ + i = encodeVarintTypes(dAtA, i, uint64(m.BlockVersion)) + } + if m.P2PVersion != 0 { + dAtA[i] = 0x18 + i++ + i = encodeVarintTypes(dAtA, i, uint64(m.P2PVersion)) + } if m.XXX_unrecognized != nil { i += copy(dAtA[i:], m.XXX_unrecognized) } @@ -6362,13 +6405,18 @@ func (m *ResponseInfo) MarshalTo(dAtA []byte) (int, error) { i = encodeVarintTypes(dAtA, i, uint64(len(m.Version))) i += copy(dAtA[i:], m.Version) } - if m.LastBlockHeight != 0 { + if m.AppVersion != 0 { dAtA[i] = 0x18 i++ + i = encodeVarintTypes(dAtA, i, uint64(m.AppVersion)) + } + if m.LastBlockHeight != 0 { + dAtA[i] = 0x20 + i++ i = encodeVarintTypes(dAtA, i, uint64(m.LastBlockHeight)) } if len(m.LastBlockAppHash) > 0 { - dAtA[i] = 0x22 + dAtA[i] = 0x2a i++ i = encodeVarintTypes(dAtA, i, uint64(len(m.LastBlockAppHash))) i += copy(dAtA[i:], m.LastBlockAppHash) @@ -7459,8 +7507,10 @@ func NewPopulatedRequestFlush(r randyTypes, easy bool) *RequestFlush { func NewPopulatedRequestInfo(r randyTypes, easy bool) *RequestInfo { this := &RequestInfo{} this.Version = string(randStringTypes(r)) + this.BlockVersion = uint64(uint64(r.Uint32())) + this.P2PVersion = uint64(uint64(r.Uint32())) if !easy && r.Intn(10) != 0 { - this.XXX_unrecognized = randUnrecognizedTypes(r, 2) + this.XXX_unrecognized = randUnrecognizedTypes(r, 4) } return this } @@ -7717,6 +7767,7 @@ func NewPopulatedResponseInfo(r randyTypes, easy bool) *ResponseInfo { this := &ResponseInfo{} this.Data = string(randStringTypes(r)) this.Version = string(randStringTypes(r)) + this.AppVersion = uint64(uint64(r.Uint32())) this.LastBlockHeight = int64(r.Int63()) if r.Intn(2) == 0 { this.LastBlockHeight *= -1 @@ -7727,7 +7778,7 @@ func NewPopulatedResponseInfo(r randyTypes, easy bool) *ResponseInfo { this.LastBlockAppHash[i] = byte(r.Intn(256)) } if !easy && r.Intn(10) != 0 { - this.XXX_unrecognized = randUnrecognizedTypes(r, 5) + this.XXX_unrecognized = randUnrecognizedTypes(r, 6) } return this } @@ -8382,6 +8433,12 @@ func (m *RequestInfo) Size() (n int) { if l > 0 { n += 1 + l + sovTypes(uint64(l)) } + if m.BlockVersion != 0 { + n += 1 + sovTypes(uint64(m.BlockVersion)) + } + if m.P2PVersion != 0 { + n += 1 + sovTypes(uint64(m.P2PVersion)) + } if m.XXX_unrecognized != nil { n += len(m.XXX_unrecognized) } @@ -8693,6 +8750,9 @@ func (m *ResponseInfo) Size() (n int) { if l > 0 { n += 1 + l + sovTypes(uint64(l)) } + if m.AppVersion != 0 { + n += 1 + sovTypes(uint64(m.AppVersion)) + } if m.LastBlockHeight != 0 { n += 1 + sovTypes(uint64(m.LastBlockHeight)) } @@ -9781,6 +9841,44 @@ func (m *RequestInfo) Unmarshal(dAtA []byte) error { } m.Version = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex + case 2: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field BlockVersion", wireType) + } + m.BlockVersion = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTypes + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.BlockVersion |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + case 3: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field P2PVersion", wireType) + } + m.P2PVersion = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTypes + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.P2PVersion |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } default: iNdEx = preIndex skippy, err := skipTypes(dAtA[iNdEx:]) @@ -11459,6 +11557,25 @@ func (m *ResponseInfo) Unmarshal(dAtA []byte) error { m.Version = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex case 3: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field AppVersion", wireType) + } + m.AppVersion = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTypes + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.AppVersion |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + case 4: if wireType != 0 { return fmt.Errorf("proto: wrong wireType = %d for field LastBlockHeight", wireType) } @@ -11477,7 +11594,7 @@ func (m *ResponseInfo) Unmarshal(dAtA []byte) error { break } } - case 4: + case 5: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field LastBlockAppHash", wireType) } @@ -14768,145 +14885,148 @@ var ( ErrIntOverflowTypes = fmt.Errorf("proto: integer overflow") ) -func init() { proto.RegisterFile("abci/types/types.proto", fileDescriptor_types_07d64ea985a686e2) } +func init() { proto.RegisterFile("abci/types/types.proto", fileDescriptor_types_4449c1011851ea19) } func init() { - golang_proto.RegisterFile("abci/types/types.proto", fileDescriptor_types_07d64ea985a686e2) + golang_proto.RegisterFile("abci/types/types.proto", fileDescriptor_types_4449c1011851ea19) } -var fileDescriptor_types_07d64ea985a686e2 = []byte{ - // 2133 bytes of a gzipped FileDescriptorProto +var fileDescriptor_types_4449c1011851ea19 = []byte{ + // 2177 bytes of a gzipped FileDescriptorProto 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xdc, 0x58, 0xcd, 0x93, 0x1b, 0x47, - 0x15, 0xdf, 0xd1, 0x6a, 0x25, 0xcd, 0xdb, 0x5d, 0x49, 0x6e, 0x7f, 0xc9, 0x22, 0xac, 0x5d, 0x13, - 0x48, 0xbc, 0xc4, 0xd1, 0x06, 0x87, 0x50, 0xeb, 0x38, 0xa4, 0x6a, 0x65, 0x1b, 0x76, 0x2b, 0x01, - 0x96, 0xf1, 0x07, 0x17, 0xaa, 0xa6, 0x5a, 0x9a, 0xb6, 0x34, 0x65, 0x69, 0x66, 0x32, 0xd3, 0xda, - 0x68, 0x7d, 0xcc, 0x39, 0x87, 0x1c, 0xa8, 0xe2, 0x5f, 0xe0, 0x4f, 0xe0, 0xc8, 0x89, 0xca, 0x91, - 0x03, 0x67, 0x03, 0x4b, 0x71, 0x80, 0x2b, 0x45, 0x15, 0x47, 0xea, 0xbd, 0xee, 0xf9, 0xdc, 0x91, - 0x89, 0x03, 0x27, 0x2e, 0xd2, 0xf4, 0xfb, 0xe8, 0x8f, 0xd7, 0xef, 0xbd, 0xdf, 0x7b, 0x0d, 0x57, - 0xf8, 0x68, 0xec, 0xed, 0xc9, 0xd3, 0x50, 0xc4, 0xea, 0x77, 0x10, 0x46, 0x81, 0x0c, 0xd8, 0x06, - 0x0d, 0xfa, 0x6f, 0x4f, 0x3c, 0x39, 0x5d, 0x8c, 0x06, 0xe3, 0x60, 0xbe, 0x37, 0x09, 0x26, 0xc1, - 0x1e, 0x71, 0x47, 0x8b, 0xa7, 0x34, 0xa2, 0x01, 0x7d, 0x29, 0xad, 0xfe, 0xf5, 0x49, 0x10, 0x4c, - 0x66, 0x22, 0x93, 0x92, 0xde, 0x5c, 0xc4, 0x92, 0xcf, 0x43, 0x2d, 0xb0, 0x9f, 0x9b, 0x4f, 0x0a, - 0xdf, 0x15, 0xd1, 0xdc, 0xf3, 0x65, 0xfe, 0x73, 0xe6, 0x8d, 0xe2, 0xbd, 0x71, 0x30, 0x9f, 0x07, - 0x7e, 0x7e, 0x43, 0xfd, 0xbb, 0xff, 0x51, 0x73, 0x1c, 0x9d, 0x86, 0x32, 0xd8, 0x9b, 0x8b, 0xe8, - 0xd9, 0x4c, 0xe8, 0x3f, 0xa5, 0x6c, 0xfd, 0xae, 0x0e, 0x4d, 0x5b, 0x7c, 0xb2, 0x10, 0xb1, 0x64, - 0x37, 0xa1, 0x2e, 0xc6, 0xd3, 0xa0, 0x57, 0xbb, 0x61, 0xdc, 0xdc, 0xbc, 0xcd, 0x06, 0x6a, 0x11, - 0xcd, 0x7d, 0x30, 0x9e, 0x06, 0x87, 0x6b, 0x36, 0x49, 0xb0, 0xb7, 0x60, 0xe3, 0xe9, 0x6c, 0x11, - 0x4f, 0x7b, 0xeb, 0x24, 0x7a, 0xb1, 0x28, 0xfa, 0x43, 0x64, 0x1d, 0xae, 0xd9, 0x4a, 0x06, 0xa7, - 0xf5, 0xfc, 0xa7, 0x41, 0xaf, 0x5e, 0x35, 0xed, 0x91, 0xff, 0x94, 0xa6, 0x45, 0x09, 0xb6, 0x0f, - 0x10, 0x0b, 0xe9, 0x04, 0xa1, 0xf4, 0x02, 0xbf, 0xb7, 0x41, 0xf2, 0x57, 0x8b, 0xf2, 0x0f, 0x85, - 0xfc, 0x29, 0xb1, 0x0f, 0xd7, 0x6c, 0x33, 0x4e, 0x06, 0xa8, 0xe9, 0xf9, 0x9e, 0x74, 0xc6, 0x53, - 0xee, 0xf9, 0xbd, 0x46, 0x95, 0xe6, 0x91, 0xef, 0xc9, 0x7b, 0xc8, 0x46, 0x4d, 0x2f, 0x19, 0xe0, - 0x51, 0x3e, 0x59, 0x88, 0xe8, 0xb4, 0xd7, 0xac, 0x3a, 0xca, 0xcf, 0x90, 0x85, 0x47, 0x21, 0x19, - 0x76, 0x17, 0x36, 0x47, 0x62, 0xe2, 0xf9, 0xce, 0x68, 0x16, 0x8c, 0x9f, 0xf5, 0x5a, 0xa4, 0xd2, - 0x2b, 0xaa, 0x0c, 0x51, 0x60, 0x88, 0xfc, 0xc3, 0x35, 0x1b, 0x46, 0xe9, 0x88, 0xdd, 0x86, 0xd6, - 0x78, 0x2a, 0xc6, 0xcf, 0x1c, 0xb9, 0xec, 0x99, 0xa4, 0x79, 0xb9, 0xa8, 0x79, 0x0f, 0xb9, 0x8f, - 0x96, 0x87, 0x6b, 0x76, 0x73, 0xac, 0x3e, 0xd9, 0x7b, 0x60, 0x0a, 0xdf, 0xd5, 0xcb, 0x6d, 0x92, - 0xd2, 0x95, 0xd2, 0xbd, 0xf8, 0x6e, 0xb2, 0x58, 0x4b, 0xe8, 0x6f, 0x36, 0x80, 0x06, 0x3a, 0x8a, - 0x27, 0x7b, 0x5b, 0xa4, 0x73, 0xa9, 0xb4, 0x10, 0xf1, 0x0e, 0xd7, 0x6c, 0x2d, 0x85, 0xe6, 0x73, - 0xc5, 0xcc, 0x3b, 0x11, 0x11, 0x6e, 0xee, 0x62, 0x95, 0xf9, 0xee, 0x2b, 0x3e, 0x6d, 0xcf, 0x74, - 0x93, 0xc1, 0xb0, 0x09, 0x1b, 0x27, 0x7c, 0xb6, 0x10, 0xd6, 0x9b, 0xb0, 0x99, 0xf3, 0x14, 0xd6, - 0x83, 0xe6, 0x5c, 0xc4, 0x31, 0x9f, 0x88, 0x9e, 0x71, 0xc3, 0xb8, 0x69, 0xda, 0xc9, 0xd0, 0x6a, - 0xc3, 0x56, 0xde, 0x4f, 0x72, 0x8a, 0xe8, 0x0b, 0xa8, 0x78, 0x22, 0xa2, 0x18, 0x1d, 0x40, 0x2b, - 0xea, 0xa1, 0xf5, 0x3e, 0x74, 0xcb, 0x4e, 0xc0, 0xba, 0xb0, 0xfe, 0x4c, 0x9c, 0x6a, 0x49, 0xfc, - 0x64, 0x97, 0xf4, 0x86, 0xc8, 0x8b, 0x4d, 0x5b, 0xef, 0xee, 0x8b, 0x5a, 0xaa, 0x9c, 0xfa, 0x01, - 0xdb, 0x87, 0x3a, 0x46, 0x21, 0x69, 0x6f, 0xde, 0xee, 0x0f, 0x54, 0x88, 0x0e, 0x92, 0x10, 0x1d, - 0x3c, 0x4a, 0x42, 0x74, 0xd8, 0xfa, 0xf2, 0xc5, 0xf5, 0xb5, 0x2f, 0xfe, 0x78, 0xdd, 0xb0, 0x49, - 0x83, 0x5d, 0xc3, 0xab, 0xe4, 0x9e, 0xef, 0x78, 0xae, 0x5e, 0xa7, 0x49, 0xe3, 0x23, 0x97, 0x1d, - 0x40, 0x77, 0x1c, 0xf8, 0xb1, 0xf0, 0xe3, 0x45, 0xec, 0x84, 0x3c, 0xe2, 0xf3, 0x58, 0x47, 0x49, - 0x72, 0x71, 0xf7, 0x12, 0xf6, 0x31, 0x71, 0xed, 0xce, 0xb8, 0x48, 0x60, 0x1f, 0x00, 0x9c, 0xf0, - 0x99, 0xe7, 0x72, 0x19, 0x44, 0x71, 0xaf, 0x7e, 0x63, 0x3d, 0xa7, 0xfc, 0x24, 0x61, 0x3c, 0x0e, - 0x5d, 0x2e, 0xc5, 0xb0, 0x8e, 0x3b, 0xb3, 0x73, 0xf2, 0xec, 0x0d, 0xe8, 0xf0, 0x30, 0x74, 0x62, - 0xc9, 0xa5, 0x70, 0x46, 0xa7, 0x52, 0xc4, 0x14, 0x49, 0x5b, 0xf6, 0x36, 0x0f, 0xc3, 0x87, 0x48, - 0x1d, 0x22, 0xd1, 0x72, 0xd3, 0x7b, 0x20, 0x27, 0x67, 0x0c, 0xea, 0x2e, 0x97, 0x9c, 0xac, 0xb1, - 0x65, 0xd3, 0x37, 0xd2, 0x42, 0x2e, 0xa7, 0xfa, 0x8c, 0xf4, 0xcd, 0xae, 0x40, 0x63, 0x2a, 0xbc, - 0xc9, 0x54, 0xd2, 0xb1, 0xd6, 0x6d, 0x3d, 0x42, 0xc3, 0x87, 0x51, 0x70, 0x22, 0x28, 0xce, 0x5b, - 0xb6, 0x1a, 0x58, 0x7f, 0x35, 0xe0, 0xc2, 0xb9, 0xc0, 0xc0, 0x79, 0xa7, 0x3c, 0x9e, 0x26, 0x6b, - 0xe1, 0x37, 0x7b, 0x0b, 0xe7, 0xe5, 0xae, 0x88, 0x74, 0xfe, 0xd9, 0xd6, 0x27, 0x3e, 0x24, 0xa2, - 0x3e, 0xa8, 0x16, 0x61, 0x0f, 0xa0, 0x3b, 0xe3, 0xb1, 0x74, 0x94, 0xff, 0x3a, 0x94, 0x5f, 0xd6, - 0x0b, 0x31, 0xf5, 0x31, 0x4f, 0xfc, 0x1c, 0xdd, 0x4a, 0xab, 0xb7, 0x67, 0x05, 0x2a, 0x3b, 0x84, - 0x4b, 0xa3, 0xd3, 0xe7, 0xdc, 0x97, 0x9e, 0x2f, 0x9c, 0x73, 0x36, 0xef, 0xe8, 0xa9, 0x1e, 0x9c, - 0x78, 0xae, 0xf0, 0xc7, 0x89, 0xb1, 0x2f, 0xa6, 0x2a, 0xe9, 0x65, 0xc4, 0xd6, 0x0d, 0x68, 0x17, - 0xa3, 0x98, 0xb5, 0xa1, 0x26, 0x97, 0xfa, 0x84, 0x35, 0xb9, 0xb4, 0xac, 0xd4, 0x03, 0xd3, 0x50, - 0x3a, 0x27, 0xb3, 0x0b, 0x9d, 0x52, 0x58, 0xe7, 0xcc, 0x6d, 0xe4, 0xcd, 0x6d, 0x75, 0x60, 0xbb, - 0x10, 0xcd, 0xd6, 0xe7, 0x1b, 0xd0, 0xb2, 0x45, 0x1c, 0xa2, 0x33, 0xb1, 0x7d, 0x30, 0xc5, 0x72, - 0x2c, 0x54, 0x22, 0x35, 0x4a, 0x69, 0x4a, 0xc9, 0x3c, 0x48, 0xf8, 0x18, 0xd0, 0xa9, 0x30, 0xdb, - 0x2d, 0x80, 0xc0, 0xc5, 0xb2, 0x52, 0x1e, 0x05, 0x6e, 0x15, 0x51, 0xe0, 0x52, 0x49, 0xb6, 0x04, - 0x03, 0xbb, 0x05, 0x18, 0x28, 0x4f, 0x5c, 0xc0, 0x81, 0x3b, 0x15, 0x38, 0x50, 0xde, 0xfe, 0x0a, - 0x20, 0xb8, 0x53, 0x01, 0x04, 0xbd, 0x73, 0x6b, 0x55, 0x22, 0xc1, 0xad, 0x22, 0x12, 0x94, 0x8f, - 0x53, 0x82, 0x82, 0x0f, 0xaa, 0xa0, 0xe0, 0x5a, 0x49, 0x67, 0x25, 0x16, 0xbc, 0x7b, 0x0e, 0x0b, - 0xae, 0x94, 0x54, 0x2b, 0xc0, 0xe0, 0x4e, 0x21, 0x4b, 0x43, 0xe5, 0xd9, 0xaa, 0xd3, 0x34, 0xfb, - 0xfe, 0x79, 0x1c, 0xb9, 0x5a, 0xbe, 0xda, 0x2a, 0x20, 0xd9, 0x2b, 0x01, 0xc9, 0xe5, 0xf2, 0x2e, - 0x4b, 0x48, 0x92, 0xe1, 0xc1, 0x2e, 0xc6, 0x7d, 0xc9, 0xd3, 0x30, 0x47, 0x88, 0x28, 0x0a, 0x22, - 0x9d, 0xb0, 0xd5, 0xc0, 0xba, 0x89, 0x99, 0x28, 0xf3, 0xaf, 0x97, 0x60, 0x07, 0x39, 0x7d, 0xce, - 0xbb, 0xac, 0x5f, 0x19, 0x99, 0x2e, 0x45, 0x74, 0x3e, 0x8b, 0x99, 0x3a, 0x8b, 0xe5, 0x20, 0xa5, - 0x56, 0x80, 0x14, 0xf6, 0x1d, 0xb8, 0x40, 0x69, 0x84, 0xec, 0xe2, 0x14, 0xd2, 0x5a, 0x07, 0x19, - 0xca, 0x20, 0x2a, 0xbf, 0xbd, 0x0d, 0x17, 0x73, 0xb2, 0x98, 0x62, 0x29, 0x85, 0xd5, 0x29, 0x78, - 0xbb, 0xa9, 0xf4, 0x41, 0x18, 0x1e, 0xf2, 0x78, 0x6a, 0xfd, 0x38, 0x3b, 0x7f, 0x06, 0x57, 0x0c, - 0xea, 0xe3, 0xc0, 0x55, 0xc7, 0xda, 0xb6, 0xe9, 0x1b, 0x21, 0x6c, 0x16, 0x4c, 0x68, 0x55, 0xd3, - 0xc6, 0x4f, 0x94, 0x4a, 0x23, 0xc5, 0x54, 0x21, 0x61, 0xfd, 0xd2, 0xc8, 0xe6, 0xcb, 0x10, 0xac, - 0x0a, 0x6c, 0x8c, 0xff, 0x06, 0x6c, 0x6a, 0xaf, 0x06, 0x36, 0xd6, 0x99, 0x91, 0xdd, 0x48, 0x0a, - 0x23, 0x5f, 0xef, 0x88, 0xe8, 0x1c, 0x9e, 0xef, 0x8a, 0x25, 0x05, 0xfc, 0xba, 0xad, 0x06, 0x09, - 0xc2, 0x37, 0xc8, 0xcc, 0x45, 0x84, 0x6f, 0x12, 0x4d, 0x0d, 0xd8, 0xeb, 0x04, 0x3f, 0xc1, 0x53, - 0x1d, 0x89, 0xdb, 0x03, 0x5d, 0xe6, 0x1e, 0x23, 0xd1, 0x56, 0xbc, 0x5c, 0x32, 0x35, 0x0b, 0xd8, - 0xf5, 0x1a, 0x98, 0xb8, 0xd1, 0x38, 0xe4, 0x63, 0x41, 0x81, 0x65, 0xda, 0x19, 0xc1, 0x3a, 0x06, - 0x76, 0x3e, 0xa0, 0xd9, 0xfb, 0x50, 0x97, 0x7c, 0x82, 0xf6, 0x46, 0x93, 0xb5, 0x07, 0xaa, 0x32, - 0x1f, 0x7c, 0xf4, 0xe4, 0x98, 0x7b, 0xd1, 0xf0, 0x0a, 0x9a, 0xea, 0xef, 0x2f, 0xae, 0xb7, 0x51, - 0xe6, 0x56, 0x30, 0xf7, 0xa4, 0x98, 0x87, 0xf2, 0xd4, 0x26, 0x1d, 0xeb, 0x1f, 0x06, 0x26, 0xfa, - 0x42, 0xa0, 0x57, 0x1a, 0x2e, 0xf1, 0xe6, 0x5a, 0x0e, 0x93, 0xbf, 0x9a, 0x31, 0xbf, 0x09, 0x30, - 0xe1, 0xb1, 0xf3, 0x29, 0xf7, 0xa5, 0x70, 0xb5, 0x45, 0xcd, 0x09, 0x8f, 0x7f, 0x4e, 0x04, 0x2c, - 0x60, 0x90, 0xbd, 0x88, 0x85, 0x4b, 0xa6, 0x5d, 0xb7, 0x9b, 0x13, 0x1e, 0x3f, 0x8e, 0x85, 0x9b, - 0x9e, 0xab, 0xf9, 0xea, 0xe7, 0x2a, 0xda, 0xb1, 0x55, 0xb6, 0xe3, 0x3f, 0x73, 0x3e, 0x9c, 0x61, - 0xe0, 0xff, 0xff, 0xb9, 0xff, 0x66, 0x20, 0xf4, 0x17, 0xb3, 0x2c, 0x3b, 0x82, 0x0b, 0x69, 0x1c, - 0x39, 0x0b, 0x8a, 0xaf, 0xc4, 0x97, 0x5e, 0x1e, 0x7e, 0xdd, 0x93, 0x22, 0x39, 0x66, 0x3f, 0x81, - 0xab, 0xa5, 0x2c, 0x90, 0x4e, 0x58, 0x7b, 0x69, 0x32, 0xb8, 0x5c, 0x4c, 0x06, 0xc9, 0x7c, 0x89, - 0x25, 0xd6, 0xbf, 0x86, 0x67, 0x7f, 0x0b, 0xeb, 0xa0, 0x3c, 0x36, 0x54, 0xdd, 0xa5, 0xf5, 0x99, - 0x01, 0x9d, 0xd2, 0x66, 0xd8, 0x1e, 0x80, 0x4a, 0xad, 0xb1, 0xf7, 0x3c, 0xa9, 0xc9, 0xbb, 0x7a, - 0xe3, 0x64, 0xb2, 0x87, 0xde, 0x73, 0x61, 0x9b, 0xa3, 0xe4, 0x93, 0x7d, 0x08, 0x1d, 0xa1, 0x2b, - 0xb3, 0x24, 0xf7, 0xd5, 0x0a, 0x20, 0x95, 0xd4, 0x6d, 0xfa, 0xb4, 0x6d, 0x51, 0x18, 0x5b, 0x07, - 0x60, 0xa6, 0xf3, 0xb2, 0x6f, 0x80, 0x39, 0xe7, 0x4b, 0x5d, 0x2f, 0xab, 0x4a, 0xab, 0x35, 0xe7, - 0x4b, 0x2a, 0x95, 0xd9, 0x55, 0x68, 0x22, 0x73, 0xc2, 0xd5, 0x0a, 0xeb, 0x76, 0x63, 0xce, 0x97, - 0x3f, 0xe2, 0xb1, 0xb5, 0x0b, 0xed, 0xe2, 0x22, 0x89, 0x68, 0x82, 0x5d, 0x4a, 0xf4, 0x60, 0x22, - 0xac, 0x87, 0xd0, 0x2e, 0x96, 0xa4, 0x98, 0xc7, 0xa2, 0x60, 0xe1, 0xbb, 0x24, 0xb8, 0x61, 0xab, - 0x01, 0xf6, 0xa3, 0x27, 0x81, 0xba, 0xba, 0x7c, 0x0d, 0xfa, 0x24, 0x90, 0x22, 0x57, 0xc8, 0x2a, - 0x19, 0xeb, 0xb3, 0x0d, 0x68, 0xa8, 0xfa, 0x98, 0x0d, 0x8a, 0x7d, 0x13, 0xde, 0x9b, 0xd6, 0x54, - 0x54, 0xad, 0x98, 0x42, 0xdf, 0x1b, 0xe5, 0x16, 0x66, 0xb8, 0x79, 0xf6, 0xe2, 0x7a, 0x93, 0x70, - 0xe5, 0xe8, 0x7e, 0xd6, 0xcf, 0xac, 0x2a, 0xf7, 0x93, 0xe6, 0xa9, 0xfe, 0xca, 0xcd, 0xd3, 0x55, - 0x68, 0xfa, 0x8b, 0xb9, 0x23, 0x97, 0xb1, 0x8e, 0xcf, 0x86, 0xbf, 0x98, 0x3f, 0x5a, 0xc6, 0x78, - 0x07, 0x32, 0x90, 0x7c, 0x46, 0x2c, 0x15, 0x9d, 0x2d, 0x22, 0x20, 0x73, 0x1f, 0xb6, 0x73, 0xf0, - 0xeb, 0xb9, 0xba, 0x4a, 0x6b, 0xe7, 0x3d, 0xe4, 0xe8, 0xbe, 0x3e, 0xe5, 0x66, 0x0a, 0xc7, 0x47, - 0x2e, 0xbb, 0x59, 0xec, 0x15, 0x08, 0xb5, 0x5b, 0xe4, 0x8c, 0xb9, 0x76, 0x00, 0x31, 0x1b, 0x37, - 0x80, 0xee, 0xa9, 0x44, 0x4c, 0x12, 0x69, 0x21, 0x81, 0x98, 0x6f, 0x42, 0x27, 0x03, 0x3e, 0x25, - 0x02, 0x6a, 0x96, 0x8c, 0x4c, 0x82, 0xef, 0xc0, 0x25, 0x5f, 0x2c, 0xa5, 0x53, 0x96, 0xde, 0x24, - 0x69, 0x86, 0xbc, 0x27, 0x45, 0x8d, 0x6f, 0x43, 0x3b, 0x0b, 0x60, 0x92, 0xdd, 0x52, 0x1d, 0x5b, - 0x4a, 0x25, 0xb1, 0x6b, 0xd0, 0x4a, 0xcb, 0x8e, 0x6d, 0x12, 0x68, 0x72, 0x55, 0x6d, 0xa4, 0x85, - 0x4c, 0x24, 0xe2, 0xc5, 0x4c, 0xea, 0x49, 0xda, 0x24, 0x43, 0x85, 0x8c, 0xad, 0xe8, 0x24, 0xfb, - 0x3a, 0x6c, 0xa7, 0x71, 0x43, 0x72, 0x1d, 0x92, 0xdb, 0x4a, 0x88, 0x24, 0xb4, 0x0b, 0xdd, 0x30, - 0x0a, 0xc2, 0x20, 0x16, 0x91, 0xc3, 0x5d, 0x37, 0x12, 0x71, 0xdc, 0xeb, 0xaa, 0xf9, 0x12, 0xfa, - 0x81, 0x22, 0x5b, 0xdf, 0x85, 0xa6, 0xf6, 0x31, 0x74, 0x69, 0xb2, 0x3a, 0xb9, 0x60, 0xdd, 0x56, - 0x03, 0xcc, 0xdc, 0x07, 0x61, 0x48, 0x5e, 0x56, 0xb7, 0xf1, 0xd3, 0xfa, 0x05, 0x34, 0xf5, 0x85, - 0x55, 0xb6, 0x82, 0x3f, 0x80, 0xad, 0x90, 0x47, 0x78, 0x8c, 0x7c, 0x43, 0x98, 0x14, 0xe4, 0xc7, - 0x3c, 0x92, 0x0f, 0x85, 0x2c, 0xf4, 0x85, 0x9b, 0x24, 0xaf, 0x48, 0xd6, 0x1d, 0xd8, 0x2e, 0xc8, - 0xe0, 0xb6, 0xc8, 0x8f, 0x92, 0x48, 0xa3, 0x41, 0xba, 0x72, 0x2d, 0x5b, 0xd9, 0xba, 0x0b, 0x66, - 0x7a, 0x37, 0x58, 0x37, 0x26, 0x47, 0x37, 0xb4, 0xb9, 0xd5, 0x90, 0x7a, 0xdd, 0xe0, 0x53, 0x11, - 0xe9, 0x98, 0x50, 0x03, 0xeb, 0x31, 0x74, 0x4a, 0x29, 0x9b, 0xdd, 0x82, 0x66, 0xb8, 0x18, 0x39, - 0xc9, 0x1b, 0x45, 0xd6, 0xd5, 0x1e, 0x2f, 0x46, 0x1f, 0x89, 0xd3, 0xa4, 0xab, 0x0d, 0x69, 0x94, - 0x4d, 0x5b, 0xcb, 0x4f, 0x3b, 0x83, 0x56, 0x12, 0xfd, 0xec, 0x7b, 0x60, 0xa6, 0x6e, 0x55, 0xca, - 0x91, 0xe9, 0xd2, 0x7a, 0xd2, 0x4c, 0x10, 0xbd, 0x23, 0xf6, 0x26, 0xbe, 0x70, 0x9d, 0x2c, 0x84, - 0x68, 0x8d, 0x96, 0xdd, 0x51, 0x8c, 0x8f, 0x93, 0x78, 0xb1, 0xde, 0x81, 0x86, 0xda, 0x1b, 0xda, - 0x07, 0x67, 0x4e, 0x4a, 0x69, 0xfc, 0xae, 0x4c, 0xe6, 0x7f, 0x30, 0xa0, 0x95, 0x64, 0xc1, 0x4a, - 0xa5, 0xc2, 0xa6, 0x6b, 0x5f, 0x75, 0xd3, 0xff, 0xfb, 0xc4, 0x73, 0x0b, 0x98, 0xca, 0x2f, 0x27, - 0x81, 0xf4, 0xfc, 0x89, 0xa3, 0x6c, 0xad, 0x72, 0x50, 0x97, 0x38, 0x4f, 0x88, 0x71, 0x8c, 0xf4, - 0xdb, 0x9f, 0x6f, 0x40, 0xe7, 0x60, 0x78, 0xef, 0xe8, 0x20, 0x0c, 0x67, 0xde, 0x98, 0x53, 0xfd, - 0xbe, 0x07, 0x75, 0xea, 0x50, 0x2a, 0xde, 0x46, 0xfb, 0x55, 0xad, 0x32, 0xbb, 0x0d, 0x1b, 0xd4, - 0xa8, 0xb0, 0xaa, 0x27, 0xd2, 0x7e, 0x65, 0xc7, 0x8c, 0x8b, 0xa8, 0x56, 0xe6, 0xfc, 0x4b, 0x69, - 0xbf, 0xaa, 0x6d, 0x66, 0x1f, 0x82, 0x99, 0xb5, 0x18, 0xab, 0xde, 0x4b, 0xfb, 0x2b, 0x1b, 0x68, - 0xd4, 0xcf, 0xca, 0xb1, 0x55, 0xcf, 0x7e, 0xfd, 0x95, 0x9d, 0x26, 0xdb, 0x87, 0x66, 0x52, 0xc4, - 0x56, 0xbf, 0x68, 0xf6, 0x57, 0x34, 0xb7, 0x68, 0x1e, 0xd5, 0x35, 0x54, 0x3d, 0xbb, 0xf6, 0x2b, - 0x3b, 0x70, 0xf6, 0x1e, 0x34, 0x74, 0x65, 0x51, 0xf9, 0xaa, 0xd9, 0xaf, 0x6e, 0x51, 0xf1, 0x90, - 0x59, 0xdf, 0xb4, 0xea, 0x69, 0xb8, 0xbf, 0xf2, 0xa9, 0x80, 0x1d, 0x00, 0xe4, 0x8a, 0xff, 0x95, - 0x6f, 0xbe, 0xfd, 0xd5, 0x4f, 0x00, 0xec, 0x2e, 0xb4, 0xb2, 0x67, 0x9d, 0xea, 0x57, 0xdc, 0xfe, - 0xaa, 0xae, 0x7c, 0xf8, 0xda, 0xbf, 0xfe, 0xbc, 0x63, 0xfc, 0xfa, 0x6c, 0xc7, 0xf8, 0xcd, 0xd9, - 0x8e, 0xf1, 0xe5, 0xd9, 0x8e, 0xf1, 0xfb, 0xb3, 0x1d, 0xe3, 0x4f, 0x67, 0x3b, 0xc6, 0x6f, 0xff, - 0xb2, 0x63, 0x8c, 0x1a, 0xe4, 0xfe, 0xef, 0xfe, 0x3b, 0x00, 0x00, 0xff, 0xff, 0xf4, 0x43, 0x2c, - 0xa9, 0xb5, 0x18, 0x00, 0x00, + 0x15, 0xdf, 0xd1, 0x6a, 0x25, 0xcd, 0xd3, 0xea, 0x23, 0xed, 0xb5, 0x2d, 0x8b, 0xb0, 0xeb, 0x1a, + 0x43, 0xe2, 0x25, 0x8e, 0x36, 0x6c, 0x08, 0xb5, 0x8e, 0x43, 0xaa, 0x56, 0xb6, 0x61, 0xb7, 0x12, + 0x60, 0x19, 0xdb, 0xcb, 0x85, 0xaa, 0xa9, 0x96, 0xa6, 0x2d, 0x4d, 0x59, 0x9a, 0x99, 0xcc, 0xb4, + 0x36, 0x5a, 0x1f, 0x73, 0xce, 0x21, 0x07, 0xfe, 0x08, 0xfe, 0x84, 0x1c, 0x39, 0x51, 0x39, 0x72, + 0xe0, 0x6c, 0x60, 0x29, 0x0e, 0x70, 0xa5, 0xa8, 0xe2, 0x48, 0xf5, 0xeb, 0xee, 0xf9, 0xda, 0x91, + 0x89, 0x03, 0x27, 0x2e, 0x52, 0xf7, 0xfb, 0xe8, 0x8f, 0x37, 0xef, 0xbd, 0xdf, 0x7b, 0x0d, 0xd7, + 0xe8, 0x68, 0xec, 0xed, 0xf1, 0xf3, 0x90, 0xc5, 0xf2, 0x77, 0x10, 0x46, 0x01, 0x0f, 0xc8, 0x06, + 0x4e, 0xfa, 0x6f, 0x4f, 0x3c, 0x3e, 0x5d, 0x8c, 0x06, 0xe3, 0x60, 0xbe, 0x37, 0x09, 0x26, 0xc1, + 0x1e, 0x72, 0x47, 0x8b, 0xa7, 0x38, 0xc3, 0x09, 0x8e, 0xa4, 0x56, 0x7f, 0x67, 0x12, 0x04, 0x93, + 0x19, 0x4b, 0xa5, 0xb8, 0x37, 0x67, 0x31, 0xa7, 0xf3, 0x50, 0x09, 0x1c, 0x64, 0xd6, 0xe3, 0xcc, + 0x77, 0x59, 0x34, 0xf7, 0x7c, 0x9e, 0x1d, 0xce, 0xbc, 0x51, 0xbc, 0x37, 0x0e, 0xe6, 0xf3, 0xc0, + 0xcf, 0x1e, 0xa8, 0x7f, 0xef, 0x3f, 0x6a, 0x8e, 0xa3, 0xf3, 0x90, 0x07, 0x7b, 0x73, 0x16, 0x3d, + 0x9b, 0x31, 0xf5, 0x27, 0x95, 0xad, 0xdf, 0x55, 0xa1, 0x6e, 0xb3, 0x4f, 0x16, 0x2c, 0xe6, 0xe4, + 0x36, 0x54, 0xd9, 0x78, 0x1a, 0xf4, 0x2a, 0x37, 0x8d, 0xdb, 0xcd, 0x7d, 0x32, 0x90, 0x9b, 0x28, + 0xee, 0xc3, 0xf1, 0x34, 0x38, 0x5a, 0xb3, 0x51, 0x82, 0xbc, 0x05, 0x1b, 0x4f, 0x67, 0x8b, 0x78, + 0xda, 0x5b, 0x47, 0xd1, 0x2b, 0x79, 0xd1, 0x1f, 0x0b, 0xd6, 0xd1, 0x9a, 0x2d, 0x65, 0xc4, 0xb2, + 0x9e, 0xff, 0x34, 0xe8, 0x55, 0xcb, 0x96, 0x3d, 0xf6, 0x9f, 0xe2, 0xb2, 0x42, 0x82, 0x1c, 0x00, + 0xc4, 0x8c, 0x3b, 0x41, 0xc8, 0xbd, 0xc0, 0xef, 0x6d, 0xa0, 0xfc, 0xf5, 0xbc, 0xfc, 0x23, 0xc6, + 0x7f, 0x8e, 0xec, 0xa3, 0x35, 0xdb, 0x8c, 0xf5, 0x44, 0x68, 0x7a, 0xbe, 0xc7, 0x9d, 0xf1, 0x94, + 0x7a, 0x7e, 0xaf, 0x56, 0xa6, 0x79, 0xec, 0x7b, 0xfc, 0xbe, 0x60, 0x0b, 0x4d, 0x4f, 0x4f, 0xc4, + 0x55, 0x3e, 0x59, 0xb0, 0xe8, 0xbc, 0x57, 0x2f, 0xbb, 0xca, 0x2f, 0x04, 0x4b, 0x5c, 0x05, 0x65, + 0xc8, 0x3d, 0x68, 0x8e, 0xd8, 0xc4, 0xf3, 0x9d, 0xd1, 0x2c, 0x18, 0x3f, 0xeb, 0x35, 0x50, 0xa5, + 0x97, 0x57, 0x19, 0x0a, 0x81, 0xa1, 0xe0, 0x1f, 0xad, 0xd9, 0x30, 0x4a, 0x66, 0x64, 0x1f, 0x1a, + 0xe3, 0x29, 0x1b, 0x3f, 0x73, 0xf8, 0xb2, 0x67, 0xa2, 0xe6, 0xd5, 0xbc, 0xe6, 0x7d, 0xc1, 0x7d, + 0xbc, 0x3c, 0x5a, 0xb3, 0xeb, 0x63, 0x39, 0x24, 0xef, 0x81, 0xc9, 0x7c, 0x57, 0x6d, 0xd7, 0x44, + 0xa5, 0x6b, 0x85, 0xef, 0xe2, 0xbb, 0x7a, 0xb3, 0x06, 0x53, 0x63, 0x32, 0x80, 0x9a, 0x70, 0x14, + 0x8f, 0xf7, 0x36, 0x51, 0x67, 0xab, 0xb0, 0x11, 0xf2, 0x8e, 0xd6, 0x6c, 0x25, 0x25, 0xcc, 0xe7, + 0xb2, 0x99, 0x77, 0xc6, 0x22, 0x71, 0xb8, 0x2b, 0x65, 0xe6, 0x7b, 0x20, 0xf9, 0x78, 0x3c, 0xd3, + 0xd5, 0x93, 0x61, 0x1d, 0x36, 0xce, 0xe8, 0x6c, 0xc1, 0xac, 0x37, 0xa1, 0x99, 0xf1, 0x14, 0xd2, + 0x83, 0xfa, 0x9c, 0xc5, 0x31, 0x9d, 0xb0, 0x9e, 0x71, 0xd3, 0xb8, 0x6d, 0xda, 0x7a, 0x6a, 0xb5, + 0x61, 0x33, 0xeb, 0x27, 0xd6, 0x3c, 0x51, 0x14, 0xbe, 0x20, 0x14, 0xcf, 0x58, 0x14, 0x0b, 0x07, + 0x50, 0x8a, 0x6a, 0x4a, 0x6e, 0x41, 0x0b, 0xed, 0xe0, 0x68, 0xbe, 0xf0, 0xd3, 0xaa, 0xbd, 0x89, + 0xc4, 0x53, 0x25, 0xb4, 0x03, 0xcd, 0x70, 0x3f, 0x4c, 0x44, 0xd6, 0x51, 0x04, 0xc2, 0xfd, 0x50, + 0x09, 0x58, 0xef, 0x43, 0xb7, 0xe8, 0x4a, 0xa4, 0x0b, 0xeb, 0xcf, 0xd8, 0xb9, 0xda, 0x4f, 0x0c, + 0xc9, 0x96, 0xba, 0x16, 0xee, 0x61, 0xda, 0xea, 0x8e, 0x5f, 0x54, 0x12, 0xe5, 0xc4, 0x9b, 0xc8, + 0x01, 0x54, 0x45, 0x2c, 0xa3, 0x76, 0x73, 0xbf, 0x3f, 0x90, 0x81, 0x3e, 0xd0, 0x81, 0x3e, 0x78, + 0xac, 0x03, 0x7d, 0xd8, 0xf8, 0xea, 0xc5, 0xce, 0xda, 0x17, 0x7f, 0xdc, 0x31, 0x6c, 0xd4, 0x20, + 0x37, 0x84, 0x43, 0x50, 0xcf, 0x77, 0x3c, 0x57, 0xed, 0x53, 0xc7, 0xf9, 0xb1, 0x4b, 0x0e, 0xa1, + 0x3b, 0x0e, 0xfc, 0x98, 0xf9, 0xf1, 0x22, 0x76, 0x42, 0x1a, 0xd1, 0x79, 0xac, 0x62, 0x4d, 0x7f, + 0xfe, 0xfb, 0x9a, 0x7d, 0x82, 0x5c, 0xbb, 0x33, 0xce, 0x13, 0xc8, 0x07, 0x00, 0x67, 0x74, 0xe6, + 0xb9, 0x94, 0x07, 0x51, 0xdc, 0xab, 0xde, 0x5c, 0xcf, 0x28, 0x9f, 0x6a, 0xc6, 0x93, 0xd0, 0xa5, + 0x9c, 0x0d, 0xab, 0xe2, 0x64, 0x76, 0x46, 0x9e, 0xbc, 0x01, 0x1d, 0x1a, 0x86, 0x4e, 0xcc, 0x29, + 0x67, 0xce, 0xe8, 0x9c, 0xb3, 0x18, 0xe3, 0x71, 0xd3, 0x6e, 0xd1, 0x30, 0x7c, 0x24, 0xa8, 0x43, + 0x41, 0xb4, 0xdc, 0xe4, 0x6b, 0x62, 0xa8, 0x10, 0x02, 0x55, 0x97, 0x72, 0x8a, 0xd6, 0xd8, 0xb4, + 0x71, 0x2c, 0x68, 0x21, 0xe5, 0x53, 0x75, 0x47, 0x1c, 0x93, 0x6b, 0x50, 0x9b, 0x32, 0x6f, 0x32, + 0xe5, 0x78, 0xad, 0x75, 0x5b, 0xcd, 0x84, 0xe1, 0xc3, 0x28, 0x38, 0x63, 0x98, 0x2d, 0x1a, 0xb6, + 0x9c, 0x58, 0x7f, 0x35, 0xe0, 0xb5, 0x4b, 0xe1, 0x25, 0xd6, 0x9d, 0xd2, 0x78, 0xaa, 0xf7, 0x12, + 0x63, 0xf2, 0x96, 0x58, 0x97, 0xba, 0x2c, 0x52, 0x59, 0xac, 0xa5, 0x6e, 0x7c, 0x84, 0x44, 0x75, + 0x51, 0x25, 0x42, 0x1e, 0x42, 0x77, 0x46, 0x63, 0xee, 0xc8, 0x28, 0x70, 0x30, 0x4b, 0xad, 0xe7, + 0x22, 0xf3, 0x63, 0xaa, 0xa3, 0x45, 0x38, 0xa7, 0x52, 0x6f, 0xcf, 0x72, 0x54, 0x72, 0x04, 0x5b, + 0xa3, 0xf3, 0xe7, 0xd4, 0xe7, 0x9e, 0xcf, 0x9c, 0x4b, 0x36, 0xef, 0xa8, 0xa5, 0x1e, 0x9e, 0x79, + 0x2e, 0xf3, 0xc7, 0xda, 0xd8, 0x57, 0x12, 0x95, 0xe4, 0x63, 0xc4, 0xd6, 0x4d, 0x68, 0xe7, 0x73, + 0x01, 0x69, 0x43, 0x85, 0x2f, 0xd5, 0x0d, 0x2b, 0x7c, 0x69, 0x59, 0x89, 0x07, 0x26, 0x01, 0x79, + 0x49, 0x66, 0x17, 0x3a, 0x85, 0xe4, 0x90, 0x31, 0xb7, 0x91, 0x35, 0xb7, 0xd5, 0x81, 0x56, 0x2e, + 0x27, 0x58, 0x9f, 0x6f, 0x40, 0xc3, 0x66, 0x71, 0x28, 0x9c, 0x89, 0x1c, 0x80, 0xc9, 0x96, 0x63, + 0x26, 0xd3, 0xb1, 0x51, 0x48, 0x76, 0x52, 0xe6, 0xa1, 0xe6, 0x8b, 0xb4, 0x90, 0x08, 0x93, 0xdd, + 0x1c, 0x94, 0x5c, 0x29, 0x2a, 0x65, 0xb1, 0xe4, 0x4e, 0x1e, 0x4b, 0xb6, 0x0a, 0xb2, 0x05, 0x30, + 0xd9, 0xcd, 0x81, 0x49, 0x71, 0xe1, 0x1c, 0x9a, 0xdc, 0x2d, 0x41, 0x93, 0xe2, 0xf1, 0x57, 0xc0, + 0xc9, 0xdd, 0x12, 0x38, 0xe9, 0x5d, 0xda, 0xab, 0x14, 0x4f, 0xee, 0xe4, 0xf1, 0xa4, 0x78, 0x9d, + 0x02, 0xa0, 0x7c, 0x50, 0x06, 0x28, 0x37, 0x0a, 0x3a, 0x2b, 0x11, 0xe5, 0xdd, 0x4b, 0x88, 0x72, + 0xad, 0xa0, 0x5a, 0x02, 0x29, 0x77, 0x73, 0xb9, 0x1e, 0x4a, 0xef, 0x56, 0x9e, 0xec, 0xc9, 0x0f, + 0x2f, 0xa3, 0xd1, 0xf5, 0xe2, 0xa7, 0x2d, 0x83, 0xa3, 0xbd, 0x02, 0x1c, 0x5d, 0x2d, 0x9e, 0xb2, + 0x80, 0x47, 0x29, 0xaa, 0xec, 0x8a, 0xb8, 0x2f, 0x78, 0x9a, 0xc8, 0x11, 0x2c, 0x8a, 0x82, 0x48, + 0x25, 0x6c, 0x39, 0xb1, 0x6e, 0x8b, 0x4c, 0x94, 0xfa, 0xd7, 0x4b, 0x10, 0x08, 0x9d, 0x3e, 0xe3, + 0x5d, 0xd6, 0x97, 0x46, 0xaa, 0x8b, 0x11, 0x9d, 0xcd, 0x62, 0xa6, 0xca, 0x62, 0x19, 0x60, 0xaa, + 0xe4, 0x81, 0x69, 0x07, 0x9a, 0x22, 0x57, 0x16, 0x30, 0x87, 0x86, 0x1a, 0x73, 0xc8, 0xf7, 0xe0, + 0x35, 0xcc, 0x33, 0x12, 0xbe, 0x54, 0x20, 0x56, 0x31, 0x10, 0x3b, 0x82, 0x21, 0x2d, 0x26, 0x13, + 0xe0, 0xdb, 0x70, 0x25, 0x23, 0x2b, 0xd6, 0xc5, 0x1c, 0x27, 0x93, 0x6f, 0x37, 0x91, 0x3e, 0x0c, + 0xc3, 0x23, 0x1a, 0x4f, 0xad, 0x9f, 0xa6, 0x06, 0x4a, 0xf1, 0x8c, 0x40, 0x75, 0x1c, 0xb8, 0xf2, + 0xde, 0x2d, 0x1b, 0xc7, 0x02, 0xe3, 0x66, 0xc1, 0x04, 0x0f, 0x67, 0xda, 0x62, 0x28, 0xa4, 0x92, + 0x50, 0x32, 0x65, 0xcc, 0x58, 0xbf, 0x36, 0xd2, 0xf5, 0x52, 0x88, 0x2b, 0x43, 0x23, 0xe3, 0xbf, + 0x41, 0xa3, 0xca, 0xab, 0xa1, 0x91, 0x75, 0x61, 0xa4, 0x9f, 0x2c, 0xc1, 0x99, 0x6f, 0x76, 0x45, + 0xe1, 0x3d, 0x9e, 0xef, 0xb2, 0x25, 0x9a, 0x74, 0xdd, 0x96, 0x13, 0x5d, 0x02, 0xd4, 0xd0, 0xcc, + 0xf9, 0x12, 0xa0, 0x8e, 0x34, 0x39, 0x21, 0xb7, 0x10, 0x9f, 0x82, 0xa7, 0x2a, 0x54, 0x5b, 0x03, + 0x55, 0x4d, 0x9f, 0x08, 0xa2, 0x2d, 0x79, 0x99, 0x6c, 0x6b, 0xe6, 0xc0, 0xed, 0x75, 0x30, 0xc5, + 0x41, 0xe3, 0x90, 0x8e, 0x19, 0x46, 0x9e, 0x69, 0xa7, 0x04, 0xeb, 0x04, 0xc8, 0xe5, 0x88, 0x27, + 0xef, 0x43, 0x95, 0xd3, 0x89, 0xb0, 0xb7, 0x30, 0x59, 0x7b, 0x20, 0x1b, 0x80, 0xc1, 0x47, 0xa7, + 0x27, 0xd4, 0x8b, 0x86, 0xd7, 0x84, 0xa9, 0xfe, 0xfe, 0x62, 0xa7, 0x2d, 0x64, 0xee, 0x04, 0x73, + 0x8f, 0xb3, 0x79, 0xc8, 0xcf, 0x6d, 0xd4, 0xb1, 0xfe, 0x61, 0x08, 0x24, 0xc8, 0x65, 0x82, 0x52, + 0xc3, 0x69, 0x77, 0xaf, 0x64, 0x40, 0xfb, 0xeb, 0x19, 0xf3, 0xdb, 0x00, 0x13, 0x1a, 0x3b, 0x9f, + 0x52, 0x9f, 0x33, 0x57, 0x59, 0xd4, 0x9c, 0xd0, 0xf8, 0x97, 0x48, 0x10, 0x15, 0x8e, 0x60, 0x2f, + 0x62, 0xe6, 0xa2, 0x69, 0xd7, 0xed, 0xfa, 0x84, 0xc6, 0x4f, 0x62, 0xe6, 0x26, 0xf7, 0xaa, 0xbf, + 0xfa, 0xbd, 0xf2, 0x76, 0x6c, 0x14, 0xed, 0xf8, 0xcf, 0x8c, 0x0f, 0xa7, 0x20, 0xf9, 0xff, 0x7f, + 0xef, 0xbf, 0x19, 0xa2, 0x36, 0xc8, 0xa7, 0x61, 0x72, 0x0c, 0xaf, 0x25, 0x71, 0xe4, 0x2c, 0x30, + 0xbe, 0xb4, 0x2f, 0xbd, 0x3c, 0xfc, 0xba, 0x67, 0x79, 0x72, 0x4c, 0x7e, 0x06, 0xd7, 0x0b, 0x59, + 0x20, 0x59, 0xb0, 0xf2, 0xd2, 0x64, 0x70, 0x35, 0x9f, 0x0c, 0xf4, 0x7a, 0xda, 0x12, 0xeb, 0xdf, + 0xc0, 0xb3, 0xbf, 0x23, 0x0a, 0xa5, 0x2c, 0x78, 0x94, 0x7d, 0x4b, 0xeb, 0x33, 0x03, 0x3a, 0x85, + 0xc3, 0x90, 0x3d, 0x00, 0x99, 0x5a, 0x63, 0xef, 0xb9, 0x2e, 0xda, 0xbb, 0xea, 0xe0, 0x68, 0xb2, + 0x47, 0xde, 0x73, 0x66, 0x9b, 0x23, 0x3d, 0x24, 0x1f, 0x42, 0x87, 0xa9, 0xd2, 0x4d, 0xe7, 0xbe, + 0x4a, 0x0e, 0xc5, 0x74, 0x61, 0xa7, 0x6e, 0xdb, 0x66, 0xb9, 0xb9, 0x75, 0x08, 0x66, 0xb2, 0x2e, + 0xf9, 0x16, 0x98, 0x73, 0xba, 0x54, 0x05, 0xb5, 0x2c, 0xc5, 0x1a, 0x73, 0xba, 0xc4, 0x5a, 0x9a, + 0x5c, 0x87, 0xba, 0x60, 0x4e, 0xa8, 0xdc, 0x61, 0xdd, 0xae, 0xcd, 0xe9, 0xf2, 0x27, 0x34, 0xb6, + 0x76, 0xa1, 0x9d, 0xdf, 0x44, 0x8b, 0x6a, 0x70, 0x93, 0xa2, 0x87, 0x13, 0x66, 0x3d, 0x82, 0x76, + 0xbe, 0x66, 0x15, 0x79, 0x2c, 0x0a, 0x16, 0xbe, 0x8b, 0x82, 0x1b, 0xb6, 0x9c, 0x88, 0xb6, 0xf7, + 0x2c, 0x90, 0x9f, 0x2e, 0x5b, 0xa4, 0x9e, 0x06, 0x9c, 0x65, 0x2a, 0x5d, 0x29, 0x63, 0x7d, 0xb6, + 0x01, 0x35, 0x59, 0x40, 0x93, 0x41, 0xbe, 0x3d, 0x13, 0xdf, 0x4d, 0x69, 0x4a, 0xaa, 0x52, 0x4c, + 0xb0, 0xf1, 0x8d, 0x62, 0x8f, 0x33, 0x6c, 0x5e, 0xbc, 0xd8, 0xa9, 0x23, 0xae, 0x1c, 0x3f, 0x48, + 0x1b, 0x9e, 0x55, 0xfd, 0x80, 0xee, 0xae, 0xaa, 0xaf, 0xdc, 0x5d, 0x5d, 0x87, 0xba, 0xbf, 0x98, + 0x3b, 0x7c, 0x19, 0xab, 0xf8, 0xac, 0xf9, 0x8b, 0xf9, 0xe3, 0x65, 0x2c, 0xbe, 0x01, 0x0f, 0x38, + 0x9d, 0x21, 0x4b, 0x46, 0x67, 0x03, 0x09, 0x82, 0x79, 0x00, 0xad, 0x0c, 0xfc, 0x7a, 0xae, 0x2a, + 0xe3, 0xda, 0x59, 0x0f, 0x39, 0x7e, 0xa0, 0x6e, 0xd9, 0x4c, 0xe0, 0xf8, 0xd8, 0x25, 0xb7, 0xf3, + 0xcd, 0x04, 0xa2, 0x76, 0x03, 0x9d, 0x31, 0xd3, 0x2f, 0x08, 0xcc, 0x16, 0x07, 0x10, 0xee, 0x29, + 0x45, 0x4c, 0x14, 0x69, 0x08, 0x02, 0x32, 0xdf, 0x84, 0x4e, 0x0a, 0x7c, 0x52, 0x04, 0xe4, 0x2a, + 0x29, 0x19, 0x05, 0xdf, 0x81, 0x2d, 0x9f, 0x2d, 0xb9, 0x53, 0x94, 0x6e, 0xa2, 0x34, 0x11, 0xbc, + 0xd3, 0xbc, 0xc6, 0x77, 0xa1, 0x9d, 0x06, 0x30, 0xca, 0x6e, 0xca, 0x96, 0x2e, 0xa1, 0xa2, 0xd8, + 0x0d, 0x68, 0x24, 0x65, 0x47, 0x0b, 0x05, 0xea, 0x54, 0x56, 0x1b, 0x49, 0x21, 0x13, 0xb1, 0x78, + 0x31, 0xe3, 0x6a, 0x91, 0x36, 0xca, 0x60, 0x21, 0x63, 0x4b, 0x3a, 0xca, 0xde, 0x82, 0x56, 0x12, + 0x37, 0x28, 0xd7, 0x41, 0xb9, 0x4d, 0x4d, 0x44, 0xa1, 0x5d, 0xe8, 0x86, 0x51, 0x10, 0x06, 0x31, + 0x8b, 0x1c, 0xea, 0xba, 0x11, 0x8b, 0xe3, 0x5e, 0x57, 0xae, 0xa7, 0xe9, 0x87, 0x92, 0x6c, 0x7d, + 0x1f, 0xea, 0xba, 0x9e, 0xda, 0x82, 0x0d, 0xb4, 0x3a, 0xba, 0x60, 0xd5, 0x96, 0x13, 0x91, 0xb9, + 0x0f, 0xc3, 0x50, 0xbd, 0x0a, 0x88, 0xa1, 0xf5, 0x2b, 0xa8, 0xab, 0x0f, 0x56, 0xda, 0x2b, 0xfe, + 0x08, 0x36, 0x43, 0x1a, 0x89, 0x6b, 0x64, 0x3b, 0x46, 0x5d, 0xb1, 0x9f, 0xd0, 0x88, 0x3f, 0x62, + 0x3c, 0xd7, 0x38, 0x36, 0x51, 0x5e, 0x92, 0xac, 0xbb, 0xd0, 0xca, 0xc9, 0x88, 0x63, 0xa1, 0x1f, + 0xe9, 0x48, 0xc3, 0x49, 0xb2, 0x73, 0x25, 0xdd, 0xd9, 0xba, 0x07, 0x66, 0xf2, 0x6d, 0x44, 0x61, + 0xa9, 0xaf, 0x6e, 0x28, 0x73, 0xcb, 0x29, 0x36, 0xc3, 0xc1, 0xa7, 0x2c, 0x52, 0x31, 0x21, 0x27, + 0xd6, 0x13, 0xe8, 0x14, 0x52, 0x36, 0xb9, 0x03, 0xf5, 0x70, 0x31, 0x72, 0xf4, 0x23, 0x46, 0xda, + 0xf6, 0x9e, 0x2c, 0x46, 0x1f, 0xb1, 0x73, 0xdd, 0xf6, 0x86, 0x38, 0x4b, 0x97, 0xad, 0x64, 0x97, + 0x9d, 0x41, 0x43, 0x47, 0x3f, 0xf9, 0x01, 0x98, 0x89, 0x5b, 0x15, 0x72, 0x64, 0xb2, 0xb5, 0x5a, + 0x34, 0x15, 0x14, 0xde, 0x11, 0x7b, 0x13, 0x9f, 0xb9, 0x4e, 0x1a, 0x42, 0xb8, 0x47, 0xc3, 0xee, + 0x48, 0xc6, 0xc7, 0x3a, 0x5e, 0xac, 0x77, 0xa0, 0x26, 0xcf, 0x26, 0xec, 0x23, 0x56, 0xd6, 0xb5, + 0xb6, 0x18, 0x97, 0x26, 0xf3, 0x3f, 0x18, 0xd0, 0xd0, 0x59, 0xb0, 0x54, 0x29, 0x77, 0xe8, 0xca, + 0xd7, 0x3d, 0xf4, 0xff, 0x3e, 0xf1, 0xdc, 0x01, 0x22, 0xf3, 0xcb, 0x59, 0xc0, 0x3d, 0x7f, 0xe2, + 0x48, 0x5b, 0xcb, 0x1c, 0xd4, 0x45, 0xce, 0x29, 0x32, 0x4e, 0x04, 0x7d, 0xff, 0xf3, 0x0d, 0xe8, + 0x1c, 0x0e, 0xef, 0x1f, 0x1f, 0x86, 0xe1, 0xcc, 0x1b, 0x53, 0xac, 0xdf, 0xf7, 0xa0, 0x8a, 0x2d, + 0x4c, 0xc9, 0x13, 0x6c, 0xbf, 0xac, 0x97, 0x26, 0xfb, 0xb0, 0x81, 0x9d, 0x0c, 0x29, 0x7b, 0x89, + 0xed, 0x97, 0xb6, 0xd4, 0x62, 0x13, 0xd9, 0xeb, 0x5c, 0x7e, 0x90, 0xed, 0x97, 0xf5, 0xd5, 0xe4, + 0x43, 0x30, 0xd3, 0x16, 0x63, 0xd5, 0xb3, 0x6c, 0x7f, 0x65, 0x87, 0x2d, 0xf4, 0xd3, 0x72, 0x6c, + 0xd5, 0xeb, 0x62, 0x7f, 0x65, 0x2b, 0x4a, 0x0e, 0xa0, 0xae, 0x8b, 0xd8, 0xf2, 0x87, 0xd3, 0xfe, + 0x8a, 0xee, 0x57, 0x98, 0x47, 0x76, 0x0d, 0x65, 0xaf, 0xbb, 0xfd, 0xd2, 0x16, 0x9d, 0xbc, 0x07, + 0x35, 0x55, 0x59, 0x94, 0x3e, 0x9e, 0xf6, 0xcb, 0x7b, 0x58, 0x71, 0xc9, 0xb4, 0x6f, 0x5a, 0xf5, + 0x02, 0xdd, 0x5f, 0xf9, 0x96, 0x40, 0x0e, 0x01, 0x32, 0xc5, 0xff, 0xca, 0xa7, 0xe5, 0xfe, 0xea, + 0x37, 0x02, 0x72, 0x0f, 0x1a, 0xe9, 0xbb, 0x4f, 0xf9, 0x63, 0x71, 0x7f, 0x55, 0xdb, 0x3e, 0x7c, + 0xfd, 0x5f, 0x7f, 0xde, 0x36, 0x7e, 0x73, 0xb1, 0x6d, 0x7c, 0x79, 0xb1, 0x6d, 0x7c, 0x75, 0xb1, + 0x6d, 0xfc, 0xfe, 0x62, 0xdb, 0xf8, 0xd3, 0xc5, 0xb6, 0xf1, 0xdb, 0xbf, 0x6c, 0x1b, 0xa3, 0x1a, + 0xba, 0xff, 0xbb, 0xff, 0x0e, 0x00, 0x00, 0xff, 0xff, 0x1a, 0x7c, 0xbd, 0x95, 0x1c, 0x19, 0x00, + 0x00, } diff --git a/abci/types/types.proto b/abci/types/types.proto index 517369b1..ffa32183 100644 --- a/abci/types/types.proto +++ b/abci/types/types.proto @@ -49,6 +49,8 @@ message RequestFlush { message RequestInfo { string version = 1; + uint64 block_version = 2; + uint64 p2p_version = 3; } // nondeterministic @@ -129,9 +131,12 @@ message ResponseFlush { message ResponseInfo { string data = 1; + string version = 2; - int64 last_block_height = 3; - bytes last_block_app_hash = 4; + uint64 app_version = 3; + + int64 last_block_height = 4; + bytes last_block_app_hash = 5; } // nondeterministic diff --git a/consensus/replay.go b/consensus/replay.go index af6369c3..bffab8d2 100644 --- a/consensus/replay.go +++ b/consensus/replay.go @@ -11,6 +11,7 @@ import ( "time" abci "github.com/tendermint/tendermint/abci/types" + "github.com/tendermint/tendermint/version" //auto "github.com/tendermint/tendermint/libs/autofile" cmn "github.com/tendermint/tendermint/libs/common" dbm "github.com/tendermint/tendermint/libs/db" @@ -19,7 +20,6 @@ import ( "github.com/tendermint/tendermint/proxy" sm "github.com/tendermint/tendermint/state" "github.com/tendermint/tendermint/types" - "github.com/tendermint/tendermint/version" ) var crc32c = crc32.MakeTable(crc32.Castagnoli) @@ -227,7 +227,7 @@ func (h *Handshaker) NBlocks() int { func (h *Handshaker) Handshake(proxyApp proxy.AppConns) error { // Handshake is done via ABCI Info on the query conn. - res, err := proxyApp.Query().InfoSync(abci.RequestInfo{Version: version.Version}) + res, err := proxyApp.Query().InfoSync(proxy.RequestInfo) if err != nil { return fmt.Errorf("Error calling Info: %v", err) } @@ -238,9 +238,15 @@ func (h *Handshaker) Handshake(proxyApp proxy.AppConns) error { } appHash := res.LastBlockAppHash - h.logger.Info("ABCI Handshake", "appHeight", blockHeight, "appHash", fmt.Sprintf("%X", appHash)) + h.logger.Info("ABCI Handshake App Info", + "height", blockHeight, + "hash", fmt.Sprintf("%X", appHash), + "software-version", res.Version, + "protocol-version", res.AppVersion, + ) - // TODO: check app version. + // Set AppVersion on the state. + h.initialState.Version.Consensus.App = version.Protocol(res.AppVersion) // Replay blocks up to the latest in the blockstore. _, err = h.ReplayBlocks(h.initialState, appHash, blockHeight, proxyApp) diff --git a/consensus/replay_test.go b/consensus/replay_test.go index 160e777c..4e1fa2b7 100644 --- a/consensus/replay_test.go +++ b/consensus/replay_test.go @@ -20,6 +20,7 @@ import ( crypto "github.com/tendermint/tendermint/crypto" auto "github.com/tendermint/tendermint/libs/autofile" dbm "github.com/tendermint/tendermint/libs/db" + "github.com/tendermint/tendermint/version" cfg "github.com/tendermint/tendermint/config" "github.com/tendermint/tendermint/libs/log" @@ -337,7 +338,7 @@ func testHandshakeReplay(t *testing.T, nBlocks int, mode uint) { t.Fatalf(err.Error()) } - stateDB, state, store := stateAndStore(config, privVal.GetPubKey()) + stateDB, state, store := stateAndStore(config, privVal.GetPubKey(), kvstore.ProtocolVersion) store.chain = chain store.commits = commits @@ -352,7 +353,7 @@ func testHandshakeReplay(t *testing.T, nBlocks int, mode uint) { // run nBlocks against a new client to build up the app state. // use a throwaway tendermint state proxyApp := proxy.NewAppConns(clientCreator2) - stateDB, state, _ := stateAndStore(config, privVal.GetPubKey()) + stateDB, state, _ := stateAndStore(config, privVal.GetPubKey(), kvstore.ProtocolVersion) buildAppStateFromChain(proxyApp, stateDB, state, chain, nBlocks, mode) } @@ -442,7 +443,7 @@ func buildAppStateFromChain(proxyApp proxy.AppConns, stateDB dbm.DB, func buildTMStateFromChain(config *cfg.Config, stateDB dbm.DB, state sm.State, chain []*types.Block, mode uint) sm.State { // run the whole chain against this client to build up the tendermint state clientCreator := proxy.NewLocalClientCreator(kvstore.NewPersistentKVStoreApplication(path.Join(config.DBDir(), "1"))) - proxyApp := proxy.NewAppConns(clientCreator) // sm.NewHandshaker(config, state, store, ReplayLastBlock)) + proxyApp := proxy.NewAppConns(clientCreator) if err := proxyApp.Start(); err != nil { panic(err) } @@ -588,9 +589,10 @@ func readPieceFromWAL(msg *TimedWALMessage) interface{} { } // fresh state and mock store -func stateAndStore(config *cfg.Config, pubKey crypto.PubKey) (dbm.DB, sm.State, *mockBlockStore) { +func stateAndStore(config *cfg.Config, pubKey crypto.PubKey, appVersion version.Protocol) (dbm.DB, sm.State, *mockBlockStore) { stateDB := dbm.NewMemDB() state, _ := sm.MakeGenesisStateFromFile(config.GenesisFile()) + state.Version.Consensus.App = appVersion store := NewMockBlockStore(config, state.ConsensusParams) return stateDB, state, store } @@ -639,7 +641,7 @@ func TestInitChainUpdateValidators(t *testing.T) { config := ResetConfig("proxy_test_") privVal := privval.LoadFilePV(config.PrivValidatorFile()) - stateDB, state, store := stateAndStore(config, privVal.GetPubKey()) + stateDB, state, store := stateAndStore(config, privVal.GetPubKey(), 0x0) oldValAddr := state.Validators.Validators[0].Address diff --git a/consensus/wal_generator.go b/consensus/wal_generator.go index 980a4489..5ff597a5 100644 --- a/consensus/wal_generator.go +++ b/consensus/wal_generator.go @@ -38,7 +38,8 @@ func WALGenerateNBlocks(wr io.Writer, numBlocks int) (err error) { ///////////////////////////////////////////////////////////////////////////// // COPY PASTE FROM node.go WITH A FEW MODIFICATIONS - // NOTE: we can't import node package because of circular dependency + // NOTE: we can't import node package because of circular dependency. + // NOTE: we don't do handshake so need to set state.Version.Consensus.App directly. privValidatorFile := config.PrivValidatorFile() privValidator := privval.LoadOrGenFilePV(privValidatorFile) genDoc, err := types.GenesisDocFromFile(config.GenesisFile()) @@ -51,6 +52,7 @@ func WALGenerateNBlocks(wr io.Writer, numBlocks int) (err error) { if err != nil { return errors.Wrap(err, "failed to make genesis state") } + state.Version.Consensus.App = kvstore.ProtocolVersion blockStore := bc.NewBlockStore(blockStoreDB) proxyApp := proxy.NewAppConns(proxy.NewLocalClientCreator(app)) proxyApp.SetLogger(logger.With("module", "proxy")) diff --git a/docs/spec/abci/abci.md b/docs/spec/abci/abci.md index 54b7c899..afd72617 100644 --- a/docs/spec/abci/abci.md +++ b/docs/spec/abci/abci.md @@ -134,10 +134,13 @@ Commit are included in the header of the next block. ### Info - **Request**: - - `Version (string)`: The Tendermint version + - `Version (string)`: The Tendermint software semantic version + - `BlockVersion (uint64)`: The Tendermint Block Protocol version + - `P2PVersion (uint64)`: The Tendermint P2P Protocol version - **Response**: - `Data (string)`: Some arbitrary information - - `Version (Version)`: Version information + - `Version (string)`: The application software semantic version + - `AppVersion (uint64)`: The application protocol version - `LastBlockHeight (int64)`: Latest block for which the app has called Commit - `LastBlockAppHash ([]byte)`: Latest result of Commit @@ -145,6 +148,7 @@ Commit are included in the header of the next block. - Return information about the application state. - Used to sync Tendermint with the application during a handshake that happens on startup. + - The returned `AppVersion` will be included in the Header of every block. - Tendermint expects `LastBlockAppHash` and `LastBlockHeight` to be updated during `Commit`, ensuring that `Commit` is never called twice for the same block height. diff --git a/node/node.go b/node/node.go index 97de2473..522f18e9 100644 --- a/node/node.go +++ b/node/node.go @@ -195,8 +195,8 @@ func NewNode(config *cfg.Config, return nil, fmt.Errorf("Error starting proxy app connections: %v", err) } - // Create the handshaker, which calls RequestInfo and replays any blocks - // as necessary to sync tendermint with the app. + // Create the handshaker, which calls RequestInfo, sets the AppVersion on the state, + // and replays any blocks as necessary to sync tendermint with the app. consensusLogger := logger.With("module", "consensus") handshaker := cs.NewHandshaker(stateDB, state, blockStore, genDoc) handshaker.SetLogger(consensusLogger) @@ -204,9 +204,12 @@ func NewNode(config *cfg.Config, return nil, fmt.Errorf("Error during handshake: %v", err) } - // reload the state (it may have been updated by the handshake) + // Reload the state. It will have the Version.Consensus.App set by the + // Handshake, and may have other modifications as well (ie. depending on + // what happened during block replay). state = sm.LoadState(stateDB) + // Ensure the state's block version matches that of the software. if state.Version.Consensus.Block != version.BlockProtocol { return nil, fmt.Errorf( "Block version of the software does not match that of the state.\n"+ @@ -359,7 +362,13 @@ func NewNode(config *cfg.Config, var ( p2pLogger = logger.With("module", "p2p") - nodeInfo = makeNodeInfo(config, nodeKey.ID(), txIndexer, genDoc.ChainID) + nodeInfo = makeNodeInfo( + config, + nodeKey.ID(), + txIndexer, + genDoc.ChainID, + p2p.ProtocolVersionWithApp(state.Version.Consensus.App), + ) ) // Setup Transport. @@ -764,13 +773,14 @@ func makeNodeInfo( nodeID p2p.ID, txIndexer txindex.TxIndexer, chainID string, + protocolVersion p2p.ProtocolVersion, ) p2p.NodeInfo { txIndexerStatus := "on" if _, ok := txIndexer.(*null.TxIndex); ok { txIndexerStatus = "off" } nodeInfo := p2p.DefaultNodeInfo{ - ProtocolVersion: p2p.InitProtocolVersion, + ProtocolVersion: protocolVersion, ID_: nodeID, Network: chainID, Version: version.TMCoreSemVer, diff --git a/node/node_test.go b/node/node_test.go index f4c1f6a1..3a33e6bb 100644 --- a/node/node_test.go +++ b/node/node_test.go @@ -10,7 +10,11 @@ import ( "github.com/stretchr/testify/assert" + "github.com/tendermint/tendermint/abci/example/kvstore" "github.com/tendermint/tendermint/libs/log" + "github.com/tendermint/tendermint/p2p" + sm "github.com/tendermint/tendermint/state" + "github.com/tendermint/tendermint/version" cfg "github.com/tendermint/tendermint/config" "github.com/tendermint/tendermint/types" @@ -91,3 +95,21 @@ func TestNodeDelayedStop(t *testing.T) { startTime := tmtime.Now() assert.Equal(t, true, startTime.After(n.GenesisDoc().GenesisTime)) } + +func TestNodeSetAppVersion(t *testing.T) { + config := cfg.ResetTestRoot("node_app_version_test") + + // create & start node + n, err := DefaultNewNode(config, log.TestingLogger()) + assert.NoError(t, err, "expected no err on DefaultNewNode") + + // default config uses the kvstore app + var appVersion version.Protocol = kvstore.ProtocolVersion + + // check version is set in state + state := sm.LoadState(n.stateDB) + assert.Equal(t, state.Version.Consensus.App, appVersion) + + // check version is set in node info + assert.Equal(t, n.nodeInfo.(p2p.DefaultNodeInfo).ProtocolVersion.App, appVersion) +} diff --git a/p2p/node_info.go b/p2p/node_info.go index 5874dc85..1d408eb6 100644 --- a/p2p/node_info.go +++ b/p2p/node_info.go @@ -49,10 +49,17 @@ type ProtocolVersion struct { App version.Protocol `json:"app"` } -var InitProtocolVersion = ProtocolVersion{ - P2P: version.P2PProtocol, - Block: version.BlockProtocol, - App: 0, +// InitProtocolVersion populates the Block and P2P versions, but not the App. +var InitProtocolVersion = ProtocolVersionWithApp(0) + +// ProtocolVersionWithApp returns a fully populated ProtocolVersion +// using the provided App version and the Block and P2P versions defined in the `version` package. +func ProtocolVersionWithApp(appVersion version.Protocol) ProtocolVersion { + return ProtocolVersion{ + P2P: version.P2PProtocol, + Block: version.BlockProtocol, + App: appVersion, + } } //------------------------------------------------------------- @@ -148,7 +155,7 @@ func (info DefaultNodeInfo) ValidateBasic() error { switch txIndex { case "", "on", "off": default: - return fmt.Errorf("info.Other.TxIndex should be either 'on' or 'off', got '%v'", txIndex) + return fmt.Errorf("info.Other.TxIndex should be either 'on', 'off', or empty string, got '%v'", txIndex) } // XXX: Should we be more strict about address formats? rpcAddr := other.RPCAddress diff --git a/proxy/app_conn_test.go b/proxy/app_conn_test.go index 5eadb032..ca98f1be 100644 --- a/proxy/app_conn_test.go +++ b/proxy/app_conn_test.go @@ -143,7 +143,7 @@ func TestInfo(t *testing.T) { proxy := NewAppConnTest(cli) t.Log("Connected") - resInfo, err := proxy.InfoSync(types.RequestInfo{Version: ""}) + resInfo, err := proxy.InfoSync(RequestInfo) if err != nil { t.Errorf("Unexpected error: %v", err) } diff --git a/proxy/version.go b/proxy/version.go new file mode 100644 index 00000000..fb506e65 --- /dev/null +++ b/proxy/version.go @@ -0,0 +1,15 @@ +package proxy + +import ( + abci "github.com/tendermint/tendermint/abci/types" + "github.com/tendermint/tendermint/version" +) + +// RequestInfo contains all the information for sending +// the abci.RequestInfo message during handshake with the app. +// It contains only compile-time version information. +var RequestInfo = abci.RequestInfo{ + Version: version.Version, + BlockVersion: version.BlockProtocol.Uint64(), + P2PVersion: version.P2PProtocol.Uint64(), +} diff --git a/rpc/client/mock/abci.go b/rpc/client/mock/abci.go index 3a0ed79c..e63d22e0 100644 --- a/rpc/client/mock/abci.go +++ b/rpc/client/mock/abci.go @@ -3,10 +3,10 @@ package mock import ( abci "github.com/tendermint/tendermint/abci/types" cmn "github.com/tendermint/tendermint/libs/common" + "github.com/tendermint/tendermint/proxy" "github.com/tendermint/tendermint/rpc/client" ctypes "github.com/tendermint/tendermint/rpc/core/types" "github.com/tendermint/tendermint/types" - "github.com/tendermint/tendermint/version" ) // ABCIApp will send all abci related request to the named app, @@ -23,7 +23,7 @@ var ( ) func (a ABCIApp) ABCIInfo() (*ctypes.ResultABCIInfo, error) { - return &ctypes.ResultABCIInfo{a.App.Info(abci.RequestInfo{Version: version.Version})}, nil + return &ctypes.ResultABCIInfo{a.App.Info(proxy.RequestInfo)}, nil } func (a ABCIApp) ABCIQuery(path string, data cmn.HexBytes) (*ctypes.ResultABCIQuery, error) { diff --git a/rpc/core/abci.go b/rpc/core/abci.go index 47219563..2468a5f0 100644 --- a/rpc/core/abci.go +++ b/rpc/core/abci.go @@ -3,8 +3,8 @@ package core import ( abci "github.com/tendermint/tendermint/abci/types" cmn "github.com/tendermint/tendermint/libs/common" + "github.com/tendermint/tendermint/proxy" ctypes "github.com/tendermint/tendermint/rpc/core/types" - "github.com/tendermint/tendermint/version" ) // Query the application for some information. @@ -87,7 +87,7 @@ func ABCIQuery(path string, data cmn.HexBytes, height int64, prove bool) (*ctype // } // ``` func ABCIInfo() (*ctypes.ResultABCIInfo, error) { - resInfo, err := proxyAppQuery.InfoSync(abci.RequestInfo{Version: version.Version}) + resInfo, err := proxyAppQuery.InfoSync(proxy.RequestInfo) if err != nil { return nil, err } diff --git a/state/state.go b/state/state.go index aedb2b00..5c1b68a2 100644 --- a/state/state.go +++ b/state/state.go @@ -27,6 +27,10 @@ type Version struct { Software string } +// initStateVersion sets the Consensus.Block and Software versions, +// but leaves the Consensus.App version blank. +// The Consensus.App version will be set during the Handshake, once +// we hear from the app what protocol version it is running. var initStateVersion = Version{ Consensus: version.Consensus{ Block: version.BlockProtocol, diff --git a/version/version.go b/version/version.go index 5a089141..19b3f3da 100644 --- a/version/version.go +++ b/version/version.go @@ -28,6 +28,12 @@ const ( // Protocol is used for implementation agnostic versioning. type Protocol uint64 +// Uint64 returns the Protocol version as a uint64, +// eg. for compatibility with ABCI types. +func (p Protocol) Uint64() uint64 { + return uint64(p) +} + var ( // P2PProtocol versions all p2p behaviour and msgs. P2PProtocol Protocol = 4 From c3384e88e5fee31ecf2d3c62b20204eae965bf02 Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Thu, 18 Oct 2018 17:32:53 -0400 Subject: [PATCH 024/267] adr-016: update int64->uint64; add version to ConsensusParams (#2667) --- .../architecture/adr-016-protocol-versions.md | 51 +++++++++++-------- 1 file changed, 29 insertions(+), 22 deletions(-) diff --git a/docs/architecture/adr-016-protocol-versions.md b/docs/architecture/adr-016-protocol-versions.md index 1ae1f467..3a2351a5 100644 --- a/docs/architecture/adr-016-protocol-versions.md +++ b/docs/architecture/adr-016-protocol-versions.md @@ -96,7 +96,7 @@ Each component of the software is independently versioned in a modular way and i ## Proposal -Each of BlockVersion, AppVersion, P2PVersion, is a monotonically increasing int64. +Each of BlockVersion, AppVersion, P2PVersion, is a monotonically increasing uint64. To use these versions, we need to update the block Header, the p2p NodeInfo, and the ABCI. @@ -106,8 +106,8 @@ Block Header should include a `Version` struct as its first field like: ``` type Version struct { - Block int64 - App int64 + Block uint64 + App uint64 } ``` @@ -130,9 +130,9 @@ NodeInfo should include a Version struct as its first field like: ``` type Version struct { - P2P int64 - Block int64 - App int64 + P2P uint64 + Block uint64 + App uint64 Other []string } @@ -168,9 +168,9 @@ RequestInfo should add support for protocol versions like: ``` message RequestInfo { - string software_version - int64 block_version - int64 p2p_version + string version + uint64 block_version + uint64 p2p_version } ``` @@ -180,39 +180,46 @@ Similarly, ResponseInfo should return the versions: message ResponseInfo { string data - string software_version - int64 app_version + string version + uint64 app_version int64 last_block_height bytes last_block_app_hash } ``` +The existing `version` fields should be called `software_version` but we leave +them for now to reduce the number of breaking changes. + #### EndBlock Updating the version could be done either with new fields or by using the existing `tags`. Since we're trying to communicate information that will be included in Tendermint block Headers, it should be native to the ABCI, and not -something embedded through some scheme in the tags. +something embedded through some scheme in the tags. Thus, version updates should +be communicated through EndBlock. -ResponseEndBlock will include a new field `version_updates`: +EndBlock already contains `ConsensusParams`. We can add version information to +the ConsensusParams as well: ``` -message ResponseEndBlock { - repeated Validator validator_updates - ConsensusParams consensus_param_updates - repeated common.KVPair tags +message ConsensusParams { - VersionUpdate version_update + BlockSize block_size + EvidenceParams evidence_params + VersionParams version } -message VersionUpdate { - int64 app_version +message VersionParams { + uint64 block_version + uint64 app_version } ``` -Tendermint will use the information in VersionUpdate for the next block it -proposes. +For now, the `block_version` will be ignored, as we do not allow block version +to be updated live. If the `app_version` is set, it signals that the app's +protocol version has changed, and the new `app_version` will be included in the +`Block.Header.Version.App` for the next block. ### BlockVersion From e798766a27a0825f5e5deb460d755d2bf8813f96 Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Thu, 18 Oct 2018 18:02:20 -0400 Subject: [PATCH 025/267] types: remove Version from CanonicalXxx (#2666) --- docs/spec/blockchain/encoding.md | 1 - types/canonical.go | 10 ++------ types/vote_test.go | 42 +++++++++++++++----------------- 3 files changed, 21 insertions(+), 32 deletions(-) diff --git a/docs/spec/blockchain/encoding.md b/docs/spec/blockchain/encoding.md index ed92739d..5657784d 100644 --- a/docs/spec/blockchain/encoding.md +++ b/docs/spec/blockchain/encoding.md @@ -307,7 +307,6 @@ We call this encoding the SignBytes. For instance, SignBytes for a vote is the A ```go type CanonicalVote struct { - Version uint64 `binary:"fixed64"` Height int64 `binary:"fixed64"` Round int64 `binary:"fixed64"` VoteType byte diff --git a/types/canonical.go b/types/canonical.go index 8a33debd..c40f35dd 100644 --- a/types/canonical.go +++ b/types/canonical.go @@ -23,7 +23,6 @@ type CanonicalPartSetHeader struct { } type CanonicalProposal struct { - Version uint64 `binary:"fixed64"` Height int64 `binary:"fixed64"` Round int64 `binary:"fixed64"` Type SignedMsgType // type alias for byte @@ -35,7 +34,6 @@ type CanonicalProposal struct { } type CanonicalVote struct { - Version uint64 `binary:"fixed64"` Height int64 `binary:"fixed64"` Round int64 `binary:"fixed64"` Type SignedMsgType // type alias for byte @@ -45,9 +43,8 @@ type CanonicalVote struct { } type CanonicalHeartbeat struct { - Version uint64 `binary:"fixed64"` - Height int64 `binary:"fixed64"` - Round int `binary:"fixed64"` + Height int64 `binary:"fixed64"` + Round int `binary:"fixed64"` Type byte Sequence int `binary:"fixed64"` ValidatorAddress Address @@ -74,7 +71,6 @@ func CanonicalizePartSetHeader(psh PartSetHeader) CanonicalPartSetHeader { func CanonicalizeProposal(chainID string, proposal *Proposal) CanonicalProposal { return CanonicalProposal{ - Version: 0, // TODO Height: proposal.Height, Round: int64(proposal.Round), // cast int->int64 to make amino encode it fixed64 (does not work for int) Type: ProposalType, @@ -88,7 +84,6 @@ func CanonicalizeProposal(chainID string, proposal *Proposal) CanonicalProposal func CanonicalizeVote(chainID string, vote *Vote) CanonicalVote { return CanonicalVote{ - Version: 0, // TODO Height: vote.Height, Round: int64(vote.Round), // cast int->int64 to make amino encode it fixed64 (does not work for int) Type: vote.Type, @@ -100,7 +95,6 @@ func CanonicalizeVote(chainID string, vote *Vote) CanonicalVote { func CanonicalizeHeartbeat(chainID string, heartbeat *Heartbeat) CanonicalHeartbeat { return CanonicalHeartbeat{ - Version: 0, // TODO Height: heartbeat.Height, Round: heartbeat.Round, Type: byte(HeartbeatType), diff --git a/types/vote_test.go b/types/vote_test.go index 282953f4..066df496 100644 --- a/types/vote_test.go +++ b/types/vote_test.go @@ -54,8 +54,7 @@ func TestVoteSignable(t *testing.T) { } func TestVoteSignableTestVectors(t *testing.T) { - voteWithVersion := CanonicalizeVote("", &Vote{Height: 1, Round: 1}) - voteWithVersion.Version = 123 + vote := CanonicalizeVote("", &Vote{Height: 1, Round: 1}) tests := []struct { canonicalVote CanonicalVote @@ -64,20 +63,20 @@ func TestVoteSignableTestVectors(t *testing.T) { { CanonicalizeVote("", &Vote{}), // NOTE: Height and Round are skipped here. This case needs to be considered while parsing. - []byte{0xb, 0x2a, 0x9, 0x9, 0x0, 0x9, 0x6e, 0x88, 0xf1, 0xff, 0xff, 0xff}, + []byte{0xb, 0x22, 0x9, 0x9, 0x0, 0x9, 0x6e, 0x88, 0xf1, 0xff, 0xff, 0xff}, }, // with proper (fixed size) height and round (PreCommit): { CanonicalizeVote("", &Vote{Height: 1, Round: 1, Type: PrecommitType}), []byte{ 0x1f, // total length - 0x11, // (field_number << 3) | wire_type (version is missing) + 0x9, // (field_number << 3) | wire_type 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, // height - 0x19, // (field_number << 3) | wire_type + 0x11, // (field_number << 3) | wire_type 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, // round - 0x20, // (field_number << 3) | wire_type + 0x18, // (field_number << 3) | wire_type 0x2, // PrecommitType - 0x2a, // (field_number << 3) | wire_type + 0x22, // (field_number << 3) | wire_type // remaining fields (timestamp): 0x9, 0x9, 0x0, 0x9, 0x6e, 0x88, 0xf1, 0xff, 0xff, 0xff}, }, @@ -86,29 +85,26 @@ func TestVoteSignableTestVectors(t *testing.T) { CanonicalizeVote("", &Vote{Height: 1, Round: 1, Type: PrevoteType}), []byte{ 0x1f, // total length - 0x11, // (field_number << 3) | wire_type (version is missing) + 0x9, // (field_number << 3) | wire_type 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, // height - 0x19, // (field_number << 3) | wire_type + 0x11, // (field_number << 3) | wire_type 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, // round - 0x20, // (field_number << 3) | wire_type + 0x18, // (field_number << 3) | wire_type 0x1, // PrevoteType - 0x2a, // (field_number << 3) | wire_type + 0x22, // (field_number << 3) | wire_type // remaining fields (timestamp): 0x9, 0x9, 0x0, 0x9, 0x6e, 0x88, 0xf1, 0xff, 0xff, 0xff}, }, - // containing version (empty type) { - voteWithVersion, + vote, []byte{ - 0x26, // total length - 0x9, // (field_number << 3) | wire_type - 0x7b, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, // version (123) - 0x11, // (field_number << 3) | wire_type + 0x1d, // total length + 0x9, // (field_number << 3) | wire_type 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, // height - 0x19, // (field_number << 3) | wire_type + 0x11, // (field_number << 3) | wire_type 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, // round // remaining fields (timestamp): - 0x2a, + 0x22, 0x9, 0x9, 0x0, 0x9, 0x6e, 0x88, 0xf1, 0xff, 0xff, 0xff}, }, // containing non-empty chain_id: @@ -116,14 +112,14 @@ func TestVoteSignableTestVectors(t *testing.T) { CanonicalizeVote("test_chain_id", &Vote{Height: 1, Round: 1}), []byte{ 0x2c, // total length - 0x11, // (field_number << 3) | wire_type + 0x9, // (field_number << 3) | wire_type 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, // height - 0x19, // (field_number << 3) | wire_type + 0x11, // (field_number << 3) | wire_type 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, // round // remaining fields: - 0x2a, // (field_number << 3) | wire_type + 0x22, // (field_number << 3) | wire_type 0x9, 0x9, 0x0, 0x9, 0x6e, 0x88, 0xf1, 0xff, 0xff, 0xff, // timestamp - 0x3a, // (field_number << 3) | wire_type + 0x32, // (field_number << 3) | wire_type 0xd, 0x74, 0x65, 0x73, 0x74, 0x5f, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x5f, 0x69, 0x64}, // chainID }, } From 746d137f86f34ecdb5f2a1d2b94a66913c1c9efe Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Thu, 18 Oct 2018 18:26:32 -0400 Subject: [PATCH 026/267] p2p: Restore OriginalAddr (#2668) * p2p: bring back OriginalAddr * p2p: set OriginalAddr * update changelog --- CHANGELOG_PENDING.md | 2 ++ blockchain/reactor_test.go | 1 + p2p/dummy/peer.go | 5 +++++ p2p/peer.go | 28 ++++++++++++++++++++++++++++ p2p/peer_set_test.go | 1 + p2p/peer_test.go | 2 +- p2p/pex/pex_reactor_test.go | 1 + p2p/switch.go | 9 ++++++--- p2p/test_util.go | 12 +++++++----- p2p/transport.go | 21 +++++++++++++-------- 10 files changed, 65 insertions(+), 17 deletions(-) diff --git a/CHANGELOG_PENDING.md b/CHANGELOG_PENDING.md index 99c38997..758bfeb2 100644 --- a/CHANGELOG_PENDING.md +++ b/CHANGELOG_PENDING.md @@ -82,3 +82,5 @@ Proposal or timeoutProposal before entering prevote - [p2p] \#2555 fix p2p switch FlushThrottle value (@goolAdapter) - [libs/event] \#2518 fix event concurrency flaw (@goolAdapter) - [state] \#2616 Pass nil to NewValidatorSet() when genesis file's Validators field is nil +- [p2p] \#2668 Reconnect to originally dialed address (not self-reported + address) for persistent peers diff --git a/blockchain/reactor_test.go b/blockchain/reactor_test.go index 7fc7ffb7..fca063e0 100644 --- a/blockchain/reactor_test.go +++ b/blockchain/reactor_test.go @@ -206,3 +206,4 @@ func (tp *bcrTestPeer) IsPersistent() bool { return true } func (tp *bcrTestPeer) Get(s string) interface{} { return s } func (tp *bcrTestPeer) Set(string, interface{}) {} func (tp *bcrTestPeer) RemoteIP() net.IP { return []byte{127, 0, 0, 1} } +func (tp *bcrTestPeer) OriginalAddr() *p2p.NetAddress { return nil } diff --git a/p2p/dummy/peer.go b/p2p/dummy/peer.go index 4871719d..65ff65fb 100644 --- a/p2p/dummy/peer.go +++ b/p2p/dummy/peer.go @@ -78,3 +78,8 @@ func (p *peer) Get(key string) interface{} { } return nil } + +// OriginalAddr always returns nil. +func (p *peer) OriginalAddr() *p2p.NetAddress { + return nil +} diff --git a/p2p/peer.go b/p2p/peer.go index 00931314..944174b0 100644 --- a/p2p/peer.go +++ b/p2p/peer.go @@ -26,6 +26,7 @@ type Peer interface { NodeInfo() NodeInfo // peer's info Status() tmconn.ConnectionStatus + OriginalAddr() *NetAddress Send(byte, []byte) bool TrySend(byte, []byte) bool @@ -43,10 +44,28 @@ type peerConn struct { config *config.P2PConfig conn net.Conn // source connection + originalAddr *NetAddress // nil for inbound connections + // cached RemoteIP() ip net.IP } +func newPeerConn( + outbound, persistent bool, + config *config.P2PConfig, + conn net.Conn, + originalAddr *NetAddress, +) peerConn { + + return peerConn{ + outbound: outbound, + persistent: persistent, + config: config, + conn: conn, + originalAddr: originalAddr, + } +} + // ID only exists for SecretConnection. // NOTE: Will panic if conn is not *SecretConnection. func (pc peerConn) ID() ID { @@ -195,6 +214,15 @@ func (p *peer) NodeInfo() NodeInfo { return p.nodeInfo } +// OriginalAddr returns the original address, which was used to connect with +// the peer. Returns nil for inbound peers. +func (p *peer) OriginalAddr() *NetAddress { + if p.peerConn.outbound { + return p.peerConn.originalAddr + } + return nil +} + // Status returns the peer's ConnectionStatus. func (p *peer) Status() tmconn.ConnectionStatus { return p.mconn.Status() diff --git a/p2p/peer_set_test.go b/p2p/peer_set_test.go index c0ad8000..daa9b2c8 100644 --- a/p2p/peer_set_test.go +++ b/p2p/peer_set_test.go @@ -28,6 +28,7 @@ func (mp *mockPeer) IsPersistent() bool { return true } func (mp *mockPeer) Get(s string) interface{} { return s } func (mp *mockPeer) Set(string, interface{}) {} func (mp *mockPeer) RemoteIP() net.IP { return mp.ip } +func (mp *mockPeer) OriginalAddr() *NetAddress { return nil } // Returns a mock peer func newMockPeer(ip net.IP) *mockPeer { diff --git a/p2p/peer_test.go b/p2p/peer_test.go index 9c330ee5..02f1d2c0 100644 --- a/p2p/peer_test.go +++ b/p2p/peer_test.go @@ -114,7 +114,7 @@ func testOutboundPeerConn( return peerConn{}, cmn.ErrorWrap(err, "Error creating peer") } - pc, err := testPeerConn(conn, config, true, persistent, ourNodePrivKey) + pc, err := testPeerConn(conn, config, true, persistent, ourNodePrivKey, addr) if err != nil { if cerr := conn.Close(); cerr != nil { return peerConn{}, cmn.ErrorWrap(err, cerr.Error()) diff --git a/p2p/pex/pex_reactor_test.go b/p2p/pex/pex_reactor_test.go index b0338c3c..9d3f49bb 100644 --- a/p2p/pex/pex_reactor_test.go +++ b/p2p/pex/pex_reactor_test.go @@ -402,6 +402,7 @@ func (mockPeer) Send(byte, []byte) bool { return false } func (mockPeer) TrySend(byte, []byte) bool { return false } func (mockPeer) Set(string, interface{}) {} func (mockPeer) Get(string) interface{} { return nil } +func (mockPeer) OriginalAddr() *p2p.NetAddress { return nil } func assertPeersWithTimeout( t *testing.T, diff --git a/p2p/switch.go b/p2p/switch.go index 64e248fc..b1406b9b 100644 --- a/p2p/switch.go +++ b/p2p/switch.go @@ -280,9 +280,12 @@ func (sw *Switch) StopPeerForError(peer Peer, reason interface{}) { sw.stopAndRemovePeer(peer, reason) if peer.IsPersistent() { - // TODO: use the original address dialed, not the self reported one - // See #2618. - addr := peer.NodeInfo().NetAddress() + addr := peer.OriginalAddr() + if addr == nil { + // FIXME: persistent peers can't be inbound right now. + // self-reported address for inbound persistent peers + addr = peer.NodeInfo().NetAddress() + } go sw.reconnectToPeer(addr) } } diff --git a/p2p/test_util.go b/p2p/test_util.go index 4d43175b..e1f7b504 100644 --- a/p2p/test_util.go +++ b/p2p/test_util.go @@ -206,7 +206,7 @@ func testInboundPeerConn( config *config.P2PConfig, ourNodePrivKey crypto.PrivKey, ) (peerConn, error) { - return testPeerConn(conn, config, false, false, ourNodePrivKey) + return testPeerConn(conn, config, false, false, ourNodePrivKey, nil) } func testPeerConn( @@ -214,6 +214,7 @@ func testPeerConn( cfg *config.P2PConfig, outbound, persistent bool, ourNodePrivKey crypto.PrivKey, + originalAddr *NetAddress, ) (pc peerConn, err error) { conn := rawConn @@ -231,10 +232,11 @@ func testPeerConn( // Only the information we already have return peerConn{ - config: cfg, - outbound: outbound, - persistent: persistent, - conn: conn, + config: cfg, + outbound: outbound, + persistent: persistent, + conn: conn, + originalAddr: originalAddr, }, nil } diff --git a/p2p/transport.go b/p2p/transport.go index b20f32f3..10565d8a 100644 --- a/p2p/transport.go +++ b/p2p/transport.go @@ -171,7 +171,7 @@ func (mt *MultiplexTransport) Accept(cfg peerConfig) (Peer, error) { cfg.outbound = false - return mt.wrapPeer(a.conn, a.nodeInfo, cfg), nil + return mt.wrapPeer(a.conn, a.nodeInfo, cfg, nil), nil case <-mt.closec: return nil, &ErrTransportClosed{} } @@ -199,7 +199,7 @@ func (mt *MultiplexTransport) Dial( cfg.outbound = true - p := mt.wrapPeer(secretConn, nodeInfo, cfg) + p := mt.wrapPeer(secretConn, nodeInfo, cfg, &addr) return p, nil } @@ -399,14 +399,19 @@ func (mt *MultiplexTransport) wrapPeer( c net.Conn, ni NodeInfo, cfg peerConfig, + dialedAddr *NetAddress, ) Peer { + + peerConn := newPeerConn( + cfg.outbound, + cfg.persistent, + &mt.p2pConfig, + c, + dialedAddr, + ) + p := newPeer( - peerConn{ - conn: c, - config: &mt.p2pConfig, - outbound: cfg.outbound, - persistent: cfg.persistent, - }, + peerConn, mt.mConfig, ni, cfg.reactorsByCh, From f536089f0b7ff7894df2d656c35bdb0508f4e7f2 Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Fri, 19 Oct 2018 11:39:27 -0400 Subject: [PATCH 027/267] types: dont use SimpleHashFromMap for header. closes #1841 (#2670) * types: dont use SimpleHashFromMap for header. closes #1841 * changelog and spec * comments --- CHANGELOG_PENDING.md | 2 ++ docs/spec/blockchain/encoding.md | 2 +- types/block.go | 42 +++++++++++++++++--------------- 3 files changed, 26 insertions(+), 20 deletions(-) diff --git a/CHANGELOG_PENDING.md b/CHANGELOG_PENDING.md index 758bfeb2..ed33be78 100644 --- a/CHANGELOG_PENDING.md +++ b/CHANGELOG_PENDING.md @@ -45,6 +45,8 @@ BREAKING CHANGES: * [types] \#2644 Add Version struct to Header * [state] \#2587 Require block.Time of the fist block to be genesis time * [state] \#2644 Require block.Version to match state.Version + * [types] \#2670 Header.Hash() builds Merkle tree out of fields in the same + order they appear in the header, instead of sorting by field name * P2P Protocol * [p2p] \#2654 Add `ProtocolVersion` struct with protocol versions to top of diff --git a/docs/spec/blockchain/encoding.md b/docs/spec/blockchain/encoding.md index 5657784d..563b0a88 100644 --- a/docs/spec/blockchain/encoding.md +++ b/docs/spec/blockchain/encoding.md @@ -216,7 +216,7 @@ prefix) before being concatenated together and hashed. Note: we will abuse notion and invoke `SimpleMerkleRoot` with arguments of type `struct` or type `[]struct`. For `struct` arguments, we compute a `[][]byte` containing the hash of each -field in the struct sorted by the hash of the field name. +field in the struct, in the same order the fields appear in the struct. For `[]struct` arguments, we compute a `[][]byte` by hashing the individual `struct` elements. ### Simple Merkle Proof diff --git a/types/block.go b/types/block.go index 06ad55fc..2a5b5fc4 100644 --- a/types/block.go +++ b/types/block.go @@ -258,8 +258,10 @@ func MaxDataBytesUnknownEvidence(maxBytes int64, valsCount int) int64 { //----------------------------------------------------------------------------- // Header defines the structure of a Tendermint block header -// NOTE: changes to the Header should be duplicated in the abci Header -// and in /docs/spec/blockchain/blockchain.md +// NOTE: changes to the Header should be duplicated in: +// - header.Hash() +// - abci.Header +// - /docs/spec/blockchain/blockchain.md type Header struct { // basic block info Version version.Consensus `json:"version"` @@ -289,6 +291,8 @@ type Header struct { } // Hash returns the hash of the header. +// It computes a Merkle tree from the header fields +// ordered as they appear in the Header. // Returns nil if ValidatorHash is missing, // since a Header is not valid unless there is // a ValidatorsHash (corresponding to the validator set). @@ -296,23 +300,23 @@ func (h *Header) Hash() cmn.HexBytes { if h == nil || len(h.ValidatorsHash) == 0 { return nil } - return merkle.SimpleHashFromMap(map[string][]byte{ - "Version": cdcEncode(h.Version), - "ChainID": cdcEncode(h.ChainID), - "Height": cdcEncode(h.Height), - "Time": cdcEncode(h.Time), - "NumTxs": cdcEncode(h.NumTxs), - "TotalTxs": cdcEncode(h.TotalTxs), - "LastBlockID": cdcEncode(h.LastBlockID), - "LastCommitHash": cdcEncode(h.LastCommitHash), - "DataHash": cdcEncode(h.DataHash), - "ValidatorsHash": cdcEncode(h.ValidatorsHash), - "NextValidatorsHash": cdcEncode(h.NextValidatorsHash), - "AppHash": cdcEncode(h.AppHash), - "ConsensusHash": cdcEncode(h.ConsensusHash), - "LastResultsHash": cdcEncode(h.LastResultsHash), - "EvidenceHash": cdcEncode(h.EvidenceHash), - "ProposerAddress": cdcEncode(h.ProposerAddress), + return merkle.SimpleHashFromByteSlices([][]byte{ + cdcEncode(h.Version), + cdcEncode(h.ChainID), + cdcEncode(h.Height), + cdcEncode(h.Time), + cdcEncode(h.NumTxs), + cdcEncode(h.TotalTxs), + cdcEncode(h.LastBlockID), + cdcEncode(h.LastCommitHash), + cdcEncode(h.DataHash), + cdcEncode(h.ValidatorsHash), + cdcEncode(h.NextValidatorsHash), + cdcEncode(h.ConsensusHash), + cdcEncode(h.AppHash), + cdcEncode(h.LastResultsHash), + cdcEncode(h.EvidenceHash), + cdcEncode(h.ProposerAddress), }) } From 7c6519adbd62cdc4c1e3003e14bbb61e71725e02 Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Fri, 19 Oct 2018 13:49:04 -0400 Subject: [PATCH 028/267] Bucky/changelog (#2673) * update changelog, add authors script * update changelog * update changelog --- CHANGELOG_PENDING.md | 59 +++++++++++++++++++++++++++++--------------- scripts/authors.sh | 16 ++++++++++++ 2 files changed, 55 insertions(+), 20 deletions(-) create mode 100755 scripts/authors.sh diff --git a/CHANGELOG_PENDING.md b/CHANGELOG_PENDING.md index ed33be78..5490ae77 100644 --- a/CHANGELOG_PENDING.md +++ b/CHANGELOG_PENDING.md @@ -1,7 +1,23 @@ # Pending +## v0.26.0 + +*October 19, 2018* + Special thanks to external contributors on this release: -@goolAdapter, @bradyjoestar +@bradyjoestar, @connorwstein, @goolAdapter, @HaoyangLiu, +@james-ray, @overbool, @phymbert, @Slamper, @Uzair1995 + +This release is primarily about adding Version fields to various data structures, +optimizing consensus messages for signing and verification in +restricted environments (like HSMs and the Ethereum Virtual Machine), and +aligning the consensus code with the [specification](https://arxiv.org/abs/1807.04938). +It also includes our first take at a generalized merkle proof system. + +See the [UPGRADING.md](UPGRADING.md#v0.26.0) for details on upgrading to the new +version. + +Friendly reminder, we have a [bug bounty program](https://hackerone.com/tendermint). BREAKING CHANGES: @@ -9,11 +25,11 @@ BREAKING CHANGES: * [config] \#2232 timeouts as time.Duration, not ints * [config] \#2505 Remove Mempool.RecheckEmpty (it was effectively useless anyways) * [config] `mempool.wal` is disabled by default - * [rpc] \#2298 `/abci_query` takes `prove` argument instead of `trusted` and switches the default - behaviour to `prove=false` * [privval] \#2459 Split `SocketPVMsg`s implementations into Request and Response, where the Response may contain a error message (returned by the remote signer) * [state] \#2644 Add Version field to State, breaking the format of State as encoded on disk. + * [rpc] \#2298 `/abci_query` takes `prove` argument instead of `trusted` and switches the default + behaviour to `prove=false` * [rpc] \#2654 Remove all `node_info.other.*_version` fields in `/status` and `/net_info` @@ -25,13 +41,13 @@ BREAKING CHANGES: `AppVersion` * Go API - * [node] Remove node.RunForever * [config] \#2232 timeouts as time.Duration, not ints - * [rpc/client] \#2298 `ABCIQueryOptions.Trusted` -> `ABCIQueryOptions.Prove` - * [types] \#2298 Remove `Index` and `Total` fields from `TxProof`. * [crypto/merkle & lite] \#2298 Various changes to accomodate General Merkle trees * [crypto/merkle] \#2595 Remove all Hasher objects in favor of byte slices * [crypto/merkle] \#2635 merkle.SimpleHashFromTwoHashes is no longer exported + * [node] Remove node.RunForever + * [rpc/client] \#2298 `ABCIQueryOptions.Trusted` -> `ABCIQueryOptions.Prove` + * [types] \#2298 Remove `Index` and `Total` fields from `TxProof`. * [types] \#2598 `VoteTypeXxx` are now of type `SignedMsgType byte` and named `XxxType`, eg. `PrevoteType`, `PrecommitType`. @@ -43,6 +59,8 @@ BREAKING CHANGES: `SignedMsgType` to enumerate. * [types] \#2512 Remove the pubkey field from the validator hash * [types] \#2644 Add Version struct to Header + * [types] \#2609 ConsensusParams.Hash() is the hash of the amino encoded + struct instead of the Merkle tree of the fields * [state] \#2587 Require block.Time of the fist block to be genesis time * [state] \#2644 Require block.Version to match state.Version * [types] \#2670 Header.Hash() builds Merkle tree out of fields in the same @@ -52,11 +70,10 @@ BREAKING CHANGES: * [p2p] \#2654 Add `ProtocolVersion` struct with protocol versions to top of DefaultNodeInfo and require `ProtocolVersion.Block` to match during peer handshake - FEATURES: -- [crypto/merkle] \#2298 General Merkle Proof scheme for chaining various types of Merkle trees together - [abci] \#2557 Add `Codespace` field to `Response{CheckTx, DeliverTx, Query}` - [abci] \#2662 Add `BlockVersion` and `P2PVersion` to `RequestInfo` +- [crypto/merkle] \#2298 General Merkle Proof scheme for chaining various types of Merkle trees together IMPROVEMENTS: - Additional Metrics @@ -66,23 +83,25 @@ IMPROVEMENTS: - [crypto/ed25519] \#2558 Switch to use latest `golang.org/x/crypto` through our fork at github.com/tendermint/crypto - [tools] \#2238 Binary dependencies are now locked to a specific git commit -- [crypto] \#2099 make crypto random use chacha, and have forward secrecy of generated randomness BUG FIXES: - [autofile] \#2428 Group.RotateFile need call Flush() before rename (@goolAdapter) -- [node] \#2434 Make node respond to signal interrupts while sleeping for genesis time -- [consensus] [\#1690](https://github.com/tendermint/tendermint/issues/1690) wait for -timeoutPrecommit before starting next round -- [consensus] [\#1745](https://github.com/tendermint/tendermint/issues/1745) wait for -Proposal or timeoutProposal before entering prevote -- [evidence] \#2515 fix db iter leak (@goolAdapter) -- [common/bit_array] Fixed a bug in the `Or` function -- [common/bit_array] Fixed a bug in the `Sub` function (@james-ray) -- [common] \#2534 Make bit array's PickRandom choose uniformly from true bits +- [common] Fixed a bug in the `BitArray.Or` method +- [common] Fixed a bug in the `BitArray.Sub` method (@james-ray) +- [common] \#2534 Fix `BitArray.PickRandom` to choose uniformly from true bits +- [consensus] [\#1690](https://github.com/tendermint/tendermint/issues/1690) Wait for + timeoutPrecommit before starting next round +- [consensus] [\#1745](https://github.com/tendermint/tendermint/issues/1745) Wait for + Proposal or timeoutProposal before entering prevote +- [consensus] \#2642 Only propose ValidBlock, not LockedBlock +- [consensus] \#2642 Initialized ValidRound and LockedRound to -1 - [consensus] \#1637 Limit the amount of evidence that can be included in a block -- [p2p] \#2555 fix p2p switch FlushThrottle value (@goolAdapter) -- [libs/event] \#2518 fix event concurrency flaw (@goolAdapter) +- [evidence] \#2515 Fix db iter leak (@goolAdapter) +- [libs/event] \#2518 Fix event concurrency flaw (@goolAdapter) +- [node] \#2434 Make node respond to signal interrupts while sleeping for genesis time - [state] \#2616 Pass nil to NewValidatorSet() when genesis file's Validators field is nil +- [p2p] \#2555 Fix p2p switch FlushThrottle value (@goolAdapter) - [p2p] \#2668 Reconnect to originally dialed address (not self-reported address) for persistent peers + diff --git a/scripts/authors.sh b/scripts/authors.sh new file mode 100755 index 00000000..7aafb012 --- /dev/null +++ b/scripts/authors.sh @@ -0,0 +1,16 @@ +#! /bin/bash + +# Usage: +# `./authors.sh` +# Print a list of all authors who have committed to develop since master. +# +# `./authors.sh ` +# Lookup the email address on Github and print the associated username + +author=$1 + +if [[ "$author" == "" ]]; then + git log master..develop | grep Author | sort | uniq +else + curl -s "https://api.github.com/search/users?q=$author+in%3Aemail&type=Users&utf8=%E2%9C%93" | jq .items[0].login +fi From 30519e8361c19f4bf320ef4d26288ebc621ad725 Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Fri, 19 Oct 2018 14:23:14 -0400 Subject: [PATCH 029/267] types: first field in Canonical structs is Type (#2675) * types: first field in Canonical structs is Type * fix spec --- docs/spec/blockchain/encoding.md | 4 ++-- types/canonical.go | 14 +++++++------- types/vote_test.go | 24 ++++++++++++------------ 3 files changed, 21 insertions(+), 21 deletions(-) diff --git a/docs/spec/blockchain/encoding.md b/docs/spec/blockchain/encoding.md index 563b0a88..2f9fcdca 100644 --- a/docs/spec/blockchain/encoding.md +++ b/docs/spec/blockchain/encoding.md @@ -301,15 +301,15 @@ Where the `"value"` is the base64 encoding of the raw pubkey bytes, and the Signed messages (eg. votes, proposals) in the consensus are encoded using Amino. When signing, the elements of a message are re-ordered so the fixed-length fields -are first, making it easy to quickly check the version, height, round, and type. +are first, making it easy to quickly check the type, height, and round. The `ChainID` is also appended to the end. We call this encoding the SignBytes. For instance, SignBytes for a vote is the Amino encoding of the following struct: ```go type CanonicalVote struct { + Type byte Height int64 `binary:"fixed64"` Round int64 `binary:"fixed64"` - VoteType byte Timestamp time.Time BlockID CanonicalBlockID ChainID string diff --git a/types/canonical.go b/types/canonical.go index c40f35dd..632dcb62 100644 --- a/types/canonical.go +++ b/types/canonical.go @@ -23,9 +23,9 @@ type CanonicalPartSetHeader struct { } type CanonicalProposal struct { + Type SignedMsgType // type alias for byte Height int64 `binary:"fixed64"` Round int64 `binary:"fixed64"` - Type SignedMsgType // type alias for byte POLRound int64 `binary:"fixed64"` Timestamp time.Time BlockPartsHeader CanonicalPartSetHeader @@ -34,19 +34,19 @@ type CanonicalProposal struct { } type CanonicalVote struct { + Type SignedMsgType // type alias for byte Height int64 `binary:"fixed64"` Round int64 `binary:"fixed64"` - Type SignedMsgType // type alias for byte Timestamp time.Time BlockID CanonicalBlockID ChainID string } type CanonicalHeartbeat struct { + Type byte Height int64 `binary:"fixed64"` Round int `binary:"fixed64"` - Type byte - Sequence int `binary:"fixed64"` + Sequence int `binary:"fixed64"` ValidatorAddress Address ValidatorIndex int ChainID string @@ -71,9 +71,9 @@ func CanonicalizePartSetHeader(psh PartSetHeader) CanonicalPartSetHeader { func CanonicalizeProposal(chainID string, proposal *Proposal) CanonicalProposal { return CanonicalProposal{ + Type: ProposalType, Height: proposal.Height, Round: int64(proposal.Round), // cast int->int64 to make amino encode it fixed64 (does not work for int) - Type: ProposalType, POLRound: int64(proposal.POLRound), Timestamp: proposal.Timestamp, BlockPartsHeader: CanonicalizePartSetHeader(proposal.BlockPartsHeader), @@ -84,9 +84,9 @@ func CanonicalizeProposal(chainID string, proposal *Proposal) CanonicalProposal func CanonicalizeVote(chainID string, vote *Vote) CanonicalVote { return CanonicalVote{ + Type: vote.Type, Height: vote.Height, Round: int64(vote.Round), // cast int->int64 to make amino encode it fixed64 (does not work for int) - Type: vote.Type, Timestamp: vote.Timestamp, BlockID: CanonicalizeBlockID(vote.BlockID), ChainID: chainID, @@ -95,9 +95,9 @@ func CanonicalizeVote(chainID string, vote *Vote) CanonicalVote { func CanonicalizeHeartbeat(chainID string, heartbeat *Heartbeat) CanonicalHeartbeat { return CanonicalHeartbeat{ + Type: byte(HeartbeatType), Height: heartbeat.Height, Round: heartbeat.Round, - Type: byte(HeartbeatType), Sequence: heartbeat.Sequence, ValidatorAddress: heartbeat.ValidatorAddress, ValidatorIndex: heartbeat.ValidatorIndex, diff --git a/types/vote_test.go b/types/vote_test.go index 066df496..2172f060 100644 --- a/types/vote_test.go +++ b/types/vote_test.go @@ -70,12 +70,12 @@ func TestVoteSignableTestVectors(t *testing.T) { CanonicalizeVote("", &Vote{Height: 1, Round: 1, Type: PrecommitType}), []byte{ 0x1f, // total length - 0x9, // (field_number << 3) | wire_type - 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, // height + 0x8, // (field_number << 3) | wire_type + 0x2, // PrecommitType 0x11, // (field_number << 3) | wire_type + 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, // height + 0x19, // (field_number << 3) | wire_type 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, // round - 0x18, // (field_number << 3) | wire_type - 0x2, // PrecommitType 0x22, // (field_number << 3) | wire_type // remaining fields (timestamp): 0x9, 0x9, 0x0, 0x9, 0x6e, 0x88, 0xf1, 0xff, 0xff, 0xff}, @@ -85,12 +85,12 @@ func TestVoteSignableTestVectors(t *testing.T) { CanonicalizeVote("", &Vote{Height: 1, Round: 1, Type: PrevoteType}), []byte{ 0x1f, // total length - 0x9, // (field_number << 3) | wire_type - 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, // height + 0x8, // (field_number << 3) | wire_type + 0x1, // PrevoteType 0x11, // (field_number << 3) | wire_type + 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, // height + 0x19, // (field_number << 3) | wire_type 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, // round - 0x18, // (field_number << 3) | wire_type - 0x1, // PrevoteType 0x22, // (field_number << 3) | wire_type // remaining fields (timestamp): 0x9, 0x9, 0x0, 0x9, 0x6e, 0x88, 0xf1, 0xff, 0xff, 0xff}, @@ -99,9 +99,9 @@ func TestVoteSignableTestVectors(t *testing.T) { vote, []byte{ 0x1d, // total length - 0x9, // (field_number << 3) | wire_type - 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, // height 0x11, // (field_number << 3) | wire_type + 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, // height + 0x19, // (field_number << 3) | wire_type 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, // round // remaining fields (timestamp): 0x22, @@ -112,9 +112,9 @@ func TestVoteSignableTestVectors(t *testing.T) { CanonicalizeVote("test_chain_id", &Vote{Height: 1, Round: 1}), []byte{ 0x2c, // total length - 0x9, // (field_number << 3) | wire_type - 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, // height 0x11, // (field_number << 3) | wire_type + 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, // height + 0x19, // (field_number << 3) | wire_type 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, // round // remaining fields: 0x22, // (field_number << 3) | wire_type From 9d62bd0ad3bf691002a815339b4dd675441bce93 Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Fri, 19 Oct 2018 14:29:45 -0400 Subject: [PATCH 030/267] crypto: use stdlib crypto/rand. ref #2099 (#2669) * crypto: use stdlib crypto/rand. ref #2099 * comment --- crypto/random.go | 42 +++++++++++++++++++++++++++++++----------- 1 file changed, 31 insertions(+), 11 deletions(-) diff --git a/crypto/random.go b/crypto/random.go index af328642..914c321b 100644 --- a/crypto/random.go +++ b/crypto/random.go @@ -9,10 +9,11 @@ import ( "sync" "golang.org/x/crypto/chacha20poly1305" - - . "github.com/tendermint/tendermint/libs/common" ) +// NOTE: This is ignored for now until we have time +// to properly review the MixEntropy function - https://github.com/tendermint/tendermint/issues/2099. +// // The randomness here is derived from xoring a chacha20 keystream with // output from crypto/rand's OS Entropy Reader. (Due to fears of the OS' // entropy being backdoored) @@ -23,9 +24,13 @@ var gRandInfo *randInfo func init() { gRandInfo = &randInfo{} - gRandInfo.MixEntropy(randBytes(32)) // Init + + // TODO: uncomment after reviewing MixEntropy - + // https://github.com/tendermint/tendermint/issues/2099 + // gRandInfo.MixEntropy(randBytes(32)) // Init } +// WARNING: This function needs review - https://github.com/tendermint/tendermint/issues/2099. // Mix additional bytes of randomness, e.g. from hardware, user-input, etc. // It is OK to call it multiple times. It does not diminish security. func MixEntropy(seedBytes []byte) { @@ -37,20 +42,28 @@ func randBytes(numBytes int) []byte { b := make([]byte, numBytes) _, err := crand.Read(b) if err != nil { - PanicCrisis(err) + panic(err) } return b } +// This only uses the OS's randomness +func CRandBytes(numBytes int) []byte { + return randBytes(numBytes) +} + +/* TODO: uncomment after reviewing MixEntropy - https://github.com/tendermint/tendermint/issues/2099 // This uses the OS and the Seed(s). func CRandBytes(numBytes int) []byte { - b := make([]byte, numBytes) - _, err := gRandInfo.Read(b) - if err != nil { - PanicCrisis(err) - } - return b + return randBytes(numBytes) + b := make([]byte, numBytes) + _, err := gRandInfo.Read(b) + if err != nil { + panic(err) + } + return b } +*/ // CRandHex returns a hex encoded string that's floor(numDigits/2) * 2 long. // @@ -60,10 +73,17 @@ func CRandHex(numDigits int) string { return hex.EncodeToString(CRandBytes(numDigits / 2)) } +// Returns a crand.Reader. +func CReader() io.Reader { + return crand.Reader +} + +/* TODO: uncomment after reviewing MixEntropy - https://github.com/tendermint/tendermint/issues/2099 // Returns a crand.Reader mixed with user-supplied entropy func CReader() io.Reader { return gRandInfo } +*/ //-------------------------------------------------------------------------------- @@ -75,7 +95,7 @@ type randInfo struct { } // You can call this as many times as you'd like. -// XXX TODO review +// XXX/TODO: review - https://github.com/tendermint/tendermint/issues/2099 func (ri *randInfo) MixEntropy(seedBytes []byte) { ri.mtx.Lock() defer ri.mtx.Unlock() From f94eb42ebe1c53db52b07e4080e060acc4077a5a Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Fri, 19 Oct 2018 20:27:00 -0400 Subject: [PATCH 031/267] Version bump; Update Upgrading.md; linkify Changelog (#2679) * version bump * update UPGRADING.md * add missing pr numbers to changelog pending * linkify changelog --- CHANGELOG_PENDING.md | 94 ++++++++++++++++++++++---------------------- UPGRADING.md | 76 +++++++++++++++++++++++++++++++++++ version/version.go | 4 +- 3 files changed, 125 insertions(+), 49 deletions(-) diff --git a/CHANGELOG_PENDING.md b/CHANGELOG_PENDING.md index 5490ae77..163c4649 100644 --- a/CHANGELOG_PENDING.md +++ b/CHANGELOG_PENDING.md @@ -22,86 +22,86 @@ Friendly reminder, we have a [bug bounty program](https://hackerone.com/tendermi BREAKING CHANGES: * CLI/RPC/Config - * [config] \#2232 timeouts as time.Duration, not ints - * [config] \#2505 Remove Mempool.RecheckEmpty (it was effectively useless anyways) - * [config] `mempool.wal` is disabled by default - * [privval] \#2459 Split `SocketPVMsg`s implementations into Request and Response, where the Response may contain a error message (returned by the remote signer) - * [state] \#2644 Add Version field to State, breaking the format of State as + * [config] [\#2232](https://github.com/tendermint/tendermint/issues/2232) timeouts as time.Duration, not ints + * [config] [\#2505](https://github.com/tendermint/tendermint/issues/2505) Remove Mempool.RecheckEmpty (it was effectively useless anyways) + * [config] [\#2490](https://github.com/tendermint/tendermint/issues/2490) `mempool.wal` is disabled by default + * [privval] [\#2459](https://github.com/tendermint/tendermint/issues/2459) Split `SocketPVMsg`s implementations into Request and Response, where the Response may contain a error message (returned by the remote signer) + * [state] [\#2644](https://github.com/tendermint/tendermint/issues/2644) Add Version field to State, breaking the format of State as encoded on disk. - * [rpc] \#2298 `/abci_query` takes `prove` argument instead of `trusted` and switches the default + * [rpc] [\#2298](https://github.com/tendermint/tendermint/issues/2298) `/abci_query` takes `prove` argument instead of `trusted` and switches the default behaviour to `prove=false` - * [rpc] \#2654 Remove all `node_info.other.*_version` fields in `/status` and + * [rpc] [\#2654](https://github.com/tendermint/tendermint/issues/2654) Remove all `node_info.other.*_version` fields in `/status` and `/net_info` * Apps - * [abci] \#2298 ResponseQuery.Proof is now a structured merkle.Proof, not just + * [abci] [\#2298](https://github.com/tendermint/tendermint/issues/2298) ResponseQuery.Proof is now a structured merkle.Proof, not just arbitrary bytes - * [abci] \#2644 Add Version to Header and shift all fields by one - * [abci] \#2662 Bump the field numbers for some `ResponseInfo` fields to make room for + * [abci] [\#2644](https://github.com/tendermint/tendermint/issues/2644) Add Version to Header and shift all fields by one + * [abci] [\#2662](https://github.com/tendermint/tendermint/issues/2662) Bump the field numbers for some `ResponseInfo` fields to make room for `AppVersion` * Go API - * [config] \#2232 timeouts as time.Duration, not ints - * [crypto/merkle & lite] \#2298 Various changes to accomodate General Merkle trees - * [crypto/merkle] \#2595 Remove all Hasher objects in favor of byte slices - * [crypto/merkle] \#2635 merkle.SimpleHashFromTwoHashes is no longer exported - * [node] Remove node.RunForever - * [rpc/client] \#2298 `ABCIQueryOptions.Trusted` -> `ABCIQueryOptions.Prove` - * [types] \#2298 Remove `Index` and `Total` fields from `TxProof`. - * [types] \#2598 `VoteTypeXxx` are now of type `SignedMsgType byte` and named `XxxType`, eg. `PrevoteType`, + * [config] [\#2232](https://github.com/tendermint/tendermint/issues/2232) timeouts as time.Duration, not ints + * [crypto/merkle & lite] [\#2298](https://github.com/tendermint/tendermint/issues/2298) Various changes to accomodate General Merkle trees + * [crypto/merkle] [\#2595](https://github.com/tendermint/tendermint/issues/2595) Remove all Hasher objects in favor of byte slices + * [crypto/merkle] [\#2635](https://github.com/tendermint/tendermint/issues/2635) merkle.SimpleHashFromTwoHashes is no longer exported + * [node] [\#2479](https://github.com/tendermint/tendermint/issues/2479) Remove node.RunForever + * [rpc/client] [\#2298](https://github.com/tendermint/tendermint/issues/2298) `ABCIQueryOptions.Trusted` -> `ABCIQueryOptions.Prove` + * [types] [\#2298](https://github.com/tendermint/tendermint/issues/2298) Remove `Index` and `Total` fields from `TxProof`. + * [types] [\#2598](https://github.com/tendermint/tendermint/issues/2598) `VoteTypeXxx` are now of type `SignedMsgType byte` and named `XxxType`, eg. `PrevoteType`, `PrecommitType`. * Blockchain Protocol * [types] Update SignBytes for `Vote`/`Proposal`/`Heartbeat`: - * \#2459 Use amino encoding instead of JSON in `SignBytes`. - * \#2598 Reorder fields and use fixed sized encoding. - * \#2598 Change `Type` field fromt `string` to `byte` and use new + * [\#2459](https://github.com/tendermint/tendermint/issues/2459) Use amino encoding instead of JSON in `SignBytes`. + * [\#2598](https://github.com/tendermint/tendermint/issues/2598) Reorder fields and use fixed sized encoding. + * [\#2598](https://github.com/tendermint/tendermint/issues/2598) Change `Type` field fromt `string` to `byte` and use new `SignedMsgType` to enumerate. - * [types] \#2512 Remove the pubkey field from the validator hash - * [types] \#2644 Add Version struct to Header - * [types] \#2609 ConsensusParams.Hash() is the hash of the amino encoded + * [types] [\#2512](https://github.com/tendermint/tendermint/issues/2512) Remove the pubkey field from the validator hash + * [types] [\#2644](https://github.com/tendermint/tendermint/issues/2644) Add Version struct to Header + * [types] [\#2609](https://github.com/tendermint/tendermint/issues/2609) ConsensusParams.Hash() is the hash of the amino encoded struct instead of the Merkle tree of the fields - * [state] \#2587 Require block.Time of the fist block to be genesis time - * [state] \#2644 Require block.Version to match state.Version - * [types] \#2670 Header.Hash() builds Merkle tree out of fields in the same + * [state] [\#2587](https://github.com/tendermint/tendermint/issues/2587) Require block.Time of the fist block to be genesis time + * [state] [\#2644](https://github.com/tendermint/tendermint/issues/2644) Require block.Version to match state.Version + * [types] [\#2670](https://github.com/tendermint/tendermint/issues/2670) Header.Hash() builds Merkle tree out of fields in the same order they appear in the header, instead of sorting by field name * P2P Protocol - * [p2p] \#2654 Add `ProtocolVersion` struct with protocol versions to top of + * [p2p] [\#2654](https://github.com/tendermint/tendermint/issues/2654) Add `ProtocolVersion` struct with protocol versions to top of DefaultNodeInfo and require `ProtocolVersion.Block` to match during peer handshake FEATURES: -- [abci] \#2557 Add `Codespace` field to `Response{CheckTx, DeliverTx, Query}` -- [abci] \#2662 Add `BlockVersion` and `P2PVersion` to `RequestInfo` -- [crypto/merkle] \#2298 General Merkle Proof scheme for chaining various types of Merkle trees together +- [abci] [\#2557](https://github.com/tendermint/tendermint/issues/2557) Add `Codespace` field to `Response{CheckTx, DeliverTx, Query}` +- [abci] [\#2662](https://github.com/tendermint/tendermint/issues/2662) Add `BlockVersion` and `P2PVersion` to `RequestInfo` +- [crypto/merkle] [\#2298](https://github.com/tendermint/tendermint/issues/2298) General Merkle Proof scheme for chaining various types of Merkle trees together IMPROVEMENTS: - Additional Metrics - [consensus] [\#2169](https://github.com/cosmos/cosmos-sdk/issues/2169) - [p2p] [\#2169](https://github.com/cosmos/cosmos-sdk/issues/2169) -- [config] \#2232 Added ValidateBasic method, which performs basic checks -- [crypto/ed25519] \#2558 Switch to use latest `golang.org/x/crypto` through our fork at +- [config] [\#2232](https://github.com/tendermint/tendermint/issues/2232) Added ValidateBasic method, which performs basic checks +- [crypto/ed25519] [\#2558](https://github.com/tendermint/tendermint/issues/2558) Switch to use latest `golang.org/x/crypto` through our fork at github.com/tendermint/crypto -- [tools] \#2238 Binary dependencies are now locked to a specific git commit +- [tools] [\#2238](https://github.com/tendermint/tendermint/issues/2238) Binary dependencies are now locked to a specific git commit BUG FIXES: -- [autofile] \#2428 Group.RotateFile need call Flush() before rename (@goolAdapter) -- [common] Fixed a bug in the `BitArray.Or` method -- [common] Fixed a bug in the `BitArray.Sub` method (@james-ray) -- [common] \#2534 Fix `BitArray.PickRandom` to choose uniformly from true bits +- [autofile] [\#2428](https://github.com/tendermint/tendermint/issues/2428) Group.RotateFile need call Flush() before rename (@goolAdapter) +- [common] [\#2533](https://github.com/tendermint/tendermint/issues/2533) Fixed a bug in the `BitArray.Or` method +- [common] [\#2506](https://github.com/tendermint/tendermint/issues/2506) Fixed a bug in the `BitArray.Sub` method (@james-ray) +- [common] [\#2534](https://github.com/tendermint/tendermint/issues/2534) Fix `BitArray.PickRandom` to choose uniformly from true bits - [consensus] [\#1690](https://github.com/tendermint/tendermint/issues/1690) Wait for timeoutPrecommit before starting next round - [consensus] [\#1745](https://github.com/tendermint/tendermint/issues/1745) Wait for Proposal or timeoutProposal before entering prevote -- [consensus] \#2642 Only propose ValidBlock, not LockedBlock -- [consensus] \#2642 Initialized ValidRound and LockedRound to -1 -- [consensus] \#1637 Limit the amount of evidence that can be included in a +- [consensus] [\#2642](https://github.com/tendermint/tendermint/issues/2642) Only propose ValidBlock, not LockedBlock +- [consensus] [\#2642](https://github.com/tendermint/tendermint/issues/2642) Initialized ValidRound and LockedRound to -1 +- [consensus] [\#1637](https://github.com/tendermint/tendermint/issues/1637) Limit the amount of evidence that can be included in a block -- [evidence] \#2515 Fix db iter leak (@goolAdapter) -- [libs/event] \#2518 Fix event concurrency flaw (@goolAdapter) -- [node] \#2434 Make node respond to signal interrupts while sleeping for genesis time -- [state] \#2616 Pass nil to NewValidatorSet() when genesis file's Validators field is nil -- [p2p] \#2555 Fix p2p switch FlushThrottle value (@goolAdapter) -- [p2p] \#2668 Reconnect to originally dialed address (not self-reported +- [evidence] [\#2515](https://github.com/tendermint/tendermint/issues/2515) Fix db iter leak (@goolAdapter) +- [libs/event] [\#2518](https://github.com/tendermint/tendermint/issues/2518) Fix event concurrency flaw (@goolAdapter) +- [node] [\#2434](https://github.com/tendermint/tendermint/issues/2434) Make node respond to signal interrupts while sleeping for genesis time +- [state] [\#2616](https://github.com/tendermint/tendermint/issues/2616) Pass nil to NewValidatorSet() when genesis file's Validators field is nil +- [p2p] [\#2555](https://github.com/tendermint/tendermint/issues/2555) Fix p2p switch FlushThrottle value (@goolAdapter) +- [p2p] [\#2668](https://github.com/tendermint/tendermint/issues/2668) Reconnect to originally dialed address (not self-reported address) for persistent peers diff --git a/UPGRADING.md b/UPGRADING.md index 81e56e58..cb0830a4 100644 --- a/UPGRADING.md +++ b/UPGRADING.md @@ -3,6 +3,82 @@ This guide provides steps to be followed when you upgrade your applications to a newer version of Tendermint Core. +## v0.26.0 + +New 0.26.0 release contains a lot of changes to core data types. 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. + +To reset the state do: + +``` +$ tendermint unsafe_reset_all +``` + +Here we summarize some other notable changes to be mindful of. + +### Config Changes + +All timeouts must be changed from integers to strings with their duration, for +instance `flush_throttle_timeout = 100` would be changed to +`flush_throttle_timeout = "100ms"` and `timeout_propose = 3000` would be changed +to `timeout_propose = "3s"`. + +### RPC Changes + +The default behaviour of `/abci_query` has been changed to not return a proof, +and the name of the parameter that controls this has been changed from `trusted` +to `prove`. To get proofs with your queries, ensure you set `prove=true`. + +Various version fields like `amino_version`, `p2p_version`, `consensus_version`, +and `rpc_version` have been removed from the `node_info.other` and are +consolidated under the tendermint semantic version (ie. `node_info.version`) and +the new `block` and `p2p` protocol versions under `node_info.protocol_version`.. + +### ABCI Changes + +Field numbers were bumped in the `Header` and `ResponseInfo` messages to make +room for new `version` fields. It should be straight forward to recompile the +protobuf file for these changes. + +#### Proofs + +The `ResponseQuery.Proof` field is now structured as a `[]ProofOp` to support +generalized Merkle tree constructions where the leaves of one Merkle tree are +the root of another. If you don't need this functionaluty, and you used to +return `` here, you should instead return a single `ProofOp` with +just the `Data` field set: + +``` +[]ProofOp{ + ProofOp{ + Data: , + } +} +``` + +For more information, see: + +- [ADR-026](https://github.com/tendermint/tendermint/blob/30519e8361c19f4bf320ef4d26288ebc621ad725/docs/architecture/adr-026-general-merkle-proof.md) +- [Relevant ABCI + documentation](https://github.com/tendermint/tendermint/blob/30519e8361c19f4bf320ef4d26288ebc621ad725/docs/spec/abci/apps.md#query-proofs) +- [Description of + keys](https://github.com/tendermint/tendermint/blob/30519e8361c19f4bf320ef4d26288ebc621ad725/crypto/merkle/proof_key_path.go#L14) + +### Go API Changes + +#### 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 +serialized before they are passed in. + +#### node + +The `node.RunForever` function was removed. Signal handling and running forever +should instead be explicitly configured by the caller. See how we do it +[here](https://github.com/tendermint/tendermint/blob/30519e8361c19f4bf320ef4d26288ebc621ad725/cmd/tendermint/commands/run_node.go#L60). + ## v0.25.0 This release has minimal impact. diff --git a/version/version.go b/version/version.go index 19b3f3da..b4664fd7 100644 --- a/version/version.go +++ b/version/version.go @@ -18,10 +18,10 @@ 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.25.0" + TMCoreSemVer = "0.26.0" // ABCISemVer is the semantic version of the ABCI library - ABCISemVer = "0.14.0" + ABCISemVer = "0.15.0" ABCIVersion = ABCISemVer ) From fe1d59ab7b80c04ddaaa60f93b0f8656c1ed8f4b Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Mon, 22 Oct 2018 17:55:49 -0400 Subject: [PATCH 032/267] Set protocol versions in NodeInfo from state (#2686) * use types.NewValidator * node: set p2p.ProtocolVersion from state, not globals --- benchmarks/codec_test.go | 2 +- node/node.go | 6 +++++- p2p/node_info.go | 20 ++++++++++++-------- p2p/peer_test.go | 2 +- p2p/test_util.go | 2 +- state/state.go | 11 +---------- types/protobuf_test.go | 18 +++--------------- 7 files changed, 24 insertions(+), 37 deletions(-) diff --git a/benchmarks/codec_test.go b/benchmarks/codec_test.go index 2be1db15..3e027028 100644 --- a/benchmarks/codec_test.go +++ b/benchmarks/codec_test.go @@ -14,7 +14,7 @@ import ( func testNodeInfo(id p2p.ID) p2p.DefaultNodeInfo { return p2p.DefaultNodeInfo{ - ProtocolVersion: p2p.InitProtocolVersion, + ProtocolVersion: p2p.ProtocolVersion{1, 2, 3}, ID_: id, Moniker: "SOMENAME", Network: "SOMENAME", diff --git a/node/node.go b/node/node.go index 522f18e9..f62a8b47 100644 --- a/node/node.go +++ b/node/node.go @@ -367,7 +367,11 @@ func NewNode(config *cfg.Config, nodeKey.ID(), txIndexer, genDoc.ChainID, - p2p.ProtocolVersionWithApp(state.Version.Consensus.App), + p2p.NewProtocolVersion( + version.P2PProtocol, // global + state.Version.Consensus.Block, + state.Version.Consensus.App, + ), ) ) diff --git a/p2p/node_info.go b/p2p/node_info.go index 1d408eb6..e46174e0 100644 --- a/p2p/node_info.go +++ b/p2p/node_info.go @@ -49,16 +49,20 @@ type ProtocolVersion struct { App version.Protocol `json:"app"` } -// InitProtocolVersion populates the Block and P2P versions, but not the App. -var InitProtocolVersion = ProtocolVersionWithApp(0) +// defaultProtocolVersion populates the Block and P2P versions using +// the global values, but not the App. +var defaultProtocolVersion = NewProtocolVersion( + version.P2PProtocol, + version.BlockProtocol, + 0, +) -// ProtocolVersionWithApp returns a fully populated ProtocolVersion -// using the provided App version and the Block and P2P versions defined in the `version` package. -func ProtocolVersionWithApp(appVersion version.Protocol) ProtocolVersion { +// NewProtocolVersion returns a fully populated ProtocolVersion. +func NewProtocolVersion(p2p, block, app version.Protocol) ProtocolVersion { return ProtocolVersion{ - P2P: version.P2PProtocol, - Block: version.BlockProtocol, - App: appVersion, + P2P: p2p, + Block: block, + App: app, } } diff --git a/p2p/peer_test.go b/p2p/peer_test.go index 02f1d2c0..d3d9f0c7 100644 --- a/p2p/peer_test.go +++ b/p2p/peer_test.go @@ -207,7 +207,7 @@ func (rp *remotePeer) accept(l net.Listener) { func (rp *remotePeer) nodeInfo(l net.Listener) NodeInfo { return DefaultNodeInfo{ - ProtocolVersion: InitProtocolVersion, + ProtocolVersion: defaultProtocolVersion, ID_: rp.Addr().ID, ListenAddr: l.Addr().String(), Network: "testing", diff --git a/p2p/test_util.go b/p2p/test_util.go index e1f7b504..d72c0c76 100644 --- a/p2p/test_util.go +++ b/p2p/test_util.go @@ -249,7 +249,7 @@ func testNodeInfo(id ID, name string) NodeInfo { func testNodeInfoWithNetwork(id ID, name, network string) NodeInfo { return DefaultNodeInfo{ - ProtocolVersion: InitProtocolVersion, + ProtocolVersion: defaultProtocolVersion, ID_: id, ListenAddr: fmt.Sprintf("127.0.0.1:%d", cmn.RandIntn(64512)+1023), Network: network, diff --git a/state/state.go b/state/state.go index 5c1b68a2..d6ec6f0b 100644 --- a/state/state.go +++ b/state/state.go @@ -222,7 +222,6 @@ func MakeGenesisState(genDoc *types.GenesisDoc) (State, error) { return State{}, fmt.Errorf("Error in genesis file: %v", err) } - // Make validators slice var validatorSet, nextValidatorSet *types.ValidatorSet if genDoc.Validators == nil { validatorSet = types.NewValidatorSet(nil) @@ -230,15 +229,7 @@ func MakeGenesisState(genDoc *types.GenesisDoc) (State, error) { } else { validators := make([]*types.Validator, len(genDoc.Validators)) for i, val := range genDoc.Validators { - pubKey := val.PubKey - address := pubKey.Address() - - // Make validator - validators[i] = &types.Validator{ - Address: address, - PubKey: pubKey, - VotingPower: val.Power, - } + validators[i] = types.NewValidator(val.PubKey, val.Power) } validatorSet = types.NewValidatorSet(validators) nextValidatorSet = types.NewValidatorSet(validators).CopyIncrementAccum(1) diff --git a/types/protobuf_test.go b/types/protobuf_test.go index f8682abf..c940f1b4 100644 --- a/types/protobuf_test.go +++ b/types/protobuf_test.go @@ -30,17 +30,9 @@ func TestABCIValidators(t *testing.T) { pkEd := ed25519.GenPrivKey().PubKey() // correct validator - tmValExpected := &Validator{ - Address: pkEd.Address(), - PubKey: pkEd, - VotingPower: 10, - } + tmValExpected := NewValidator(pkEd, 10) - tmVal := &Validator{ - Address: pkEd.Address(), - PubKey: pkEd, - VotingPower: 10, - } + tmVal := NewValidator(pkEd, 10) abciVal := TM2PB.ValidatorUpdate(tmVal) tmVals, err := PB2TM.ValidatorUpdates([]abci.ValidatorUpdate{abciVal}) @@ -127,11 +119,7 @@ func TestABCIValidatorFromPubKeyAndPower(t *testing.T) { func TestABCIValidatorWithoutPubKey(t *testing.T) { pkEd := ed25519.GenPrivKey().PubKey() - abciVal := TM2PB.Validator(&Validator{ - Address: pkEd.Address(), - PubKey: pkEd, - VotingPower: 10, - }) + abciVal := TM2PB.Validator(NewValidator(pkEd, 10)) // pubkey must be nil tmValExpected := abci.Validator{ From be929acd6a726b322fe9dcc61fc6713dfc668349 Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Tue, 23 Oct 2018 13:21:47 -0400 Subject: [PATCH 033/267] Update to Amino v0.13.0-rc0 (#2687) * types: test tm2pm on fully populated header * upgrade for amino v0.13.0-rc0 * fix lint * comment out final test --- Gopkg.lock | 6 ++-- Gopkg.toml | 2 +- state/state.go | 33 +++++++---------- types/block.go | 32 ++++++++++++++--- types/block_test.go | 18 ++++++---- types/evidence.go | 2 +- types/evidence_test.go | 2 +- types/proto3/block.pb.go | 63 ++++++++++++++++---------------- types/proto3/block.proto | 11 +++--- types/protobuf.go | 13 ++++--- types/protobuf_test.go | 78 ++++++++++++++++++++++++++++++++++------ types/results_test.go | 2 +- types/vote.go | 2 +- types/vote_test.go | 26 +++++++------- 14 files changed, 187 insertions(+), 103 deletions(-) diff --git a/Gopkg.lock b/Gopkg.lock index 0f70bb2f..d5d6c1b2 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -365,12 +365,12 @@ revision = "e5840949ff4fff0c56f9b6a541e22b63581ea9df" [[projects]] - digest = "1:e0a2a4be1e20c305badc2b0a7a9ab7fef6da500763bec23ab81df3b5f9eec9ee" + digest = "1:3ff2c9d4def5ec999ab672b9059d0ba41a1351913ea78e63b5402e4ba4ef8da4" name = "github.com/tendermint/go-amino" packages = ["."] pruneopts = "UT" - revision = "a8328986c1608950fa5d3d1c0472cccc4f8fc02c" - version = "v0.12.0-rc0" + revision = "ff047d9e357e66d937d6900d4a2e04501cc62c70" + version = "v0.13.0-rc0" [[projects]] digest = "1:72b71e3a29775e5752ed7a8012052a3dee165e27ec18cedddae5288058f09acf" diff --git a/Gopkg.toml b/Gopkg.toml index 07ff3c53..622ca00e 100644 --- a/Gopkg.toml +++ b/Gopkg.toml @@ -58,7 +58,7 @@ [[constraint]] name = "github.com/tendermint/go-amino" - version = "v0.12.0-rc0" + version = "v0.13.0-rc0" [[constraint]] name = "google.golang.org/grpc" diff --git a/state/state.go b/state/state.go index d6ec6f0b..0dbd718d 100644 --- a/state/state.go +++ b/state/state.go @@ -128,7 +128,7 @@ func (state State) IsEmpty() bool { // MakeBlock builds a block from the current state with the given txs, commit, // and evidence. Note it also takes a proposerAddress because the state does not -// track rounds, and hence doesn't know the correct proposer. TODO: alleviate this! +// track rounds, and hence does not know the correct proposer. TODO: fix this! func (state State) MakeBlock( height int64, txs []types.Tx, @@ -140,29 +140,22 @@ func (state State) MakeBlock( // Build base block with block data. block := types.MakeBlock(height, txs, commit, evidence) - // Fill rest of header with state data. - block.Version = state.Version.Consensus - block.ChainID = state.ChainID - - // Set time + // Set time. + var timestamp time.Time if height == 1 { - block.Time = state.LastBlockTime // genesis time + timestamp = state.LastBlockTime // genesis time } else { - block.Time = MedianTime(commit, state.LastValidators) + timestamp = MedianTime(commit, state.LastValidators) } - block.LastBlockID = state.LastBlockID - block.TotalTxs = state.LastBlockTotalTx + block.NumTxs - - block.ValidatorsHash = state.Validators.Hash() - block.NextValidatorsHash = state.NextValidators.Hash() - block.ConsensusHash = state.ConsensusParams.Hash() - block.AppHash = state.AppHash - block.LastResultsHash = state.LastResultsHash - - // NOTE: we can't use the state.Validators because we don't - // IncrementAccum for rounds there. - block.ProposerAddress = proposerAddress + // Fill rest of header with state data. + block.Header.Populate( + state.Version.Consensus, state.ChainID, + timestamp, state.LastBlockID, state.LastBlockTotalTx+block.NumTxs, + state.Validators.Hash(), state.NextValidators.Hash(), + state.ConsensusParams.Hash(), state.AppHash, state.LastResultsHash, + proposerAddress, + ) return block, block.MakePartSet(types.BlockPartSizeBytes) } diff --git a/types/block.go b/types/block.go index 2a5b5fc4..ce605263 100644 --- a/types/block.go +++ b/types/block.go @@ -15,7 +15,7 @@ import ( const ( // MaxHeaderBytes is a maximum header size (including amino overhead). - MaxHeaderBytes int64 = 534 + MaxHeaderBytes int64 = 537 // MaxAminoOverheadForBlock - maximum amino overhead to encode a block (up to // MaxBlockSizeBytes in size) not including it's parts except Data. @@ -257,11 +257,11 @@ func MaxDataBytesUnknownEvidence(maxBytes int64, valsCount int) int64 { //----------------------------------------------------------------------------- -// Header defines the structure of a Tendermint block header +// Header defines the structure of a Tendermint block header. // NOTE: changes to the Header should be duplicated in: -// - header.Hash() -// - abci.Header -// - /docs/spec/blockchain/blockchain.md +// - header.Hash() +// - abci.Header +// - /docs/spec/blockchain/blockchain.md type Header struct { // basic block info Version version.Consensus `json:"version"` @@ -290,6 +290,28 @@ type Header struct { ProposerAddress Address `json:"proposer_address"` // original proposer of the block } +// Populate the Header with state-derived data. +// Call this after MakeBlock to complete the Header. +func (h *Header) Populate( + version version.Consensus, chainID string, + timestamp time.Time, lastBlockID BlockID, totalTxs int64, + valHash, nextValHash []byte, + consensusHash, appHash, lastResultsHash []byte, + proposerAddress Address, +) { + h.Version = version + h.ChainID = chainID + h.Time = timestamp + h.LastBlockID = lastBlockID + h.TotalTxs = totalTxs + h.ValidatorsHash = valHash + h.NextValidatorsHash = nextValHash + h.ConsensusHash = consensusHash + h.AppHash = appHash + h.LastResultsHash = lastResultsHash + h.ProposerAddress = proposerAddress +} + // Hash returns the hash of the header. // It computes a Merkle tree from the header fields // ordered as they appear in the Header. diff --git a/types/block_test.go b/types/block_test.go index d268e411..e34ba29b 100644 --- a/types/block_test.go +++ b/types/block_test.go @@ -242,11 +242,15 @@ func TestMaxHeaderBytes(t *testing.T) { maxChainID += "𠜎" } + // time is varint encoded so need to pick the max. + // year int, month Month, day, hour, min, sec, nsec int, loc *Location + timestamp := time.Date(math.MaxInt64, 0, 0, 0, 0, 0, math.MaxInt64, time.UTC) + h := Header{ Version: version.Consensus{math.MaxInt64, math.MaxInt64}, ChainID: maxChainID, Height: math.MaxInt64, - Time: time.Now().UTC(), + Time: timestamp, NumTxs: math.MaxInt64, TotalTxs: math.MaxInt64, LastBlockID: makeBlockID(make([]byte, tmhash.Size), math.MaxInt64, make([]byte, tmhash.Size)), @@ -288,9 +292,9 @@ func TestBlockMaxDataBytes(t *testing.T) { }{ 0: {-10, 1, 0, true, 0}, 1: {10, 1, 0, true, 0}, - 2: {744, 1, 0, true, 0}, - 3: {745, 1, 0, false, 0}, - 4: {746, 1, 0, false, 1}, + 2: {750, 1, 0, true, 0}, + 3: {751, 1, 0, false, 0}, + 4: {752, 1, 0, false, 1}, } for i, tc := range testCases { @@ -316,9 +320,9 @@ func TestBlockMaxDataBytesUnknownEvidence(t *testing.T) { }{ 0: {-10, 1, true, 0}, 1: {10, 1, true, 0}, - 2: {826, 1, true, 0}, - 3: {827, 1, false, 0}, - 4: {828, 1, false, 1}, + 2: {833, 1, true, 0}, + 3: {834, 1, false, 0}, + 4: {835, 1, false, 1}, } for i, tc := range testCases { diff --git a/types/evidence.go b/types/evidence.go index 57523ab1..6d42ed22 100644 --- a/types/evidence.go +++ b/types/evidence.go @@ -14,7 +14,7 @@ import ( const ( // MaxEvidenceBytes is a maximum size of any evidence (including amino overhead). - MaxEvidenceBytes int64 = 440 + MaxEvidenceBytes int64 = 444 ) // ErrEvidenceInvalid wraps a piece of evidence and the error denoting how or why it is invalid. diff --git a/types/evidence_test.go b/types/evidence_test.go index a8d7efff..79805691 100644 --- a/types/evidence_test.go +++ b/types/evidence_test.go @@ -61,7 +61,7 @@ func TestEvidence(t *testing.T) { {vote1, makeVote(val, chainID, 0, 10, 3, 1, blockID2), false}, // wrong round {vote1, makeVote(val, chainID, 0, 10, 2, 2, blockID2), false}, // wrong step {vote1, makeVote(val2, chainID, 0, 10, 2, 1, blockID), false}, // wrong validator - {vote1, badVote, false}, // signed by wrong key + {vote1, badVote, false}, // signed by wrong key } pubKey := val.GetPubKey() diff --git a/types/proto3/block.pb.go b/types/proto3/block.pb.go index 446b3919..7efc7ca7 100644 --- a/types/proto3/block.pb.go +++ b/types/proto3/block.pb.go @@ -244,13 +244,14 @@ func (m *Version) GetApp() uint64 { return 0 } -// Timestamp wraps how amino encodes time. Note that this is different from the protobuf well-known type -// protobuf/timestamp.proto in the sense that there seconds and nanos are varint encoded. See: +// Timestamp wraps how amino encodes time. +// This is the protobuf well-known type protobuf/timestamp.proto +// See: // https://github.com/google/protobuf/blob/d2980062c859649523d5fd51d6b55ab310e47482/src/google/protobuf/timestamp.proto#L123-L135 -// Also nanos do not get skipped if they are zero in amino. +// NOTE/XXX: nanos do not get skipped if they are zero in amino. type Timestamp struct { - Seconds int64 `protobuf:"fixed64,1,opt,name=seconds" json:"seconds,omitempty"` - Nanos int32 `protobuf:"fixed32,2,opt,name=nanos" json:"nanos,omitempty"` + Seconds int64 `protobuf:"varint,1,opt,name=seconds" json:"seconds,omitempty"` + Nanos int32 `protobuf:"varint,2,opt,name=nanos" json:"nanos,omitempty"` } func (m *Timestamp) Reset() { *m = Timestamp{} } @@ -285,31 +286,31 @@ func init() { proto.RegisterFile("block.proto", fileDescriptor0) } var fileDescriptor0 = []byte{ // 443 bytes of a gzipped FileDescriptorProto 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x6c, 0x53, 0xcd, 0x6a, 0xdb, 0x40, - 0x10, 0x46, 0x8d, 0x62, 0xc7, 0x23, 0x3b, 0x76, 0x86, 0xb6, 0x88, 0x9e, 0x8c, 0x68, 0x8b, 0x7b, + 0x10, 0x46, 0xb5, 0x6c, 0xc7, 0x23, 0x3b, 0x4e, 0x86, 0xb6, 0x88, 0x9e, 0x8c, 0x68, 0x8b, 0x7b, 0x31, 0x24, 0x39, 0x94, 0xd2, 0x93, 0x6b, 0x17, 0x12, 0x28, 0x21, 0x6c, 0x8d, 0xef, 0x1b, 0x6b, - 0xa9, 0x45, 0x2d, 0xad, 0xd0, 0xac, 0x4b, 0xde, 0xb0, 0xaf, 0x55, 0x66, 0x56, 0x52, 0x2d, 0x93, - 0x93, 0xf7, 0xfb, 0x99, 0x6f, 0x76, 0xc7, 0x23, 0x88, 0x9e, 0xf6, 0x76, 0xfb, 0x7b, 0x5e, 0x56, - 0xd6, 0x59, 0xec, 0xc9, 0xcf, 0x6d, 0xf2, 0x05, 0x46, 0x8f, 0xba, 0x72, 0x3f, 0x8d, 0xbb, 0x33, - 0x3a, 0x35, 0x15, 0xbe, 0x86, 0xf3, 0xb5, 0x75, 0x7a, 0x1f, 0x07, 0xd3, 0x60, 0x76, 0xa5, 0x3c, - 0x40, 0x84, 0xf0, 0x4e, 0xd3, 0x2e, 0x7e, 0x35, 0x0d, 0x66, 0x43, 0x25, 0xe7, 0x64, 0x03, 0xfd, - 0x6f, 0x9c, 0x78, 0xbf, 0x6a, 0xe5, 0xe0, 0xbf, 0x8c, 0x9f, 0x21, 0xe2, 0x64, 0xf2, 0xb9, 0x52, - 0x19, 0xdd, 0xbc, 0xf1, 0xed, 0x6f, 0xe7, 0x9d, 0xa6, 0xea, 0xd8, 0x99, 0xfc, 0x0d, 0xa1, 0x57, - 0x5f, 0xe6, 0x13, 0xf4, 0x37, 0xa6, 0xa2, 0xcc, 0x16, 0x12, 0x1d, 0xdd, 0x8c, 0x9b, 0xfa, 0x9a, - 0x56, 0x8d, 0x8e, 0x31, 0xf4, 0x97, 0x3b, 0x9d, 0x15, 0xf7, 0x2b, 0x69, 0x35, 0x50, 0x0d, 0xc4, - 0xb7, 0x1c, 0x97, 0xfd, 0xda, 0xb9, 0xf8, 0x6c, 0x1a, 0xcc, 0x50, 0xd5, 0x08, 0x3f, 0x40, 0xb8, - 0xce, 0x72, 0x13, 0x87, 0x92, 0x7c, 0xd5, 0x24, 0x33, 0x47, 0x4e, 0xe7, 0xa5, 0x12, 0x99, 0xcb, - 0x1f, 0x0e, 0xf9, 0xfa, 0x99, 0xe2, 0x73, 0x5f, 0xee, 0x11, 0xbe, 0x83, 0x0b, 0x99, 0x0d, 0x2b, - 0x3d, 0x51, 0x5a, 0x8c, 0xd7, 0x10, 0xfd, 0xd0, 0xe4, 0xea, 0xf1, 0xc4, 0xfd, 0xee, 0xdd, 0x6b, - 0x5a, 0x1d, 0x7b, 0xf0, 0x23, 0x5c, 0x32, 0x5c, 0xda, 0x3c, 0xcf, 0x9c, 0x0c, 0xf3, 0x42, 0x86, - 0x79, 0xc2, 0x72, 0xdb, 0x95, 0x76, 0x5a, 0x1c, 0x03, 0x71, 0xb4, 0x98, 0x33, 0x36, 0x7a, 0x9f, - 0xa5, 0xda, 0xd9, 0x8a, 0xc4, 0x01, 0x3e, 0xa3, 0xcb, 0xe2, 0x1c, 0xf0, 0xc1, 0x3c, 0xbb, 0x13, - 0x6f, 0x24, 0xde, 0x17, 0x14, 0x7c, 0x0f, 0xa3, 0xa5, 0x2d, 0xc8, 0x14, 0x74, 0xf0, 0xd6, 0xa1, - 0x58, 0xbb, 0x24, 0xff, 0x03, 0x8b, 0xb2, 0x14, 0x7d, 0x24, 0x7a, 0x03, 0x71, 0x06, 0x63, 0x7e, - 0x85, 0x32, 0x74, 0xd8, 0x3b, 0x9f, 0x70, 0x29, 0x8e, 0x53, 0x1a, 0x13, 0x18, 0x7e, 0xff, 0x93, - 0xa5, 0xa6, 0xd8, 0x1a, 0xb1, 0x8d, 0xc5, 0xd6, 0xe1, 0x38, 0xed, 0xb1, 0xb2, 0xa5, 0x25, 0x53, - 0x2d, 0xd2, 0xb4, 0x32, 0x44, 0xf1, 0xc4, 0xa7, 0x9d, 0xd0, 0xc9, 0x75, 0xbb, 0x3e, 0xbc, 0xd6, - 0x32, 0x69, 0xd9, 0xa3, 0x50, 0x79, 0x80, 0x13, 0x38, 0x5b, 0x94, 0xa5, 0x2c, 0x4c, 0xa8, 0xf8, - 0x98, 0x7c, 0x85, 0x41, 0xbb, 0x00, 0xfc, 0x22, 0x32, 0x5b, 0x5b, 0xa4, 0x24, 0x65, 0x13, 0xd5, - 0x40, 0x8e, 0x2b, 0x74, 0x61, 0x49, 0x4a, 0xc7, 0xca, 0x83, 0xa7, 0xfa, 0xa3, 0xfa, 0x17, 0x00, - 0x00, 0xff, 0xff, 0xd5, 0x8b, 0x28, 0x26, 0x6a, 0x03, 0x00, 0x00, + 0xa9, 0x45, 0x2d, 0xad, 0xd0, 0xac, 0x4b, 0xde, 0xb0, 0xaf, 0x55, 0x66, 0x56, 0x52, 0x23, 0x93, + 0x93, 0xf7, 0xfb, 0x99, 0x6f, 0x76, 0xc7, 0x23, 0x88, 0x1e, 0x0f, 0x76, 0xf7, 0x7b, 0x51, 0x56, + 0xd6, 0x59, 0x1c, 0xc8, 0xcf, 0x4d, 0xf2, 0x05, 0x26, 0x0f, 0xba, 0x72, 0x3f, 0x8d, 0xbb, 0x35, + 0x3a, 0x35, 0x15, 0xbe, 0x86, 0xfe, 0xc6, 0x3a, 0x7d, 0x88, 0x83, 0x59, 0x30, 0xbf, 0x54, 0x1e, + 0x20, 0x42, 0x78, 0xab, 0x69, 0x1f, 0xbf, 0x9a, 0x05, 0xf3, 0xb1, 0x92, 0x73, 0xb2, 0x85, 0xe1, + 0x37, 0x4e, 0xbc, 0x5b, 0xb7, 0x72, 0xf0, 0x5f, 0xc6, 0xcf, 0x10, 0x71, 0x32, 0xf9, 0x5c, 0xa9, + 0x8c, 0xae, 0xdf, 0xf8, 0xf6, 0x37, 0x8b, 0x4e, 0x53, 0xf5, 0xdc, 0x99, 0xfc, 0x0d, 0x61, 0x50, + 0x5f, 0xe6, 0x13, 0x0c, 0xb7, 0xa6, 0xa2, 0xcc, 0x16, 0x12, 0x1d, 0x5d, 0x4f, 0x9b, 0xfa, 0x9a, + 0x56, 0x8d, 0x8e, 0x31, 0x0c, 0x57, 0x7b, 0x9d, 0x15, 0x77, 0x6b, 0x69, 0x35, 0x52, 0x0d, 0xc4, + 0xb7, 0x1c, 0x97, 0xfd, 0xda, 0xbb, 0xb8, 0x37, 0x0b, 0xe6, 0xa8, 0x6a, 0x84, 0x1f, 0x20, 0xdc, + 0x64, 0xb9, 0x89, 0x43, 0x49, 0xbe, 0x6c, 0x92, 0x99, 0x23, 0xa7, 0xf3, 0x52, 0x89, 0xcc, 0xe5, + 0xf7, 0xc7, 0x7c, 0xf3, 0x44, 0x71, 0xdf, 0x97, 0x7b, 0x84, 0xef, 0xe0, 0x4c, 0x66, 0xc3, 0xca, + 0x40, 0x94, 0x16, 0xe3, 0x15, 0x44, 0x3f, 0x34, 0xb9, 0x7a, 0x3c, 0xf1, 0xb0, 0x7b, 0xf7, 0x9a, + 0x56, 0xcf, 0x3d, 0xf8, 0x11, 0xce, 0x19, 0xae, 0x6c, 0x9e, 0x67, 0x4e, 0x86, 0x79, 0x26, 0xc3, + 0x3c, 0x61, 0xb9, 0xed, 0x5a, 0x3b, 0x2d, 0x8e, 0x91, 0x38, 0x5a, 0xcc, 0x19, 0x5b, 0x7d, 0xc8, + 0x52, 0xed, 0x6c, 0x45, 0xe2, 0x00, 0x9f, 0xd1, 0x65, 0x71, 0x01, 0x78, 0x6f, 0x9e, 0xdc, 0x89, + 0x37, 0x12, 0xef, 0x0b, 0x0a, 0xbe, 0x87, 0xc9, 0xca, 0x16, 0x64, 0x0a, 0x3a, 0x7a, 0xeb, 0x58, + 0xac, 0x5d, 0x92, 0xff, 0x81, 0x65, 0x59, 0x8a, 0x3e, 0x11, 0xbd, 0x81, 0x38, 0x87, 0x29, 0xbf, + 0x42, 0x19, 0x3a, 0x1e, 0x9c, 0x4f, 0x38, 0x17, 0xc7, 0x29, 0x8d, 0x09, 0x8c, 0xbf, 0xff, 0xc9, + 0x52, 0x53, 0xec, 0x8c, 0xd8, 0xa6, 0x62, 0xeb, 0x70, 0x9c, 0xf6, 0x50, 0xd9, 0xd2, 0x92, 0xa9, + 0x96, 0x69, 0x5a, 0x19, 0xa2, 0xf8, 0xc2, 0xa7, 0x9d, 0xd0, 0xc9, 0x55, 0xbb, 0x3e, 0xbc, 0xd6, + 0x32, 0x69, 0xd9, 0xa3, 0x50, 0x79, 0x80, 0x17, 0xd0, 0x5b, 0x96, 0xa5, 0x2c, 0x4c, 0xa8, 0xf8, + 0x98, 0x7c, 0x85, 0x51, 0xbb, 0x00, 0xfc, 0x22, 0x32, 0x3b, 0x5b, 0xa4, 0x24, 0x65, 0x3d, 0xd5, + 0x40, 0x8e, 0x2b, 0x74, 0x61, 0x49, 0x4a, 0xfb, 0xca, 0x83, 0xc7, 0xfa, 0xa3, 0xfa, 0x17, 0x00, + 0x00, 0xff, 0xff, 0x8f, 0x82, 0xc0, 0x0c, 0x6a, 0x03, 0x00, 0x00, } diff --git a/types/proto3/block.proto b/types/proto3/block.proto index dd64a9e9..1c76746c 100644 --- a/types/proto3/block.proto +++ b/types/proto3/block.proto @@ -46,11 +46,12 @@ message Version { uint64 App = 2; } -// Timestamp wraps how amino encodes time. Note that this is different from the protobuf well-known type -// protobuf/timestamp.proto in the sense that there seconds and nanos are varint encoded. See: +// Timestamp wraps how amino encodes time. +// This is the protobuf well-known type protobuf/timestamp.proto +// See: // https://github.com/google/protobuf/blob/d2980062c859649523d5fd51d6b55ab310e47482/src/google/protobuf/timestamp.proto#L123-L135 -// Also nanos do not get skipped if they are zero in amino. +// NOTE/XXX: nanos do not get skipped if they are zero in amino. message Timestamp { - sfixed64 seconds = 1; - sfixed32 nanos = 2; + int64 seconds = 1; + int32 nanos = 2; } diff --git a/types/protobuf.go b/types/protobuf.go index c9c429c8..e1ec81e8 100644 --- a/types/protobuf.go +++ b/types/protobuf.go @@ -34,6 +34,10 @@ type tm2pb struct{} func (tm2pb) Header(header *Header) abci.Header { return abci.Header{ + Version: abci.Version{ + Block: header.Version.Block.Uint64(), + App: header.Version.App.Uint64(), + }, ChainID: header.ChainID, Height: header.Height, Time: header.Time, @@ -45,10 +49,11 @@ func (tm2pb) Header(header *Header) abci.Header { LastCommitHash: header.LastCommitHash, DataHash: header.DataHash, - ValidatorsHash: header.ValidatorsHash, - ConsensusHash: header.ConsensusHash, - AppHash: header.AppHash, - LastResultsHash: header.LastResultsHash, + ValidatorsHash: header.ValidatorsHash, + NextValidatorsHash: header.NextValidatorsHash, + ConsensusHash: header.ConsensusHash, + AppHash: header.AppHash, + LastResultsHash: header.LastResultsHash, EvidenceHash: header.EvidenceHash, ProposerAddress: header.ProposerAddress, diff --git a/types/protobuf_test.go b/types/protobuf_test.go index c940f1b4..7e7f55a1 100644 --- a/types/protobuf_test.go +++ b/types/protobuf_test.go @@ -4,12 +4,16 @@ import ( "testing" "time" + "github.com/golang/protobuf/proto" "github.com/stretchr/testify/assert" + + "github.com/tendermint/go-amino" + abci "github.com/tendermint/tendermint/abci/types" "github.com/tendermint/tendermint/crypto" "github.com/tendermint/tendermint/crypto/ed25519" "github.com/tendermint/tendermint/crypto/secp256k1" - tmtime "github.com/tendermint/tendermint/types/time" + "github.com/tendermint/tendermint/version" ) func TestABCIPubKey(t *testing.T) { @@ -67,17 +71,71 @@ func TestABCIConsensusParams(t *testing.T) { assert.Equal(t, *cp, cp2) } -func TestABCIHeader(t *testing.T) { - header := &Header{ - Height: int64(3), - Time: tmtime.Now(), - NumTxs: int64(10), - ProposerAddress: []byte("cloak"), +func newHeader( + height, numTxs int64, + commitHash, dataHash, evidenceHash []byte, +) *Header { + return &Header{ + Height: height, + NumTxs: numTxs, + LastCommitHash: commitHash, + DataHash: dataHash, + EvidenceHash: evidenceHash, } - abciHeader := TM2PB.Header(header) +} + +func TestABCIHeader(t *testing.T) { + // build a full header + var height int64 = 5 + var numTxs int64 = 3 + header := newHeader( + height, numTxs, + []byte("lastCommitHash"), []byte("dataHash"), []byte("evidenceHash"), + ) + protocolVersion := version.Consensus{7, 8} + timestamp := time.Now() + lastBlockID := BlockID{ + Hash: []byte("hash"), + PartsHeader: PartSetHeader{ + Total: 10, + Hash: []byte("hash"), + }, + } + var totalTxs int64 = 100 + header.Populate( + protocolVersion, "chainID", + timestamp, lastBlockID, totalTxs, + []byte("valHash"), []byte("nextValHash"), + []byte("consHash"), []byte("appHash"), []byte("lastResultsHash"), + []byte("proposerAddress"), + ) + + cdc := amino.NewCodec() + headerBz := cdc.MustMarshalBinaryBare(header) + + pbHeader := TM2PB.Header(header) + pbHeaderBz, err := proto.Marshal(&pbHeader) + assert.NoError(t, err) + + // assert some fields match + assert.EqualValues(t, protocolVersion.Block, pbHeader.Version.Block) + assert.EqualValues(t, protocolVersion.App, pbHeader.Version.App) + assert.EqualValues(t, "chainID", pbHeader.ChainID) + assert.EqualValues(t, height, pbHeader.Height) + assert.EqualValues(t, timestamp, pbHeader.Time) + assert.EqualValues(t, numTxs, pbHeader.NumTxs) + assert.EqualValues(t, totalTxs, pbHeader.TotalTxs) + assert.EqualValues(t, lastBlockID.Hash, pbHeader.LastBlockId.Hash) + assert.EqualValues(t, []byte("lastCommitHash"), pbHeader.LastCommitHash) + assert.Equal(t, []byte("proposerAddress"), pbHeader.ProposerAddress) + + // assert the encodings match + // NOTE: they don't yet because Amino encodes + // int64 as zig-zag and we're using non-zigzag in the protobuf. + // See https://github.com/tendermint/tendermint/issues/2682 + _, _ = headerBz, pbHeaderBz + // assert.EqualValues(t, headerBz, pbHeaderBz) - assert.Equal(t, int64(3), abciHeader.Height) - assert.Equal(t, []byte("cloak"), abciHeader.ProposerAddress) } func TestABCIEvidence(t *testing.T) { diff --git a/types/results_test.go b/types/results_test.go index 80803385..4e57e580 100644 --- a/types/results_test.go +++ b/types/results_test.go @@ -43,7 +43,7 @@ func TestABCIResults(t *testing.T) { } } -func TestABCIBytes(t *testing.T) { +func TestABCIResultsBytes(t *testing.T) { results := NewResults([]*abci.ResponseDeliverTx{ {Code: 0, Data: []byte{}}, {Code: 0, Data: []byte("one")}, diff --git a/types/vote.go b/types/vote.go index 2d70e21b..2a713309 100644 --- a/types/vote.go +++ b/types/vote.go @@ -12,7 +12,7 @@ import ( const ( // MaxVoteBytes is a maximum vote size (including amino overhead). - MaxVoteBytes int64 = 200 + MaxVoteBytes int64 = 203 ) var ( diff --git a/types/vote_test.go b/types/vote_test.go index 2172f060..3b2f0848 100644 --- a/types/vote_test.go +++ b/types/vote_test.go @@ -9,7 +9,6 @@ import ( "github.com/stretchr/testify/require" "github.com/tendermint/tendermint/crypto/ed25519" "github.com/tendermint/tendermint/crypto/tmhash" - tmtime "github.com/tendermint/tendermint/types/time" ) func examplePrevote() *Vote { @@ -63,13 +62,13 @@ func TestVoteSignableTestVectors(t *testing.T) { { CanonicalizeVote("", &Vote{}), // NOTE: Height and Round are skipped here. This case needs to be considered while parsing. - []byte{0xb, 0x22, 0x9, 0x9, 0x0, 0x9, 0x6e, 0x88, 0xf1, 0xff, 0xff, 0xff}, + // []byte{0x22, 0x9, 0x9, 0x0, 0x9, 0x6e, 0x88, 0xf1, 0xff, 0xff, 0xff}, + []byte{0x22, 0xb, 0x8, 0x80, 0x92, 0xb8, 0xc3, 0x98, 0xfe, 0xff, 0xff, 0xff, 0x1}, }, // with proper (fixed size) height and round (PreCommit): { CanonicalizeVote("", &Vote{Height: 1, Round: 1, Type: PrecommitType}), []byte{ - 0x1f, // total length 0x8, // (field_number << 3) | wire_type 0x2, // PrecommitType 0x11, // (field_number << 3) | wire_type @@ -78,13 +77,12 @@ func TestVoteSignableTestVectors(t *testing.T) { 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, // round 0x22, // (field_number << 3) | wire_type // remaining fields (timestamp): - 0x9, 0x9, 0x0, 0x9, 0x6e, 0x88, 0xf1, 0xff, 0xff, 0xff}, + 0xb, 0x8, 0x80, 0x92, 0xb8, 0xc3, 0x98, 0xfe, 0xff, 0xff, 0xff, 0x1}, }, // with proper (fixed size) height and round (PreVote): { CanonicalizeVote("", &Vote{Height: 1, Round: 1, Type: PrevoteType}), []byte{ - 0x1f, // total length 0x8, // (field_number << 3) | wire_type 0x1, // PrevoteType 0x11, // (field_number << 3) | wire_type @@ -93,38 +91,36 @@ func TestVoteSignableTestVectors(t *testing.T) { 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, // round 0x22, // (field_number << 3) | wire_type // remaining fields (timestamp): - 0x9, 0x9, 0x0, 0x9, 0x6e, 0x88, 0xf1, 0xff, 0xff, 0xff}, + 0xb, 0x8, 0x80, 0x92, 0xb8, 0xc3, 0x98, 0xfe, 0xff, 0xff, 0xff, 0x1}, }, { vote, []byte{ - 0x1d, // total length 0x11, // (field_number << 3) | wire_type 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, // height 0x19, // (field_number << 3) | wire_type 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, // round // remaining fields (timestamp): 0x22, - 0x9, 0x9, 0x0, 0x9, 0x6e, 0x88, 0xf1, 0xff, 0xff, 0xff}, + 0xb, 0x8, 0x80, 0x92, 0xb8, 0xc3, 0x98, 0xfe, 0xff, 0xff, 0xff, 0x1}, }, // containing non-empty chain_id: { CanonicalizeVote("test_chain_id", &Vote{Height: 1, Round: 1}), []byte{ - 0x2c, // total length 0x11, // (field_number << 3) | wire_type 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, // height 0x19, // (field_number << 3) | wire_type 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, // round // remaining fields: - 0x22, // (field_number << 3) | wire_type - 0x9, 0x9, 0x0, 0x9, 0x6e, 0x88, 0xf1, 0xff, 0xff, 0xff, // timestamp + 0x22, // (field_number << 3) | wire_type + 0xb, 0x8, 0x80, 0x92, 0xb8, 0xc3, 0x98, 0xfe, 0xff, 0xff, 0xff, 0x1, // timestamp 0x32, // (field_number << 3) | wire_type 0xd, 0x74, 0x65, 0x73, 0x74, 0x5f, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x5f, 0x69, 0x64}, // chainID }, } for i, tc := range tests { - got, err := cdc.MarshalBinary(tc.canonicalVote) + got, err := cdc.MarshalBinaryBare(tc.canonicalVote) require.NoError(t, err) require.Equal(t, tc.want, got, "test case #%v: got unexpected sign bytes for Vote.", i) @@ -210,12 +206,16 @@ func TestVoteVerify(t *testing.T) { } func TestMaxVoteBytes(t *testing.T) { + // time is varint encoded so need to pick the max. + // year int, month Month, day, hour, min, sec, nsec int, loc *Location + timestamp := time.Date(math.MaxInt64, 0, 0, 0, 0, 0, math.MaxInt64, time.UTC) + vote := &Vote{ ValidatorAddress: tmhash.Sum([]byte("validator_address")), ValidatorIndex: math.MaxInt64, Height: math.MaxInt64, Round: math.MaxInt64, - Timestamp: tmtime.Now(), + Timestamp: timestamp, Type: PrevoteType, BlockID: BlockID{ Hash: tmhash.Sum([]byte("blockID_hash")), From 9795e12ef2eae822733e5add1300bdd37caf5d7c Mon Sep 17 00:00:00 2001 From: Jun Kimura Date: Wed, 24 Oct 2018 17:07:33 +0900 Subject: [PATCH 034/267] fix `RecoverAndLogHandler` not to call multiple writeheader (#2688) --- rpc/lib/server/http_server.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/rpc/lib/server/http_server.go b/rpc/lib/server/http_server.go index 8069a81d..6de376c2 100644 --- a/rpc/lib/server/http_server.go +++ b/rpc/lib/server/http_server.go @@ -173,8 +173,7 @@ func RecoverAndLogHandler(handler http.Handler, logger log.Logger) http.Handler "Panic in RPC HTTP handler", "err", e, "stack", string(debug.Stack()), ) - rww.WriteHeader(http.StatusInternalServerError) - WriteRPCResponseHTTP(rww, types.RPCInternalError("", e.(error))) + WriteRPCResponseHTTPError(rww, http.StatusInternalServerError, types.RPCInternalError("", e.(error))) } } From 6643c5dd1151fdee803eb1a27e54b5dd81c65be5 Mon Sep 17 00:00:00 2001 From: Ismail Khoffi Date: Thu, 25 Oct 2018 03:34:01 +0200 Subject: [PATCH 035/267] Catch up with amino 0.13.0 (#2690) * catch up with amino changes in https://github.com/tendermint/go-amino/pull/222 * WIP: update to amino v0.13.0 * update to fixed amino release --- Gopkg.lock | 72 ++++++++++++++--------------- Gopkg.toml | 2 +- blockchain/store.go | 2 +- consensus/reactor.go | 6 +-- consensus/replay_test.go | 4 +- consensus/state.go | 2 +- crypto/merkle/proof_simple_value.go | 4 +- libs/common/errors_test.go | 2 +- lite/dbprovider.go | 10 ++-- p2p/conn/connection.go | 10 ++-- p2p/conn/connection_test.go | 38 +++++++-------- p2p/conn/secret_connection.go | 8 ++-- p2p/metrics.go | 2 +- p2p/transport.go | 4 +- p2p/transport_test.go | 4 +- privval/priv_validator.go | 12 ++--- privval/remote_signer.go | 4 +- types/block.go | 2 +- types/block_test.go | 2 +- types/evidence_test.go | 2 +- types/heartbeat.go | 2 +- types/heartbeat_test.go | 4 +- types/proposal.go | 2 +- types/proposal_test.go | 6 +-- types/results.go | 2 +- types/tx_test.go | 8 ++-- types/validator_set_test.go | 4 +- types/vote.go | 2 +- types/vote_test.go | 12 ++--- 29 files changed, 117 insertions(+), 117 deletions(-) diff --git a/Gopkg.lock b/Gopkg.lock index d5d6c1b2..566fed4a 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -11,11 +11,11 @@ [[projects]] branch = "master" - digest = "1:2c00f064ba355903866cbfbf3f7f4c0fe64af6638cc7d1b8bdcf3181bc67f1d8" + digest = "1:c0decf632843204d2b8781de7b26e7038584e2dcccc7e2f401e88ae85b1df2b7" name = "github.com/btcsuite/btcd" packages = ["btcec"] pruneopts = "UT" - revision = "f5e261fc9ec3437697fb31d8b38453c293204b29" + revision = "67e573d211ace594f1366b4ce9d39726c4b19bd0" [[projects]] digest = "1:1d8e1cb71c33a9470bbbae09bfec09db43c6bf358dfcae13cd8807c4e2a9a2bf" @@ -28,12 +28,12 @@ revision = "d4cc87b860166d00d6b5b9e0d3b3d71d6088d4d4" [[projects]] - digest = "1:a2c1d0e43bd3baaa071d1b9ed72c27d78169b2b269f71c105ac4ba34b1be4a39" + digest = "1:ffe9824d294da03b391f44e1ae8281281b4afc1bdaa9588c9097785e3af10cec" name = "github.com/davecgh/go-spew" packages = ["spew"] pruneopts = "UT" - revision = "346938d642f2ec3594ed81d874461961cd0faa76" - version = "v1.1.0" + revision = "8991bc29aa16c548c550c7ff78260e27b9ab7c73" + version = "v1.1.1" [[projects]] digest = "1:c7644c73a3d23741fdba8a99b1464e021a224b7e205be497271a8003a15ca41b" @@ -83,12 +83,12 @@ version = "v0.3.0" [[projects]] - digest = "1:c4a2528ccbcabf90f9f3c464a5fc9e302d592861bbfd0b7135a7de8a943d0406" + digest = "1:586ea76dbd0374d6fb649a91d70d652b7fe0ccffb8910a77468e7702e7901f3d" name = "github.com/go-stack/stack" packages = ["."] pruneopts = "UT" - revision = "259ab82a6cad3992b4e21ff5cac294ccb06474bc" - version = "v1.7.0" + revision = "2fee6af1a9795aafbe0253a0cfbdf668e1fb8a9a" + version = "v1.8.0" [[projects]] digest = "1:35621fe20f140f05a0c4ef662c26c0ab4ee50bca78aa30fe87d33120bd28165e" @@ -136,8 +136,7 @@ version = "v1.2.0" [[projects]] - branch = "master" - digest = "1:12247a2e99a060cc692f6680e5272c8adf0b8f572e6bce0d7095e624c958a240" + digest = "1:ea40c24cdbacd054a6ae9de03e62c5f252479b96c716375aace5c120d68647c8" name = "github.com/hashicorp/hcl" packages = [ ".", @@ -151,7 +150,8 @@ "json/token", ] pruneopts = "UT" - revision = "ef8a98b0bbce4a65b5aa4c368430a80ddc533168" + revision = "8cb6e5b959231cc1119e43259c4a608f9c51a241" + version = "v1.0.0" [[projects]] digest = "1:870d441fe217b8e689d7949fef6e43efbc787e50f200cb1e70dbca9204a1d6be" @@ -193,12 +193,12 @@ version = "v1.0.1" [[projects]] - branch = "master" - digest = "1:5ab79470a1d0fb19b041a624415612f8236b3c06070161a910562f2b2d064355" + digest = "1:53bc4cd4914cd7cd52139990d5170d6dc99067ae31c56530621b18b35fc30318" name = "github.com/mitchellh/mapstructure" packages = ["."] pruneopts = "UT" - revision = "f15292f7a699fcc1a38a80977f80a046874ba8ac" + revision = "3536a929edddb9a5b34bd6861dc4a9647cb459fe" + version = "v1.1.2" [[projects]] digest = "1:95741de3af260a92cc5c7f3f3061e85273f5a81b5db20d4bd68da74bd521675e" @@ -244,7 +244,7 @@ [[projects]] branch = "master" - digest = "1:63b68062b8968092eb86bedc4e68894bd096ea6b24920faca8b9dcf451f54bb5" + digest = "1:db712fde5d12d6cdbdf14b777f0c230f4ff5ab0be8e35b239fc319953ed577a4" name = "github.com/prometheus/common" packages = [ "expfmt", @@ -252,11 +252,11 @@ "model", ] pruneopts = "UT" - revision = "c7de2306084e37d54b8be01f3541a8464345e9a5" + revision = "7e9e6cabbd393fc208072eedef99188d0ce788b6" [[projects]] branch = "master" - digest = "1:8c49953a1414305f2ff5465147ee576dd705487c35b15918fcd4efdc0cb7a290" + digest = "1:ef74914912f99c79434d9c09658274678bc85080ebe3ab32bec3940ebce5e1fc" name = "github.com/prometheus/procfs" packages = [ ".", @@ -265,7 +265,7 @@ "xfs", ] pruneopts = "UT" - revision = "05ee40e3a273f7245e8777337fc7b46e533a9a92" + revision = "185b4288413d2a0dd0806f78c90dde719829e5ae" [[projects]] digest = "1:c4556a44e350b50a490544d9b06e9fba9c286c21d6c0e47f54f3a9214597298c" @@ -275,15 +275,15 @@ revision = "e2704e165165ec55d062f5919b4b29494e9fa790" [[projects]] - digest = "1:bd1ae00087d17c5a748660b8e89e1043e1e5479d0fea743352cda2f8dd8c4f84" + digest = "1:6a4a11ba764a56d2758899ec6f3848d24698d48442ebce85ee7a3f63284526cd" name = "github.com/spf13/afero" packages = [ ".", "mem", ] pruneopts = "UT" - revision = "787d034dfe70e44075ccc060d346146ef53270ad" - version = "v1.1.1" + revision = "d40851caa0d747393da1ffb28f7f9d8b4eeffebd" + version = "v1.1.2" [[projects]] digest = "1:516e71bed754268937f57d4ecb190e01958452336fa73dbac880894164e91c1f" @@ -302,20 +302,20 @@ version = "v0.0.1" [[projects]] - branch = "master" - digest = "1:080e5f630945ad754f4b920e60b4d3095ba0237ebf88dc462eb28002932e3805" + digest = "1:68ea4e23713989dc20b1bded5d9da2c5f9be14ff9885beef481848edd18c26cb" name = "github.com/spf13/jwalterweatherman" packages = ["."] pruneopts = "UT" - revision = "7c0cea34c8ece3fbeb2b27ab9b59511d360fb394" + revision = "4a4406e478ca629068e7768fc33f3f044173c0a6" + version = "v1.0.0" [[projects]] - digest = "1:9424f440bba8f7508b69414634aef3b2b3a877e522d8a4624692412805407bb7" + digest = "1:c1b1102241e7f645bc8e0c22ae352e8f0dc6484b6cb4d132fa9f24174e0119e2" name = "github.com/spf13/pflag" packages = ["."] pruneopts = "UT" - revision = "583c0c0531f06d5278b7d917446061adc344b5cd" - version = "v1.0.1" + revision = "298182f68c66c05229eb03ac171abe6e309ee79a" + version = "v1.0.3" [[projects]] digest = "1:f8e1a678a2571e265f4bf91a3e5e32aa6b1474a55cb0ea849750cc177b664d96" @@ -338,7 +338,7 @@ [[projects]] branch = "master" - digest = "1:b3cfb8d82b1601a846417c3f31c03a7961862cb2c98dcf0959c473843e6d9a2b" + digest = "1:59483b8e8183f10ab21a85ba1f4cbb4a2335d48891801f79ed7b9499f44d383c" name = "github.com/syndtr/goleveldb" packages = [ "leveldb", @@ -355,7 +355,7 @@ "leveldb/util", ] pruneopts = "UT" - revision = "c4c61651e9e37fa117f53c5a906d3b63090d8445" + revision = "6b91fda63f2e36186f1c9d0e48578defb69c5d43" [[projects]] digest = "1:605b6546f3f43745695298ec2d342d3e952b6d91cdf9f349bea9315f677d759f" @@ -365,12 +365,12 @@ revision = "e5840949ff4fff0c56f9b6a541e22b63581ea9df" [[projects]] - digest = "1:3ff2c9d4def5ec999ab672b9059d0ba41a1351913ea78e63b5402e4ba4ef8da4" + digest = "1:5f52e817b6c9d52ddba70dece0ea31134d82a52c05bce98fbc739ab2a832df28" name = "github.com/tendermint/go-amino" packages = ["."] pruneopts = "UT" - revision = "ff047d9e357e66d937d6900d4a2e04501cc62c70" - version = "v0.13.0-rc0" + revision = "cb07448b240918aa8d8df4505153549b86b77134" + version = "v0.13.0" [[projects]] digest = "1:72b71e3a29775e5752ed7a8012052a3dee165e27ec18cedddae5288058f09acf" @@ -415,14 +415,14 @@ [[projects]] branch = "master" - digest = "1:bb0fe59917bdd5b89f49b9a8b26e5f465e325d9223b3a8e32254314bdf51e0f1" + digest = "1:d1da39c9bac61327dbef1d8ef9f210425e99fd2924b6fb5f0bc587a193353637" name = "golang.org/x/sys" packages = [ "cpu", "unix", ] pruneopts = "UT" - revision = "3dc4335d56c789b04b0ba99b7a37249d9b614314" + revision = "8a28ead16f52c8aaeffbf79239b251dfdf6c4f96" [[projects]] digest = "1:a2ab62866c75542dd18d2b069fec854577a20211d7c0ea6ae746072a1dccdd18" @@ -449,11 +449,11 @@ [[projects]] branch = "master" - digest = "1:077c1c599507b3b3e9156d17d36e1e61928ee9b53a5b420f10f28ebd4a0b275c" + digest = "1:56b0bca90b7e5d1facf5fbdacba23e4e0ce069d25381b8e2f70ef1e7ebfb9c1a" name = "google.golang.org/genproto" packages = ["googleapis/rpc/status"] pruneopts = "UT" - revision = "daca94659cb50e9f37c1b834680f2e46358f10b0" + revision = "94acd270e44e65579b9ee3cdab25034d33fed608" [[projects]] digest = "1:2dab32a43451e320e49608ff4542fdfc653c95dcc35d0065ec9c6c3dd540ed74" diff --git a/Gopkg.toml b/Gopkg.toml index 622ca00e..e24965dc 100644 --- a/Gopkg.toml +++ b/Gopkg.toml @@ -58,7 +58,7 @@ [[constraint]] name = "github.com/tendermint/go-amino" - version = "v0.13.0-rc0" + version = "v0.13.0" [[constraint]] name = "google.golang.org/grpc" diff --git a/blockchain/store.go b/blockchain/store.go index fa9ee518..498cca68 100644 --- a/blockchain/store.go +++ b/blockchain/store.go @@ -63,7 +63,7 @@ func (bs *BlockStore) LoadBlock(height int64) *types.Block { part := bs.LoadBlockPart(height, i) buf = append(buf, part.Bytes...) } - err := cdc.UnmarshalBinary(buf, block) + err := cdc.UnmarshalBinaryLengthPrefixed(buf, block) if err != nil { // NOTE: The existence of meta should imply the existence of the // block. So, make sure meta is only saved after blocks are saved. diff --git a/consensus/reactor.go b/consensus/reactor.go index bcf77fb3..6643273c 100644 --- a/consensus/reactor.go +++ b/consensus/reactor.go @@ -429,9 +429,9 @@ func (conR *ConsensusReactor) broadcastHasVoteMessage(vote *types.Vote) { func makeRoundStepMessages(rs *cstypes.RoundState) (nrsMsg *NewRoundStepMessage, csMsg *CommitStepMessage) { nrsMsg = &NewRoundStepMessage{ - Height: rs.Height, - Round: rs.Round, - Step: rs.Step, + Height: rs.Height, + Round: rs.Round, + Step: rs.Step, SecondsSinceStartTime: int(time.Since(rs.StartTime).Seconds()), LastCommitRound: rs.LastCommit.Round(), } diff --git a/consensus/replay_test.go b/consensus/replay_test.go index 4e1fa2b7..d6691103 100644 --- a/consensus/replay_test.go +++ b/consensus/replay_test.go @@ -520,7 +520,7 @@ func makeBlockchainFromWAL(wal WAL) ([]*types.Block, []*types.Commit, error) { // if its not the first one, we have a full block if thisBlockParts != nil { var block = new(types.Block) - _, err = cdc.UnmarshalBinaryReader(thisBlockParts.GetReader(), block, 0) + _, err = cdc.UnmarshalBinaryLengthPrefixedReader(thisBlockParts.GetReader(), block, 0) if err != nil { panic(err) } @@ -553,7 +553,7 @@ func makeBlockchainFromWAL(wal WAL) ([]*types.Block, []*types.Commit, error) { } // grab the last block too var block = new(types.Block) - _, err = cdc.UnmarshalBinaryReader(thisBlockParts.GetReader(), block, 0) + _, err = cdc.UnmarshalBinaryLengthPrefixedReader(thisBlockParts.GetReader(), block, 0) if err != nil { panic(err) } diff --git a/consensus/state.go b/consensus/state.go index 37567400..0b079f13 100644 --- a/consensus/state.go +++ b/consensus/state.go @@ -1468,7 +1468,7 @@ func (cs *ConsensusState) addProposalBlockPart(msg *BlockPartMessage, peerID p2p } if added && cs.ProposalBlockParts.IsComplete() { // Added and completed! - _, err = cdc.UnmarshalBinaryReader( + _, err = cdc.UnmarshalBinaryLengthPrefixedReader( cs.ProposalBlockParts.GetReader(), &cs.ProposalBlock, int64(cs.state.ConsensusParams.BlockSize.MaxBytes), diff --git a/crypto/merkle/proof_simple_value.go b/crypto/merkle/proof_simple_value.go index 5b7b5232..904b6e5e 100644 --- a/crypto/merkle/proof_simple_value.go +++ b/crypto/merkle/proof_simple_value.go @@ -42,7 +42,7 @@ func SimpleValueOpDecoder(pop ProofOp) (ProofOperator, error) { return nil, cmn.NewError("unexpected ProofOp.Type; got %v, want %v", pop.Type, ProofOpSimpleValue) } var op SimpleValueOp // a bit strange as we'll discard this, but it works. - err := cdc.UnmarshalBinary(pop.Data, &op) + err := cdc.UnmarshalBinaryLengthPrefixed(pop.Data, &op) if err != nil { return nil, cmn.ErrorWrap(err, "decoding ProofOp.Data into SimpleValueOp") } @@ -50,7 +50,7 @@ func SimpleValueOpDecoder(pop ProofOp) (ProofOperator, error) { } func (op SimpleValueOp) ProofOp() ProofOp { - bz := cdc.MustMarshalBinary(op) + bz := cdc.MustMarshalBinaryLengthPrefixed(op) return ProofOp{ Type: ProofOpSimpleValue, Key: op.key, diff --git a/libs/common/errors_test.go b/libs/common/errors_test.go index 326468c9..b85936dd 100644 --- a/libs/common/errors_test.go +++ b/libs/common/errors_test.go @@ -24,7 +24,7 @@ func TestErrorPanic(t *testing.T) { var err = capturePanic() assert.Equal(t, pnk{"something"}, err.Data()) -assert.Equal(t, "{something}", fmt.Sprintf("%v", err)) + assert.Equal(t, "{something}", fmt.Sprintf("%v", err)) assert.Contains(t, fmt.Sprintf("%#v", err), "This is the message in ErrorWrap(r, message).") assert.Contains(t, fmt.Sprintf("%#v", err), "Stack Trace:\n 0") } diff --git a/lite/dbprovider.go b/lite/dbprovider.go index cab695b4..e0c4e65b 100644 --- a/lite/dbprovider.go +++ b/lite/dbprovider.go @@ -56,7 +56,7 @@ func (dbp *DBProvider) SaveFullCommit(fc FullCommit) error { // We might be overwriting what we already have, but // it makes the logic easier for now. vsKey := validatorSetKey(fc.ChainID(), fc.Height()) - vsBz, err := dbp.cdc.MarshalBinary(fc.Validators) + vsBz, err := dbp.cdc.MarshalBinaryLengthPrefixed(fc.Validators) if err != nil { return err } @@ -64,7 +64,7 @@ func (dbp *DBProvider) SaveFullCommit(fc FullCommit) error { // Save the fc.NextValidators. nvsKey := validatorSetKey(fc.ChainID(), fc.Height()+1) - nvsBz, err := dbp.cdc.MarshalBinary(fc.NextValidators) + nvsBz, err := dbp.cdc.MarshalBinaryLengthPrefixed(fc.NextValidators) if err != nil { return err } @@ -72,7 +72,7 @@ func (dbp *DBProvider) SaveFullCommit(fc FullCommit) error { // Save the fc.SignedHeader shKey := signedHeaderKey(fc.ChainID(), fc.Height()) - shBz, err := dbp.cdc.MarshalBinary(fc.SignedHeader) + shBz, err := dbp.cdc.MarshalBinaryLengthPrefixed(fc.SignedHeader) if err != nil { return err } @@ -121,7 +121,7 @@ func (dbp *DBProvider) LatestFullCommit(chainID string, minHeight, maxHeight int // Found the latest full commit signed header. shBz := itr.Value() sh := types.SignedHeader{} - err := dbp.cdc.UnmarshalBinary(shBz, &sh) + err := dbp.cdc.UnmarshalBinaryLengthPrefixed(shBz, &sh) if err != nil { return FullCommit{}, err } else { @@ -150,7 +150,7 @@ func (dbp *DBProvider) getValidatorSet(chainID string, height int64) (valset *ty err = lerr.ErrUnknownValidators(chainID, height) return } - err = dbp.cdc.UnmarshalBinary(vsBz, &valset) + err = dbp.cdc.UnmarshalBinaryLengthPrefixed(vsBz, &valset) if err != nil { return } diff --git a/p2p/conn/connection.go b/p2p/conn/connection.go index 0e33adab..80fc53dd 100644 --- a/p2p/conn/connection.go +++ b/p2p/conn/connection.go @@ -337,7 +337,7 @@ FOR_LOOP: } case <-c.pingTimer.Chan(): c.Logger.Debug("Send Ping") - _n, err = cdc.MarshalBinaryWriter(c.bufConnWriter, PacketPing{}) + _n, err = cdc.MarshalBinaryLengthPrefixedWriter(c.bufConnWriter, PacketPing{}) if err != nil { break SELECTION } @@ -359,7 +359,7 @@ FOR_LOOP: } case <-c.pong: c.Logger.Debug("Send Pong") - _n, err = cdc.MarshalBinaryWriter(c.bufConnWriter, PacketPong{}) + _n, err = cdc.MarshalBinaryLengthPrefixedWriter(c.bufConnWriter, PacketPong{}) if err != nil { break SELECTION } @@ -477,7 +477,7 @@ FOR_LOOP: var packet Packet var _n int64 var err error - _n, err = cdc.UnmarshalBinaryReader(c.bufConnReader, &packet, int64(c._maxPacketMsgSize)) + _n, err = cdc.UnmarshalBinaryLengthPrefixedReader(c.bufConnReader, &packet, int64(c._maxPacketMsgSize)) c.recvMonitor.Update(int(_n)) if err != nil { if c.IsRunning() { @@ -553,7 +553,7 @@ func (c *MConnection) stopPongTimer() { // maxPacketMsgSize returns a maximum size of PacketMsg, including the overhead // of amino encoding. func (c *MConnection) maxPacketMsgSize() int { - return len(cdc.MustMarshalBinary(PacketMsg{ + return len(cdc.MustMarshalBinaryLengthPrefixed(PacketMsg{ ChannelID: 0x01, EOF: 1, Bytes: make([]byte, c.config.MaxPacketMsgPayloadSize), @@ -723,7 +723,7 @@ func (ch *Channel) nextPacketMsg() PacketMsg { // Not goroutine-safe func (ch *Channel) writePacketMsgTo(w io.Writer) (n int64, err error) { var packet = ch.nextPacketMsg() - n, err = cdc.MarshalBinaryWriter(w, packet) + n, err = cdc.MarshalBinaryLengthPrefixedWriter(w, packet) atomic.AddInt64(&ch.recentlySent, n) return } diff --git a/p2p/conn/connection_test.go b/p2p/conn/connection_test.go index 95b5488a..59fe0d1d 100644 --- a/p2p/conn/connection_test.go +++ b/p2p/conn/connection_test.go @@ -140,7 +140,7 @@ func TestMConnectionPongTimeoutResultsInError(t *testing.T) { go func() { // read ping var pkt PacketPing - _, err = cdc.UnmarshalBinaryReader(server, &pkt, maxPingPongPacketSize) + _, err = cdc.UnmarshalBinaryLengthPrefixedReader(server, &pkt, maxPingPongPacketSize) assert.Nil(t, err) serverGotPing <- struct{}{} }() @@ -176,22 +176,22 @@ func TestMConnectionMultiplePongsInTheBeginning(t *testing.T) { defer mconn.Stop() // sending 3 pongs in a row (abuse) - _, err = server.Write(cdc.MustMarshalBinary(PacketPong{})) + _, err = server.Write(cdc.MustMarshalBinaryLengthPrefixed(PacketPong{})) require.Nil(t, err) - _, err = server.Write(cdc.MustMarshalBinary(PacketPong{})) + _, err = server.Write(cdc.MustMarshalBinaryLengthPrefixed(PacketPong{})) require.Nil(t, err) - _, err = server.Write(cdc.MustMarshalBinary(PacketPong{})) + _, err = server.Write(cdc.MustMarshalBinaryLengthPrefixed(PacketPong{})) require.Nil(t, err) serverGotPing := make(chan struct{}) go func() { // read ping (one byte) var packet, err = Packet(nil), error(nil) - _, err = cdc.UnmarshalBinaryReader(server, &packet, maxPingPongPacketSize) + _, err = cdc.UnmarshalBinaryLengthPrefixedReader(server, &packet, maxPingPongPacketSize) require.Nil(t, err) serverGotPing <- struct{}{} // respond with pong - _, err = server.Write(cdc.MustMarshalBinary(PacketPong{})) + _, err = server.Write(cdc.MustMarshalBinaryLengthPrefixed(PacketPong{})) require.Nil(t, err) }() <-serverGotPing @@ -227,18 +227,18 @@ func TestMConnectionMultiplePings(t *testing.T) { // sending 3 pings in a row (abuse) // see https://github.com/tendermint/tendermint/issues/1190 - _, err = server.Write(cdc.MustMarshalBinary(PacketPing{})) + _, err = server.Write(cdc.MustMarshalBinaryLengthPrefixed(PacketPing{})) require.Nil(t, err) var pkt PacketPong - _, err = cdc.UnmarshalBinaryReader(server, &pkt, maxPingPongPacketSize) + _, err = cdc.UnmarshalBinaryLengthPrefixedReader(server, &pkt, maxPingPongPacketSize) require.Nil(t, err) - _, err = server.Write(cdc.MustMarshalBinary(PacketPing{})) + _, err = server.Write(cdc.MustMarshalBinaryLengthPrefixed(PacketPing{})) require.Nil(t, err) - _, err = cdc.UnmarshalBinaryReader(server, &pkt, maxPingPongPacketSize) + _, err = cdc.UnmarshalBinaryLengthPrefixedReader(server, &pkt, maxPingPongPacketSize) require.Nil(t, err) - _, err = server.Write(cdc.MustMarshalBinary(PacketPing{})) + _, err = server.Write(cdc.MustMarshalBinaryLengthPrefixed(PacketPing{})) require.Nil(t, err) - _, err = cdc.UnmarshalBinaryReader(server, &pkt, maxPingPongPacketSize) + _, err = cdc.UnmarshalBinaryLengthPrefixedReader(server, &pkt, maxPingPongPacketSize) require.Nil(t, err) assert.True(t, mconn.IsRunning()) @@ -270,20 +270,20 @@ func TestMConnectionPingPongs(t *testing.T) { go func() { // read ping var pkt PacketPing - _, err = cdc.UnmarshalBinaryReader(server, &pkt, maxPingPongPacketSize) + _, err = cdc.UnmarshalBinaryLengthPrefixedReader(server, &pkt, maxPingPongPacketSize) require.Nil(t, err) serverGotPing <- struct{}{} // respond with pong - _, err = server.Write(cdc.MustMarshalBinary(PacketPong{})) + _, err = server.Write(cdc.MustMarshalBinaryLengthPrefixed(PacketPong{})) require.Nil(t, err) time.Sleep(mconn.config.PingInterval) // read ping - _, err = cdc.UnmarshalBinaryReader(server, &pkt, maxPingPongPacketSize) + _, err = cdc.UnmarshalBinaryLengthPrefixedReader(server, &pkt, maxPingPongPacketSize) require.Nil(t, err) // respond with pong - _, err = server.Write(cdc.MustMarshalBinary(PacketPong{})) + _, err = server.Write(cdc.MustMarshalBinaryLengthPrefixed(PacketPong{})) require.Nil(t, err) }() <-serverGotPing @@ -380,7 +380,7 @@ func TestMConnectionReadErrorBadEncoding(t *testing.T) { client := mconnClient.conn // send badly encoded msgPacket - bz := cdc.MustMarshalBinary(PacketMsg{}) + bz := cdc.MustMarshalBinaryLengthPrefixed(PacketMsg{}) bz[4] += 0x01 // Invalid prefix bytes. // Write it. @@ -428,7 +428,7 @@ func TestMConnectionReadErrorLongMessage(t *testing.T) { EOF: 1, Bytes: make([]byte, mconnClient.config.MaxPacketMsgPayloadSize), } - _, err = cdc.MarshalBinaryWriter(buf, packet) + _, err = cdc.MarshalBinaryLengthPrefixedWriter(buf, packet) assert.Nil(t, err) _, err = client.Write(buf.Bytes()) assert.Nil(t, err) @@ -441,7 +441,7 @@ func TestMConnectionReadErrorLongMessage(t *testing.T) { EOF: 1, Bytes: make([]byte, mconnClient.config.MaxPacketMsgPayloadSize+100), } - _, err = cdc.MarshalBinaryWriter(buf, packet) + _, err = cdc.MarshalBinaryLengthPrefixedWriter(buf, packet) assert.Nil(t, err) _, err = client.Write(buf.Bytes()) assert.NotNil(t, err) diff --git a/p2p/conn/secret_connection.go b/p2p/conn/secret_connection.go index acdd96de..1dc66aff 100644 --- a/p2p/conn/secret_connection.go +++ b/p2p/conn/secret_connection.go @@ -211,7 +211,7 @@ func shareEphPubKey(conn io.ReadWriteCloser, locEphPub *[32]byte) (remEphPub *[3 // Send our pubkey and receive theirs in tandem. var trs, _ = cmn.Parallel( func(_ int) (val interface{}, err error, abort bool) { - var _, err1 = cdc.MarshalBinaryWriter(conn, locEphPub) + var _, err1 = cdc.MarshalBinaryLengthPrefixedWriter(conn, locEphPub) if err1 != nil { return nil, err1, true // abort } @@ -219,7 +219,7 @@ func shareEphPubKey(conn io.ReadWriteCloser, locEphPub *[32]byte) (remEphPub *[3 }, func(_ int) (val interface{}, err error, abort bool) { var _remEphPub [32]byte - var _, err2 = cdc.UnmarshalBinaryReader(conn, &_remEphPub, 1024*1024) // TODO + var _, err2 = cdc.UnmarshalBinaryLengthPrefixedReader(conn, &_remEphPub, 1024*1024) // TODO if err2 != nil { return nil, err2, true // abort } @@ -305,7 +305,7 @@ func shareAuthSignature(sc *SecretConnection, pubKey crypto.PubKey, signature [] // Send our info and receive theirs in tandem. var trs, _ = cmn.Parallel( func(_ int) (val interface{}, err error, abort bool) { - var _, err1 = cdc.MarshalBinaryWriter(sc, authSigMessage{pubKey, signature}) + var _, err1 = cdc.MarshalBinaryLengthPrefixedWriter(sc, authSigMessage{pubKey, signature}) if err1 != nil { return nil, err1, true // abort } @@ -313,7 +313,7 @@ func shareAuthSignature(sc *SecretConnection, pubKey crypto.PubKey, signature [] }, func(_ int) (val interface{}, err error, abort bool) { var _recvMsg authSigMessage - var _, err2 = cdc.UnmarshalBinaryReader(sc, &_recvMsg, 1024*1024) // TODO + var _, err2 = cdc.UnmarshalBinaryLengthPrefixedReader(sc, &_recvMsg, 1024*1024) // TODO if err2 != nil { return nil, err2, true // abort } diff --git a/p2p/metrics.go b/p2p/metrics.go index ed26d119..b066fb31 100644 --- a/p2p/metrics.go +++ b/p2p/metrics.go @@ -62,7 +62,7 @@ func PrometheusMetrics(namespace string) *Metrics { // NopMetrics returns no-op Metrics. func NopMetrics() *Metrics { return &Metrics{ - Peers: discard.NewGauge(), + Peers: discard.NewGauge(), PeerReceiveBytesTotal: discard.NewCounter(), PeerSendBytesTotal: discard.NewCounter(), PeerPendingSendBytes: discard.NewGauge(), diff --git a/p2p/transport.go b/p2p/transport.go index 10565d8a..0b9b436f 100644 --- a/p2p/transport.go +++ b/p2p/transport.go @@ -446,11 +446,11 @@ func handshake( ) go func(errc chan<- error, c net.Conn) { - _, err := cdc.MarshalBinaryWriter(c, ourNodeInfo) + _, err := cdc.MarshalBinaryLengthPrefixedWriter(c, ourNodeInfo) errc <- err }(errc, c) go func(errc chan<- error, c net.Conn) { - _, err := cdc.UnmarshalBinaryReader( + _, err := cdc.UnmarshalBinaryLengthPrefixedReader( c, &peerNodeInfo, int64(MaxNodeInfoSize()), diff --git a/p2p/transport_test.go b/p2p/transport_test.go index cce223a3..8a5c06bc 100644 --- a/p2p/transport_test.go +++ b/p2p/transport_test.go @@ -516,7 +516,7 @@ func TestTransportHandshake(t *testing.T) { } go func(c net.Conn) { - _, err := cdc.MarshalBinaryWriter(c, peerNodeInfo.(DefaultNodeInfo)) + _, err := cdc.MarshalBinaryLengthPrefixedWriter(c, peerNodeInfo.(DefaultNodeInfo)) if err != nil { t.Error(err) } @@ -524,7 +524,7 @@ func TestTransportHandshake(t *testing.T) { go func(c net.Conn) { var ni DefaultNodeInfo - _, err := cdc.UnmarshalBinaryReader( + _, err := cdc.UnmarshalBinaryLengthPrefixedReader( c, &ni, int64(MaxNodeInfoSize()), diff --git a/privval/priv_validator.go b/privval/priv_validator.go index c5fba509..a13f5426 100644 --- a/privval/priv_validator.go +++ b/privval/priv_validator.go @@ -314,10 +314,10 @@ func (pv *FilePV) String() string { // returns true if the only difference in the votes is their timestamp. func checkVotesOnlyDifferByTimestamp(lastSignBytes, newSignBytes []byte) (time.Time, bool) { var lastVote, newVote types.CanonicalVote - if err := cdc.UnmarshalBinary(lastSignBytes, &lastVote); err != nil { + if err := cdc.UnmarshalBinaryLengthPrefixed(lastSignBytes, &lastVote); err != nil { panic(fmt.Sprintf("LastSignBytes cannot be unmarshalled into vote: %v", err)) } - if err := cdc.UnmarshalBinary(newSignBytes, &newVote); err != nil { + if err := cdc.UnmarshalBinaryLengthPrefixed(newSignBytes, &newVote); err != nil { panic(fmt.Sprintf("signBytes cannot be unmarshalled into vote: %v", err)) } @@ -337,10 +337,10 @@ func checkVotesOnlyDifferByTimestamp(lastSignBytes, newSignBytes []byte) (time.T // returns true if the only difference in the proposals is their timestamp func checkProposalsOnlyDifferByTimestamp(lastSignBytes, newSignBytes []byte) (time.Time, bool) { var lastProposal, newProposal types.CanonicalProposal - if err := cdc.UnmarshalBinary(lastSignBytes, &lastProposal); err != nil { + if err := cdc.UnmarshalBinaryLengthPrefixed(lastSignBytes, &lastProposal); err != nil { panic(fmt.Sprintf("LastSignBytes cannot be unmarshalled into proposal: %v", err)) } - if err := cdc.UnmarshalBinary(newSignBytes, &newProposal); err != nil { + if err := cdc.UnmarshalBinaryLengthPrefixed(newSignBytes, &newProposal); err != nil { panic(fmt.Sprintf("signBytes cannot be unmarshalled into proposal: %v", err)) } @@ -349,8 +349,8 @@ func checkProposalsOnlyDifferByTimestamp(lastSignBytes, newSignBytes []byte) (ti now := tmtime.Now() lastProposal.Timestamp = now newProposal.Timestamp = now - lastProposalBytes, _ := cdc.MarshalBinary(lastProposal) - newProposalBytes, _ := cdc.MarshalBinary(newProposal) + lastProposalBytes, _ := cdc.MarshalBinaryLengthPrefixed(lastProposal) + newProposalBytes, _ := cdc.MarshalBinaryLengthPrefixed(newProposal) return lastTime, bytes.Equal(newProposalBytes, lastProposalBytes) } diff --git a/privval/remote_signer.go b/privval/remote_signer.go index 399ee790..eacc840c 100644 --- a/privval/remote_signer.go +++ b/privval/remote_signer.go @@ -248,7 +248,7 @@ func (e *RemoteSignerError) Error() string { func readMsg(r io.Reader) (msg RemoteSignerMsg, err error) { const maxRemoteSignerMsgSize = 1024 * 10 - _, err = cdc.UnmarshalBinaryReader(r, &msg, maxRemoteSignerMsgSize) + _, err = cdc.UnmarshalBinaryLengthPrefixedReader(r, &msg, maxRemoteSignerMsgSize) if _, ok := err.(timeoutError); ok { err = cmn.ErrorWrap(ErrConnTimeout, err.Error()) } @@ -256,7 +256,7 @@ func readMsg(r io.Reader) (msg RemoteSignerMsg, err error) { } func writeMsg(w io.Writer, msg interface{}) (err error) { - _, err = cdc.MarshalBinaryWriter(w, msg) + _, err = cdc.MarshalBinaryLengthPrefixedWriter(w, msg) if _, ok := err.(timeoutError); ok { err = cmn.ErrorWrap(ErrConnTimeout, err.Error()) } diff --git a/types/block.go b/types/block.go index ce605263..70b840c6 100644 --- a/types/block.go +++ b/types/block.go @@ -149,7 +149,7 @@ func (b *Block) MakePartSet(partSize int) *PartSet { // We prefix the byte length, so that unmarshaling // can easily happen via a reader. - bz, err := cdc.MarshalBinary(b) + bz, err := cdc.MarshalBinaryLengthPrefixed(b) if err != nil { panic(err) } diff --git a/types/block_test.go b/types/block_test.go index e34ba29b..34107398 100644 --- a/types/block_test.go +++ b/types/block_test.go @@ -265,7 +265,7 @@ func TestMaxHeaderBytes(t *testing.T) { ProposerAddress: tmhash.Sum([]byte("proposer_address")), } - bz, err := cdc.MarshalBinary(h) + bz, err := cdc.MarshalBinaryLengthPrefixed(h) require.NoError(t, err) assert.EqualValues(t, MaxHeaderBytes, len(bz)) diff --git a/types/evidence_test.go b/types/evidence_test.go index 79805691..44276ab1 100644 --- a/types/evidence_test.go +++ b/types/evidence_test.go @@ -105,7 +105,7 @@ func TestMaxEvidenceBytes(t *testing.T) { VoteB: makeVote(val, chainID, math.MaxInt64, math.MaxInt64, math.MaxInt64, math.MaxInt64, blockID2), } - bz, err := cdc.MarshalBinary(ev) + bz, err := cdc.MarshalBinaryLengthPrefixed(ev) require.NoError(t, err) assert.EqualValues(t, MaxEvidenceBytes, len(bz)) diff --git a/types/heartbeat.go b/types/heartbeat.go index de03d5cc..9dea039e 100644 --- a/types/heartbeat.go +++ b/types/heartbeat.go @@ -23,7 +23,7 @@ type Heartbeat struct { // SignBytes returns the Heartbeat bytes for signing. // It panics if the Heartbeat is nil. func (heartbeat *Heartbeat) SignBytes(chainID string) []byte { - bz, err := cdc.MarshalBinary(CanonicalizeHeartbeat(chainID, heartbeat)) + bz, err := cdc.MarshalBinaryLengthPrefixed(CanonicalizeHeartbeat(chainID, heartbeat)) if err != nil { panic(err) } diff --git a/types/heartbeat_test.go b/types/heartbeat_test.go index 550bcc73..e1ffdd6f 100644 --- a/types/heartbeat_test.go +++ b/types/heartbeat_test.go @@ -39,7 +39,7 @@ func TestHeartbeatWriteSignBytes(t *testing.T) { { testHeartbeat := &Heartbeat{ValidatorIndex: 1, Height: 10, Round: 1} signBytes := testHeartbeat.SignBytes(chainID) - expected, err := cdc.MarshalBinary(CanonicalizeHeartbeat(chainID, testHeartbeat)) + expected, err := cdc.MarshalBinaryLengthPrefixed(CanonicalizeHeartbeat(chainID, testHeartbeat)) require.NoError(t, err) require.Equal(t, expected, signBytes, "Got unexpected sign bytes for Heartbeat") } @@ -47,7 +47,7 @@ func TestHeartbeatWriteSignBytes(t *testing.T) { { testHeartbeat := &Heartbeat{} signBytes := testHeartbeat.SignBytes(chainID) - expected, err := cdc.MarshalBinary(CanonicalizeHeartbeat(chainID, testHeartbeat)) + expected, err := cdc.MarshalBinaryLengthPrefixed(CanonicalizeHeartbeat(chainID, testHeartbeat)) require.NoError(t, err) require.Equal(t, expected, signBytes, "Got unexpected sign bytes for Heartbeat") } diff --git a/types/proposal.go b/types/proposal.go index a2bc8e36..5d70a3c8 100644 --- a/types/proposal.go +++ b/types/proposal.go @@ -52,7 +52,7 @@ func (p *Proposal) String() string { // SignBytes returns the Proposal bytes for signing func (p *Proposal) SignBytes(chainID string) []byte { - bz, err := cdc.MarshalBinary(CanonicalizeProposal(chainID, p)) + bz, err := cdc.MarshalBinaryLengthPrefixed(CanonicalizeProposal(chainID, p)) if err != nil { panic(err) } diff --git a/types/proposal_test.go b/types/proposal_test.go index 5f943308..8ae1f3e5 100644 --- a/types/proposal_test.go +++ b/types/proposal_test.go @@ -27,7 +27,7 @@ func TestProposalSignable(t *testing.T) { chainID := "test_chain_id" signBytes := testProposal.SignBytes(chainID) - expected, err := cdc.MarshalBinary(CanonicalizeProposal(chainID, testProposal)) + expected, err := cdc.MarshalBinaryLengthPrefixed(CanonicalizeProposal(chainID, testProposal)) require.NoError(t, err) require.Equal(t, expected, signBytes, "Got unexpected sign bytes for Proposal") } @@ -57,9 +57,9 @@ func TestProposalVerifySignature(t *testing.T) { // serialize, deserialize and verify again.... newProp := new(Proposal) - bs, err := cdc.MarshalBinary(prop) + bs, err := cdc.MarshalBinaryLengthPrefixed(prop) require.NoError(t, err) - err = cdc.UnmarshalBinary(bs, &newProp) + err = cdc.UnmarshalBinaryLengthPrefixed(bs, &newProp) require.NoError(t, err) // verify the transmitted proposal diff --git a/types/results.go b/types/results.go index 6b5b82d2..db781168 100644 --- a/types/results.go +++ b/types/results.go @@ -48,7 +48,7 @@ func NewResultFromResponse(response *abci.ResponseDeliverTx) ABCIResult { // Bytes serializes the ABCIResponse using wire func (a ABCIResults) Bytes() []byte { - bz, err := cdc.MarshalBinary(a) + bz, err := cdc.MarshalBinaryLengthPrefixed(a) if err != nil { panic(err) } diff --git a/types/tx_test.go b/types/tx_test.go index 9fb8ff34..6ce23d6f 100644 --- a/types/tx_test.go +++ b/types/tx_test.go @@ -79,9 +79,9 @@ func TestValidTxProof(t *testing.T) { // read-write must also work var p2 TxProof - bin, err := cdc.MarshalBinary(proof) + bin, err := cdc.MarshalBinaryLengthPrefixed(proof) assert.Nil(t, err) - err = cdc.UnmarshalBinary(bin, &p2) + err = cdc.UnmarshalBinaryLengthPrefixed(bin, &p2) if assert.Nil(t, err, "%d: %d: %+v", h, i, err) { assert.Nil(t, p2.Validate(root), "%d: %d", h, i) } @@ -105,7 +105,7 @@ func testTxProofUnchangable(t *testing.T) { // make sure it is valid to start with assert.Nil(t, proof.Validate(root)) - bin, err := cdc.MarshalBinary(proof) + bin, err := cdc.MarshalBinaryLengthPrefixed(proof) assert.Nil(t, err) // try mutating the data and make sure nothing breaks @@ -120,7 +120,7 @@ func testTxProofUnchangable(t *testing.T) { // This makes sure that the proof doesn't deserialize into something valid. func assertBadProof(t *testing.T, root []byte, bad []byte, good TxProof) { var proof TxProof - err := cdc.UnmarshalBinary(bad, &proof) + err := cdc.UnmarshalBinaryLengthPrefixed(bad, &proof) if err == nil { err = proof.Validate(root) if err == nil { diff --git a/types/validator_set_test.go b/types/validator_set_test.go index d886b419..aad9d85a 100644 --- a/types/validator_set_test.go +++ b/types/validator_set_test.go @@ -272,7 +272,7 @@ func randValidatorSet(numValidators int) *ValidatorSet { } func (valSet *ValidatorSet) toBytes() []byte { - bz, err := cdc.MarshalBinary(valSet) + bz, err := cdc.MarshalBinaryLengthPrefixed(valSet) if err != nil { panic(err) } @@ -280,7 +280,7 @@ func (valSet *ValidatorSet) toBytes() []byte { } func (valSet *ValidatorSet) fromBytes(b []byte) { - err := cdc.UnmarshalBinary(b, &valSet) + err := cdc.UnmarshalBinaryLengthPrefixed(b, &valSet) if err != nil { // DATA HAS BEEN CORRUPTED OR THE SPEC HAS CHANGED panic(err) diff --git a/types/vote.go b/types/vote.go index 2a713309..e1095bf1 100644 --- a/types/vote.go +++ b/types/vote.go @@ -59,7 +59,7 @@ type Vote struct { } func (vote *Vote) SignBytes(chainID string) []byte { - bz, err := cdc.MarshalBinary(CanonicalizeVote(chainID, vote)) + bz, err := cdc.MarshalBinaryLengthPrefixed(CanonicalizeVote(chainID, vote)) if err != nil { panic(err) } diff --git a/types/vote_test.go b/types/vote_test.go index 3b2f0848..1d7e3daf 100644 --- a/types/vote_test.go +++ b/types/vote_test.go @@ -46,7 +46,7 @@ func TestVoteSignable(t *testing.T) { vote := examplePrecommit() signBytes := vote.SignBytes("test_chain_id") - expected, err := cdc.MarshalBinary(CanonicalizeVote("test_chain_id", vote)) + expected, err := cdc.MarshalBinaryLengthPrefixed(CanonicalizeVote("test_chain_id", vote)) require.NoError(t, err) require.Equal(t, expected, signBytes, "Got unexpected sign bytes for Vote.") @@ -130,9 +130,9 @@ func TestVoteSignableTestVectors(t *testing.T) { func TestVoteProposalNotEq(t *testing.T) { cv := CanonicalizeVote("", &Vote{Height: 1, Round: 1}) p := CanonicalizeProposal("", &Proposal{Height: 1, Round: 1}) - vb, err := cdc.MarshalBinary(cv) + vb, err := cdc.MarshalBinaryLengthPrefixed(cv) require.NoError(t, err) - pb, err := cdc.MarshalBinary(p) + pb, err := cdc.MarshalBinaryLengthPrefixed(p) require.NoError(t, err) require.NotEqual(t, vb, pb) } @@ -154,9 +154,9 @@ func TestVoteVerifySignature(t *testing.T) { // serialize, deserialize and verify again.... precommit := new(Vote) - bs, err := cdc.MarshalBinary(vote) + bs, err := cdc.MarshalBinaryLengthPrefixed(vote) require.NoError(t, err) - err = cdc.UnmarshalBinary(bs, &precommit) + err = cdc.UnmarshalBinaryLengthPrefixed(bs, &precommit) require.NoError(t, err) // verify the transmitted vote @@ -230,7 +230,7 @@ func TestMaxVoteBytes(t *testing.T) { err := privVal.SignVote("test_chain_id", vote) require.NoError(t, err) - bz, err := cdc.MarshalBinary(vote) + bz, err := cdc.MarshalBinaryLengthPrefixed(vote) require.NoError(t, err) assert.EqualValues(t, MaxVoteBytes, len(bz)) From bbf15b3d09ac05ff2cfd58b53313ec612e1a9b07 Mon Sep 17 00:00:00 2001 From: zhangzheng Date: Thu, 25 Oct 2018 18:27:32 +0800 Subject: [PATCH 036/267] tm-monitor: update health after we added / removed node (#2694) Refs #2693 --- tools/tm-monitor/monitor/network.go | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/tools/tm-monitor/monitor/network.go b/tools/tm-monitor/monitor/network.go index 9b147c06..bb5dd0ba 100644 --- a/tools/tm-monitor/monitor/network.go +++ b/tools/tm-monitor/monitor/network.go @@ -140,14 +140,22 @@ func (n *Network) NodeIsOnline(name string) { // NewNode is called when the new node is added to the monitor. func (n *Network) NewNode(name string) { + n.mu.Lock() + defer n.mu.Unlock() + n.NumNodesMonitored++ n.NumNodesMonitoredOnline++ + n.updateHealth() } // NodeDeleted is called when the node is deleted from under the monitor. func (n *Network) NodeDeleted(name string) { + n.mu.Lock() + defer n.mu.Unlock() + n.NumNodesMonitored-- n.NumNodesMonitoredOnline-- + n.updateHealth() } func (n *Network) updateHealth() { From a67ae814698b76206049e378a9ed64e7a2ed3832 Mon Sep 17 00:00:00 2001 From: Anton Kaliaev Date: Thu, 25 Oct 2018 16:40:20 +0200 Subject: [PATCH 037/267] if some process locks a block in round 0, then 0 is valid proposal.POLRound in rounds > 0 This condition is really hard to get. Initially, lockedRound and validRound are set to -1 as we start with round 0. Refs #2702 --- consensus/state.go | 6 +++--- types/vote_set.go | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/consensus/state.go b/consensus/state.go index 0b079f13..c6efe98f 100644 --- a/consensus/state.go +++ b/consensus/state.go @@ -1425,9 +1425,9 @@ func (cs *ConsensusState) defaultSetProposal(proposal *types.Proposal) error { return nil } - // Verify POLRound, which must be -1 or between 0 and proposal.Round exclusive. - if proposal.POLRound != -1 && - (proposal.POLRound < 0 || proposal.Round <= proposal.POLRound) { + // Verify POLRound, which must be -1 or between 0 inclusive and proposal.Round exclusive. + if proposal.POLRound < -1 || + (proposal.POLRound >= 0 && proposal.POLRound >= proposal.Round) { return ErrInvalidProposalPOLRound } diff --git a/types/vote_set.go b/types/vote_set.go index cdfa3d40..0cf6cbb7 100644 --- a/types/vote_set.go +++ b/types/vote_set.go @@ -158,7 +158,7 @@ func (voteSet *VoteSet) addVote(vote *Vote) (added bool, err error) { if (vote.Height != voteSet.height) || (vote.Round != voteSet.round) || (vote.Type != voteSet.type_) { - return false, errors.Wrapf(ErrVoteUnexpectedStep, "Got %d/%d/%d, expected %d/%d/%d", + return false, errors.Wrapf(ErrVoteUnexpectedStep, "Expected %d/%d/%d, but got %d/%d/%d", voteSet.height, voteSet.round, voteSet.type_, vote.Height, vote.Round, vote.Type) } From b6d5b8b74574269e5c51ed9d14477e8b9e07def1 Mon Sep 17 00:00:00 2001 From: Ismail Khoffi Date: Mon, 29 Oct 2018 14:16:50 +0100 Subject: [PATCH 038/267] Update to amino 0.14.0 (#2710) * WIP: update to amino 0.14.0 * update Changelog * Update to latest amino version (v0.14.0) --- CHANGELOG_PENDING.md | 1 + Gopkg.lock | 16 +-- Gopkg.toml | 2 +- Makefile | 4 +- types/block.go | 2 +- types/block_test.go | 12 +- types/evidence.go | 2 +- types/proto3/block.pb.go | 262 +++++++++++++++++++++++++++------------ types/proto3/block.proto | 8 +- types/vote.go | 2 +- 10 files changed, 206 insertions(+), 105 deletions(-) diff --git a/CHANGELOG_PENDING.md b/CHANGELOG_PENDING.md index 163c4649..a56487ff 100644 --- a/CHANGELOG_PENDING.md +++ b/CHANGELOG_PENDING.md @@ -50,6 +50,7 @@ BREAKING CHANGES: * [types] [\#2298](https://github.com/tendermint/tendermint/issues/2298) Remove `Index` and `Total` fields from `TxProof`. * [types] [\#2598](https://github.com/tendermint/tendermint/issues/2598) `VoteTypeXxx` are now of type `SignedMsgType byte` and named `XxxType`, eg. `PrevoteType`, `PrecommitType`. + * [types] [\#2682](https://github.com/tendermint/tendermint/issues/2682) Use proto3 `varint` encoding for ints that are usually unsigned (instead of zigzag encoding). * Blockchain Protocol * [types] Update SignBytes for `Vote`/`Proposal`/`Heartbeat`: diff --git a/Gopkg.lock b/Gopkg.lock index 566fed4a..513e0bd7 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -286,12 +286,12 @@ version = "v1.1.2" [[projects]] - digest = "1:516e71bed754268937f57d4ecb190e01958452336fa73dbac880894164e91c1f" + digest = "1:08d65904057412fc0270fc4812a1c90c594186819243160dc779a402d4b6d0bc" name = "github.com/spf13/cast" packages = ["."] pruneopts = "UT" - revision = "8965335b8c7107321228e3e3702cab9832751bac" - version = "v1.2.0" + revision = "8c9545af88b134710ab1cd196795e7f2388358d7" + version = "v1.3.0" [[projects]] digest = "1:7ffc0983035bc7e297da3688d9fe19d60a420e9c38bef23f845c53788ed6a05e" @@ -365,12 +365,12 @@ revision = "e5840949ff4fff0c56f9b6a541e22b63581ea9df" [[projects]] - digest = "1:5f52e817b6c9d52ddba70dece0ea31134d82a52c05bce98fbc739ab2a832df28" + digest = "1:10b3a599325740c84a7c81f3f3cb2e1fdb70b3ea01b7fa28495567a2519df431" name = "github.com/tendermint/go-amino" packages = ["."] pruneopts = "UT" - revision = "cb07448b240918aa8d8df4505153549b86b77134" - version = "v0.13.0" + revision = "6dcc6ddc143e116455c94b25c1004c99e0d0ca12" + version = "v0.14.0" [[projects]] digest = "1:72b71e3a29775e5752ed7a8012052a3dee165e27ec18cedddae5288058f09acf" @@ -415,14 +415,14 @@ [[projects]] branch = "master" - digest = "1:d1da39c9bac61327dbef1d8ef9f210425e99fd2924b6fb5f0bc587a193353637" + digest = "1:fd98d154bf152ad5a49600ede7d7341851bcdfe358b9b82e5ccdba818618167c" name = "golang.org/x/sys" packages = [ "cpu", "unix", ] pruneopts = "UT" - revision = "8a28ead16f52c8aaeffbf79239b251dfdf6c4f96" + revision = "2772b66316d2c587efeb188dcd5ebc6987656e84" [[projects]] digest = "1:a2ab62866c75542dd18d2b069fec854577a20211d7c0ea6ae746072a1dccdd18" diff --git a/Gopkg.toml b/Gopkg.toml index e24965dc..955d6c6d 100644 --- a/Gopkg.toml +++ b/Gopkg.toml @@ -58,7 +58,7 @@ [[constraint]] name = "github.com/tendermint/go-amino" - version = "v0.13.0" + version = "v0.14.0" [[constraint]] name = "google.golang.org/grpc" diff --git a/Makefile b/Makefile index 0b78574b..4390b1fb 100644 --- a/Makefile +++ b/Makefile @@ -36,7 +36,7 @@ install: ######################################## ### Protobuf -protoc_all: protoc_libs protoc_merkle protoc_abci protoc_grpc +protoc_all: protoc_libs protoc_merkle protoc_abci protoc_grpc protoc_proto3types %.pb.go: %.proto ## If you get the following error, @@ -52,6 +52,8 @@ protoc_all: protoc_libs protoc_merkle protoc_abci protoc_grpc # see protobuf section above protoc_abci: abci/types/types.pb.go +protoc_proto3types: types/proto3/block.pb.go + build_abci: @go build -i ./abci/cmd/... diff --git a/types/block.go b/types/block.go index 70b840c6..477e3999 100644 --- a/types/block.go +++ b/types/block.go @@ -15,7 +15,7 @@ import ( const ( // MaxHeaderBytes is a maximum header size (including amino overhead). - MaxHeaderBytes int64 = 537 + MaxHeaderBytes int64 = 533 // MaxAminoOverheadForBlock - maximum amino overhead to encode a block (up to // MaxBlockSizeBytes in size) not including it's parts except Data. diff --git a/types/block_test.go b/types/block_test.go index 34107398..28e73f66 100644 --- a/types/block_test.go +++ b/types/block_test.go @@ -292,9 +292,9 @@ func TestBlockMaxDataBytes(t *testing.T) { }{ 0: {-10, 1, 0, true, 0}, 1: {10, 1, 0, true, 0}, - 2: {750, 1, 0, true, 0}, - 3: {751, 1, 0, false, 0}, - 4: {752, 1, 0, false, 1}, + 2: {742, 1, 0, true, 0}, + 3: {743, 1, 0, false, 0}, + 4: {744, 1, 0, false, 1}, } for i, tc := range testCases { @@ -320,9 +320,9 @@ func TestBlockMaxDataBytesUnknownEvidence(t *testing.T) { }{ 0: {-10, 1, true, 0}, 1: {10, 1, true, 0}, - 2: {833, 1, true, 0}, - 3: {834, 1, false, 0}, - 4: {835, 1, false, 1}, + 2: {824, 1, true, 0}, + 3: {825, 1, false, 0}, + 4: {826, 1, false, 1}, } for i, tc := range testCases { diff --git a/types/evidence.go b/types/evidence.go index 6d42ed22..7a808d57 100644 --- a/types/evidence.go +++ b/types/evidence.go @@ -14,7 +14,7 @@ import ( const ( // MaxEvidenceBytes is a maximum size of any evidence (including amino overhead). - MaxEvidenceBytes int64 = 444 + MaxEvidenceBytes int64 = 436 ) // ErrEvidenceInvalid wraps a piece of evidence and the error denoting how or why it is invalid. diff --git a/types/proto3/block.pb.go b/types/proto3/block.pb.go index 7efc7ca7..99dadac1 100644 --- a/types/proto3/block.pb.go +++ b/types/proto3/block.pb.go @@ -1,22 +1,9 @@ -// Code generated by protoc-gen-go. DO NOT EDIT. -// source: block.proto +// Code generated by protoc-gen-gogo. DO NOT EDIT. +// source: types/proto3/block.proto -/* -Package proto3 is a generated protocol buffer package. - -It is generated from these files: - block.proto - -It has these top-level messages: - PartSetHeader - BlockID - Header - Version - Timestamp -*/ package proto3 -import proto "github.com/golang/protobuf/proto" +import proto "github.com/gogo/protobuf/proto" import fmt "fmt" import math "math" @@ -29,17 +16,39 @@ var _ = math.Inf // is compatible with the proto package it is being compiled against. // A compilation error at this line likely means your copy of the // proto package needs to be updated. -const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package +const _ = proto.GoGoProtoPackageIsVersion2 // please upgrade the proto package type PartSetHeader struct { - Total int32 `protobuf:"zigzag32,1,opt,name=Total" json:"Total,omitempty"` - Hash []byte `protobuf:"bytes,2,opt,name=Hash,proto3" json:"Hash,omitempty"` + Total int32 `protobuf:"varint,1,opt,name=Total,proto3" json:"Total,omitempty"` + Hash []byte `protobuf:"bytes,2,opt,name=Hash,proto3" json:"Hash,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` } -func (m *PartSetHeader) Reset() { *m = PartSetHeader{} } -func (m *PartSetHeader) String() string { return proto.CompactTextString(m) } -func (*PartSetHeader) ProtoMessage() {} -func (*PartSetHeader) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{0} } +func (m *PartSetHeader) Reset() { *m = PartSetHeader{} } +func (m *PartSetHeader) String() string { return proto.CompactTextString(m) } +func (*PartSetHeader) ProtoMessage() {} +func (*PartSetHeader) Descriptor() ([]byte, []int) { + return fileDescriptor_block_57c41dfc0fc285b3, []int{0} +} +func (m *PartSetHeader) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_PartSetHeader.Unmarshal(m, b) +} +func (m *PartSetHeader) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_PartSetHeader.Marshal(b, m, deterministic) +} +func (dst *PartSetHeader) XXX_Merge(src proto.Message) { + xxx_messageInfo_PartSetHeader.Merge(dst, src) +} +func (m *PartSetHeader) XXX_Size() int { + return xxx_messageInfo_PartSetHeader.Size(m) +} +func (m *PartSetHeader) XXX_DiscardUnknown() { + xxx_messageInfo_PartSetHeader.DiscardUnknown(m) +} + +var xxx_messageInfo_PartSetHeader proto.InternalMessageInfo func (m *PartSetHeader) GetTotal() int32 { if m != nil { @@ -56,14 +65,36 @@ func (m *PartSetHeader) GetHash() []byte { } type BlockID struct { - Hash []byte `protobuf:"bytes,1,opt,name=Hash,proto3" json:"Hash,omitempty"` - PartsHeader *PartSetHeader `protobuf:"bytes,2,opt,name=PartsHeader" json:"PartsHeader,omitempty"` + Hash []byte `protobuf:"bytes,1,opt,name=Hash,proto3" json:"Hash,omitempty"` + PartsHeader *PartSetHeader `protobuf:"bytes,2,opt,name=PartsHeader" json:"PartsHeader,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` } -func (m *BlockID) Reset() { *m = BlockID{} } -func (m *BlockID) String() string { return proto.CompactTextString(m) } -func (*BlockID) ProtoMessage() {} -func (*BlockID) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{1} } +func (m *BlockID) Reset() { *m = BlockID{} } +func (m *BlockID) String() string { return proto.CompactTextString(m) } +func (*BlockID) ProtoMessage() {} +func (*BlockID) Descriptor() ([]byte, []int) { + return fileDescriptor_block_57c41dfc0fc285b3, []int{1} +} +func (m *BlockID) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_BlockID.Unmarshal(m, b) +} +func (m *BlockID) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_BlockID.Marshal(b, m, deterministic) +} +func (dst *BlockID) XXX_Merge(src proto.Message) { + xxx_messageInfo_BlockID.Merge(dst, src) +} +func (m *BlockID) XXX_Size() int { + return xxx_messageInfo_BlockID.Size(m) +} +func (m *BlockID) XXX_DiscardUnknown() { + xxx_messageInfo_BlockID.DiscardUnknown(m) +} + +var xxx_messageInfo_BlockID proto.InternalMessageInfo func (m *BlockID) GetHash() []byte { if m != nil { @@ -82,11 +113,11 @@ func (m *BlockID) GetPartsHeader() *PartSetHeader { type Header struct { // basic block info Version *Version `protobuf:"bytes,1,opt,name=Version" json:"Version,omitempty"` - ChainID string `protobuf:"bytes,2,opt,name=ChainID" json:"ChainID,omitempty"` - Height int64 `protobuf:"zigzag64,3,opt,name=Height" json:"Height,omitempty"` + ChainID string `protobuf:"bytes,2,opt,name=ChainID,proto3" json:"ChainID,omitempty"` + Height int64 `protobuf:"varint,3,opt,name=Height,proto3" json:"Height,omitempty"` Time *Timestamp `protobuf:"bytes,4,opt,name=Time" json:"Time,omitempty"` - NumTxs int64 `protobuf:"zigzag64,5,opt,name=NumTxs" json:"NumTxs,omitempty"` - TotalTxs int64 `protobuf:"zigzag64,6,opt,name=TotalTxs" json:"TotalTxs,omitempty"` + NumTxs int64 `protobuf:"varint,5,opt,name=NumTxs,proto3" json:"NumTxs,omitempty"` + TotalTxs int64 `protobuf:"varint,6,opt,name=TotalTxs,proto3" json:"TotalTxs,omitempty"` // prev block info LastBlockID *BlockID `protobuf:"bytes,7,opt,name=LastBlockID" json:"LastBlockID,omitempty"` // hashes of block data @@ -99,14 +130,36 @@ type Header struct { AppHash []byte `protobuf:"bytes,13,opt,name=AppHash,proto3" json:"AppHash,omitempty"` LastResultsHash []byte `protobuf:"bytes,14,opt,name=LastResultsHash,proto3" json:"LastResultsHash,omitempty"` // consensus info - EvidenceHash []byte `protobuf:"bytes,15,opt,name=EvidenceHash,proto3" json:"EvidenceHash,omitempty"` - ProposerAddress []byte `protobuf:"bytes,16,opt,name=ProposerAddress,proto3" json:"ProposerAddress,omitempty"` + EvidenceHash []byte `protobuf:"bytes,15,opt,name=EvidenceHash,proto3" json:"EvidenceHash,omitempty"` + ProposerAddress []byte `protobuf:"bytes,16,opt,name=ProposerAddress,proto3" json:"ProposerAddress,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` } -func (m *Header) Reset() { *m = Header{} } -func (m *Header) String() string { return proto.CompactTextString(m) } -func (*Header) ProtoMessage() {} -func (*Header) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{2} } +func (m *Header) Reset() { *m = Header{} } +func (m *Header) String() string { return proto.CompactTextString(m) } +func (*Header) ProtoMessage() {} +func (*Header) Descriptor() ([]byte, []int) { + return fileDescriptor_block_57c41dfc0fc285b3, []int{2} +} +func (m *Header) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_Header.Unmarshal(m, b) +} +func (m *Header) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_Header.Marshal(b, m, deterministic) +} +func (dst *Header) XXX_Merge(src proto.Message) { + xxx_messageInfo_Header.Merge(dst, src) +} +func (m *Header) XXX_Size() int { + return xxx_messageInfo_Header.Size(m) +} +func (m *Header) XXX_DiscardUnknown() { + xxx_messageInfo_Header.DiscardUnknown(m) +} + +var xxx_messageInfo_Header proto.InternalMessageInfo func (m *Header) GetVersion() *Version { if m != nil { @@ -221,14 +274,36 @@ func (m *Header) GetProposerAddress() []byte { } type Version struct { - Block uint64 `protobuf:"varint,1,opt,name=Block" json:"Block,omitempty"` - App uint64 `protobuf:"varint,2,opt,name=App" json:"App,omitempty"` + Block uint64 `protobuf:"varint,1,opt,name=Block,proto3" json:"Block,omitempty"` + App uint64 `protobuf:"varint,2,opt,name=App,proto3" json:"App,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` } -func (m *Version) Reset() { *m = Version{} } -func (m *Version) String() string { return proto.CompactTextString(m) } -func (*Version) ProtoMessage() {} -func (*Version) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{3} } +func (m *Version) Reset() { *m = Version{} } +func (m *Version) String() string { return proto.CompactTextString(m) } +func (*Version) ProtoMessage() {} +func (*Version) Descriptor() ([]byte, []int) { + return fileDescriptor_block_57c41dfc0fc285b3, []int{3} +} +func (m *Version) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_Version.Unmarshal(m, b) +} +func (m *Version) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_Version.Marshal(b, m, deterministic) +} +func (dst *Version) XXX_Merge(src proto.Message) { + xxx_messageInfo_Version.Merge(dst, src) +} +func (m *Version) XXX_Size() int { + return xxx_messageInfo_Version.Size(m) +} +func (m *Version) XXX_DiscardUnknown() { + xxx_messageInfo_Version.DiscardUnknown(m) +} + +var xxx_messageInfo_Version proto.InternalMessageInfo func (m *Version) GetBlock() uint64 { if m != nil { @@ -250,14 +325,36 @@ func (m *Version) GetApp() uint64 { // https://github.com/google/protobuf/blob/d2980062c859649523d5fd51d6b55ab310e47482/src/google/protobuf/timestamp.proto#L123-L135 // NOTE/XXX: nanos do not get skipped if they are zero in amino. type Timestamp struct { - Seconds int64 `protobuf:"varint,1,opt,name=seconds" json:"seconds,omitempty"` - Nanos int32 `protobuf:"varint,2,opt,name=nanos" json:"nanos,omitempty"` + Seconds int64 `protobuf:"varint,1,opt,name=seconds,proto3" json:"seconds,omitempty"` + Nanos int32 `protobuf:"varint,2,opt,name=nanos,proto3" json:"nanos,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` } -func (m *Timestamp) Reset() { *m = Timestamp{} } -func (m *Timestamp) String() string { return proto.CompactTextString(m) } -func (*Timestamp) ProtoMessage() {} -func (*Timestamp) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{4} } +func (m *Timestamp) Reset() { *m = Timestamp{} } +func (m *Timestamp) String() string { return proto.CompactTextString(m) } +func (*Timestamp) ProtoMessage() {} +func (*Timestamp) Descriptor() ([]byte, []int) { + return fileDescriptor_block_57c41dfc0fc285b3, []int{4} +} +func (m *Timestamp) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_Timestamp.Unmarshal(m, b) +} +func (m *Timestamp) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_Timestamp.Marshal(b, m, deterministic) +} +func (dst *Timestamp) XXX_Merge(src proto.Message) { + xxx_messageInfo_Timestamp.Merge(dst, src) +} +func (m *Timestamp) XXX_Size() int { + return xxx_messageInfo_Timestamp.Size(m) +} +func (m *Timestamp) XXX_DiscardUnknown() { + xxx_messageInfo_Timestamp.DiscardUnknown(m) +} + +var xxx_messageInfo_Timestamp proto.InternalMessageInfo func (m *Timestamp) GetSeconds() int64 { if m != nil { @@ -281,36 +378,37 @@ func init() { proto.RegisterType((*Timestamp)(nil), "proto3.Timestamp") } -func init() { proto.RegisterFile("block.proto", fileDescriptor0) } +func init() { proto.RegisterFile("types/proto3/block.proto", fileDescriptor_block_57c41dfc0fc285b3) } -var fileDescriptor0 = []byte{ - // 443 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x6c, 0x53, 0xcd, 0x6a, 0xdb, 0x40, - 0x10, 0x46, 0xb5, 0x6c, 0xc7, 0x23, 0x3b, 0x4e, 0x86, 0xb6, 0x88, 0x9e, 0x8c, 0x68, 0x8b, 0x7b, - 0x31, 0x24, 0x39, 0x94, 0xd2, 0x93, 0x6b, 0x17, 0x12, 0x28, 0x21, 0x6c, 0x8d, 0xef, 0x1b, 0x6b, - 0xa9, 0x45, 0x2d, 0xad, 0xd0, 0xac, 0x4b, 0xde, 0xb0, 0xaf, 0x55, 0x66, 0x56, 0x52, 0x23, 0x93, - 0x93, 0xf7, 0xfb, 0x99, 0x6f, 0x76, 0xc7, 0x23, 0x88, 0x1e, 0x0f, 0x76, 0xf7, 0x7b, 0x51, 0x56, - 0xd6, 0x59, 0x1c, 0xc8, 0xcf, 0x4d, 0xf2, 0x05, 0x26, 0x0f, 0xba, 0x72, 0x3f, 0x8d, 0xbb, 0x35, - 0x3a, 0x35, 0x15, 0xbe, 0x86, 0xfe, 0xc6, 0x3a, 0x7d, 0x88, 0x83, 0x59, 0x30, 0xbf, 0x54, 0x1e, - 0x20, 0x42, 0x78, 0xab, 0x69, 0x1f, 0xbf, 0x9a, 0x05, 0xf3, 0xb1, 0x92, 0x73, 0xb2, 0x85, 0xe1, - 0x37, 0x4e, 0xbc, 0x5b, 0xb7, 0x72, 0xf0, 0x5f, 0xc6, 0xcf, 0x10, 0x71, 0x32, 0xf9, 0x5c, 0xa9, - 0x8c, 0xae, 0xdf, 0xf8, 0xf6, 0x37, 0x8b, 0x4e, 0x53, 0xf5, 0xdc, 0x99, 0xfc, 0x0d, 0x61, 0x50, - 0x5f, 0xe6, 0x13, 0x0c, 0xb7, 0xa6, 0xa2, 0xcc, 0x16, 0x12, 0x1d, 0x5d, 0x4f, 0x9b, 0xfa, 0x9a, - 0x56, 0x8d, 0x8e, 0x31, 0x0c, 0x57, 0x7b, 0x9d, 0x15, 0x77, 0x6b, 0x69, 0x35, 0x52, 0x0d, 0xc4, - 0xb7, 0x1c, 0x97, 0xfd, 0xda, 0xbb, 0xb8, 0x37, 0x0b, 0xe6, 0xa8, 0x6a, 0x84, 0x1f, 0x20, 0xdc, - 0x64, 0xb9, 0x89, 0x43, 0x49, 0xbe, 0x6c, 0x92, 0x99, 0x23, 0xa7, 0xf3, 0x52, 0x89, 0xcc, 0xe5, - 0xf7, 0xc7, 0x7c, 0xf3, 0x44, 0x71, 0xdf, 0x97, 0x7b, 0x84, 0xef, 0xe0, 0x4c, 0x66, 0xc3, 0xca, - 0x40, 0x94, 0x16, 0xe3, 0x15, 0x44, 0x3f, 0x34, 0xb9, 0x7a, 0x3c, 0xf1, 0xb0, 0x7b, 0xf7, 0x9a, - 0x56, 0xcf, 0x3d, 0xf8, 0x11, 0xce, 0x19, 0xae, 0x6c, 0x9e, 0x67, 0x4e, 0x86, 0x79, 0x26, 0xc3, - 0x3c, 0x61, 0xb9, 0xed, 0x5a, 0x3b, 0x2d, 0x8e, 0x91, 0x38, 0x5a, 0xcc, 0x19, 0x5b, 0x7d, 0xc8, - 0x52, 0xed, 0x6c, 0x45, 0xe2, 0x00, 0x9f, 0xd1, 0x65, 0x71, 0x01, 0x78, 0x6f, 0x9e, 0xdc, 0x89, - 0x37, 0x12, 0xef, 0x0b, 0x0a, 0xbe, 0x87, 0xc9, 0xca, 0x16, 0x64, 0x0a, 0x3a, 0x7a, 0xeb, 0x58, - 0xac, 0x5d, 0x92, 0xff, 0x81, 0x65, 0x59, 0x8a, 0x3e, 0x11, 0xbd, 0x81, 0x38, 0x87, 0x29, 0xbf, - 0x42, 0x19, 0x3a, 0x1e, 0x9c, 0x4f, 0x38, 0x17, 0xc7, 0x29, 0x8d, 0x09, 0x8c, 0xbf, 0xff, 0xc9, - 0x52, 0x53, 0xec, 0x8c, 0xd8, 0xa6, 0x62, 0xeb, 0x70, 0x9c, 0xf6, 0x50, 0xd9, 0xd2, 0x92, 0xa9, - 0x96, 0x69, 0x5a, 0x19, 0xa2, 0xf8, 0xc2, 0xa7, 0x9d, 0xd0, 0xc9, 0x55, 0xbb, 0x3e, 0xbc, 0xd6, - 0x32, 0x69, 0xd9, 0xa3, 0x50, 0x79, 0x80, 0x17, 0xd0, 0x5b, 0x96, 0xa5, 0x2c, 0x4c, 0xa8, 0xf8, - 0x98, 0x7c, 0x85, 0x51, 0xbb, 0x00, 0xfc, 0x22, 0x32, 0x3b, 0x5b, 0xa4, 0x24, 0x65, 0x3d, 0xd5, - 0x40, 0x8e, 0x2b, 0x74, 0x61, 0x49, 0x4a, 0xfb, 0xca, 0x83, 0xc7, 0xfa, 0xa3, 0xfa, 0x17, 0x00, - 0x00, 0xff, 0xff, 0x8f, 0x82, 0xc0, 0x0c, 0x6a, 0x03, 0x00, 0x00, +var fileDescriptor_block_57c41dfc0fc285b3 = []byte{ + // 451 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x6c, 0x53, 0x5f, 0x6f, 0xd3, 0x30, + 0x10, 0x57, 0x68, 0xda, 0xae, 0x97, 0x76, 0x1d, 0x27, 0x40, 0x16, 0x4f, 0x55, 0x04, 0xa8, 0xbc, + 0x74, 0xda, 0xf6, 0x80, 0x10, 0x4f, 0xa5, 0x45, 0xda, 0x24, 0x34, 0x4d, 0xa6, 0xea, 0xbb, 0xd7, + 0x58, 0x34, 0xa2, 0x89, 0xa3, 0x9c, 0x8b, 0xc6, 0x27, 0xe4, 0x6b, 0x21, 0x9f, 0x93, 0xd0, 0x44, + 0x7b, 0xf3, 0xef, 0xcf, 0xfd, 0xce, 0xbe, 0x5c, 0x40, 0xd8, 0x3f, 0x85, 0xa6, 0xcb, 0xa2, 0x34, + 0xd6, 0xdc, 0x5c, 0x3e, 0x1e, 0xcc, 0xee, 0xd7, 0x82, 0x01, 0x0e, 0x3c, 0x17, 0x7f, 0x86, 0xc9, + 0x83, 0x2a, 0xed, 0x0f, 0x6d, 0x6f, 0xb5, 0x4a, 0x74, 0x89, 0xaf, 0xa0, 0xbf, 0x31, 0x56, 0x1d, + 0x44, 0x30, 0x0b, 0xe6, 0x7d, 0xe9, 0x01, 0x22, 0x84, 0xb7, 0x8a, 0xf6, 0xe2, 0xc5, 0x2c, 0x98, + 0x8f, 0x25, 0x9f, 0xe3, 0x2d, 0x0c, 0xbf, 0xba, 0xc4, 0xbb, 0x75, 0x23, 0x07, 0xff, 0x65, 0xfc, + 0x04, 0x91, 0x4b, 0x26, 0x9f, 0xcb, 0x95, 0xd1, 0xf5, 0x6b, 0xdf, 0xfe, 0x66, 0xd1, 0x6a, 0x2a, + 0x4f, 0x9d, 0xf1, 0xdf, 0x10, 0x06, 0xd5, 0x65, 0x3e, 0xc2, 0x70, 0xab, 0x4b, 0x4a, 0x4d, 0xce, + 0xd1, 0xd1, 0xf5, 0xb4, 0xae, 0xaf, 0x68, 0x59, 0xeb, 0x28, 0x60, 0xb8, 0xda, 0xab, 0x34, 0xbf, + 0x5b, 0x73, 0xab, 0x91, 0xac, 0x21, 0xbe, 0x71, 0x71, 0xe9, 0xcf, 0xbd, 0x15, 0xbd, 0x59, 0x30, + 0xef, 0xc9, 0x0a, 0xe1, 0x7b, 0x08, 0x37, 0x69, 0xa6, 0x45, 0xc8, 0xc9, 0x2f, 0xeb, 0x64, 0xc7, + 0x91, 0x55, 0x59, 0x21, 0x59, 0x76, 0xe5, 0xf7, 0xc7, 0x6c, 0xf3, 0x44, 0xa2, 0xef, 0xcb, 0x3d, + 0xc2, 0xb7, 0x70, 0xc6, 0xb3, 0x71, 0xca, 0x80, 0x95, 0x06, 0xe3, 0x15, 0x44, 0xdf, 0x15, 0xd9, + 0x6a, 0x3c, 0x62, 0xd8, 0xbe, 0x7b, 0x45, 0xcb, 0x53, 0x0f, 0x7e, 0x80, 0x73, 0x07, 0x57, 0x26, + 0xcb, 0x52, 0xcb, 0xc3, 0x3c, 0xe3, 0x61, 0x76, 0x58, 0xd7, 0x76, 0xad, 0xac, 0x62, 0xc7, 0x88, + 0x1d, 0x0d, 0x76, 0x19, 0x5b, 0x75, 0x48, 0x13, 0x65, 0x4d, 0x49, 0xec, 0x00, 0x9f, 0xd1, 0x66, + 0x71, 0x01, 0x78, 0xaf, 0x9f, 0x6c, 0xc7, 0x1b, 0xb1, 0xf7, 0x19, 0x05, 0xdf, 0xc1, 0x64, 0x65, + 0x72, 0xd2, 0x39, 0x1d, 0xbd, 0x75, 0xcc, 0xd6, 0x36, 0xe9, 0xbe, 0xc0, 0xb2, 0x28, 0x58, 0x9f, + 0xb0, 0x5e, 0x43, 0x9c, 0xc3, 0xd4, 0xbd, 0x42, 0x6a, 0x3a, 0x1e, 0xac, 0x4f, 0x38, 0x67, 0x47, + 0x97, 0xc6, 0x18, 0xc6, 0xdf, 0x7e, 0xa7, 0x89, 0xce, 0x77, 0x9a, 0x6d, 0x53, 0xb6, 0xb5, 0x38, + 0x97, 0xf6, 0x50, 0x9a, 0xc2, 0x90, 0x2e, 0x97, 0x49, 0x52, 0x6a, 0x22, 0x71, 0xe1, 0xd3, 0x3a, + 0x74, 0x7c, 0xd5, 0xac, 0x8f, 0x5b, 0x6b, 0x9e, 0x34, 0xef, 0x51, 0x28, 0x3d, 0xc0, 0x0b, 0xe8, + 0x2d, 0x8b, 0x82, 0x17, 0x26, 0x94, 0xee, 0x18, 0x7f, 0x81, 0x51, 0xb3, 0x00, 0xee, 0x45, 0xa4, + 0x77, 0x26, 0x4f, 0x88, 0xcb, 0x7a, 0xb2, 0x86, 0x2e, 0x2e, 0x57, 0xb9, 0x21, 0x2e, 0xed, 0x4b, + 0x0f, 0x1e, 0xab, 0x9f, 0xea, 0x5f, 0x00, 0x00, 0x00, 0xff, 0xff, 0x4f, 0x84, 0xb5, 0xf8, 0x77, + 0x03, 0x00, 0x00, } diff --git a/types/proto3/block.proto b/types/proto3/block.proto index 1c76746c..93cf1bc7 100644 --- a/types/proto3/block.proto +++ b/types/proto3/block.proto @@ -4,7 +4,7 @@ package proto3; message PartSetHeader { - sint32 Total = 1; + int32 Total = 1; bytes Hash = 2; } @@ -17,10 +17,10 @@ message Header { // basic block info Version Version = 1; string ChainID = 2; - sint64 Height = 3; + int64 Height = 3; Timestamp Time = 4; - sint64 NumTxs = 5; - sint64 TotalTxs = 6; + int64 NumTxs = 5; + int64 TotalTxs = 6; // prev block info BlockID LastBlockID = 7; diff --git a/types/vote.go b/types/vote.go index e1095bf1..333684fc 100644 --- a/types/vote.go +++ b/types/vote.go @@ -12,7 +12,7 @@ import ( const ( // MaxVoteBytes is a maximum vote size (including amino overhead). - MaxVoteBytes int64 = 203 + MaxVoteBytes int64 = 199 ) var ( From b24de1c01cc8627cf72b4f5c1295f436a9904aed Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Mon, 29 Oct 2018 10:13:11 -0400 Subject: [PATCH 039/267] update changelog pending and readme (#2725) --- CHANGELOG_PENDING.md | 17 +++++++++++------ README.md | 2 +- 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/CHANGELOG_PENDING.md b/CHANGELOG_PENDING.md index a56487ff..b8a9102a 100644 --- a/CHANGELOG_PENDING.md +++ b/CHANGELOG_PENDING.md @@ -2,7 +2,7 @@ ## v0.26.0 -*October 19, 2018* +*October 29, 2018* Special thanks to external contributors on this release: @bradyjoestar, @connorwstein, @goolAdapter, @HaoyangLiu, @@ -17,9 +17,14 @@ It also includes our first take at a generalized merkle proof system. See the [UPGRADING.md](UPGRADING.md#v0.26.0) for details on upgrading to the new version. +Please note that we are still making breaking changes to the protocols. +While the new Version fields should help us to keep the software backwards compatible +even while upgrading the protocols, we cannot guarantee that new releases will +be compatible with old chains just yet. Thanks for bearing with us! + Friendly reminder, we have a [bug bounty program](https://hackerone.com/tendermint). -BREAKING CHANGES: +### BREAKING CHANGES: * CLI/RPC/Config * [config] [\#2232](https://github.com/tendermint/tendermint/issues/2232) timeouts as time.Duration, not ints @@ -50,7 +55,6 @@ BREAKING CHANGES: * [types] [\#2298](https://github.com/tendermint/tendermint/issues/2298) Remove `Index` and `Total` fields from `TxProof`. * [types] [\#2598](https://github.com/tendermint/tendermint/issues/2598) `VoteTypeXxx` are now of type `SignedMsgType byte` and named `XxxType`, eg. `PrevoteType`, `PrecommitType`. - * [types] [\#2682](https://github.com/tendermint/tendermint/issues/2682) Use proto3 `varint` encoding for ints that are usually unsigned (instead of zigzag encoding). * Blockchain Protocol * [types] Update SignBytes for `Vote`/`Proposal`/`Heartbeat`: @@ -66,17 +70,18 @@ BREAKING CHANGES: * [state] [\#2644](https://github.com/tendermint/tendermint/issues/2644) Require block.Version to match state.Version * [types] [\#2670](https://github.com/tendermint/tendermint/issues/2670) Header.Hash() builds Merkle tree out of fields in the same order they appear in the header, instead of sorting by field name + * [types] [\#2682](https://github.com/tendermint/tendermint/issues/2682) Use proto3 `varint` encoding for ints that are usually unsigned (instead of zigzag encoding). * P2P Protocol * [p2p] [\#2654](https://github.com/tendermint/tendermint/issues/2654) Add `ProtocolVersion` struct with protocol versions to top of DefaultNodeInfo and require `ProtocolVersion.Block` to match during peer handshake -FEATURES: +### FEATURES: - [abci] [\#2557](https://github.com/tendermint/tendermint/issues/2557) Add `Codespace` field to `Response{CheckTx, DeliverTx, Query}` - [abci] [\#2662](https://github.com/tendermint/tendermint/issues/2662) Add `BlockVersion` and `P2PVersion` to `RequestInfo` - [crypto/merkle] [\#2298](https://github.com/tendermint/tendermint/issues/2298) General Merkle Proof scheme for chaining various types of Merkle trees together -IMPROVEMENTS: +### IMPROVEMENTS: - Additional Metrics - [consensus] [\#2169](https://github.com/cosmos/cosmos-sdk/issues/2169) - [p2p] [\#2169](https://github.com/cosmos/cosmos-sdk/issues/2169) @@ -85,7 +90,7 @@ IMPROVEMENTS: github.com/tendermint/crypto - [tools] [\#2238](https://github.com/tendermint/tendermint/issues/2238) Binary dependencies are now locked to a specific git commit -BUG FIXES: +### BUG FIXES: - [autofile] [\#2428](https://github.com/tendermint/tendermint/issues/2428) Group.RotateFile need call Flush() before rename (@goolAdapter) - [common] [\#2533](https://github.com/tendermint/tendermint/issues/2533) Fixed a bug in the `BitArray.Or` method - [common] [\#2506](https://github.com/tendermint/tendermint/issues/2506) Fixed a bug in the `BitArray.Sub` method (@james-ray) diff --git a/README.md b/README.md index 069f9f13..328557ae 100644 --- a/README.md +++ b/README.md @@ -36,7 +36,7 @@ We are also still making breaking changes to the protocol and the APIs. Thus, we tag the releases as *alpha software*. In any case, if you intend to run Tendermint in production, -please [contact us](https://riot.im/app/#/room/#tendermint:matrix.org) :) +please [contact us](mailto:partners@tendermint.com) and [join the chat](https://riot.im/app/#/room/#tendermint:matrix.org). ## Security From cdc252b8182deb749150c33a2cc985e12e3c8437 Mon Sep 17 00:00:00 2001 From: Zach Date: Tue, 30 Oct 2018 10:34:51 -0400 Subject: [PATCH 040/267] add fail-test file instead of dep, closes #2638 (#2728) original author of this file is @ebuchman: https://github.com/ebuchman/fail-test --- Gopkg.lock | 8 ----- Gopkg.toml | 4 --- consensus/state.go | 2 +- libs/fail/fail.go | 78 ++++++++++++++++++++++++++++++++++++++++++++++ state/execution.go | 2 +- 5 files changed, 80 insertions(+), 14 deletions(-) create mode 100644 libs/fail/fail.go diff --git a/Gopkg.lock b/Gopkg.lock index 513e0bd7..f4656e6b 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -35,13 +35,6 @@ revision = "8991bc29aa16c548c550c7ff78260e27b9ab7c73" version = "v1.1.1" -[[projects]] - digest = "1:c7644c73a3d23741fdba8a99b1464e021a224b7e205be497271a8003a15ca41b" - name = "github.com/ebuchman/fail-test" - packages = ["."] - pruneopts = "UT" - revision = "95f809107225be108efcf10a3509e4ea6ceef3c4" - [[projects]] digest = "1:544229a3ca0fb2dd5ebc2896d3d2ff7ce096d9751635301e44e37e761349ee70" name = "github.com/fortytw2/leaktest" @@ -503,7 +496,6 @@ input-imports = [ "github.com/btcsuite/btcutil/base58", "github.com/btcsuite/btcutil/bech32", - "github.com/ebuchman/fail-test", "github.com/fortytw2/leaktest", "github.com/go-kit/kit/log", "github.com/go-kit/kit/log/level", diff --git a/Gopkg.toml b/Gopkg.toml index 955d6c6d..47418bef 100644 --- a/Gopkg.toml +++ b/Gopkg.toml @@ -81,10 +81,6 @@ name = "github.com/jmhodges/levigo" revision = "c42d9e0ca023e2198120196f842701bb4c55d7b9" -[[constraint]] - name = "github.com/ebuchman/fail-test" - revision = "95f809107225be108efcf10a3509e4ea6ceef3c4" - # last revision used by go-crypto [[constraint]] name = "github.com/btcsuite/btcutil" diff --git a/consensus/state.go b/consensus/state.go index 0b079f13..40aeeb7a 100644 --- a/consensus/state.go +++ b/consensus/state.go @@ -9,8 +9,8 @@ import ( "sync" "time" - fail "github.com/ebuchman/fail-test" cmn "github.com/tendermint/tendermint/libs/common" + "github.com/tendermint/tendermint/libs/fail" "github.com/tendermint/tendermint/libs/log" tmtime "github.com/tendermint/tendermint/types/time" diff --git a/libs/fail/fail.go b/libs/fail/fail.go new file mode 100644 index 00000000..edfca13e --- /dev/null +++ b/libs/fail/fail.go @@ -0,0 +1,78 @@ +package fail + +import ( + "fmt" + "math/rand" + "os" + "strconv" +) + +var callIndexToFail int + +func init() { + callIndexToFailS := os.Getenv("FAIL_TEST_INDEX") + + if callIndexToFailS == "" { + callIndexToFail = -1 + } else { + var err error + callIndexToFail, err = strconv.Atoi(callIndexToFailS) + if err != nil { + callIndexToFail = -1 + } + } +} + +// Fail when FAIL_TEST_INDEX == callIndex +var ( + callIndex int //indexes Fail calls + + callRandIndex int // indexes a run of FailRand calls + callRandIndexToFail = -1 // the callRandIndex to fail on in FailRand +) + +func Fail() { + if callIndexToFail < 0 { + return + } + + if callIndex == callIndexToFail { + Exit() + } + + callIndex += 1 +} + +// FailRand should be called n successive times. +// It will fail on a random one of those calls +// n must be greater than 0 +func FailRand(n int) { + if callIndexToFail < 0 { + return + } + + if callRandIndexToFail < 0 { + // first call in the loop, pick a random index to fail at + callRandIndexToFail = rand.Intn(n) + callRandIndex = 0 + } + + if callIndex == callIndexToFail { + if callRandIndex == callRandIndexToFail { + Exit() + } + } + + callRandIndex += 1 + + if callRandIndex == n { + callIndex += 1 + } +} + +func Exit() { + fmt.Printf("*** fail-test %d ***\n", callIndex) + proc, _ := os.FindProcess(os.Getpid()) + proc.Signal(os.Interrupt) + // panic(fmt.Sprintf("*** fail-test %d ***", callIndex)) +} diff --git a/state/execution.go b/state/execution.go index 68298a8d..72f6cc97 100644 --- a/state/execution.go +++ b/state/execution.go @@ -4,9 +4,9 @@ import ( "fmt" "time" - "github.com/ebuchman/fail-test" abci "github.com/tendermint/tendermint/abci/types" dbm "github.com/tendermint/tendermint/libs/db" + "github.com/tendermint/tendermint/libs/fail" "github.com/tendermint/tendermint/libs/log" "github.com/tendermint/tendermint/mempool" "github.com/tendermint/tendermint/proxy" From 56d7160606b16dc0a3d56c9b9b9d2b9aeb2c6484 Mon Sep 17 00:00:00 2001 From: Dev Ojha Date: Tue, 30 Oct 2018 08:36:53 -0700 Subject: [PATCH 041/267] Add ValidatorPubkeyTypes as a consensus param (#2636) * Add ValidatorPubkeyTypes as a consensus param Ref #2414 * update spec * address anton's comment * Switch to Validator and Validator Params * Correct changelog entry * Address bucky's comments! * forgot to update changelog * fix typo * fix Params naming --- CHANGELOG_PENDING.md | 2 + abci/types/types.pb.go | 914 ++++++++++++++++++++++++------------- abci/types/types.proto | 13 +- abci/types/typespb_test.go | 158 ++++++- docs/spec/abci/abci.md | 13 +- evidence/pool.go | 2 +- evidence/pool_test.go | 2 +- evidence/reactor.go | 2 +- state/execution.go | 2 +- state/state_test.go | 8 +- state/store.go | 2 +- state/validation.go | 2 +- types/params.go | 72 ++- types/params_test.go | 69 +-- types/protobuf.go | 24 +- types/protobuf_test.go | 1 - 16 files changed, 893 insertions(+), 393 deletions(-) diff --git a/CHANGELOG_PENDING.md b/CHANGELOG_PENDING.md index b8a9102a..5c25a4b1 100644 --- a/CHANGELOG_PENDING.md +++ b/CHANGELOG_PENDING.md @@ -57,6 +57,8 @@ Friendly reminder, we have a [bug bounty program](https://hackerone.com/tendermi `PrecommitType`. * Blockchain Protocol + * [abci] [\#2636](https://github.com/tendermint/tendermint/issues/2636) Add ValidatorParams field to ConsensusParams. + (Used to control which pubkey types validators can use, by abci type) * [types] Update SignBytes for `Vote`/`Proposal`/`Heartbeat`: * [\#2459](https://github.com/tendermint/tendermint/issues/2459) Use amino encoding instead of JSON in `SignBytes`. * [\#2598](https://github.com/tendermint/tendermint/issues/2598) Reorder fields and use fixed sized encoding. diff --git a/abci/types/types.pb.go b/abci/types/types.pb.go index 6a70bb97..c867dffc 100644 --- a/abci/types/types.pb.go +++ b/abci/types/types.pb.go @@ -61,7 +61,7 @@ func (m *Request) Reset() { *m = Request{} } func (m *Request) String() string { return proto.CompactTextString(m) } func (*Request) ProtoMessage() {} func (*Request) Descriptor() ([]byte, []int) { - return fileDescriptor_types_4449c1011851ea19, []int{0} + return fileDescriptor_types_5b877df1938afe10, []int{0} } func (m *Request) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -483,7 +483,7 @@ func (m *RequestEcho) Reset() { *m = RequestEcho{} } func (m *RequestEcho) String() string { return proto.CompactTextString(m) } func (*RequestEcho) ProtoMessage() {} func (*RequestEcho) Descriptor() ([]byte, []int) { - return fileDescriptor_types_4449c1011851ea19, []int{1} + return fileDescriptor_types_5b877df1938afe10, []int{1} } func (m *RequestEcho) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -529,7 +529,7 @@ func (m *RequestFlush) Reset() { *m = RequestFlush{} } func (m *RequestFlush) String() string { return proto.CompactTextString(m) } func (*RequestFlush) ProtoMessage() {} func (*RequestFlush) Descriptor() ([]byte, []int) { - return fileDescriptor_types_4449c1011851ea19, []int{2} + return fileDescriptor_types_5b877df1938afe10, []int{2} } func (m *RequestFlush) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -571,7 +571,7 @@ func (m *RequestInfo) Reset() { *m = RequestInfo{} } func (m *RequestInfo) String() string { return proto.CompactTextString(m) } func (*RequestInfo) ProtoMessage() {} func (*RequestInfo) Descriptor() ([]byte, []int) { - return fileDescriptor_types_4449c1011851ea19, []int{3} + return fileDescriptor_types_5b877df1938afe10, []int{3} } func (m *RequestInfo) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -634,7 +634,7 @@ func (m *RequestSetOption) Reset() { *m = RequestSetOption{} } func (m *RequestSetOption) String() string { return proto.CompactTextString(m) } func (*RequestSetOption) ProtoMessage() {} func (*RequestSetOption) Descriptor() ([]byte, []int) { - return fileDescriptor_types_4449c1011851ea19, []int{4} + return fileDescriptor_types_5b877df1938afe10, []int{4} } func (m *RequestSetOption) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -692,7 +692,7 @@ func (m *RequestInitChain) Reset() { *m = RequestInitChain{} } func (m *RequestInitChain) String() string { return proto.CompactTextString(m) } func (*RequestInitChain) ProtoMessage() {} func (*RequestInitChain) Descriptor() ([]byte, []int) { - return fileDescriptor_types_4449c1011851ea19, []int{5} + return fileDescriptor_types_5b877df1938afe10, []int{5} } func (m *RequestInitChain) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -770,7 +770,7 @@ func (m *RequestQuery) Reset() { *m = RequestQuery{} } func (m *RequestQuery) String() string { return proto.CompactTextString(m) } func (*RequestQuery) ProtoMessage() {} func (*RequestQuery) Descriptor() ([]byte, []int) { - return fileDescriptor_types_4449c1011851ea19, []int{6} + return fileDescriptor_types_5b877df1938afe10, []int{6} } func (m *RequestQuery) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -827,7 +827,6 @@ func (m *RequestQuery) GetProve() bool { return false } -// NOTE: validators here have empty pubkeys. type RequestBeginBlock struct { Hash []byte `protobuf:"bytes,1,opt,name=hash,proto3" json:"hash,omitempty"` Header Header `protobuf:"bytes,2,opt,name=header" json:"header"` @@ -842,7 +841,7 @@ func (m *RequestBeginBlock) Reset() { *m = RequestBeginBlock{} } func (m *RequestBeginBlock) String() string { return proto.CompactTextString(m) } func (*RequestBeginBlock) ProtoMessage() {} func (*RequestBeginBlock) Descriptor() ([]byte, []int) { - return fileDescriptor_types_4449c1011851ea19, []int{7} + return fileDescriptor_types_5b877df1938afe10, []int{7} } func (m *RequestBeginBlock) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -910,7 +909,7 @@ func (m *RequestCheckTx) Reset() { *m = RequestCheckTx{} } func (m *RequestCheckTx) String() string { return proto.CompactTextString(m) } func (*RequestCheckTx) ProtoMessage() {} func (*RequestCheckTx) Descriptor() ([]byte, []int) { - return fileDescriptor_types_4449c1011851ea19, []int{8} + return fileDescriptor_types_5b877df1938afe10, []int{8} } func (m *RequestCheckTx) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -957,7 +956,7 @@ func (m *RequestDeliverTx) Reset() { *m = RequestDeliverTx{} } func (m *RequestDeliverTx) String() string { return proto.CompactTextString(m) } func (*RequestDeliverTx) ProtoMessage() {} func (*RequestDeliverTx) Descriptor() ([]byte, []int) { - return fileDescriptor_types_4449c1011851ea19, []int{9} + return fileDescriptor_types_5b877df1938afe10, []int{9} } func (m *RequestDeliverTx) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -1004,7 +1003,7 @@ func (m *RequestEndBlock) Reset() { *m = RequestEndBlock{} } func (m *RequestEndBlock) String() string { return proto.CompactTextString(m) } func (*RequestEndBlock) ProtoMessage() {} func (*RequestEndBlock) Descriptor() ([]byte, []int) { - return fileDescriptor_types_4449c1011851ea19, []int{10} + return fileDescriptor_types_5b877df1938afe10, []int{10} } func (m *RequestEndBlock) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -1050,7 +1049,7 @@ func (m *RequestCommit) Reset() { *m = RequestCommit{} } func (m *RequestCommit) String() string { return proto.CompactTextString(m) } func (*RequestCommit) ProtoMessage() {} func (*RequestCommit) Descriptor() ([]byte, []int) { - return fileDescriptor_types_4449c1011851ea19, []int{11} + return fileDescriptor_types_5b877df1938afe10, []int{11} } func (m *RequestCommit) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -1103,7 +1102,7 @@ func (m *Response) Reset() { *m = Response{} } func (m *Response) String() string { return proto.CompactTextString(m) } func (*Response) ProtoMessage() {} func (*Response) Descriptor() ([]byte, []int) { - return fileDescriptor_types_4449c1011851ea19, []int{12} + return fileDescriptor_types_5b877df1938afe10, []int{12} } func (m *Response) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -1556,7 +1555,7 @@ func (m *ResponseException) Reset() { *m = ResponseException{} } func (m *ResponseException) String() string { return proto.CompactTextString(m) } func (*ResponseException) ProtoMessage() {} func (*ResponseException) Descriptor() ([]byte, []int) { - return fileDescriptor_types_4449c1011851ea19, []int{13} + return fileDescriptor_types_5b877df1938afe10, []int{13} } func (m *ResponseException) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -1603,7 +1602,7 @@ func (m *ResponseEcho) Reset() { *m = ResponseEcho{} } func (m *ResponseEcho) String() string { return proto.CompactTextString(m) } func (*ResponseEcho) ProtoMessage() {} func (*ResponseEcho) Descriptor() ([]byte, []int) { - return fileDescriptor_types_4449c1011851ea19, []int{14} + return fileDescriptor_types_5b877df1938afe10, []int{14} } func (m *ResponseEcho) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -1649,7 +1648,7 @@ func (m *ResponseFlush) Reset() { *m = ResponseFlush{} } func (m *ResponseFlush) String() string { return proto.CompactTextString(m) } func (*ResponseFlush) ProtoMessage() {} func (*ResponseFlush) Descriptor() ([]byte, []int) { - return fileDescriptor_types_4449c1011851ea19, []int{15} + return fileDescriptor_types_5b877df1938afe10, []int{15} } func (m *ResponseFlush) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -1693,7 +1692,7 @@ func (m *ResponseInfo) Reset() { *m = ResponseInfo{} } func (m *ResponseInfo) String() string { return proto.CompactTextString(m) } func (*ResponseInfo) ProtoMessage() {} func (*ResponseInfo) Descriptor() ([]byte, []int) { - return fileDescriptor_types_4449c1011851ea19, []int{16} + return fileDescriptor_types_5b877df1938afe10, []int{16} } func (m *ResponseInfo) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -1772,7 +1771,7 @@ func (m *ResponseSetOption) Reset() { *m = ResponseSetOption{} } func (m *ResponseSetOption) String() string { return proto.CompactTextString(m) } func (*ResponseSetOption) ProtoMessage() {} func (*ResponseSetOption) Descriptor() ([]byte, []int) { - return fileDescriptor_types_4449c1011851ea19, []int{17} + return fileDescriptor_types_5b877df1938afe10, []int{17} } func (m *ResponseSetOption) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -1834,7 +1833,7 @@ func (m *ResponseInitChain) Reset() { *m = ResponseInitChain{} } func (m *ResponseInitChain) String() string { return proto.CompactTextString(m) } func (*ResponseInitChain) ProtoMessage() {} func (*ResponseInitChain) Descriptor() ([]byte, []int) { - return fileDescriptor_types_4449c1011851ea19, []int{18} + return fileDescriptor_types_5b877df1938afe10, []int{18} } func (m *ResponseInitChain) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -1897,7 +1896,7 @@ func (m *ResponseQuery) Reset() { *m = ResponseQuery{} } func (m *ResponseQuery) String() string { return proto.CompactTextString(m) } func (*ResponseQuery) ProtoMessage() {} func (*ResponseQuery) Descriptor() ([]byte, []int) { - return fileDescriptor_types_4449c1011851ea19, []int{19} + return fileDescriptor_types_5b877df1938afe10, []int{19} } func (m *ResponseQuery) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -2000,7 +1999,7 @@ func (m *ResponseBeginBlock) Reset() { *m = ResponseBeginBlock{} } func (m *ResponseBeginBlock) String() string { return proto.CompactTextString(m) } func (*ResponseBeginBlock) ProtoMessage() {} func (*ResponseBeginBlock) Descriptor() ([]byte, []int) { - return fileDescriptor_types_4449c1011851ea19, []int{20} + return fileDescriptor_types_5b877df1938afe10, []int{20} } func (m *ResponseBeginBlock) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -2054,7 +2053,7 @@ func (m *ResponseCheckTx) Reset() { *m = ResponseCheckTx{} } func (m *ResponseCheckTx) String() string { return proto.CompactTextString(m) } func (*ResponseCheckTx) ProtoMessage() {} func (*ResponseCheckTx) Descriptor() ([]byte, []int) { - return fileDescriptor_types_4449c1011851ea19, []int{21} + return fileDescriptor_types_5b877df1938afe10, []int{21} } func (m *ResponseCheckTx) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -2157,7 +2156,7 @@ func (m *ResponseDeliverTx) Reset() { *m = ResponseDeliverTx{} } func (m *ResponseDeliverTx) String() string { return proto.CompactTextString(m) } func (*ResponseDeliverTx) ProtoMessage() {} func (*ResponseDeliverTx) Descriptor() ([]byte, []int) { - return fileDescriptor_types_4449c1011851ea19, []int{22} + return fileDescriptor_types_5b877df1938afe10, []int{22} } func (m *ResponseDeliverTx) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -2255,7 +2254,7 @@ func (m *ResponseEndBlock) Reset() { *m = ResponseEndBlock{} } func (m *ResponseEndBlock) String() string { return proto.CompactTextString(m) } func (*ResponseEndBlock) ProtoMessage() {} func (*ResponseEndBlock) Descriptor() ([]byte, []int) { - return fileDescriptor_types_4449c1011851ea19, []int{23} + return fileDescriptor_types_5b877df1938afe10, []int{23} } func (m *ResponseEndBlock) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -2317,7 +2316,7 @@ func (m *ResponseCommit) Reset() { *m = ResponseCommit{} } func (m *ResponseCommit) String() string { return proto.CompactTextString(m) } func (*ResponseCommit) ProtoMessage() {} func (*ResponseCommit) Descriptor() ([]byte, []int) { - return fileDescriptor_types_4449c1011851ea19, []int{24} + return fileDescriptor_types_5b877df1938afe10, []int{24} } func (m *ResponseCommit) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -2356,18 +2355,19 @@ func (m *ResponseCommit) GetData() []byte { // ConsensusParams contains all consensus-relevant parameters // that can be adjusted by the abci app type ConsensusParams struct { - BlockSize *BlockSize `protobuf:"bytes,1,opt,name=block_size,json=blockSize" json:"block_size,omitempty"` - EvidenceParams *EvidenceParams `protobuf:"bytes,2,opt,name=evidence_params,json=evidenceParams" json:"evidence_params,omitempty"` - XXX_NoUnkeyedLiteral struct{} `json:"-"` - XXX_unrecognized []byte `json:"-"` - XXX_sizecache int32 `json:"-"` + BlockSize *BlockSizeParams `protobuf:"bytes,1,opt,name=block_size,json=blockSize" json:"block_size,omitempty"` + Evidence *EvidenceParams `protobuf:"bytes,2,opt,name=evidence" json:"evidence,omitempty"` + Validator *ValidatorParams `protobuf:"bytes,3,opt,name=validator" json:"validator,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` } func (m *ConsensusParams) Reset() { *m = ConsensusParams{} } func (m *ConsensusParams) String() string { return proto.CompactTextString(m) } func (*ConsensusParams) ProtoMessage() {} func (*ConsensusParams) Descriptor() ([]byte, []int) { - return fileDescriptor_types_4449c1011851ea19, []int{25} + return fileDescriptor_types_5b877df1938afe10, []int{25} } func (m *ConsensusParams) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -2396,22 +2396,29 @@ func (m *ConsensusParams) XXX_DiscardUnknown() { var xxx_messageInfo_ConsensusParams proto.InternalMessageInfo -func (m *ConsensusParams) GetBlockSize() *BlockSize { +func (m *ConsensusParams) GetBlockSize() *BlockSizeParams { if m != nil { return m.BlockSize } return nil } -func (m *ConsensusParams) GetEvidenceParams() *EvidenceParams { +func (m *ConsensusParams) GetEvidence() *EvidenceParams { if m != nil { - return m.EvidenceParams + return m.Evidence + } + return nil +} + +func (m *ConsensusParams) GetValidator() *ValidatorParams { + if m != nil { + return m.Validator } return nil } // BlockSize contains limits on the block size. -type BlockSize struct { +type BlockSizeParams struct { // Note: must be greater than 0 MaxBytes int64 `protobuf:"varint,1,opt,name=max_bytes,json=maxBytes,proto3" json:"max_bytes,omitempty"` // Note: must be greater or equal to -1 @@ -2421,18 +2428,18 @@ type BlockSize struct { XXX_sizecache int32 `json:"-"` } -func (m *BlockSize) Reset() { *m = BlockSize{} } -func (m *BlockSize) String() string { return proto.CompactTextString(m) } -func (*BlockSize) ProtoMessage() {} -func (*BlockSize) Descriptor() ([]byte, []int) { - return fileDescriptor_types_4449c1011851ea19, []int{26} +func (m *BlockSizeParams) Reset() { *m = BlockSizeParams{} } +func (m *BlockSizeParams) String() string { return proto.CompactTextString(m) } +func (*BlockSizeParams) ProtoMessage() {} +func (*BlockSizeParams) Descriptor() ([]byte, []int) { + return fileDescriptor_types_5b877df1938afe10, []int{26} } -func (m *BlockSize) XXX_Unmarshal(b []byte) error { +func (m *BlockSizeParams) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) } -func (m *BlockSize) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { +func (m *BlockSizeParams) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { if deterministic { - return xxx_messageInfo_BlockSize.Marshal(b, m, deterministic) + return xxx_messageInfo_BlockSizeParams.Marshal(b, m, deterministic) } else { b = b[:cap(b)] n, err := m.MarshalTo(b) @@ -2442,26 +2449,26 @@ func (m *BlockSize) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { return b[:n], nil } } -func (dst *BlockSize) XXX_Merge(src proto.Message) { - xxx_messageInfo_BlockSize.Merge(dst, src) +func (dst *BlockSizeParams) XXX_Merge(src proto.Message) { + xxx_messageInfo_BlockSizeParams.Merge(dst, src) } -func (m *BlockSize) XXX_Size() int { +func (m *BlockSizeParams) XXX_Size() int { return m.Size() } -func (m *BlockSize) XXX_DiscardUnknown() { - xxx_messageInfo_BlockSize.DiscardUnknown(m) +func (m *BlockSizeParams) XXX_DiscardUnknown() { + xxx_messageInfo_BlockSizeParams.DiscardUnknown(m) } -var xxx_messageInfo_BlockSize proto.InternalMessageInfo +var xxx_messageInfo_BlockSizeParams proto.InternalMessageInfo -func (m *BlockSize) GetMaxBytes() int64 { +func (m *BlockSizeParams) GetMaxBytes() int64 { if m != nil { return m.MaxBytes } return 0 } -func (m *BlockSize) GetMaxGas() int64 { +func (m *BlockSizeParams) GetMaxGas() int64 { if m != nil { return m.MaxGas } @@ -2481,7 +2488,7 @@ func (m *EvidenceParams) Reset() { *m = EvidenceParams{} } func (m *EvidenceParams) String() string { return proto.CompactTextString(m) } func (*EvidenceParams) ProtoMessage() {} func (*EvidenceParams) Descriptor() ([]byte, []int) { - return fileDescriptor_types_4449c1011851ea19, []int{27} + return fileDescriptor_types_5b877df1938afe10, []int{27} } func (m *EvidenceParams) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -2517,6 +2524,54 @@ func (m *EvidenceParams) GetMaxAge() int64 { return 0 } +// ValidatorParams contains limits on validators. +type ValidatorParams struct { + PubKeyTypes []string `protobuf:"bytes,1,rep,name=pub_key_types,json=pubKeyTypes" json:"pub_key_types,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *ValidatorParams) Reset() { *m = ValidatorParams{} } +func (m *ValidatorParams) String() string { return proto.CompactTextString(m) } +func (*ValidatorParams) ProtoMessage() {} +func (*ValidatorParams) Descriptor() ([]byte, []int) { + return fileDescriptor_types_5b877df1938afe10, []int{28} +} +func (m *ValidatorParams) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *ValidatorParams) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_ValidatorParams.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalTo(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (dst *ValidatorParams) XXX_Merge(src proto.Message) { + xxx_messageInfo_ValidatorParams.Merge(dst, src) +} +func (m *ValidatorParams) XXX_Size() int { + return m.Size() +} +func (m *ValidatorParams) XXX_DiscardUnknown() { + xxx_messageInfo_ValidatorParams.DiscardUnknown(m) +} + +var xxx_messageInfo_ValidatorParams proto.InternalMessageInfo + +func (m *ValidatorParams) GetPubKeyTypes() []string { + if m != nil { + return m.PubKeyTypes + } + return nil +} + type LastCommitInfo struct { Round int32 `protobuf:"varint,1,opt,name=round,proto3" json:"round,omitempty"` Votes []VoteInfo `protobuf:"bytes,2,rep,name=votes" json:"votes"` @@ -2529,7 +2584,7 @@ func (m *LastCommitInfo) Reset() { *m = LastCommitInfo{} } func (m *LastCommitInfo) String() string { return proto.CompactTextString(m) } func (*LastCommitInfo) ProtoMessage() {} func (*LastCommitInfo) Descriptor() ([]byte, []int) { - return fileDescriptor_types_4449c1011851ea19, []int{28} + return fileDescriptor_types_5b877df1938afe10, []int{29} } func (m *LastCommitInfo) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -2603,7 +2658,7 @@ func (m *Header) Reset() { *m = Header{} } func (m *Header) String() string { return proto.CompactTextString(m) } func (*Header) ProtoMessage() {} func (*Header) Descriptor() ([]byte, []int) { - return fileDescriptor_types_4449c1011851ea19, []int{29} + return fileDescriptor_types_5b877df1938afe10, []int{30} } func (m *Header) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -2756,7 +2811,7 @@ func (m *Version) Reset() { *m = Version{} } func (m *Version) String() string { return proto.CompactTextString(m) } func (*Version) ProtoMessage() {} func (*Version) Descriptor() ([]byte, []int) { - return fileDescriptor_types_4449c1011851ea19, []int{30} + return fileDescriptor_types_5b877df1938afe10, []int{31} } func (m *Version) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -2811,7 +2866,7 @@ func (m *BlockID) Reset() { *m = BlockID{} } func (m *BlockID) String() string { return proto.CompactTextString(m) } func (*BlockID) ProtoMessage() {} func (*BlockID) Descriptor() ([]byte, []int) { - return fileDescriptor_types_4449c1011851ea19, []int{31} + return fileDescriptor_types_5b877df1938afe10, []int{32} } func (m *BlockID) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -2866,7 +2921,7 @@ func (m *PartSetHeader) Reset() { *m = PartSetHeader{} } func (m *PartSetHeader) String() string { return proto.CompactTextString(m) } func (*PartSetHeader) ProtoMessage() {} func (*PartSetHeader) Descriptor() ([]byte, []int) { - return fileDescriptor_types_4449c1011851ea19, []int{32} + return fileDescriptor_types_5b877df1938afe10, []int{33} } func (m *PartSetHeader) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -2923,7 +2978,7 @@ func (m *Validator) Reset() { *m = Validator{} } func (m *Validator) String() string { return proto.CompactTextString(m) } func (*Validator) ProtoMessage() {} func (*Validator) Descriptor() ([]byte, []int) { - return fileDescriptor_types_4449c1011851ea19, []int{33} + return fileDescriptor_types_5b877df1938afe10, []int{34} } func (m *Validator) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -2979,7 +3034,7 @@ func (m *ValidatorUpdate) Reset() { *m = ValidatorUpdate{} } func (m *ValidatorUpdate) String() string { return proto.CompactTextString(m) } func (*ValidatorUpdate) ProtoMessage() {} func (*ValidatorUpdate) Descriptor() ([]byte, []int) { - return fileDescriptor_types_4449c1011851ea19, []int{34} + return fileDescriptor_types_5b877df1938afe10, []int{35} } func (m *ValidatorUpdate) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -3035,7 +3090,7 @@ func (m *VoteInfo) Reset() { *m = VoteInfo{} } func (m *VoteInfo) String() string { return proto.CompactTextString(m) } func (*VoteInfo) ProtoMessage() {} func (*VoteInfo) Descriptor() ([]byte, []int) { - return fileDescriptor_types_4449c1011851ea19, []int{35} + return fileDescriptor_types_5b877df1938afe10, []int{36} } func (m *VoteInfo) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -3090,7 +3145,7 @@ func (m *PubKey) Reset() { *m = PubKey{} } func (m *PubKey) String() string { return proto.CompactTextString(m) } func (*PubKey) ProtoMessage() {} func (*PubKey) Descriptor() ([]byte, []int) { - return fileDescriptor_types_4449c1011851ea19, []int{36} + return fileDescriptor_types_5b877df1938afe10, []int{37} } func (m *PubKey) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -3148,7 +3203,7 @@ func (m *Evidence) Reset() { *m = Evidence{} } func (m *Evidence) String() string { return proto.CompactTextString(m) } func (*Evidence) ProtoMessage() {} func (*Evidence) Descriptor() ([]byte, []int) { - return fileDescriptor_types_4449c1011851ea19, []int{37} + return fileDescriptor_types_5b877df1938afe10, []int{38} } func (m *Evidence) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -3265,10 +3320,12 @@ func init() { golang_proto.RegisterType((*ResponseCommit)(nil), "types.ResponseCommit") proto.RegisterType((*ConsensusParams)(nil), "types.ConsensusParams") golang_proto.RegisterType((*ConsensusParams)(nil), "types.ConsensusParams") - proto.RegisterType((*BlockSize)(nil), "types.BlockSize") - golang_proto.RegisterType((*BlockSize)(nil), "types.BlockSize") + proto.RegisterType((*BlockSizeParams)(nil), "types.BlockSizeParams") + golang_proto.RegisterType((*BlockSizeParams)(nil), "types.BlockSizeParams") proto.RegisterType((*EvidenceParams)(nil), "types.EvidenceParams") golang_proto.RegisterType((*EvidenceParams)(nil), "types.EvidenceParams") + proto.RegisterType((*ValidatorParams)(nil), "types.ValidatorParams") + golang_proto.RegisterType((*ValidatorParams)(nil), "types.ValidatorParams") proto.RegisterType((*LastCommitInfo)(nil), "types.LastCommitInfo") golang_proto.RegisterType((*LastCommitInfo)(nil), "types.LastCommitInfo") proto.RegisterType((*Header)(nil), "types.Header") @@ -4714,7 +4771,10 @@ func (this *ConsensusParams) Equal(that interface{}) bool { if !this.BlockSize.Equal(that1.BlockSize) { return false } - if !this.EvidenceParams.Equal(that1.EvidenceParams) { + if !this.Evidence.Equal(that1.Evidence) { + return false + } + if !this.Validator.Equal(that1.Validator) { return false } if !bytes.Equal(this.XXX_unrecognized, that1.XXX_unrecognized) { @@ -4722,14 +4782,14 @@ func (this *ConsensusParams) Equal(that interface{}) bool { } return true } -func (this *BlockSize) Equal(that interface{}) bool { +func (this *BlockSizeParams) Equal(that interface{}) bool { if that == nil { return this == nil } - that1, ok := that.(*BlockSize) + that1, ok := that.(*BlockSizeParams) if !ok { - that2, ok := that.(BlockSize) + that2, ok := that.(BlockSizeParams) if ok { that1 = &that2 } else { @@ -4779,6 +4839,38 @@ func (this *EvidenceParams) Equal(that interface{}) bool { } return true } +func (this *ValidatorParams) Equal(that interface{}) bool { + if that == nil { + return this == nil + } + + that1, ok := that.(*ValidatorParams) + if !ok { + that2, ok := that.(ValidatorParams) + if ok { + that1 = &that2 + } else { + return false + } + } + if that1 == nil { + return this == nil + } else if this == nil { + return false + } + if len(this.PubKeyTypes) != len(that1.PubKeyTypes) { + return false + } + for i := range this.PubKeyTypes { + if this.PubKeyTypes[i] != that1.PubKeyTypes[i] { + return false + } + } + if !bytes.Equal(this.XXX_unrecognized, that1.XXX_unrecognized) { + return false + } + return true +} func (this *LastCommitInfo) Equal(that interface{}) bool { if that == nil { return this == nil @@ -6868,23 +6960,33 @@ func (m *ConsensusParams) MarshalTo(dAtA []byte) (int, error) { } i += n33 } - if m.EvidenceParams != nil { + if m.Evidence != nil { dAtA[i] = 0x12 i++ - i = encodeVarintTypes(dAtA, i, uint64(m.EvidenceParams.Size())) - n34, err := m.EvidenceParams.MarshalTo(dAtA[i:]) + i = encodeVarintTypes(dAtA, i, uint64(m.Evidence.Size())) + n34, err := m.Evidence.MarshalTo(dAtA[i:]) if err != nil { return 0, err } i += n34 } + if m.Validator != nil { + dAtA[i] = 0x1a + i++ + i = encodeVarintTypes(dAtA, i, uint64(m.Validator.Size())) + n35, err := m.Validator.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n35 + } if m.XXX_unrecognized != nil { i += copy(dAtA[i:], m.XXX_unrecognized) } return i, nil } -func (m *BlockSize) Marshal() (dAtA []byte, err error) { +func (m *BlockSizeParams) Marshal() (dAtA []byte, err error) { size := m.Size() dAtA = make([]byte, size) n, err := m.MarshalTo(dAtA) @@ -6894,7 +6996,7 @@ func (m *BlockSize) Marshal() (dAtA []byte, err error) { return dAtA[:n], nil } -func (m *BlockSize) MarshalTo(dAtA []byte) (int, error) { +func (m *BlockSizeParams) MarshalTo(dAtA []byte) (int, error) { var i int _ = i var l int @@ -6941,6 +7043,42 @@ func (m *EvidenceParams) MarshalTo(dAtA []byte) (int, error) { return i, nil } +func (m *ValidatorParams) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalTo(dAtA) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *ValidatorParams) MarshalTo(dAtA []byte) (int, error) { + var i int + _ = i + var l int + _ = l + if len(m.PubKeyTypes) > 0 { + for _, s := range m.PubKeyTypes { + dAtA[i] = 0xa + i++ + l = len(s) + for l >= 1<<7 { + dAtA[i] = uint8(uint64(l)&0x7f | 0x80) + l >>= 7 + i++ + } + dAtA[i] = uint8(l) + i++ + i += copy(dAtA[i:], s) + } + } + if m.XXX_unrecognized != nil { + i += copy(dAtA[i:], m.XXX_unrecognized) + } + return i, nil +} + func (m *LastCommitInfo) Marshal() (dAtA []byte, err error) { size := m.Size() dAtA = make([]byte, size) @@ -6997,11 +7135,11 @@ func (m *Header) MarshalTo(dAtA []byte) (int, error) { dAtA[i] = 0xa i++ i = encodeVarintTypes(dAtA, i, uint64(m.Version.Size())) - n35, err := m.Version.MarshalTo(dAtA[i:]) + n36, err := m.Version.MarshalTo(dAtA[i:]) if err != nil { return 0, err } - i += n35 + i += n36 if len(m.ChainID) > 0 { dAtA[i] = 0x12 i++ @@ -7016,11 +7154,11 @@ func (m *Header) MarshalTo(dAtA []byte) (int, error) { dAtA[i] = 0x22 i++ i = encodeVarintTypes(dAtA, i, uint64(github_com_gogo_protobuf_types.SizeOfStdTime(m.Time))) - n36, err := github_com_gogo_protobuf_types.StdTimeMarshalTo(m.Time, dAtA[i:]) + n37, err := github_com_gogo_protobuf_types.StdTimeMarshalTo(m.Time, dAtA[i:]) if err != nil { return 0, err } - i += n36 + i += n37 if m.NumTxs != 0 { dAtA[i] = 0x28 i++ @@ -7034,11 +7172,11 @@ func (m *Header) MarshalTo(dAtA []byte) (int, error) { dAtA[i] = 0x3a i++ i = encodeVarintTypes(dAtA, i, uint64(m.LastBlockId.Size())) - n37, err := m.LastBlockId.MarshalTo(dAtA[i:]) + n38, err := m.LastBlockId.MarshalTo(dAtA[i:]) if err != nil { return 0, err } - i += n37 + i += n38 if len(m.LastCommitHash) > 0 { dAtA[i] = 0x42 i++ @@ -7156,11 +7294,11 @@ func (m *BlockID) MarshalTo(dAtA []byte) (int, error) { dAtA[i] = 0x12 i++ i = encodeVarintTypes(dAtA, i, uint64(m.PartsHeader.Size())) - n38, err := m.PartsHeader.MarshalTo(dAtA[i:]) + n39, err := m.PartsHeader.MarshalTo(dAtA[i:]) if err != nil { return 0, err } - i += n38 + i += n39 if m.XXX_unrecognized != nil { i += copy(dAtA[i:], m.XXX_unrecognized) } @@ -7249,11 +7387,11 @@ func (m *ValidatorUpdate) MarshalTo(dAtA []byte) (int, error) { dAtA[i] = 0xa i++ i = encodeVarintTypes(dAtA, i, uint64(m.PubKey.Size())) - n39, err := m.PubKey.MarshalTo(dAtA[i:]) + n40, err := m.PubKey.MarshalTo(dAtA[i:]) if err != nil { return 0, err } - i += n39 + i += n40 if m.Power != 0 { dAtA[i] = 0x10 i++ @@ -7283,11 +7421,11 @@ func (m *VoteInfo) MarshalTo(dAtA []byte) (int, error) { dAtA[i] = 0xa i++ i = encodeVarintTypes(dAtA, i, uint64(m.Validator.Size())) - n40, err := m.Validator.MarshalTo(dAtA[i:]) + n41, err := m.Validator.MarshalTo(dAtA[i:]) if err != nil { return 0, err } - i += n40 + i += n41 if m.SignedLastBlock { dAtA[i] = 0x10 i++ @@ -7361,11 +7499,11 @@ func (m *Evidence) MarshalTo(dAtA []byte) (int, error) { dAtA[i] = 0x12 i++ i = encodeVarintTypes(dAtA, i, uint64(m.Validator.Size())) - n41, err := m.Validator.MarshalTo(dAtA[i:]) + n42, err := m.Validator.MarshalTo(dAtA[i:]) if err != nil { return 0, err } - i += n41 + i += n42 if m.Height != 0 { dAtA[i] = 0x18 i++ @@ -7374,11 +7512,11 @@ func (m *Evidence) MarshalTo(dAtA []byte) (int, error) { dAtA[i] = 0x22 i++ i = encodeVarintTypes(dAtA, i, uint64(github_com_gogo_protobuf_types.SizeOfStdTime(m.Time))) - n42, err := github_com_gogo_protobuf_types.StdTimeMarshalTo(m.Time, dAtA[i:]) + n43, err := github_com_gogo_protobuf_types.StdTimeMarshalTo(m.Time, dAtA[i:]) if err != nil { return 0, err } - i += n42 + i += n43 if m.TotalVotingPower != 0 { dAtA[i] = 0x28 i++ @@ -7971,19 +8109,22 @@ func NewPopulatedResponseCommit(r randyTypes, easy bool) *ResponseCommit { func NewPopulatedConsensusParams(r randyTypes, easy bool) *ConsensusParams { this := &ConsensusParams{} if r.Intn(10) != 0 { - this.BlockSize = NewPopulatedBlockSize(r, easy) + this.BlockSize = NewPopulatedBlockSizeParams(r, easy) } if r.Intn(10) != 0 { - this.EvidenceParams = NewPopulatedEvidenceParams(r, easy) + this.Evidence = NewPopulatedEvidenceParams(r, easy) + } + if r.Intn(10) != 0 { + this.Validator = NewPopulatedValidatorParams(r, easy) } if !easy && r.Intn(10) != 0 { - this.XXX_unrecognized = randUnrecognizedTypes(r, 3) + this.XXX_unrecognized = randUnrecognizedTypes(r, 4) } return this } -func NewPopulatedBlockSize(r randyTypes, easy bool) *BlockSize { - this := &BlockSize{} +func NewPopulatedBlockSizeParams(r randyTypes, easy bool) *BlockSizeParams { + this := &BlockSizeParams{} this.MaxBytes = int64(r.Int63()) if r.Intn(2) == 0 { this.MaxBytes *= -1 @@ -8010,6 +8151,19 @@ func NewPopulatedEvidenceParams(r randyTypes, easy bool) *EvidenceParams { return this } +func NewPopulatedValidatorParams(r randyTypes, easy bool) *ValidatorParams { + this := &ValidatorParams{} + v31 := r.Intn(10) + this.PubKeyTypes = make([]string, v31) + for i := 0; i < v31; i++ { + this.PubKeyTypes[i] = string(randStringTypes(r)) + } + if !easy && r.Intn(10) != 0 { + this.XXX_unrecognized = randUnrecognizedTypes(r, 2) + } + return this +} + func NewPopulatedLastCommitInfo(r randyTypes, easy bool) *LastCommitInfo { this := &LastCommitInfo{} this.Round = int32(r.Int31()) @@ -8017,11 +8171,11 @@ func NewPopulatedLastCommitInfo(r randyTypes, easy bool) *LastCommitInfo { this.Round *= -1 } if r.Intn(10) != 0 { - v31 := r.Intn(5) - this.Votes = make([]VoteInfo, v31) - for i := 0; i < v31; i++ { - v32 := NewPopulatedVoteInfo(r, easy) - this.Votes[i] = *v32 + v32 := r.Intn(5) + this.Votes = make([]VoteInfo, v32) + for i := 0; i < v32; i++ { + v33 := NewPopulatedVoteInfo(r, easy) + this.Votes[i] = *v33 } } if !easy && r.Intn(10) != 0 { @@ -8032,15 +8186,15 @@ func NewPopulatedLastCommitInfo(r randyTypes, easy bool) *LastCommitInfo { func NewPopulatedHeader(r randyTypes, easy bool) *Header { this := &Header{} - v33 := NewPopulatedVersion(r, easy) - this.Version = *v33 + v34 := NewPopulatedVersion(r, easy) + this.Version = *v34 this.ChainID = string(randStringTypes(r)) this.Height = int64(r.Int63()) if r.Intn(2) == 0 { this.Height *= -1 } - v34 := github_com_gogo_protobuf_types.NewPopulatedStdTime(r, easy) - this.Time = *v34 + v35 := github_com_gogo_protobuf_types.NewPopulatedStdTime(r, easy) + this.Time = *v35 this.NumTxs = int64(r.Int63()) if r.Intn(2) == 0 { this.NumTxs *= -1 @@ -8049,51 +8203,51 @@ func NewPopulatedHeader(r randyTypes, easy bool) *Header { if r.Intn(2) == 0 { this.TotalTxs *= -1 } - v35 := NewPopulatedBlockID(r, easy) - this.LastBlockId = *v35 - v36 := r.Intn(100) - this.LastCommitHash = make([]byte, v36) - for i := 0; i < v36; i++ { + v36 := NewPopulatedBlockID(r, easy) + this.LastBlockId = *v36 + v37 := r.Intn(100) + this.LastCommitHash = make([]byte, v37) + for i := 0; i < v37; i++ { this.LastCommitHash[i] = byte(r.Intn(256)) } - v37 := r.Intn(100) - this.DataHash = make([]byte, v37) - for i := 0; i < v37; i++ { + v38 := r.Intn(100) + this.DataHash = make([]byte, v38) + for i := 0; i < v38; i++ { this.DataHash[i] = byte(r.Intn(256)) } - v38 := r.Intn(100) - this.ValidatorsHash = make([]byte, v38) - for i := 0; i < v38; i++ { + v39 := r.Intn(100) + this.ValidatorsHash = make([]byte, v39) + for i := 0; i < v39; i++ { this.ValidatorsHash[i] = byte(r.Intn(256)) } - v39 := r.Intn(100) - this.NextValidatorsHash = make([]byte, v39) - for i := 0; i < v39; i++ { + v40 := r.Intn(100) + this.NextValidatorsHash = make([]byte, v40) + for i := 0; i < v40; i++ { this.NextValidatorsHash[i] = byte(r.Intn(256)) } - v40 := r.Intn(100) - this.ConsensusHash = make([]byte, v40) - for i := 0; i < v40; i++ { + v41 := r.Intn(100) + this.ConsensusHash = make([]byte, v41) + for i := 0; i < v41; i++ { this.ConsensusHash[i] = byte(r.Intn(256)) } - v41 := r.Intn(100) - this.AppHash = make([]byte, v41) - for i := 0; i < v41; i++ { + v42 := r.Intn(100) + this.AppHash = make([]byte, v42) + for i := 0; i < v42; i++ { this.AppHash[i] = byte(r.Intn(256)) } - v42 := r.Intn(100) - this.LastResultsHash = make([]byte, v42) - for i := 0; i < v42; i++ { + v43 := r.Intn(100) + this.LastResultsHash = make([]byte, v43) + for i := 0; i < v43; i++ { this.LastResultsHash[i] = byte(r.Intn(256)) } - v43 := r.Intn(100) - this.EvidenceHash = make([]byte, v43) - for i := 0; i < v43; i++ { + v44 := r.Intn(100) + this.EvidenceHash = make([]byte, v44) + for i := 0; i < v44; i++ { this.EvidenceHash[i] = byte(r.Intn(256)) } - v44 := r.Intn(100) - this.ProposerAddress = make([]byte, v44) - for i := 0; i < v44; i++ { + v45 := r.Intn(100) + this.ProposerAddress = make([]byte, v45) + for i := 0; i < v45; i++ { this.ProposerAddress[i] = byte(r.Intn(256)) } if !easy && r.Intn(10) != 0 { @@ -8114,13 +8268,13 @@ func NewPopulatedVersion(r randyTypes, easy bool) *Version { func NewPopulatedBlockID(r randyTypes, easy bool) *BlockID { this := &BlockID{} - v45 := r.Intn(100) - this.Hash = make([]byte, v45) - for i := 0; i < v45; i++ { + v46 := r.Intn(100) + this.Hash = make([]byte, v46) + for i := 0; i < v46; i++ { this.Hash[i] = byte(r.Intn(256)) } - v46 := NewPopulatedPartSetHeader(r, easy) - this.PartsHeader = *v46 + v47 := NewPopulatedPartSetHeader(r, easy) + this.PartsHeader = *v47 if !easy && r.Intn(10) != 0 { this.XXX_unrecognized = randUnrecognizedTypes(r, 3) } @@ -8133,9 +8287,9 @@ func NewPopulatedPartSetHeader(r randyTypes, easy bool) *PartSetHeader { if r.Intn(2) == 0 { this.Total *= -1 } - v47 := r.Intn(100) - this.Hash = make([]byte, v47) - for i := 0; i < v47; i++ { + v48 := r.Intn(100) + this.Hash = make([]byte, v48) + for i := 0; i < v48; i++ { this.Hash[i] = byte(r.Intn(256)) } if !easy && r.Intn(10) != 0 { @@ -8146,9 +8300,9 @@ func NewPopulatedPartSetHeader(r randyTypes, easy bool) *PartSetHeader { func NewPopulatedValidator(r randyTypes, easy bool) *Validator { this := &Validator{} - v48 := r.Intn(100) - this.Address = make([]byte, v48) - for i := 0; i < v48; i++ { + v49 := r.Intn(100) + this.Address = make([]byte, v49) + for i := 0; i < v49; i++ { this.Address[i] = byte(r.Intn(256)) } this.Power = int64(r.Int63()) @@ -8163,8 +8317,8 @@ func NewPopulatedValidator(r randyTypes, easy bool) *Validator { func NewPopulatedValidatorUpdate(r randyTypes, easy bool) *ValidatorUpdate { this := &ValidatorUpdate{} - v49 := NewPopulatedPubKey(r, easy) - this.PubKey = *v49 + v50 := NewPopulatedPubKey(r, easy) + this.PubKey = *v50 this.Power = int64(r.Int63()) if r.Intn(2) == 0 { this.Power *= -1 @@ -8177,8 +8331,8 @@ func NewPopulatedValidatorUpdate(r randyTypes, easy bool) *ValidatorUpdate { func NewPopulatedVoteInfo(r randyTypes, easy bool) *VoteInfo { this := &VoteInfo{} - v50 := NewPopulatedValidator(r, easy) - this.Validator = *v50 + v51 := NewPopulatedValidator(r, easy) + this.Validator = *v51 this.SignedLastBlock = bool(bool(r.Intn(2) == 0)) if !easy && r.Intn(10) != 0 { this.XXX_unrecognized = randUnrecognizedTypes(r, 3) @@ -8189,9 +8343,9 @@ func NewPopulatedVoteInfo(r randyTypes, easy bool) *VoteInfo { func NewPopulatedPubKey(r randyTypes, easy bool) *PubKey { this := &PubKey{} this.Type = string(randStringTypes(r)) - v51 := r.Intn(100) - this.Data = make([]byte, v51) - for i := 0; i < v51; i++ { + v52 := r.Intn(100) + this.Data = make([]byte, v52) + for i := 0; i < v52; i++ { this.Data[i] = byte(r.Intn(256)) } if !easy && r.Intn(10) != 0 { @@ -8203,14 +8357,14 @@ func NewPopulatedPubKey(r randyTypes, easy bool) *PubKey { func NewPopulatedEvidence(r randyTypes, easy bool) *Evidence { this := &Evidence{} this.Type = string(randStringTypes(r)) - v52 := NewPopulatedValidator(r, easy) - this.Validator = *v52 + v53 := NewPopulatedValidator(r, easy) + this.Validator = *v53 this.Height = int64(r.Int63()) if r.Intn(2) == 0 { this.Height *= -1 } - v53 := github_com_gogo_protobuf_types.NewPopulatedStdTime(r, easy) - this.Time = *v53 + v54 := github_com_gogo_protobuf_types.NewPopulatedStdTime(r, easy) + this.Time = *v54 this.TotalVotingPower = int64(r.Int63()) if r.Intn(2) == 0 { this.TotalVotingPower *= -1 @@ -8240,9 +8394,9 @@ func randUTF8RuneTypes(r randyTypes) rune { return rune(ru + 61) } func randStringTypes(r randyTypes) string { - v54 := r.Intn(100) - tmps := make([]rune, v54) - for i := 0; i < v54; i++ { + v55 := r.Intn(100) + tmps := make([]rune, v55) + for i := 0; i < v55; i++ { tmps[i] = randUTF8RuneTypes(r) } return string(tmps) @@ -8264,11 +8418,11 @@ func randFieldTypes(dAtA []byte, r randyTypes, fieldNumber int, wire int) []byte switch wire { case 0: dAtA = encodeVarintPopulateTypes(dAtA, uint64(key)) - v55 := r.Int63() + v56 := r.Int63() if r.Intn(2) == 0 { - v55 *= -1 + v56 *= -1 } - dAtA = encodeVarintPopulateTypes(dAtA, uint64(v55)) + dAtA = encodeVarintPopulateTypes(dAtA, uint64(v56)) case 1: dAtA = encodeVarintPopulateTypes(dAtA, uint64(key)) dAtA = append(dAtA, byte(r.Intn(256)), byte(r.Intn(256)), byte(r.Intn(256)), byte(r.Intn(256)), byte(r.Intn(256)), byte(r.Intn(256)), byte(r.Intn(256)), byte(r.Intn(256))) @@ -8987,8 +9141,12 @@ func (m *ConsensusParams) Size() (n int) { l = m.BlockSize.Size() n += 1 + l + sovTypes(uint64(l)) } - if m.EvidenceParams != nil { - l = m.EvidenceParams.Size() + if m.Evidence != nil { + l = m.Evidence.Size() + n += 1 + l + sovTypes(uint64(l)) + } + if m.Validator != nil { + l = m.Validator.Size() n += 1 + l + sovTypes(uint64(l)) } if m.XXX_unrecognized != nil { @@ -8997,7 +9155,7 @@ func (m *ConsensusParams) Size() (n int) { return n } -func (m *BlockSize) Size() (n int) { +func (m *BlockSizeParams) Size() (n int) { var l int _ = l if m.MaxBytes != 0 { @@ -9024,6 +9182,21 @@ func (m *EvidenceParams) Size() (n int) { return n } +func (m *ValidatorParams) Size() (n int) { + var l int + _ = l + if len(m.PubKeyTypes) > 0 { + for _, s := range m.PubKeyTypes { + l = len(s) + n += 1 + l + sovTypes(uint64(l)) + } + } + if m.XXX_unrecognized != nil { + n += len(m.XXX_unrecognized) + } + return n +} + func (m *LastCommitInfo) Size() (n int) { var l int _ = l @@ -13060,7 +13233,7 @@ func (m *ConsensusParams) Unmarshal(dAtA []byte) error { return io.ErrUnexpectedEOF } if m.BlockSize == nil { - m.BlockSize = &BlockSize{} + m.BlockSize = &BlockSizeParams{} } if err := m.BlockSize.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err @@ -13068,7 +13241,7 @@ func (m *ConsensusParams) Unmarshal(dAtA []byte) error { iNdEx = postIndex case 2: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field EvidenceParams", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field Evidence", wireType) } var msglen int for shift := uint(0); ; shift += 7 { @@ -13092,10 +13265,43 @@ func (m *ConsensusParams) Unmarshal(dAtA []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - if m.EvidenceParams == nil { - m.EvidenceParams = &EvidenceParams{} + if m.Evidence == nil { + m.Evidence = &EvidenceParams{} } - if err := m.EvidenceParams.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + if err := m.Evidence.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Validator", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTypes + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthTypes + } + postIndex := iNdEx + msglen + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Validator == nil { + m.Validator = &ValidatorParams{} + } + if err := m.Validator.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex @@ -13121,7 +13327,7 @@ func (m *ConsensusParams) Unmarshal(dAtA []byte) error { } return nil } -func (m *BlockSize) Unmarshal(dAtA []byte) error { +func (m *BlockSizeParams) Unmarshal(dAtA []byte) error { l := len(dAtA) iNdEx := 0 for iNdEx < l { @@ -13144,10 +13350,10 @@ func (m *BlockSize) Unmarshal(dAtA []byte) error { fieldNum := int32(wire >> 3) wireType := int(wire & 0x7) if wireType == 4 { - return fmt.Errorf("proto: BlockSize: wiretype end group for non-group") + return fmt.Errorf("proto: BlockSizeParams: wiretype end group for non-group") } if fieldNum <= 0 { - return fmt.Errorf("proto: BlockSize: illegal tag %d (wire type %d)", fieldNum, wire) + return fmt.Errorf("proto: BlockSizeParams: illegal tag %d (wire type %d)", fieldNum, wire) } switch fieldNum { case 1: @@ -13280,6 +13486,86 @@ func (m *EvidenceParams) Unmarshal(dAtA []byte) error { } return nil } +func (m *ValidatorParams) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTypes + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: ValidatorParams: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: ValidatorParams: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field PubKeyTypes", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTypes + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthTypes + } + postIndex := iNdEx + intStringLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.PubKeyTypes = append(m.PubKeyTypes, string(dAtA[iNdEx:postIndex])) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipTypes(dAtA[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthTypes + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + m.XXX_unrecognized = append(m.XXX_unrecognized, dAtA[iNdEx:iNdEx+skippy]...) + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} func (m *LastCommitInfo) Unmarshal(dAtA []byte) error { l := len(dAtA) iNdEx := 0 @@ -14885,148 +15171,150 @@ var ( ErrIntOverflowTypes = fmt.Errorf("proto: integer overflow") ) -func init() { proto.RegisterFile("abci/types/types.proto", fileDescriptor_types_4449c1011851ea19) } +func init() { proto.RegisterFile("abci/types/types.proto", fileDescriptor_types_5b877df1938afe10) } func init() { - golang_proto.RegisterFile("abci/types/types.proto", fileDescriptor_types_4449c1011851ea19) + golang_proto.RegisterFile("abci/types/types.proto", fileDescriptor_types_5b877df1938afe10) } -var fileDescriptor_types_4449c1011851ea19 = []byte{ - // 2177 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xdc, 0x58, 0xcd, 0x93, 0x1b, 0x47, - 0x15, 0xdf, 0xd1, 0x6a, 0x25, 0xcd, 0xd3, 0xea, 0x23, 0xed, 0xb5, 0x2d, 0x8b, 0xb0, 0xeb, 0x1a, - 0x43, 0xe2, 0x25, 0x8e, 0x36, 0x6c, 0x08, 0xb5, 0x8e, 0x43, 0xaa, 0x56, 0xb6, 0x61, 0xb7, 0x12, - 0x60, 0x19, 0xdb, 0xcb, 0x85, 0xaa, 0xa9, 0x96, 0xa6, 0x2d, 0x4d, 0x59, 0x9a, 0x99, 0xcc, 0xb4, - 0x36, 0x5a, 0x1f, 0x73, 0xce, 0x21, 0x07, 0xfe, 0x08, 0xfe, 0x84, 0x1c, 0x39, 0x51, 0x39, 0x72, - 0xe0, 0x6c, 0x60, 0x29, 0x0e, 0x70, 0xa5, 0xa8, 0xe2, 0x48, 0xf5, 0xeb, 0xee, 0xf9, 0xda, 0x91, - 0x89, 0x03, 0x27, 0x2e, 0x52, 0xf7, 0xfb, 0xe8, 0x8f, 0x37, 0xef, 0xbd, 0xdf, 0x7b, 0x0d, 0xd7, - 0xe8, 0x68, 0xec, 0xed, 0xf1, 0xf3, 0x90, 0xc5, 0xf2, 0x77, 0x10, 0x46, 0x01, 0x0f, 0xc8, 0x06, - 0x4e, 0xfa, 0x6f, 0x4f, 0x3c, 0x3e, 0x5d, 0x8c, 0x06, 0xe3, 0x60, 0xbe, 0x37, 0x09, 0x26, 0xc1, - 0x1e, 0x72, 0x47, 0x8b, 0xa7, 0x38, 0xc3, 0x09, 0x8e, 0xa4, 0x56, 0x7f, 0x67, 0x12, 0x04, 0x93, - 0x19, 0x4b, 0xa5, 0xb8, 0x37, 0x67, 0x31, 0xa7, 0xf3, 0x50, 0x09, 0x1c, 0x64, 0xd6, 0xe3, 0xcc, - 0x77, 0x59, 0x34, 0xf7, 0x7c, 0x9e, 0x1d, 0xce, 0xbc, 0x51, 0xbc, 0x37, 0x0e, 0xe6, 0xf3, 0xc0, - 0xcf, 0x1e, 0xa8, 0x7f, 0xef, 0x3f, 0x6a, 0x8e, 0xa3, 0xf3, 0x90, 0x07, 0x7b, 0x73, 0x16, 0x3d, - 0x9b, 0x31, 0xf5, 0x27, 0x95, 0xad, 0xdf, 0x55, 0xa1, 0x6e, 0xb3, 0x4f, 0x16, 0x2c, 0xe6, 0xe4, - 0x36, 0x54, 0xd9, 0x78, 0x1a, 0xf4, 0x2a, 0x37, 0x8d, 0xdb, 0xcd, 0x7d, 0x32, 0x90, 0x9b, 0x28, - 0xee, 0xc3, 0xf1, 0x34, 0x38, 0x5a, 0xb3, 0x51, 0x82, 0xbc, 0x05, 0x1b, 0x4f, 0x67, 0x8b, 0x78, - 0xda, 0x5b, 0x47, 0xd1, 0x2b, 0x79, 0xd1, 0x1f, 0x0b, 0xd6, 0xd1, 0x9a, 0x2d, 0x65, 0xc4, 0xb2, - 0x9e, 0xff, 0x34, 0xe8, 0x55, 0xcb, 0x96, 0x3d, 0xf6, 0x9f, 0xe2, 0xb2, 0x42, 0x82, 0x1c, 0x00, - 0xc4, 0x8c, 0x3b, 0x41, 0xc8, 0xbd, 0xc0, 0xef, 0x6d, 0xa0, 0xfc, 0xf5, 0xbc, 0xfc, 0x23, 0xc6, - 0x7f, 0x8e, 0xec, 0xa3, 0x35, 0xdb, 0x8c, 0xf5, 0x44, 0x68, 0x7a, 0xbe, 0xc7, 0x9d, 0xf1, 0x94, - 0x7a, 0x7e, 0xaf, 0x56, 0xa6, 0x79, 0xec, 0x7b, 0xfc, 0xbe, 0x60, 0x0b, 0x4d, 0x4f, 0x4f, 0xc4, - 0x55, 0x3e, 0x59, 0xb0, 0xe8, 0xbc, 0x57, 0x2f, 0xbb, 0xca, 0x2f, 0x04, 0x4b, 0x5c, 0x05, 0x65, - 0xc8, 0x3d, 0x68, 0x8e, 0xd8, 0xc4, 0xf3, 0x9d, 0xd1, 0x2c, 0x18, 0x3f, 0xeb, 0x35, 0x50, 0xa5, - 0x97, 0x57, 0x19, 0x0a, 0x81, 0xa1, 0xe0, 0x1f, 0xad, 0xd9, 0x30, 0x4a, 0x66, 0x64, 0x1f, 0x1a, - 0xe3, 0x29, 0x1b, 0x3f, 0x73, 0xf8, 0xb2, 0x67, 0xa2, 0xe6, 0xd5, 0xbc, 0xe6, 0x7d, 0xc1, 0x7d, - 0xbc, 0x3c, 0x5a, 0xb3, 0xeb, 0x63, 0x39, 0x24, 0xef, 0x81, 0xc9, 0x7c, 0x57, 0x6d, 0xd7, 0x44, - 0xa5, 0x6b, 0x85, 0xef, 0xe2, 0xbb, 0x7a, 0xb3, 0x06, 0x53, 0x63, 0x32, 0x80, 0x9a, 0x70, 0x14, - 0x8f, 0xf7, 0x36, 0x51, 0x67, 0xab, 0xb0, 0x11, 0xf2, 0x8e, 0xd6, 0x6c, 0x25, 0x25, 0xcc, 0xe7, - 0xb2, 0x99, 0x77, 0xc6, 0x22, 0x71, 0xb8, 0x2b, 0x65, 0xe6, 0x7b, 0x20, 0xf9, 0x78, 0x3c, 0xd3, - 0xd5, 0x93, 0x61, 0x1d, 0x36, 0xce, 0xe8, 0x6c, 0xc1, 0xac, 0x37, 0xa1, 0x99, 0xf1, 0x14, 0xd2, - 0x83, 0xfa, 0x9c, 0xc5, 0x31, 0x9d, 0xb0, 0x9e, 0x71, 0xd3, 0xb8, 0x6d, 0xda, 0x7a, 0x6a, 0xb5, - 0x61, 0x33, 0xeb, 0x27, 0xd6, 0x3c, 0x51, 0x14, 0xbe, 0x20, 0x14, 0xcf, 0x58, 0x14, 0x0b, 0x07, - 0x50, 0x8a, 0x6a, 0x4a, 0x6e, 0x41, 0x0b, 0xed, 0xe0, 0x68, 0xbe, 0xf0, 0xd3, 0xaa, 0xbd, 0x89, - 0xc4, 0x53, 0x25, 0xb4, 0x03, 0xcd, 0x70, 0x3f, 0x4c, 0x44, 0xd6, 0x51, 0x04, 0xc2, 0xfd, 0x50, - 0x09, 0x58, 0xef, 0x43, 0xb7, 0xe8, 0x4a, 0xa4, 0x0b, 0xeb, 0xcf, 0xd8, 0xb9, 0xda, 0x4f, 0x0c, - 0xc9, 0x96, 0xba, 0x16, 0xee, 0x61, 0xda, 0xea, 0x8e, 0x5f, 0x54, 0x12, 0xe5, 0xc4, 0x9b, 0xc8, - 0x01, 0x54, 0x45, 0x2c, 0xa3, 0x76, 0x73, 0xbf, 0x3f, 0x90, 0x81, 0x3e, 0xd0, 0x81, 0x3e, 0x78, - 0xac, 0x03, 0x7d, 0xd8, 0xf8, 0xea, 0xc5, 0xce, 0xda, 0x17, 0x7f, 0xdc, 0x31, 0x6c, 0xd4, 0x20, - 0x37, 0x84, 0x43, 0x50, 0xcf, 0x77, 0x3c, 0x57, 0xed, 0x53, 0xc7, 0xf9, 0xb1, 0x4b, 0x0e, 0xa1, - 0x3b, 0x0e, 0xfc, 0x98, 0xf9, 0xf1, 0x22, 0x76, 0x42, 0x1a, 0xd1, 0x79, 0xac, 0x62, 0x4d, 0x7f, - 0xfe, 0xfb, 0x9a, 0x7d, 0x82, 0x5c, 0xbb, 0x33, 0xce, 0x13, 0xc8, 0x07, 0x00, 0x67, 0x74, 0xe6, - 0xb9, 0x94, 0x07, 0x51, 0xdc, 0xab, 0xde, 0x5c, 0xcf, 0x28, 0x9f, 0x6a, 0xc6, 0x93, 0xd0, 0xa5, - 0x9c, 0x0d, 0xab, 0xe2, 0x64, 0x76, 0x46, 0x9e, 0xbc, 0x01, 0x1d, 0x1a, 0x86, 0x4e, 0xcc, 0x29, - 0x67, 0xce, 0xe8, 0x9c, 0xb3, 0x18, 0xe3, 0x71, 0xd3, 0x6e, 0xd1, 0x30, 0x7c, 0x24, 0xa8, 0x43, - 0x41, 0xb4, 0xdc, 0xe4, 0x6b, 0x62, 0xa8, 0x10, 0x02, 0x55, 0x97, 0x72, 0x8a, 0xd6, 0xd8, 0xb4, - 0x71, 0x2c, 0x68, 0x21, 0xe5, 0x53, 0x75, 0x47, 0x1c, 0x93, 0x6b, 0x50, 0x9b, 0x32, 0x6f, 0x32, - 0xe5, 0x78, 0xad, 0x75, 0x5b, 0xcd, 0x84, 0xe1, 0xc3, 0x28, 0x38, 0x63, 0x98, 0x2d, 0x1a, 0xb6, - 0x9c, 0x58, 0x7f, 0x35, 0xe0, 0xb5, 0x4b, 0xe1, 0x25, 0xd6, 0x9d, 0xd2, 0x78, 0xaa, 0xf7, 0x12, - 0x63, 0xf2, 0x96, 0x58, 0x97, 0xba, 0x2c, 0x52, 0x59, 0xac, 0xa5, 0x6e, 0x7c, 0x84, 0x44, 0x75, - 0x51, 0x25, 0x42, 0x1e, 0x42, 0x77, 0x46, 0x63, 0xee, 0xc8, 0x28, 0x70, 0x30, 0x4b, 0xad, 0xe7, - 0x22, 0xf3, 0x63, 0xaa, 0xa3, 0x45, 0x38, 0xa7, 0x52, 0x6f, 0xcf, 0x72, 0x54, 0x72, 0x04, 0x5b, - 0xa3, 0xf3, 0xe7, 0xd4, 0xe7, 0x9e, 0xcf, 0x9c, 0x4b, 0x36, 0xef, 0xa8, 0xa5, 0x1e, 0x9e, 0x79, - 0x2e, 0xf3, 0xc7, 0xda, 0xd8, 0x57, 0x12, 0x95, 0xe4, 0x63, 0xc4, 0xd6, 0x4d, 0x68, 0xe7, 0x73, - 0x01, 0x69, 0x43, 0x85, 0x2f, 0xd5, 0x0d, 0x2b, 0x7c, 0x69, 0x59, 0x89, 0x07, 0x26, 0x01, 0x79, - 0x49, 0x66, 0x17, 0x3a, 0x85, 0xe4, 0x90, 0x31, 0xb7, 0x91, 0x35, 0xb7, 0xd5, 0x81, 0x56, 0x2e, - 0x27, 0x58, 0x9f, 0x6f, 0x40, 0xc3, 0x66, 0x71, 0x28, 0x9c, 0x89, 0x1c, 0x80, 0xc9, 0x96, 0x63, - 0x26, 0xd3, 0xb1, 0x51, 0x48, 0x76, 0x52, 0xe6, 0xa1, 0xe6, 0x8b, 0xb4, 0x90, 0x08, 0x93, 0xdd, - 0x1c, 0x94, 0x5c, 0x29, 0x2a, 0x65, 0xb1, 0xe4, 0x4e, 0x1e, 0x4b, 0xb6, 0x0a, 0xb2, 0x05, 0x30, - 0xd9, 0xcd, 0x81, 0x49, 0x71, 0xe1, 0x1c, 0x9a, 0xdc, 0x2d, 0x41, 0x93, 0xe2, 0xf1, 0x57, 0xc0, - 0xc9, 0xdd, 0x12, 0x38, 0xe9, 0x5d, 0xda, 0xab, 0x14, 0x4f, 0xee, 0xe4, 0xf1, 0xa4, 0x78, 0x9d, - 0x02, 0xa0, 0x7c, 0x50, 0x06, 0x28, 0x37, 0x0a, 0x3a, 0x2b, 0x11, 0xe5, 0xdd, 0x4b, 0x88, 0x72, - 0xad, 0xa0, 0x5a, 0x02, 0x29, 0x77, 0x73, 0xb9, 0x1e, 0x4a, 0xef, 0x56, 0x9e, 0xec, 0xc9, 0x0f, - 0x2f, 0xa3, 0xd1, 0xf5, 0xe2, 0xa7, 0x2d, 0x83, 0xa3, 0xbd, 0x02, 0x1c, 0x5d, 0x2d, 0x9e, 0xb2, - 0x80, 0x47, 0x29, 0xaa, 0xec, 0x8a, 0xb8, 0x2f, 0x78, 0x9a, 0xc8, 0x11, 0x2c, 0x8a, 0x82, 0x48, - 0x25, 0x6c, 0x39, 0xb1, 0x6e, 0x8b, 0x4c, 0x94, 0xfa, 0xd7, 0x4b, 0x10, 0x08, 0x9d, 0x3e, 0xe3, - 0x5d, 0xd6, 0x97, 0x46, 0xaa, 0x8b, 0x11, 0x9d, 0xcd, 0x62, 0xa6, 0xca, 0x62, 0x19, 0x60, 0xaa, - 0xe4, 0x81, 0x69, 0x07, 0x9a, 0x22, 0x57, 0x16, 0x30, 0x87, 0x86, 0x1a, 0x73, 0xc8, 0xf7, 0xe0, - 0x35, 0xcc, 0x33, 0x12, 0xbe, 0x54, 0x20, 0x56, 0x31, 0x10, 0x3b, 0x82, 0x21, 0x2d, 0x26, 0x13, - 0xe0, 0xdb, 0x70, 0x25, 0x23, 0x2b, 0xd6, 0xc5, 0x1c, 0x27, 0x93, 0x6f, 0x37, 0x91, 0x3e, 0x0c, - 0xc3, 0x23, 0x1a, 0x4f, 0xad, 0x9f, 0xa6, 0x06, 0x4a, 0xf1, 0x8c, 0x40, 0x75, 0x1c, 0xb8, 0xf2, - 0xde, 0x2d, 0x1b, 0xc7, 0x02, 0xe3, 0x66, 0xc1, 0x04, 0x0f, 0x67, 0xda, 0x62, 0x28, 0xa4, 0x92, - 0x50, 0x32, 0x65, 0xcc, 0x58, 0xbf, 0x36, 0xd2, 0xf5, 0x52, 0x88, 0x2b, 0x43, 0x23, 0xe3, 0xbf, - 0x41, 0xa3, 0xca, 0xab, 0xa1, 0x91, 0x75, 0x61, 0xa4, 0x9f, 0x2c, 0xc1, 0x99, 0x6f, 0x76, 0x45, - 0xe1, 0x3d, 0x9e, 0xef, 0xb2, 0x25, 0x9a, 0x74, 0xdd, 0x96, 0x13, 0x5d, 0x02, 0xd4, 0xd0, 0xcc, - 0xf9, 0x12, 0xa0, 0x8e, 0x34, 0x39, 0x21, 0xb7, 0x10, 0x9f, 0x82, 0xa7, 0x2a, 0x54, 0x5b, 0x03, - 0x55, 0x4d, 0x9f, 0x08, 0xa2, 0x2d, 0x79, 0x99, 0x6c, 0x6b, 0xe6, 0xc0, 0xed, 0x75, 0x30, 0xc5, - 0x41, 0xe3, 0x90, 0x8e, 0x19, 0x46, 0x9e, 0x69, 0xa7, 0x04, 0xeb, 0x04, 0xc8, 0xe5, 0x88, 0x27, - 0xef, 0x43, 0x95, 0xd3, 0x89, 0xb0, 0xb7, 0x30, 0x59, 0x7b, 0x20, 0x1b, 0x80, 0xc1, 0x47, 0xa7, - 0x27, 0xd4, 0x8b, 0x86, 0xd7, 0x84, 0xa9, 0xfe, 0xfe, 0x62, 0xa7, 0x2d, 0x64, 0xee, 0x04, 0x73, - 0x8f, 0xb3, 0x79, 0xc8, 0xcf, 0x6d, 0xd4, 0xb1, 0xfe, 0x61, 0x08, 0x24, 0xc8, 0x65, 0x82, 0x52, - 0xc3, 0x69, 0x77, 0xaf, 0x64, 0x40, 0xfb, 0xeb, 0x19, 0xf3, 0xdb, 0x00, 0x13, 0x1a, 0x3b, 0x9f, - 0x52, 0x9f, 0x33, 0x57, 0x59, 0xd4, 0x9c, 0xd0, 0xf8, 0x97, 0x48, 0x10, 0x15, 0x8e, 0x60, 0x2f, - 0x62, 0xe6, 0xa2, 0x69, 0xd7, 0xed, 0xfa, 0x84, 0xc6, 0x4f, 0x62, 0xe6, 0x26, 0xf7, 0xaa, 0xbf, - 0xfa, 0xbd, 0xf2, 0x76, 0x6c, 0x14, 0xed, 0xf8, 0xcf, 0x8c, 0x0f, 0xa7, 0x20, 0xf9, 0xff, 0x7f, - 0xef, 0xbf, 0x19, 0xa2, 0x36, 0xc8, 0xa7, 0x61, 0x72, 0x0c, 0xaf, 0x25, 0x71, 0xe4, 0x2c, 0x30, - 0xbe, 0xb4, 0x2f, 0xbd, 0x3c, 0xfc, 0xba, 0x67, 0x79, 0x72, 0x4c, 0x7e, 0x06, 0xd7, 0x0b, 0x59, - 0x20, 0x59, 0xb0, 0xf2, 0xd2, 0x64, 0x70, 0x35, 0x9f, 0x0c, 0xf4, 0x7a, 0xda, 0x12, 0xeb, 0xdf, - 0xc0, 0xb3, 0xbf, 0x23, 0x0a, 0xa5, 0x2c, 0x78, 0x94, 0x7d, 0x4b, 0xeb, 0x33, 0x03, 0x3a, 0x85, - 0xc3, 0x90, 0x3d, 0x00, 0x99, 0x5a, 0x63, 0xef, 0xb9, 0x2e, 0xda, 0xbb, 0xea, 0xe0, 0x68, 0xb2, - 0x47, 0xde, 0x73, 0x66, 0x9b, 0x23, 0x3d, 0x24, 0x1f, 0x42, 0x87, 0xa9, 0xd2, 0x4d, 0xe7, 0xbe, - 0x4a, 0x0e, 0xc5, 0x74, 0x61, 0xa7, 0x6e, 0xdb, 0x66, 0xb9, 0xb9, 0x75, 0x08, 0x66, 0xb2, 0x2e, - 0xf9, 0x16, 0x98, 0x73, 0xba, 0x54, 0x05, 0xb5, 0x2c, 0xc5, 0x1a, 0x73, 0xba, 0xc4, 0x5a, 0x9a, - 0x5c, 0x87, 0xba, 0x60, 0x4e, 0xa8, 0xdc, 0x61, 0xdd, 0xae, 0xcd, 0xe9, 0xf2, 0x27, 0x34, 0xb6, - 0x76, 0xa1, 0x9d, 0xdf, 0x44, 0x8b, 0x6a, 0x70, 0x93, 0xa2, 0x87, 0x13, 0x66, 0x3d, 0x82, 0x76, - 0xbe, 0x66, 0x15, 0x79, 0x2c, 0x0a, 0x16, 0xbe, 0x8b, 0x82, 0x1b, 0xb6, 0x9c, 0x88, 0xb6, 0xf7, - 0x2c, 0x90, 0x9f, 0x2e, 0x5b, 0xa4, 0x9e, 0x06, 0x9c, 0x65, 0x2a, 0x5d, 0x29, 0x63, 0x7d, 0xb6, - 0x01, 0x35, 0x59, 0x40, 0x93, 0x41, 0xbe, 0x3d, 0x13, 0xdf, 0x4d, 0x69, 0x4a, 0xaa, 0x52, 0x4c, - 0xb0, 0xf1, 0x8d, 0x62, 0x8f, 0x33, 0x6c, 0x5e, 0xbc, 0xd8, 0xa9, 0x23, 0xae, 0x1c, 0x3f, 0x48, - 0x1b, 0x9e, 0x55, 0xfd, 0x80, 0xee, 0xae, 0xaa, 0xaf, 0xdc, 0x5d, 0x5d, 0x87, 0xba, 0xbf, 0x98, - 0x3b, 0x7c, 0x19, 0xab, 0xf8, 0xac, 0xf9, 0x8b, 0xf9, 0xe3, 0x65, 0x2c, 0xbe, 0x01, 0x0f, 0x38, - 0x9d, 0x21, 0x4b, 0x46, 0x67, 0x03, 0x09, 0x82, 0x79, 0x00, 0xad, 0x0c, 0xfc, 0x7a, 0xae, 0x2a, - 0xe3, 0xda, 0x59, 0x0f, 0x39, 0x7e, 0xa0, 0x6e, 0xd9, 0x4c, 0xe0, 0xf8, 0xd8, 0x25, 0xb7, 0xf3, - 0xcd, 0x04, 0xa2, 0x76, 0x03, 0x9d, 0x31, 0xd3, 0x2f, 0x08, 0xcc, 0x16, 0x07, 0x10, 0xee, 0x29, - 0x45, 0x4c, 0x14, 0x69, 0x08, 0x02, 0x32, 0xdf, 0x84, 0x4e, 0x0a, 0x7c, 0x52, 0x04, 0xe4, 0x2a, - 0x29, 0x19, 0x05, 0xdf, 0x81, 0x2d, 0x9f, 0x2d, 0xb9, 0x53, 0x94, 0x6e, 0xa2, 0x34, 0x11, 0xbc, - 0xd3, 0xbc, 0xc6, 0x77, 0xa1, 0x9d, 0x06, 0x30, 0xca, 0x6e, 0xca, 0x96, 0x2e, 0xa1, 0xa2, 0xd8, - 0x0d, 0x68, 0x24, 0x65, 0x47, 0x0b, 0x05, 0xea, 0x54, 0x56, 0x1b, 0x49, 0x21, 0x13, 0xb1, 0x78, - 0x31, 0xe3, 0x6a, 0x91, 0x36, 0xca, 0x60, 0x21, 0x63, 0x4b, 0x3a, 0xca, 0xde, 0x82, 0x56, 0x12, - 0x37, 0x28, 0xd7, 0x41, 0xb9, 0x4d, 0x4d, 0x44, 0xa1, 0x5d, 0xe8, 0x86, 0x51, 0x10, 0x06, 0x31, - 0x8b, 0x1c, 0xea, 0xba, 0x11, 0x8b, 0xe3, 0x5e, 0x57, 0xae, 0xa7, 0xe9, 0x87, 0x92, 0x6c, 0x7d, - 0x1f, 0xea, 0xba, 0x9e, 0xda, 0x82, 0x0d, 0xb4, 0x3a, 0xba, 0x60, 0xd5, 0x96, 0x13, 0x91, 0xb9, - 0x0f, 0xc3, 0x50, 0xbd, 0x0a, 0x88, 0xa1, 0xf5, 0x2b, 0xa8, 0xab, 0x0f, 0x56, 0xda, 0x2b, 0xfe, - 0x08, 0x36, 0x43, 0x1a, 0x89, 0x6b, 0x64, 0x3b, 0x46, 0x5d, 0xb1, 0x9f, 0xd0, 0x88, 0x3f, 0x62, - 0x3c, 0xd7, 0x38, 0x36, 0x51, 0x5e, 0x92, 0xac, 0xbb, 0xd0, 0xca, 0xc9, 0x88, 0x63, 0xa1, 0x1f, - 0xe9, 0x48, 0xc3, 0x49, 0xb2, 0x73, 0x25, 0xdd, 0xd9, 0xba, 0x07, 0x66, 0xf2, 0x6d, 0x44, 0x61, - 0xa9, 0xaf, 0x6e, 0x28, 0x73, 0xcb, 0x29, 0x36, 0xc3, 0xc1, 0xa7, 0x2c, 0x52, 0x31, 0x21, 0x27, - 0xd6, 0x13, 0xe8, 0x14, 0x52, 0x36, 0xb9, 0x03, 0xf5, 0x70, 0x31, 0x72, 0xf4, 0x23, 0x46, 0xda, - 0xf6, 0x9e, 0x2c, 0x46, 0x1f, 0xb1, 0x73, 0xdd, 0xf6, 0x86, 0x38, 0x4b, 0x97, 0xad, 0x64, 0x97, - 0x9d, 0x41, 0x43, 0x47, 0x3f, 0xf9, 0x01, 0x98, 0x89, 0x5b, 0x15, 0x72, 0x64, 0xb2, 0xb5, 0x5a, - 0x34, 0x15, 0x14, 0xde, 0x11, 0x7b, 0x13, 0x9f, 0xb9, 0x4e, 0x1a, 0x42, 0xb8, 0x47, 0xc3, 0xee, - 0x48, 0xc6, 0xc7, 0x3a, 0x5e, 0xac, 0x77, 0xa0, 0x26, 0xcf, 0x26, 0xec, 0x23, 0x56, 0xd6, 0xb5, - 0xb6, 0x18, 0x97, 0x26, 0xf3, 0x3f, 0x18, 0xd0, 0xd0, 0x59, 0xb0, 0x54, 0x29, 0x77, 0xe8, 0xca, - 0xd7, 0x3d, 0xf4, 0xff, 0x3e, 0xf1, 0xdc, 0x01, 0x22, 0xf3, 0xcb, 0x59, 0xc0, 0x3d, 0x7f, 0xe2, - 0x48, 0x5b, 0xcb, 0x1c, 0xd4, 0x45, 0xce, 0x29, 0x32, 0x4e, 0x04, 0x7d, 0xff, 0xf3, 0x0d, 0xe8, - 0x1c, 0x0e, 0xef, 0x1f, 0x1f, 0x86, 0xe1, 0xcc, 0x1b, 0x53, 0xac, 0xdf, 0xf7, 0xa0, 0x8a, 0x2d, - 0x4c, 0xc9, 0x13, 0x6c, 0xbf, 0xac, 0x97, 0x26, 0xfb, 0xb0, 0x81, 0x9d, 0x0c, 0x29, 0x7b, 0x89, - 0xed, 0x97, 0xb6, 0xd4, 0x62, 0x13, 0xd9, 0xeb, 0x5c, 0x7e, 0x90, 0xed, 0x97, 0xf5, 0xd5, 0xe4, - 0x43, 0x30, 0xd3, 0x16, 0x63, 0xd5, 0xb3, 0x6c, 0x7f, 0x65, 0x87, 0x2d, 0xf4, 0xd3, 0x72, 0x6c, - 0xd5, 0xeb, 0x62, 0x7f, 0x65, 0x2b, 0x4a, 0x0e, 0xa0, 0xae, 0x8b, 0xd8, 0xf2, 0x87, 0xd3, 0xfe, - 0x8a, 0xee, 0x57, 0x98, 0x47, 0x76, 0x0d, 0x65, 0xaf, 0xbb, 0xfd, 0xd2, 0x16, 0x9d, 0xbc, 0x07, - 0x35, 0x55, 0x59, 0x94, 0x3e, 0x9e, 0xf6, 0xcb, 0x7b, 0x58, 0x71, 0xc9, 0xb4, 0x6f, 0x5a, 0xf5, - 0x02, 0xdd, 0x5f, 0xf9, 0x96, 0x40, 0x0e, 0x01, 0x32, 0xc5, 0xff, 0xca, 0xa7, 0xe5, 0xfe, 0xea, - 0x37, 0x02, 0x72, 0x0f, 0x1a, 0xe9, 0xbb, 0x4f, 0xf9, 0x63, 0x71, 0x7f, 0x55, 0xdb, 0x3e, 0x7c, - 0xfd, 0x5f, 0x7f, 0xde, 0x36, 0x7e, 0x73, 0xb1, 0x6d, 0x7c, 0x79, 0xb1, 0x6d, 0x7c, 0x75, 0xb1, - 0x6d, 0xfc, 0xfe, 0x62, 0xdb, 0xf8, 0xd3, 0xc5, 0xb6, 0xf1, 0xdb, 0xbf, 0x6c, 0x1b, 0xa3, 0x1a, - 0xba, 0xff, 0xbb, 0xff, 0x0e, 0x00, 0x00, 0xff, 0xff, 0x1a, 0x7c, 0xbd, 0x95, 0x1c, 0x19, 0x00, - 0x00, +var fileDescriptor_types_5b877df1938afe10 = []byte{ + // 2214 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xdc, 0x58, 0xcb, 0x73, 0x1b, 0xc7, + 0xd1, 0xe7, 0x82, 0x20, 0x81, 0x6d, 0x10, 0x0f, 0x8d, 0x28, 0x09, 0xc2, 0xe7, 0x8f, 0x54, 0xad, + 0x12, 0x5b, 0x8c, 0x65, 0xd0, 0xa6, 0xa3, 0x14, 0x65, 0x39, 0xa9, 0x22, 0x24, 0xc5, 0x64, 0xd9, + 0x49, 0x98, 0x95, 0xc4, 0x5c, 0x52, 0xb5, 0x35, 0xc0, 0x8e, 0x80, 0x2d, 0x02, 0xbb, 0xeb, 0xdd, + 0x01, 0x0d, 0xea, 0x98, 0xb3, 0x0f, 0x3e, 0xe4, 0x8f, 0xc8, 0x35, 0x37, 0x1f, 0x73, 0x4a, 0xf9, + 0x98, 0x43, 0xce, 0x4a, 0xc2, 0x54, 0x0e, 0xc9, 0x35, 0x95, 0xaa, 0x1c, 0x53, 0xd3, 0x33, 0xb3, + 0x2f, 0x2e, 0x14, 0xcb, 0xc9, 0x29, 0x17, 0x60, 0xa6, 0x1f, 0xf3, 0xe8, 0xed, 0xee, 0x5f, 0xf7, + 0xc0, 0x75, 0x3a, 0x1c, 0x79, 0xbb, 0xfc, 0x3c, 0x64, 0xb1, 0xfc, 0xed, 0x87, 0x51, 0xc0, 0x03, + 0xb2, 0x86, 0x93, 0xde, 0x3b, 0x63, 0x8f, 0x4f, 0xe6, 0xc3, 0xfe, 0x28, 0x98, 0xed, 0x8e, 0x83, + 0x71, 0xb0, 0x8b, 0xdc, 0xe1, 0xfc, 0x39, 0xce, 0x70, 0x82, 0x23, 0xa9, 0xd5, 0xdb, 0x1e, 0x07, + 0xc1, 0x78, 0xca, 0x52, 0x29, 0xee, 0xcd, 0x58, 0xcc, 0xe9, 0x2c, 0x54, 0x02, 0xfb, 0x99, 0xf5, + 0x38, 0xf3, 0x5d, 0x16, 0xcd, 0x3c, 0x9f, 0x67, 0x87, 0x53, 0x6f, 0x18, 0xef, 0x8e, 0x82, 0xd9, + 0x2c, 0xf0, 0xb3, 0x07, 0xea, 0x3d, 0xf8, 0xb7, 0x9a, 0xa3, 0xe8, 0x3c, 0xe4, 0xc1, 0xee, 0x8c, + 0x45, 0xa7, 0x53, 0xa6, 0xfe, 0xa4, 0xb2, 0xf5, 0xdb, 0x2a, 0xd4, 0x6c, 0xf6, 0xe9, 0x9c, 0xc5, + 0x9c, 0xdc, 0x81, 0x2a, 0x1b, 0x4d, 0x82, 0x6e, 0xe5, 0x96, 0x71, 0xa7, 0xb1, 0x47, 0xfa, 0x72, + 0x13, 0xc5, 0x7d, 0x3c, 0x9a, 0x04, 0x87, 0x2b, 0x36, 0x4a, 0x90, 0xb7, 0x61, 0xed, 0xf9, 0x74, + 0x1e, 0x4f, 0xba, 0xab, 0x28, 0x7a, 0x35, 0x2f, 0xfa, 0x43, 0xc1, 0x3a, 0x5c, 0xb1, 0xa5, 0x8c, + 0x58, 0xd6, 0xf3, 0x9f, 0x07, 0xdd, 0x6a, 0xd9, 0xb2, 0x47, 0xfe, 0x73, 0x5c, 0x56, 0x48, 0x90, + 0x7d, 0x80, 0x98, 0x71, 0x27, 0x08, 0xb9, 0x17, 0xf8, 0xdd, 0x35, 0x94, 0xbf, 0x91, 0x97, 0x7f, + 0xc2, 0xf8, 0x4f, 0x90, 0x7d, 0xb8, 0x62, 0x9b, 0xb1, 0x9e, 0x08, 0x4d, 0xcf, 0xf7, 0xb8, 0x33, + 0x9a, 0x50, 0xcf, 0xef, 0xae, 0x97, 0x69, 0x1e, 0xf9, 0x1e, 0x7f, 0x28, 0xd8, 0x42, 0xd3, 0xd3, + 0x13, 0x71, 0x95, 0x4f, 0xe7, 0x2c, 0x3a, 0xef, 0xd6, 0xca, 0xae, 0xf2, 0x53, 0xc1, 0x12, 0x57, + 0x41, 0x19, 0xf2, 0x00, 0x1a, 0x43, 0x36, 0xf6, 0x7c, 0x67, 0x38, 0x0d, 0x46, 0xa7, 0xdd, 0x3a, + 0xaa, 0x74, 0xf3, 0x2a, 0x03, 0x21, 0x30, 0x10, 0xfc, 0xc3, 0x15, 0x1b, 0x86, 0xc9, 0x8c, 0xec, + 0x41, 0x7d, 0x34, 0x61, 0xa3, 0x53, 0x87, 0x2f, 0xba, 0x26, 0x6a, 0x5e, 0xcb, 0x6b, 0x3e, 0x14, + 0xdc, 0xa7, 0x8b, 0xc3, 0x15, 0xbb, 0x36, 0x92, 0x43, 0x72, 0x0f, 0x4c, 0xe6, 0xbb, 0x6a, 0xbb, + 0x06, 0x2a, 0x5d, 0x2f, 0x7c, 0x17, 0xdf, 0xd5, 0x9b, 0xd5, 0x99, 0x1a, 0x93, 0x3e, 0xac, 0x0b, + 0x47, 0xf1, 0x78, 0x77, 0x03, 0x75, 0x36, 0x0b, 0x1b, 0x21, 0xef, 0x70, 0xc5, 0x56, 0x52, 0xc2, + 0x7c, 0x2e, 0x9b, 0x7a, 0x67, 0x2c, 0x12, 0x87, 0xbb, 0x5a, 0x66, 0xbe, 0x47, 0x92, 0x8f, 0xc7, + 0x33, 0x5d, 0x3d, 0x19, 0xd4, 0x60, 0xed, 0x8c, 0x4e, 0xe7, 0xcc, 0x7a, 0x0b, 0x1a, 0x19, 0x4f, + 0x21, 0x5d, 0xa8, 0xcd, 0x58, 0x1c, 0xd3, 0x31, 0xeb, 0x1a, 0xb7, 0x8c, 0x3b, 0xa6, 0xad, 0xa7, + 0x56, 0x0b, 0x36, 0xb2, 0x7e, 0x62, 0xcd, 0x12, 0x45, 0xe1, 0x0b, 0x42, 0xf1, 0x8c, 0x45, 0xb1, + 0x70, 0x00, 0xa5, 0xa8, 0xa6, 0xe4, 0x36, 0x34, 0xd1, 0x0e, 0x8e, 0xe6, 0x0b, 0x3f, 0xad, 0xda, + 0x1b, 0x48, 0x3c, 0x51, 0x42, 0xdb, 0xd0, 0x08, 0xf7, 0xc2, 0x44, 0x64, 0x15, 0x45, 0x20, 0xdc, + 0x0b, 0x95, 0x80, 0xf5, 0x01, 0x74, 0x8a, 0xae, 0x44, 0x3a, 0xb0, 0x7a, 0xca, 0xce, 0xd5, 0x7e, + 0x62, 0x48, 0x36, 0xd5, 0xb5, 0x70, 0x0f, 0xd3, 0x56, 0x77, 0xfc, 0xa2, 0x92, 0x28, 0x27, 0xde, + 0x44, 0xf6, 0xa1, 0x2a, 0x62, 0x19, 0xb5, 0x1b, 0x7b, 0xbd, 0xbe, 0x0c, 0xf4, 0xbe, 0x0e, 0xf4, + 0xfe, 0x53, 0x1d, 0xe8, 0x83, 0xfa, 0x57, 0x2f, 0xb7, 0x57, 0xbe, 0xf8, 0xc3, 0xb6, 0x61, 0xa3, + 0x06, 0xb9, 0x29, 0x1c, 0x82, 0x7a, 0xbe, 0xe3, 0xb9, 0x6a, 0x9f, 0x1a, 0xce, 0x8f, 0x5c, 0x72, + 0x00, 0x9d, 0x51, 0xe0, 0xc7, 0xcc, 0x8f, 0xe7, 0xb1, 0x13, 0xd2, 0x88, 0xce, 0x62, 0x15, 0x6b, + 0xfa, 0xf3, 0x3f, 0xd4, 0xec, 0x63, 0xe4, 0xda, 0xed, 0x51, 0x9e, 0x40, 0x3e, 0x04, 0x38, 0xa3, + 0x53, 0xcf, 0xa5, 0x3c, 0x88, 0xe2, 0x6e, 0xf5, 0xd6, 0x6a, 0x46, 0xf9, 0x44, 0x33, 0x9e, 0x85, + 0x2e, 0xe5, 0x6c, 0x50, 0x15, 0x27, 0xb3, 0x33, 0xf2, 0xe4, 0x4d, 0x68, 0xd3, 0x30, 0x74, 0x62, + 0x4e, 0x39, 0x73, 0x86, 0xe7, 0x9c, 0xc5, 0x18, 0x8f, 0x1b, 0x76, 0x93, 0x86, 0xe1, 0x13, 0x41, + 0x1d, 0x08, 0xa2, 0xe5, 0x26, 0x5f, 0x13, 0x43, 0x85, 0x10, 0xa8, 0xba, 0x94, 0x53, 0xb4, 0xc6, + 0x86, 0x8d, 0x63, 0x41, 0x0b, 0x29, 0x9f, 0xa8, 0x3b, 0xe2, 0x98, 0x5c, 0x87, 0xf5, 0x09, 0xf3, + 0xc6, 0x13, 0x8e, 0xd7, 0x5a, 0xb5, 0xd5, 0x4c, 0x18, 0x3e, 0x8c, 0x82, 0x33, 0x86, 0xd9, 0xa2, + 0x6e, 0xcb, 0x89, 0xf5, 0x17, 0x03, 0xae, 0x5c, 0x0a, 0x2f, 0xb1, 0xee, 0x84, 0xc6, 0x13, 0xbd, + 0x97, 0x18, 0x93, 0xb7, 0xc5, 0xba, 0xd4, 0x65, 0x91, 0xca, 0x62, 0x4d, 0x75, 0xe3, 0x43, 0x24, + 0xaa, 0x8b, 0x2a, 0x11, 0xf2, 0x18, 0x3a, 0x53, 0x1a, 0x73, 0x47, 0x46, 0x81, 0x83, 0x59, 0x6a, + 0x35, 0x17, 0x99, 0x9f, 0x50, 0x1d, 0x2d, 0xc2, 0x39, 0x95, 0x7a, 0x6b, 0x9a, 0xa3, 0x92, 0x43, + 0xd8, 0x1c, 0x9e, 0xbf, 0xa0, 0x3e, 0xf7, 0x7c, 0xe6, 0x5c, 0xb2, 0x79, 0x5b, 0x2d, 0xf5, 0xf8, + 0xcc, 0x73, 0x99, 0x3f, 0xd2, 0xc6, 0xbe, 0x9a, 0xa8, 0x24, 0x1f, 0x23, 0xb6, 0x6e, 0x41, 0x2b, + 0x9f, 0x0b, 0x48, 0x0b, 0x2a, 0x7c, 0xa1, 0x6e, 0x58, 0xe1, 0x0b, 0xcb, 0x4a, 0x3c, 0x30, 0x09, + 0xc8, 0x4b, 0x32, 0x3b, 0xd0, 0x2e, 0x24, 0x87, 0x8c, 0xb9, 0x8d, 0xac, 0xb9, 0xad, 0x36, 0x34, + 0x73, 0x39, 0xc1, 0xfa, 0x7c, 0x0d, 0xea, 0x36, 0x8b, 0x43, 0xe1, 0x4c, 0x64, 0x1f, 0x4c, 0xb6, + 0x18, 0x31, 0x99, 0x8e, 0x8d, 0x42, 0xb2, 0x93, 0x32, 0x8f, 0x35, 0x5f, 0xa4, 0x85, 0x44, 0x98, + 0xec, 0xe4, 0xa0, 0xe4, 0x6a, 0x51, 0x29, 0x8b, 0x25, 0x77, 0xf3, 0x58, 0xb2, 0x59, 0x90, 0x2d, + 0x80, 0xc9, 0x4e, 0x0e, 0x4c, 0x8a, 0x0b, 0xe7, 0xd0, 0xe4, 0x7e, 0x09, 0x9a, 0x14, 0x8f, 0xbf, + 0x04, 0x4e, 0xee, 0x97, 0xc0, 0x49, 0xf7, 0xd2, 0x5e, 0xa5, 0x78, 0x72, 0x37, 0x8f, 0x27, 0xc5, + 0xeb, 0x14, 0x00, 0xe5, 0xc3, 0x32, 0x40, 0xb9, 0x59, 0xd0, 0x59, 0x8a, 0x28, 0xef, 0x5f, 0x42, + 0x94, 0xeb, 0x05, 0xd5, 0x12, 0x48, 0xb9, 0x9f, 0xcb, 0xf5, 0x50, 0x7a, 0xb7, 0xf2, 0x64, 0x4f, + 0xbe, 0x77, 0x19, 0x8d, 0x6e, 0x14, 0x3f, 0x6d, 0x19, 0x1c, 0xed, 0x16, 0xe0, 0xe8, 0x5a, 0xf1, + 0x94, 0x05, 0x3c, 0x4a, 0x51, 0x65, 0x47, 0xc4, 0x7d, 0xc1, 0xd3, 0x44, 0x8e, 0x60, 0x51, 0x14, + 0x44, 0x2a, 0x61, 0xcb, 0x89, 0x75, 0x47, 0x64, 0xa2, 0xd4, 0xbf, 0x5e, 0x81, 0x40, 0xe8, 0xf4, + 0x19, 0xef, 0xb2, 0xbe, 0x34, 0x52, 0x5d, 0x8c, 0xe8, 0x6c, 0x16, 0x33, 0x55, 0x16, 0xcb, 0x00, + 0x53, 0x25, 0x0f, 0x4c, 0xdb, 0xd0, 0x10, 0xb9, 0xb2, 0x80, 0x39, 0x34, 0xd4, 0x98, 0x43, 0xbe, + 0x03, 0x57, 0x30, 0xcf, 0x48, 0xf8, 0x52, 0x81, 0x58, 0xc5, 0x40, 0x6c, 0x0b, 0x86, 0xb4, 0x98, + 0x4c, 0x80, 0xef, 0xc0, 0xd5, 0x8c, 0xac, 0x58, 0x17, 0x73, 0x9c, 0x4c, 0xbe, 0x9d, 0x44, 0xfa, + 0x20, 0x0c, 0x0f, 0x69, 0x3c, 0xb1, 0x7e, 0x94, 0x1a, 0x28, 0xc5, 0x33, 0x02, 0xd5, 0x51, 0xe0, + 0xca, 0x7b, 0x37, 0x6d, 0x1c, 0x0b, 0x8c, 0x9b, 0x06, 0x63, 0x3c, 0x9c, 0x69, 0x8b, 0xa1, 0x90, + 0x4a, 0x42, 0xc9, 0x94, 0x31, 0x63, 0xfd, 0xd2, 0x48, 0xd7, 0x4b, 0x21, 0xae, 0x0c, 0x8d, 0x8c, + 0xff, 0x04, 0x8d, 0x2a, 0xaf, 0x87, 0x46, 0xd6, 0x85, 0x91, 0x7e, 0xb2, 0x04, 0x67, 0xbe, 0xd9, + 0x15, 0x85, 0xf7, 0x78, 0xbe, 0xcb, 0x16, 0x68, 0xd2, 0x55, 0x5b, 0x4e, 0x74, 0x09, 0xb0, 0x8e, + 0x66, 0xce, 0x97, 0x00, 0x35, 0xa4, 0xc9, 0x09, 0xb9, 0x8d, 0xf8, 0x14, 0x3c, 0x57, 0xa1, 0xda, + 0xec, 0xab, 0x6a, 0xfa, 0x58, 0x10, 0x6d, 0xc9, 0xcb, 0x64, 0x5b, 0x33, 0x07, 0x6e, 0x6f, 0x80, + 0x29, 0x0e, 0x1a, 0x87, 0x74, 0xc4, 0x30, 0xf2, 0x4c, 0x3b, 0x25, 0x58, 0xc7, 0x40, 0x2e, 0x47, + 0x3c, 0xf9, 0x00, 0xaa, 0x9c, 0x8e, 0x85, 0xbd, 0x85, 0xc9, 0x5a, 0x7d, 0xd9, 0x00, 0xf4, 0x3f, + 0x3e, 0x39, 0xa6, 0x5e, 0x34, 0xb8, 0x2e, 0x4c, 0xf5, 0xb7, 0x97, 0xdb, 0x2d, 0x21, 0x73, 0x37, + 0x98, 0x79, 0x9c, 0xcd, 0x42, 0x7e, 0x6e, 0xa3, 0x8e, 0xf5, 0x77, 0x43, 0x20, 0x41, 0x2e, 0x13, + 0x94, 0x1a, 0x4e, 0xbb, 0x7b, 0x25, 0x03, 0xda, 0x5f, 0xcf, 0x98, 0xff, 0x0f, 0x30, 0xa6, 0xb1, + 0xf3, 0x19, 0xf5, 0x39, 0x73, 0x95, 0x45, 0xcd, 0x31, 0x8d, 0x7f, 0x86, 0x04, 0x51, 0xe1, 0x08, + 0xf6, 0x3c, 0x66, 0x2e, 0x9a, 0x76, 0xd5, 0xae, 0x8d, 0x69, 0xfc, 0x2c, 0x66, 0x6e, 0x72, 0xaf, + 0xda, 0xeb, 0xdf, 0x2b, 0x6f, 0xc7, 0x7a, 0xd1, 0x8e, 0xff, 0xc8, 0xf8, 0x70, 0x0a, 0x92, 0xff, + 0xfb, 0xf7, 0xfe, 0xab, 0x21, 0x6a, 0x83, 0x7c, 0x1a, 0x26, 0x47, 0x70, 0x25, 0x89, 0x23, 0x67, + 0x8e, 0xf1, 0xa5, 0x7d, 0xe9, 0xd5, 0xe1, 0xd7, 0x39, 0xcb, 0x93, 0x63, 0xf2, 0x63, 0xb8, 0x51, + 0xc8, 0x02, 0xc9, 0x82, 0x95, 0x57, 0x26, 0x83, 0x6b, 0xf9, 0x64, 0xa0, 0xd7, 0xd3, 0x96, 0x58, + 0xfd, 0x06, 0x9e, 0xfd, 0x2d, 0x51, 0x28, 0x65, 0xc1, 0xa3, 0xec, 0x5b, 0x5a, 0xbf, 0x36, 0xa0, + 0x5d, 0x38, 0x0c, 0xb9, 0x07, 0x20, 0x53, 0x6b, 0xec, 0xbd, 0x60, 0x85, 0x2c, 0x86, 0x26, 0x7b, + 0xe2, 0xbd, 0x60, 0xea, 0xe0, 0xe6, 0x50, 0x13, 0xc8, 0x7b, 0x50, 0x67, 0xaa, 0x80, 0x53, 0xb7, + 0xbd, 0x56, 0xa8, 0xeb, 0x94, 0x4e, 0x22, 0x46, 0xbe, 0x0b, 0x66, 0x62, 0xc3, 0x42, 0xf1, 0x9e, + 0x98, 0x5c, 0x6f, 0x94, 0x08, 0x5a, 0x1f, 0x41, 0xbb, 0x70, 0x0c, 0xf2, 0x7f, 0x60, 0xce, 0xe8, + 0x42, 0x55, 0xe1, 0xb2, 0x7e, 0xab, 0xcf, 0xe8, 0x02, 0x0b, 0x70, 0x72, 0x03, 0x6a, 0x82, 0x39, + 0xa6, 0xf2, 0x2b, 0xac, 0xda, 0xeb, 0x33, 0xba, 0xf8, 0x88, 0xc6, 0xd6, 0x0e, 0xb4, 0xf2, 0x47, + 0xd3, 0xa2, 0x1a, 0x11, 0xa5, 0xe8, 0xc1, 0x98, 0x59, 0xf7, 0xa0, 0x5d, 0x38, 0x11, 0xb1, 0xa0, + 0x19, 0xce, 0x87, 0xce, 0x29, 0x3b, 0x77, 0xf0, 0xc8, 0xe8, 0x33, 0xa6, 0xdd, 0x08, 0xe7, 0xc3, + 0x8f, 0xd9, 0xf9, 0x53, 0x41, 0xb2, 0x9e, 0x40, 0x2b, 0x5f, 0x1f, 0x8b, 0x9c, 0x19, 0x05, 0x73, + 0xdf, 0xc5, 0xf5, 0xd7, 0x6c, 0x39, 0x11, 0x2d, 0xf6, 0x59, 0x20, 0xdd, 0x24, 0x5b, 0x10, 0x9f, + 0x04, 0x9c, 0x65, 0xaa, 0x6a, 0x29, 0x63, 0xfd, 0x62, 0x0d, 0xd6, 0x65, 0xb1, 0x4e, 0xfa, 0xf9, + 0x56, 0x50, 0xf8, 0x88, 0xd2, 0x94, 0x54, 0xa5, 0x98, 0xe0, 0xf0, 0x9b, 0xc5, 0x7e, 0x6a, 0xd0, + 0xb8, 0x78, 0xb9, 0x5d, 0x43, 0x0c, 0x3b, 0x7a, 0x94, 0x36, 0x57, 0xcb, 0x7a, 0x0f, 0xdd, 0xc9, + 0x55, 0x5f, 0xbb, 0x93, 0xbb, 0x01, 0x35, 0x7f, 0x3e, 0x73, 0xf8, 0x22, 0x56, 0xb9, 0x60, 0xdd, + 0x9f, 0xcf, 0x9e, 0x2e, 0xf0, 0xd3, 0xf1, 0x80, 0xd3, 0x29, 0xb2, 0x64, 0x26, 0xa8, 0x23, 0x41, + 0x30, 0xf7, 0xa1, 0x99, 0x81, 0x7a, 0xcf, 0x55, 0x25, 0x63, 0x2b, 0xeb, 0x8d, 0x47, 0x8f, 0xd4, + 0x2d, 0x1b, 0x09, 0xf4, 0x1f, 0xb9, 0xe4, 0x4e, 0xbe, 0x71, 0xc1, 0x0a, 0xa1, 0x8e, 0x8e, 0x9f, + 0xe9, 0x4d, 0x44, 0x7d, 0x20, 0x0e, 0x20, 0x42, 0x41, 0x8a, 0x98, 0x28, 0x52, 0x17, 0x04, 0x64, + 0xbe, 0x05, 0xed, 0x14, 0x64, 0xa5, 0x08, 0xc8, 0x55, 0x52, 0x32, 0x0a, 0xbe, 0x0b, 0x9b, 0x3e, + 0x5b, 0x70, 0xa7, 0x28, 0xdd, 0x40, 0x69, 0x22, 0x78, 0x27, 0x79, 0x8d, 0x6f, 0x43, 0x2b, 0x4d, + 0x16, 0x28, 0xbb, 0x21, 0xdb, 0xc7, 0x84, 0x8a, 0x62, 0x37, 0xa1, 0x9e, 0x94, 0x38, 0x4d, 0x14, + 0xa8, 0x51, 0x59, 0xd9, 0x24, 0x45, 0x53, 0xc4, 0xe2, 0xf9, 0x94, 0xab, 0x45, 0x5a, 0x28, 0x83, + 0x45, 0x93, 0x2d, 0xe9, 0x28, 0x7b, 0x1b, 0x9a, 0x3a, 0xec, 0xa4, 0x5c, 0x1b, 0xe5, 0x36, 0x34, + 0x11, 0x85, 0x76, 0xa0, 0x13, 0x46, 0x41, 0x18, 0xc4, 0x2c, 0x72, 0xa8, 0xeb, 0x46, 0x2c, 0x8e, + 0xbb, 0x1d, 0xb9, 0x9e, 0xa6, 0x1f, 0x48, 0xb2, 0xf5, 0x1e, 0xd4, 0x74, 0xed, 0xb6, 0x09, 0x6b, + 0x68, 0x75, 0x74, 0xc1, 0xaa, 0x2d, 0x27, 0x02, 0x25, 0x0e, 0xc2, 0x50, 0xbd, 0x40, 0x88, 0xa1, + 0xf5, 0x73, 0xa8, 0xa9, 0x0f, 0x56, 0xda, 0x97, 0x7e, 0x1f, 0x36, 0x42, 0x1a, 0x89, 0x6b, 0x64, + 0xbb, 0x53, 0xdd, 0x1d, 0x1c, 0xd3, 0x88, 0x3f, 0x61, 0x3c, 0xd7, 0xa4, 0x36, 0x50, 0x5e, 0x92, + 0xac, 0xfb, 0xd0, 0xcc, 0xc9, 0x88, 0x63, 0xa1, 0x1f, 0xe9, 0x48, 0xc3, 0x49, 0xb2, 0x73, 0x25, + 0xdd, 0xd9, 0x7a, 0x00, 0x66, 0xf2, 0x6d, 0x44, 0x11, 0xab, 0xaf, 0x6e, 0x28, 0x73, 0xcb, 0x29, + 0x36, 0xde, 0xc1, 0x67, 0x2c, 0x52, 0x31, 0x21, 0x27, 0xd6, 0xb3, 0x4c, 0x66, 0x90, 0x79, 0x9b, + 0xdc, 0x85, 0x9a, 0xca, 0x0c, 0x2a, 0x2a, 0x75, 0x8b, 0x7d, 0x8c, 0xa9, 0x41, 0xb7, 0xd8, 0x32, + 0x51, 0xa4, 0xcb, 0x56, 0xb2, 0xcb, 0x4e, 0xa1, 0xae, 0xa3, 0x3f, 0x9f, 0x26, 0xe5, 0x8a, 0x9d, + 0x62, 0x9a, 0x54, 0x8b, 0xa6, 0x82, 0xc2, 0x3b, 0x62, 0x6f, 0xec, 0x33, 0xd7, 0x49, 0x43, 0x08, + 0xf7, 0xa8, 0xdb, 0x6d, 0xc9, 0xf8, 0x44, 0xc7, 0x8b, 0xf5, 0x2e, 0xac, 0xcb, 0xb3, 0x09, 0xfb, + 0x88, 0x95, 0x75, 0x5d, 0x2f, 0xc6, 0xa5, 0xc0, 0xf1, 0x7b, 0x03, 0xea, 0x3a, 0x79, 0x96, 0x2a, + 0xe5, 0x0e, 0x5d, 0xf9, 0xba, 0x87, 0xfe, 0xef, 0x27, 0x9e, 0xbb, 0x40, 0x64, 0x7e, 0x39, 0x0b, + 0xb8, 0xe7, 0x8f, 0x1d, 0x69, 0x6b, 0x99, 0x83, 0x3a, 0xc8, 0x39, 0x41, 0xc6, 0xb1, 0xa0, 0xef, + 0x7d, 0xbe, 0x06, 0xed, 0x83, 0xc1, 0xc3, 0xa3, 0x83, 0x30, 0x9c, 0x7a, 0x23, 0x8a, 0xbd, 0xc2, + 0x2e, 0x54, 0xb1, 0x5d, 0x2a, 0x79, 0xee, 0xed, 0x95, 0xf5, 0xed, 0x64, 0x0f, 0xd6, 0xb0, 0x6b, + 0x22, 0x65, 0xaf, 0xbe, 0xbd, 0xd2, 0xf6, 0x5d, 0x6c, 0x22, 0xfb, 0xaa, 0xcb, 0x8f, 0xbf, 0xbd, + 0xb2, 0x1e, 0x9e, 0xfc, 0x00, 0xcc, 0xb4, 0x9d, 0x59, 0xf6, 0x04, 0xdc, 0x5b, 0xda, 0xcd, 0x0b, + 0xfd, 0xb4, 0xf4, 0x5b, 0xf6, 0x92, 0xd9, 0x5b, 0xda, 0xf6, 0x92, 0x7d, 0xa8, 0xe9, 0x82, 0xb9, + 0xfc, 0x91, 0xb6, 0xb7, 0xa4, 0xd3, 0x16, 0xe6, 0x91, 0x1d, 0x4a, 0xd9, 0x4b, 0x72, 0xaf, 0xf4, + 0x39, 0x80, 0xdc, 0x83, 0x75, 0x55, 0xc5, 0x94, 0x3e, 0xd4, 0xf6, 0xca, 0xfb, 0x65, 0x71, 0xc9, + 0xb4, 0x47, 0x5b, 0xf6, 0xda, 0xdd, 0x5b, 0xfa, 0x6e, 0x41, 0x0e, 0x00, 0x32, 0x8d, 0xc6, 0xd2, + 0x67, 0xec, 0xde, 0xf2, 0xf7, 0x08, 0xf2, 0x00, 0xea, 0xe9, 0x1b, 0x53, 0xf9, 0xc3, 0x74, 0x6f, + 0xd9, 0x13, 0xc1, 0xe0, 0x8d, 0x7f, 0xfe, 0x69, 0xcb, 0xf8, 0xd5, 0xc5, 0x96, 0xf1, 0xe5, 0xc5, + 0x96, 0xf1, 0xd5, 0xc5, 0x96, 0xf1, 0xbb, 0x8b, 0x2d, 0xe3, 0x8f, 0x17, 0x5b, 0xc6, 0x6f, 0xfe, + 0xbc, 0x65, 0x0c, 0xd7, 0xd1, 0xfd, 0xdf, 0xff, 0x57, 0x00, 0x00, 0x00, 0xff, 0xff, 0xfc, 0x8d, + 0xcb, 0x04, 0x88, 0x19, 0x00, 0x00, } diff --git a/abci/types/types.proto b/abci/types/types.proto index ffa32183..b48ff1e8 100644 --- a/abci/types/types.proto +++ b/abci/types/types.proto @@ -74,7 +74,6 @@ message RequestQuery { bool prove = 4; } -// NOTE: validators here have empty pubkeys. message RequestBeginBlock { bytes hash = 1; Header header = 2 [(gogoproto.nullable)=false]; @@ -208,12 +207,13 @@ message ResponseCommit { // ConsensusParams contains all consensus-relevant parameters // that can be adjusted by the abci app message ConsensusParams { - BlockSize block_size = 1; - EvidenceParams evidence_params = 2; + BlockSizeParams block_size = 1; + EvidenceParams evidence = 2; + ValidatorParams validator = 3; } // BlockSize contains limits on the block size. -message BlockSize { +message BlockSizeParams { // Note: must be greater than 0 int64 max_bytes = 1; // Note: must be greater or equal to -1 @@ -226,6 +226,11 @@ message EvidenceParams { int64 max_age = 1; } +// ValidatorParams contains limits on validators. +message ValidatorParams { + repeated string pub_key_types = 1; +} + message LastCommitInfo { int32 round = 1; repeated VoteInfo votes = 2 [(gogoproto.nullable)=false]; diff --git a/abci/types/typespb_test.go b/abci/types/typespb_test.go index 53c5cd94..9375cc7f 100644 --- a/abci/types/typespb_test.go +++ b/abci/types/typespb_test.go @@ -1479,15 +1479,15 @@ func TestConsensusParamsMarshalTo(t *testing.T) { } } -func TestBlockSizeProto(t *testing.T) { +func TestBlockSizeParamsProto(t *testing.T) { seed := time.Now().UnixNano() popr := math_rand.New(math_rand.NewSource(seed)) - p := NewPopulatedBlockSize(popr, false) + p := NewPopulatedBlockSizeParams(popr, false) dAtA, err := github_com_gogo_protobuf_proto.Marshal(p) if err != nil { t.Fatalf("seed = %d, err = %v", seed, err) } - msg := &BlockSize{} + msg := &BlockSizeParams{} if err := github_com_gogo_protobuf_proto.Unmarshal(dAtA, msg); err != nil { t.Fatalf("seed = %d, err = %v", seed, err) } @@ -1510,10 +1510,10 @@ func TestBlockSizeProto(t *testing.T) { } } -func TestBlockSizeMarshalTo(t *testing.T) { +func TestBlockSizeParamsMarshalTo(t *testing.T) { seed := time.Now().UnixNano() popr := math_rand.New(math_rand.NewSource(seed)) - p := NewPopulatedBlockSize(popr, false) + p := NewPopulatedBlockSizeParams(popr, false) size := p.Size() dAtA := make([]byte, size) for i := range dAtA { @@ -1523,7 +1523,7 @@ func TestBlockSizeMarshalTo(t *testing.T) { if err != nil { t.Fatalf("seed = %d, err = %v", seed, err) } - msg := &BlockSize{} + msg := &BlockSizeParams{} if err := github_com_gogo_protobuf_proto.Unmarshal(dAtA, msg); err != nil { t.Fatalf("seed = %d, err = %v", seed, err) } @@ -1591,6 +1591,62 @@ func TestEvidenceParamsMarshalTo(t *testing.T) { } } +func TestValidatorParamsProto(t *testing.T) { + seed := time.Now().UnixNano() + popr := math_rand.New(math_rand.NewSource(seed)) + p := NewPopulatedValidatorParams(popr, false) + dAtA, err := github_com_gogo_protobuf_proto.Marshal(p) + if err != nil { + t.Fatalf("seed = %d, err = %v", seed, err) + } + msg := &ValidatorParams{} + if err := github_com_gogo_protobuf_proto.Unmarshal(dAtA, msg); err != nil { + t.Fatalf("seed = %d, err = %v", seed, err) + } + littlefuzz := make([]byte, len(dAtA)) + copy(littlefuzz, dAtA) + for i := range dAtA { + dAtA[i] = byte(popr.Intn(256)) + } + if !p.Equal(msg) { + t.Fatalf("seed = %d, %#v !Proto %#v", seed, msg, p) + } + if len(littlefuzz) > 0 { + fuzzamount := 100 + for i := 0; i < fuzzamount; i++ { + littlefuzz[popr.Intn(len(littlefuzz))] = byte(popr.Intn(256)) + littlefuzz = append(littlefuzz, byte(popr.Intn(256))) + } + // shouldn't panic + _ = github_com_gogo_protobuf_proto.Unmarshal(littlefuzz, msg) + } +} + +func TestValidatorParamsMarshalTo(t *testing.T) { + seed := time.Now().UnixNano() + popr := math_rand.New(math_rand.NewSource(seed)) + p := NewPopulatedValidatorParams(popr, false) + size := p.Size() + dAtA := make([]byte, size) + for i := range dAtA { + dAtA[i] = byte(popr.Intn(256)) + } + _, err := p.MarshalTo(dAtA) + if err != nil { + t.Fatalf("seed = %d, err = %v", seed, err) + } + msg := &ValidatorParams{} + if err := github_com_gogo_protobuf_proto.Unmarshal(dAtA, msg); err != nil { + t.Fatalf("seed = %d, err = %v", seed, err) + } + for i := range dAtA { + dAtA[i] = byte(popr.Intn(256)) + } + if !p.Equal(msg) { + t.Fatalf("seed = %d, %#v !Proto %#v", seed, msg, p) + } +} + func TestLastCommitInfoProto(t *testing.T) { seed := time.Now().UnixNano() popr := math_rand.New(math_rand.NewSource(seed)) @@ -2619,16 +2675,16 @@ func TestConsensusParamsJSON(t *testing.T) { t.Fatalf("seed = %d, %#v !Json Equal %#v", seed, msg, p) } } -func TestBlockSizeJSON(t *testing.T) { +func TestBlockSizeParamsJSON(t *testing.T) { seed := time.Now().UnixNano() popr := math_rand.New(math_rand.NewSource(seed)) - p := NewPopulatedBlockSize(popr, true) + p := NewPopulatedBlockSizeParams(popr, true) marshaler := github_com_gogo_protobuf_jsonpb.Marshaler{} jsondata, err := marshaler.MarshalToString(p) if err != nil { t.Fatalf("seed = %d, err = %v", seed, err) } - msg := &BlockSize{} + msg := &BlockSizeParams{} err = github_com_gogo_protobuf_jsonpb.UnmarshalString(jsondata, msg) if err != nil { t.Fatalf("seed = %d, err = %v", seed, err) @@ -2655,6 +2711,24 @@ func TestEvidenceParamsJSON(t *testing.T) { t.Fatalf("seed = %d, %#v !Json Equal %#v", seed, msg, p) } } +func TestValidatorParamsJSON(t *testing.T) { + seed := time.Now().UnixNano() + popr := math_rand.New(math_rand.NewSource(seed)) + p := NewPopulatedValidatorParams(popr, true) + marshaler := github_com_gogo_protobuf_jsonpb.Marshaler{} + jsondata, err := marshaler.MarshalToString(p) + if err != nil { + t.Fatalf("seed = %d, err = %v", seed, err) + } + msg := &ValidatorParams{} + err = github_com_gogo_protobuf_jsonpb.UnmarshalString(jsondata, msg) + if err != nil { + t.Fatalf("seed = %d, err = %v", seed, err) + } + if !p.Equal(msg) { + t.Fatalf("seed = %d, %#v !Json Equal %#v", seed, msg, p) + } +} func TestLastCommitInfoJSON(t *testing.T) { seed := time.Now().UnixNano() popr := math_rand.New(math_rand.NewSource(seed)) @@ -3563,12 +3637,12 @@ func TestConsensusParamsProtoCompactText(t *testing.T) { } } -func TestBlockSizeProtoText(t *testing.T) { +func TestBlockSizeParamsProtoText(t *testing.T) { seed := time.Now().UnixNano() popr := math_rand.New(math_rand.NewSource(seed)) - p := NewPopulatedBlockSize(popr, true) + p := NewPopulatedBlockSizeParams(popr, true) dAtA := github_com_gogo_protobuf_proto.MarshalTextString(p) - msg := &BlockSize{} + msg := &BlockSizeParams{} if err := github_com_gogo_protobuf_proto.UnmarshalText(dAtA, msg); err != nil { t.Fatalf("seed = %d, err = %v", seed, err) } @@ -3577,12 +3651,12 @@ func TestBlockSizeProtoText(t *testing.T) { } } -func TestBlockSizeProtoCompactText(t *testing.T) { +func TestBlockSizeParamsProtoCompactText(t *testing.T) { seed := time.Now().UnixNano() popr := math_rand.New(math_rand.NewSource(seed)) - p := NewPopulatedBlockSize(popr, true) + p := NewPopulatedBlockSizeParams(popr, true) dAtA := github_com_gogo_protobuf_proto.CompactTextString(p) - msg := &BlockSize{} + msg := &BlockSizeParams{} if err := github_com_gogo_protobuf_proto.UnmarshalText(dAtA, msg); err != nil { t.Fatalf("seed = %d, err = %v", seed, err) } @@ -3619,6 +3693,34 @@ func TestEvidenceParamsProtoCompactText(t *testing.T) { } } +func TestValidatorParamsProtoText(t *testing.T) { + seed := time.Now().UnixNano() + popr := math_rand.New(math_rand.NewSource(seed)) + p := NewPopulatedValidatorParams(popr, true) + dAtA := github_com_gogo_protobuf_proto.MarshalTextString(p) + msg := &ValidatorParams{} + if err := github_com_gogo_protobuf_proto.UnmarshalText(dAtA, msg); err != nil { + t.Fatalf("seed = %d, err = %v", seed, err) + } + if !p.Equal(msg) { + t.Fatalf("seed = %d, %#v !Proto %#v", seed, msg, p) + } +} + +func TestValidatorParamsProtoCompactText(t *testing.T) { + seed := time.Now().UnixNano() + popr := math_rand.New(math_rand.NewSource(seed)) + p := NewPopulatedValidatorParams(popr, true) + dAtA := github_com_gogo_protobuf_proto.CompactTextString(p) + msg := &ValidatorParams{} + if err := github_com_gogo_protobuf_proto.UnmarshalText(dAtA, msg); err != nil { + t.Fatalf("seed = %d, err = %v", seed, err) + } + if !p.Equal(msg) { + t.Fatalf("seed = %d, %#v !Proto %#v", seed, msg, p) + } +} + func TestLastCommitInfoProtoText(t *testing.T) { seed := time.Now().UnixNano() popr := math_rand.New(math_rand.NewSource(seed)) @@ -4471,10 +4573,10 @@ func TestConsensusParamsSize(t *testing.T) { } } -func TestBlockSizeSize(t *testing.T) { +func TestBlockSizeParamsSize(t *testing.T) { seed := time.Now().UnixNano() popr := math_rand.New(math_rand.NewSource(seed)) - p := NewPopulatedBlockSize(popr, true) + p := NewPopulatedBlockSizeParams(popr, true) size2 := github_com_gogo_protobuf_proto.Size(p) dAtA, err := github_com_gogo_protobuf_proto.Marshal(p) if err != nil { @@ -4515,6 +4617,28 @@ func TestEvidenceParamsSize(t *testing.T) { } } +func TestValidatorParamsSize(t *testing.T) { + seed := time.Now().UnixNano() + popr := math_rand.New(math_rand.NewSource(seed)) + p := NewPopulatedValidatorParams(popr, true) + size2 := github_com_gogo_protobuf_proto.Size(p) + dAtA, err := github_com_gogo_protobuf_proto.Marshal(p) + if err != nil { + t.Fatalf("seed = %d, err = %v", seed, err) + } + size := p.Size() + if len(dAtA) != size { + t.Errorf("seed = %d, size %v != marshalled size %v", seed, size, len(dAtA)) + } + if size2 != size { + t.Errorf("seed = %d, size %v != before marshal proto.Size %v", seed, size, size2) + } + size3 := github_com_gogo_protobuf_proto.Size(p) + if size3 != size { + t.Errorf("seed = %d, size %v != after marshal proto.Size %v", seed, size, size3) + } +} + func TestLastCommitInfoSize(t *testing.T) { seed := time.Now().UnixNano() popr := math_rand.New(math_rand.NewSource(seed)) diff --git a/docs/spec/abci/abci.md b/docs/spec/abci/abci.md index afd72617..f057002e 100644 --- a/docs/spec/abci/abci.md +++ b/docs/spec/abci/abci.md @@ -441,11 +441,12 @@ Commit are included in the header of the next block. ### ConsensusParams - **Fields**: - - `BlockSize (BlockSize)`: Parameters limiting the size of a block. - - `EvidenceParams (EvidenceParams)`: Parameters limiting the validity of + - `BlockSize (BlockSizeParams)`: Parameters limiting the size of a block. + - `Evidence (EvidenceParams)`: Parameters limiting the validity of evidence of byzantine behaviour. + - `Validator (ValidatorParams)`: Parameters limitng the types of pubkeys validators can use. -### BlockSize +### BlockSizeParams - **Fields**: - `MaxBytes (int64)`: Max size of a block, in bytes. @@ -463,6 +464,12 @@ Commit are included in the header of the next block. similar mechanism for handling Nothing-At-Stake attacks. - NOTE: this should change to time (instead of blocks)! +### ValidatorParams + +- **Fields**: + - `PubKeyTypes ([]string)`: List of accepted pubkey types. Uses same + naming as `PubKey.Type`. + ### Proof - **Fields**: diff --git a/evidence/pool.go b/evidence/pool.go index 0f3d482a..da00a348 100644 --- a/evidence/pool.go +++ b/evidence/pool.go @@ -127,7 +127,7 @@ func (evpool *EvidencePool) MarkEvidenceAsCommitted(height int64, evidence []typ } // remove committed evidence from the clist - maxAge := evpool.State().ConsensusParams.EvidenceParams.MaxAge + maxAge := evpool.State().ConsensusParams.Evidence.MaxAge evpool.removeEvidence(height, maxAge, blockEvidenceMap) } diff --git a/evidence/pool_test.go b/evidence/pool_test.go index c3ed569e..4e69596b 100644 --- a/evidence/pool_test.go +++ b/evidence/pool_test.go @@ -38,7 +38,7 @@ func initializeValidatorState(valAddr []byte, height int64) dbm.DB { NextValidators: valSet.CopyIncrementAccum(1), LastHeightValidatorsChanged: 1, ConsensusParams: types.ConsensusParams{ - EvidenceParams: types.EvidenceParams{ + Evidence: types.EvidenceParams{ MaxAge: 1000000, }, }, diff --git a/evidence/reactor.go b/evidence/reactor.go index cfe47364..52eb4a56 100644 --- a/evidence/reactor.go +++ b/evidence/reactor.go @@ -164,7 +164,7 @@ func (evR EvidenceReactor) checkSendEvidenceMessage(peer p2p.Peer, ev types.Evid // NOTE: We only send evidence to peers where // peerHeight - maxAge < evidenceHeight < peerHeight - maxAge := evR.evpool.State().ConsensusParams.EvidenceParams.MaxAge + maxAge := evR.evpool.State().ConsensusParams.Evidence.MaxAge peerHeight := peerState.GetHeight() if peerHeight < evHeight { // peer is behind. sleep while he catches up diff --git a/state/execution.go b/state/execution.go index 72f6cc97..cc8e7e75 100644 --- a/state/execution.go +++ b/state/execution.go @@ -186,7 +186,7 @@ func (blockExec *BlockExecutor) Commit( state.Validators.Size(), ), ), - mempool.PostCheckMaxGas(state.ConsensusParams.MaxGas), + mempool.PostCheckMaxGas(state.ConsensusParams.BlockSize.MaxGas), ) return res.Data, err diff --git a/state/state_test.go b/state/state_test.go index b1f24d30..88200e17 100644 --- a/state/state_test.go +++ b/state/state_test.go @@ -390,11 +390,11 @@ func TestConsensusParamsChangesSaveLoad(t *testing.T) { func makeParams(blockBytes, blockGas, evidenceAge int64) types.ConsensusParams { return types.ConsensusParams{ - BlockSize: types.BlockSize{ + BlockSize: types.BlockSizeParams{ MaxBytes: blockBytes, MaxGas: blockGas, }, - EvidenceParams: types.EvidenceParams{ + Evidence: types.EvidenceParams{ MaxAge: evidenceAge, }, } @@ -416,7 +416,7 @@ func TestApplyUpdates(t *testing.T) { 1: {initParams, abci.ConsensusParams{}, initParams}, 2: {initParams, abci.ConsensusParams{ - BlockSize: &abci.BlockSize{ + BlockSize: &abci.BlockSizeParams{ MaxBytes: 44, MaxGas: 55, }, @@ -424,7 +424,7 @@ func TestApplyUpdates(t *testing.T) { makeParams(44, 55, 3)}, 3: {initParams, abci.ConsensusParams{ - EvidenceParams: &abci.EvidenceParams{ + Evidence: &abci.EvidenceParams{ MaxAge: 66, }, }, diff --git a/state/store.go b/state/store.go index 2f90c747..7a0ef255 100644 --- a/state/store.go +++ b/state/store.go @@ -251,7 +251,7 @@ func LoadConsensusParams(db dbm.DB, height int64) (types.ConsensusParams, error) return empty, ErrNoConsensusParamsForHeight{height} } - if paramsInfo.ConsensusParams == empty { + if paramsInfo.ConsensusParams.Equals(&empty) { paramsInfo2 := loadConsensusParamsInfo(db, paramsInfo.LastHeightChanged) if paramsInfo2 == nil { panic( diff --git a/state/validation.go b/state/validation.go index ff1791e2..a1291984 100644 --- a/state/validation.go +++ b/state/validation.go @@ -178,7 +178,7 @@ func VerifyEvidence(stateDB dbm.DB, state State, evidence types.Evidence) error height := state.LastBlockHeight evidenceAge := height - evidence.Height() - maxAge := state.ConsensusParams.EvidenceParams.MaxAge + maxAge := state.ConsensusParams.Evidence.MaxAge if evidenceAge > maxAge { return fmt.Errorf("Evidence from height %d is too old. Min height is %d", evidence.Height(), height-maxAge) diff --git a/types/params.go b/types/params.go index ed1e7963..81cf429f 100644 --- a/types/params.go +++ b/types/params.go @@ -17,12 +17,13 @@ const ( // ConsensusParams contains consensus critical parameters that determine the // validity of blocks. type ConsensusParams struct { - BlockSize `json:"block_size_params"` - EvidenceParams `json:"evidence_params"` + BlockSize BlockSizeParams `json:"block_size"` + Evidence EvidenceParams `json:"evidence"` + Validator ValidatorParams `json:"validator"` } -// BlockSize contain limits on the block size. -type BlockSize struct { +// BlockSizeParams define limits on the block size. +type BlockSizeParams struct { MaxBytes int64 `json:"max_bytes"` MaxGas int64 `json:"max_gas"` } @@ -32,17 +33,24 @@ type EvidenceParams struct { MaxAge int64 `json:"max_age"` // only accept new evidence more recent than this } +// ValidatorParams restrict the public key types validators can use. +// NOTE: uses ABCI pubkey naming, not Amino routes. +type ValidatorParams struct { + PubKeyTypes []string `json:"pub_key_types"` +} + // DefaultConsensusParams returns a default ConsensusParams. func DefaultConsensusParams() *ConsensusParams { return &ConsensusParams{ - DefaultBlockSize(), + DefaultBlockSizeParams(), DefaultEvidenceParams(), + DefaultValidatorParams(), } } -// DefaultBlockSize returns a default BlockSize. -func DefaultBlockSize() BlockSize { - return BlockSize{ +// DefaultBlockSizeParams returns a default BlockSizeParams. +func DefaultBlockSizeParams() BlockSizeParams { + return BlockSizeParams{ MaxBytes: 22020096, // 21MB MaxGas: -1, } @@ -55,6 +63,12 @@ func DefaultEvidenceParams() EvidenceParams { } } +// DefaultValidatorParams returns a default ValidatorParams, which allows +// only ed25519 pubkeys. +func DefaultValidatorParams() ValidatorParams { + return ValidatorParams{[]string{ABCIPubKeyTypeEd25519}} +} + // Validate validates the ConsensusParams to ensure all values are within their // allowed limits, and returns an error if they are not. func (params *ConsensusParams) Validate() error { @@ -72,9 +86,22 @@ func (params *ConsensusParams) Validate() error { params.BlockSize.MaxGas) } - if params.EvidenceParams.MaxAge <= 0 { + if params.Evidence.MaxAge <= 0 { return cmn.NewError("EvidenceParams.MaxAge must be greater than 0. Got %d", - params.EvidenceParams.MaxAge) + params.Evidence.MaxAge) + } + + if len(params.Validator.PubKeyTypes) == 0 { + return cmn.NewError("len(Validator.PubKeyTypes) must be greater than 0") + } + + // Check if keyType is a known ABCIPubKeyType + for i := 0; i < len(params.Validator.PubKeyTypes); i++ { + keyType := params.Validator.PubKeyTypes[i] + if _, ok := ABCIPubKeyTypesToAminoRoutes[keyType]; !ok { + return cmn.NewError("params.Validator.PubKeyTypes[%d], %s, is an unknown pubkey type", + i, keyType) + } } return nil @@ -94,6 +121,24 @@ func (params *ConsensusParams) Hash() []byte { return hasher.Sum(nil) } +func (params *ConsensusParams) Equals(params2 *ConsensusParams) bool { + return params.BlockSize == params2.BlockSize && + params.Evidence == params2.Evidence && + stringSliceEqual(params.Validator.PubKeyTypes, params2.Validator.PubKeyTypes) +} + +func stringSliceEqual(a, b []string) bool { + if len(a) != len(b) { + return false + } + for i := 0; i < len(a); i++ { + if a[i] != b[i] { + return false + } + } + return true +} + // Update returns a copy of the params with updates from the non-zero fields of p2. // NOTE: note: must not modify the original func (params ConsensusParams) Update(params2 *abci.ConsensusParams) ConsensusParams { @@ -108,8 +153,11 @@ func (params ConsensusParams) Update(params2 *abci.ConsensusParams) ConsensusPar res.BlockSize.MaxBytes = params2.BlockSize.MaxBytes res.BlockSize.MaxGas = params2.BlockSize.MaxGas } - if params2.EvidenceParams != nil { - res.EvidenceParams.MaxAge = params2.EvidenceParams.MaxAge + if params2.Evidence != nil { + res.Evidence.MaxAge = params2.Evidence.MaxAge + } + if params2.Validator != nil { + res.Validator.PubKeyTypes = params2.Validator.PubKeyTypes } return res } diff --git a/types/params_test.go b/types/params_test.go index 2936e5a4..dc1936fb 100644 --- a/types/params_test.go +++ b/types/params_test.go @@ -9,23 +9,32 @@ import ( abci "github.com/tendermint/tendermint/abci/types" ) +var ( + valEd25519 = []string{ABCIPubKeyTypeEd25519} + valSecp256k1 = []string{ABCIPubKeyTypeSecp256k1} +) + func TestConsensusParamsValidation(t *testing.T) { testCases := []struct { params ConsensusParams valid bool }{ // test block size - 0: {makeParams(1, 0, 1), true}, - 1: {makeParams(0, 0, 1), false}, - 2: {makeParams(47*1024*1024, 0, 1), true}, - 3: {makeParams(10, 0, 1), true}, - 4: {makeParams(100*1024*1024, 0, 1), true}, - 5: {makeParams(101*1024*1024, 0, 1), false}, - 6: {makeParams(1024*1024*1024, 0, 1), false}, - 7: {makeParams(1024*1024*1024, 0, -1), false}, + 0: {makeParams(1, 0, 1, valEd25519), true}, + 1: {makeParams(0, 0, 1, valEd25519), false}, + 2: {makeParams(47*1024*1024, 0, 1, valEd25519), true}, + 3: {makeParams(10, 0, 1, valEd25519), true}, + 4: {makeParams(100*1024*1024, 0, 1, valEd25519), true}, + 5: {makeParams(101*1024*1024, 0, 1, valEd25519), false}, + 6: {makeParams(1024*1024*1024, 0, 1, valEd25519), false}, + 7: {makeParams(1024*1024*1024, 0, -1, valEd25519), false}, // test evidence age - 8: {makeParams(1, 0, 0), false}, - 9: {makeParams(1, 0, -1), false}, + 8: {makeParams(1, 0, 0, valEd25519), false}, + 9: {makeParams(1, 0, -1, valEd25519), false}, + // test no pubkey type provided + 10: {makeParams(1, 0, 1, []string{}), false}, + // test invalid pubkey type provided + 11: {makeParams(1, 0, 1, []string{"potatoes make good pubkeys"}), false}, } for i, tc := range testCases { if tc.valid { @@ -36,28 +45,31 @@ func TestConsensusParamsValidation(t *testing.T) { } } -func makeParams(blockBytes, blockGas, evidenceAge int64) ConsensusParams { +func makeParams(blockBytes, blockGas, evidenceAge int64, pubkeyTypes []string) ConsensusParams { return ConsensusParams{ - BlockSize: BlockSize{ + BlockSize: BlockSizeParams{ MaxBytes: blockBytes, MaxGas: blockGas, }, - EvidenceParams: EvidenceParams{ + Evidence: EvidenceParams{ MaxAge: evidenceAge, }, + Validator: ValidatorParams{ + PubKeyTypes: pubkeyTypes, + }, } } func TestConsensusParamsHash(t *testing.T) { params := []ConsensusParams{ - makeParams(4, 2, 3), - makeParams(1, 4, 3), - makeParams(1, 2, 4), - makeParams(2, 5, 7), - makeParams(1, 7, 6), - makeParams(9, 5, 4), - makeParams(7, 8, 9), - makeParams(4, 6, 5), + makeParams(4, 2, 3, valEd25519), + makeParams(1, 4, 3, valEd25519), + makeParams(1, 2, 4, valEd25519), + makeParams(2, 5, 7, valEd25519), + makeParams(1, 7, 6, valEd25519), + makeParams(9, 5, 4, valEd25519), + makeParams(7, 8, 9, valEd25519), + makeParams(4, 6, 5, valEd25519), } hashes := make([][]byte, len(params)) @@ -83,23 +95,26 @@ func TestConsensusParamsUpdate(t *testing.T) { }{ // empty updates { - makeParams(1, 2, 3), + makeParams(1, 2, 3, valEd25519), &abci.ConsensusParams{}, - makeParams(1, 2, 3), + makeParams(1, 2, 3, valEd25519), }, // fine updates { - makeParams(1, 2, 3), + makeParams(1, 2, 3, valEd25519), &abci.ConsensusParams{ - BlockSize: &abci.BlockSize{ + BlockSize: &abci.BlockSizeParams{ MaxBytes: 100, MaxGas: 200, }, - EvidenceParams: &abci.EvidenceParams{ + Evidence: &abci.EvidenceParams{ MaxAge: 300, }, + Validator: &abci.ValidatorParams{ + PubKeyTypes: valSecp256k1, + }, }, - makeParams(100, 200, 300), + makeParams(100, 200, 300, valSecp256k1), }, } for _, tc := range testCases { diff --git a/types/protobuf.go b/types/protobuf.go index e1ec81e8..1535c1e3 100644 --- a/types/protobuf.go +++ b/types/protobuf.go @@ -24,6 +24,12 @@ const ( ABCIPubKeyTypeSecp256k1 = "secp256k1" ) +// TODO: Make non-global by allowing for registration of more pubkey types +var ABCIPubKeyTypesToAminoRoutes = map[string]string{ + ABCIPubKeyTypeEd25519: ed25519.PubKeyAminoRoute, + ABCIPubKeyTypeSecp256k1: secp256k1.PubKeyAminoRoute, +} + //------------------------------------------------------- // TM2PB is used for converting Tendermint ABCI to protobuf ABCI. @@ -119,12 +125,15 @@ func (tm2pb) ValidatorUpdates(vals *ValidatorSet) []abci.ValidatorUpdate { func (tm2pb) ConsensusParams(params *ConsensusParams) *abci.ConsensusParams { return &abci.ConsensusParams{ - BlockSize: &abci.BlockSize{ + BlockSize: &abci.BlockSizeParams{ MaxBytes: params.BlockSize.MaxBytes, MaxGas: params.BlockSize.MaxGas, }, - EvidenceParams: &abci.EvidenceParams{ - MaxAge: params.EvidenceParams.MaxAge, + Evidence: &abci.EvidenceParams{ + MaxAge: params.Evidence.MaxAge, + }, + Validator: &abci.ValidatorParams{ + PubKeyTypes: params.Validator.PubKeyTypes, }, } } @@ -215,12 +224,15 @@ func (pb2tm) ValidatorUpdates(vals []abci.ValidatorUpdate) ([]*Validator, error) func (pb2tm) ConsensusParams(csp *abci.ConsensusParams) ConsensusParams { return ConsensusParams{ - BlockSize: BlockSize{ + BlockSize: BlockSizeParams{ MaxBytes: csp.BlockSize.MaxBytes, MaxGas: csp.BlockSize.MaxGas, }, - EvidenceParams: EvidenceParams{ - MaxAge: csp.EvidenceParams.MaxAge, + Evidence: EvidenceParams{ + MaxAge: csp.Evidence.MaxAge, + }, + Validator: ValidatorParams{ + PubKeyTypes: csp.Validator.PubKeyTypes, }, } } diff --git a/types/protobuf_test.go b/types/protobuf_test.go index 7e7f55a1..f5a2ce5d 100644 --- a/types/protobuf_test.go +++ b/types/protobuf_test.go @@ -64,7 +64,6 @@ func TestABCIValidators(t *testing.T) { func TestABCIConsensusParams(t *testing.T) { cp := DefaultConsensusParams() - cp.EvidenceParams.MaxAge = 0 // TODO add this to ABCI abciCP := TM2PB.ConsensusParams(cp) cp2 := PB2TM.ConsensusParams(abciCP) From 60437953ac0a99cabf6589e3d60fdb5b77b136d1 Mon Sep 17 00:00:00 2001 From: yutianwu Date: Tue, 30 Oct 2018 23:46:55 +0800 Subject: [PATCH 042/267] [R4R] libs/log: add year to log format (#2707) * add year to log format * update documentation --- CHANGELOG_PENDING.md | 1 + docs/architecture/adr-001-logging.md | 8 ++++---- libs/log/tmfmt_logger.go | 6 +++--- 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/CHANGELOG_PENDING.md b/CHANGELOG_PENDING.md index 5c25a4b1..c8c55f53 100644 --- a/CHANGELOG_PENDING.md +++ b/CHANGELOG_PENDING.md @@ -91,6 +91,7 @@ Friendly reminder, we have a [bug bounty program](https://hackerone.com/tendermi - [crypto/ed25519] [\#2558](https://github.com/tendermint/tendermint/issues/2558) Switch to use latest `golang.org/x/crypto` through our fork at github.com/tendermint/crypto - [tools] [\#2238](https://github.com/tendermint/tendermint/issues/2238) Binary dependencies are now locked to a specific git commit +- [libs/log] [\#2706](https://github.com/tendermint/tendermint/issues/2706) Add year to log format ### BUG FIXES: - [autofile] [\#2428](https://github.com/tendermint/tendermint/issues/2428) Group.RotateFile need call Flush() before rename (@goolAdapter) diff --git a/docs/architecture/adr-001-logging.md b/docs/architecture/adr-001-logging.md index a11a49e1..77e5d39a 100644 --- a/docs/architecture/adr-001-logging.md +++ b/docs/architecture/adr-001-logging.md @@ -52,13 +52,13 @@ On top of this interface, we will need to implement a stdout logger, which will Many people say that they like the current output, so let's stick with it. ``` -NOTE[04-25|14:45:08] ABCI Replay Blocks module=consensus appHeight=0 storeHeight=0 stateHeight=0 +NOTE[2017-04-25|14:45:08] ABCI Replay Blocks module=consensus appHeight=0 storeHeight=0 stateHeight=0 ``` Couple of minor changes: ``` -I[04-25|14:45:08.322] ABCI Replay Blocks module=consensus appHeight=0 storeHeight=0 stateHeight=0 +I[2017-04-25|14:45:08.322] ABCI Replay Blocks module=consensus appHeight=0 storeHeight=0 stateHeight=0 ``` Notice the level is encoded using only one char plus milliseconds. @@ -155,14 +155,14 @@ Important keyvals should go first. Example: ``` correct -I[04-25|14:45:08.322] ABCI Replay Blocks module=consensus instance=1 appHeight=0 storeHeight=0 stateHeight=0 +I[2017-04-25|14:45:08.322] ABCI Replay Blocks module=consensus instance=1 appHeight=0 storeHeight=0 stateHeight=0 ``` not ``` wrong -I[04-25|14:45:08.322] ABCI Replay Blocks module=consensus appHeight=0 storeHeight=0 stateHeight=0 instance=1 +I[2017-04-25|14:45:08.322] ABCI Replay Blocks module=consensus appHeight=0 storeHeight=0 stateHeight=0 instance=1 ``` for that in most cases you'll need to add `instance` field to a logger upon creating, not when u log a particular message: diff --git a/libs/log/tmfmt_logger.go b/libs/log/tmfmt_logger.go index de155fef..247ce8fc 100644 --- a/libs/log/tmfmt_logger.go +++ b/libs/log/tmfmt_logger.go @@ -84,13 +84,13 @@ func (l tmfmtLogger) Log(keyvals ...interface{}) error { // Form a custom Tendermint line // // Example: - // D[05-02|11:06:44.322] Stopping AddrBook (ignoring: already stopped) + // D[2016-05-02|11:06:44.322] Stopping AddrBook (ignoring: already stopped) // // Description: // D - first character of the level, uppercase (ASCII only) - // [05-02|11:06:44.322] - our time format (see https://golang.org/src/time/format.go) + // [2016-05-02|11:06:44.322] - our time format (see https://golang.org/src/time/format.go) // Stopping ... - message - enc.buf.WriteString(fmt.Sprintf("%c[%s] %-44s ", lvl[0]-32, time.Now().Format("01-02|15:04:05.000"), msg)) + enc.buf.WriteString(fmt.Sprintf("%c[%s] %-44s ", lvl[0]-32, time.Now().Format("2016-01-02|15:04:05.000"), msg)) if module != unknown { enc.buf.WriteString("module=" + module + " ") From a530352f6165ac4c9dc64921fae2ab7500c9bc39 Mon Sep 17 00:00:00 2001 From: Ismail Khoffi Date: Tue, 30 Oct 2018 17:16:55 +0100 Subject: [PATCH 043/267] Align Vote/Proposal fields with canonical order and fields (#2730) * reorder fields * add TestVoteString & update tests * remove redundant info from Proposal.String() * update spec * revert changes on vote.String() -> more human friendly --- Gopkg.lock | 6 +++--- docs/spec/blockchain/blockchain.md | 16 ++++++++-------- types/proposal.go | 14 ++++++++++---- types/vote.go | 9 +++++---- types/vote_test.go | 26 ++++++++++++++++++++------ 5 files changed, 46 insertions(+), 25 deletions(-) diff --git a/Gopkg.lock b/Gopkg.lock index f4656e6b..59e42f92 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -408,14 +408,14 @@ [[projects]] branch = "master" - digest = "1:fd98d154bf152ad5a49600ede7d7341851bcdfe358b9b82e5ccdba818618167c" + digest = "1:5207b4bc950fd0e45544263103af3e119c94fba6717f9d61931f7a19a7c0706a" name = "golang.org/x/sys" packages = [ "cpu", "unix", ] pruneopts = "UT" - revision = "2772b66316d2c587efeb188dcd5ebc6987656e84" + revision = "f7626d0b1519d8323581a047ca8b372ebf28de9a" [[projects]] digest = "1:a2ab62866c75542dd18d2b069fec854577a20211d7c0ea6ae746072a1dccdd18" @@ -446,7 +446,7 @@ name = "google.golang.org/genproto" packages = ["googleapis/rpc/status"] pruneopts = "UT" - revision = "94acd270e44e65579b9ee3cdab25034d33fed608" + revision = "b69ba1387ce2108ac9bc8e8e5e5a46e7d5c72313" [[projects]] digest = "1:2dab32a43451e320e49608ff4542fdfc653c95dcc35d0065ec9c6c3dd540ed74" diff --git a/docs/spec/blockchain/blockchain.md b/docs/spec/blockchain/blockchain.md index c5291ed4..06168537 100644 --- a/docs/spec/blockchain/blockchain.md +++ b/docs/spec/blockchain/blockchain.md @@ -146,14 +146,14 @@ The vote includes information about the validator signing it. ```go type Vote struct { - ValidatorAddress []byte - ValidatorIndex int - Height int64 - Round int - Timestamp Time - Type int8 - BlockID BlockID - Signature []byte + Type SignedMsgType // byte + Height int64 + Round int + Timestamp time.Time + BlockID BlockID + ValidatorAddress Address + ValidatorIndex int + Signature []byte } ``` diff --git a/types/proposal.go b/types/proposal.go index 5d70a3c8..fa82fdbb 100644 --- a/types/proposal.go +++ b/types/proposal.go @@ -20,11 +20,12 @@ var ( // to be considered valid. It may depend on votes from a previous round, // a so-called Proof-of-Lock (POL) round, as noted in the POLRound and POLBlockID. type Proposal struct { + Type SignedMsgType Height int64 `json:"height"` Round int `json:"round"` + POLRound int `json:"pol_round"` // -1 if null. Timestamp time.Time `json:"timestamp"` BlockPartsHeader PartSetHeader `json:"block_parts_header"` - POLRound int `json:"pol_round"` // -1 if null. POLBlockID BlockID `json:"pol_block_id"` // zero if null. Signature []byte `json:"signature"` } @@ -33,11 +34,12 @@ type Proposal struct { // If there is no POLRound, polRound should be -1. func NewProposal(height int64, round int, blockPartsHeader PartSetHeader, polRound int, polBlockID BlockID) *Proposal { return &Proposal{ + Type: ProposalType, Height: height, Round: round, + POLRound: polRound, Timestamp: tmtime.Now(), BlockPartsHeader: blockPartsHeader, - POLRound: polRound, POLBlockID: polBlockID, } } @@ -45,9 +47,13 @@ func NewProposal(height int64, round int, blockPartsHeader PartSetHeader, polRou // String returns a string representation of the Proposal. func (p *Proposal) String() string { return fmt.Sprintf("Proposal{%v/%v %v (%v,%v) %X @ %s}", - p.Height, p.Round, p.BlockPartsHeader, p.POLRound, + p.Height, + p.Round, + p.BlockPartsHeader, + p.POLRound, p.POLBlockID, - cmn.Fingerprint(p.Signature), CanonicalTime(p.Timestamp)) + cmn.Fingerprint(p.Signature), + CanonicalTime(p.Timestamp)) } // SignBytes returns the Proposal bytes for signing diff --git a/types/vote.go b/types/vote.go index 333684fc..826330d5 100644 --- a/types/vote.go +++ b/types/vote.go @@ -48,13 +48,13 @@ type Address = crypto.Address // Represents a prevote, precommit, or commit vote from validators for consensus. type Vote struct { - ValidatorAddress Address `json:"validator_address"` - ValidatorIndex int `json:"validator_index"` + Type SignedMsgType `json:"type"` Height int64 `json:"height"` Round int `json:"round"` Timestamp time.Time `json:"timestamp"` - Type SignedMsgType `json:"type"` BlockID BlockID `json:"block_id"` // zero if vote is nil. + ValidatorAddress Address `json:"validator_address"` + ValidatorIndex int `json:"validator_index"` Signature []byte `json:"signature"` } @@ -94,7 +94,8 @@ func (vote *Vote) String() string { typeString, cmn.Fingerprint(vote.BlockID.Hash), cmn.Fingerprint(vote.Signature), - CanonicalTime(vote.Timestamp)) + CanonicalTime(vote.Timestamp), + ) } func (vote *Vote) Verify(chainID string, pubKey crypto.PubKey) error { diff --git a/types/vote_test.go b/types/vote_test.go index 1d7e3daf..57273585 100644 --- a/types/vote_test.go +++ b/types/vote_test.go @@ -26,12 +26,10 @@ func exampleVote(t byte) *Vote { } return &Vote{ - ValidatorAddress: tmhash.Sum([]byte("validator_address")), - ValidatorIndex: 56789, - Height: 12345, - Round: 2, - Timestamp: stamp, - Type: SignedMsgType(t), + Type: SignedMsgType(t), + Height: 12345, + Round: 2, + Timestamp: stamp, BlockID: BlockID{ Hash: tmhash.Sum([]byte("blockID_hash")), PartsHeader: PartSetHeader{ @@ -39,6 +37,8 @@ func exampleVote(t byte) *Vote { Hash: tmhash.Sum([]byte("blockID_part_set_header_hash")), }, }, + ValidatorAddress: tmhash.Sum([]byte("validator_address")), + ValidatorIndex: 56789, } } @@ -235,3 +235,17 @@ func TestMaxVoteBytes(t *testing.T) { assert.EqualValues(t, MaxVoteBytes, len(bz)) } + +func TestVoteString(t *testing.T) { + str := examplePrecommit().String() + expected := `Vote{56789:6AF1F4111082 12345/02/2(Precommit) 8B01023386C3 000000000000 @ 2017-12-25T03:00:01.234Z}` + if str != expected { + t.Errorf("Got unexpected string for Vote. Expected:\n%v\nGot:\n%v", expected, str) + } + + str2 := examplePrevote().String() + expected = `Vote{56789:6AF1F4111082 12345/02/1(Prevote) 8B01023386C3 000000000000 @ 2017-12-25T03:00:01.234Z}` + if str2 != expected { + t.Errorf("Got unexpected string for Vote. Expected:\n%v\nGot:\n%v", expected, str2) + } +} From 7a033444800843cdba95a86eb02ab07de1bcf41d Mon Sep 17 00:00:00 2001 From: Zarko Milosevic Date: Wed, 31 Oct 2018 14:20:36 +0100 Subject: [PATCH 044/267] Introduce EventValidBlock for informing peers about wanted block (#2652) * Introduce EventValidBlock for informing peer about wanted block * Merge with develop * Add isCommit flag to NewValidBlock message - Add test for the case of +2/3 Precommit from the previous round --- CHANGELOG_PENDING.md | 2 + consensus/byzantine_test.go | 2 +- consensus/common_test.go | 5 + consensus/reactor.go | 87 ++++---- consensus/state.go | 61 +++--- consensus/state_test.go | 185 ++++++++++++++++++ .../reactors/consensus/consensus-reactor.md | 8 +- p2p/metrics.go | 2 +- types/event_bus.go | 4 + types/events.go | 2 + types/evidence_test.go | 2 +- 11 files changed, 285 insertions(+), 75 deletions(-) diff --git a/CHANGELOG_PENDING.md b/CHANGELOG_PENDING.md index c8c55f53..ce0cf247 100644 --- a/CHANGELOG_PENDING.md +++ b/CHANGELOG_PENDING.md @@ -102,6 +102,8 @@ Friendly reminder, we have a [bug bounty program](https://hackerone.com/tendermi timeoutPrecommit before starting next round - [consensus] [\#1745](https://github.com/tendermint/tendermint/issues/1745) Wait for Proposal or timeoutProposal before entering prevote +- [consensus] [\#2583](https://github.com/tendermint/tendermint/issues/2583) ensure valid + block property with faulty proposer - [consensus] [\#2642](https://github.com/tendermint/tendermint/issues/2642) Only propose ValidBlock, not LockedBlock - [consensus] [\#2642](https://github.com/tendermint/tendermint/issues/2642) Initialized ValidRound and LockedRound to -1 - [consensus] [\#1637](https://github.com/tendermint/tendermint/issues/1637) Limit the amount of evidence that can be included in a diff --git a/consensus/byzantine_test.go b/consensus/byzantine_test.go index 60c2b0db..ed4cc90c 100644 --- a/consensus/byzantine_test.go +++ b/consensus/byzantine_test.go @@ -263,7 +263,7 @@ func (br *ByzantineReactor) AddPeer(peer p2p.Peer) { // Send our state to peer. // If we're fast_syncing, broadcast a RoundStepMessage later upon SwitchToConsensus(). if !br.reactor.fastSync { - br.reactor.sendNewRoundStepMessages(peer) + br.reactor.sendNewRoundStepMessage(peer) } } func (br *ByzantineReactor) RemovePeer(peer p2p.Peer, reason interface{}) { diff --git a/consensus/common_test.go b/consensus/common_test.go index ca14a292..c949922f 100644 --- a/consensus/common_test.go +++ b/consensus/common_test.go @@ -420,6 +420,11 @@ func ensureNewProposal(proposalCh <-chan interface{}, height int64, round int) { "Timeout expired while waiting for NewProposal event") } +func ensureNewValidBlock(validBlockCh <-chan interface{}, height int64, round int) { + ensureNewEvent(validBlockCh, height, round, ensureTimeout, + "Timeout expired while waiting for NewValidBlock event") +} + func ensureNewBlock(blockCh <-chan interface{}, height int64) { select { case <-time.After(ensureTimeout): diff --git a/consensus/reactor.go b/consensus/reactor.go index 6643273c..8d5e726f 100644 --- a/consensus/reactor.go +++ b/consensus/reactor.go @@ -174,7 +174,7 @@ func (conR *ConsensusReactor) AddPeer(peer p2p.Peer) { // Send our state to peer. // If we're fast_syncing, broadcast a RoundStepMessage later upon SwitchToConsensus(). if !conR.FastSync() { - conR.sendNewRoundStepMessages(peer) + conR.sendNewRoundStepMessage(peer) } } @@ -215,8 +215,8 @@ func (conR *ConsensusReactor) Receive(chID byte, src p2p.Peer, msgBytes []byte) switch msg := msg.(type) { case *NewRoundStepMessage: ps.ApplyNewRoundStepMessage(msg) - case *CommitStepMessage: - ps.ApplyCommitStepMessage(msg) + case *NewValidBlockMessage: + ps.ApplyNewValidBlockMessage(msg) case *HasVoteMessage: ps.ApplyHasVoteMessage(msg) case *VoteSetMaj23Message: @@ -365,7 +365,12 @@ func (conR *ConsensusReactor) subscribeToBroadcastEvents() { const subscriber = "consensus-reactor" conR.conS.evsw.AddListenerForEvent(subscriber, types.EventNewRoundStep, func(data tmevents.EventData) { - conR.broadcastNewRoundStepMessages(data.(*cstypes.RoundState)) + conR.broadcastNewRoundStepMessage(data.(*cstypes.RoundState)) + }) + + conR.conS.evsw.AddListenerForEvent(subscriber, types.EventValidBlock, + func(data tmevents.EventData) { + conR.broadcastNewValidBlockMessage(data.(*cstypes.RoundState)) }) conR.conS.evsw.AddListenerForEvent(subscriber, types.EventVote, @@ -391,14 +396,20 @@ func (conR *ConsensusReactor) broadcastProposalHeartbeatMessage(hb *types.Heartb conR.Switch.Broadcast(StateChannel, cdc.MustMarshalBinaryBare(msg)) } -func (conR *ConsensusReactor) broadcastNewRoundStepMessages(rs *cstypes.RoundState) { - nrsMsg, csMsg := makeRoundStepMessages(rs) - if nrsMsg != nil { - conR.Switch.Broadcast(StateChannel, cdc.MustMarshalBinaryBare(nrsMsg)) - } - if csMsg != nil { - conR.Switch.Broadcast(StateChannel, cdc.MustMarshalBinaryBare(csMsg)) +func (conR *ConsensusReactor) broadcastNewRoundStepMessage(rs *cstypes.RoundState) { + nrsMsg := makeRoundStepMessage(rs) + conR.Switch.Broadcast(StateChannel, cdc.MustMarshalBinaryBare(nrsMsg)) +} + +func (conR *ConsensusReactor) broadcastNewValidBlockMessage(rs *cstypes.RoundState) { + csMsg := &NewValidBlockMessage{ + Height: rs.Height, + Round: rs.Round, + BlockPartsHeader: rs.ProposalBlockParts.Header(), + BlockParts: rs.ProposalBlockParts.BitArray(), + IsCommit: rs.Step == cstypes.RoundStepCommit, } + conR.Switch.Broadcast(StateChannel, cdc.MustMarshalBinaryBare(csMsg)) } // Broadcasts HasVoteMessage to peers that care. @@ -427,33 +438,21 @@ func (conR *ConsensusReactor) broadcastHasVoteMessage(vote *types.Vote) { */ } -func makeRoundStepMessages(rs *cstypes.RoundState) (nrsMsg *NewRoundStepMessage, csMsg *CommitStepMessage) { +func makeRoundStepMessage(rs *cstypes.RoundState) (nrsMsg *NewRoundStepMessage) { nrsMsg = &NewRoundStepMessage{ - Height: rs.Height, - Round: rs.Round, - Step: rs.Step, + Height: rs.Height, + Round: rs.Round, + Step: rs.Step, SecondsSinceStartTime: int(time.Since(rs.StartTime).Seconds()), LastCommitRound: rs.LastCommit.Round(), } - if rs.Step == cstypes.RoundStepCommit { - csMsg = &CommitStepMessage{ - Height: rs.Height, - BlockPartsHeader: rs.ProposalBlockParts.Header(), - BlockParts: rs.ProposalBlockParts.BitArray(), - } - } return } -func (conR *ConsensusReactor) sendNewRoundStepMessages(peer p2p.Peer) { +func (conR *ConsensusReactor) sendNewRoundStepMessage(peer p2p.Peer) { rs := conR.conS.GetRoundState() - nrsMsg, csMsg := makeRoundStepMessages(rs) - if nrsMsg != nil { - peer.Send(StateChannel, cdc.MustMarshalBinaryBare(nrsMsg)) - } - if csMsg != nil { - peer.Send(StateChannel, cdc.MustMarshalBinaryBare(csMsg)) - } + nrsMsg := makeRoundStepMessage(rs) + peer.Send(StateChannel, cdc.MustMarshalBinaryBare(nrsMsg)) } func (conR *ConsensusReactor) gossipDataRoutine(peer p2p.Peer, ps *PeerState) { @@ -524,6 +523,7 @@ OUTER_LOOP: msg := &ProposalMessage{Proposal: rs.Proposal} logger.Debug("Sending proposal", "height", prs.Height, "round", prs.Round) if peer.Send(DataChannel, cdc.MustMarshalBinaryBare(msg)) { + // NOTE[ZM]: A peer might have received different proposal msg so this Proposal msg will be rejected! ps.SetHasProposal(rs.Proposal) } } @@ -964,11 +964,18 @@ func (ps *PeerState) SetHasProposal(proposal *types.Proposal) { if ps.PRS.Height != proposal.Height || ps.PRS.Round != proposal.Round { return } + if ps.PRS.Proposal { return } ps.PRS.Proposal = true + + // ps.PRS.ProposalBlockParts is set due to NewValidBlockMessage + if ps.PRS.ProposalBlockParts != nil { + return + } + ps.PRS.ProposalBlockPartsHeader = proposal.BlockPartsHeader ps.PRS.ProposalBlockParts = cmn.NewBitArray(proposal.BlockPartsHeader.Total) ps.PRS.ProposalPOLRound = proposal.POLRound @@ -1211,7 +1218,6 @@ func (ps *PeerState) ApplyNewRoundStepMessage(msg *NewRoundStepMessage) { // Just remember these values. psHeight := ps.PRS.Height psRound := ps.PRS.Round - //psStep := ps.PRS.Step psCatchupCommitRound := ps.PRS.CatchupCommitRound psCatchupCommit := ps.PRS.CatchupCommit @@ -1252,8 +1258,8 @@ func (ps *PeerState) ApplyNewRoundStepMessage(msg *NewRoundStepMessage) { } } -// ApplyCommitStepMessage updates the peer state for the new commit. -func (ps *PeerState) ApplyCommitStepMessage(msg *CommitStepMessage) { +// ApplyNewValidBlockMessage updates the peer state for the new valid block. +func (ps *PeerState) ApplyNewValidBlockMessage(msg *NewValidBlockMessage) { ps.mtx.Lock() defer ps.mtx.Unlock() @@ -1261,6 +1267,10 @@ func (ps *PeerState) ApplyCommitStepMessage(msg *CommitStepMessage) { return } + if ps.PRS.Round != msg.Round && !msg.IsCommit { + return + } + ps.PRS.ProposalBlockPartsHeader = msg.BlockPartsHeader ps.PRS.ProposalBlockParts = msg.BlockParts } @@ -1344,7 +1354,7 @@ type ConsensusMessage interface{} func RegisterConsensusMessages(cdc *amino.Codec) { cdc.RegisterInterface((*ConsensusMessage)(nil), nil) cdc.RegisterConcrete(&NewRoundStepMessage{}, "tendermint/NewRoundStepMessage", nil) - cdc.RegisterConcrete(&CommitStepMessage{}, "tendermint/CommitStep", nil) + cdc.RegisterConcrete(&NewValidBlockMessage{}, "tendermint/NewValidBlockMessage", nil) cdc.RegisterConcrete(&ProposalMessage{}, "tendermint/Proposal", nil) cdc.RegisterConcrete(&ProposalPOLMessage{}, "tendermint/ProposalPOL", nil) cdc.RegisterConcrete(&BlockPartMessage{}, "tendermint/BlockPart", nil) @@ -1384,15 +1394,18 @@ func (m *NewRoundStepMessage) String() string { //------------------------------------- // CommitStepMessage is sent when a block is committed. -type CommitStepMessage struct { +type NewValidBlockMessage struct { Height int64 + Round int BlockPartsHeader types.PartSetHeader BlockParts *cmn.BitArray + IsCommit bool } // String returns a string representation. -func (m *CommitStepMessage) String() string { - return fmt.Sprintf("[CommitStep H:%v BP:%v BA:%v]", m.Height, m.BlockPartsHeader, m.BlockParts) +func (m *NewValidBlockMessage) String() string { + return fmt.Sprintf("[ValidBlockMessage H:%v R:%v BP:%v BA:%v IsCommit:%v]", + m.Height, m.Round, m.BlockPartsHeader, m.BlockParts, m.IsCommit) } //------------------------------------- diff --git a/consensus/state.go b/consensus/state.go index 40aeeb7a..3048ee3d 100644 --- a/consensus/state.go +++ b/consensus/state.go @@ -904,13 +904,6 @@ func (cs *ConsensusState) defaultDecideProposal(height int64, round int) { polRound, polBlockID := cs.Votes.POLInfo() proposal := types.NewProposal(height, round, blockParts.Header(), polRound, polBlockID) if err := cs.privValidator.SignProposal(cs.state.ChainID, proposal); err == nil { - // Set fields - /* fields set by setProposal and addBlockPart - cs.Proposal = proposal - cs.ProposalBlock = block - cs.ProposalBlockParts = blockParts - */ - // send proposal and block parts on internal msg queue cs.sendInternalMessage(msgInfo{&ProposalMessage{proposal}, ""}) for i := 0; i < blockParts.Total(); i++ { @@ -994,14 +987,6 @@ func (cs *ConsensusState) enterPrevote(height int64, round int) { cs.newStep() }() - // fire event for how we got here - if cs.isProposalComplete() { - cs.eventBus.PublishEventCompleteProposal(cs.RoundStateEvent()) - } else { - // we received +2/3 prevotes for a future round - // TODO: catchup event? - } - cs.Logger.Info(fmt.Sprintf("enterPrevote(%v/%v). Current: %v/%v/%v", height, round, cs.Height, cs.Round, cs.Step)) // Sign and broadcast vote as necessary @@ -1240,6 +1225,8 @@ func (cs *ConsensusState) enterCommit(height int64, commitRound int) { // Set up ProposalBlockParts and keep waiting. cs.ProposalBlock = nil cs.ProposalBlockParts = types.NewPartSetFromHeader(blockID.PartsHeader) + cs.eventBus.PublishEventValidBlock(cs.RoundStateEvent()) + cs.evsw.FireEvent(types.EventValidBlock, &cs.RoundState) } else { // We just need to keep waiting. } @@ -1420,11 +1407,6 @@ func (cs *ConsensusState) defaultSetProposal(proposal *types.Proposal) error { return nil } - // We don't care about the proposal if we're already in cstypes.RoundStepCommit. - if cstypes.RoundStepCommit <= cs.Step { - return nil - } - // Verify POLRound, which must be -1 or between 0 and proposal.Round exclusive. if proposal.POLRound != -1 && (proposal.POLRound < 0 || proposal.Round <= proposal.POLRound) { @@ -1437,7 +1419,12 @@ func (cs *ConsensusState) defaultSetProposal(proposal *types.Proposal) error { } cs.Proposal = proposal - cs.ProposalBlockParts = types.NewPartSetFromHeader(proposal.BlockPartsHeader) + // We don't update cs.ProposalBlockParts if it is already set. + // This happens if we're already in cstypes.RoundStepCommit or if there is a valid block in the current round. + // TODO: We can check if Proposal is for a different block as this is a sign of misbehavior! + if cs.ProposalBlockParts == nil { + cs.ProposalBlockParts = types.NewPartSetFromHeader(proposal.BlockPartsHeader) + } cs.Logger.Info("Received proposal", "proposal", proposal) return nil } @@ -1478,6 +1465,7 @@ func (cs *ConsensusState) addProposalBlockPart(msg *BlockPartMessage, peerID p2p } // NOTE: it's possible to receive complete proposal blocks for future rounds without having the proposal cs.Logger.Info("Received complete proposal block", "height", cs.ProposalBlock.Height, "hash", cs.ProposalBlock.Hash()) + cs.eventBus.PublishEventCompleteProposal(cs.RoundStateEvent()) // Update Valid* if we can. prevotes := cs.Votes.Prevotes(cs.Round) @@ -1616,16 +1604,26 @@ func (cs *ConsensusState) addVote(vote *types.Vote, peerID p2p.ID) (added bool, // Update Valid* if we can. // NOTE: our proposal block may be nil or not what received a polka.. - // TODO: we may want to still update the ValidBlock and obtain it via gossipping - if len(blockID.Hash) != 0 && - (cs.ValidRound < vote.Round) && - (vote.Round <= cs.Round) && - cs.ProposalBlock.HashesTo(blockID.Hash) { + if len(blockID.Hash) != 0 && (cs.ValidRound < vote.Round) && (vote.Round == cs.Round) { - cs.Logger.Info("Updating ValidBlock because of POL.", "validRound", cs.ValidRound, "POLRound", vote.Round) - cs.ValidRound = vote.Round - cs.ValidBlock = cs.ProposalBlock - cs.ValidBlockParts = cs.ProposalBlockParts + if cs.ProposalBlock.HashesTo(blockID.Hash) { + cs.Logger.Info( + "Updating ValidBlock because of POL.", "validRound", cs.ValidRound, "POLRound", vote.Round) + cs.ValidRound = vote.Round + cs.ValidBlock = cs.ProposalBlock + cs.ValidBlockParts = cs.ProposalBlockParts + } else { + cs.Logger.Info( + "Valid block we don't know about. Set ProposalBlock=nil", + "proposal", cs.ProposalBlock.Hash(), "blockId", blockID.Hash) + // We're getting the wrong block. + cs.ProposalBlock = nil + } + if !cs.ProposalBlockParts.HasHeader(blockID.PartsHeader) { + cs.ProposalBlockParts = types.NewPartSetFromHeader(blockID.PartsHeader) + } + cs.evsw.FireEvent(types.EventValidBlock, &cs.RoundState) + cs.eventBus.PublishEventValidBlock(cs.RoundStateEvent()) } } @@ -1634,7 +1632,8 @@ func (cs *ConsensusState) addVote(vote *types.Vote, peerID p2p.ID) (added bool, // Round-skip if there is any 2/3+ of votes ahead of us cs.enterNewRound(height, vote.Round) } else if cs.Round == vote.Round && cstypes.RoundStepPrevote <= cs.Step { // current round - if prevotes.HasTwoThirdsMajority() { + blockID, ok := prevotes.TwoThirdsMajority() + if ok && (cs.isProposalComplete() || len(blockID.Hash) == 0) { cs.enterPrecommit(height, vote.Round) } else if prevotes.HasTwoThirdsAny() { cs.enterPrevoteWait(height, vote.Round) diff --git a/consensus/state_test.go b/consensus/state_test.go index 83c4bb14..87c8b285 100644 --- a/consensus/state_test.go +++ b/consensus/state_test.go @@ -966,6 +966,117 @@ func TestProposeValidBlock(t *testing.T) { assert.True(t, bytes.Equal(rs.ProposalBlock.Hash(), rs.ValidBlock.Hash())) } +// What we want: +// P0 miss to lock B but set valid block to B after receiving delayed prevote. +func TestSetValidBlockOnDelayedPrevote(t *testing.T) { + cs1, vss := randConsensusState(4) + vs2, vs3, vs4 := vss[1], vss[2], vss[3] + height, round := cs1.Height, cs1.Round + + partSize := types.BlockPartSizeBytes + + proposalCh := subscribe(cs1.eventBus, types.EventQueryCompleteProposal) + timeoutWaitCh := subscribe(cs1.eventBus, types.EventQueryTimeoutWait) + newRoundCh := subscribe(cs1.eventBus, types.EventQueryNewRound) + validBlockCh := subscribe(cs1.eventBus, types.EventQueryValidBlock) + voteCh := subscribeToVoter(cs1, cs1.privValidator.GetAddress()) + + // start round and wait for propose and prevote + startTestRound(cs1, cs1.Height, round) + ensureNewRound(newRoundCh, height, round) + + ensureNewProposal(proposalCh, height, round) + rs := cs1.GetRoundState() + propBlock := rs.ProposalBlock + propBlockHash := propBlock.Hash() + propBlockParts := propBlock.MakePartSet(partSize) + + ensurePrevote(voteCh, height, round) + validatePrevote(t, cs1, round, vss[0], propBlockHash) + + // vs2 send prevote for propBlock + signAddVotes(cs1, types.PrevoteType, propBlockHash, propBlockParts.Header(), vs2) + + // vs3 send prevote nil + signAddVotes(cs1, types.PrevoteType, nil, types.PartSetHeader{}, vs3) + + ensureNewTimeout(timeoutWaitCh, height, round, cs1.config.TimeoutPrevote.Nanoseconds()) + + ensurePrecommit(voteCh, height, round) + // we should have precommitted + validatePrecommit(t, cs1, round, -1, vss[0], nil, nil) + + rs = cs1.GetRoundState() + + assert.True(t, rs.ValidBlock == nil) + assert.True(t, rs.ValidBlockParts == nil) + assert.True(t, rs.ValidRound == -1) + + // vs2 send (delayed) prevote for propBlock + signAddVotes(cs1, types.PrevoteType, propBlockHash, propBlockParts.Header(), vs4) + + ensureNewValidBlock(validBlockCh, height, round) + + rs = cs1.GetRoundState() + + assert.True(t, bytes.Equal(rs.ValidBlock.Hash(), propBlockHash)) + assert.True(t, rs.ValidBlockParts.Header().Equals(propBlockParts.Header())) + assert.True(t, rs.ValidRound == round) +} + +// What we want: +// P0 miss to lock B as Proposal Block is missing, but set valid block to B after +// receiving delayed Block Proposal. +func TestSetValidBlockOnDelayedProposal(t *testing.T) { + cs1, vss := randConsensusState(4) + vs2, vs3, vs4 := vss[1], vss[2], vss[3] + height, round := cs1.Height, cs1.Round + + partSize := types.BlockPartSizeBytes + + timeoutWaitCh := subscribe(cs1.eventBus, types.EventQueryTimeoutWait) + timeoutProposeCh := subscribe(cs1.eventBus, types.EventQueryTimeoutPropose) + newRoundCh := subscribe(cs1.eventBus, types.EventQueryNewRound) + validBlockCh := subscribe(cs1.eventBus, types.EventQueryValidBlock) + voteCh := subscribeToVoter(cs1, cs1.privValidator.GetAddress()) + proposalCh := subscribe(cs1.eventBus, types.EventQueryCompleteProposal) + + round = round + 1 // move to round in which P0 is not proposer + incrementRound(vs2, vs3, vs4) + + startTestRound(cs1, cs1.Height, round) + ensureNewRound(newRoundCh, height, round) + + ensureNewTimeout(timeoutProposeCh, height, round, cs1.config.TimeoutPropose.Nanoseconds()) + + ensurePrevote(voteCh, height, round) + validatePrevote(t, cs1, round, vss[0], nil) + + prop, propBlock := decideProposal(cs1, vs2, vs2.Height, vs2.Round+1) + propBlockHash := propBlock.Hash() + propBlockParts := propBlock.MakePartSet(partSize) + + // vs2, vs3 and vs4 send prevote for propBlock + signAddVotes(cs1, types.PrevoteType, propBlockHash, propBlockParts.Header(), vs2, vs3, vs4) + ensureNewValidBlock(validBlockCh, height, round) + + ensureNewTimeout(timeoutWaitCh, height, round, cs1.config.TimeoutPrevote.Nanoseconds()) + + ensurePrecommit(voteCh, height, round) + validatePrecommit(t, cs1, round, -1, vss[0], nil, nil) + + if err := cs1.SetProposalAndBlock(prop, propBlock, propBlockParts, "some peer"); err != nil { + t.Fatal(err) + } + + ensureNewProposal(proposalCh, height, round) + rs := cs1.GetRoundState() + + assert.True(t, bytes.Equal(rs.ValidBlock.Hash(), propBlockHash)) + assert.True(t, rs.ValidBlockParts.Header().Equals(propBlockParts.Header())) + assert.True(t, rs.ValidRound == round) +} + // 4 vals, 3 Nil Precommits at P0 // What we want: // P0 waits for timeoutPrecommit before starting next round @@ -1078,6 +1189,80 @@ func TestWaitTimeoutProposeOnNilPolkaForTheCurrentRound(t *testing.T) { validatePrevote(t, cs1, round, vss[0], nil) } +// What we want: +// P0 emit NewValidBlock event upon receiving 2/3+ Precommit for B but hasn't received block B yet +func TestEmitNewValidBlockEventOnCommitWithoutBlock(t *testing.T) { + cs1, vss := randConsensusState(4) + vs2, vs3, vs4 := vss[1], vss[2], vss[3] + height, round := cs1.Height, 1 + + incrementRound(vs2, vs3, vs4) + + partSize := types.BlockPartSizeBytes + + newRoundCh := subscribe(cs1.eventBus, types.EventQueryNewRound) + validBlockCh := subscribe(cs1.eventBus, types.EventQueryValidBlock) + + _, propBlock := decideProposal(cs1, vs2, vs2.Height, vs2.Round) + propBlockHash := propBlock.Hash() + propBlockParts := propBlock.MakePartSet(partSize) + + // start round in which PO is not proposer + startTestRound(cs1, height, round) + ensureNewRound(newRoundCh, height, round) + + // vs2, vs3 and vs4 send precommit for propBlock + signAddVotes(cs1, types.PrecommitType, propBlockHash, propBlockParts.Header(), vs2, vs3, vs4) + ensureNewValidBlock(validBlockCh, height, round) + + rs := cs1.GetRoundState() + assert.True(t, rs.Step == cstypes.RoundStepCommit) + assert.True(t, rs.ProposalBlock == nil) + assert.True(t, rs.ProposalBlockParts.Header().Equals(propBlockParts.Header())) + +} + +// What we want: +// P0 receives 2/3+ Precommit for B for round 0, while being in round 1. It emits NewValidBlock event. +// After receiving block, it executes block and moves to the next height. +func TestCommitFromPreviousRound(t *testing.T) { + cs1, vss := randConsensusState(4) + vs2, vs3, vs4 := vss[1], vss[2], vss[3] + height, round := cs1.Height, 1 + + partSize := types.BlockPartSizeBytes + + newRoundCh := subscribe(cs1.eventBus, types.EventQueryNewRound) + validBlockCh := subscribe(cs1.eventBus, types.EventQueryValidBlock) + proposalCh := subscribe(cs1.eventBus, types.EventQueryCompleteProposal) + + prop, propBlock := decideProposal(cs1, vs2, vs2.Height, vs2.Round) + propBlockHash := propBlock.Hash() + propBlockParts := propBlock.MakePartSet(partSize) + + // start round in which PO is not proposer + startTestRound(cs1, height, round) + ensureNewRound(newRoundCh, height, round) + + // vs2, vs3 and vs4 send precommit for propBlock for the previous round + signAddVotes(cs1, types.PrecommitType, propBlockHash, propBlockParts.Header(), vs2, vs3, vs4) + + ensureNewValidBlock(validBlockCh, height, round) + + rs := cs1.GetRoundState() + assert.True(t, rs.Step == cstypes.RoundStepCommit) + assert.True(t, rs.CommitRound == vs2.Round) + assert.True(t, rs.ProposalBlock == nil) + assert.True(t, rs.ProposalBlockParts.Header().Equals(propBlockParts.Header())) + + if err := cs1.SetProposalAndBlock(prop, propBlock, propBlockParts, "some peer"); err != nil { + t.Fatal(err) + } + + ensureNewProposal(proposalCh, height, round) + ensureNewRound(newRoundCh, height+1, 0) +} + //------------------------------------------------------------------------------------------ // SlashingSuite // TODO: Slashing diff --git a/docs/spec/reactors/consensus/consensus-reactor.md b/docs/spec/reactors/consensus/consensus-reactor.md index 7be35032..5ba03322 100644 --- a/docs/spec/reactors/consensus/consensus-reactor.md +++ b/docs/spec/reactors/consensus/consensus-reactor.md @@ -129,11 +129,11 @@ handleMessage(msg): Reset prs.CatchupCommitRound and prs.CatchupCommit ``` -### CommitStepMessage handler +### NewValidBlockMessage handler ``` handleMessage(msg): - if prs.Height == msg.Height then + if prs.Height == msg.Height && prs.Round == msg.Round then prs.ProposalBlockPartsHeader = msg.BlockPartsHeader prs.ProposalBlockParts = msg.BlockParts ``` @@ -161,8 +161,8 @@ handleMessage(msg): handleMessage(msg): if prs.Height != msg.Height || prs.Round != msg.Round || prs.Proposal then return prs.Proposal = true - prs.ProposalBlockPartsHeader = msg.BlockPartsHeader - prs.ProposalBlockParts = empty set + if prs.ProposalBlockParts == empty set then // otherwise it is set in NewValidBlockMessage handler + prs.ProposalBlockPartsHeader = msg.BlockPartsHeader prs.ProposalPOLRound = msg.POLRound prs.ProposalPOL = nil Send msg through internal peerMsgQueue to ConsensusState service diff --git a/p2p/metrics.go b/p2p/metrics.go index b066fb31..ed26d119 100644 --- a/p2p/metrics.go +++ b/p2p/metrics.go @@ -62,7 +62,7 @@ func PrometheusMetrics(namespace string) *Metrics { // NopMetrics returns no-op Metrics. func NopMetrics() *Metrics { return &Metrics{ - Peers: discard.NewGauge(), + Peers: discard.NewGauge(), PeerReceiveBytesTotal: discard.NewCounter(), PeerSendBytesTotal: discard.NewCounter(), PeerPendingSendBytes: discard.NewGauge(), diff --git a/types/event_bus.go b/types/event_bus.go index 269d5ab1..65206e93 100644 --- a/types/event_bus.go +++ b/types/event_bus.go @@ -83,6 +83,10 @@ func (b *EventBus) PublishEventVote(data EventDataVote) error { return b.Publish(EventVote, data) } +func (b *EventBus) PublishEventValidBlock(data EventDataRoundState) error { + return b.Publish(EventValidBlock, data) +} + // PublishEventTx publishes tx event with tags from Result. Note it will add // predefined tags (EventTypeKey, TxHashKey). Existing tags with the same names // will be overwritten. diff --git a/types/events.go b/types/events.go index 09f7216e..33aa712e 100644 --- a/types/events.go +++ b/types/events.go @@ -23,6 +23,7 @@ const ( EventTimeoutWait = "TimeoutWait" EventTx = "Tx" EventUnlock = "Unlock" + EventValidBlock = "ValidBlock" EventValidatorSetUpdates = "ValidatorSetUpdates" EventVote = "Vote" ) @@ -119,6 +120,7 @@ var ( EventQueryTx = QueryForEvent(EventTx) EventQueryUnlock = QueryForEvent(EventUnlock) EventQueryValidatorSetUpdates = QueryForEvent(EventValidatorSetUpdates) + EventQueryValidBlock = QueryForEvent(EventValidBlock) EventQueryVote = QueryForEvent(EventVote) ) diff --git a/types/evidence_test.go b/types/evidence_test.go index 44276ab1..033b51e5 100644 --- a/types/evidence_test.go +++ b/types/evidence_test.go @@ -61,7 +61,7 @@ func TestEvidence(t *testing.T) { {vote1, makeVote(val, chainID, 0, 10, 3, 1, blockID2), false}, // wrong round {vote1, makeVote(val, chainID, 0, 10, 2, 2, blockID2), false}, // wrong step {vote1, makeVote(val2, chainID, 0, 10, 2, 1, blockID), false}, // wrong validator - {vote1, badVote, false}, // signed by wrong key + {vote1, badVote, false}, // signed by wrong key } pubKey := val.GetPubKey() From c5905900eb26d0af7297faee97f1861cee840c00 Mon Sep 17 00:00:00 2001 From: Zarko Milosevic Date: Wed, 31 Oct 2018 15:27:11 +0100 Subject: [PATCH 045/267] Simplify proposal msg (#2735) * Align Proposal message with spec * Update spec --- CHANGELOG_PENDING.md | 1 + Gopkg.lock | 4 +-- consensus/byzantine_test.go | 8 ++--- consensus/common_test.go | 4 +-- consensus/reactor.go | 4 +-- consensus/replay.go | 2 +- consensus/replay_test.go | 2 +- consensus/state.go | 7 ++-- consensus/state_test.go | 12 ++++--- consensus/types/round_state_test.go | 7 ++-- docs/spec/reactors/consensus/consensus.md | 14 +++----- privval/priv_validator_test.go | 16 ++++----- types/canonical.go | 30 ++++++++--------- types/proposal.go | 40 +++++++++++------------ types/proposal_test.go | 16 +++++---- 15 files changed, 82 insertions(+), 85 deletions(-) diff --git a/CHANGELOG_PENDING.md b/CHANGELOG_PENDING.md index ce0cf247..f28b4608 100644 --- a/CHANGELOG_PENDING.md +++ b/CHANGELOG_PENDING.md @@ -108,6 +108,7 @@ Friendly reminder, we have a [bug bounty program](https://hackerone.com/tendermi - [consensus] [\#2642](https://github.com/tendermint/tendermint/issues/2642) Initialized ValidRound and LockedRound to -1 - [consensus] [\#1637](https://github.com/tendermint/tendermint/issues/1637) Limit the amount of evidence that can be included in a block +- [consensus] [\#2646](https://github.com/tendermint/tendermint/issues/2646) Simplify Proposal message (align with spec) - [evidence] [\#2515](https://github.com/tendermint/tendermint/issues/2515) Fix db iter leak (@goolAdapter) - [libs/event] [\#2518](https://github.com/tendermint/tendermint/issues/2518) Fix event concurrency flaw (@goolAdapter) - [node] [\#2434](https://github.com/tendermint/tendermint/issues/2434) Make node respond to signal interrupts while sleeping for genesis time diff --git a/Gopkg.lock b/Gopkg.lock index 59e42f92..35542bf6 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -408,14 +408,14 @@ [[projects]] branch = "master" - digest = "1:5207b4bc950fd0e45544263103af3e119c94fba6717f9d61931f7a19a7c0706a" + digest = "1:6f86e2f2e2217cd4d74dec6786163cf80e4d2b99adb341ecc60a45113b844dca" name = "golang.org/x/sys" packages = [ "cpu", "unix", ] pruneopts = "UT" - revision = "f7626d0b1519d8323581a047ca8b372ebf28de9a" + revision = "7e31e0c00fa05cb5fbf4347b585621d6709e19a4" [[projects]] digest = "1:a2ab62866c75542dd18d2b069fec854577a20211d7c0ea6ae746072a1dccdd18" diff --git a/consensus/byzantine_test.go b/consensus/byzantine_test.go index ed4cc90c..6f46c04d 100644 --- a/consensus/byzantine_test.go +++ b/consensus/byzantine_test.go @@ -179,16 +179,16 @@ func byzantineDecideProposalFunc(t *testing.T, height int64, round int, cs *Cons // Create a new proposal block from state/txs from the mempool. block1, blockParts1 := cs.createProposalBlock() - polRound, polBlockID := cs.Votes.POLInfo() - proposal1 := types.NewProposal(height, round, blockParts1.Header(), polRound, polBlockID) + polRound, propBlockID := cs.ValidRound, types.BlockID{block1.Hash(), blockParts1.Header()} + proposal1 := types.NewProposal(height, round, polRound, propBlockID) if err := cs.privValidator.SignProposal(cs.state.ChainID, proposal1); err != nil { t.Error(err) } // Create a new proposal block from state/txs from the mempool. block2, blockParts2 := cs.createProposalBlock() - polRound, polBlockID = cs.Votes.POLInfo() - proposal2 := types.NewProposal(height, round, blockParts2.Header(), polRound, polBlockID) + polRound, propBlockID = cs.ValidRound, types.BlockID{block2.Hash(), blockParts2.Header()} + proposal2 := types.NewProposal(height, round, polRound, propBlockID) if err := cs.privValidator.SignProposal(cs.state.ChainID, proposal2); err != nil { t.Error(err) } diff --git a/consensus/common_test.go b/consensus/common_test.go index c949922f..ae8bb6bf 100644 --- a/consensus/common_test.go +++ b/consensus/common_test.go @@ -130,8 +130,8 @@ func decideProposal(cs1 *ConsensusState, vs *validatorStub, height int64, round } // Make proposal - polRound, polBlockID := cs1.Votes.POLInfo() - proposal = types.NewProposal(height, round, blockParts.Header(), polRound, polBlockID) + polRound, propBlockID := cs1.ValidRound, types.BlockID{block.Hash(), blockParts.Header()} + proposal = types.NewProposal(height, round, polRound, propBlockID) if err := vs.SignProposal(cs1.state.ChainID, proposal); err != nil { panic(err) } diff --git a/consensus/reactor.go b/consensus/reactor.go index 8d5e726f..e8c7adc7 100644 --- a/consensus/reactor.go +++ b/consensus/reactor.go @@ -976,8 +976,8 @@ func (ps *PeerState) SetHasProposal(proposal *types.Proposal) { return } - ps.PRS.ProposalBlockPartsHeader = proposal.BlockPartsHeader - ps.PRS.ProposalBlockParts = cmn.NewBitArray(proposal.BlockPartsHeader.Total) + ps.PRS.ProposalBlockPartsHeader = proposal.BlockID.PartsHeader + ps.PRS.ProposalBlockParts = cmn.NewBitArray(proposal.BlockID.PartsHeader.Total) ps.PRS.ProposalPOLRound = proposal.POLRound ps.PRS.ProposalPOL = nil // Nil until ProposalPOLMessage received. } diff --git a/consensus/replay.go b/consensus/replay.go index bffab8d2..fcff877f 100644 --- a/consensus/replay.go +++ b/consensus/replay.go @@ -73,7 +73,7 @@ func (cs *ConsensusState) readReplayMessage(msg *TimedWALMessage, newStepCh chan case *ProposalMessage: p := msg.Proposal cs.Logger.Info("Replay: Proposal", "height", p.Height, "round", p.Round, "header", - p.BlockPartsHeader, "pol", p.POLRound, "peer", peerID) + p.BlockID.PartsHeader, "pol", p.POLRound, "peer", peerID) case *BlockPartMessage: cs.Logger.Info("Replay: BlockPart", "height", msg.Height, "round", msg.Round, "peer", peerID) case *VoteMessage: diff --git a/consensus/replay_test.go b/consensus/replay_test.go index d6691103..70c4ba33 100644 --- a/consensus/replay_test.go +++ b/consensus/replay_test.go @@ -575,7 +575,7 @@ func readPieceFromWAL(msg *TimedWALMessage) interface{} { case msgInfo: switch msg := m.Msg.(type) { case *ProposalMessage: - return &msg.Proposal.BlockPartsHeader + return &msg.Proposal.BlockID.PartsHeader case *BlockPartMessage: return msg.Part case *VoteMessage: diff --git a/consensus/state.go b/consensus/state.go index 3048ee3d..5b144898 100644 --- a/consensus/state.go +++ b/consensus/state.go @@ -901,9 +901,10 @@ func (cs *ConsensusState) defaultDecideProposal(height int64, round int) { } // Make proposal - polRound, polBlockID := cs.Votes.POLInfo() - proposal := types.NewProposal(height, round, blockParts.Header(), polRound, polBlockID) + propBlockId := types.BlockID{block.Hash(), blockParts.Header()} + proposal := types.NewProposal(height, round, cs.ValidRound, propBlockId) if err := cs.privValidator.SignProposal(cs.state.ChainID, proposal); err == nil { + // send proposal and block parts on internal msg queue cs.sendInternalMessage(msgInfo{&ProposalMessage{proposal}, ""}) for i := 0; i < blockParts.Total(); i++ { @@ -1423,7 +1424,7 @@ func (cs *ConsensusState) defaultSetProposal(proposal *types.Proposal) error { // This happens if we're already in cstypes.RoundStepCommit or if there is a valid block in the current round. // TODO: We can check if Proposal is for a different block as this is a sign of misbehavior! if cs.ProposalBlockParts == nil { - cs.ProposalBlockParts = types.NewPartSetFromHeader(proposal.BlockPartsHeader) + cs.ProposalBlockParts = types.NewPartSetFromHeader(proposal.BlockID.PartsHeader) } cs.Logger.Info("Received proposal", "proposal", proposal) return nil diff --git a/consensus/state_test.go b/consensus/state_test.go index 87c8b285..9bf4fada 100644 --- a/consensus/state_test.go +++ b/consensus/state_test.go @@ -197,7 +197,9 @@ func TestStateBadProposal(t *testing.T) { stateHash[0] = byte((stateHash[0] + 1) % 255) propBlock.AppHash = stateHash propBlockParts := propBlock.MakePartSet(partSize) - proposal := types.NewProposal(vs2.Height, round, propBlockParts.Header(), -1, types.BlockID{}) + proposal := types.NewProposal( + vs2.Height, round, -1, + types.BlockID{propBlock.Hash(), propBlockParts.Header()}) if err := vs2.SignProposal(config.ChainID(), proposal); err != nil { t.Fatal("failed to sign bad proposal", err) } @@ -811,6 +813,7 @@ func TestStateLockPOLSafety2(t *testing.T) { _, propBlock0 := decideProposal(cs1, vss[0], height, round) propBlockHash0 := propBlock0.Hash() propBlockParts0 := propBlock0.MakePartSet(partSize) + propBlockID0 := types.BlockID{propBlockHash0, propBlockParts0.Header()} // the others sign a polka but we don't see it prevotes := signVotes(types.PrevoteType, propBlockHash0, propBlockParts0.Header(), vs2, vs3, vs4) @@ -819,7 +822,6 @@ func TestStateLockPOLSafety2(t *testing.T) { prop1, propBlock1 := decideProposal(cs1, vs2, vs2.Height, vs2.Round+1) propBlockHash1 := propBlock1.Hash() propBlockParts1 := propBlock1.MakePartSet(partSize) - propBlockID1 := types.BlockID{propBlockHash1, propBlockParts1.Header()} incrementRound(vs2, vs3, vs4) @@ -854,7 +856,7 @@ func TestStateLockPOLSafety2(t *testing.T) { round = round + 1 // moving to the next round // in round 2 we see the polkad block from round 0 - newProp := types.NewProposal(height, round, propBlockParts0.Header(), 0, propBlockID1) + newProp := types.NewProposal(height, round, 0, propBlockID0) if err := vs3.SignProposal(config.ChainID(), newProp); err != nil { t.Fatal(err) } @@ -909,7 +911,7 @@ func TestProposeValidBlock(t *testing.T) { ensurePrevote(voteCh, height, round) validatePrevote(t, cs1, round, vss[0], propBlockHash) - // the others sign a polka but we don't see it + // the others sign a polka signAddVotes(cs1, types.PrevoteType, propBlockHash, propBlock.MakePartSet(partSize).Header(), vs2, vs3, vs4) ensurePrecommit(voteCh, height, round) @@ -964,6 +966,8 @@ func TestProposeValidBlock(t *testing.T) { rs = cs1.GetRoundState() assert.True(t, bytes.Equal(rs.ProposalBlock.Hash(), propBlockHash)) assert.True(t, bytes.Equal(rs.ProposalBlock.Hash(), rs.ValidBlock.Hash())) + assert.True(t, rs.Proposal.POLRound == rs.ValidRound) + assert.True(t, bytes.Equal(rs.Proposal.BlockID.Hash, rs.ValidBlock.Hash())) } // What we want: diff --git a/consensus/types/round_state_test.go b/consensus/types/round_state_test.go index a330981f..6a1c4533 100644 --- a/consensus/types/round_state_test.go +++ b/consensus/types/round_state_test.go @@ -63,11 +63,8 @@ func BenchmarkRoundStateDeepCopy(b *testing.B) { // Random Proposal proposal := &types.Proposal{ Timestamp: tmtime.Now(), - BlockPartsHeader: types.PartSetHeader{ - Hash: cmn.RandBytes(20), - }, - POLBlockID: blockID, - Signature: sig, + BlockID: blockID, + Signature: sig, } // Random HeightVoteSet // TODO: hvs := diff --git a/docs/spec/reactors/consensus/consensus.md b/docs/spec/reactors/consensus/consensus.md index a1cf17bc..0f192230 100644 --- a/docs/spec/reactors/consensus/consensus.md +++ b/docs/spec/reactors/consensus/consensus.md @@ -47,25 +47,21 @@ type ProposalMessage struct { ### Proposal Proposal contains height and round for which this proposal is made, BlockID as a unique identifier -of proposed block, timestamp, and two fields (POLRound and POLBlockID) that are needed for -termination of the consensus. The message is signed by the validator private key. +of proposed block, timestamp, and POLRound (a so-called Proof-of-Lock (POL) round) that is needed for +termination of the consensus. If POLRound >= 0, then BlockID corresponds to the block that +is locked in POLRound. The message is signed by the validator private key. ```go type Proposal struct { Height int64 Round int - Timestamp Time - BlockID BlockID POLRound int - POLBlockID BlockID + BlockID BlockID + Timestamp Time Signature Signature } ``` -NOTE: In the current version of the Tendermint, the consensus value in proposal is represented with -PartSetHeader, and with BlockID in vote message. It should be aligned as suggested in this spec as -BlockID contains PartSetHeader. - ## VoteMessage VoteMessage is sent to vote for some block (or to inform others that a process does not vote in the diff --git a/privval/priv_validator_test.go b/privval/priv_validator_test.go index 90796ddf..4f4eed97 100644 --- a/privval/priv_validator_test.go +++ b/privval/priv_validator_test.go @@ -140,8 +140,8 @@ func TestSignProposal(t *testing.T) { require.Nil(t, err) privVal := GenFilePV(tempFile.Name()) - block1 := types.PartSetHeader{5, []byte{1, 2, 3}} - block2 := types.PartSetHeader{10, []byte{3, 2, 1}} + block1 := types.BlockID{[]byte{1, 2, 3}, types.PartSetHeader{5, []byte{1, 2, 3}}} + block2 := types.BlockID{[]byte{3, 2, 1}, types.PartSetHeader{10, []byte{3, 2, 1}}} height, round := int64(10), 1 // sign a proposal for first time @@ -179,7 +179,7 @@ func TestDifferByTimestamp(t *testing.T) { require.Nil(t, err) privVal := GenFilePV(tempFile.Name()) - block1 := types.PartSetHeader{5, []byte{1, 2, 3}} + block1 := types.BlockID{[]byte{1, 2, 3}, types.PartSetHeader{5, []byte{1, 2, 3}}} height, round := int64(10), 1 chainID := "mychainid" @@ -241,11 +241,11 @@ func newVote(addr types.Address, idx int, height int64, round int, typ byte, blo } } -func newProposal(height int64, round int, partsHeader types.PartSetHeader) *types.Proposal { +func newProposal(height int64, round int, blockID types.BlockID) *types.Proposal { return &types.Proposal{ - Height: height, - Round: round, - BlockPartsHeader: partsHeader, - Timestamp: tmtime.Now(), + Height: height, + Round: round, + BlockID: blockID, + Timestamp: tmtime.Now(), } } diff --git a/types/canonical.go b/types/canonical.go index 632dcb62..a4f6f214 100644 --- a/types/canonical.go +++ b/types/canonical.go @@ -23,14 +23,13 @@ type CanonicalPartSetHeader struct { } type CanonicalProposal struct { - Type SignedMsgType // type alias for byte - Height int64 `binary:"fixed64"` - Round int64 `binary:"fixed64"` - POLRound int64 `binary:"fixed64"` - Timestamp time.Time - BlockPartsHeader CanonicalPartSetHeader - POLBlockID CanonicalBlockID - ChainID string + Type SignedMsgType // type alias for byte + Height int64 `binary:"fixed64"` + Round int64 `binary:"fixed64"` + POLRound int64 `binary:"fixed64"` + BlockID CanonicalBlockID + Timestamp time.Time + ChainID string } type CanonicalVote struct { @@ -71,14 +70,13 @@ func CanonicalizePartSetHeader(psh PartSetHeader) CanonicalPartSetHeader { func CanonicalizeProposal(chainID string, proposal *Proposal) CanonicalProposal { return CanonicalProposal{ - Type: ProposalType, - Height: proposal.Height, - Round: int64(proposal.Round), // cast int->int64 to make amino encode it fixed64 (does not work for int) - POLRound: int64(proposal.POLRound), - Timestamp: proposal.Timestamp, - BlockPartsHeader: CanonicalizePartSetHeader(proposal.BlockPartsHeader), - POLBlockID: CanonicalizeBlockID(proposal.POLBlockID), - ChainID: chainID, + Type: ProposalType, + Height: proposal.Height, + Round: int64(proposal.Round), // cast int->int64 to make amino encode it fixed64 (does not work for int) + POLRound: int64(proposal.POLRound), + BlockID: CanonicalizeBlockID(proposal.BlockID), + Timestamp: proposal.Timestamp, + ChainID: chainID, } } diff --git a/types/proposal.go b/types/proposal.go index fa82fdbb..09cfd196 100644 --- a/types/proposal.go +++ b/types/proposal.go @@ -15,43 +15,41 @@ var ( ) // Proposal defines a block proposal for the consensus. -// It refers to the block only by its PartSetHeader. +// It refers to the block by BlockID field. // It must be signed by the correct proposer for the given Height/Round // to be considered valid. It may depend on votes from a previous round, -// a so-called Proof-of-Lock (POL) round, as noted in the POLRound and POLBlockID. +// a so-called Proof-of-Lock (POL) round, as noted in the POLRound. +// If POLRound >= 0, then BlockID corresponds to the block that is locked in POLRound. type Proposal struct { - Type SignedMsgType - Height int64 `json:"height"` - Round int `json:"round"` - POLRound int `json:"pol_round"` // -1 if null. - Timestamp time.Time `json:"timestamp"` - BlockPartsHeader PartSetHeader `json:"block_parts_header"` - POLBlockID BlockID `json:"pol_block_id"` // zero if null. - Signature []byte `json:"signature"` + Type SignedMsgType + Height int64 `json:"height"` + Round int `json:"round"` + POLRound int `json:"pol_round"` // -1 if null. + BlockID BlockID `json:"block_id"` + Timestamp time.Time `json:"timestamp"` + Signature []byte `json:"signature"` } // NewProposal returns a new Proposal. // If there is no POLRound, polRound should be -1. -func NewProposal(height int64, round int, blockPartsHeader PartSetHeader, polRound int, polBlockID BlockID) *Proposal { +func NewProposal(height int64, round int, polRound int, blockID BlockID) *Proposal { return &Proposal{ - Type: ProposalType, - Height: height, - Round: round, - POLRound: polRound, - Timestamp: tmtime.Now(), - BlockPartsHeader: blockPartsHeader, - POLBlockID: polBlockID, + Type: ProposalType, + Height: height, + Round: round, + BlockID: blockID, + POLRound: polRound, + Timestamp: tmtime.Now(), } } // String returns a string representation of the Proposal. func (p *Proposal) String() string { - return fmt.Sprintf("Proposal{%v/%v %v (%v,%v) %X @ %s}", + return fmt.Sprintf("Proposal{%v/%v (%v, %v) %X @ %s}", p.Height, p.Round, - p.BlockPartsHeader, + p.BlockID, p.POLRound, - p.POLBlockID, cmn.Fingerprint(p.Signature), CanonicalTime(p.Timestamp)) } diff --git a/types/proposal_test.go b/types/proposal_test.go index 8ae1f3e5..9738db2d 100644 --- a/types/proposal_test.go +++ b/types/proposal_test.go @@ -15,11 +15,11 @@ func init() { panic(err) } testProposal = &Proposal{ - Height: 12345, - Round: 23456, - BlockPartsHeader: PartSetHeader{111, []byte("blockparts")}, - POLRound: -1, - Timestamp: stamp, + Height: 12345, + Round: 23456, + BlockID: BlockID{[]byte{1, 2, 3}, PartSetHeader{111, []byte("blockparts")}}, + POLRound: -1, + Timestamp: stamp, } } @@ -34,7 +34,7 @@ func TestProposalSignable(t *testing.T) { func TestProposalString(t *testing.T) { str := testProposal.String() - expected := `Proposal{12345/23456 111:626C6F636B70 (-1,:0:000000000000) 000000000000 @ 2018-02-11T07:09:22.765Z}` + expected := `Proposal{12345/23456 (010203:111:626C6F636B70, -1) 000000000000 @ 2018-02-11T07:09:22.765Z}` if str != expected { t.Errorf("Got unexpected string for Proposal. Expected:\n%v\nGot:\n%v", expected, str) } @@ -44,7 +44,9 @@ func TestProposalVerifySignature(t *testing.T) { privVal := NewMockPV() pubKey := privVal.GetPubKey() - prop := NewProposal(4, 2, PartSetHeader{777, []byte("proper")}, 2, BlockID{}) + prop := NewProposal( + 4, 2, 2, + BlockID{[]byte{1, 2, 3}, PartSetHeader{777, []byte("proper")}}) signBytes := prop.SignBytes("test_chain_id") // sign it From a83c268d7f1e33249c4acff514eef5901efb66e0 Mon Sep 17 00:00:00 2001 From: Zarko Milosevic Date: Wed, 31 Oct 2018 15:59:01 +0100 Subject: [PATCH 046/267] Fix spec (#2736) --- consensus/reactor.go | 4 +++- .../reactors/consensus/consensus-reactor.md | 9 ++++++--- docs/spec/reactors/consensus/consensus.md | 19 +++++++++++-------- 3 files changed, 20 insertions(+), 12 deletions(-) diff --git a/consensus/reactor.go b/consensus/reactor.go index e8c7adc7..bf6f7ba7 100644 --- a/consensus/reactor.go +++ b/consensus/reactor.go @@ -1393,7 +1393,9 @@ func (m *NewRoundStepMessage) String() string { //------------------------------------- -// CommitStepMessage is sent when a block is committed. +// NewValidBlockMessage is sent when a validator observes a valid block B in some round r, +//i.e., there is a Proposal for block B and 2/3+ prevotes for the block B in the round r. +// In case the block is also committed, then IsCommit flag is set to true. type NewValidBlockMessage struct { Height int64 Round int diff --git a/docs/spec/reactors/consensus/consensus-reactor.md b/docs/spec/reactors/consensus/consensus-reactor.md index 5ba03322..23275b12 100644 --- a/docs/spec/reactors/consensus/consensus-reactor.md +++ b/docs/spec/reactors/consensus/consensus-reactor.md @@ -133,9 +133,12 @@ handleMessage(msg): ``` handleMessage(msg): - if prs.Height == msg.Height && prs.Round == msg.Round then - prs.ProposalBlockPartsHeader = msg.BlockPartsHeader - prs.ProposalBlockParts = msg.BlockParts + if prs.Height != msg.Height then return + + if prs.Round != msg.Round && !msg.IsCommit then return + + prs.ProposalBlockPartsHeader = msg.BlockPartsHeader + prs.ProposalBlockParts = msg.BlockParts ``` ### HasVoteMessage handler diff --git a/docs/spec/reactors/consensus/consensus.md b/docs/spec/reactors/consensus/consensus.md index 0f192230..e5d1f4cc 100644 --- a/docs/spec/reactors/consensus/consensus.md +++ b/docs/spec/reactors/consensus/consensus.md @@ -26,7 +26,7 @@ only to a subset of processes called peers. By the gossiping protocol, a validat all needed information (`ProposalMessage`, `VoteMessage` and `BlockPartMessage`) so they can reach agreement on some block, and also obtain the content of the chosen block (block parts). As part of the gossiping protocol, processes also send auxiliary messages that inform peers about the -executed steps of the core consensus algorithm (`NewRoundStepMessage` and `CommitStepMessage`), and +executed steps of the core consensus algorithm (`NewRoundStepMessage` and `NewValidBlockMessage`), and also messages that inform peers what votes the process has seen (`HasVoteMessage`, `VoteSetMaj23Message` and `VoteSetBitsMessage`). These messages are then used in the gossiping protocol to determine what messages a process should send to its peers. @@ -132,23 +132,26 @@ type NewRoundStepMessage struct { } ``` -## CommitStepMessage +## NewValidBlockMessage -CommitStepMessage is sent when an agreement on some block is reached. It contains height for which -agreement is reached, block parts header that describes the decided block and is used to obtain all +NewValidBlockMessage is sent when a validator observes a valid block B in some round r, +i.e., there is a Proposal for block B and 2/3+ prevotes for the block B in the round r. +It contains height and round in which valid block is observed, block parts header that describes +the valid block and is used to obtain all block parts, and a bit array of the block parts a process currently has, so its peers can know what parts it is missing so they can send them. +In case the block is also committed, then IsCommit flag is set to true. ```go -type CommitStepMessage struct { +type NewValidBlockMessage struct { Height int64 - BlockID BlockID + Round int + BlockPartsHeader PartSetHeader BlockParts BitArray + IsCommit bool } ``` -TODO: We use BlockID instead of BlockPartsHeader (in current implementation) for symmetry. - ## ProposalPOLMessage ProposalPOLMessage is sent when a previous block is re-proposed. From 1660e30ffe077773014ab11b3745b8b4b6933c8f Mon Sep 17 00:00:00 2001 From: Jae Kwon Date: Wed, 31 Oct 2018 08:02:13 -0700 Subject: [PATCH 047/267] Fix general merkle keypath to start w/ last op's key (#2733) * Fix general merkle keypath to start w/ last op's key * Update CHANGELOG_PENDING.md --- CHANGELOG_PENDING.md | 1 + crypto/merkle/proof.go | 9 +-- crypto/merkle/proof_test.go | 136 ++++++++++++++++++++++++++++++++++++ 3 files changed, 142 insertions(+), 4 deletions(-) create mode 100644 crypto/merkle/proof_test.go diff --git a/CHANGELOG_PENDING.md b/CHANGELOG_PENDING.md index f28b4608..2d970e40 100644 --- a/CHANGELOG_PENDING.md +++ b/CHANGELOG_PENDING.md @@ -109,6 +109,7 @@ Friendly reminder, we have a [bug bounty program](https://hackerone.com/tendermi - [consensus] [\#1637](https://github.com/tendermint/tendermint/issues/1637) Limit the amount of evidence that can be included in a block - [consensus] [\#2646](https://github.com/tendermint/tendermint/issues/2646) Simplify Proposal message (align with spec) +- [crypto] [\#2733](https://github.com/tendermint/tendermint/pull/2733) Fix general merkle keypath to start w/ last op's key - [evidence] [\#2515](https://github.com/tendermint/tendermint/issues/2515) Fix db iter leak (@goolAdapter) - [libs/event] [\#2518](https://github.com/tendermint/tendermint/issues/2518) Fix event concurrency flaw (@goolAdapter) - [node] [\#2434](https://github.com/tendermint/tendermint/issues/2434) Make node respond to signal interrupts while sleeping for genesis time diff --git a/crypto/merkle/proof.go b/crypto/merkle/proof.go index 3059ed3b..5705c96b 100644 --- a/crypto/merkle/proof.go +++ b/crypto/merkle/proof.go @@ -43,10 +43,11 @@ func (poz ProofOperators) Verify(root []byte, keypath string, args [][]byte) (er for i, op := range poz { key := op.GetKey() if len(key) != 0 { - if !bytes.Equal(keys[0], key) { - return cmn.NewError("Key mismatch on operation #%d: expected %+v but %+v", i, []byte(keys[0]), []byte(key)) + lastKey := keys[len(keys)-1] + if !bytes.Equal(lastKey, key) { + return cmn.NewError("Key mismatch on operation #%d: expected %+v but got %+v", i, string(lastKey), string(key)) } - keys = keys[1:] + keys = keys[:len(keys)-1] } args, err = op.Run(args) if err != nil { @@ -54,7 +55,7 @@ func (poz ProofOperators) Verify(root []byte, keypath string, args [][]byte) (er } } if !bytes.Equal(root, args[0]) { - return cmn.NewError("Calculated root hash is invalid: expected %+v but %+v", root, args[0]) + return cmn.NewError("Calculated root hash is invalid: expected %+v but got %+v", root, args[0]) } if len(keys) != 0 { return cmn.NewError("Keypath not consumed all") diff --git a/crypto/merkle/proof_test.go b/crypto/merkle/proof_test.go new file mode 100644 index 00000000..cc208e9a --- /dev/null +++ b/crypto/merkle/proof_test.go @@ -0,0 +1,136 @@ +package merkle + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/tendermint/go-amino" + cmn "github.com/tendermint/tendermint/libs/common" +) + +const ProofOpDomino = "test:domino" + +// Expects given input, produces given output. +// Like the game dominos. +type DominoOp struct { + key string // unexported, may be empty + Input string + Output string +} + +func NewDominoOp(key, input, output string) DominoOp { + return DominoOp{ + key: key, + Input: input, + Output: output, + } +} + +func DominoOpDecoder(pop ProofOp) (ProofOperator, error) { + if pop.Type != ProofOpDomino { + panic("unexpected proof op type") + } + var op DominoOp // a bit strange as we'll discard this, but it works. + err := amino.UnmarshalBinaryLengthPrefixed(pop.Data, &op) + if err != nil { + return nil, cmn.ErrorWrap(err, "decoding ProofOp.Data into SimpleValueOp") + } + return NewDominoOp(string(pop.Key), op.Input, op.Output), nil +} + +func (dop DominoOp) ProofOp() ProofOp { + bz := amino.MustMarshalBinaryLengthPrefixed(dop) + return ProofOp{ + Type: ProofOpDomino, + Key: []byte(dop.key), + Data: bz, + } +} + +func (dop DominoOp) Run(input [][]byte) (output [][]byte, err error) { + if len(input) != 1 { + return nil, cmn.NewError("Expected input of length 1") + } + if string(input[0]) != dop.Input { + return nil, cmn.NewError("Expected input %v, got %v", + dop.Input, string(input[0])) + } + return [][]byte{[]byte(dop.Output)}, nil +} + +func (dop DominoOp) GetKey() []byte { + return []byte(dop.key) +} + +//---------------------------------------- + +func TestProofOperators(t *testing.T) { + var err error + + // ProofRuntime setup + // TODO test this somehow. + // prt := NewProofRuntime() + // prt.RegisterOpDecoder(ProofOpDomino, DominoOpDecoder) + + // ProofOperators setup + op1 := NewDominoOp("KEY1", "INPUT1", "INPUT2") + op2 := NewDominoOp("KEY2", "INPUT2", "INPUT3") + op3 := NewDominoOp("", "INPUT3", "INPUT4") + op4 := NewDominoOp("KEY4", "INPUT4", "OUTPUT4") + + // Good + popz := ProofOperators([]ProofOperator{op1, op2, op3, op4}) + err = popz.Verify(bz("OUTPUT4"), "/KEY4/KEY2/KEY1", [][]byte{bz("INPUT1")}) + assert.Nil(t, err) + err = popz.VerifyValue(bz("OUTPUT4"), "/KEY4/KEY2/KEY1", bz("INPUT1")) + assert.Nil(t, err) + + // BAD INPUT + err = popz.Verify(bz("OUTPUT4"), "/KEY4/KEY2/KEY1", [][]byte{bz("INPUT1_WRONG")}) + assert.NotNil(t, err) + err = popz.VerifyValue(bz("OUTPUT4"), "/KEY4/KEY2/KEY1", bz("INPUT1_WRONG")) + assert.NotNil(t, err) + + // BAD KEY 1 + err = popz.Verify(bz("OUTPUT4"), "/KEY3/KEY2/KEY1", [][]byte{bz("INPUT1")}) + assert.NotNil(t, err) + + // BAD KEY 2 + err = popz.Verify(bz("OUTPUT4"), "KEY4/KEY2/KEY1", [][]byte{bz("INPUT1")}) + assert.NotNil(t, err) + + // BAD KEY 3 + err = popz.Verify(bz("OUTPUT4"), "/KEY4/KEY2/KEY1/", [][]byte{bz("INPUT1")}) + assert.NotNil(t, err) + + // BAD KEY 4 + err = popz.Verify(bz("OUTPUT4"), "//KEY4/KEY2/KEY1", [][]byte{bz("INPUT1")}) + assert.NotNil(t, err) + + // BAD OUTPUT 1 + err = popz.Verify(bz("OUTPUT4_WRONG"), "/KEY4/KEY2/KEY1", [][]byte{bz("INPUT1")}) + assert.NotNil(t, err) + + // BAD OUTPUT 2 + err = popz.Verify(bz(""), "/KEY4/KEY2/KEY1", [][]byte{bz("INPUT1")}) + assert.NotNil(t, err) + + // BAD POPZ 1 + popz = []ProofOperator{op1, op2, op4} + err = popz.Verify(bz("OUTPUT4"), "/KEY4/KEY2/KEY1", [][]byte{bz("INPUT1")}) + assert.NotNil(t, err) + + // BAD POPZ 2 + popz = []ProofOperator{op4, op3, op2, op1} + err = popz.Verify(bz("OUTPUT4"), "/KEY4/KEY2/KEY1", [][]byte{bz("INPUT1")}) + assert.NotNil(t, err) + + // BAD POPZ 3 + popz = []ProofOperator{} + err = popz.Verify(bz("OUTPUT4"), "/KEY4/KEY2/KEY1", [][]byte{bz("INPUT1")}) + assert.NotNil(t, err) +} + +func bz(s string) []byte { + return []byte(s) +} From a22c962e28053f9f51d71d3394e0c62bf551fb99 Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Wed, 31 Oct 2018 12:42:05 -0400 Subject: [PATCH 048/267] TMHASH is 32 bytes. Closes #1990 (#2732) * tmhash is fully 32 bytes. closes #1990 * AddressSize * fix tests * fix max sizes --- crypto/crypto.go | 21 +++++++++++++------ crypto/ed25519/ed25519.go | 2 +- crypto/merkle/simple_map_test.go | 12 +++++------ crypto/tmhash/hash.go | 33 ++++++++++++++++++++++-------- crypto/tmhash/hash_test.go | 23 +++++++++++++++++++-- docs/spec/blockchain/blockchain.md | 2 +- docs/spec/blockchain/encoding.md | 7 +++---- docs/spec/blockchain/state.md | 4 ++-- p2p/key.go | 3 +-- state/validation.go | 4 ++-- types/block.go | 2 +- types/block_test.go | 17 +++++++-------- types/evidence.go | 2 +- types/validator.go | 8 ++++---- types/vote.go | 2 +- types/vote_test.go | 5 +++-- 16 files changed, 96 insertions(+), 51 deletions(-) diff --git a/crypto/crypto.go b/crypto/crypto.go index 09c12ff7..2462b0a9 100644 --- a/crypto/crypto.go +++ b/crypto/crypto.go @@ -1,21 +1,23 @@ package crypto import ( + "github.com/tendermint/tendermint/crypto/tmhash" cmn "github.com/tendermint/tendermint/libs/common" ) -type PrivKey interface { - Bytes() []byte - Sign(msg []byte) ([]byte, error) - PubKey() PubKey - Equals(PrivKey) bool -} +const ( + AddressSize = tmhash.TruncatedSize +) // An address is a []byte, but hex-encoded even in JSON. // []byte leaves us the option to change the address length. // Use an alias so Unmarshal methods (with ptr receivers) are available too. type Address = cmn.HexBytes +func AddressHash(bz []byte) Address { + return Address(tmhash.SumTruncated(bz)) +} + type PubKey interface { Address() Address Bytes() []byte @@ -23,6 +25,13 @@ type PubKey interface { Equals(PubKey) bool } +type PrivKey interface { + Bytes() []byte + Sign(msg []byte) ([]byte, error) + PubKey() PubKey + Equals(PrivKey) bool +} + type Symmetric interface { Keygen() []byte Encrypt(plaintext []byte, secret []byte) (ciphertext []byte) diff --git a/crypto/ed25519/ed25519.go b/crypto/ed25519/ed25519.go index 61872d98..e077cbda 100644 --- a/crypto/ed25519/ed25519.go +++ b/crypto/ed25519/ed25519.go @@ -136,7 +136,7 @@ type PubKeyEd25519 [PubKeyEd25519Size]byte // Address is the SHA256-20 of the raw pubkey bytes. func (pubKey PubKeyEd25519) Address() crypto.Address { - return crypto.Address(tmhash.Sum(pubKey[:])) + return crypto.Address(tmhash.SumTruncated(pubKey[:])) } // Bytes marshals the PubKey using amino encoding. diff --git a/crypto/merkle/simple_map_test.go b/crypto/merkle/simple_map_test.go index bc095c00..7abde119 100644 --- a/crypto/merkle/simple_map_test.go +++ b/crypto/merkle/simple_map_test.go @@ -13,14 +13,14 @@ func TestSimpleMap(t *testing.T) { values []string // each string gets converted to []byte in test want string }{ - {[]string{"key1"}, []string{"value1"}, "fa9bc106ffd932d919bee935ceb6cf2b3dd72d8f"}, - {[]string{"key1"}, []string{"value2"}, "e00e7dcfe54e9fafef5111e813a587f01ba9c3e8"}, + {[]string{"key1"}, []string{"value1"}, "321d150de16dceb51c72981b432b115045383259b1a550adf8dc80f927508967"}, + {[]string{"key1"}, []string{"value2"}, "2a9e4baf321eac99f6eecc3406603c14bc5e85bb7b80483cbfc75b3382d24a2f"}, // swap order with 2 keys - {[]string{"key1", "key2"}, []string{"value1", "value2"}, "eff12d1c703a1022ab509287c0f196130123d786"}, - {[]string{"key2", "key1"}, []string{"value2", "value1"}, "eff12d1c703a1022ab509287c0f196130123d786"}, + {[]string{"key1", "key2"}, []string{"value1", "value2"}, "c4d8913ab543ba26aa970646d4c99a150fd641298e3367cf68ca45fb45a49881"}, + {[]string{"key2", "key1"}, []string{"value2", "value1"}, "c4d8913ab543ba26aa970646d4c99a150fd641298e3367cf68ca45fb45a49881"}, // swap order with 3 keys - {[]string{"key1", "key2", "key3"}, []string{"value1", "value2", "value3"}, "b2c62a277c08dbd2ad73ca53cd1d6bfdf5830d26"}, - {[]string{"key1", "key3", "key2"}, []string{"value1", "value3", "value2"}, "b2c62a277c08dbd2ad73ca53cd1d6bfdf5830d26"}, + {[]string{"key1", "key2", "key3"}, []string{"value1", "value2", "value3"}, "b23cef00eda5af4548a213a43793f2752d8d9013b3f2b64bc0523a4791196268"}, + {[]string{"key1", "key3", "key2"}, []string{"value1", "value3", "value2"}, "b23cef00eda5af4548a213a43793f2752d8d9013b3f2b64bc0523a4791196268"}, } for i, tc := range tests { db := newSimpleMap() diff --git a/crypto/tmhash/hash.go b/crypto/tmhash/hash.go index 1b29d868..f9b95824 100644 --- a/crypto/tmhash/hash.go +++ b/crypto/tmhash/hash.go @@ -6,10 +6,27 @@ import ( ) const ( - Size = 20 + Size = sha256.Size BlockSize = sha256.BlockSize ) +// New returns a new hash.Hash. +func New() hash.Hash { + return sha256.New() +} + +// Sum returns the SHA256 of the bz. +func Sum(bz []byte) []byte { + h := sha256.Sum256(bz) + return h[:] +} + +//------------------------------------------------------------- + +const ( + TruncatedSize = 20 +) + type sha256trunc struct { sha256 hash.Hash } @@ -19,7 +36,7 @@ func (h sha256trunc) Write(p []byte) (n int, err error) { } func (h sha256trunc) Sum(b []byte) []byte { shasum := h.sha256.Sum(b) - return shasum[:Size] + return shasum[:TruncatedSize] } func (h sha256trunc) Reset() { @@ -27,22 +44,22 @@ func (h sha256trunc) Reset() { } func (h sha256trunc) Size() int { - return Size + return TruncatedSize } func (h sha256trunc) BlockSize() int { return h.sha256.BlockSize() } -// New returns a new hash.Hash. -func New() hash.Hash { +// NewTruncated returns a new hash.Hash. +func NewTruncated() hash.Hash { return sha256trunc{ sha256: sha256.New(), } } -// Sum returns the first 20 bytes of SHA256 of the bz. -func Sum(bz []byte) []byte { +// SumTruncated returns the first 20 bytes of SHA256 of the bz. +func SumTruncated(bz []byte) []byte { hash := sha256.Sum256(bz) - return hash[:Size] + return hash[:TruncatedSize] } diff --git a/crypto/tmhash/hash_test.go b/crypto/tmhash/hash_test.go index 27938039..89a77980 100644 --- a/crypto/tmhash/hash_test.go +++ b/crypto/tmhash/hash_test.go @@ -14,10 +14,29 @@ func TestHash(t *testing.T) { hasher.Write(testVector) bz := hasher.Sum(nil) + bz2 := tmhash.Sum(testVector) + hasher = sha256.New() hasher.Write(testVector) - bz2 := hasher.Sum(nil) - bz2 = bz2[:20] + bz3 := hasher.Sum(nil) assert.Equal(t, bz, bz2) + assert.Equal(t, bz, bz3) +} + +func TestHashTruncated(t *testing.T) { + testVector := []byte("abc") + hasher := tmhash.NewTruncated() + hasher.Write(testVector) + bz := hasher.Sum(nil) + + bz2 := tmhash.SumTruncated(testVector) + + hasher = sha256.New() + hasher.Write(testVector) + bz3 := hasher.Sum(nil) + bz3 = bz3[:tmhash.TruncatedSize] + + assert.Equal(t, bz, bz2) + assert.Equal(t, bz, bz3) } diff --git a/docs/spec/blockchain/blockchain.md b/docs/spec/blockchain/blockchain.md index 06168537..d96a3c7b 100644 --- a/docs/spec/blockchain/blockchain.md +++ b/docs/spec/blockchain/blockchain.md @@ -344,7 +344,7 @@ next validator sets Merkle root. ### ConsensusParamsHash ```go -block.ConsensusParamsHash == tmhash(amino(state.ConsensusParams)) +block.ConsensusParamsHash == TMHASH(amino(state.ConsensusParams)) ``` Hash of the amino-encoded consensus parameters. diff --git a/docs/spec/blockchain/encoding.md b/docs/spec/blockchain/encoding.md index 2f9fcdca..f5120cdd 100644 --- a/docs/spec/blockchain/encoding.md +++ b/docs/spec/blockchain/encoding.md @@ -176,13 +176,12 @@ greater, for example: h0 h1 h3 h4 h0 h1 h2 h3 h4 h5 ``` -Tendermint always uses the `TMHASH` hash function, which is the first 20-bytes -of the SHA256: +Tendermint always uses the `TMHASH` hash function, which is equivalent to +SHA256: ``` func TMHASH(bz []byte) []byte { - shasum := SHA256(bz) - return shasum[:20] + return SHA256(bz) } ``` diff --git a/docs/spec/blockchain/state.md b/docs/spec/blockchain/state.md index a0badd71..502f9d69 100644 --- a/docs/spec/blockchain/state.md +++ b/docs/spec/blockchain/state.md @@ -56,8 +56,8 @@ type Validator struct { } ``` -When hashing the Validator struct, the pubkey is not hashed, -because the address is already the hash of the pubkey. +When hashing the Validator struct, the address is not included, +because it is redundant with the pubkey. The `state.Validators`, `state.LastValidators`, and `state.NextValidators`, must always by sorted by validator address, so that there is a canonical order for computing the SimpleMerkleRoot. diff --git a/p2p/key.go b/p2p/key.go index 3f38b48a..fc64f27b 100644 --- a/p2p/key.go +++ b/p2p/key.go @@ -8,7 +8,6 @@ import ( crypto "github.com/tendermint/tendermint/crypto" "github.com/tendermint/tendermint/crypto/ed25519" - "github.com/tendermint/tendermint/crypto/tmhash" cmn "github.com/tendermint/tendermint/libs/common" ) @@ -17,7 +16,7 @@ type ID string // IDByteLength is the length of a crypto.Address. Currently only 20. // TODO: support other length addresses ? -const IDByteLength = tmhash.Size +const IDByteLength = crypto.AddressSize //------------------------------------------------------------------------------ // Persistent peer ID diff --git a/state/validation.go b/state/validation.go index a1291984..34522484 100644 --- a/state/validation.go +++ b/state/validation.go @@ -5,7 +5,7 @@ import ( "errors" "fmt" - "github.com/tendermint/tendermint/crypto/tmhash" + "github.com/tendermint/tendermint/crypto" dbm "github.com/tendermint/tendermint/libs/db" "github.com/tendermint/tendermint/types" ) @@ -158,7 +158,7 @@ func validateBlock(stateDB dbm.DB, state State, block *types.Block) error { // NOTE: We can't actually verify it's the right proposer because we dont // know what round the block was first proposed. So just check that it's // a legit address and a known validator. - if len(block.ProposerAddress) != tmhash.Size || + if len(block.ProposerAddress) != crypto.AddressSize || !state.Validators.HasAddress(block.ProposerAddress) { return fmt.Errorf( "Block.Header.ProposerAddress, %X, is not a validator", diff --git a/types/block.go b/types/block.go index 477e3999..46ad73a7 100644 --- a/types/block.go +++ b/types/block.go @@ -15,7 +15,7 @@ import ( const ( // MaxHeaderBytes is a maximum header size (including amino overhead). - MaxHeaderBytes int64 = 533 + MaxHeaderBytes int64 = 653 // MaxAminoOverheadForBlock - maximum amino overhead to encode a block (up to // MaxBlockSizeBytes in size) not including it's parts except Data. diff --git a/types/block_test.go b/types/block_test.go index 28e73f66..46881a09 100644 --- a/types/block_test.go +++ b/types/block_test.go @@ -10,6 +10,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/tendermint/tendermint/crypto" "github.com/tendermint/tendermint/crypto/tmhash" cmn "github.com/tendermint/tendermint/libs/common" "github.com/tendermint/tendermint/version" @@ -116,7 +117,7 @@ func TestBlockMakePartSetWithEvidence(t *testing.T) { partSet := MakeBlock(h, []Tx{Tx("Hello World")}, commit, evList).MakePartSet(1024) assert.NotNil(t, partSet) - assert.Equal(t, 2, partSet.Total()) + assert.Equal(t, 3, partSet.Total()) } func TestBlockHashesTo(t *testing.T) { @@ -262,7 +263,7 @@ func TestMaxHeaderBytes(t *testing.T) { AppHash: tmhash.Sum([]byte("app_hash")), LastResultsHash: tmhash.Sum([]byte("last_results_hash")), EvidenceHash: tmhash.Sum([]byte("evidence_hash")), - ProposerAddress: tmhash.Sum([]byte("proposer_address")), + ProposerAddress: crypto.AddressHash([]byte("proposer_address")), } bz, err := cdc.MarshalBinaryLengthPrefixed(h) @@ -292,9 +293,9 @@ func TestBlockMaxDataBytes(t *testing.T) { }{ 0: {-10, 1, 0, true, 0}, 1: {10, 1, 0, true, 0}, - 2: {742, 1, 0, true, 0}, - 3: {743, 1, 0, false, 0}, - 4: {744, 1, 0, false, 1}, + 2: {886, 1, 0, true, 0}, + 3: {887, 1, 0, false, 0}, + 4: {888, 1, 0, false, 1}, } for i, tc := range testCases { @@ -320,9 +321,9 @@ func TestBlockMaxDataBytesUnknownEvidence(t *testing.T) { }{ 0: {-10, 1, true, 0}, 1: {10, 1, true, 0}, - 2: {824, 1, true, 0}, - 3: {825, 1, false, 0}, - 4: {826, 1, false, 1}, + 2: {984, 1, true, 0}, + 3: {985, 1, false, 0}, + 4: {986, 1, false, 1}, } for i, tc := range testCases { diff --git a/types/evidence.go b/types/evidence.go index 7a808d57..d1e15c81 100644 --- a/types/evidence.go +++ b/types/evidence.go @@ -14,7 +14,7 @@ import ( const ( // MaxEvidenceBytes is a maximum size of any evidence (including amino overhead). - MaxEvidenceBytes int64 = 436 + MaxEvidenceBytes int64 = 484 ) // ErrEvidenceInvalid wraps a piece of evidence and the error denoting how or why it is invalid. diff --git a/types/validator.go b/types/validator.go index af347184..4bfd78a6 100644 --- a/types/validator.go +++ b/types/validator.go @@ -77,15 +77,15 @@ func (v *Validator) Hash() []byte { } // Bytes computes the unique encoding of a validator with a given voting power. -// These are the bytes that gets hashed in consensus. It excludes pubkey -// as its redundant with the address. This also excludes accum which changes +// These are the bytes that gets hashed in consensus. It excludes address +// as its redundant with the pubkey. This also excludes accum which changes // every round. func (v *Validator) Bytes() []byte { return cdcEncode((struct { - Address Address + PubKey crypto.PubKey VotingPower int64 }{ - v.Address, + v.PubKey, v.VotingPower, })) } diff --git a/types/vote.go b/types/vote.go index 826330d5..1d7e9cf6 100644 --- a/types/vote.go +++ b/types/vote.go @@ -12,7 +12,7 @@ import ( const ( // MaxVoteBytes is a maximum vote size (including amino overhead). - MaxVoteBytes int64 = 199 + MaxVoteBytes int64 = 223 ) var ( diff --git a/types/vote_test.go b/types/vote_test.go index 57273585..cda54f89 100644 --- a/types/vote_test.go +++ b/types/vote_test.go @@ -7,6 +7,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/tendermint/tendermint/crypto" "github.com/tendermint/tendermint/crypto/ed25519" "github.com/tendermint/tendermint/crypto/tmhash" ) @@ -37,7 +38,7 @@ func exampleVote(t byte) *Vote { Hash: tmhash.Sum([]byte("blockID_part_set_header_hash")), }, }, - ValidatorAddress: tmhash.Sum([]byte("validator_address")), + ValidatorAddress: crypto.AddressHash([]byte("validator_address")), ValidatorIndex: 56789, } } @@ -211,7 +212,7 @@ func TestMaxVoteBytes(t *testing.T) { timestamp := time.Date(math.MaxInt64, 0, 0, 0, 0, 0, math.MaxInt64, time.UTC) vote := &Vote{ - ValidatorAddress: tmhash.Sum([]byte("validator_address")), + ValidatorAddress: crypto.AddressHash([]byte("validator_address")), ValidatorIndex: math.MaxInt64, Height: math.MaxInt64, Round: math.MaxInt64, From fb91ef7462b421349a56c32733724d920fce3ad4 Mon Sep 17 00:00:00 2001 From: Anton Kaliaev Date: Thu, 1 Nov 2018 07:07:18 +0100 Subject: [PATCH 049/267] validate reactor messages (#2711) * validate reactor messages Refs #2683 * validate blockchain messages Refs #2683 * validate evidence messages Refs #2683 * todo * check ProposalPOL and signature sizes * add a changelog entry * check addr is valid when we add it to the addrbook * validate incoming netAddr (not just nil check!) * fixes after Bucky's review * check timestamps * beef up block#ValidateBasic * move some checks into bcBlockResponseMessage * update Gopkg.lock Fix ``` grouped write of manifest, lock and vendor: failed to export github.com/tendermint/go-amino: fatal: failed to unpack tree object 6dcc6ddc143e116455c94b25c1004c99e0d0ca12 ``` by running `dep ensure -update` * bump year since now we check it * generate test/p2p/data on the fly using tendermint testnet * allow sync chains older than 1 year * use full path when creating a testnet * move testnet gen to test/docker/Dockerfile * relax LastCommitRound check Refs #2737 * fix conflicts after merge * add small comment * some ValidateBasic updates * fixes * AppHash length is not fixed --- CHANGELOG_PENDING.md | 4 + blockchain/reactor.go | 53 +++++- config/toml.go | 2 +- consensus/common_test.go | 2 - consensus/mempool_test.go | 1 - consensus/reactor.go | 161 ++++++++++++++++-- consensus/replay.go | 8 +- consensus/types/round_state.go | 7 + crypto/crypto.go | 1 + evidence/reactor.go | 23 ++- p2p/pex/addrbook.go | 4 + p2p/pex/errors.go | 8 + p2p/pex/pex_reactor.go | 28 ++- state/validation.go | 50 +++--- test/docker/Dockerfile | 11 +- test/p2p/README.md | 9 +- test/p2p/data/mach1/core/config/genesis.json | 39 ----- test/p2p/data/mach1/core/config/node_key.json | 6 - .../mach1/core/config/priv_validator.json | 14 -- test/p2p/data/mach2/core/config/genesis.json | 39 ----- test/p2p/data/mach2/core/config/node_key.json | 6 - .../mach2/core/config/priv_validator.json | 14 -- test/p2p/data/mach3/core/config/genesis.json | 39 ----- test/p2p/data/mach3/core/config/node_key.json | 6 - .../mach3/core/config/priv_validator.json | 14 -- test/p2p/data/mach4/core/config/genesis.json | 39 ----- test/p2p/data/mach4/core/config/node_key.json | 6 - .../mach4/core/config/priv_validator.json | 14 -- test/p2p/ip_plus_id.sh | 2 +- test/p2p/peer.sh | 6 +- test/p2p/pex/test_addrbook.sh | 6 +- types/block.go | 117 ++++++++++--- types/block_test.go | 6 +- types/evidence.go | 21 +++ types/heartbeat.go | 31 ++++ types/part_set.go | 26 ++- types/proposal.go | 29 ++++ types/signable.go | 12 ++ types/signed_msg_type.go | 9 +- types/validation.go | 40 +++++ types/vote.go | 38 ++++- 41 files changed, 614 insertions(+), 337 deletions(-) delete mode 100644 test/p2p/data/mach1/core/config/genesis.json delete mode 100644 test/p2p/data/mach1/core/config/node_key.json delete mode 100644 test/p2p/data/mach1/core/config/priv_validator.json delete mode 100644 test/p2p/data/mach2/core/config/genesis.json delete mode 100644 test/p2p/data/mach2/core/config/node_key.json delete mode 100644 test/p2p/data/mach2/core/config/priv_validator.json delete mode 100644 test/p2p/data/mach3/core/config/genesis.json delete mode 100644 test/p2p/data/mach3/core/config/node_key.json delete mode 100644 test/p2p/data/mach3/core/config/priv_validator.json delete mode 100644 test/p2p/data/mach4/core/config/genesis.json delete mode 100644 test/p2p/data/mach4/core/config/node_key.json delete mode 100644 test/p2p/data/mach4/core/config/priv_validator.json create mode 100644 types/validation.go diff --git a/CHANGELOG_PENDING.md b/CHANGELOG_PENDING.md index 2d970e40..cad2f444 100644 --- a/CHANGELOG_PENDING.md +++ b/CHANGELOG_PENDING.md @@ -92,6 +92,10 @@ Friendly reminder, we have a [bug bounty program](https://hackerone.com/tendermi github.com/tendermint/crypto - [tools] [\#2238](https://github.com/tendermint/tendermint/issues/2238) Binary dependencies are now locked to a specific git commit - [libs/log] [\#2706](https://github.com/tendermint/tendermint/issues/2706) Add year to log format +- [consensus] [\#2683] validate all incoming messages +- [evidence] [\#2683] validate all incoming messages +- [blockchain] [\#2683] validate all incoming messages +- [p2p/pex] [\#2683] validate pexAddrsMessage addresses ### BUG FIXES: - [autofile] [\#2428](https://github.com/tendermint/tendermint/issues/2428) Group.RotateFile need call Flush() before rename (@goolAdapter) diff --git a/blockchain/reactor.go b/blockchain/reactor.go index fc1b1f4d..59318dcc 100644 --- a/blockchain/reactor.go +++ b/blockchain/reactor.go @@ -1,6 +1,7 @@ package blockchain import ( + "errors" "fmt" "reflect" "time" @@ -180,6 +181,12 @@ func (bcR *BlockchainReactor) Receive(chID byte, src p2p.Peer, msgBytes []byte) return } + if err = msg.ValidateBasic(); err != nil { + bcR.Logger.Error("Peer sent us invalid msg", "peer", src, "msg", msg, "err", err) + bcR.Switch.StopPeerForError(src, err) + return + } + bcR.Logger.Debug("Receive", "src", src, "chID", chID, "msg", msg) switch msg := msg.(type) { @@ -188,7 +195,6 @@ func (bcR *BlockchainReactor) Receive(chID byte, src p2p.Peer, msgBytes []byte) // Unfortunately not queued since the queue is full. } case *bcBlockResponseMessage: - // Got a block. bcR.pool.AddBlock(src.ID(), msg.Block, len(msgBytes)) case *bcStatusRequestMessage: // Send peer our state. @@ -352,7 +358,9 @@ func (bcR *BlockchainReactor) BroadcastStatusRequest() error { // Messages // BlockchainMessage is a generic message for this reactor. -type BlockchainMessage interface{} +type BlockchainMessage interface { + ValidateBasic() error +} func RegisterBlockchainMessages(cdc *amino.Codec) { cdc.RegisterInterface((*BlockchainMessage)(nil), nil) @@ -377,6 +385,14 @@ type bcBlockRequestMessage struct { Height int64 } +// ValidateBasic performs basic validation. +func (m *bcBlockRequestMessage) ValidateBasic() error { + if m.Height < 0 { + return errors.New("Negative Height") + } + return nil +} + func (m *bcBlockRequestMessage) String() string { return fmt.Sprintf("[bcBlockRequestMessage %v]", m.Height) } @@ -385,6 +401,14 @@ type bcNoBlockResponseMessage struct { Height int64 } +// ValidateBasic performs basic validation. +func (m *bcNoBlockResponseMessage) ValidateBasic() error { + if m.Height < 0 { + return errors.New("Negative Height") + } + return nil +} + func (brm *bcNoBlockResponseMessage) String() string { return fmt.Sprintf("[bcNoBlockResponseMessage %d]", brm.Height) } @@ -395,6 +419,15 @@ type bcBlockResponseMessage struct { Block *types.Block } +// ValidateBasic performs basic validation. +func (m *bcBlockResponseMessage) ValidateBasic() error { + if err := m.Block.ValidateBasic(); err != nil { + return err + } + + return nil +} + func (m *bcBlockResponseMessage) String() string { return fmt.Sprintf("[bcBlockResponseMessage %v]", m.Block.Height) } @@ -405,6 +438,14 @@ type bcStatusRequestMessage struct { Height int64 } +// ValidateBasic performs basic validation. +func (m *bcStatusRequestMessage) ValidateBasic() error { + if m.Height < 0 { + return errors.New("Negative Height") + } + return nil +} + func (m *bcStatusRequestMessage) String() string { return fmt.Sprintf("[bcStatusRequestMessage %v]", m.Height) } @@ -415,6 +456,14 @@ type bcStatusResponseMessage struct { Height int64 } +// ValidateBasic performs basic validation. +func (m *bcStatusResponseMessage) ValidateBasic() error { + if m.Height < 0 { + return errors.New("Negative Height") + } + return nil +} + func (m *bcStatusResponseMessage) String() string { return fmt.Sprintf("[bcStatusResponseMessage %v]", m.Height) } diff --git a/config/toml.go b/config/toml.go index 62e5fa97..d73b9c81 100644 --- a/config/toml.go +++ b/config/toml.go @@ -342,7 +342,7 @@ func ResetTestRoot(testName string) *Config { } var testGenesis = `{ - "genesis_time": "2017-10-10T08:20:13.695936996Z", + "genesis_time": "2018-10-10T08:20:13.695936996Z", "chain_id": "tendermint_test", "validators": [ { diff --git a/consensus/common_test.go b/consensus/common_test.go index ae8bb6bf..4f48f442 100644 --- a/consensus/common_test.go +++ b/consensus/common_test.go @@ -615,8 +615,6 @@ func randGenesisDoc(numValidators int, randPower bool, minPower int64) (*types.G func randGenesisState(numValidators int, randPower bool, minPower int64) (sm.State, []types.PrivValidator) { genDoc, privValidators := randGenesisDoc(numValidators, randPower, minPower) s0, _ := sm.MakeGenesisState(genDoc) - db := dbm.NewMemDB() // remove this ? - sm.SaveState(db, s0) return s0, privValidators } diff --git a/consensus/mempool_test.go b/consensus/mempool_test.go index ed97ae68..3dc1cd5f 100644 --- a/consensus/mempool_test.go +++ b/consensus/mempool_test.go @@ -10,7 +10,6 @@ import ( "github.com/tendermint/tendermint/abci/example/code" abci "github.com/tendermint/tendermint/abci/types" - "github.com/tendermint/tendermint/types" ) diff --git a/consensus/reactor.go b/consensus/reactor.go index bf6f7ba7..fc41e573 100644 --- a/consensus/reactor.go +++ b/consensus/reactor.go @@ -8,8 +8,7 @@ import ( "github.com/pkg/errors" - "github.com/tendermint/go-amino" - + amino "github.com/tendermint/go-amino" cstypes "github.com/tendermint/tendermint/consensus/types" cmn "github.com/tendermint/tendermint/libs/common" tmevents "github.com/tendermint/tendermint/libs/events" @@ -205,6 +204,13 @@ func (conR *ConsensusReactor) Receive(chID byte, src p2p.Peer, msgBytes []byte) conR.Switch.StopPeerForError(src, err) return } + + if err = msg.ValidateBasic(); err != nil { + conR.Logger.Error("Peer sent us invalid msg", "peer", src, "msg", msg, "err", err) + conR.Switch.StopPeerForError(src, err) + return + } + conR.Logger.Debug("Receive", "src", src, "chId", chID, "msg", msg) // Get peer states @@ -242,8 +248,7 @@ func (conR *ConsensusReactor) Receive(chID byte, src p2p.Peer, msgBytes []byte) case types.PrecommitType: ourVotes = votes.Precommits(msg.Round).BitArrayByBlockID(msg.BlockID) default: - conR.Logger.Error("Bad VoteSetBitsMessage field Type") - return + panic("Bad VoteSetBitsMessage field Type. Forgot to add a check in ValidateBasic?") } src.TrySend(VoteSetBitsChannel, cdc.MustMarshalBinaryBare(&VoteSetBitsMessage{ Height: msg.Height, @@ -322,8 +327,7 @@ func (conR *ConsensusReactor) Receive(chID byte, src p2p.Peer, msgBytes []byte) case types.PrecommitType: ourVotes = votes.Precommits(msg.Round).BitArrayByBlockID(msg.BlockID) default: - conR.Logger.Error("Bad VoteSetBitsMessage field Type") - return + panic("Bad VoteSetBitsMessage field Type. Forgot to add a check in ValidateBasic?") } ps.ApplyVoteSetBitsMessage(msg, ourVotes) } else { @@ -440,9 +444,9 @@ func (conR *ConsensusReactor) broadcastHasVoteMessage(vote *types.Vote) { func makeRoundStepMessage(rs *cstypes.RoundState) (nrsMsg *NewRoundStepMessage) { nrsMsg = &NewRoundStepMessage{ - Height: rs.Height, - Round: rs.Round, - Step: rs.Step, + Height: rs.Height, + Round: rs.Round, + Step: rs.Step, SecondsSinceStartTime: int(time.Since(rs.StartTime).Seconds()), LastCommitRound: rs.LastCommit.Round(), } @@ -1349,7 +1353,9 @@ func (ps *PeerState) StringIndented(indent string) string { // Messages // ConsensusMessage is a message that can be sent and received on the ConsensusReactor -type ConsensusMessage interface{} +type ConsensusMessage interface { + ValidateBasic() error +} func RegisterConsensusMessages(cdc *amino.Codec) { cdc.RegisterInterface((*ConsensusMessage)(nil), nil) @@ -1385,6 +1391,27 @@ type NewRoundStepMessage struct { LastCommitRound int } +// ValidateBasic performs basic validation. +func (m *NewRoundStepMessage) ValidateBasic() error { + if m.Height < 0 { + return errors.New("Negative Height") + } + if m.Round < 0 { + return errors.New("Negative Round") + } + if !m.Step.IsValid() { + return errors.New("Invalid Step") + } + + // NOTE: SecondsSinceStartTime may be negative + + if (m.Height == 1 && m.LastCommitRound != -1) || + (m.Height > 1 && m.LastCommitRound < -1) { // TODO: #2737 LastCommitRound should always be >= 0 for heights > 1 + return errors.New("Invalid LastCommitRound (for 1st block: -1, for others: >= 0)") + } + return nil +} + // String returns a string representation. func (m *NewRoundStepMessage) String() string { return fmt.Sprintf("[NewRoundStep H:%v R:%v S:%v LCR:%v]", @@ -1404,6 +1431,25 @@ type NewValidBlockMessage struct { IsCommit bool } +// ValidateBasic performs basic validation. +func (m *NewValidBlockMessage) ValidateBasic() error { + if m.Height < 0 { + return errors.New("Negative Height") + } + if m.Round < 0 { + return errors.New("Negative Round") + } + if err := m.BlockPartsHeader.ValidateBasic(); err != nil { + return fmt.Errorf("Wrong BlockPartsHeader: %v", err) + } + if m.BlockParts.Size() != m.BlockPartsHeader.Total { + return fmt.Errorf("BlockParts bit array size %d not equal to BlockPartsHeader.Total %d", + m.BlockParts.Size(), + m.BlockPartsHeader.Total) + } + return nil +} + // String returns a string representation. func (m *NewValidBlockMessage) String() string { return fmt.Sprintf("[ValidBlockMessage H:%v R:%v BP:%v BA:%v IsCommit:%v]", @@ -1417,6 +1463,11 @@ type ProposalMessage struct { Proposal *types.Proposal } +// ValidateBasic performs basic validation. +func (m *ProposalMessage) ValidateBasic() error { + return m.Proposal.ValidateBasic() +} + // String returns a string representation. func (m *ProposalMessage) String() string { return fmt.Sprintf("[Proposal %v]", m.Proposal) @@ -1431,6 +1482,20 @@ type ProposalPOLMessage struct { ProposalPOL *cmn.BitArray } +// ValidateBasic performs basic validation. +func (m *ProposalPOLMessage) ValidateBasic() error { + if m.Height < 0 { + return errors.New("Negative Height") + } + if m.ProposalPOLRound < 0 { + return errors.New("Negative ProposalPOLRound") + } + if m.ProposalPOL.Size() == 0 { + return errors.New("Empty ProposalPOL bit array") + } + return nil +} + // String returns a string representation. func (m *ProposalPOLMessage) String() string { return fmt.Sprintf("[ProposalPOL H:%v POLR:%v POL:%v]", m.Height, m.ProposalPOLRound, m.ProposalPOL) @@ -1445,6 +1510,20 @@ type BlockPartMessage struct { Part *types.Part } +// ValidateBasic performs basic validation. +func (m *BlockPartMessage) ValidateBasic() error { + if m.Height < 0 { + return errors.New("Negative Height") + } + if m.Round < 0 { + return errors.New("Negative Round") + } + if err := m.Part.ValidateBasic(); err != nil { + return fmt.Errorf("Wrong Part: %v", err) + } + return nil +} + // String returns a string representation. func (m *BlockPartMessage) String() string { return fmt.Sprintf("[BlockPart H:%v R:%v P:%v]", m.Height, m.Round, m.Part) @@ -1457,6 +1536,11 @@ type VoteMessage struct { Vote *types.Vote } +// ValidateBasic performs basic validation. +func (m *VoteMessage) ValidateBasic() error { + return m.Vote.ValidateBasic() +} + // String returns a string representation. func (m *VoteMessage) String() string { return fmt.Sprintf("[Vote %v]", m.Vote) @@ -1472,6 +1556,23 @@ type HasVoteMessage struct { Index int } +// ValidateBasic performs basic validation. +func (m *HasVoteMessage) ValidateBasic() error { + if m.Height < 0 { + return errors.New("Negative Height") + } + if m.Round < 0 { + return errors.New("Negative Round") + } + if !types.IsVoteTypeValid(m.Type) { + return errors.New("Invalid Type") + } + if m.Index < 0 { + return errors.New("Negative Index") + } + return nil +} + // String returns a string representation. func (m *HasVoteMessage) String() string { return fmt.Sprintf("[HasVote VI:%v V:{%v/%02d/%v}]", m.Index, m.Height, m.Round, m.Type) @@ -1487,6 +1588,23 @@ type VoteSetMaj23Message struct { BlockID types.BlockID } +// ValidateBasic performs basic validation. +func (m *VoteSetMaj23Message) ValidateBasic() error { + if m.Height < 0 { + return errors.New("Negative Height") + } + if m.Round < 0 { + return errors.New("Negative Round") + } + if !types.IsVoteTypeValid(m.Type) { + return errors.New("Invalid Type") + } + if err := m.BlockID.ValidateBasic(); err != nil { + return fmt.Errorf("Wrong BlockID: %v", err) + } + return nil +} + // String returns a string representation. func (m *VoteSetMaj23Message) String() string { return fmt.Sprintf("[VSM23 %v/%02d/%v %v]", m.Height, m.Round, m.Type, m.BlockID) @@ -1503,6 +1621,24 @@ type VoteSetBitsMessage struct { Votes *cmn.BitArray } +// ValidateBasic performs basic validation. +func (m *VoteSetBitsMessage) ValidateBasic() error { + if m.Height < 0 { + return errors.New("Negative Height") + } + if m.Round < 0 { + return errors.New("Negative Round") + } + if !types.IsVoteTypeValid(m.Type) { + return errors.New("Invalid Type") + } + if err := m.BlockID.ValidateBasic(); err != nil { + return fmt.Errorf("Wrong BlockID: %v", err) + } + // NOTE: Votes.Size() can be zero if the node does not have any + return nil +} + // String returns a string representation. func (m *VoteSetBitsMessage) String() string { return fmt.Sprintf("[VSB %v/%02d/%v %v %v]", m.Height, m.Round, m.Type, m.BlockID, m.Votes) @@ -1515,6 +1651,11 @@ type ProposalHeartbeatMessage struct { Heartbeat *types.Heartbeat } +// ValidateBasic performs basic validation. +func (m *ProposalHeartbeatMessage) ValidateBasic() error { + return m.Heartbeat.ValidateBasic() +} + // String returns a string representation. func (m *ProposalHeartbeatMessage) String() string { return fmt.Sprintf("[HEARTBEAT %v]", m.Heartbeat) diff --git a/consensus/replay.go b/consensus/replay.go index fcff877f..abc43eb5 100644 --- a/consensus/replay.go +++ b/consensus/replay.go @@ -264,8 +264,12 @@ func (h *Handshaker) Handshake(proxyApp proxy.AppConns) error { // Replay all blocks since appBlockHeight and ensure the result matches the current state. // Returns the final AppHash or an error. -func (h *Handshaker) ReplayBlocks(state sm.State, appHash []byte, appBlockHeight int64, proxyApp proxy.AppConns) ([]byte, error) { - +func (h *Handshaker) ReplayBlocks( + state sm.State, + appHash []byte, + appBlockHeight int64, + proxyApp proxy.AppConns, +) ([]byte, error) { storeBlockHeight := h.store.Height() stateBlockHeight := state.LastBlockHeight h.logger.Info("ABCI Replay Blocks", "appHeight", appBlockHeight, "storeHeight", storeBlockHeight, "stateHeight", stateBlockHeight) diff --git a/consensus/types/round_state.go b/consensus/types/round_state.go index d3f6468b..ef423611 100644 --- a/consensus/types/round_state.go +++ b/consensus/types/round_state.go @@ -26,8 +26,15 @@ const ( RoundStepPrecommitWait = RoundStepType(0x07) // Did receive any +2/3 precommits, start timeout RoundStepCommit = RoundStepType(0x08) // Entered commit state machine // NOTE: RoundStepNewHeight acts as RoundStepCommitWait. + + // NOTE: Update IsValid method if you change this! ) +// IsValid returns true if the step is valid, false if unknown/undefined. +func (rs RoundStepType) IsValid() bool { + return uint8(rs) >= 0x01 && uint8(rs) <= 0x08 +} + // String returns a string func (rs RoundStepType) String() string { switch rs { diff --git a/crypto/crypto.go b/crypto/crypto.go index 2462b0a9..b3526f88 100644 --- a/crypto/crypto.go +++ b/crypto/crypto.go @@ -6,6 +6,7 @@ import ( ) const ( + // AddressSize is the size of a pubkey address. AddressSize = tmhash.TruncatedSize ) diff --git a/evidence/reactor.go b/evidence/reactor.go index 52eb4a56..32753b2b 100644 --- a/evidence/reactor.go +++ b/evidence/reactor.go @@ -74,6 +74,13 @@ func (evR *EvidenceReactor) Receive(chID byte, src p2p.Peer, msgBytes []byte) { evR.Switch.StopPeerForError(src, err) return } + + if err = msg.ValidateBasic(); err != nil { + evR.Logger.Error("Peer sent us invalid msg", "peer", src, "msg", msg, "err", err) + evR.Switch.StopPeerForError(src, err) + return + } + evR.Logger.Debug("Receive", "src", src, "chId", chID, "msg", msg) switch msg := msg.(type) { @@ -191,7 +198,9 @@ type PeerState interface { // Messages // EvidenceMessage is a message sent or received by the EvidenceReactor. -type EvidenceMessage interface{} +type EvidenceMessage interface { + ValidateBasic() error +} func RegisterEvidenceMessages(cdc *amino.Codec) { cdc.RegisterInterface((*EvidenceMessage)(nil), nil) @@ -209,11 +218,21 @@ func decodeMsg(bz []byte) (msg EvidenceMessage, err error) { //------------------------------------- -// EvidenceMessage contains a list of evidence. +// EvidenceListMessage contains a list of evidence. type EvidenceListMessage struct { Evidence []types.Evidence } +// ValidateBasic performs basic validation. +func (m *EvidenceListMessage) ValidateBasic() error { + for i, ev := range m.Evidence { + if err := ev.ValidateBasic(); err != nil { + return fmt.Errorf("Invalid evidence (#%d): %v", i, err) + } + } + return nil +} + // String returns a string representation of the EvidenceListMessage. func (m *EvidenceListMessage) String() string { return fmt.Sprintf("[EvidenceListMessage %v]", m.Evidence) diff --git a/p2p/pex/addrbook.go b/p2p/pex/addrbook.go index e0c0e0b9..61710bbf 100644 --- a/p2p/pex/addrbook.go +++ b/p2p/pex/addrbook.go @@ -648,6 +648,10 @@ func (a *addrBook) addAddress(addr, src *p2p.NetAddress) error { return ErrAddrBookNonRoutable{addr} } + if !addr.Valid() { + return ErrAddrBookInvalidAddr{addr} + } + // TODO: we should track ourAddrs by ID and by IP:PORT and refuse both. if _, ok := a.ourAddrs[addr.String()]; ok { return ErrAddrBookSelf{addr} diff --git a/p2p/pex/errors.go b/p2p/pex/errors.go index 7f660bdc..fbee748a 100644 --- a/p2p/pex/errors.go +++ b/p2p/pex/errors.go @@ -46,3 +46,11 @@ type ErrAddrBookNilAddr struct { func (err ErrAddrBookNilAddr) Error() string { return fmt.Sprintf("Cannot add a nil address. Got (addr, src) = (%v, %v)", err.Addr, err.Src) } + +type ErrAddrBookInvalidAddr struct { + Addr *p2p.NetAddress +} + +func (err ErrAddrBookInvalidAddr) Error() string { + return fmt.Sprintf("Cannot add invalid address %v", err.Addr) +} diff --git a/p2p/pex/pex_reactor.go b/p2p/pex/pex_reactor.go index c919794a..46a12c48 100644 --- a/p2p/pex/pex_reactor.go +++ b/p2p/pex/pex_reactor.go @@ -288,21 +288,37 @@ func (r *PEXReactor) RequestAddrs(p Peer) { func (r *PEXReactor) ReceiveAddrs(addrs []*p2p.NetAddress, src Peer) error { id := string(src.ID()) if !r.requestsSent.Has(id) { - return cmn.NewError("Received unsolicited pexAddrsMessage") + return errors.New("Unsolicited pexAddrsMessage") } r.requestsSent.Delete(id) srcAddr := src.NodeInfo().NetAddress() for _, netAddr := range addrs { - // NOTE: GetSelection methods should never return nil addrs + // Validate netAddr. Disconnect from a peer if it sends us invalid data. if netAddr == nil { - return cmn.NewError("received nil addr") + return errors.New("nil address in pexAddrsMessage") + } + // TODO: extract validating logic from NewNetAddressStringWithOptionalID + // and put it in netAddr#Valid (#2722) + na, err := p2p.NewNetAddressString(netAddr.String()) + if err != nil { + return fmt.Errorf("%s address in pexAddrsMessage is invalid: %v", + netAddr.String(), + err, + ) } - err := r.book.AddAddress(netAddr, srcAddr) - r.logErrAddrBook(err) + // NOTE: we check netAddr validity and routability in book#AddAddress. + err = r.book.AddAddress(na, srcAddr) + if err != nil { + r.logErrAddrBook(err) + // XXX: should we be strict about incoming data and disconnect from a + // peer here too? + continue + } - // If this address came from a seed node, try to connect to it without waiting. + // If this address came from a seed node, try to connect to it without + // waiting. for _, seedAddr := range r.seedAddrs { if seedAddr.Equals(srcAddr) { r.ensurePeers() diff --git a/state/validation.go b/state/validation.go index 34522484..e28d40e8 100644 --- a/state/validation.go +++ b/state/validation.go @@ -21,22 +21,19 @@ func validateBlock(stateDB dbm.DB, state State, block *types.Block) error { // Validate basic info. if block.Version != state.Version.Consensus { - return fmt.Errorf( - "Wrong Block.Header.Version. Expected %v, got %v", + return fmt.Errorf("Wrong Block.Header.Version. Expected %v, got %v", state.Version.Consensus, block.Version, ) } if block.ChainID != state.ChainID { - return fmt.Errorf( - "Wrong Block.Header.ChainID. Expected %v, got %v", + return fmt.Errorf("Wrong Block.Header.ChainID. Expected %v, got %v", state.ChainID, block.ChainID, ) } if block.Height != state.LastBlockHeight+1 { - return fmt.Errorf( - "Wrong Block.Header.Height. Expected %v, got %v", + return fmt.Errorf("Wrong Block.Header.Height. Expected %v, got %v", state.LastBlockHeight+1, block.Height, ) @@ -44,16 +41,15 @@ func validateBlock(stateDB dbm.DB, state State, block *types.Block) error { // Validate prev block info. if !block.LastBlockID.Equals(state.LastBlockID) { - return fmt.Errorf( - "Wrong Block.Header.LastBlockID. Expected %v, got %v", + return fmt.Errorf("Wrong Block.Header.LastBlockID. Expected %v, got %v", state.LastBlockID, block.LastBlockID, ) } + newTxs := int64(len(block.Data.Txs)) if block.TotalTxs != state.LastBlockTotalTx+newTxs { - return fmt.Errorf( - "Wrong Block.Header.TotalTxs. Expected %v, got %v", + return fmt.Errorf("Wrong Block.Header.TotalTxs. Expected %v, got %v", state.LastBlockTotalTx+newTxs, block.TotalTxs, ) @@ -61,46 +57,44 @@ func validateBlock(stateDB dbm.DB, state State, block *types.Block) error { // Validate app info if !bytes.Equal(block.AppHash, state.AppHash) { - return fmt.Errorf( - "Wrong Block.Header.AppHash. Expected %X, got %v", + return fmt.Errorf("Wrong Block.Header.AppHash. Expected %X, got %v", state.AppHash, block.AppHash, ) } if !bytes.Equal(block.ConsensusHash, state.ConsensusParams.Hash()) { - return fmt.Errorf( - "Wrong Block.Header.ConsensusHash. Expected %X, got %v", + return fmt.Errorf("Wrong Block.Header.ConsensusHash. Expected %X, got %v", state.ConsensusParams.Hash(), block.ConsensusHash, ) } if !bytes.Equal(block.LastResultsHash, state.LastResultsHash) { - return fmt.Errorf( - "Wrong Block.Header.LastResultsHash. Expected %X, got %v", + return fmt.Errorf("Wrong Block.Header.LastResultsHash. Expected %X, got %v", state.LastResultsHash, block.LastResultsHash, ) } if !bytes.Equal(block.ValidatorsHash, state.Validators.Hash()) { - return fmt.Errorf( - "Wrong Block.Header.ValidatorsHash. Expected %X, got %v", + return fmt.Errorf("Wrong Block.Header.ValidatorsHash. Expected %X, got %v", state.Validators.Hash(), block.ValidatorsHash, ) } if !bytes.Equal(block.NextValidatorsHash, state.NextValidators.Hash()) { - return fmt.Errorf("Wrong Block.Header.NextValidatorsHash. Expected %X, got %v", state.NextValidators.Hash(), block.NextValidatorsHash) + return fmt.Errorf("Wrong Block.Header.NextValidatorsHash. Expected %X, got %v", + state.NextValidators.Hash(), + block.NextValidatorsHash, + ) } // Validate block LastCommit. if block.Height == 1 { if len(block.LastCommit.Precommits) != 0 { - return errors.New("Block at height 1 (first block) should have no LastCommit precommits") + return errors.New("Block at height 1 can't have LastCommit precommits") } } else { if len(block.LastCommit.Precommits) != state.LastValidators.Size() { - return fmt.Errorf( - "Invalid block commit size. Expected %v, got %v", + return fmt.Errorf("Invalid block commit size. Expected %v, got %v", state.LastValidators.Size(), len(block.LastCommit.Precommits), ) @@ -115,8 +109,7 @@ func validateBlock(stateDB dbm.DB, state State, block *types.Block) error { // Validate block Time if block.Height > 1 { if !block.Time.After(state.LastBlockTime) { - return fmt.Errorf( - "Block time %v not greater than last block time %v", + return fmt.Errorf("Block time %v not greater than last block time %v", block.Time, state.LastBlockTime, ) @@ -124,8 +117,7 @@ func validateBlock(stateDB dbm.DB, state State, block *types.Block) error { medianTime := MedianTime(block.LastCommit, state.LastValidators) if !block.Time.Equal(medianTime) { - return fmt.Errorf( - "Invalid block time. Expected %v, got %v", + return fmt.Errorf("Invalid block time. Expected %v, got %v", medianTime, block.Time, ) @@ -133,8 +125,7 @@ func validateBlock(stateDB dbm.DB, state State, block *types.Block) error { } else if block.Height == 1 { genesisTime := state.LastBlockTime if !block.Time.Equal(genesisTime) { - return fmt.Errorf( - "Block time %v is not equal to genesis time %v", + return fmt.Errorf("Block time %v is not equal to genesis time %v", block.Time, genesisTime, ) @@ -160,8 +151,7 @@ func validateBlock(stateDB dbm.DB, state State, block *types.Block) error { // a legit address and a known validator. if len(block.ProposerAddress) != crypto.AddressSize || !state.Validators.HasAddress(block.ProposerAddress) { - return fmt.Errorf( - "Block.Header.ProposerAddress, %X, is not a validator", + return fmt.Errorf("Block.Header.ProposerAddress, %X, is not a validator", block.ProposerAddress, ) } diff --git a/test/docker/Dockerfile b/test/docker/Dockerfile index 6bb320be..1a64d417 100644 --- a/test/docker/Dockerfile +++ b/test/docker/Dockerfile @@ -14,6 +14,7 @@ ENV GOBIN $GOPATH/bin WORKDIR $REPO # Copy in the code +# TODO: rewrite to only copy Makefile & other files? COPY . $REPO # Install the vendored dependencies @@ -21,16 +22,18 @@ COPY . $REPO RUN make get_tools RUN make get_vendor_deps -# Now copy in the code -# NOTE: this will overwrite whatever is in vendor/ -COPY . $REPO - # install ABCI CLI RUN make install_abci # install Tendermint RUN make install +RUN tendermint testnet --node-dir-prefix="mach" --v=4 --populate-persistent-peers=false --o=$REPO/test/p2p/data + +# Now copy in the code +# NOTE: this will overwrite whatever is in vendor/ +COPY . $REPO + # expose the volume for debugging VOLUME $REPO diff --git a/test/p2p/README.md b/test/p2p/README.md index 4ee3690a..956ce906 100644 --- a/test/p2p/README.md +++ b/test/p2p/README.md @@ -19,7 +19,7 @@ docker network create --driver bridge --subnet 172.57.0.0/16 my_testnet ``` This gives us a new network with IP addresses in the rage `172.57.0.0 - 172.57.255.255`. -Peers on the network can have any IP address in this range. +Peers on the network can have any IP address in this range. For our four node network, let's pick `172.57.0.101 - 172.57.0.104`. Since we use Tendermint's default listening port of 26656, our list of seed nodes will look like: @@ -37,7 +37,7 @@ for i in $(seq 1 4); do --ip="172.57.0.$((100 + $i))" \ --name local_testnet_$i \ --entrypoint tendermint \ - -e TMHOME=/go/src/github.com/tendermint/tendermint/test/p2p/data/mach$i/core \ + -e TMHOME=/go/src/github.com/tendermint/tendermint/test/p2p/data/mach$((i-1)) \ tendermint_tester node --p2p.persistent_peers 172.57.0.101:26656,172.57.0.102:26656,172.57.0.103:26656,172.57.0.104:26656 --proxy_app=kvstore done ``` @@ -47,8 +47,5 @@ If you now run `docker ps`, you'll see your containers! We can confirm they are making blocks by checking the `/status` message using `curl` and `jq` to pretty print the output json: ``` -curl 172.57.0.101:26657/status | jq . +curl 172.57.0.101:26657/status | jq . ``` - - - diff --git a/test/p2p/data/mach1/core/config/genesis.json b/test/p2p/data/mach1/core/config/genesis.json deleted file mode 100644 index 515c1071..00000000 --- a/test/p2p/data/mach1/core/config/genesis.json +++ /dev/null @@ -1,39 +0,0 @@ -{ - "genesis_time": "2016-06-24T20:01:19.322Z", - "chain_id": "chain-9ujDWI", - "validators": [ - { - "pub_key": { - "type": "tendermint/PubKeyEd25519", - "value": "vokz3/FgDAJuNHGPF4Wkzeq5DDVpizlOOLaUeukd4RY=" - }, - "power": "1", - "name": "mach1" - }, - { - "pub_key": { - "type": "tendermint/PubKeyEd25519", - "value": "bcU0RlMjEmWH0qKpO1nWibcXBzsd6WiiWm7xPVlTGK0=" - }, - "power": "1", - "name": "mach2" - }, - { - "pub_key": { - "type": "tendermint/PubKeyEd25519", - "value": "rmesaX0TWqC0YB6lfqqz/r9Lqk8inEWlmMKYWxL80aE=" - }, - "power": "1", - "name": "mach3" - }, - { - "pub_key": { - "type": "tendermint/PubKeyEd25519", - "value": "nryPWM7UtG3NWrirpZHdJTzXy1A3Jz/aMrwLZGHE79k=" - }, - "power": "1", - "name": "mach4" - } - ], - "app_hash": "" -} diff --git a/test/p2p/data/mach1/core/config/node_key.json b/test/p2p/data/mach1/core/config/node_key.json deleted file mode 100644 index 4fa96085..00000000 --- a/test/p2p/data/mach1/core/config/node_key.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "priv_key": { - "type": "tendermint/PrivKeyEd25519", - "value": "BpYtFp8xSrudBa5aBLRuSPD72PGDAUm0dJORDL3Kd5YJbluUzRefVFrjwoHZv1yeDj2P9xkEi2L3hJCUz/qFkQ==" - } -} diff --git a/test/p2p/data/mach1/core/config/priv_validator.json b/test/p2p/data/mach1/core/config/priv_validator.json deleted file mode 100644 index ea2a01f5..00000000 --- a/test/p2p/data/mach1/core/config/priv_validator.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "address": "AE47BBD4B3ACD80BFE17F6E0C66C5B8492A81AE4", - "pub_key": { - "type": "tendermint/PubKeyEd25519", - "value": "vokz3/FgDAJuNHGPF4Wkzeq5DDVpizlOOLaUeukd4RY=" - }, - "last_height": "0", - "last_round": "0", - "last_step": 0, - "priv_key": { - "type": "tendermint/PrivKeyEd25519", - "value": "VHqgfHqM4WxcsqQMbCbRWwoylgQQqfHqblC2NvGrOJq+iTPf8WAMAm40cY8XhaTN6rkMNWmLOU44tpR66R3hFg==" - } -} diff --git a/test/p2p/data/mach2/core/config/genesis.json b/test/p2p/data/mach2/core/config/genesis.json deleted file mode 100644 index 515c1071..00000000 --- a/test/p2p/data/mach2/core/config/genesis.json +++ /dev/null @@ -1,39 +0,0 @@ -{ - "genesis_time": "2016-06-24T20:01:19.322Z", - "chain_id": "chain-9ujDWI", - "validators": [ - { - "pub_key": { - "type": "tendermint/PubKeyEd25519", - "value": "vokz3/FgDAJuNHGPF4Wkzeq5DDVpizlOOLaUeukd4RY=" - }, - "power": "1", - "name": "mach1" - }, - { - "pub_key": { - "type": "tendermint/PubKeyEd25519", - "value": "bcU0RlMjEmWH0qKpO1nWibcXBzsd6WiiWm7xPVlTGK0=" - }, - "power": "1", - "name": "mach2" - }, - { - "pub_key": { - "type": "tendermint/PubKeyEd25519", - "value": "rmesaX0TWqC0YB6lfqqz/r9Lqk8inEWlmMKYWxL80aE=" - }, - "power": "1", - "name": "mach3" - }, - { - "pub_key": { - "type": "tendermint/PubKeyEd25519", - "value": "nryPWM7UtG3NWrirpZHdJTzXy1A3Jz/aMrwLZGHE79k=" - }, - "power": "1", - "name": "mach4" - } - ], - "app_hash": "" -} diff --git a/test/p2p/data/mach2/core/config/node_key.json b/test/p2p/data/mach2/core/config/node_key.json deleted file mode 100644 index 6eb15110..00000000 --- a/test/p2p/data/mach2/core/config/node_key.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "priv_key": { - "type": "tendermint/PrivKeyEd25519", - "value": "uM6LDVE4wQIIUmq9rc6RxzX8zEGG4G4Jcuw15klzQopF68YfJM4bkbPSavurEcJ4nvBMusKBg2GcARFrZqnFKA==" - } -} diff --git a/test/p2p/data/mach2/core/config/priv_validator.json b/test/p2p/data/mach2/core/config/priv_validator.json deleted file mode 100644 index 6e0cd7f8..00000000 --- a/test/p2p/data/mach2/core/config/priv_validator.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "address": "5D61EE46CCE91F579086522D7FD8CEC3F854E946", - "pub_key": { - "type": "tendermint/PubKeyEd25519", - "value": "bcU0RlMjEmWH0qKpO1nWibcXBzsd6WiiWm7xPVlTGK0=" - }, - "last_height": "0", - "last_round": "0", - "last_step": 0, - "priv_key": { - "type": "tendermint/PrivKeyEd25519", - "value": "0EeInmBQL8MSnQq38zSxg47Z7R7Nmcu5a3GtWr9agUNtxTRGUyMSZYfSoqk7WdaJtxcHOx3paKJabvE9WVMYrQ==" - } -} diff --git a/test/p2p/data/mach3/core/config/genesis.json b/test/p2p/data/mach3/core/config/genesis.json deleted file mode 100644 index 515c1071..00000000 --- a/test/p2p/data/mach3/core/config/genesis.json +++ /dev/null @@ -1,39 +0,0 @@ -{ - "genesis_time": "2016-06-24T20:01:19.322Z", - "chain_id": "chain-9ujDWI", - "validators": [ - { - "pub_key": { - "type": "tendermint/PubKeyEd25519", - "value": "vokz3/FgDAJuNHGPF4Wkzeq5DDVpizlOOLaUeukd4RY=" - }, - "power": "1", - "name": "mach1" - }, - { - "pub_key": { - "type": "tendermint/PubKeyEd25519", - "value": "bcU0RlMjEmWH0qKpO1nWibcXBzsd6WiiWm7xPVlTGK0=" - }, - "power": "1", - "name": "mach2" - }, - { - "pub_key": { - "type": "tendermint/PubKeyEd25519", - "value": "rmesaX0TWqC0YB6lfqqz/r9Lqk8inEWlmMKYWxL80aE=" - }, - "power": "1", - "name": "mach3" - }, - { - "pub_key": { - "type": "tendermint/PubKeyEd25519", - "value": "nryPWM7UtG3NWrirpZHdJTzXy1A3Jz/aMrwLZGHE79k=" - }, - "power": "1", - "name": "mach4" - } - ], - "app_hash": "" -} diff --git a/test/p2p/data/mach3/core/config/node_key.json b/test/p2p/data/mach3/core/config/node_key.json deleted file mode 100644 index 0885bcf9..00000000 --- a/test/p2p/data/mach3/core/config/node_key.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "priv_key": { - "type": "tendermint/PrivKeyEd25519", - "value": "kT3orG0YkipT9rAZbvAjtGk/7Pu1ZeCE8LSUF2jz2uiSs1rdlUVi/gccRlvCRLKvrtSicOyEkmk0FHPOGS3mgg==" - } -} diff --git a/test/p2p/data/mach3/core/config/priv_validator.json b/test/p2p/data/mach3/core/config/priv_validator.json deleted file mode 100644 index ec68ca7b..00000000 --- a/test/p2p/data/mach3/core/config/priv_validator.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "address": "705F9DA2CC7D7AF5F4519455ED99622E40E439A1", - "pub_key": { - "type": "tendermint/PubKeyEd25519", - "value": "rmesaX0TWqC0YB6lfqqz/r9Lqk8inEWlmMKYWxL80aE=" - }, - "last_height": "0", - "last_round": "0", - "last_step": 0, - "priv_key": { - "type": "tendermint/PrivKeyEd25519", - "value": "waTkfzSfxfVW9Kmie6d2uUQkwxK6ps9u5EuGc0jXw/KuZ6xpfRNaoLRgHqV+qrP+v0uqTyKcRaWYwphbEvzRoQ==" - } -} diff --git a/test/p2p/data/mach4/core/config/genesis.json b/test/p2p/data/mach4/core/config/genesis.json deleted file mode 100644 index 515c1071..00000000 --- a/test/p2p/data/mach4/core/config/genesis.json +++ /dev/null @@ -1,39 +0,0 @@ -{ - "genesis_time": "2016-06-24T20:01:19.322Z", - "chain_id": "chain-9ujDWI", - "validators": [ - { - "pub_key": { - "type": "tendermint/PubKeyEd25519", - "value": "vokz3/FgDAJuNHGPF4Wkzeq5DDVpizlOOLaUeukd4RY=" - }, - "power": "1", - "name": "mach1" - }, - { - "pub_key": { - "type": "tendermint/PubKeyEd25519", - "value": "bcU0RlMjEmWH0qKpO1nWibcXBzsd6WiiWm7xPVlTGK0=" - }, - "power": "1", - "name": "mach2" - }, - { - "pub_key": { - "type": "tendermint/PubKeyEd25519", - "value": "rmesaX0TWqC0YB6lfqqz/r9Lqk8inEWlmMKYWxL80aE=" - }, - "power": "1", - "name": "mach3" - }, - { - "pub_key": { - "type": "tendermint/PubKeyEd25519", - "value": "nryPWM7UtG3NWrirpZHdJTzXy1A3Jz/aMrwLZGHE79k=" - }, - "power": "1", - "name": "mach4" - } - ], - "app_hash": "" -} diff --git a/test/p2p/data/mach4/core/config/node_key.json b/test/p2p/data/mach4/core/config/node_key.json deleted file mode 100644 index d6a5d79c..00000000 --- a/test/p2p/data/mach4/core/config/node_key.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "priv_key": { - "type": "tendermint/PrivKeyEd25519", - "value": "QIIm8/QEEawiJi3Zozv+J9b+1CufCEkGs3lxGMlRy4L4FVIXCoXJTwYIrotZtwoMqLYEqQV1hbKKJmFA3GFelw==" - } -} diff --git a/test/p2p/data/mach4/core/config/priv_validator.json b/test/p2p/data/mach4/core/config/priv_validator.json deleted file mode 100644 index 468550ea..00000000 --- a/test/p2p/data/mach4/core/config/priv_validator.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "address": "D1054266EC9EEA511ED9A76DEFD520BBE1B5E850", - "pub_key": { - "type": "tendermint/PubKeyEd25519", - "value": "nryPWM7UtG3NWrirpZHdJTzXy1A3Jz/aMrwLZGHE79k=" - }, - "last_height": "0", - "last_round": "0", - "last_step": 0, - "priv_key": { - "type": "tendermint/PrivKeyEd25519", - "value": "xMw+0o8CDC29qYvNvwjDztNwRw508l6TjV0pXo49KwyevI9YztS0bc1auKulkd0lPNfLUDcnP9oyvAtkYcTv2Q==" - } -} diff --git a/test/p2p/ip_plus_id.sh b/test/p2p/ip_plus_id.sh index 0d2248fe..95871d3f 100755 --- a/test/p2p/ip_plus_id.sh +++ b/test/p2p/ip_plus_id.sh @@ -3,5 +3,5 @@ set -eu ID=$1 DOCKER_IMAGE=$2 -NODEID="$(docker run --rm -e TMHOME=/go/src/github.com/tendermint/tendermint/test/p2p/data/mach$ID/core $DOCKER_IMAGE tendermint show_node_id)" +NODEID="$(docker run --rm -e TMHOME=/go/src/github.com/tendermint/tendermint/test/p2p/data/mach$((ID-1)) $DOCKER_IMAGE tendermint show_node_id)" echo "$NODEID@172.57.0.$((100+$ID))" diff --git a/test/p2p/peer.sh b/test/p2p/peer.sh index ad04d000..63d46f8d 100644 --- a/test/p2p/peer.sh +++ b/test/p2p/peer.sh @@ -15,13 +15,15 @@ echo "starting tendermint peer ID=$ID" # NOTE: $NODE_FLAGS should be unescaped (no quotes). otherwise it will be # treated as one flag. +# test/p2p/data/mach$((ID-1)) data is generated in test/docker/Dockerfile using +# the tendermint testnet command. if [[ "$ID" == "x" ]]; then # Set "x" to "1" to print to console. docker run \ --net="$NETWORK_NAME" \ --ip=$(test/p2p/ip.sh "$ID") \ --name "local_testnet_$ID" \ --entrypoint tendermint \ - -e TMHOME="/go/src/github.com/tendermint/tendermint/test/p2p/data/mach$ID/core" \ + -e TMHOME="/go/src/github.com/tendermint/tendermint/test/p2p/data/mach$((ID-1))" \ -e GOMAXPROCS=1 \ --log-driver=syslog \ --log-opt syslog-address=udp://127.0.0.1:5514 \ @@ -34,7 +36,7 @@ else --ip=$(test/p2p/ip.sh "$ID") \ --name "local_testnet_$ID" \ --entrypoint tendermint \ - -e TMHOME="/go/src/github.com/tendermint/tendermint/test/p2p/data/mach$ID/core" \ + -e TMHOME="/go/src/github.com/tendermint/tendermint/test/p2p/data/mach$((ID-1))" \ -e GOMAXPROCS=1 \ --log-driver=syslog \ --log-opt syslog-address=udp://127.0.0.1:5514 \ diff --git a/test/p2p/pex/test_addrbook.sh b/test/p2p/pex/test_addrbook.sh index 9c58db30..06f9212f 100644 --- a/test/p2p/pex/test_addrbook.sh +++ b/test/p2p/pex/test_addrbook.sh @@ -18,7 +18,7 @@ echo "1. restart peer $ID" docker stop "local_testnet_$ID" echo "stopped local_testnet_$ID" # preserve addrbook.json -docker cp "local_testnet_$ID:/go/src/github.com/tendermint/tendermint/test/p2p/data/mach1/core/config/addrbook.json" "/tmp/addrbook.json" +docker cp "local_testnet_$ID:/go/src/github.com/tendermint/tendermint/test/p2p/data/mach0/config/addrbook.json" "/tmp/addrbook.json" set +e #CIRCLE docker rm -vf "local_testnet_$ID" set -e @@ -32,11 +32,11 @@ bash test/p2p/client.sh "$DOCKER_IMAGE" "$NETWORK_NAME" "$CLIENT_NAME" "test/p2p # Now we know that the node is up. -docker cp "/tmp/addrbook.json" "local_testnet_$ID:/go/src/github.com/tendermint/tendermint/test/p2p/data/mach1/core/config/addrbook.json" +docker cp "/tmp/addrbook.json" "local_testnet_$ID:/go/src/github.com/tendermint/tendermint/test/p2p/data/mach0/config/addrbook.json" echo "with the following addrbook:" cat /tmp/addrbook.json # exec doesn't work on circle -# docker exec "local_testnet_$ID" cat "/go/src/github.com/tendermint/tendermint/test/p2p/data/mach1/core/config/addrbook.json" +# docker exec "local_testnet_$ID" cat "/go/src/github.com/tendermint/tendermint/test/p2p/data/mach0/config/addrbook.json" echo "" echo "----------------------------------------------------------------------" diff --git a/types/block.go b/types/block.go index 46ad73a7..4ae51d4d 100644 --- a/types/block.go +++ b/types/block.go @@ -2,12 +2,14 @@ package types import ( "bytes" - "errors" "fmt" "strings" "sync" "time" + "github.com/pkg/errors" + + "github.com/tendermint/tendermint/crypto" "github.com/tendermint/tendermint/crypto/merkle" cmn "github.com/tendermint/tendermint/libs/common" "github.com/tendermint/tendermint/version" @@ -57,54 +59,117 @@ func MakeBlock(height int64, txs []Tx, lastCommit *Commit, evidence []Evidence) // ValidateBasic performs basic validation that doesn't involve state data. // It checks the internal consistency of the block. +// Further validation is done using state#ValidateBlock. func (b *Block) ValidateBasic() error { if b == nil { - return errors.New("Nil blocks are invalid") + return errors.New("nil block") } b.mtx.Lock() defer b.mtx.Unlock() - if b.Height < 0 { - return fmt.Errorf( - "Negative Block.Header.Height: %v", - b.Height, - ) + if len(b.ChainID) > MaxChainIDLen { + return fmt.Errorf("ChainID is too long. Max is %d, got %d", MaxChainIDLen, len(b.ChainID)) } + if b.Height < 0 { + return errors.New("Negative Header.Height") + } else if b.Height == 0 { + return errors.New("Zero Header.Height") + } + + // NOTE: Timestamp validation is subtle and handled elsewhere. + newTxs := int64(len(b.Data.Txs)) if b.NumTxs != newTxs { - return fmt.Errorf( - "Wrong Block.Header.NumTxs. Expected %v, got %v", + return fmt.Errorf("Wrong Header.NumTxs. Expected %v, got %v", newTxs, b.NumTxs, ) } + + // TODO: fix tests so we can do this + /*if b.TotalTxs < b.NumTxs { + return fmt.Errorf("Header.TotalTxs (%d) is less than Header.NumTxs (%d)", b.TotalTxs, b.NumTxs) + }*/ + if b.TotalTxs < 0 { + return errors.New("Negative Header.TotalTxs") + } + + if err := b.LastBlockID.ValidateBasic(); err != nil { + return fmt.Errorf("Wrong Header.LastBlockID: %v", err) + } + + // Validate the last commit and its hash. + if b.Header.Height > 1 { + if b.LastCommit == nil { + return errors.New("nil LastCommit") + } + if err := b.LastCommit.ValidateBasic(); err != nil { + return fmt.Errorf("Wrong LastCommit") + } + } + if err := ValidateHash(b.LastCommitHash); err != nil { + return fmt.Errorf("Wrong Header.LastCommitHash: %v", err) + } if !bytes.Equal(b.LastCommitHash, b.LastCommit.Hash()) { - return fmt.Errorf( - "Wrong Block.Header.LastCommitHash. Expected %v, got %v", - b.LastCommitHash, + return fmt.Errorf("Wrong Header.LastCommitHash. Expected %v, got %v", b.LastCommit.Hash(), + b.LastCommitHash, ) } - if b.Header.Height != 1 { - if err := b.LastCommit.ValidateBasic(); err != nil { - return err - } + + // Validate the hash of the transactions. + // NOTE: b.Data.Txs may be nil, but b.Data.Hash() + // still works fine + if err := ValidateHash(b.DataHash); err != nil { + return fmt.Errorf("Wrong Header.DataHash: %v", err) } if !bytes.Equal(b.DataHash, b.Data.Hash()) { return fmt.Errorf( - "Wrong Block.Header.DataHash. Expected %v, got %v", - b.DataHash, + "Wrong Header.DataHash. Expected %v, got %v", b.Data.Hash(), + b.DataHash, ) } + + // Basic validation of hashes related to application data. + // Will validate fully against state in state#ValidateBlock. + if err := ValidateHash(b.ValidatorsHash); err != nil { + return fmt.Errorf("Wrong Header.ValidatorsHash: %v", err) + } + if err := ValidateHash(b.NextValidatorsHash); err != nil { + return fmt.Errorf("Wrong Header.NextValidatorsHash: %v", err) + } + if err := ValidateHash(b.ConsensusHash); err != nil { + return fmt.Errorf("Wrong Header.ConsensusHash: %v", err) + } + // NOTE: AppHash is arbitrary length + if err := ValidateHash(b.LastResultsHash); err != nil { + return fmt.Errorf("Wrong Header.LastResultsHash: %v", err) + } + + // Validate evidence and its hash. + if err := ValidateHash(b.EvidenceHash); err != nil { + return fmt.Errorf("Wrong Header.EvidenceHash: %v", err) + } + // NOTE: b.Evidence.Evidence may be nil, but we're just looping. + for i, ev := range b.Evidence.Evidence { + if err := ev.ValidateBasic(); err != nil { + return fmt.Errorf("Invalid evidence (#%d): %v", i, err) + } + } if !bytes.Equal(b.EvidenceHash, b.Evidence.Hash()) { - return fmt.Errorf( - "Wrong Block.Header.EvidenceHash. Expected %v, got %v", + return fmt.Errorf("Wrong Header.EvidenceHash. Expected %v, got %v", b.EvidenceHash, b.Evidence.Hash(), ) } + + if len(b.ProposerAddress) != crypto.AddressSize { + return fmt.Errorf("Expected len(Header.ProposerAddress) to be %d, got %d", + crypto.AddressSize, len(b.ProposerAddress)) + } + return nil } @@ -719,6 +784,18 @@ func (blockID BlockID) Key() string { return string(blockID.Hash) + string(bz) } +// ValidateBasic performs basic validation. +func (blockID BlockID) ValidateBasic() error { + // Hash can be empty in case of POLBlockID in Proposal. + if err := ValidateHash(blockID.Hash); err != nil { + return fmt.Errorf("Wrong Hash") + } + if err := blockID.PartsHeader.ValidateBasic(); err != nil { + return fmt.Errorf("Wrong PartsHeader: %v", err) + } + return nil +} + // String returns a human readable string representation of the BlockID func (blockID BlockID) String() string { return fmt.Sprintf(`%v:%v`, blockID.Hash, blockID.PartsHeader) diff --git a/types/block_test.go b/types/block_test.go index 46881a09..cdea293f 100644 --- a/types/block_test.go +++ b/types/block_test.go @@ -80,11 +80,13 @@ func TestBlockValidateBasic(t *testing.T) { blk.EvidenceHash = []byte("something else") }, true}, } - for _, tc := range testCases { + for i, tc := range testCases { t.Run(tc.testName, func(t *testing.T) { block := MakeBlock(h, txs, commit, evList) + block.ProposerAddress = valSet.GetProposer().Address tc.malleateBlock(block) - assert.Equal(t, tc.expErr, block.ValidateBasic() != nil, "ValidateBasic had an unexpected result") + err = block.ValidateBasic() + assert.Equal(t, tc.expErr, err != nil, "#%d: %v", i, err) }) } } diff --git a/types/evidence.go b/types/evidence.go index d1e15c81..fb242345 100644 --- a/types/evidence.go +++ b/types/evidence.go @@ -4,6 +4,7 @@ import ( "bytes" "fmt" + "github.com/pkg/errors" "github.com/tendermint/tendermint/crypto/tmhash" amino "github.com/tendermint/go-amino" @@ -60,6 +61,7 @@ type Evidence interface { Verify(chainID string, pubKey crypto.PubKey) error // verify the evidence Equal(Evidence) bool // check equality of evidence + ValidateBasic() error String() string } @@ -172,6 +174,23 @@ func (dve *DuplicateVoteEvidence) Equal(ev Evidence) bool { return bytes.Equal(dveHash, evHash) } +// ValidateBasic performs basic validation. +func (dve *DuplicateVoteEvidence) ValidateBasic() error { + if len(dve.PubKey.Bytes()) == 0 { + return errors.New("Empty PubKey") + } + if dve.VoteA == nil || dve.VoteB == nil { + return fmt.Errorf("One or both of the votes are empty %v, %v", dve.VoteA, dve.VoteB) + } + if err := dve.VoteA.ValidateBasic(); err != nil { + return fmt.Errorf("Invalid VoteA: %v", err) + } + if err := dve.VoteB.ValidateBasic(); err != nil { + return fmt.Errorf("Invalid VoteB: %v", err) + } + return nil +} + //----------------------------------------------------------------- // UNSTABLE @@ -201,6 +220,7 @@ func (e MockGoodEvidence) Equal(ev Evidence) bool { return e.Height_ == e2.Height_ && bytes.Equal(e.Address_, e2.Address_) } +func (e MockGoodEvidence) ValidateBasic() error { return nil } func (e MockGoodEvidence) String() string { return fmt.Sprintf("GoodEvidence: %d/%s", e.Height_, e.Address_) } @@ -218,6 +238,7 @@ func (e MockBadEvidence) Equal(ev Evidence) bool { return e.Height_ == e2.Height_ && bytes.Equal(e.Address_, e2.Address_) } +func (e MockBadEvidence) ValidateBasic() error { return nil } func (e MockBadEvidence) String() string { return fmt.Sprintf("BadEvidence: %d/%s", e.Height_, e.Address_) } diff --git a/types/heartbeat.go b/types/heartbeat.go index 9dea039e..986e9384 100644 --- a/types/heartbeat.go +++ b/types/heartbeat.go @@ -3,6 +3,8 @@ package types import ( "fmt" + "github.com/pkg/errors" + "github.com/tendermint/tendermint/crypto" cmn "github.com/tendermint/tendermint/libs/common" ) @@ -50,3 +52,32 @@ func (heartbeat *Heartbeat) String() string { heartbeat.Height, heartbeat.Round, heartbeat.Sequence, fmt.Sprintf("/%X.../", cmn.Fingerprint(heartbeat.Signature[:]))) } + +// ValidateBasic performs basic validation. +func (heartbeat *Heartbeat) ValidateBasic() error { + if len(heartbeat.ValidatorAddress) != crypto.AddressSize { + return fmt.Errorf("Expected ValidatorAddress size to be %d bytes, got %d bytes", + crypto.AddressSize, + len(heartbeat.ValidatorAddress), + ) + } + if heartbeat.ValidatorIndex < 0 { + return errors.New("Negative ValidatorIndex") + } + if heartbeat.Height < 0 { + return errors.New("Negative Height") + } + if heartbeat.Round < 0 { + return errors.New("Negative Round") + } + if heartbeat.Sequence < 0 { + return errors.New("Negative Sequence") + } + if len(heartbeat.Signature) == 0 { + return errors.New("Signature is missing") + } + if len(heartbeat.Signature) > MaxSignatureSize { + return fmt.Errorf("Signature is too big (max: %d)", MaxSignatureSize) + } + return nil +} diff --git a/types/part_set.go b/types/part_set.go index 812b1c2f..af59851c 100644 --- a/types/part_set.go +++ b/types/part_set.go @@ -2,11 +2,12 @@ package types import ( "bytes" - "errors" "fmt" "io" "sync" + "github.com/pkg/errors" + "github.com/tendermint/tendermint/crypto/merkle" "github.com/tendermint/tendermint/crypto/tmhash" cmn "github.com/tendermint/tendermint/libs/common" @@ -36,6 +37,17 @@ func (part *Part) Hash() []byte { return part.hash } +// ValidateBasic performs basic validation. +func (part *Part) ValidateBasic() error { + if part.Index < 0 { + return errors.New("Negative Index") + } + if len(part.Bytes) > BlockPartSizeBytes { + return fmt.Errorf("Too big (max: %d)", BlockPartSizeBytes) + } + return nil +} + func (part *Part) String() string { return part.StringIndented("") } @@ -70,6 +82,18 @@ func (psh PartSetHeader) Equals(other PartSetHeader) bool { return psh.Total == other.Total && bytes.Equal(psh.Hash, other.Hash) } +// ValidateBasic performs basic validation. +func (psh PartSetHeader) ValidateBasic() error { + if psh.Total < 0 { + return errors.New("Negative Total") + } + // Hash can be empty in case of POLBlockID.PartsHeader in Proposal. + if err := ValidateHash(psh.Hash); err != nil { + return errors.Wrap(err, "Wrong Hash") + } + return nil +} + //------------------------------------- type PartSet struct { diff --git a/types/proposal.go b/types/proposal.go index 09cfd196..f3b62aae 100644 --- a/types/proposal.go +++ b/types/proposal.go @@ -43,6 +43,35 @@ func NewProposal(height int64, round int, polRound int, blockID BlockID) *Propos } } +// ValidateBasic performs basic validation. +func (p *Proposal) ValidateBasic() error { + if p.Type != ProposalType { + return errors.New("Invalid Type") + } + if p.Height < 0 { + return errors.New("Negative Height") + } + if p.Round < 0 { + return errors.New("Negative Round") + } + if p.POLRound < -1 { + return errors.New("Negative POLRound (exception: -1)") + } + if err := p.BlockID.ValidateBasic(); err != nil { + return fmt.Errorf("Wrong BlockID: %v", err) + } + + // NOTE: Timestamp validation is subtle and handled elsewhere. + + if len(p.Signature) == 0 { + return errors.New("Signature is missing") + } + if len(p.Signature) > MaxSignatureSize { + return fmt.Errorf("Signature is too big (max: %d)", MaxSignatureSize) + } + return nil +} + // String returns a string representation of the Proposal. func (p *Proposal) String() string { return fmt.Sprintf("Proposal{%v/%v (%v, %v) %X @ %s}", diff --git a/types/signable.go b/types/signable.go index cc649888..baabdff0 100644 --- a/types/signable.go +++ b/types/signable.go @@ -1,5 +1,17 @@ package types +import ( + "github.com/tendermint/tendermint/crypto/ed25519" + cmn "github.com/tendermint/tendermint/libs/common" +) + +var ( + // MaxSignatureSize is a maximum allowed signature size for the Heartbeat, + // Proposal and Vote. + // XXX: secp256k1 does not have Size nor MaxSize defined. + MaxSignatureSize = cmn.MaxInt(ed25519.SignatureSize, 64) +) + // Signable is an interface for all signable things. // It typically removes signatures before serializing. // SignBytes returns the bytes to be signed diff --git a/types/signed_msg_type.go b/types/signed_msg_type.go index cc3ddbdc..10e7c70c 100644 --- a/types/signed_msg_type.go +++ b/types/signed_msg_type.go @@ -15,11 +15,10 @@ const ( HeartbeatType SignedMsgType = 0x30 ) -func IsVoteTypeValid(type_ SignedMsgType) bool { - switch type_ { - case PrevoteType: - return true - case PrecommitType: +// IsVoteTypeValid returns true if t is a valid vote type. +func IsVoteTypeValid(t SignedMsgType) bool { + switch t { + case PrevoteType, PrecommitType: return true default: return false diff --git a/types/validation.go b/types/validation.go new file mode 100644 index 00000000..1271cfd9 --- /dev/null +++ b/types/validation.go @@ -0,0 +1,40 @@ +package types + +import ( + "fmt" + "time" + + "github.com/tendermint/tendermint/crypto/tmhash" + tmtime "github.com/tendermint/tendermint/types/time" +) + +// ValidateTime does a basic time validation ensuring time does not drift too +// much: +/- one year. +// TODO: reduce this to eg 1 day +// NOTE: DO NOT USE in ValidateBasic methods in this package. This function +// can only be used for real time validation, like on proposals and votes +// in the consensus. If consensus is stuck, and rounds increase for more than a day, +// having only a 1-day band here could break things... +// Can't use for validating blocks because we may be syncing years worth of history. +func ValidateTime(t time.Time) error { + var ( + now = tmtime.Now() + oneYear = 8766 * time.Hour + ) + if t.Before(now.Add(-oneYear)) || t.After(now.Add(oneYear)) { + return fmt.Errorf("Time drifted too much. Expected: -1 < %v < 1 year", now) + } + return nil +} + +// ValidateHash returns an error if the hash is not empty, but its +// size != tmhash.Size. +func ValidateHash(h []byte) error { + if len(h) > 0 && len(h) != tmhash.Size { + return fmt.Errorf("Expected size to be %d bytes, got %d bytes", + tmhash.Size, + len(h), + ) + } + return nil +} diff --git a/types/vote.go b/types/vote.go index 1d7e9cf6..bf14d403 100644 --- a/types/vote.go +++ b/types/vote.go @@ -46,7 +46,8 @@ func NewConflictingVoteError(val *Validator, voteA, voteB *Vote) *ErrVoteConflic // Address is hex bytes. type Address = crypto.Address -// Represents a prevote, precommit, or commit vote from validators for consensus. +// Vote represents a prevote, precommit, or commit vote from validators for +// consensus. type Vote struct { Type SignedMsgType `json:"type"` Height int64 `json:"height"` @@ -108,3 +109,38 @@ func (vote *Vote) Verify(chainID string, pubKey crypto.PubKey) error { } return nil } + +// ValidateBasic performs basic validation. +func (vote *Vote) ValidateBasic() error { + if !IsVoteTypeValid(vote.Type) { + return errors.New("Invalid Type") + } + if vote.Height < 0 { + return errors.New("Negative Height") + } + if vote.Round < 0 { + return errors.New("Negative Round") + } + + // NOTE: Timestamp validation is subtle and handled elsewhere. + + if err := vote.BlockID.ValidateBasic(); err != nil { + return fmt.Errorf("Wrong BlockID: %v", err) + } + if len(vote.ValidatorAddress) != crypto.AddressSize { + return fmt.Errorf("Expected ValidatorAddress size to be %d bytes, got %d bytes", + crypto.AddressSize, + len(vote.ValidatorAddress), + ) + } + if vote.ValidatorIndex < 0 { + return errors.New("Negative ValidatorIndex") + } + if len(vote.Signature) == 0 { + return errors.New("Signature is missing") + } + if len(vote.Signature) > MaxSignatureSize { + return fmt.Errorf("Signature is too big (max: %d)", MaxSignatureSize) + } + return nil +} From 80e4fe6c0d72df0a41ff6909cd0d626d5980894b Mon Sep 17 00:00:00 2001 From: Anton Kaliaev Date: Fri, 2 Nov 2018 10:16:29 +0100 Subject: [PATCH 050/267] [ADR] [DRAFT] pubsub 2.0 (#2532) * pubsub adr Refs #951, #1879, #1880 * highlight question * fix typos after Ismail's review --- docs/architecture/adr-033-pubsub.md | 122 ++++++++++++++++++++++++++++ docs/architecture/adr-template.md | 4 + 2 files changed, 126 insertions(+) create mode 100644 docs/architecture/adr-033-pubsub.md diff --git a/docs/architecture/adr-033-pubsub.md b/docs/architecture/adr-033-pubsub.md new file mode 100644 index 00000000..0ef0cae6 --- /dev/null +++ b/docs/architecture/adr-033-pubsub.md @@ -0,0 +1,122 @@ +# ADR 033: pubsub 2.0 + +Author: Anton Kaliaev (@melekes) + +## Changelog + +02-10-2018: Initial draft + +## Context + +Since the initial version of the pubsub, there's been a number of issues +raised: #951, #1879, #1880. Some of them are high-level issues questioning the +core design choices made. Others are minor and mostly about the interface of +`Subscribe()` / `Publish()` functions. + +### Sync vs Async + +Now, when publishing a message to subscribers, we can do it in a goroutine: + +_using channels for data transmission_ +```go +for each subscriber { + out := subscriber.outc + go func() { + out <- msg + } +} +``` + +_by invoking callback functions_ +```go +for each subscriber { + go subscriber.callbackFn() +} +``` + +This gives us greater performance and allows us to avoid "slow client problem" +(when other subscribers have to wait for a slow subscriber). A pool of +goroutines can be used to avoid uncontrolled memory growth. + +In certain cases, this is what you want. But in our case, because we need +strict ordering of events (if event A was published before B, the guaranteed +delivery order will be A -> B), we can't use goroutines. + +There is also a question whenever we should have a non-blocking send: + +```go +for each subscriber { + out := subscriber.outc + select { + case out <- msg: + default: + log("subscriber %v buffer is full, skipping...") + } +} +``` + +This fixes the "slow client problem", but there is no way for a slow client to +know if it had missed a message. On the other hand, if we're going to stick +with blocking send, **devs must always ensure subscriber's handling code does not +block**. As you can see, there is an implicit choice between ordering guarantees +and using goroutines. + +The interim option is to run goroutines pool for a single message, wait for all +goroutines to finish. This will solve "slow client problem", but we'd still +have to wait `max(goroutine_X_time)` before we can publish the next message. +My opinion: not worth doing. + +### Channels vs Callbacks + +Yet another question is whether we should use channels for message transmission or +call subscriber-defined callback functions. Callback functions give subscribers +more flexibility - you can use mutexes in there, channels, spawn goroutines, +anything you really want. But they also carry local scope, which can result in +memory leaks and/or memory usage increase. + +Go channels are de-facto standard for carrying data between goroutines. + +**Question: Is it worth switching to callback functions?** + +### Why `Subscribe()` accepts an `out` channel? + +Because in our tests, we create buffered channels (cap: 1). Alternatively, we +can make capacity an argument. + +## Decision + +Change Subscribe() function to return out channel: + +```go +// outCap can be used to set capacity of out channel (unbuffered by default). +Subscribe(ctx context.Context, clientID string, query Query, outCap... int) (out <-chan interface{}, err error) { +``` + +It's more idiomatic since we're closing it during Unsubscribe/UnsubscribeAll calls. + +Also, we should make tags available to subscribers: + +```go +type MsgAndTags struct { + Msg interface{} + Tags TagMap +} + +// outCap can be used to set capacity of out channel (unbuffered by default). +Subscribe(ctx context.Context, clientID string, query Query, outCap... int) (out <-chan MsgAndTags, err error) { +``` + +## Status + +In review + +## Consequences + +### Positive + +- more idiomatic interface +- subscribers know what tags msg was published with + +### Negative + +### Neutral diff --git a/docs/architecture/adr-template.md b/docs/architecture/adr-template.md index d47c7f55..4879afc4 100644 --- a/docs/architecture/adr-template.md +++ b/docs/architecture/adr-template.md @@ -1,5 +1,9 @@ # ADR 000: Template for an ADR +Author: + +## Changelog + ## Context ## Decision From 322cee9156a4b99980f5c7d256877eaa3f4dc85e Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Fri, 2 Nov 2018 13:55:09 -0400 Subject: [PATCH 051/267] Release/v0.26.0 (#2726) * changelog_pending -> changelog * update changelog * update changelog * update changelog and upgrading --- CHANGELOG.md | 143 +++++++++++++++++++++++++++++++++++++++++++ CHANGELOG_PENDING.md | 101 +----------------------------- UPGRADING.md | 10 ++- 3 files changed, 152 insertions(+), 102 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6032fc20..792386e5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,148 @@ # Changelog +## v0.26.0 + +*November 2, 2018* + +Special thanks to external contributors on this release: +@bradyjoestar, @connorwstein, @goolAdapter, @HaoyangLiu, +@james-ray, @overbool, @phymbert, @Slamper, @Uzair1995, @yutianwu. + +Special thanks to @Slamper for a series of bug reports in our [bug bounty +program](https://hackerone.com/tendermint) which are fixed in this release. + +This release is primarily about adding Version fields to various data structures, +optimizing consensus messages for signing and verification in +restricted environments (like HSMs and the Ethereum Virtual Machine), and +aligning the consensus code with the [specification](https://arxiv.org/abs/1807.04938). +It also includes our first take at a generalized merkle proof system, and +changes the length of hashes used for hashing data structures from 20 to 32 +bytes. + +See the [UPGRADING.md](UPGRADING.md#v0.26.0) for details on upgrading to the new +version. + +Please note that we are still making breaking changes to the protocols. +While the new Version fields should help us to keep the software backwards compatible +even while upgrading the protocols, we cannot guarantee that new releases will +be compatible with old chains just yet. We expect there will be another breaking +release or two before the Cosmos Hub launch, but we will otherwise be paying +increasing attention to backwards compatibility. Thanks for bearing with us! + +### BREAKING CHANGES: + +* CLI/RPC/Config + * [config] [\#2232](https://github.com/tendermint/tendermint/issues/2232) Timeouts are now strings like "3s" and "100ms", not ints + * [config] [\#2505](https://github.com/tendermint/tendermint/issues/2505) Remove Mempool.RecheckEmpty (it was effectively useless anyways) + * [config] [\#2490](https://github.com/tendermint/tendermint/issues/2490) `mempool.wal` is disabled by default + * [privval] [\#2459](https://github.com/tendermint/tendermint/issues/2459) Split `SocketPVMsg`s implementations into Request and Response, where the Response may contain a error message (returned by the remote signer) + * [state] [\#2644](https://github.com/tendermint/tendermint/issues/2644) Add Version field to State, breaking the format of State as + encoded on disk. + * [rpc] [\#2298](https://github.com/tendermint/tendermint/issues/2298) `/abci_query` takes `prove` argument instead of `trusted` and switches the default + behaviour to `prove=false` + * [rpc] [\#2654](https://github.com/tendermint/tendermint/issues/2654) Remove all `node_info.other.*_version` fields in `/status` and + `/net_info` + * [rpc] [\#2636](https://github.com/tendermint/tendermint/issues/2636) Remove + `_params` suffix from fields in `consensus_params`. + +* Apps + * [abci] [\#2298](https://github.com/tendermint/tendermint/issues/2298) ResponseQuery.Proof is now a structured merkle.Proof, not just + arbitrary bytes + * [abci] [\#2644](https://github.com/tendermint/tendermint/issues/2644) Add Version to Header and shift all fields by one + * [abci] [\#2662](https://github.com/tendermint/tendermint/issues/2662) Bump the field numbers for some `ResponseInfo` fields to make room for + `AppVersion` + * [abci] [\#2636](https://github.com/tendermint/tendermint/issues/2636) Updates to ConsensusParams + * Remove `Params` suffix from field names + * Add `Params` suffix to message types + * Add new field and type, `Validator ValidatorParams`, to control what types of validator keys are allowed. + +* Go API + * [config] [\#2232](https://github.com/tendermint/tendermint/issues/2232) Timeouts are time.Duration, not ints + * [crypto/merkle & lite] [\#2298](https://github.com/tendermint/tendermint/issues/2298) Various changes to accomodate General Merkle trees + * [crypto/merkle] [\#2595](https://github.com/tendermint/tendermint/issues/2595) Remove all Hasher objects in favor of byte slices + * [crypto/merkle] [\#2635](https://github.com/tendermint/tendermint/issues/2635) merkle.SimpleHashFromTwoHashes is no longer exported + * [node] [\#2479](https://github.com/tendermint/tendermint/issues/2479) Remove node.RunForever + * [rpc/client] [\#2298](https://github.com/tendermint/tendermint/issues/2298) `ABCIQueryOptions.Trusted` -> `ABCIQueryOptions.Prove` + * [types] [\#2298](https://github.com/tendermint/tendermint/issues/2298) Remove `Index` and `Total` fields from `TxProof`. + * [types] [\#2598](https://github.com/tendermint/tendermint/issues/2598) + `VoteTypeXxx` are now of type `SignedMsgType byte` and named `XxxType`, eg. + `PrevoteType`, `PrecommitType`. + * [types] [\#2636](https://github.com/tendermint/tendermint/issues/2636) Rename fields in ConsensusParams to remove `Params` suffixes + * [types] [\#2735](https://github.com/tendermint/tendermint/issues/2735) Simplify Proposal message to align with spec + +* Blockchain Protocol + * [crypto/tmhash] [\#2732](https://github.com/tendermint/tendermint/issues/2732) TMHASH is now full 32-byte SHA256 + * All hashes in the block header and Merkle trees are now 32-bytes + * PubKey Addresses are still only 20-bytes + * [state] [\#2587](https://github.com/tendermint/tendermint/issues/2587) Require block.Time of the fist block to be genesis time + * [state] [\#2644](https://github.com/tendermint/tendermint/issues/2644) Require block.Version to match state.Version + * [types] Update SignBytes for `Vote`/`Proposal`/`Heartbeat`: + * [\#2459](https://github.com/tendermint/tendermint/issues/2459) Use amino encoding instead of JSON in `SignBytes`. + * [\#2598](https://github.com/tendermint/tendermint/issues/2598) Reorder fields and use fixed sized encoding. + * [\#2598](https://github.com/tendermint/tendermint/issues/2598) Change `Type` field from `string` to `byte` and use new + `SignedMsgType` to enumerate. + * [types] [\#2730](https://github.com/tendermint/tendermint/issues/2730) Use + same order for fields in `Vote` as in the SignBytes + * [types] [\#2732](https://github.com/tendermint/tendermint/issues/2732) Remove the address field from the validator hash + * [types] [\#2644](https://github.com/tendermint/tendermint/issues/2644) Add Version struct to Header + * [types] [\#2609](https://github.com/tendermint/tendermint/issues/2609) ConsensusParams.Hash() is the hash of the amino encoded + struct instead of the Merkle tree of the fields + * [types] [\#2670](https://github.com/tendermint/tendermint/issues/2670) Header.Hash() builds Merkle tree out of fields in the same + order they appear in the header, instead of sorting by field name + * [types] [\#2682](https://github.com/tendermint/tendermint/issues/2682) Use proto3 `varint` encoding for ints that are usually unsigned (instead of zigzag encoding). + * [types] [\#2636](https://github.com/tendermint/tendermint/issues/2636) Add Validator field to ConsensusParams + (Used to control which pubkey types validators can use, by abci type). + +* P2P Protocol + * [consensus] [\#2652](https://github.com/tendermint/tendermint/issues/2652) + Replace `CommitStepMessage` with `NewValidBlockMessage` + * [consensus] [\#2735](https://github.com/tendermint/tendermint/issues/2735) Simplify `Proposal` message to align with spec + * [consensus] [\#2730](https://github.com/tendermint/tendermint/issues/2730) + Add `Type` field to `Proposal` and use same order of fields as in the + SignBytes for both `Proposal` and `Vote` + * [p2p] [\#2654](https://github.com/tendermint/tendermint/issues/2654) Add `ProtocolVersion` struct with protocol versions to top of + DefaultNodeInfo and require `ProtocolVersion.Block` to match during peer handshake + + +### FEATURES: +- [abci] [\#2557](https://github.com/tendermint/tendermint/issues/2557) Add `Codespace` field to `Response{CheckTx, DeliverTx, Query}` +- [abci] [\#2662](https://github.com/tendermint/tendermint/issues/2662) Add `BlockVersion` and `P2PVersion` to `RequestInfo` +- [crypto/merkle] [\#2298](https://github.com/tendermint/tendermint/issues/2298) General Merkle Proof scheme for chaining various types of Merkle trees together + +### IMPROVEMENTS: +- Additional Metrics + - [consensus] [\#2169](https://github.com/cosmos/cosmos-sdk/issues/2169) + - [p2p] [\#2169](https://github.com/cosmos/cosmos-sdk/issues/2169) +- [config] [\#2232](https://github.com/tendermint/tendermint/issues/2232) Added ValidateBasic method, which performs basic checks +- [crypto/ed25519] [\#2558](https://github.com/tendermint/tendermint/issues/2558) Switch to use latest `golang.org/x/crypto` through our fork at + github.com/tendermint/crypto +- [libs/log] [\#2707](https://github.com/tendermint/tendermint/issues/2707) Add year to log format (@yutianwu) +- [tools] [\#2238](https://github.com/tendermint/tendermint/issues/2238) Binary dependencies are now locked to a specific git commit + +### BUG FIXES: +- [\#2711](https://github.com/tendermint/tendermint/issues/2711) Validate all incoming reactor messages. Fixes various bugs due to negative ints. +- [autofile] [\#2428](https://github.com/tendermint/tendermint/issues/2428) Group.RotateFile need call Flush() before rename (@goolAdapter) +- [common] [\#2533](https://github.com/tendermint/tendermint/issues/2533) Fixed a bug in the `BitArray.Or` method +- [common] [\#2506](https://github.com/tendermint/tendermint/issues/2506) Fixed a bug in the `BitArray.Sub` method (@james-ray) +- [common] [\#2534](https://github.com/tendermint/tendermint/issues/2534) Fix `BitArray.PickRandom` to choose uniformly from true bits +- [consensus] [\#1690](https://github.com/tendermint/tendermint/issues/1690) Wait for + timeoutPrecommit before starting next round +- [consensus] [\#1745](https://github.com/tendermint/tendermint/issues/1745) Wait for + Proposal or timeoutProposal before entering prevote +- [consensus] [\#2642](https://github.com/tendermint/tendermint/issues/2642) Only propose ValidBlock, not LockedBlock +- [consensus] [\#2642](https://github.com/tendermint/tendermint/issues/2642) Initialized ValidRound and LockedRound to -1 +- [consensus] [\#1637](https://github.com/tendermint/tendermint/issues/1637) Limit the amount of evidence that can be included in a + block +- [consensus] [\#2652](https://github.com/tendermint/tendermint/issues/2652) Ensure valid block property with faulty proposer +- [evidence] [\#2515](https://github.com/tendermint/tendermint/issues/2515) Fix db iter leak (@goolAdapter) +- [libs/event] [\#2518](https://github.com/tendermint/tendermint/issues/2518) Fix event concurrency flaw (@goolAdapter) +- [node] [\#2434](https://github.com/tendermint/tendermint/issues/2434) Make node respond to signal interrupts while sleeping for genesis time +- [state] [\#2616](https://github.com/tendermint/tendermint/issues/2616) Pass nil to NewValidatorSet() when genesis file's Validators field is nil +- [p2p] [\#2555](https://github.com/tendermint/tendermint/issues/2555) Fix p2p switch FlushThrottle value (@goolAdapter) +- [p2p] [\#2668](https://github.com/tendermint/tendermint/issues/2668) Reconnect to originally dialed address (not self-reported + address) for persistent peers + + ## v0.25.0 *September 22, 2018* diff --git a/CHANGELOG_PENDING.md b/CHANGELOG_PENDING.md index cad2f444..f5e56a12 100644 --- a/CHANGELOG_PENDING.md +++ b/CHANGELOG_PENDING.md @@ -1,124 +1,27 @@ # Pending -## v0.26.0 +## v0.26.1 -*October 29, 2018* +*TBA* Special thanks to external contributors on this release: -@bradyjoestar, @connorwstein, @goolAdapter, @HaoyangLiu, -@james-ray, @overbool, @phymbert, @Slamper, @Uzair1995 - -This release is primarily about adding Version fields to various data structures, -optimizing consensus messages for signing and verification in -restricted environments (like HSMs and the Ethereum Virtual Machine), and -aligning the consensus code with the [specification](https://arxiv.org/abs/1807.04938). -It also includes our first take at a generalized merkle proof system. - -See the [UPGRADING.md](UPGRADING.md#v0.26.0) for details on upgrading to the new -version. - -Please note that we are still making breaking changes to the protocols. -While the new Version fields should help us to keep the software backwards compatible -even while upgrading the protocols, we cannot guarantee that new releases will -be compatible with old chains just yet. Thanks for bearing with us! Friendly reminder, we have a [bug bounty program](https://hackerone.com/tendermint). ### BREAKING CHANGES: * CLI/RPC/Config - * [config] [\#2232](https://github.com/tendermint/tendermint/issues/2232) timeouts as time.Duration, not ints - * [config] [\#2505](https://github.com/tendermint/tendermint/issues/2505) Remove Mempool.RecheckEmpty (it was effectively useless anyways) - * [config] [\#2490](https://github.com/tendermint/tendermint/issues/2490) `mempool.wal` is disabled by default - * [privval] [\#2459](https://github.com/tendermint/tendermint/issues/2459) Split `SocketPVMsg`s implementations into Request and Response, where the Response may contain a error message (returned by the remote signer) - * [state] [\#2644](https://github.com/tendermint/tendermint/issues/2644) Add Version field to State, breaking the format of State as - encoded on disk. - * [rpc] [\#2298](https://github.com/tendermint/tendermint/issues/2298) `/abci_query` takes `prove` argument instead of `trusted` and switches the default - behaviour to `prove=false` - * [rpc] [\#2654](https://github.com/tendermint/tendermint/issues/2654) Remove all `node_info.other.*_version` fields in `/status` and - `/net_info` * Apps - * [abci] [\#2298](https://github.com/tendermint/tendermint/issues/2298) ResponseQuery.Proof is now a structured merkle.Proof, not just - arbitrary bytes - * [abci] [\#2644](https://github.com/tendermint/tendermint/issues/2644) Add Version to Header and shift all fields by one - * [abci] [\#2662](https://github.com/tendermint/tendermint/issues/2662) Bump the field numbers for some `ResponseInfo` fields to make room for - `AppVersion` * Go API - * [config] [\#2232](https://github.com/tendermint/tendermint/issues/2232) timeouts as time.Duration, not ints - * [crypto/merkle & lite] [\#2298](https://github.com/tendermint/tendermint/issues/2298) Various changes to accomodate General Merkle trees - * [crypto/merkle] [\#2595](https://github.com/tendermint/tendermint/issues/2595) Remove all Hasher objects in favor of byte slices - * [crypto/merkle] [\#2635](https://github.com/tendermint/tendermint/issues/2635) merkle.SimpleHashFromTwoHashes is no longer exported - * [node] [\#2479](https://github.com/tendermint/tendermint/issues/2479) Remove node.RunForever - * [rpc/client] [\#2298](https://github.com/tendermint/tendermint/issues/2298) `ABCIQueryOptions.Trusted` -> `ABCIQueryOptions.Prove` - * [types] [\#2298](https://github.com/tendermint/tendermint/issues/2298) Remove `Index` and `Total` fields from `TxProof`. - * [types] [\#2598](https://github.com/tendermint/tendermint/issues/2598) `VoteTypeXxx` are now of type `SignedMsgType byte` and named `XxxType`, eg. `PrevoteType`, - `PrecommitType`. * Blockchain Protocol - * [abci] [\#2636](https://github.com/tendermint/tendermint/issues/2636) Add ValidatorParams field to ConsensusParams. - (Used to control which pubkey types validators can use, by abci type) - * [types] Update SignBytes for `Vote`/`Proposal`/`Heartbeat`: - * [\#2459](https://github.com/tendermint/tendermint/issues/2459) Use amino encoding instead of JSON in `SignBytes`. - * [\#2598](https://github.com/tendermint/tendermint/issues/2598) Reorder fields and use fixed sized encoding. - * [\#2598](https://github.com/tendermint/tendermint/issues/2598) Change `Type` field fromt `string` to `byte` and use new - `SignedMsgType` to enumerate. - * [types] [\#2512](https://github.com/tendermint/tendermint/issues/2512) Remove the pubkey field from the validator hash - * [types] [\#2644](https://github.com/tendermint/tendermint/issues/2644) Add Version struct to Header - * [types] [\#2609](https://github.com/tendermint/tendermint/issues/2609) ConsensusParams.Hash() is the hash of the amino encoded - struct instead of the Merkle tree of the fields - * [state] [\#2587](https://github.com/tendermint/tendermint/issues/2587) Require block.Time of the fist block to be genesis time - * [state] [\#2644](https://github.com/tendermint/tendermint/issues/2644) Require block.Version to match state.Version - * [types] [\#2670](https://github.com/tendermint/tendermint/issues/2670) Header.Hash() builds Merkle tree out of fields in the same - order they appear in the header, instead of sorting by field name - * [types] [\#2682](https://github.com/tendermint/tendermint/issues/2682) Use proto3 `varint` encoding for ints that are usually unsigned (instead of zigzag encoding). * P2P Protocol - * [p2p] [\#2654](https://github.com/tendermint/tendermint/issues/2654) Add `ProtocolVersion` struct with protocol versions to top of - DefaultNodeInfo and require `ProtocolVersion.Block` to match during peer handshake ### FEATURES: -- [abci] [\#2557](https://github.com/tendermint/tendermint/issues/2557) Add `Codespace` field to `Response{CheckTx, DeliverTx, Query}` -- [abci] [\#2662](https://github.com/tendermint/tendermint/issues/2662) Add `BlockVersion` and `P2PVersion` to `RequestInfo` -- [crypto/merkle] [\#2298](https://github.com/tendermint/tendermint/issues/2298) General Merkle Proof scheme for chaining various types of Merkle trees together ### IMPROVEMENTS: -- Additional Metrics - - [consensus] [\#2169](https://github.com/cosmos/cosmos-sdk/issues/2169) - - [p2p] [\#2169](https://github.com/cosmos/cosmos-sdk/issues/2169) -- [config] [\#2232](https://github.com/tendermint/tendermint/issues/2232) Added ValidateBasic method, which performs basic checks -- [crypto/ed25519] [\#2558](https://github.com/tendermint/tendermint/issues/2558) Switch to use latest `golang.org/x/crypto` through our fork at - github.com/tendermint/crypto -- [tools] [\#2238](https://github.com/tendermint/tendermint/issues/2238) Binary dependencies are now locked to a specific git commit -- [libs/log] [\#2706](https://github.com/tendermint/tendermint/issues/2706) Add year to log format -- [consensus] [\#2683] validate all incoming messages -- [evidence] [\#2683] validate all incoming messages -- [blockchain] [\#2683] validate all incoming messages -- [p2p/pex] [\#2683] validate pexAddrsMessage addresses ### BUG FIXES: -- [autofile] [\#2428](https://github.com/tendermint/tendermint/issues/2428) Group.RotateFile need call Flush() before rename (@goolAdapter) -- [common] [\#2533](https://github.com/tendermint/tendermint/issues/2533) Fixed a bug in the `BitArray.Or` method -- [common] [\#2506](https://github.com/tendermint/tendermint/issues/2506) Fixed a bug in the `BitArray.Sub` method (@james-ray) -- [common] [\#2534](https://github.com/tendermint/tendermint/issues/2534) Fix `BitArray.PickRandom` to choose uniformly from true bits -- [consensus] [\#1690](https://github.com/tendermint/tendermint/issues/1690) Wait for - timeoutPrecommit before starting next round -- [consensus] [\#1745](https://github.com/tendermint/tendermint/issues/1745) Wait for - Proposal or timeoutProposal before entering prevote -- [consensus] [\#2583](https://github.com/tendermint/tendermint/issues/2583) ensure valid - block property with faulty proposer -- [consensus] [\#2642](https://github.com/tendermint/tendermint/issues/2642) Only propose ValidBlock, not LockedBlock -- [consensus] [\#2642](https://github.com/tendermint/tendermint/issues/2642) Initialized ValidRound and LockedRound to -1 -- [consensus] [\#1637](https://github.com/tendermint/tendermint/issues/1637) Limit the amount of evidence that can be included in a - block -- [consensus] [\#2646](https://github.com/tendermint/tendermint/issues/2646) Simplify Proposal message (align with spec) -- [crypto] [\#2733](https://github.com/tendermint/tendermint/pull/2733) Fix general merkle keypath to start w/ last op's key -- [evidence] [\#2515](https://github.com/tendermint/tendermint/issues/2515) Fix db iter leak (@goolAdapter) -- [libs/event] [\#2518](https://github.com/tendermint/tendermint/issues/2518) Fix event concurrency flaw (@goolAdapter) -- [node] [\#2434](https://github.com/tendermint/tendermint/issues/2434) Make node respond to signal interrupts while sleeping for genesis time -- [state] [\#2616](https://github.com/tendermint/tendermint/issues/2616) Pass nil to NewValidatorSet() when genesis file's Validators field is nil -- [p2p] [\#2555](https://github.com/tendermint/tendermint/issues/2555) Fix p2p switch FlushThrottle value (@goolAdapter) -- [p2p] [\#2668](https://github.com/tendermint/tendermint/issues/2668) Reconnect to originally dialed address (not self-reported - address) for persistent peers - diff --git a/UPGRADING.md b/UPGRADING.md index cb0830a4..055dbec4 100644 --- a/UPGRADING.md +++ b/UPGRADING.md @@ -5,7 +5,7 @@ a newer version of Tendermint Core. ## v0.26.0 -New 0.26.0 release contains a lot of changes to core data types. It is not +New 0.26.0 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. @@ -33,7 +33,7 @@ to `prove`. To get proofs with your queries, ensure you set `prove=true`. Various version fields like `amino_version`, `p2p_version`, `consensus_version`, and `rpc_version` have been removed from the `node_info.other` and are consolidated under the tendermint semantic version (ie. `node_info.version`) and -the new `block` and `p2p` protocol versions under `node_info.protocol_version`.. +the new `block` and `p2p` protocol versions under `node_info.protocol_version`. ### ABCI Changes @@ -45,7 +45,7 @@ protobuf file for these changes. The `ResponseQuery.Proof` field is now structured as a `[]ProofOp` to support generalized Merkle tree constructions where the leaves of one Merkle tree are -the root of another. If you don't need this functionaluty, and you used to +the root of another. If you don't need this functionality, and you used to return `` here, you should instead return a single `ProofOp` with just the `Data` field set: @@ -79,6 +79,10 @@ The `node.RunForever` function was removed. Signal handling and running forever should instead be explicitly configured by the caller. See how we do it [here](https://github.com/tendermint/tendermint/blob/30519e8361c19f4bf320ef4d26288ebc621ad725/cmd/tendermint/commands/run_node.go#L60). +### Other + +All hashes, except for public key addresses, are now 32-bytes. + ## v0.25.0 This release has minimal impact. From 7246ffc48fa5a45daa5acddcf5b726b7bb6f259c Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Mon, 5 Nov 2018 22:32:52 -0800 Subject: [PATCH 052/267] mempool: ErrPreCheck and more log info (#2724) * mempool: ErrPreCheck and more log info * change Pre/PostCheckFunc to return errors also, continue execution when checking txs in mempool_test if err=PreCheckErr --- mempool/mempool.go | 62 +++++++++++++++++++++++++++++++++-------- mempool/mempool_test.go | 54 +++++++++++++---------------------- 2 files changed, 69 insertions(+), 47 deletions(-) diff --git a/mempool/mempool.go b/mempool/mempool.go index 65cd5535..5e52989a 100644 --- a/mempool/mempool.go +++ b/mempool/mempool.go @@ -25,12 +25,12 @@ import ( // PreCheckFunc is an optional filter executed before CheckTx and rejects // transaction if false is returned. An example would be to ensure that a // transaction doesn't exceeded the block size. -type PreCheckFunc func(types.Tx) bool +type PreCheckFunc func(types.Tx) error // PostCheckFunc is an optional filter executed after CheckTx and rejects // transaction if false is returned. An example would be to ensure a // transaction doesn't require more gas than available for the block. -type PostCheckFunc func(types.Tx, *abci.ResponseCheckTx) bool +type PostCheckFunc func(types.Tx, *abci.ResponseCheckTx) error /* @@ -68,24 +68,48 @@ var ( ErrMempoolIsFull = errors.New("Mempool is full") ) +// ErrPreCheck is returned when tx is too big +type ErrPreCheck struct { + Reason error +} + +func (e ErrPreCheck) Error() string { + return e.Reason.Error() +} + +// IsPreCheckError returns true if err is due to pre check failure. +func IsPreCheckError(err error) bool { + _, ok := err.(ErrPreCheck) + return ok +} + // PreCheckAminoMaxBytes checks that the size of the transaction plus the amino // overhead is smaller or equal to the expected maxBytes. func PreCheckAminoMaxBytes(maxBytes int64) PreCheckFunc { - return func(tx types.Tx) bool { + return func(tx types.Tx) error { // We have to account for the amino overhead in the tx size as well aminoOverhead := amino.UvarintSize(uint64(len(tx))) - return int64(len(tx)+aminoOverhead) <= maxBytes + txSize := int64(len(tx) + aminoOverhead) + if txSize > maxBytes { + return fmt.Errorf("Tx size (including amino overhead) is too big: %d, max: %d", + txSize, maxBytes) + } + return nil } } // PostCheckMaxGas checks that the wanted gas is smaller or equal to the passed -// maxGas. Returns true if maxGas is -1. +// maxGas. Returns nil if maxGas is -1. func PostCheckMaxGas(maxGas int64) PostCheckFunc { - return func(tx types.Tx, res *abci.ResponseCheckTx) bool { + return func(tx types.Tx, res *abci.ResponseCheckTx) error { if maxGas == -1 { - return true + return nil } - return res.GasWanted <= maxGas + if res.GasWanted > maxGas { + return fmt.Errorf("gas wanted %d is greater than max gas %d", + res.GasWanted, maxGas) + } + return nil } } @@ -285,8 +309,10 @@ func (mem *Mempool) CheckTx(tx types.Tx, cb func(*abci.Response)) (err error) { return ErrMempoolIsFull } - if mem.preCheck != nil && !mem.preCheck(tx) { - return + if mem.preCheck != nil { + if err := mem.preCheck(tx); err != nil { + return ErrPreCheck{err} + } } // CACHE @@ -346,7 +372,13 @@ func (mem *Mempool) resCbNormal(req *abci.Request, res *abci.Response) { tx: tx, } mem.txs.PushBack(memTx) - mem.logger.Info("Added good transaction", "tx", TxID(tx), "res", r, "total", mem.Size()) + mem.logger.Info("Added good transaction", + "tx", TxID(tx), + "res", r, + "height", memTx.height, + "total", mem.Size(), + "counter", memTx.counter, + ) mem.metrics.TxSizeBytes.Observe(float64(len(tx))) mem.notifyTxsAvailable() } else { @@ -566,7 +598,13 @@ func (mem *Mempool) recheckTxs(goodTxs []types.Tx) { } func (mem *Mempool) isPostCheckPass(tx types.Tx, r *abci.ResponseCheckTx) bool { - return mem.postCheck == nil || mem.postCheck(tx, r) + if mem.postCheck == nil { + return true + } + if err := mem.postCheck(tx, r); err != nil { + return false + } + return true } //-------------------------------------------------------------------------------- diff --git a/mempool/mempool_test.go b/mempool/mempool_test.go index 5aabd00e..44917afb 100644 --- a/mempool/mempool_test.go +++ b/mempool/mempool_test.go @@ -14,7 +14,6 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - amino "github.com/tendermint/go-amino" "github.com/tendermint/tendermint/abci/example/counter" "github.com/tendermint/tendermint/abci/example/kvstore" abci "github.com/tendermint/tendermint/abci/types" @@ -66,7 +65,10 @@ func checkTxs(t *testing.T, mempool *Mempool, count int) types.Txs { t.Error(err) } if err := mempool.CheckTx(txBytes, nil); err != nil { - t.Fatalf("Error after CheckTx: %v", err) + if IsPreCheckError(err) { + continue + } + t.Fatalf("CheckTx failed: %v while checking #%d tx", err, i) } } return txs @@ -126,47 +128,29 @@ func TestMempoolFilters(t *testing.T) { mempool := newMempoolWithApp(cc) emptyTxArr := []types.Tx{[]byte{}} - nopPreFilter := func(tx types.Tx) bool { return true } - nopPostFilter := func(tx types.Tx, res *abci.ResponseCheckTx) bool { return true } - - // This is the same filter we expect to be used within node/node.go and state/execution.go - nBytePreFilter := func(n int) func(tx types.Tx) bool { - return func(tx types.Tx) bool { - // We have to account for the amino overhead in the tx size as well - aminoOverhead := amino.UvarintSize(uint64(len(tx))) - return (len(tx) + aminoOverhead) <= n - } - } - - nGasPostFilter := func(n int64) func(tx types.Tx, res *abci.ResponseCheckTx) bool { - return func(tx types.Tx, res *abci.ResponseCheckTx) bool { - if n == -1 { - return true - } - return res.GasWanted <= n - } - } + nopPreFilter := func(tx types.Tx) error { return nil } + nopPostFilter := func(tx types.Tx, res *abci.ResponseCheckTx) error { return nil } // each table driven test creates numTxsToCreate txs with checkTx, and at the end clears all remaining txs. // each tx has 20 bytes + amino overhead = 21 bytes, 1 gas tests := []struct { numTxsToCreate int - preFilter func(tx types.Tx) bool - postFilter func(tx types.Tx, res *abci.ResponseCheckTx) bool + preFilter PreCheckFunc + postFilter PostCheckFunc expectedNumTxs int }{ {10, nopPreFilter, nopPostFilter, 10}, - {10, nBytePreFilter(10), nopPostFilter, 0}, - {10, nBytePreFilter(20), nopPostFilter, 0}, - {10, nBytePreFilter(21), nopPostFilter, 10}, - {10, nopPreFilter, nGasPostFilter(-1), 10}, - {10, nopPreFilter, nGasPostFilter(0), 0}, - {10, nopPreFilter, nGasPostFilter(1), 10}, - {10, nopPreFilter, nGasPostFilter(3000), 10}, - {10, nBytePreFilter(10), nGasPostFilter(20), 0}, - {10, nBytePreFilter(30), nGasPostFilter(20), 10}, - {10, nBytePreFilter(21), nGasPostFilter(1), 10}, - {10, nBytePreFilter(21), nGasPostFilter(0), 0}, + {10, PreCheckAminoMaxBytes(10), nopPostFilter, 0}, + {10, PreCheckAminoMaxBytes(20), nopPostFilter, 0}, + {10, PreCheckAminoMaxBytes(21), nopPostFilter, 10}, + {10, nopPreFilter, PostCheckMaxGas(-1), 10}, + {10, nopPreFilter, PostCheckMaxGas(0), 0}, + {10, nopPreFilter, PostCheckMaxGas(1), 10}, + {10, nopPreFilter, PostCheckMaxGas(3000), 10}, + {10, PreCheckAminoMaxBytes(10), PostCheckMaxGas(20), 0}, + {10, PreCheckAminoMaxBytes(30), PostCheckMaxGas(20), 10}, + {10, PreCheckAminoMaxBytes(21), PostCheckMaxGas(1), 10}, + {10, PreCheckAminoMaxBytes(21), PostCheckMaxGas(0), 0}, } for tcIndex, tt := range tests { mempool.Update(1, emptyTxArr, tt.preFilter, tt.postFilter) From b8a9b0bf782c17d97af3fda46ddc73f7d35bd680 Mon Sep 17 00:00:00 2001 From: Anton Kaliaev Date: Tue, 6 Nov 2018 07:39:05 +0100 Subject: [PATCH 053/267] Mempool WAL is still created by default in home directory, leads to permission errors (#2758) * only invoke InitWAL/CloseWAL if WalPath is not empty Closes #2717 * panic if WAL is not initialized when calling CloseWAL * add a changelog entry --- CHANGELOG_PENDING.md | 1 + config/config.go | 5 +++++ mempool/mempool.go | 46 ++++++++++++++++++----------------------- mempool/mempool_test.go | 7 ++----- node/node.go | 11 ++++++++-- 5 files changed, 37 insertions(+), 33 deletions(-) diff --git a/CHANGELOG_PENDING.md b/CHANGELOG_PENDING.md index f5e56a12..ea0a666b 100644 --- a/CHANGELOG_PENDING.md +++ b/CHANGELOG_PENDING.md @@ -25,3 +25,4 @@ Friendly reminder, we have a [bug bounty program](https://hackerone.com/tendermi ### IMPROVEMENTS: ### BUG FIXES: +- [mempool] fix a bug where we create a WAL despite `wal_dir` being empty diff --git a/config/config.go b/config/config.go index ede57207..fa618211 100644 --- a/config/config.go +++ b/config/config.go @@ -497,6 +497,11 @@ func (cfg *MempoolConfig) WalDir() string { return rootify(cfg.WalPath, cfg.RootDir) } +// WalEnabled returns true if the WAL is enabled. +func (cfg *MempoolConfig) WalEnabled() bool { + return cfg.WalPath != "" +} + // ValidateBasic performs basic validation (checking param bounds, etc.) and // returns an error if any check fails. func (cfg *MempoolConfig) ValidateBasic() error { diff --git a/mempool/mempool.go b/mempool/mempool.go index 5e52989a..9c956316 100644 --- a/mempool/mempool.go +++ b/mempool/mempool.go @@ -213,39 +213,33 @@ func WithMetrics(metrics *Metrics) MempoolOption { return func(mem *Mempool) { mem.metrics = metrics } } +// InitWAL creates a directory for the WAL file and opens a file itself. +// +// *panics* if can't create directory or open file. +// *not thread safe* +func (mem *Mempool) InitWAL() { + walDir := mem.config.WalDir() + err := cmn.EnsureDir(walDir, 0700) + if err != nil { + panic(errors.Wrap(err, "Error ensuring Mempool WAL dir")) + } + af, err := auto.OpenAutoFile(walDir + "/wal") + if err != nil { + panic(errors.Wrap(err, "Error opening Mempool WAL file")) + } + mem.wal = af +} + // CloseWAL closes and discards the underlying WAL file. // Any further writes will not be relayed to disk. -func (mem *Mempool) CloseWAL() bool { - if mem == nil { - return false - } - +func (mem *Mempool) CloseWAL() { mem.proxyMtx.Lock() defer mem.proxyMtx.Unlock() - if mem.wal == nil { - return false - } - if err := mem.wal.Close(); err != nil && mem.logger != nil { - mem.logger.Error("Mempool.CloseWAL", "err", err) + if err := mem.wal.Close(); err != nil { + mem.logger.Error("Error closing WAL", "err", err) } mem.wal = nil - return true -} - -func (mem *Mempool) InitWAL() { - walDir := mem.config.WalDir() - if walDir != "" { - err := cmn.EnsureDir(walDir, 0700) - if err != nil { - cmn.PanicSanity(errors.Wrap(err, "Error ensuring Mempool wal dir")) - } - af, err := auto.OpenAutoFile(walDir + "/wal") - if err != nil { - cmn.PanicSanity(errors.Wrap(err, "Error opening Mempool wal file")) - } - mem.wal = af - } } // Lock locks the mempool. The consensus must be able to hold lock to safely update. diff --git a/mempool/mempool_test.go b/mempool/mempool_test.go index 44917afb..b8df6f73 100644 --- a/mempool/mempool_test.go +++ b/mempool/mempool_test.go @@ -369,15 +369,12 @@ func TestMempoolCloseWAL(t *testing.T) { // 7. Invoke CloseWAL() and ensure it discards the // WAL thus any other write won't go through. - require.True(t, mempool.CloseWAL(), "CloseWAL should CloseWAL") + mempool.CloseWAL() mempool.CheckTx(types.Tx([]byte("bar")), nil) sum2 := checksumFile(walFilepath, t) require.Equal(t, sum1, sum2, "expected no change to the WAL after invoking CloseWAL() since it was discarded") - // 8. Second CloseWAL should do nothing - require.False(t, mempool.CloseWAL(), "CloseWAL should CloseWAL") - - // 9. Sanity check to ensure that the WAL file still exists + // 8. Sanity check to ensure that the WAL file still exists m3, err := filepath.Glob(filepath.Join(rootDir, "*")) require.Nil(t, err, "successful globbing expected") require.Equal(t, 1, len(m3), "expecting the wal match in") diff --git a/node/node.go b/node/node.go index f62a8b47..4710397f 100644 --- a/node/node.go +++ b/node/node.go @@ -13,8 +13,8 @@ import ( "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus/promhttp" - "github.com/tendermint/go-amino" + amino "github.com/tendermint/go-amino" abci "github.com/tendermint/tendermint/abci/types" bc "github.com/tendermint/tendermint/blockchain" cfg "github.com/tendermint/tendermint/config" @@ -279,7 +279,9 @@ func NewNode(config *cfg.Config, ) mempoolLogger := logger.With("module", "mempool") mempool.SetLogger(mempoolLogger) - mempool.InitWAL() // no need to have the mempool wal during tests + if config.Mempool.WalEnabled() { + mempool.InitWAL() // no need to have the mempool wal during tests + } mempoolReactor := mempl.NewMempoolReactor(config.Mempool, mempool) mempoolReactor.SetLogger(mempoolLogger) @@ -586,6 +588,11 @@ func (n *Node) OnStop() { // TODO: gracefully disconnect from peers. n.sw.Stop() + // stop mempool WAL + if n.config.Mempool.WalEnabled() { + n.mempoolReactor.Mempool.CloseWAL() + } + if err := n.transport.Close(); err != nil { n.Logger.Error("Error closing transport", "err", err) } From 03e42d2e3866f01a00625f608e3bbfaeb30690de Mon Sep 17 00:00:00 2001 From: Jae Kwon Date: Mon, 5 Nov 2018 22:53:44 -0800 Subject: [PATCH 054/267] =?UTF-8?q?Fix=20crypto/merkle=20ProofOperators.Ve?= =?UTF-8?q?rify=20to=20check=20bounds=20on=20keypath=20pa=E2=80=A6=20(#275?= =?UTF-8?q?6)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Fix crypto/merkle ProofOperators.Verify to check bounds on keypath parts. * Update PENDING --- CHANGELOG_PENDING.md | 2 ++ crypto/merkle/proof.go | 3 +++ crypto/merkle/proof_test.go | 4 ++++ 3 files changed, 9 insertions(+) diff --git a/CHANGELOG_PENDING.md b/CHANGELOG_PENDING.md index ea0a666b..68a55039 100644 --- a/CHANGELOG_PENDING.md +++ b/CHANGELOG_PENDING.md @@ -25,4 +25,6 @@ Friendly reminder, we have a [bug bounty program](https://hackerone.com/tendermi ### IMPROVEMENTS: ### BUG FIXES: + +- [crypto/merkle] [\#2756](https://github.com/tendermint/tendermint/issues/2756) Fix crypto/merkle ProofOperators.Verify to check bounds on keypath parts. - [mempool] fix a bug where we create a WAL despite `wal_dir` being empty diff --git a/crypto/merkle/proof.go b/crypto/merkle/proof.go index 5705c96b..8f8b460c 100644 --- a/crypto/merkle/proof.go +++ b/crypto/merkle/proof.go @@ -43,6 +43,9 @@ func (poz ProofOperators) Verify(root []byte, keypath string, args [][]byte) (er for i, op := range poz { key := op.GetKey() if len(key) != 0 { + if len(keys) == 0 { + return cmn.NewError("Key path has insufficient # of parts: expected no more keys but got %+v", string(key)) + } lastKey := keys[len(keys)-1] if !bytes.Equal(lastKey, key) { return cmn.NewError("Key mismatch on operation #%d: expected %+v but got %+v", i, string(lastKey), string(key)) diff --git a/crypto/merkle/proof_test.go b/crypto/merkle/proof_test.go index cc208e9a..320b9188 100644 --- a/crypto/merkle/proof_test.go +++ b/crypto/merkle/proof_test.go @@ -107,6 +107,10 @@ func TestProofOperators(t *testing.T) { err = popz.Verify(bz("OUTPUT4"), "//KEY4/KEY2/KEY1", [][]byte{bz("INPUT1")}) assert.NotNil(t, err) + // BAD KEY 5 + err = popz.Verify(bz("OUTPUT4"), "/KEY2/KEY1", [][]byte{bz("INPUT1")}) + assert.NotNil(t, err) + // BAD OUTPUT 1 err = popz.Verify(bz("OUTPUT4_WRONG"), "/KEY4/KEY2/KEY1", [][]byte{bz("INPUT1")}) assert.NotNil(t, err) From d460df1335fb3e0af2a9346c16cc35013552ba5d Mon Sep 17 00:00:00 2001 From: Anton Kaliaev Date: Wed, 7 Nov 2018 05:23:44 +0100 Subject: [PATCH 055/267] mempool: print postCheck error (#2762) This is a follow-up from https://github.com/tendermint/tendermint/pull/2724 Closes #2761 --- mempool/mempool.go | 29 ++++++++++++++--------------- mempool/mempool_test.go | 3 +++ 2 files changed, 17 insertions(+), 15 deletions(-) diff --git a/mempool/mempool.go b/mempool/mempool.go index 9c956316..15e3119c 100644 --- a/mempool/mempool.go +++ b/mempool/mempool.go @@ -356,8 +356,11 @@ func (mem *Mempool) resCbNormal(req *abci.Request, res *abci.Response) { switch r := res.Value.(type) { case *abci.Response_CheckTx: tx := req.GetCheckTx().Tx - if (r.CheckTx.Code == abci.CodeTypeOK) && - mem.isPostCheckPass(tx, r.CheckTx) { + var postCheckErr error + if mem.postCheck != nil { + postCheckErr = mem.postCheck(tx, r.CheckTx) + } + if (r.CheckTx.Code == abci.CodeTypeOK) && postCheckErr == nil { mem.counter++ memTx := &mempoolTx{ counter: mem.counter, @@ -377,7 +380,7 @@ func (mem *Mempool) resCbNormal(req *abci.Request, res *abci.Response) { mem.notifyTxsAvailable() } else { // ignore bad transaction - mem.logger.Info("Rejected bad transaction", "tx", TxID(tx), "res", r) + mem.logger.Info("Rejected bad transaction", "tx", TxID(tx), "res", r, "err", postCheckErr) mem.metrics.FailedTxs.Add(1) // remove from cache (it might be good later) mem.cache.Remove(tx) @@ -390,6 +393,7 @@ func (mem *Mempool) resCbNormal(req *abci.Request, res *abci.Response) { func (mem *Mempool) resCbRecheck(req *abci.Request, res *abci.Response) { switch r := res.Value.(type) { case *abci.Response_CheckTx: + tx := req.GetCheckTx().Tx memTx := mem.recheckCursor.Value.(*mempoolTx) if !bytes.Equal(req.GetCheckTx().Tx, memTx.tx) { cmn.PanicSanity( @@ -400,15 +404,20 @@ func (mem *Mempool) resCbRecheck(req *abci.Request, res *abci.Response) { ), ) } - if (r.CheckTx.Code == abci.CodeTypeOK) && mem.isPostCheckPass(memTx.tx, r.CheckTx) { + var postCheckErr error + if mem.postCheck != nil { + postCheckErr = mem.postCheck(tx, r.CheckTx) + } + if (r.CheckTx.Code == abci.CodeTypeOK) && postCheckErr == nil { // Good, nothing to do. } else { // Tx became invalidated due to newly committed block. + mem.logger.Info("Tx is no longer valid", "tx", TxID(tx), "res", r, "err", postCheckErr) mem.txs.Remove(mem.recheckCursor) mem.recheckCursor.DetachPrev() // remove from cache (it might be good later) - mem.cache.Remove(req.GetCheckTx().Tx) + mem.cache.Remove(tx) } if mem.recheckCursor == mem.recheckEnd { mem.recheckCursor = nil @@ -591,16 +600,6 @@ func (mem *Mempool) recheckTxs(goodTxs []types.Tx) { mem.proxyAppConn.FlushAsync() } -func (mem *Mempool) isPostCheckPass(tx types.Tx, r *abci.ResponseCheckTx) bool { - if mem.postCheck == nil { - return true - } - if err := mem.postCheck(tx, r); err != nil { - return false - } - return true -} - //-------------------------------------------------------------------------------- // mempoolTx is a transaction that successfully ran diff --git a/mempool/mempool_test.go b/mempool/mempool_test.go index b8df6f73..e25da7fe 100644 --- a/mempool/mempool_test.go +++ b/mempool/mempool_test.go @@ -65,6 +65,9 @@ func checkTxs(t *testing.T, mempool *Mempool, count int) types.Txs { t.Error(err) } if err := mempool.CheckTx(txBytes, nil); err != nil { + // Skip invalid txs. + // TestMempoolFilters will fail otherwise. It asserts a number of txs + // returned. if IsPreCheckError(err) { continue } From 6e9aee546061423864fef3f9fa332ef37c3dca96 Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Tue, 6 Nov 2018 21:12:46 -0800 Subject: [PATCH 056/267] p2p: peer-id -> peer_id (#2771) * p2p: peer-id -> peer_id * update changelog --- CHANGELOG_PENDING.md | 1 + p2p/peer.go | 6 +++--- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/CHANGELOG_PENDING.md b/CHANGELOG_PENDING.md index 68a55039..1996bfcc 100644 --- a/CHANGELOG_PENDING.md +++ b/CHANGELOG_PENDING.md @@ -28,3 +28,4 @@ Friendly reminder, we have a [bug bounty program](https://hackerone.com/tendermi - [crypto/merkle] [\#2756](https://github.com/tendermint/tendermint/issues/2756) Fix crypto/merkle ProofOperators.Verify to check bounds on keypath parts. - [mempool] fix a bug where we create a WAL despite `wal_dir` being empty +- [p2p] \#2771 Fix `peer-id` label name in prometheus metrics diff --git a/p2p/peer.go b/p2p/peer.go index 944174b0..e98c16d2 100644 --- a/p2p/peer.go +++ b/p2p/peer.go @@ -240,7 +240,7 @@ func (p *peer) Send(chID byte, msgBytes []byte) bool { } res := p.mconn.Send(chID, msgBytes) if res { - p.metrics.PeerSendBytesTotal.With("peer-id", string(p.ID())).Add(float64(len(msgBytes))) + p.metrics.PeerSendBytesTotal.With("peer_id", string(p.ID())).Add(float64(len(msgBytes))) } return res } @@ -255,7 +255,7 @@ func (p *peer) TrySend(chID byte, msgBytes []byte) bool { } res := p.mconn.TrySend(chID, msgBytes) if res { - p.metrics.PeerSendBytesTotal.With("peer-id", string(p.ID())).Add(float64(len(msgBytes))) + p.metrics.PeerSendBytesTotal.With("peer_id", string(p.ID())).Add(float64(len(msgBytes))) } return res } @@ -330,7 +330,7 @@ func (p *peer) metricsReporter() { sendQueueSize += float64(chStatus.SendQueueSize) } - p.metrics.PeerPendingSendBytes.With("peer-id", string(p.ID())).Set(sendQueueSize) + p.metrics.PeerPendingSendBytes.With("peer_id", string(p.ID())).Set(sendQueueSize) case <-p.Quit(): return } From 8b773283132dd62a999131cba79130fa8f064bc3 Mon Sep 17 00:00:00 2001 From: Zach Date: Fri, 9 Nov 2018 09:11:06 -0500 Subject: [PATCH 057/267] [docs] improve organization of ABCI docs & fix links (#2749) * dedup with spec/abci/client-server * fixup abci/readme * link to getting started in abci/README * https * spec/abci: some deduplication * docs: remove extraneous comment --- abci/README.md | 156 +++----------------------------- docs/app-dev/app-development.md | 84 ----------------- docs/spec/abci/client-server.md | 44 +++++---- 3 files changed, 36 insertions(+), 248 deletions(-) diff --git a/abci/README.md b/abci/README.md index 63b43e54..110ad40e 100644 --- a/abci/README.md +++ b/abci/README.md @@ -1,7 +1,5 @@ # Application BlockChain Interface (ABCI) -[![CircleCI](https://circleci.com/gh/tendermint/abci.svg?style=svg)](https://circleci.com/gh/tendermint/abci) - Blockchains are systems for multi-master state machine replication. **ABCI** is an interface that defines the boundary between the replication engine (the blockchain), and the state machine (the application). @@ -12,160 +10,28 @@ Previously, the ABCI was referred to as TMSP. The community has provided a number of addtional implementations, see the [Tendermint Ecosystem](https://tendermint.com/ecosystem) + +## Installation & Usage + +To get up and running quickly, see the [getting started guide](../docs/app-dev/getting-started.md) along with the [abci-cli documentation](../docs/app-dev/abci-cli.md) which will go through the examples found in the [examples](./example/) directory. + ## Specification A detailed description of the ABCI methods and message types is contained in: -- [A prose specification](specification.md) -- [A protobuf file](https://github.com/tendermint/tendermint/blob/master/abci/types/types.proto) -- [A Go interface](https://github.com/tendermint/tendermint/blob/master/abci/types/application.go). - -For more background information on ABCI, motivations, and tendermint, please visit [the documentation](https://tendermint.com/docs/). -The two guides to focus on are the `Application Development Guide` and `Using ABCI-CLI`. - +- [The main spec](../docs/spec/abci/abci.md) +- [A protobuf file](./types/types.proto) +- [A Go interface](./types/application.go) ## Protocol Buffers -To compile the protobuf file, run: +To compile the protobuf file, run (from the root of the repo): ``` -cd $GOPATH/src/github.com/tendermint/tendermint/; make protoc_abci +make protoc_abci ``` See `protoc --help` and [the Protocol Buffers site](https://developers.google.com/protocol-buffers) -for details on compiling for other languages. Note we also include a [GRPC](http://www.grpc.io/docs) +for details on compiling for other languages. Note we also include a [GRPC](https://www.grpc.io/docs) service definition. -## Install ABCI-CLI - -The `abci-cli` is a simple tool for debugging ABCI servers and running some -example apps. To install it: - -``` -mkdir -p $GOPATH/src/github.com/tendermint -cd $GOPATH/src/github.com/tendermint -git clone https://github.com/tendermint/tendermint.git -cd tendermint -make get_tools -make get_vendor_deps -make install_abci -``` - -## Implementation - -We provide three implementations of the ABCI in Go: - -- Golang in-process -- ABCI-socket -- GRPC - -Note the GRPC version is maintained primarily to simplify onboarding and prototyping and is not receiving the same -attention to security and performance as the others - -### In Process - -The simplest implementation just uses function calls within Go. -This means ABCI applications written in Golang can be compiled with TendermintCore and run as a single binary. - -See the [examples](#examples) below for more information. - -### Socket (TSP) - -ABCI is best implemented as a streaming protocol. -The socket implementation provides for asynchronous, ordered message passing over unix or tcp. -Messages are serialized using Protobuf3 and length-prefixed with a [signed Varint](https://developers.google.com/protocol-buffers/docs/encoding?csw=1#signed-integers) - -For example, if the Protobuf3 encoded ABCI message is `0xDEADBEEF` (4 bytes), the length-prefixed message is `0x08DEADBEEF`, since `0x08` is the signed varint -encoding of `4`. If the Protobuf3 encoded ABCI message is 65535 bytes long, the length-prefixed message would be like `0xFEFF07...`. - -Note the benefit of using this `varint` encoding over the old version (where integers were encoded as `` is that -it is the standard way to encode integers in Protobuf. It is also generally shorter. - -### GRPC - -GRPC is an rpc framework native to Protocol Buffers with support in many languages. -Implementing the ABCI using GRPC can allow for faster prototyping, but is expected to be much slower than -the ordered, asynchronous socket protocol. The implementation has also not received as much testing or review. - -Note the length-prefixing used in the socket implementation does not apply for GRPC. - -## Usage - -The `abci-cli` tool wraps an ABCI client and can be used for probing/testing an ABCI server. -For instance, `abci-cli test` will run a test sequence against a listening server running the Counter application (see below). -It can also be used to run some example applications. -See [the documentation](https://tendermint.com/docs/) for more details. - -### Examples - -Check out the variety of example applications in the [example directory](example/). -It also contains the code refered to by the `counter` and `kvstore` apps; these apps come -built into the `abci-cli` binary. - -#### Counter - -The `abci-cli counter` application illustrates nonce checking in transactions. It's code looks like: - -```golang -func cmdCounter(cmd *cobra.Command, args []string) error { - - app := counter.NewCounterApplication(flagSerial) - - logger := log.NewTMLogger(log.NewSyncWriter(os.Stdout)) - - // Start the listener - srv, err := server.NewServer(flagAddrC, flagAbci, app) - if err != nil { - return err - } - srv.SetLogger(logger.With("module", "abci-server")) - if err := srv.Start(); err != nil { - return err - } - - // Wait forever - cmn.TrapSignal(func() { - // Cleanup - srv.Stop() - }) - return nil -} -``` - -and can be found in [this file](cmd/abci-cli/abci-cli.go). - -#### kvstore - -The `abci-cli kvstore` application, which illustrates a simple key-value Merkle tree - -```golang -func cmdKVStore(cmd *cobra.Command, args []string) error { - logger := log.NewTMLogger(log.NewSyncWriter(os.Stdout)) - - // Create the application - in memory or persisted to disk - var app types.Application - if flagPersist == "" { - app = kvstore.NewKVStoreApplication() - } else { - app = kvstore.NewPersistentKVStoreApplication(flagPersist) - app.(*kvstore.PersistentKVStoreApplication).SetLogger(logger.With("module", "kvstore")) - } - - // Start the listener - srv, err := server.NewServer(flagAddrD, flagAbci, app) - if err != nil { - return err - } - srv.SetLogger(logger.With("module", "abci-server")) - if err := srv.Start(); err != nil { - return err - } - - // Wait forever - cmn.TrapSignal(func() { - // Cleanup - srv.Stop() - }) - return nil -} -``` diff --git a/docs/app-dev/app-development.md b/docs/app-dev/app-development.md index 2618bb1e..d157ce37 100644 --- a/docs/app-dev/app-development.md +++ b/docs/app-dev/app-development.md @@ -47,90 +47,6 @@ The mempool and consensus logic act as clients, and each maintains an open ABCI connection with the application, which hosts an ABCI server. Shown are the request and response types sent on each connection. -## Message Protocol - -The message protocol consists of pairs of requests and responses. Some -messages have no fields, while others may include byte-arrays, strings, -or integers. See the `message Request` and `message Response` -definitions in [the protobuf definition -file](https://github.com/tendermint/tendermint/blob/develop/abci/types/types.proto), -and the [protobuf -documentation](https://developers.google.com/protocol-buffers/docs/overview) -for more details. - -For each request, a server should respond with the corresponding -response, where order of requests is preserved in the order of -responses. - -## Server - -To use ABCI in your programming language of choice, there must be a ABCI -server in that language. Tendermint supports two kinds of implementation -of the server: - -- Asynchronous, raw socket server (Tendermint Socket Protocol, also - known as TSP or Teaspoon) -- GRPC - -Both can be tested using the `abci-cli` by setting the `--abci` flag -appropriately (ie. to `socket` or `grpc`). - -See examples, in various stages of maintenance, in -[Go](https://github.com/tendermint/tendermint/tree/develop/abci/server), -[JavaScript](https://github.com/tendermint/js-abci), -[Python](https://github.com/tendermint/tendermint/tree/develop/abci/example/python3/abci), -[C++](https://github.com/mdyring/cpp-tmsp), and -[Java](https://github.com/jTendermint/jabci). - -### GRPC - -If GRPC is available in your language, this is the easiest approach, -though it will have significant performance overhead. - -To get started with GRPC, copy in the [protobuf -file](https://github.com/tendermint/tendermint/blob/develop/abci/types/types.proto) -and compile it using the GRPC plugin for your language. For instance, -for golang, the command is `protoc --go_out=plugins=grpc:. types.proto`. -See the [grpc documentation for more details](http://www.grpc.io/docs/). -`protoc` will autogenerate all the necessary code for ABCI client and -server in your language, including whatever interface your application -must satisfy to be used by the ABCI server for handling requests. - -### TSP - -If GRPC is not available in your language, or you require higher -performance, or otherwise enjoy programming, you may implement your own -ABCI server using the Tendermint Socket Protocol, known affectionately -as Teaspoon. The first step is still to auto-generate the relevant data -types and codec in your language using `protoc`. Messages coming over -the socket are proto3 encoded, but additionally length-prefixed to -facilitate use as a streaming protocol. proto3 doesn't have an -official length-prefix standard, so we use our own. The first byte in -the prefix represents the length of the Big Endian encoded length. The -remaining bytes in the prefix are the Big Endian encoded length. - -For example, if the proto3 encoded ABCI message is 0xDEADBEEF (4 -bytes), the length-prefixed message is 0x0104DEADBEEF. If the proto3 -encoded ABCI message is 65535 bytes long, the length-prefixed message -would be like 0x02FFFF.... - -Note this prefixing does not apply for grpc. - -An ABCI server must also be able to support multiple connections, as -Tendermint uses three connections. - -## Client - -There are currently two use-cases for an ABCI client. One is a testing -tool, as in the `abci-cli`, which allows ABCI requests to be sent via -command line. The other is a consensus engine, such as Tendermint Core, -which makes requests to the application every time a new transaction is -received or a block is committed. - -It is unlikely that you will need to implement a client. For details of -our client, see -[here](https://github.com/tendermint/tendermint/tree/develop/abci/client). - Most of the examples below are from [kvstore application](https://github.com/tendermint/tendermint/blob/develop/abci/example/kvstore/kvstore.go), which is a part of the abci repo. [persistent_kvstore diff --git a/docs/spec/abci/client-server.md b/docs/spec/abci/client-server.md index 822bfd1f..5ac7b3eb 100644 --- a/docs/spec/abci/client-server.md +++ b/docs/spec/abci/client-server.md @@ -3,12 +3,8 @@ This section is for those looking to implement their own ABCI Server, perhaps in a new programming language. -You are expected to have read [ABCI Methods and Types](abci.md) and [ABCI -Applications](apps.md). - -See additional details in the [ABCI -readme](https://github.com/tendermint/tendermint/blob/develop/abci/README.md)(TODO: deduplicate -those details). +You are expected to have read [ABCI Methods and Types](./abci.md) and [ABCI +Applications](./apps.md). ## Message Protocol @@ -24,17 +20,16 @@ For each request, a server should respond with the corresponding response, where the order of requests is preserved in the order of responses. -## Server +## Server Implementations To use ABCI in your programming language of choice, there must be a ABCI -server in that language. Tendermint supports two kinds of implementation -of the server: +server in that language. Tendermint supports three implementations of the ABCI, written in Go: -- Asynchronous, raw socket server (Tendermint Socket Protocol, also - known as TSP or Teaspoon) +- In-process (Golang only) +- ABCI-socket - GRPC -Both can be tested using the `abci-cli` by setting the `--abci` flag +The latter two can be tested using the `abci-cli` by setting the `--abci` flag appropriately (ie. to `socket` or `grpc`). See examples, in various stages of maintenance, in @@ -44,6 +39,12 @@ See examples, in various stages of maintenance, in [C++](https://github.com/mdyring/cpp-tmsp), and [Java](https://github.com/jTendermint/jabci). +### In Process + +The simplest implementation uses function calls within Golang. +This means ABCI applications written in Golang can be compiled with TendermintCore and run as a single binary. + + ### GRPC If GRPC is available in your language, this is the easiest approach, @@ -58,15 +59,18 @@ See the [grpc documentation for more details](http://www.grpc.io/docs/). server in your language, including whatever interface your application must satisfy to be used by the ABCI server for handling requests. +Note the length-prefixing used in the socket implementation (TSP) does not apply for GRPC. + ### TSP +Tendermint Socket Protocol is an asynchronous, raw socket server which provides ordered message passing over unix or tcp. +Messages are serialized using Protobuf3 and length-prefixed with a [signed Varint](https://developers.google.com/protocol-buffers/docs/encoding?csw=1#signed-integers) + If GRPC is not available in your language, or you require higher performance, or otherwise enjoy programming, you may implement your own -ABCI server using the Tendermint Socket Protocol, known affectionately -as Teaspoon. The first step is still to auto-generate the relevant data -types and codec in your language using `protoc`. Messages coming over -the socket are proto3 encoded, but additionally length-prefixed to -facilitate use as a streaming protocol. proto3 doesn't have an +ABCI server using the Tendermint Socket Protocol. The first step is still to auto-generate the relevant data +types and codec in your language using `protoc`. In addition to being proto3 encoded, messages coming over +the socket are length-prefixed to facilitate use as a streaming protocol. proto3 doesn't have an official length-prefix standard, so we use our own. The first byte in the prefix represents the length of the Big Endian encoded length. The remaining bytes in the prefix are the Big Endian encoded length. @@ -76,12 +80,14 @@ bytes), the length-prefixed message is 0x0104DEADBEEF. If the proto3 encoded ABCI message is 65535 bytes long, the length-prefixed message would be like 0x02FFFF.... -Note this prefixing does not apply for grpc. +The benefit of using this `varint` encoding over the old version (where integers were encoded as `` is that +it is the standard way to encode integers in Protobuf. It is also generally shorter. + +As noted above, this prefixing does not apply for GRPC. An ABCI server must also be able to support multiple connections, as Tendermint uses three connections. - ### Async vs Sync The main ABCI server (ie. non-GRPC) provides ordered asynchronous messages. From 46d32af055fec0b2c101352e729a620600df83e7 Mon Sep 17 00:00:00 2001 From: Catalin Pirvu Date: Fri, 9 Nov 2018 16:59:04 +0200 Subject: [PATCH 058/267] Add tests for ValidateBasic methods (#2754) Fixes #2740 --- evidence/reactor_test.go | 28 +++++++++++++++++++++++++ types/evidence_test.go | 37 ++++++++++++++++++++++++++++++++- types/heartbeat_test.go | 44 +++++++++++++++++++++++++++++++++++++++ types/part_set_test.go | 45 ++++++++++++++++++++++++++++++++++++++++ types/proposal_test.go | 41 ++++++++++++++++++++++++++++++++++++ types/vote_test.go | 28 +++++++++++++++++++++++++ 6 files changed, 222 insertions(+), 1 deletion(-) diff --git a/evidence/reactor_test.go b/evidence/reactor_test.go index ea9657d2..69dcdec5 100644 --- a/evidence/reactor_test.go +++ b/evidence/reactor_test.go @@ -10,6 +10,7 @@ import ( "github.com/stretchr/testify/assert" cfg "github.com/tendermint/tendermint/config" + "github.com/tendermint/tendermint/crypto/secp256k1" dbm "github.com/tendermint/tendermint/libs/db" "github.com/tendermint/tendermint/libs/log" "github.com/tendermint/tendermint/p2p" @@ -178,3 +179,30 @@ func TestReactorSelectiveBroadcast(t *testing.T) { peers := reactors[1].Switch.Peers().List() assert.Equal(t, 1, len(peers)) } +func TestEvidenceListMessageValidationBasic(t *testing.T) { + + testCases := []struct { + testName string + malleateEvListMsg func(*EvidenceListMessage) + expectErr bool + }{ + {"Good EvidenceListMessage", func(evList *EvidenceListMessage) {}, false}, + {"Invalid EvidenceListMessage", func(evList *EvidenceListMessage) { + evList.Evidence = append(evList.Evidence, + &types.DuplicateVoteEvidence{PubKey: secp256k1.GenPrivKey().PubKey()}) + }, true}, + } + for _, tc := range testCases { + t.Run(tc.testName, func(t *testing.T) { + evListMsg := &EvidenceListMessage{} + n := 3 + valAddr := []byte("myval") + evListMsg.Evidence = make([]types.Evidence, n) + for i := 0; i < n; i++ { + evListMsg.Evidence[i] = types.NewMockGoodEvidence(int64(i+1), 0, valAddr) + } + tc.malleateEvListMsg(evListMsg) + assert.Equal(t, tc.expectErr, evListMsg.ValidateBasic() != nil, "Validate Basic had an unexpected result") + }) + } +} diff --git a/types/evidence_test.go b/types/evidence_test.go index 033b51e5..a96b63a9 100644 --- a/types/evidence_test.go +++ b/types/evidence_test.go @@ -61,7 +61,7 @@ func TestEvidence(t *testing.T) { {vote1, makeVote(val, chainID, 0, 10, 3, 1, blockID2), false}, // wrong round {vote1, makeVote(val, chainID, 0, 10, 2, 2, blockID2), false}, // wrong step {vote1, makeVote(val2, chainID, 0, 10, 2, 1, blockID), false}, // wrong validator - {vote1, badVote, false}, // signed by wrong key + {vote1, badVote, false}, // signed by wrong key } pubKey := val.GetPubKey() @@ -121,3 +121,38 @@ func randomDuplicatedVoteEvidence() *DuplicateVoteEvidence { VoteB: makeVote(val, chainID, 0, 10, 2, 1, blockID2), } } + +func TestDuplicateVoteEvidenceValidation(t *testing.T) { + val := NewMockPV() + blockID := makeBlockID(tmhash.Sum([]byte("blockhash")), math.MaxInt64, tmhash.Sum([]byte("partshash"))) + blockID2 := makeBlockID(tmhash.Sum([]byte("blockhash2")), math.MaxInt64, tmhash.Sum([]byte("partshash"))) + const chainID = "mychain" + + testCases := []struct { + testName string + malleateEvidence func(*DuplicateVoteEvidence) + expectErr bool + }{ + {"Good DuplicateVoteEvidence", func(ev *DuplicateVoteEvidence) {}, false}, + {"Nil vote A", func(ev *DuplicateVoteEvidence) { ev.VoteA = nil }, true}, + {"Nil vote B", func(ev *DuplicateVoteEvidence) { ev.VoteB = nil }, true}, + {"Nil votes", func(ev *DuplicateVoteEvidence) { + ev.VoteA = nil + ev.VoteB = nil + }, true}, + {"Invalid vote type", func(ev *DuplicateVoteEvidence) { + ev.VoteA = makeVote(val, chainID, math.MaxInt64, math.MaxInt64, math.MaxInt64, 0, blockID2) + }, true}, + } + for _, tc := range testCases { + t.Run(tc.testName, func(t *testing.T) { + ev := &DuplicateVoteEvidence{ + PubKey: secp256k1.GenPrivKey().PubKey(), + VoteA: makeVote(val, chainID, math.MaxInt64, math.MaxInt64, math.MaxInt64, 0x02, blockID), + VoteB: makeVote(val, chainID, math.MaxInt64, math.MaxInt64, math.MaxInt64, 0x02, blockID2), + } + tc.malleateEvidence(ev) + assert.Equal(t, tc.expectErr, ev.ValidateBasic() != nil, "Validate Basic had an unexpected result") + }) + } +} diff --git a/types/heartbeat_test.go b/types/heartbeat_test.go index e1ffdd6f..0951c7b9 100644 --- a/types/heartbeat_test.go +++ b/types/heartbeat_test.go @@ -3,8 +3,10 @@ package types import ( "testing" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/tendermint/tendermint/crypto/ed25519" + "github.com/tendermint/tendermint/crypto/secp256k1" ) func TestHeartbeatCopy(t *testing.T) { @@ -58,3 +60,45 @@ func TestHeartbeatWriteSignBytes(t *testing.T) { require.Equal(t, string(signBytes), "null") }) } + +func TestHeartbeatValidateBasic(t *testing.T) { + testCases := []struct { + testName string + malleateHeartBeat func(*Heartbeat) + expectErr bool + }{ + {"Good HeartBeat", func(hb *Heartbeat) {}, false}, + {"Invalid address size", func(hb *Heartbeat) { + hb.ValidatorAddress = nil + }, true}, + {"Negative validator index", func(hb *Heartbeat) { + hb.ValidatorIndex = -1 + }, true}, + {"Negative height", func(hb *Heartbeat) { + hb.Height = -1 + }, true}, + {"Negative round", func(hb *Heartbeat) { + hb.Round = -1 + }, true}, + {"Negative sequence", func(hb *Heartbeat) { + hb.Sequence = -1 + }, true}, + {"Missing signature", func(hb *Heartbeat) { + hb.Signature = nil + }, true}, + {"Signature too big", func(hb *Heartbeat) { + hb.Signature = make([]byte, MaxSignatureSize+1) + }, true}, + } + for _, tc := range testCases { + t.Run(tc.testName, func(t *testing.T) { + hb := &Heartbeat{ + ValidatorAddress: secp256k1.GenPrivKey().PubKey().Address(), + Signature: make([]byte, 4), + ValidatorIndex: 1, Height: 10, Round: 1} + + tc.malleateHeartBeat(hb) + assert.Equal(t, tc.expectErr, hb.ValidateBasic() != nil, "Validate Basic had an unexpected result") + }) + } +} diff --git a/types/part_set_test.go b/types/part_set_test.go index 3576e747..e597088c 100644 --- a/types/part_set_test.go +++ b/types/part_set_test.go @@ -83,3 +83,48 @@ func TestWrongProof(t *testing.T) { t.Errorf("Expected to fail adding a part with bad bytes.") } } + +func TestPartSetHeaderSetValidateBasic(t *testing.T) { + + testCases := []struct { + testName string + malleatePartSetHeader func(*PartSetHeader) + expectErr bool + }{ + {"Good PartSet", func(psHeader *PartSetHeader) {}, false}, + {"Negative Total", func(psHeader *PartSetHeader) { psHeader.Total = -2 }, true}, + {"Invalid Hash", func(psHeader *PartSetHeader) { psHeader.Hash = make([]byte, 1) }, true}, + } + for _, tc := range testCases { + t.Run(tc.testName, func(t *testing.T) { + data := cmn.RandBytes(testPartSize * 100) + ps := NewPartSetFromData(data, testPartSize) + psHeader := ps.Header() + tc.malleatePartSetHeader(&psHeader) + assert.Equal(t, tc.expectErr, psHeader.ValidateBasic() != nil, "Validate Basic had an unexpected result") + }) + } +} + +func TestPartValidateBasic(t *testing.T) { + + testCases := []struct { + testName string + malleatePart func(*Part) + expectErr bool + }{ + {"Good Part", func(pt *Part) {}, false}, + {"Negative index", func(pt *Part) { pt.Index = -1 }, true}, + {"Too big part", func(pt *Part) { pt.Bytes = make([]byte, BlockPartSizeBytes+1) }, true}, + } + + for _, tc := range testCases { + t.Run(tc.testName, func(t *testing.T) { + data := cmn.RandBytes(testPartSize * 100) + ps := NewPartSetFromData(data, testPartSize) + part := ps.GetPart(0) + tc.malleatePart(part) + assert.Equal(t, tc.expectErr, part.ValidateBasic() != nil, "Validate Basic had an unexpected result") + }) + } +} diff --git a/types/proposal_test.go b/types/proposal_test.go index 9738db2d..f1c048e1 100644 --- a/types/proposal_test.go +++ b/types/proposal_test.go @@ -1,10 +1,13 @@ package types import ( + "math" "testing" "time" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/tendermint/tendermint/crypto/tmhash" ) var testProposal *Proposal @@ -97,3 +100,41 @@ func BenchmarkProposalVerifySignature(b *testing.B) { pubKey.VerifyBytes(testProposal.SignBytes("test_chain_id"), testProposal.Signature) } } + +func TestProposalValidateBasic(t *testing.T) { + + privVal := NewMockPV() + testCases := []struct { + testName string + malleateProposal func(*Proposal) + expectErr bool + }{ + {"Good Proposal", func(p *Proposal) {}, false}, + {"Invalid Type", func(p *Proposal) { p.Type = PrecommitType }, true}, + {"Invalid Height", func(p *Proposal) { p.Height = -1 }, true}, + {"Invalid Round", func(p *Proposal) { p.Round = -1 }, true}, + {"Invalid POLRound", func(p *Proposal) { p.POLRound = -2 }, true}, + {"Invalid BlockId", func(p *Proposal) { + p.BlockID = BlockID{[]byte{1, 2, 3}, PartSetHeader{111, []byte("blockparts")}} + }, true}, + {"Invalid Signature", func(p *Proposal) { + p.Signature = make([]byte, 0) + }, true}, + {"Too big Signature", func(p *Proposal) { + p.Signature = make([]byte, MaxSignatureSize+1) + }, true}, + } + blockID := makeBlockID(tmhash.Sum([]byte("blockhash")), math.MaxInt64, tmhash.Sum([]byte("partshash"))) + + for _, tc := range testCases { + t.Run(tc.testName, func(t *testing.T) { + prop := NewProposal( + 4, 2, 2, + blockID) + err := privVal.SignProposal("test_chain_id", prop) + require.NoError(t, err) + tc.malleateProposal(prop) + assert.Equal(t, tc.expectErr, prop.ValidateBasic() != nil, "Validate Basic had an unexpected result") + }) + } +} diff --git a/types/vote_test.go b/types/vote_test.go index cda54f89..942f2d6b 100644 --- a/types/vote_test.go +++ b/types/vote_test.go @@ -250,3 +250,31 @@ func TestVoteString(t *testing.T) { t.Errorf("Got unexpected string for Vote. Expected:\n%v\nGot:\n%v", expected, str2) } } + +func TestVoteValidateBasic(t *testing.T) { + privVal := NewMockPV() + + testCases := []struct { + testName string + malleateVote func(*Vote) + expectErr bool + }{ + {"Good Vote", func(v *Vote) {}, false}, + {"Negative Height", func(v *Vote) { v.Height = -1 }, true}, + {"Negative Round", func(v *Vote) { v.Round = -1 }, true}, + {"Invalid BlockID", func(v *Vote) { v.BlockID = BlockID{[]byte{1, 2, 3}, PartSetHeader{111, []byte("blockparts")}} }, true}, + {"Invalid Address", func(v *Vote) { v.ValidatorAddress = make([]byte, 1) }, true}, + {"Invalid ValidatorIndex", func(v *Vote) { v.ValidatorIndex = -1 }, true}, + {"Invalid Signature", func(v *Vote) { v.Signature = nil }, true}, + {"Too big Signature", func(v *Vote) { v.Signature = make([]byte, MaxSignatureSize+1) }, true}, + } + for _, tc := range testCases { + t.Run(tc.testName, func(t *testing.T) { + vote := examplePrecommit() + err := privVal.SignVote("test_chain_id", vote) + require.NoError(t, err) + tc.malleateVote(vote) + assert.Equal(t, tc.expectErr, vote.ValidateBasic() != nil, "Validate Basic had an unexpected result") + }) + } +} From d178ea9eaf6ef3113dc951f16bbe4f54a1c505ce Mon Sep 17 00:00:00 2001 From: Anton Kaliaev Date: Tue, 6 Nov 2018 13:12:12 +0100 Subject: [PATCH 059/267] use our logger in autofile/group --- consensus/wal.go | 6 ++++++ libs/autofile/autofile.go | 15 ++++++++++++++- libs/autofile/group.go | 7 +++---- 3 files changed, 23 insertions(+), 5 deletions(-) diff --git a/consensus/wal.go b/consensus/wal.go index 6472c257..bbc9908f 100644 --- a/consensus/wal.go +++ b/consensus/wal.go @@ -13,6 +13,7 @@ import ( amino "github.com/tendermint/go-amino" auto "github.com/tendermint/tendermint/libs/autofile" cmn "github.com/tendermint/tendermint/libs/common" + "github.com/tendermint/tendermint/libs/log" "github.com/tendermint/tendermint/types" tmtime "github.com/tendermint/tendermint/types/time" ) @@ -95,6 +96,11 @@ func (wal *baseWAL) Group() *auto.Group { return wal.group } +func (wal *baseWAL) SetLogger(l log.Logger) { + wal.BaseService.Logger = l + wal.group.SetLogger(l) +} + func (wal *baseWAL) OnStart() error { size, err := wal.group.Head.Size() if err != nil { diff --git a/libs/autofile/autofile.go b/libs/autofile/autofile.go index 6822545e..8e820d7d 100644 --- a/libs/autofile/autofile.go +++ b/libs/autofile/autofile.go @@ -83,6 +83,8 @@ func OpenAutoFile(path string) (*AutoFile, error) { return af, nil } +// Close shuts down the closing goroutine, SIGHUP handler and closes the +// AutoFile. func (af *AutoFile) Close() error { af.closeTicker.Stop() close(af.closeTickerStopc) @@ -116,6 +118,10 @@ func (af *AutoFile) closeFile() (err error) { return file.Close() } +// Write writes len(b) bytes to the AutoFile. It returns the number of bytes +// written and an error, if any. Write returns a non-nil error when n != +// len(b). +// Opens AutoFile if needed. func (af *AutoFile) Write(b []byte) (n int, err error) { af.mtx.Lock() defer af.mtx.Unlock() @@ -130,6 +136,10 @@ func (af *AutoFile) Write(b []byte) (n int, err error) { return } +// Sync commits the current contents of the file to stable storage. Typically, +// this means flushing the file system's in-memory copy of recently written +// data to disk. +// Opens AutoFile if needed. func (af *AutoFile) Sync() error { af.mtx.Lock() defer af.mtx.Unlock() @@ -158,6 +168,9 @@ func (af *AutoFile) openFile() error { return nil } +// Size returns the size of the AutoFile. It returns -1 and an error if fails +// get stats or open file. +// Opens AutoFile if needed. func (af *AutoFile) Size() (int64, error) { af.mtx.Lock() defer af.mtx.Unlock() @@ -171,10 +184,10 @@ func (af *AutoFile) Size() (int64, error) { return -1, err } } + stat, err := af.file.Stat() if err != nil { return -1, err } return stat.Size(), nil - } diff --git a/libs/autofile/group.go b/libs/autofile/group.go index ea272b61..fcdf0d0d 100644 --- a/libs/autofile/group.go +++ b/libs/autofile/group.go @@ -5,7 +5,6 @@ import ( "errors" "fmt" "io" - "log" "os" "path" "path/filepath" @@ -253,18 +252,18 @@ func (g *Group) checkTotalSizeLimit() { } if index == gInfo.MaxIndex { // Special degenerate case, just do nothing. - log.Println("WARNING: Group's head " + g.Head.Path + "may grow without bound") + g.Logger.Info("Group's head may grow without bound", "head", g.Head.Path) return } pathToRemove := filePathForIndex(g.Head.Path, index, gInfo.MaxIndex) fileInfo, err := os.Stat(pathToRemove) if err != nil { - log.Println("WARNING: Failed to fetch info for file @" + pathToRemove) + g.Logger.Error("Failed to fetch info for file", "file", pathToRemove) continue } err = os.Remove(pathToRemove) if err != nil { - log.Println(err) + g.Logger.Error("Failed to remove path", "path", pathToRemove) return } totalSize -= fileInfo.Size() From 091d2c3e5ecb595f69d1f7273ed7c907c087c2c7 Mon Sep 17 00:00:00 2001 From: Anton Kaliaev Date: Tue, 6 Nov 2018 13:12:42 +0100 Subject: [PATCH 060/267] openFile creates a file if not exist => ErrNotExist is not possible --- libs/autofile/autofile.go | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/libs/autofile/autofile.go b/libs/autofile/autofile.go index 8e820d7d..a1e2f49e 100644 --- a/libs/autofile/autofile.go +++ b/libs/autofile/autofile.go @@ -176,11 +176,7 @@ func (af *AutoFile) Size() (int64, error) { defer af.mtx.Unlock() if af.file == nil { - err := af.openFile() - if err != nil { - if err == os.ErrNotExist { - return 0, nil - } + if err := af.openFile(); err != nil { return -1, err } } From 13badc1d29eafed52a836b69471ac5d3db3a18ae Mon Sep 17 00:00:00 2001 From: Anton Kaliaev Date: Tue, 6 Nov 2018 13:14:47 +0100 Subject: [PATCH 061/267] [autofile/group] do not panic when checking size It's OK if the head will grow a little bit bigger, but we'll avoid panic. Refs #2703 --- CHANGELOG_PENDING.md | 1 + libs/autofile/group.go | 9 +++++---- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/CHANGELOG_PENDING.md b/CHANGELOG_PENDING.md index 1996bfcc..f63bcdc4 100644 --- a/CHANGELOG_PENDING.md +++ b/CHANGELOG_PENDING.md @@ -29,3 +29,4 @@ Friendly reminder, we have a [bug bounty program](https://hackerone.com/tendermi - [crypto/merkle] [\#2756](https://github.com/tendermint/tendermint/issues/2756) Fix crypto/merkle ProofOperators.Verify to check bounds on keypath parts. - [mempool] fix a bug where we create a WAL despite `wal_dir` being empty - [p2p] \#2771 Fix `peer-id` label name in prometheus metrics +- [autofile] [\#2703] do not panic when checking Head size diff --git a/libs/autofile/group.go b/libs/autofile/group.go index fcdf0d0d..1ec6b240 100644 --- a/libs/autofile/group.go +++ b/libs/autofile/group.go @@ -230,7 +230,8 @@ func (g *Group) checkHeadSizeLimit() { } size, err := g.Head.Size() if err != nil { - panic(err) + g.Logger.Error("Group's head may grow without bound", "head", g.Head.Path, "err", err) + return } if size >= limit { g.RotateFile() @@ -252,11 +253,11 @@ func (g *Group) checkTotalSizeLimit() { } if index == gInfo.MaxIndex { // Special degenerate case, just do nothing. - g.Logger.Info("Group's head may grow without bound", "head", g.Head.Path) + g.Logger.Error("Group's head may grow without bound", "head", g.Head.Path) return } pathToRemove := filePathForIndex(g.Head.Path, index, gInfo.MaxIndex) - fileInfo, err := os.Stat(pathToRemove) + fInfo, err := os.Stat(pathToRemove) if err != nil { g.Logger.Error("Failed to fetch info for file", "file", pathToRemove) continue @@ -266,7 +267,7 @@ func (g *Group) checkTotalSizeLimit() { g.Logger.Error("Failed to remove path", "path", pathToRemove) return } - totalSize -= fileInfo.Size() + totalSize -= fInfo.Size() } } From 1944d8534b6048666bb73fa3142c666acddb098d Mon Sep 17 00:00:00 2001 From: Anton Kaliaev Date: Tue, 6 Nov 2018 15:54:04 +0100 Subject: [PATCH 062/267] test AutoFile#Size (happy path) --- libs/autofile/autofile_test.go | 37 ++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/libs/autofile/autofile_test.go b/libs/autofile/autofile_test.go index 5408d820..0b3521c2 100644 --- a/libs/autofile/autofile_test.go +++ b/libs/autofile/autofile_test.go @@ -84,3 +84,40 @@ func TestOpenAutoFilePerms(t *testing.T) { t.Errorf("unexpected error %v", e) } } + +func TestAutoFileSize(t *testing.T) { + // First, create an AutoFile writing to a tempfile dir + f, err := ioutil.TempFile("", "sighup_test") + require.NoError(t, err) + err = f.Close() + require.NoError(t, err) + + // Here is the actual AutoFile. + af, err := OpenAutoFile(f.Name()) + require.NoError(t, err) + + // 1. Empty file + size, err := af.Size() + require.Zero(t, size) + require.NoError(t, err) + + // 2. Not empty file + data := []byte("Maniac\n") + _, err = af.Write(data) + require.NoError(t, err) + size, err = af.Size() + require.EqualValues(t, len(data), size) + require.NoError(t, err) + + // 3. Not existing file + err = af.Close() + require.NoError(t, err) + err = os.Remove(f.Name()) + require.NoError(t, err) + size, err = af.Size() + require.EqualValues(t, 0, size, "Expected a new file to be empty") + require.NoError(t, err) + + // Cleanup + _ = os.Remove(f.Name()) +} From 5b19fcf20450de8351213ea3dd8ada1f820b444d Mon Sep 17 00:00:00 2001 From: Jae Kwon Date: Sun, 11 Nov 2018 03:50:25 -0800 Subject: [PATCH 063/267] p2p: AddressBook requires addresses to have IDs; Do not close conn immediately after sending pex addrs in seed mode (#2797) * Require addressbook to only store addresses with valid ID * Do not shut down peer immediately after sending pex addrs in SeedMode * p2p: fix #2773 * seed mode: use go-routine to sleep before stopping peer --- CHANGELOG.md | 4 ++-- p2p/netaddress.go | 18 ++++++++++++++++-- p2p/node_info.go | 3 ++- p2p/pex/addrbook.go | 4 ++++ p2p/pex/errors.go | 8 ++++++++ p2p/pex/pex_reactor.go | 6 +++++- p2p/switch.go | 2 +- 7 files changed, 38 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 792386e5..fd36e385 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -139,8 +139,8 @@ increasing attention to backwards compatibility. Thanks for bearing with us! - [node] [\#2434](https://github.com/tendermint/tendermint/issues/2434) Make node respond to signal interrupts while sleeping for genesis time - [state] [\#2616](https://github.com/tendermint/tendermint/issues/2616) Pass nil to NewValidatorSet() when genesis file's Validators field is nil - [p2p] [\#2555](https://github.com/tendermint/tendermint/issues/2555) Fix p2p switch FlushThrottle value (@goolAdapter) -- [p2p] [\#2668](https://github.com/tendermint/tendermint/issues/2668) Reconnect to originally dialed address (not self-reported - address) for persistent peers +- [p2p] [\#2668](https://github.com/tendermint/tendermint/issues/2668) Reconnect to originally dialed address (not self-reported address) for persistent peers +- [p2p] [\#2797](https://github.com/tendermint/tendermint/pull/2797) AddressBook requires addresses to have IDs; Do not crap out immediately after sending pex addrs in seed mode ## v0.25.0 diff --git a/p2p/netaddress.go b/p2p/netaddress.go index ec9a0ea7..f60271bc 100644 --- a/p2p/netaddress.go +++ b/p2p/netaddress.go @@ -32,8 +32,10 @@ type NetAddress struct { str string } -// IDAddressString returns id@hostPort. -func IDAddressString(id ID, hostPort string) string { +// IDAddressString returns id@hostPort. It strips the leading +// protocol from protocolHostPort if it exists. +func IDAddressString(id ID, protocolHostPort string) string { + hostPort := removeProtocolIfDefined(protocolHostPort) return fmt.Sprintf("%s@%s", id, hostPort) } @@ -218,10 +220,22 @@ func (na *NetAddress) Routable() bool { // For IPv4 these are either a 0 or all bits set address. For IPv6 a zero // address or one that matches the RFC3849 documentation address format. func (na *NetAddress) Valid() bool { + if string(na.ID) != "" { + data, err := hex.DecodeString(string(na.ID)) + if err != nil || len(data) != IDByteLength { + return false + } + } return na.IP != nil && !(na.IP.IsUnspecified() || na.RFC3849() || na.IP.Equal(net.IPv4bcast)) } +// HasID returns true if the address has an ID. +// NOTE: It does not check whether the ID is valid or not. +func (na *NetAddress) HasID() bool { + return string(na.ID) != "" +} + // Local returns true if it is a local address. func (na *NetAddress) Local() bool { return na.IP.IsLoopback() || zero4.Contains(na.IP) diff --git a/p2p/node_info.go b/p2p/node_info.go index e46174e0..c36d98d9 100644 --- a/p2p/node_info.go +++ b/p2p/node_info.go @@ -216,7 +216,8 @@ OUTER_LOOP: // ListenAddr. Note that the ListenAddr is not authenticated and // may not match that address actually dialed if its an outbound peer. func (info DefaultNodeInfo) NetAddress() *NetAddress { - netAddr, err := NewNetAddressString(IDAddressString(info.ID(), info.ListenAddr)) + idAddr := IDAddressString(info.ID(), info.ListenAddr) + netAddr, err := NewNetAddressString(idAddr) if err != nil { switch err.(type) { case ErrNetAddressLookup: diff --git a/p2p/pex/addrbook.go b/p2p/pex/addrbook.go index 61710bbf..405a4628 100644 --- a/p2p/pex/addrbook.go +++ b/p2p/pex/addrbook.go @@ -652,6 +652,10 @@ func (a *addrBook) addAddress(addr, src *p2p.NetAddress) error { return ErrAddrBookInvalidAddr{addr} } + if !addr.HasID() { + return ErrAddrBookInvalidAddrNoID{addr} + } + // TODO: we should track ourAddrs by ID and by IP:PORT and refuse both. if _, ok := a.ourAddrs[addr.String()]; ok { return ErrAddrBookSelf{addr} diff --git a/p2p/pex/errors.go b/p2p/pex/errors.go index fbee748a..1f44ceee 100644 --- a/p2p/pex/errors.go +++ b/p2p/pex/errors.go @@ -54,3 +54,11 @@ type ErrAddrBookInvalidAddr struct { func (err ErrAddrBookInvalidAddr) Error() string { return fmt.Sprintf("Cannot add invalid address %v", err.Addr) } + +type ErrAddrBookInvalidAddrNoID struct { + Addr *p2p.NetAddress +} + +func (err ErrAddrBookInvalidAddrNoID) Error() string { + return fmt.Sprintf("Cannot add address with no ID %v", err.Addr) +} diff --git a/p2p/pex/pex_reactor.go b/p2p/pex/pex_reactor.go index 46a12c48..85d292b0 100644 --- a/p2p/pex/pex_reactor.go +++ b/p2p/pex/pex_reactor.go @@ -221,7 +221,11 @@ func (r *PEXReactor) Receive(chID byte, src Peer, msgBytes []byte) { // 2) limit the output size if r.config.SeedMode { r.SendAddrs(src, r.book.GetSelectionWithBias(biasToSelectNewPeers)) - r.Switch.StopPeerGracefully(src) + go func() { + // TODO Fix properly #2092 + time.Sleep(time.Second * 5) + r.Switch.StopPeerGracefully(src) + }() } else { r.SendAddrs(src, r.book.GetSelection()) } diff --git a/p2p/switch.go b/p2p/switch.go index b1406b9b..47a42cd0 100644 --- a/p2p/switch.go +++ b/p2p/switch.go @@ -616,7 +616,7 @@ func (sw *Switch) addPeer(p Peer) error { return err } - p.SetLogger(sw.Logger.With("peer", p.NodeInfo().NetAddress().String)) + p.SetLogger(sw.Logger.With("peer", p.NodeInfo().NetAddress())) // All good. Start peer if sw.IsRunning() { From 7a4b62d3be77425e83b489b9e44f6bdec4246ef5 Mon Sep 17 00:00:00 2001 From: Ismail Khoffi Date: Sun, 11 Nov 2018 13:57:08 +0100 Subject: [PATCH 064/267] check the result of `ps.peer.Send` before calling `ps.setHasVote` (#2787) - actually call `ps.SetHasVote` instead to avoid carrying around `votes.Height()`, `votes.Round()`, `types.SignedMsgType(votes.Type())` --- consensus/reactor.go | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/consensus/reactor.go b/consensus/reactor.go index fc41e573..12e8e0f1 100644 --- a/consensus/reactor.go +++ b/consensus/reactor.go @@ -1017,7 +1017,11 @@ func (ps *PeerState) PickSendVote(votes types.VoteSetReader) bool { if vote, ok := ps.PickVoteToSend(votes); ok { msg := &VoteMessage{vote} ps.logger.Debug("Sending vote message", "ps", ps, "vote", vote) - return ps.peer.Send(VoteChannel, cdc.MustMarshalBinaryBare(msg)) + if ps.peer.Send(VoteChannel, cdc.MustMarshalBinaryBare(msg)) { + ps.SetHasVote(vote) + return true + } + return false } return false } @@ -1046,7 +1050,6 @@ func (ps *PeerState) PickVoteToSend(votes types.VoteSetReader) (vote *types.Vote return nil, false // Not something worth sending } if index, ok := votes.BitArray().Sub(psVotes).PickRandom(); ok { - ps.setHasVote(height, round, type_, index) return votes.GetByIndex(index), true } return nil, false From 905abf1388a89db2c7ae4ee7df422aaa19796964 Mon Sep 17 00:00:00 2001 From: Mehmet Gurevin Date: Sun, 11 Nov 2018 16:14:52 +0300 Subject: [PATCH 065/267] p2p: re-check after sleeps (#2664) * p2p: re-check after sleeps * use NodeInfo as an interface * Revert "use NodeInfo as an interface" This reverts commit 5f7d055e6c745ac8c8e5a9a7f0bd5ea5bc3d448c. * Revert "p2p: re-check after sleeps" This reverts commit 7f41070da070eadd3312efce1cc821aaf3e23771. * preserve dial to itself * ignore ensured connections while re-connecting * re-check after sleep * keep protocol definition on net addresses * decrease log level * Revert "preserve dial to itself" This reverts commit 0c6e0fc58da78c378c32bb9ded2dd04ad5e754a9. * correct func comment according to modification Co-Authored-By: mgurevin --- p2p/switch.go | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/p2p/switch.go b/p2p/switch.go index 47a42cd0..b70900ea 100644 --- a/p2p/switch.go +++ b/p2p/switch.go @@ -328,6 +328,11 @@ func (sw *Switch) reconnectToPeer(addr *NetAddress) { return } + if sw.IsDialingOrExistingAddress(addr) { + sw.Logger.Debug("Peer connection has been established or dialed while we waiting next try", "addr", addr) + return + } + err := sw.DialPeerWithAddress(addr, true) if err == nil { return // success @@ -415,12 +420,15 @@ func (sw *Switch) DialPeersAsync(addrBook AddrBook, peers []string, persistent b if addr.Same(ourAddr) { sw.Logger.Debug("Ignore attempt to connect to ourselves", "addr", addr, "ourAddr", ourAddr) return - } else if sw.IsDialingOrExistingAddress(addr) { + } + + sw.randomSleep(0) + + if sw.IsDialingOrExistingAddress(addr) { sw.Logger.Debug("Ignore attempt to connect to an existing peer", "addr", addr) return } - sw.randomSleep(0) err := sw.DialPeerWithAddress(addr, persistent) if err != nil { switch err.(type) { From 3ff820bdf41663b4fab27f6145dc2a777db08275 Mon Sep 17 00:00:00 2001 From: Ismail Khoffi Date: Sun, 11 Nov 2018 16:09:33 +0100 Subject: [PATCH 066/267] fix amino overhead computation for Tx (#2792) * fix amino overhead computation for Tx: - also count the fieldnum / typ3 - add method to compute overhead per Tx - slightly clarify comment on MaxAminoOverheadForBlock - add tests * fix TestReapMaxBytesMaxGas according to amino overhead * fix TestMempoolFilters according to amino overhead * address review comments: - add a note about fieldNum = 1 - add forgotten godoc comment * fix and use sm.TxPreCheck * fix test * remove print statement --- mempool/mempool.go | 11 +++++--- mempool/mempool_test.go | 16 ++++++------ node/node.go | 13 ++-------- state/execution.go | 10 ++------ state/tx_filter.go | 15 ++++++++--- state/tx_filter_test.go | 22 +++++++++++----- types/block.go | 2 ++ types/block_test.go | 2 +- types/tx.go | 17 ++++++++++++ types/tx_test.go | 57 +++++++++++++++++++++++++++++++++++++++++ 10 files changed, 123 insertions(+), 42 deletions(-) diff --git a/mempool/mempool.go b/mempool/mempool.go index 15e3119c..b84eb4a6 100644 --- a/mempool/mempool.go +++ b/mempool/mempool.go @@ -11,7 +11,6 @@ import ( "github.com/pkg/errors" - amino "github.com/tendermint/go-amino" abci "github.com/tendermint/tendermint/abci/types" cfg "github.com/tendermint/tendermint/config" auto "github.com/tendermint/tendermint/libs/autofile" @@ -88,8 +87,12 @@ func IsPreCheckError(err error) bool { func PreCheckAminoMaxBytes(maxBytes int64) PreCheckFunc { return func(tx types.Tx) error { // We have to account for the amino overhead in the tx size as well - aminoOverhead := amino.UvarintSize(uint64(len(tx))) - txSize := int64(len(tx) + aminoOverhead) + // NOTE: fieldNum = 1 as types.Block.Data contains Txs []Tx as first field. + // If this field order ever changes this needs to updated here accordingly. + // NOTE: if some []Tx are encoded without a parenting struct, the + // fieldNum is also equal to 1. + aminoOverhead := types.ComputeAminoOverhead(tx, 1) + txSize := int64(len(tx)) + aminoOverhead if txSize > maxBytes { return fmt.Errorf("Tx size (including amino overhead) is too big: %d, max: %d", txSize, maxBytes) @@ -482,7 +485,7 @@ func (mem *Mempool) ReapMaxBytesMaxGas(maxBytes, maxGas int64) types.Txs { for e := mem.txs.Front(); e != nil; e = e.Next() { memTx := e.Value.(*mempoolTx) // Check total size requirement - aminoOverhead := int64(amino.UvarintSize(uint64(len(memTx.tx)))) + aminoOverhead := types.ComputeAminoOverhead(memTx.tx, 1) if maxBytes > -1 && totalBytes+int64(len(memTx.tx))+aminoOverhead > maxBytes { return txs } diff --git a/mempool/mempool_test.go b/mempool/mempool_test.go index e25da7fe..d7ab8273 100644 --- a/mempool/mempool_test.go +++ b/mempool/mempool_test.go @@ -107,11 +107,11 @@ func TestReapMaxBytesMaxGas(t *testing.T) { {20, 0, -1, 0}, {20, 0, 10, 0}, {20, 10, 10, 0}, - {20, 21, 10, 1}, - {20, 210, -1, 10}, - {20, 210, 5, 5}, - {20, 210, 10, 10}, - {20, 210, 15, 10}, + {20, 22, 10, 1}, + {20, 220, -1, 10}, + {20, 220, 5, 5}, + {20, 220, 10, 10}, + {20, 220, 15, 10}, {20, 20000, -1, 20}, {20, 20000, 5, 5}, {20, 20000, 30, 20}, @@ -145,15 +145,15 @@ func TestMempoolFilters(t *testing.T) { {10, nopPreFilter, nopPostFilter, 10}, {10, PreCheckAminoMaxBytes(10), nopPostFilter, 0}, {10, PreCheckAminoMaxBytes(20), nopPostFilter, 0}, - {10, PreCheckAminoMaxBytes(21), nopPostFilter, 10}, + {10, PreCheckAminoMaxBytes(22), nopPostFilter, 10}, {10, nopPreFilter, PostCheckMaxGas(-1), 10}, {10, nopPreFilter, PostCheckMaxGas(0), 0}, {10, nopPreFilter, PostCheckMaxGas(1), 10}, {10, nopPreFilter, PostCheckMaxGas(3000), 10}, {10, PreCheckAminoMaxBytes(10), PostCheckMaxGas(20), 0}, {10, PreCheckAminoMaxBytes(30), PostCheckMaxGas(20), 10}, - {10, PreCheckAminoMaxBytes(21), PostCheckMaxGas(1), 10}, - {10, PreCheckAminoMaxBytes(21), PostCheckMaxGas(0), 0}, + {10, PreCheckAminoMaxBytes(22), PostCheckMaxGas(1), 10}, + {10, PreCheckAminoMaxBytes(22), PostCheckMaxGas(0), 0}, } for tcIndex, tt := range tests { mempool.Update(1, emptyTxArr, tt.preFilter, tt.postFilter) diff --git a/node/node.go b/node/node.go index 4710397f..0652a392 100644 --- a/node/node.go +++ b/node/node.go @@ -265,17 +265,8 @@ func NewNode(config *cfg.Config, proxyApp.Mempool(), state.LastBlockHeight, mempl.WithMetrics(memplMetrics), - mempl.WithPreCheck( - mempl.PreCheckAminoMaxBytes( - types.MaxDataBytesUnknownEvidence( - state.ConsensusParams.BlockSize.MaxBytes, - state.Validators.Size(), - ), - ), - ), - mempl.WithPostCheck( - mempl.PostCheckMaxGas(state.ConsensusParams.BlockSize.MaxGas), - ), + mempl.WithPreCheck(sm.TxPreCheck(state)), + mempl.WithPostCheck(sm.TxPostCheck(state)), ) mempoolLogger := logger.With("module", "mempool") mempool.SetLogger(mempoolLogger) diff --git a/state/execution.go b/state/execution.go index cc8e7e75..4f5a1545 100644 --- a/state/execution.go +++ b/state/execution.go @@ -8,7 +8,6 @@ import ( dbm "github.com/tendermint/tendermint/libs/db" "github.com/tendermint/tendermint/libs/fail" "github.com/tendermint/tendermint/libs/log" - "github.com/tendermint/tendermint/mempool" "github.com/tendermint/tendermint/proxy" "github.com/tendermint/tendermint/types" ) @@ -180,13 +179,8 @@ func (blockExec *BlockExecutor) Commit( err = blockExec.mempool.Update( block.Height, block.Txs, - mempool.PreCheckAminoMaxBytes( - types.MaxDataBytesUnknownEvidence( - state.ConsensusParams.BlockSize.MaxBytes, - state.Validators.Size(), - ), - ), - mempool.PostCheckMaxGas(state.ConsensusParams.BlockSize.MaxGas), + TxPreCheck(state), + TxPostCheck(state), ) return res.Data, err diff --git a/state/tx_filter.go b/state/tx_filter.go index b8882d8e..518eb187 100644 --- a/state/tx_filter.go +++ b/state/tx_filter.go @@ -1,15 +1,22 @@ package state import ( + mempl "github.com/tendermint/tendermint/mempool" "github.com/tendermint/tendermint/types" ) -// TxFilter returns a function to filter transactions. The function limits the -// size of a transaction to the maximum block's data size. -func TxFilter(state State) func(tx types.Tx) bool { +// TxPreCheck returns a function to filter transactions before processing. +// The function limits the size of a transaction to the block's maximum data size. +func TxPreCheck(state State) mempl.PreCheckFunc { maxDataBytes := types.MaxDataBytesUnknownEvidence( state.ConsensusParams.BlockSize.MaxBytes, state.Validators.Size(), ) - return func(tx types.Tx) bool { return int64(len(tx)) <= maxDataBytes } + return mempl.PreCheckAminoMaxBytes(maxDataBytes) +} + +// TxPostCheck returns a function to filter transactions after processing. +// The function limits the gas wanted by a transaction to the block's maximum total gas. +func TxPostCheck(state State) mempl.PostCheckFunc { + return mempl.PostCheckMaxGas(state.ConsensusParams.BlockSize.MaxGas) } diff --git a/state/tx_filter_test.go b/state/tx_filter_test.go index e6b8999f..52ae396b 100644 --- a/state/tx_filter_test.go +++ b/state/tx_filter_test.go @@ -18,12 +18,18 @@ func TestTxFilter(t *testing.T) { genDoc := randomGenesisDoc() genDoc.ConsensusParams.BlockSize.MaxBytes = 3000 + // Max size of Txs is much smaller than size of block, + // since we need to account for commits and evidence. testCases := []struct { - tx types.Tx - isTxValid bool + tx types.Tx + isErr bool }{ - {types.Tx(cmn.RandBytes(250)), true}, - {types.Tx(cmn.RandBytes(3001)), false}, + {types.Tx(cmn.RandBytes(250)), false}, + {types.Tx(cmn.RandBytes(1809)), false}, + {types.Tx(cmn.RandBytes(1810)), false}, + {types.Tx(cmn.RandBytes(1811)), true}, + {types.Tx(cmn.RandBytes(1812)), true}, + {types.Tx(cmn.RandBytes(3000)), true}, } for i, tc := range testCases { @@ -31,8 +37,12 @@ func TestTxFilter(t *testing.T) { state, err := LoadStateFromDBOrGenesisDoc(stateDB, genDoc) require.NoError(t, err) - f := TxFilter(state) - assert.Equal(t, tc.isTxValid, f(tc.tx), "#%v", i) + f := TxPreCheck(state) + if tc.isErr { + assert.NotNil(t, f(tc.tx), "#%v", i) + } else { + assert.Nil(t, f(tc.tx), "#%v", i) + } } } diff --git a/types/block.go b/types/block.go index 4ae51d4d..b2cddb5e 100644 --- a/types/block.go +++ b/types/block.go @@ -21,6 +21,8 @@ const ( // MaxAminoOverheadForBlock - maximum amino overhead to encode a block (up to // MaxBlockSizeBytes in size) not including it's parts except Data. + // This means it also excludes the overhead for individual transactions. + // To compute individual transactions' overhead use types.ComputeAminoOverhead(tx types.Tx, fieldNum int). // // Uvarint length of MaxBlockSizeBytes: 4 bytes // 2 fields (2 embedded): 2 bytes diff --git a/types/block_test.go b/types/block_test.go index cdea293f..bedd8c8d 100644 --- a/types/block_test.go +++ b/types/block_test.go @@ -250,7 +250,7 @@ func TestMaxHeaderBytes(t *testing.T) { timestamp := time.Date(math.MaxInt64, 0, 0, 0, 0, 0, math.MaxInt64, time.UTC) h := Header{ - Version: version.Consensus{math.MaxInt64, math.MaxInt64}, + Version: version.Consensus{Block: math.MaxInt64, App: math.MaxInt64}, ChainID: maxChainID, Height: math.MaxInt64, Time: timestamp, diff --git a/types/tx.go b/types/tx.go index 10c097e3..41be7794 100644 --- a/types/tx.go +++ b/types/tx.go @@ -5,6 +5,8 @@ import ( "errors" "fmt" + "github.com/tendermint/go-amino" + abci "github.com/tendermint/tendermint/abci/types" "github.com/tendermint/tendermint/crypto/merkle" "github.com/tendermint/tendermint/crypto/tmhash" @@ -118,3 +120,18 @@ type TxResult struct { Tx Tx `json:"tx"` Result abci.ResponseDeliverTx `json:"result"` } + +// ComputeAminoOverhead calculates the overhead for amino encoding a transaction. +// The overhead consists of varint encoding the field number and the wire type +// (= length-delimited = 2), and another varint encoding the length of the +// transaction. +// The field number can be the field number of the particular transaction, or +// the field number of the parenting struct that contains the transactions []Tx +// as a field (this field number is repeated for each contained Tx). +// If some []Tx are encoded directly (without a parenting struct), the default +// fieldNum is also 1 (see BinFieldNum in amino.MarshalBinaryBare). +func ComputeAminoOverhead(tx Tx, fieldNum int) int64 { + fnum := uint64(fieldNum) + typ3AndFieldNum := (uint64(fnum) << 3) | uint64(amino.Typ3_ByteLength) + return int64(amino.UvarintSize(typ3AndFieldNum)) + int64(amino.UvarintSize(uint64(len(tx)))) +} diff --git a/types/tx_test.go b/types/tx_test.go index 6ce23d6f..3afaaccc 100644 --- a/types/tx_test.go +++ b/types/tx_test.go @@ -96,6 +96,63 @@ func TestTxProofUnchangable(t *testing.T) { } } +func TestComputeTxsOverhead(t *testing.T) { + cases := []struct { + txs Txs + wantOverhead int + }{ + {Txs{[]byte{6, 6, 6, 6, 6, 6}}, 2}, + // one 21 Mb transaction: + {Txs{make([]byte, 22020096, 22020096)}, 5}, + // two 21Mb/2 sized transactions: + {Txs{make([]byte, 11010048, 11010048), make([]byte, 11010048, 11010048)}, 10}, + {Txs{[]byte{1, 2, 3}, []byte{1, 2, 3}, []byte{4, 5, 6}}, 6}, + {Txs{[]byte{100, 5, 64}, []byte{42, 116, 118}, []byte{6, 6, 6}, []byte{6, 6, 6}}, 8}, + } + + for _, tc := range cases { + totalBytes := int64(0) + totalOverhead := int64(0) + for _, tx := range tc.txs { + aminoOverhead := ComputeAminoOverhead(tx, 1) + totalOverhead += aminoOverhead + totalBytes += aminoOverhead + int64(len(tx)) + } + bz, err := cdc.MarshalBinaryBare(tc.txs) + assert.EqualValues(t, tc.wantOverhead, totalOverhead) + assert.NoError(t, err) + assert.EqualValues(t, len(bz), totalBytes) + } +} + +func TestComputeAminoOverhead(t *testing.T) { + cases := []struct { + tx Tx + fieldNum int + want int + }{ + {[]byte{6, 6, 6}, 1, 2}, + {[]byte{6, 6, 6}, 16, 3}, + {[]byte{6, 6, 6}, 32, 3}, + {[]byte{6, 6, 6}, 64, 3}, + {[]byte{6, 6, 6}, 512, 3}, + {[]byte{6, 6, 6}, 1024, 3}, + {[]byte{6, 6, 6}, 2048, 4}, + {make([]byte, 64), 1, 2}, + {make([]byte, 65), 1, 2}, + {make([]byte, 127), 1, 2}, + {make([]byte, 128), 1, 3}, + {make([]byte, 256), 1, 3}, + {make([]byte, 512), 1, 3}, + {make([]byte, 1024), 1, 3}, + {make([]byte, 128), 16, 4}, + } + for _, tc := range cases { + got := ComputeAminoOverhead(tc.tx, tc.fieldNum) + assert.EqualValues(t, tc.want, got) + } +} + func testTxProofUnchangable(t *testing.T) { // make some proof txs := makeTxs(randInt(2, 100), randInt(16, 128)) From 533b3cfb739aaa432faa25b58a4729305267329e Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Sun, 11 Nov 2018 12:08:28 -0500 Subject: [PATCH 067/267] Release/v0.26.1 (#2803) * changelog and version * fix changelog --- CHANGELOG.md | 32 ++++++++++++++++++++++++++++---- CHANGELOG_PENDING.md | 6 +----- version/version.go | 2 +- 3 files changed, 30 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fd36e385..eb22e8b8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,31 @@ # Changelog +## v0.26.1 + +*November 11, 2018* + +Special thanks to external contributors on this release: @katakonst + +Friendly reminder, we have a [bug bounty program](https://hackerone.com/tendermint). + +### IMPROVEMENTS: + +- [consensus] [\#2704](https://github.com/tendermint/tendermint/issues/2704) Simplify valid POL round logic +- [docs] [\#2749](https://github.com/tendermint/tendermint/issues/2749) Deduplicate some ABCI docs +- [mempool] More detailed log messages + - [\#2724](https://github.com/tendermint/tendermint/issues/2724) + - [\#2762](https://github.com/tendermint/tendermint/issues/2762) + +### BUG FIXES: + +- [autofile] [\#2703](https://github.com/tendermint/tendermint/issues/2703) Do not panic when checking Head size +- [crypto/merkle] [\#2756](https://github.com/tendermint/tendermint/issues/2756) Fix crypto/merkle ProofOperators.Verify to check bounds on keypath parts. +- [mempool] fix a bug where we create a WAL despite `wal_dir` being empty +- [p2p] [\#2771](https://github.com/tendermint/tendermint/issues/2771) Fix `peer-id` label name to `peer_id` in prometheus metrics +- [p2p] [\#2797](https://github.com/tendermint/tendermint/pull/2797) Fix IDs in peer NodeInfo and require them for addresses + in AddressBook +- [p2p] [\#2797](https://github.com/tendermint/tendermint/pull/2797) Do not close conn immediately after sending pex addrs in seed mode. Partial fix for [\#2092](https://github.com/tendermint/tendermint/issues/2092). + ## v0.26.0 *November 2, 2018* @@ -140,8 +166,6 @@ increasing attention to backwards compatibility. Thanks for bearing with us! - [state] [\#2616](https://github.com/tendermint/tendermint/issues/2616) Pass nil to NewValidatorSet() when genesis file's Validators field is nil - [p2p] [\#2555](https://github.com/tendermint/tendermint/issues/2555) Fix p2p switch FlushThrottle value (@goolAdapter) - [p2p] [\#2668](https://github.com/tendermint/tendermint/issues/2668) Reconnect to originally dialed address (not self-reported address) for persistent peers -- [p2p] [\#2797](https://github.com/tendermint/tendermint/pull/2797) AddressBook requires addresses to have IDs; Do not crap out immediately after sending pex addrs in seed mode - ## v0.25.0 @@ -307,8 +331,8 @@ BUG FIXES: *August 22nd, 2018* BUG FIXES: -- [libs/autofile] \#2261 Fix log rotation so it actually happens. - - Fixes issues with consensus WAL growing unbounded ala \#2259 +- [libs/autofile] [\#2261](https://github.com/tendermint/tendermint/issues/2261) Fix log rotation so it actually happens. + - Fixes issues with consensus WAL growing unbounded ala [\#2259](https://github.com/tendermint/tendermint/issues/2259) ## 0.23.0 diff --git a/CHANGELOG_PENDING.md b/CHANGELOG_PENDING.md index f63bcdc4..ea3b9759 100644 --- a/CHANGELOG_PENDING.md +++ b/CHANGELOG_PENDING.md @@ -1,6 +1,6 @@ # Pending -## v0.26.1 +## v0.26.2 *TBA* @@ -26,7 +26,3 @@ Friendly reminder, we have a [bug bounty program](https://hackerone.com/tendermi ### BUG FIXES: -- [crypto/merkle] [\#2756](https://github.com/tendermint/tendermint/issues/2756) Fix crypto/merkle ProofOperators.Verify to check bounds on keypath parts. -- [mempool] fix a bug where we create a WAL despite `wal_dir` being empty -- [p2p] \#2771 Fix `peer-id` label name in prometheus metrics -- [autofile] [\#2703] do not panic when checking Head size diff --git a/version/version.go b/version/version.go index b4664fd7..b7a72a7f 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.0" + TMCoreSemVer = "0.26.1" // ABCISemVer is the semantic version of the ABCI library ABCISemVer = "0.15.0" From e11699038dc60ca76e56bd382686485553e18982 Mon Sep 17 00:00:00 2001 From: yutianwu Date: Mon, 12 Nov 2018 03:47:34 +0800 Subject: [PATCH 068/267] [R4R] Add adr-034: PrivValidator file structure (#2751) * add adr-034 * update changelog * minor changes * do some refactor --- CHANGELOG.md | 2 + .../adr-034-priv-validator-file-structure.md | 72 +++++++++++++++++++ 2 files changed, 74 insertions(+) create mode 100644 docs/architecture/adr-034-priv-validator-file-structure.md diff --git a/CHANGELOG.md b/CHANGELOG.md index eb22e8b8..89c5cd8f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -134,6 +134,8 @@ increasing attention to backwards compatibility. Thanks for bearing with us! - [abci] [\#2557](https://github.com/tendermint/tendermint/issues/2557) Add `Codespace` field to `Response{CheckTx, DeliverTx, Query}` - [abci] [\#2662](https://github.com/tendermint/tendermint/issues/2662) Add `BlockVersion` and `P2PVersion` to `RequestInfo` - [crypto/merkle] [\#2298](https://github.com/tendermint/tendermint/issues/2298) General Merkle Proof scheme for chaining various types of Merkle trees together +- [docs/architecture] [\#1181](https://github.com/tendermint/tendermint/issues/1181) S +plit immutable and mutable parts of priv_validator.json ### IMPROVEMENTS: - Additional Metrics diff --git a/docs/architecture/adr-034-priv-validator-file-structure.md b/docs/architecture/adr-034-priv-validator-file-structure.md new file mode 100644 index 00000000..83160bfb --- /dev/null +++ b/docs/architecture/adr-034-priv-validator-file-structure.md @@ -0,0 +1,72 @@ +# ADR 034: PrivValidator file structure + +## Changelog + +03-11-2018: Initial Draft + +## Context + +For now, the PrivValidator file `priv_validator.json` contains mutable and immutable parts. +Even in an insecure mode which does not encrypt private key on disk, it is reasonable to separate +the mutable part and immutable part. + +References: +[#1181](https://github.com/tendermint/tendermint/issues/1181) +[#2657](https://github.com/tendermint/tendermint/issues/2657) +[#2313](https://github.com/tendermint/tendermint/issues/2313) + +## Proposed Solution + +We can split mutable and immutable parts with two structs: +```go +// FilePVKey stores the immutable part of PrivValidator +type FilePVKey struct { + Address types.Address `json:"address"` + PubKey crypto.PubKey `json:"pub_key"` + PrivKey crypto.PrivKey `json:"priv_key"` + + filePath string +} + +// FilePVState stores the mutable part of PrivValidator +type FilePVLastSignState struct { + Height int64 `json:"height"` + Round int `json:"round"` + Step int8 `json:"step"` + Signature []byte `json:"signature,omitempty"` + SignBytes cmn.HexBytes `json:"signbytes,omitempty"` + + filePath string + mtx sync.Mutex +} +``` + +Then we can combine `FilePVKey` with `FilePVLastSignState` and will get the original `FilePV`. + +```go +type FilePV struct { + Key FilePVKey + LastSignState FilePVLastSignState +} +``` + +As discussed, `FilePV` should be located in `config`, and `FilePVLastSignState` should be stored in `data`. The +store path of each file should be specified in `config.yml`. + +What we need to do next is changing the methods of `FilePV`. + +## Status + +Draft. + +## Consequences + +### Positive + +- separate the mutable and immutable of PrivValidator + +### Negative + +- need to add more config for file path + +### Neutral From fb10209a9630688757d84c8dd28ec32bcb4a3e7d Mon Sep 17 00:00:00 2001 From: Zach Date: Tue, 13 Nov 2018 02:54:43 -0500 Subject: [PATCH 069/267] update to amino 0.14.1 (#2822) --- Gopkg.lock | 6 +++--- Gopkg.toml | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Gopkg.lock b/Gopkg.lock index 35542bf6..229ed3f9 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -358,12 +358,12 @@ revision = "e5840949ff4fff0c56f9b6a541e22b63581ea9df" [[projects]] - digest = "1:10b3a599325740c84a7c81f3f3cb2e1fdb70b3ea01b7fa28495567a2519df431" + digest = "1:ad9c4c1a4e7875330b1f62906f2830f043a23edb5db997e3a5ac5d3e6eadf80a" name = "github.com/tendermint/go-amino" packages = ["."] pruneopts = "UT" - revision = "6dcc6ddc143e116455c94b25c1004c99e0d0ca12" - version = "v0.14.0" + revision = "dc14acf9ef15f85828bfbc561ed9dd9d2a284885" + version = "v0.14.1" [[projects]] digest = "1:72b71e3a29775e5752ed7a8012052a3dee165e27ec18cedddae5288058f09acf" diff --git a/Gopkg.toml b/Gopkg.toml index 47418bef..a0ffb920 100644 --- a/Gopkg.toml +++ b/Gopkg.toml @@ -58,7 +58,7 @@ [[constraint]] name = "github.com/tendermint/go-amino" - version = "v0.14.0" + version = "v0.14.1" [[constraint]] name = "google.golang.org/grpc" From 5a6822c8acbd7353729cbb5d8393de2f6da39425 Mon Sep 17 00:00:00 2001 From: Anton Kaliaev Date: Tue, 13 Nov 2018 20:32:51 +0400 Subject: [PATCH 070/267] abci: localClient improvements & bugfixes & pubsub Unsubscribe issues (#2748) * use READ lock/unlock in ConsensusState#GetLastHeight Refs #2721 * do not use defers when there's no need * fix peer formatting (output its address instead of the pointer) ``` [54310]: E[11-02|11:59:39.851] Connection failed @ sendRoutine module=p2p peer=0xb78f00 conn=MConn{74.207.236.148:26656} err="pong timeout" ``` https://github.com/tendermint/tendermint/issues/2721#issuecomment-435326581 * panic if peer has no state https://github.com/tendermint/tendermint/issues/2721#issuecomment-435347165 It's confusing that sometimes we check if peer has a state, but most of the times we expect it to be there 1. https://github.com/tendermint/tendermint/blob/add79700b5fe84417538202b6c927c8cc5383672/mempool/reactor.go#L138 2. https://github.com/tendermint/tendermint/blob/add79700b5fe84417538202b6c927c8cc5383672/rpc/core/consensus.go#L196 (edited) I will change everything to always assume peer has a state and panic otherwise that should help identify issues earlier * abci/localclient: extend lock on app callback App callback should be protected by lock as well (note this was already done for InitChainAsync, why not for others???). Otherwise, when we execute the block, tx might come in and call the callback in the same time we're updating it in execBlockOnProxyApp => DATA RACE Fixes #2721 Consensus state is locked ``` goroutine 113333 [semacquire, 309 minutes]: sync.runtime_SemacquireMutex(0xc00180009c, 0xc0000c7e00) /usr/local/go/src/runtime/sema.go:71 +0x3d sync.(*RWMutex).RLock(0xc001800090) /usr/local/go/src/sync/rwmutex.go:50 +0x4e github.com/MinterTeam/minter-go-node/vendor/github.com/tendermint/tendermint/consensus.(*ConsensusState).GetRoundState(0xc001800000, 0x0) /root/go/src/github.com/MinterTeam/minter-go-node/vendor/github.com/tendermint/tendermint/consensus/state.go:218 +0x46 github.com/MinterTeam/minter-go-node/vendor/github.com/tendermint/tendermint/consensus.(*ConsensusReactor).queryMaj23Routine(0xc0017def80, 0x11104a0, 0xc0072488f0, 0xc007248 9c0) /root/go/src/github.com/MinterTeam/minter-go-node/vendor/github.com/tendermint/tendermint/consensus/reactor.go:735 +0x16d created by github.com/MinterTeam/minter-go-node/vendor/github.com/tendermint/tendermint/consensus.(*ConsensusReactor).AddPeer /root/go/src/github.com/MinterTeam/minter-go-node/vendor/github.com/tendermint/tendermint/consensus/reactor.go:172 +0x236 ``` because localClient is locked ``` goroutine 1899 [semacquire, 309 minutes]: sync.runtime_SemacquireMutex(0xc00003363c, 0xc0000cb500) /usr/local/go/src/runtime/sema.go:71 +0x3d sync.(*Mutex).Lock(0xc000033638) /usr/local/go/src/sync/mutex.go:134 +0xff github.com/MinterTeam/minter-go-node/vendor/github.com/tendermint/tendermint/abci/client.(*localClient).SetResponseCallback(0xc0001fb560, 0xc007868540) /root/go/src/github.com/MinterTeam/minter-go-node/vendor/github.com/tendermint/tendermint/abci/client/local_client.go:32 +0x33 github.com/MinterTeam/minter-go-node/vendor/github.com/tendermint/tendermint/proxy.(*appConnConsensus).SetResponseCallback(0xc00002f750, 0xc007868540) /root/go/src/github.com/MinterTeam/minter-go-node/vendor/github.com/tendermint/tendermint/proxy/app_conn.go:57 +0x40 github.com/MinterTeam/minter-go-node/vendor/github.com/tendermint/tendermint/state.execBlockOnProxyApp(0x1104e20, 0xc002ca0ba0, 0x11092a0, 0xc00002f750, 0xc0001fe960, 0xc000bfc660, 0x110cfe0, 0xc000090330, 0xc9d12, 0xc000d9d5a0, ...) /root/go/src/github.com/MinterTeam/minter-go-node/vendor/github.com/tendermint/tendermint/state/execution.go:230 +0x1fd github.com/MinterTeam/minter-go-node/vendor/github.com/tendermint/tendermint/state.(*BlockExecutor).ApplyBlock(0xc002c2a230, 0x7, 0x0, 0xc000eae880, 0x6, 0xc002e52c60, 0x16, 0x1f927, 0xc9d12, 0xc000d9d5a0, ...) /root/go/src/github.com/MinterTeam/minter-go-node/vendor/github.com/tendermint/tendermint/state/execution.go:96 +0x142 github.com/MinterTeam/minter-go-node/vendor/github.com/tendermint/tendermint/consensus.(*ConsensusState).finalizeCommit(0xc001800000, 0x1f928) /root/go/src/github.com/MinterTeam/minter-go-node/vendor/github.com/tendermint/tendermint/consensus/state.go:1339 +0xa3e github.com/MinterTeam/minter-go-node/vendor/github.com/tendermint/tendermint/consensus.(*ConsensusState).tryFinalizeCommit(0xc001800000, 0x1f928) /root/go/src/github.com/MinterTeam/minter-go-node/vendor/github.com/tendermint/tendermint/consensus/state.go:1270 +0x451 github.com/MinterTeam/minter-go-node/vendor/github.com/tendermint/tendermint/consensus.(*ConsensusState).enterCommit.func1(0xc001800000, 0x0, 0x1f928) /root/go/src/github.com/MinterTeam/minter-go-node/vendor/github.com/tendermint/tendermint/consensus/state.go:1218 +0x90 github.com/MinterTeam/minter-go-node/vendor/github.com/tendermint/tendermint/consensus.(*ConsensusState).enterCommit(0xc001800000, 0x1f928, 0x0) /root/go/src/github.com/MinterTeam/minter-go-node/vendor/github.com/tendermint/tendermint/consensus/state.go:1247 +0x6b8 github.com/MinterTeam/minter-go-node/vendor/github.com/tendermint/tendermint/consensus.(*ConsensusState).addVote(0xc001800000, 0xc003d8dea0, 0xc000cf4cc0, 0x28, 0xf1, 0xc003bc7ad0, 0xc003bc7b10) /root/go/src/github.com/MinterTeam/minter-go-node/vendor/github.com/tendermint/tendermint/consensus/state.go:1659 +0xbad github.com/MinterTeam/minter-go-node/vendor/github.com/tendermint/tendermint/consensus.(*ConsensusState).tryAddVote(0xc001800000, 0xc003d8dea0, 0xc000cf4cc0, 0x28, 0xf1, 0xf1, 0xf1) /root/go/src/github.com/MinterTeam/minter-go-node/vendor/github.com/tendermint/tendermint/consensus/state.go:1517 +0x59 github.com/MinterTeam/minter-go-node/vendor/github.com/tendermint/tendermint/consensus.(*ConsensusState).handleMsg(0xc001800000, 0xd98200, 0xc0070dbed0, 0xc000cf4cc0, 0x28) /root/go/src/github.com/MinterTeam/minter-go-node/vendor/github.com/tendermint/tendermint/consensus/state.go:660 +0x64b github.com/MinterTeam/minter-go-node/vendor/github.com/tendermint/tendermint/consensus.(*ConsensusState).receiveRoutine(0xc001800000, 0x0) /root/go/src/github.com/MinterTeam/minter-go-node/vendor/github.com/tendermint/tendermint/consensus/state.go:617 +0x670 created by github.com/MinterTeam/minter-go-node/vendor/github.com/tendermint/tendermint/consensus.(*ConsensusState).OnStart /root/go/src/github.com/MinterTeam/minter-go-node/vendor/github.com/tendermint/tendermint/consensus/state.go:311 +0x132 ``` tx comes in and CheckTx is executed right when we execute the block ``` goroutine 111044 [semacquire, 309 minutes]: sync.runtime_SemacquireMutex(0xc00003363c, 0x0) /usr/local/go/src/runtime/sema.go:71 +0x3d sync.(*Mutex).Lock(0xc000033638) /usr/local/go/src/sync/mutex.go:134 +0xff github.com/MinterTeam/minter-go-node/vendor/github.com/tendermint/tendermint/abci/client.(*localClient).CheckTxAsync(0xc0001fb0e0, 0xc002d94500, 0x13f, 0x280, 0x0) /root/go/src/github.com/MinterTeam/minter-go-node/vendor/github.com/tendermint/tendermint/abci/client/local_client.go:85 +0x47 github.com/MinterTeam/minter-go-node/vendor/github.com/tendermint/tendermint/proxy.(*appConnMempool).CheckTxAsync(0xc00002f720, 0xc002d94500, 0x13f, 0x280, 0x1) /root/go/src/github.com/MinterTeam/minter-go-node/vendor/github.com/tendermint/tendermint/proxy/app_conn.go:114 +0x51 github.com/MinterTeam/minter-go-node/vendor/github.com/tendermint/tendermint/mempool.(*Mempool).CheckTx(0xc002d3a320, 0xc002d94500, 0x13f, 0x280, 0xc0072355f0, 0x0, 0x0) /root/go/src/github.com/MinterTeam/minter-go-node/vendor/github.com/tendermint/tendermint/mempool/mempool.go:316 +0x17b github.com/MinterTeam/minter-go-node/vendor/github.com/tendermint/tendermint/rpc/core.BroadcastTxSync(0xc002d94500, 0x13f, 0x280, 0x0, 0x0, 0x0) /root/go/src/github.com/MinterTeam/minter-go-node/vendor/github.com/tendermint/tendermint/rpc/core/mempool.go:93 +0xb8 reflect.Value.call(0xd85560, 0x10326c0, 0x13, 0xec7b8b, 0x4, 0xc00663f180, 0x1, 0x1, 0xc00663f180, 0xc00663f188, ...) /usr/local/go/src/reflect/value.go:447 +0x449 reflect.Value.Call(0xd85560, 0x10326c0, 0x13, 0xc00663f180, 0x1, 0x1, 0x0, 0x0, 0xc005cc9344) /usr/local/go/src/reflect/value.go:308 +0xa4 github.com/MinterTeam/minter-go-node/vendor/github.com/tendermint/tendermint/rpc/lib/server.makeHTTPHandler.func2(0x1102060, 0xc00663f100, 0xc0082d7900) /root/go/src/github.com/MinterTeam/minter-go-node/vendor/github.com/tendermint/tendermint/rpc/lib/server/handlers.go:269 +0x188 net/http.HandlerFunc.ServeHTTP(0xc002c81f20, 0x1102060, 0xc00663f100, 0xc0082d7900) /usr/local/go/src/net/http/server.go:1964 +0x44 net/http.(*ServeMux).ServeHTTP(0xc002c81b60, 0x1102060, 0xc00663f100, 0xc0082d7900) /usr/local/go/src/net/http/server.go:2361 +0x127 github.com/MinterTeam/minter-go-node/vendor/github.com/tendermint/tendermint/rpc/lib/server.maxBytesHandler.ServeHTTP(0x10f8a40, 0xc002c81b60, 0xf4240, 0x1102060, 0xc00663f100, 0xc0082d7900) /root/go/src/github.com/MinterTeam/minter-go-node/vendor/github.com/tendermint/tendermint/rpc/lib/server/http_server.go:219 +0xcf github.com/MinterTeam/minter-go-node/vendor/github.com/tendermint/tendermint/rpc/lib/server.RecoverAndLogHandler.func1(0x1103220, 0xc00121e620, 0xc0082d7900) /root/go/src/github.com/MinterTeam/minter-go-node/vendor/github.com/tendermint/tendermint/rpc/lib/server/http_server.go:192 +0x394 net/http.HandlerFunc.ServeHTTP(0xc002c06ea0, 0x1103220, 0xc00121e620, 0xc0082d7900) /usr/local/go/src/net/http/server.go:1964 +0x44 net/http.serverHandler.ServeHTTP(0xc001a1aa90, 0x1103220, 0xc00121e620, 0xc0082d7900) /usr/local/go/src/net/http/server.go:2741 +0xab net/http.(*conn).serve(0xc00785a3c0, 0x11041a0, 0xc000f844c0) /usr/local/go/src/net/http/server.go:1847 +0x646 created by net/http.(*Server).Serve /usr/local/go/src/net/http/server.go:2851 +0x2f5 ``` * consensus: use read lock in Receive#VoteMessage * use defer to unlock mutex because application might panic * use defer in every method of the localClient * add a changelog entry * drain channels before Unsubscribe(All) Read https://github.com/tendermint/tendermint/blob/55362ed76630f3e1ebec159a598f6a9fb5892cb1/libs/pubsub/pubsub.go#L13 for the detailed explanation of the issue. We'll need to fix it someday. Make sure to keep an eye on https://github.com/tendermint/tendermint/blob/master/docs/architecture/adr-033-pubsub.md * retry instead of panic when peer has no state in reactors other than consensus in /dump_consensus_state RPC endpoint, skip a peer with no state * rpc/core/mempool: simplify error messages * rpc/core/mempool: use time.After instead of timer also, do not log DeliverTx result (to be consistent with other memthods) * unlock before calling the callback in reqRes#SetCallback --- CHANGELOG_PENDING.md | 3 ++ abci/client/client.go | 2 +- abci/client/grpc_client.go | 2 +- abci/client/local_client.go | 67 +++++++++++++++++++++++++----------- abci/client/socket_client.go | 2 +- consensus/reactor.go | 30 ++++++++++++---- consensus/replay_file.go | 26 ++++++++++++-- consensus/state.go | 14 +++----- evidence/reactor.go | 9 +++-- evidence/reactor_test.go | 10 ++++++ libs/pubsub/pubsub.go | 12 +++++-- mempool/mempool.go | 1 + mempool/reactor.go | 23 ++++++++----- mempool/reactor_test.go | 13 +++++++ p2p/pex/addrbook.go | 15 ++++---- rpc/client/helpers.go | 13 ++++++- rpc/core/consensus.go | 5 ++- rpc/core/mempool.go | 66 +++++++++++++++++++---------------- 18 files changed, 217 insertions(+), 96 deletions(-) diff --git a/CHANGELOG_PENDING.md b/CHANGELOG_PENDING.md index ea3b9759..ea7d97c9 100644 --- a/CHANGELOG_PENDING.md +++ b/CHANGELOG_PENDING.md @@ -26,3 +26,6 @@ Friendly reminder, we have a [bug bounty program](https://hackerone.com/tendermi ### BUG FIXES: +- [abci] unlock mutex in localClient so even when app panics (e.g. during CheckTx), consensus continue working +- [abci] fix DATA RACE in localClient +- [rpc] drain channel before calling Unsubscribe(All) in /broadcast_tx_commit diff --git a/abci/client/client.go b/abci/client/client.go index 55858810..e1eea5d4 100644 --- a/abci/client/client.go +++ b/abci/client/client.go @@ -105,8 +105,8 @@ func (reqRes *ReqRes) SetCallback(cb func(res *types.Response)) { return } - defer reqRes.mtx.Unlock() reqRes.cb = cb + reqRes.mtx.Unlock() } func (reqRes *ReqRes) GetCallback() func(*types.Response) { diff --git a/abci/client/grpc_client.go b/abci/client/grpc_client.go index 4f37b17b..d5cd233a 100644 --- a/abci/client/grpc_client.go +++ b/abci/client/grpc_client.go @@ -111,8 +111,8 @@ func (cli *grpcClient) Error() error { // NOTE: callback may get internally generated flush responses. func (cli *grpcClient) SetResponseCallback(resCb Callback) { cli.mtx.Lock() - defer cli.mtx.Unlock() cli.resCb = resCb + cli.mtx.Unlock() } //---------------------------------------- diff --git a/abci/client/local_client.go b/abci/client/local_client.go index 3ac3b6af..d0e50c33 100644 --- a/abci/client/local_client.go +++ b/abci/client/local_client.go @@ -9,8 +9,13 @@ import ( var _ Client = (*localClient)(nil) +// NOTE: use defer to unlock mutex because Application might panic (e.g., in +// case of malicious tx or query). It only makes sense for publicly exposed +// methods like CheckTx (/broadcast_tx_* RPC endpoint) or Query (/abci_query +// RPC endpoint), but defers are used everywhere for the sake of consistency. type localClient struct { cmn.BaseService + mtx *sync.Mutex types.Application Callback @@ -30,8 +35,8 @@ func NewLocalClient(mtx *sync.Mutex, app types.Application) *localClient { func (app *localClient) SetResponseCallback(cb Callback) { app.mtx.Lock() - defer app.mtx.Unlock() app.Callback = cb + app.mtx.Unlock() } // TODO: change types.Application to include Error()? @@ -45,6 +50,9 @@ func (app *localClient) FlushAsync() *ReqRes { } func (app *localClient) EchoAsync(msg string) *ReqRes { + app.mtx.Lock() + defer app.mtx.Unlock() + return app.callback( types.ToRequestEcho(msg), types.ToResponseEcho(msg), @@ -53,8 +61,9 @@ func (app *localClient) EchoAsync(msg string) *ReqRes { func (app *localClient) InfoAsync(req types.RequestInfo) *ReqRes { app.mtx.Lock() + defer app.mtx.Unlock() + res := app.Application.Info(req) - app.mtx.Unlock() return app.callback( types.ToRequestInfo(req), types.ToResponseInfo(res), @@ -63,8 +72,9 @@ func (app *localClient) InfoAsync(req types.RequestInfo) *ReqRes { func (app *localClient) SetOptionAsync(req types.RequestSetOption) *ReqRes { app.mtx.Lock() + defer app.mtx.Unlock() + res := app.Application.SetOption(req) - app.mtx.Unlock() return app.callback( types.ToRequestSetOption(req), types.ToResponseSetOption(res), @@ -73,8 +83,9 @@ func (app *localClient) SetOptionAsync(req types.RequestSetOption) *ReqRes { func (app *localClient) DeliverTxAsync(tx []byte) *ReqRes { app.mtx.Lock() + defer app.mtx.Unlock() + res := app.Application.DeliverTx(tx) - app.mtx.Unlock() return app.callback( types.ToRequestDeliverTx(tx), types.ToResponseDeliverTx(res), @@ -83,8 +94,9 @@ func (app *localClient) DeliverTxAsync(tx []byte) *ReqRes { func (app *localClient) CheckTxAsync(tx []byte) *ReqRes { app.mtx.Lock() + defer app.mtx.Unlock() + res := app.Application.CheckTx(tx) - app.mtx.Unlock() return app.callback( types.ToRequestCheckTx(tx), types.ToResponseCheckTx(res), @@ -93,8 +105,9 @@ func (app *localClient) CheckTxAsync(tx []byte) *ReqRes { func (app *localClient) QueryAsync(req types.RequestQuery) *ReqRes { app.mtx.Lock() + defer app.mtx.Unlock() + res := app.Application.Query(req) - app.mtx.Unlock() return app.callback( types.ToRequestQuery(req), types.ToResponseQuery(res), @@ -103,8 +116,9 @@ func (app *localClient) QueryAsync(req types.RequestQuery) *ReqRes { func (app *localClient) CommitAsync() *ReqRes { app.mtx.Lock() + defer app.mtx.Unlock() + res := app.Application.Commit() - app.mtx.Unlock() return app.callback( types.ToRequestCommit(), types.ToResponseCommit(res), @@ -113,19 +127,20 @@ func (app *localClient) CommitAsync() *ReqRes { func (app *localClient) InitChainAsync(req types.RequestInitChain) *ReqRes { app.mtx.Lock() + defer app.mtx.Unlock() + res := app.Application.InitChain(req) - reqRes := app.callback( + return app.callback( types.ToRequestInitChain(req), types.ToResponseInitChain(res), ) - app.mtx.Unlock() - return reqRes } func (app *localClient) BeginBlockAsync(req types.RequestBeginBlock) *ReqRes { app.mtx.Lock() + defer app.mtx.Unlock() + res := app.Application.BeginBlock(req) - app.mtx.Unlock() return app.callback( types.ToRequestBeginBlock(req), types.ToResponseBeginBlock(res), @@ -134,8 +149,9 @@ func (app *localClient) BeginBlockAsync(req types.RequestBeginBlock) *ReqRes { func (app *localClient) EndBlockAsync(req types.RequestEndBlock) *ReqRes { app.mtx.Lock() + defer app.mtx.Unlock() + res := app.Application.EndBlock(req) - app.mtx.Unlock() return app.callback( types.ToRequestEndBlock(req), types.ToResponseEndBlock(res), @@ -154,64 +170,73 @@ func (app *localClient) EchoSync(msg string) (*types.ResponseEcho, error) { func (app *localClient) InfoSync(req types.RequestInfo) (*types.ResponseInfo, error) { app.mtx.Lock() + defer app.mtx.Unlock() + res := app.Application.Info(req) - app.mtx.Unlock() return &res, nil } func (app *localClient) SetOptionSync(req types.RequestSetOption) (*types.ResponseSetOption, error) { app.mtx.Lock() + defer app.mtx.Unlock() + res := app.Application.SetOption(req) - app.mtx.Unlock() return &res, nil } func (app *localClient) DeliverTxSync(tx []byte) (*types.ResponseDeliverTx, error) { app.mtx.Lock() + defer app.mtx.Unlock() + res := app.Application.DeliverTx(tx) - app.mtx.Unlock() return &res, nil } func (app *localClient) CheckTxSync(tx []byte) (*types.ResponseCheckTx, error) { app.mtx.Lock() + defer app.mtx.Unlock() + res := app.Application.CheckTx(tx) - app.mtx.Unlock() return &res, nil } func (app *localClient) QuerySync(req types.RequestQuery) (*types.ResponseQuery, error) { app.mtx.Lock() + defer app.mtx.Unlock() + res := app.Application.Query(req) - app.mtx.Unlock() return &res, nil } func (app *localClient) CommitSync() (*types.ResponseCommit, error) { app.mtx.Lock() + defer app.mtx.Unlock() + res := app.Application.Commit() - app.mtx.Unlock() return &res, nil } func (app *localClient) InitChainSync(req types.RequestInitChain) (*types.ResponseInitChain, error) { app.mtx.Lock() + defer app.mtx.Unlock() + res := app.Application.InitChain(req) - app.mtx.Unlock() return &res, nil } func (app *localClient) BeginBlockSync(req types.RequestBeginBlock) (*types.ResponseBeginBlock, error) { app.mtx.Lock() + defer app.mtx.Unlock() + res := app.Application.BeginBlock(req) - app.mtx.Unlock() return &res, nil } func (app *localClient) EndBlockSync(req types.RequestEndBlock) (*types.ResponseEndBlock, error) { app.mtx.Lock() + defer app.mtx.Unlock() + res := app.Application.EndBlock(req) - app.mtx.Unlock() return &res, nil } diff --git a/abci/client/socket_client.go b/abci/client/socket_client.go index affea1a9..531d12bc 100644 --- a/abci/client/socket_client.go +++ b/abci/client/socket_client.go @@ -118,8 +118,8 @@ func (cli *socketClient) Error() error { // NOTE: callback may get internally generated flush responses. func (cli *socketClient) SetResponseCallback(resCb Callback) { cli.mtx.Lock() - defer cli.mtx.Unlock() cli.resCb = resCb + cli.mtx.Unlock() } //---------------------------------------- diff --git a/consensus/reactor.go b/consensus/reactor.go index 12e8e0f1..1768a8f0 100644 --- a/consensus/reactor.go +++ b/consensus/reactor.go @@ -183,7 +183,11 @@ func (conR *ConsensusReactor) RemovePeer(peer p2p.Peer, reason interface{}) { return } // TODO - //peer.Get(PeerStateKey).(*PeerState).Disconnect() + // ps, ok := peer.Get(PeerStateKey).(*PeerState) + // if !ok { + // panic(fmt.Sprintf("Peer %v has no state", peer)) + // } + // ps.Disconnect() } // Receive implements Reactor @@ -214,7 +218,10 @@ func (conR *ConsensusReactor) Receive(chID byte, src p2p.Peer, msgBytes []byte) conR.Logger.Debug("Receive", "src", src, "chId", chID, "msg", msg) // Get peer states - ps := src.Get(types.PeerStateKey).(*PeerState) + ps, ok := src.Get(types.PeerStateKey).(*PeerState) + if !ok { + panic(fmt.Sprintf("Peer %v has no state", src)) + } switch chID { case StateChannel: @@ -293,9 +300,9 @@ func (conR *ConsensusReactor) Receive(chID byte, src p2p.Peer, msgBytes []byte) switch msg := msg.(type) { case *VoteMessage: cs := conR.conS - cs.mtx.Lock() + cs.mtx.RLock() height, valSize, lastCommitSize := cs.Height, cs.Validators.Size(), cs.LastCommit.Size() - cs.mtx.Unlock() + cs.mtx.RUnlock() ps.EnsureVoteBitArrays(height, valSize) ps.EnsureVoteBitArrays(height-1, lastCommitSize) ps.SetHasVote(msg.Vote) @@ -428,7 +435,10 @@ func (conR *ConsensusReactor) broadcastHasVoteMessage(vote *types.Vote) { /* // TODO: Make this broadcast more selective. for _, peer := range conR.Switch.Peers().List() { - ps := peer.Get(PeerStateKey).(*PeerState) + ps, ok := peer.Get(PeerStateKey).(*PeerState) + if !ok { + panic(fmt.Sprintf("Peer %v has no state", peer)) + } prs := ps.GetRoundState() if prs.Height == vote.Height { // TODO: Also filter on round? @@ -826,7 +836,10 @@ func (conR *ConsensusReactor) peerStatsRoutine() { continue } // Get peer state - ps := peer.Get(types.PeerStateKey).(*PeerState) + ps, ok := peer.Get(types.PeerStateKey).(*PeerState) + if !ok { + panic(fmt.Sprintf("Peer %v has no state", peer)) + } switch msg.Msg.(type) { case *VoteMessage: if numVotes := ps.RecordVote(); numVotes%votesToContributeToBecomeGoodPeer == 0 { @@ -859,7 +872,10 @@ func (conR *ConsensusReactor) StringIndented(indent string) string { s := "ConsensusReactor{\n" s += indent + " " + conR.conS.StringIndented(indent+" ") + "\n" for _, peer := range conR.Switch.Peers().List() { - ps := peer.Get(types.PeerStateKey).(*PeerState) + ps, ok := peer.Get(types.PeerStateKey).(*PeerState) + if !ok { + panic(fmt.Sprintf("Peer %v has no state", peer)) + } s += indent + " " + ps.StringIndented(indent+" ") + "\n" } s += indent + "}" diff --git a/consensus/replay_file.go b/consensus/replay_file.go index 685eb71f..a326e70e 100644 --- a/consensus/replay_file.go +++ b/consensus/replay_file.go @@ -58,7 +58,18 @@ func (cs *ConsensusState) ReplayFile(file string, console bool) error { if err != nil { return errors.Errorf("failed to subscribe %s to %v", subscriber, types.EventQueryNewRoundStep) } - defer cs.eventBus.Unsubscribe(ctx, subscriber, types.EventQueryNewRoundStep) + defer func() { + // drain newStepCh to make sure we don't block + LOOP: + for { + select { + case <-newStepCh: + default: + break LOOP + } + } + cs.eventBus.Unsubscribe(ctx, subscriber, types.EventQueryNewRoundStep) + }() // just open the file for reading, no need to use wal fp, err := os.OpenFile(file, os.O_RDONLY, 0600) @@ -221,7 +232,18 @@ func (pb *playback) replayConsoleLoop() int { if err != nil { cmn.Exit(fmt.Sprintf("failed to subscribe %s to %v", subscriber, types.EventQueryNewRoundStep)) } - defer pb.cs.eventBus.Unsubscribe(ctx, subscriber, types.EventQueryNewRoundStep) + defer func() { + // drain newStepCh to make sure we don't block + LOOP: + for { + select { + case <-newStepCh: + default: + break LOOP + } + } + pb.cs.eventBus.Unsubscribe(ctx, subscriber, types.EventQueryNewRoundStep) + }() if len(tokens) == 1 { if err := pb.replayReset(1, newStepCh); err != nil { diff --git a/consensus/state.go b/consensus/state.go index 8c2e292c..e8603011 100644 --- a/consensus/state.go +++ b/consensus/state.go @@ -207,18 +207,16 @@ func (cs *ConsensusState) GetState() sm.State { // GetLastHeight returns the last height committed. // If there were no blocks, returns 0. func (cs *ConsensusState) GetLastHeight() int64 { - cs.mtx.Lock() - defer cs.mtx.Unlock() - + cs.mtx.RLock() + defer cs.mtx.RUnlock() return cs.RoundState.Height - 1 } // GetRoundState returns a shallow copy of the internal consensus state. func (cs *ConsensusState) GetRoundState() *cstypes.RoundState { cs.mtx.RLock() - defer cs.mtx.RUnlock() - rs := cs.RoundState // copy + cs.mtx.RUnlock() return &rs } @@ -226,7 +224,6 @@ func (cs *ConsensusState) GetRoundState() *cstypes.RoundState { func (cs *ConsensusState) GetRoundStateJSON() ([]byte, error) { cs.mtx.RLock() defer cs.mtx.RUnlock() - return cdc.MarshalJSON(cs.RoundState) } @@ -234,7 +231,6 @@ func (cs *ConsensusState) GetRoundStateJSON() ([]byte, error) { func (cs *ConsensusState) GetRoundStateSimpleJSON() ([]byte, error) { cs.mtx.RLock() defer cs.mtx.RUnlock() - return cdc.MarshalJSON(cs.RoundState.RoundStateSimple()) } @@ -248,15 +244,15 @@ func (cs *ConsensusState) GetValidators() (int64, []*types.Validator) { // SetPrivValidator sets the private validator account for signing votes. func (cs *ConsensusState) SetPrivValidator(priv types.PrivValidator) { cs.mtx.Lock() - defer cs.mtx.Unlock() cs.privValidator = priv + cs.mtx.Unlock() } // SetTimeoutTicker sets the local timer. It may be useful to overwrite for testing. func (cs *ConsensusState) SetTimeoutTicker(timeoutTicker TimeoutTicker) { cs.mtx.Lock() - defer cs.mtx.Unlock() cs.timeoutTicker = timeoutTicker + cs.mtx.Unlock() } // LoadCommit loads the commit for a given height. diff --git a/evidence/reactor.go b/evidence/reactor.go index 32753b2b..48092fdf 100644 --- a/evidence/reactor.go +++ b/evidence/reactor.go @@ -160,12 +160,15 @@ func (evR *EvidenceReactor) broadcastEvidenceRoutine(peer p2p.Peer) { // Returns the message to send the peer, or nil if the evidence is invalid for the peer. // If message is nil, return true if we should sleep and try again. func (evR EvidenceReactor) checkSendEvidenceMessage(peer p2p.Peer, ev types.Evidence) (msg EvidenceMessage, retry bool) { - // make sure the peer is up to date evHeight := ev.Height() peerState, ok := peer.Get(types.PeerStateKey).(PeerState) - if !ok { - evR.Logger.Info("Found peer without PeerState", "peer", peer) + if !ok { + // Peer does not have a state yet. We set it in the consensus reactor, but + // when we add peer in Switch, the order we call reactors#AddPeer is + // different every time due to us using a map. Sometimes other reactors + // will be initialized before the consensus reactor. We should wait a few + // milliseconds and retry. return nil, true } diff --git a/evidence/reactor_test.go b/evidence/reactor_test.go index 69dcdec5..1c4e731a 100644 --- a/evidence/reactor_test.go +++ b/evidence/reactor_test.go @@ -165,6 +165,16 @@ func TestReactorSelectiveBroadcast(t *testing.T) { // make reactors from statedb reactors := makeAndConnectEvidenceReactors(config, []dbm.DB{stateDB1, stateDB2}) + + // set the peer height on each reactor + for _, r := range reactors { + for _, peer := range r.Switch.Peers().List() { + ps := peerState{height1} + peer.Set(types.PeerStateKey, ps) + } + } + + // update the first reactor peer's height to be very small peer := reactors[0].Switch.Peers().List()[0] ps := peerState{height2} peer.Set(types.PeerStateKey, ps) diff --git a/libs/pubsub/pubsub.go b/libs/pubsub/pubsub.go index 18f098d8..b4e392bb 100644 --- a/libs/pubsub/pubsub.go +++ b/libs/pubsub/pubsub.go @@ -30,9 +30,15 @@ // // s.Subscribe(ctx, sub, qry, out) // defer func() { -// for range out { -// // drain out to make sure we don't block -// } +// // drain out to make sure we don't block +// LOOP: +// for { +// select { +// case <-out: +// default: +// break LOOP +// } +// } // s.UnsubscribeAll(ctx, sub) // }() // for msg := range out { diff --git a/mempool/mempool.go b/mempool/mempool.go index b84eb4a6..0bdb4714 100644 --- a/mempool/mempool.go +++ b/mempool/mempool.go @@ -300,6 +300,7 @@ func (mem *Mempool) TxsWaitChan() <-chan struct{} { // CONTRACT: Either cb will get called, or err returned. func (mem *Mempool) CheckTx(tx types.Tx, cb func(*abci.Response)) (err error) { mem.proxyMtx.Lock() + // use defer to unlock mutex because application (*local client*) might panic defer mem.proxyMtx.Unlock() if mem.Size() >= mem.config.Size { diff --git a/mempool/reactor.go b/mempool/reactor.go index 96988be7..072f9667 100644 --- a/mempool/reactor.go +++ b/mempool/reactor.go @@ -133,16 +133,23 @@ func (memR *MempoolReactor) broadcastTxRoutine(peer p2p.Peer) { } memTx := next.Value.(*mempoolTx) + // make sure the peer is up to date - height := memTx.Height() - if peerState_i := peer.Get(types.PeerStateKey); peerState_i != nil { - peerState := peerState_i.(PeerState) - peerHeight := peerState.GetHeight() - if peerHeight < height-1 { // Allow for a lag of 1 block - time.Sleep(peerCatchupSleepIntervalMS * time.Millisecond) - continue - } + peerState, ok := peer.Get(types.PeerStateKey).(PeerState) + if !ok { + // Peer does not have a state yet. We set it in the consensus reactor, but + // when we add peer in Switch, the order we call reactors#AddPeer is + // different every time due to us using a map. Sometimes other reactors + // will be initialized before the consensus reactor. We should wait a few + // milliseconds and retry. + time.Sleep(peerCatchupSleepIntervalMS * time.Millisecond) + continue } + if peerState.GetHeight() < memTx.Height()-1 { // Allow for a lag of 1 block + time.Sleep(peerCatchupSleepIntervalMS * time.Millisecond) + continue + } + // send memTx msg := &TxMessage{Tx: memTx.tx} success := peer.Send(MempoolChannel, cdc.MustMarshalBinaryBare(msg)) diff --git a/mempool/reactor_test.go b/mempool/reactor_test.go index 8ac400b0..ad9ad8b4 100644 --- a/mempool/reactor_test.go +++ b/mempool/reactor_test.go @@ -21,6 +21,14 @@ import ( "github.com/tendermint/tendermint/types" ) +type peerState struct { + height int64 +} + +func (ps peerState) GetHeight() int64 { + return ps.height +} + // mempoolLogger is a TestingLogger which uses a different // color for each validator ("validator" key must exist). func mempoolLogger() log.Logger { @@ -107,6 +115,11 @@ func TestReactorBroadcastTxMessage(t *testing.T) { r.Stop() } }() + for _, r := range reactors { + for _, peer := range r.Switch.Peers().List() { + peer.Set(types.PeerStateKey, peerState{1}) + } + } // send a bunch of txs to the first reactor's mempool // and wait for them all to be received in the others diff --git a/p2p/pex/addrbook.go b/p2p/pex/addrbook.go index 405a4628..e2fcc043 100644 --- a/p2p/pex/addrbook.go +++ b/p2p/pex/addrbook.go @@ -162,10 +162,10 @@ func (a *addrBook) FilePath() string { // AddOurAddress one of our addresses. func (a *addrBook) AddOurAddress(addr *p2p.NetAddress) { - a.mtx.Lock() - defer a.mtx.Unlock() a.Logger.Info("Add our address to book", "addr", addr) + a.mtx.Lock() a.ourAddrs[addr.String()] = struct{}{} + a.mtx.Unlock() } // OurAddress returns true if it is our address. @@ -178,10 +178,10 @@ func (a *addrBook) OurAddress(addr *p2p.NetAddress) bool { func (a *addrBook) AddPrivateIDs(IDs []string) { a.mtx.Lock() - defer a.mtx.Unlock() for _, id := range IDs { a.privateIDs[p2p.ID(id)] = struct{}{} } + a.mtx.Unlock() } // AddAddress implements AddrBook @@ -202,7 +202,7 @@ func (a *addrBook) RemoveAddress(addr *p2p.NetAddress) { if ka == nil { return } - a.Logger.Info("Remove address from book", "addr", ka.Addr, "ID", ka.ID()) + a.Logger.Info("Remove address from book", "addr", addr) a.removeFromAllBuckets(ka) } @@ -217,8 +217,8 @@ func (a *addrBook) IsGood(addr *p2p.NetAddress) bool { // HasAddress returns true if the address is in the book. func (a *addrBook) HasAddress(addr *p2p.NetAddress) bool { a.mtx.Lock() - defer a.mtx.Unlock() ka := a.addrLookup[addr.ID] + a.mtx.Unlock() return ka != nil } @@ -461,13 +461,12 @@ ADDRS_LOOP: // ListOfKnownAddresses returns the new and old addresses. func (a *addrBook) ListOfKnownAddresses() []*knownAddress { - a.mtx.Lock() - defer a.mtx.Unlock() - addrs := []*knownAddress{} + a.mtx.Lock() for _, addr := range a.addrLookup { addrs = append(addrs, addr.copy()) } + a.mtx.Unlock() return addrs } diff --git a/rpc/client/helpers.go b/rpc/client/helpers.go index 7e64d116..2e80a306 100644 --- a/rpc/client/helpers.go +++ b/rpc/client/helpers.go @@ -69,7 +69,18 @@ func WaitForOneEvent(c EventsClient, evtTyp string, timeout time.Duration) (type } // make sure to unregister after the test is over - defer c.UnsubscribeAll(ctx, subscriber) + defer func() { + // drain evts to make sure we don't block + LOOP: + for { + select { + case <-evts: + default: + break LOOP + } + } + c.UnsubscribeAll(ctx, subscriber) + }() select { case evt := <-evts: diff --git a/rpc/core/consensus.go b/rpc/core/consensus.go index 1c2619d5..14662885 100644 --- a/rpc/core/consensus.go +++ b/rpc/core/consensus.go @@ -193,7 +193,10 @@ func DumpConsensusState() (*ctypes.ResultDumpConsensusState, error) { peers := p2pPeers.Peers().List() peerStates := make([]ctypes.PeerStateInfo, len(peers)) for i, peer := range peers { - peerState := peer.Get(types.PeerStateKey).(*cm.PeerState) + peerState, ok := peer.Get(types.PeerStateKey).(*cm.PeerState) + if !ok { // peer does not have a state yet + continue + } peerStateJSON, err := peerState.ToJSON() if err != nil { return nil, err diff --git a/rpc/core/mempool.go b/rpc/core/mempool.go index c015363a..59877492 100644 --- a/rpc/core/mempool.go +++ b/rpc/core/mempool.go @@ -8,7 +8,6 @@ import ( "github.com/pkg/errors" abci "github.com/tendermint/tendermint/abci/types" - cmn "github.com/tendermint/tendermint/libs/common" ctypes "github.com/tendermint/tendermint/rpc/core/types" "github.com/tendermint/tendermint/types" ) @@ -51,7 +50,7 @@ import ( func BroadcastTxAsync(tx types.Tx) (*ctypes.ResultBroadcastTx, error) { err := mempool.CheckTx(tx, nil) if err != nil { - return nil, fmt.Errorf("Error broadcasting transaction: %v", err) + return nil, err } return &ctypes.ResultBroadcastTx{Hash: tx.Hash()}, nil } @@ -94,7 +93,7 @@ func BroadcastTxSync(tx types.Tx) (*ctypes.ResultBroadcastTx, error) { resCh <- res }) if err != nil { - return nil, fmt.Errorf("Error broadcasting transaction: %v", err) + return nil, err } res := <-resCh r := res.GetCheckTx() @@ -106,8 +105,9 @@ func BroadcastTxSync(tx types.Tx) (*ctypes.ResultBroadcastTx, error) { }, nil } -// CONTRACT: only returns error if mempool.BroadcastTx errs (ie. problem with the app) -// or if we timeout waiting for tx to commit. +// CONTRACT: only returns error if mempool.CheckTx() errs or if we timeout +// waiting for tx to commit. +// // If CheckTx or DeliverTx fail, no error will be returned, but the returned result // will contain a non-OK ABCI code. // @@ -150,20 +150,31 @@ func BroadcastTxSync(tx types.Tx) (*ctypes.ResultBroadcastTx, error) { // |-----------+------+---------+----------+-----------------| // | tx | Tx | nil | true | The transaction | func BroadcastTxCommit(tx types.Tx) (*ctypes.ResultBroadcastTxCommit, error) { - // subscribe to tx being committed in block + // Subscribe to tx being committed in block. ctx, cancel := context.WithTimeout(context.Background(), subscribeTimeout) defer cancel() - deliverTxResCh := make(chan interface{}) + deliverTxResCh := make(chan interface{}, 1) q := types.EventQueryTxFor(tx) err := eventBus.Subscribe(ctx, "mempool", q, deliverTxResCh) if err != nil { err = errors.Wrap(err, "failed to subscribe to tx") - logger.Error("Error on broadcastTxCommit", "err", err) - return nil, fmt.Errorf("Error on broadcastTxCommit: %v", err) + logger.Error("Error on broadcast_tx_commit", "err", err) + return nil, err } - defer eventBus.Unsubscribe(context.Background(), "mempool", q) + defer func() { + // drain deliverTxResCh to make sure we don't block + LOOP: + for { + select { + case <-deliverTxResCh: + default: + break LOOP + } + } + eventBus.Unsubscribe(context.Background(), "mempool", q) + }() - // broadcast the tx and register checktx callback + // Broadcast tx and wait for CheckTx result checkTxResCh := make(chan *abci.Response, 1) err = mempool.CheckTx(tx, func(res *abci.Response) { checkTxResCh <- res @@ -172,40 +183,35 @@ func BroadcastTxCommit(tx types.Tx) (*ctypes.ResultBroadcastTxCommit, error) { logger.Error("Error on broadcastTxCommit", "err", err) return nil, fmt.Errorf("Error on broadcastTxCommit: %v", err) } - checkTxRes := <-checkTxResCh - checkTxR := checkTxRes.GetCheckTx() - if checkTxR.Code != abci.CodeTypeOK { - // CheckTx failed! + checkTxResMsg := <-checkTxResCh + checkTxRes := checkTxResMsg.GetCheckTx() + if checkTxRes.Code != abci.CodeTypeOK { return &ctypes.ResultBroadcastTxCommit{ - CheckTx: *checkTxR, + CheckTx: *checkTxRes, DeliverTx: abci.ResponseDeliverTx{}, Hash: tx.Hash(), }, nil } - // Wait for the tx to be included in a block, - // timeout after something reasonable. - // TODO: configurable? - timer := time.NewTimer(60 * 2 * time.Second) + // Wait for the tx to be included in a block or timeout. + var deliverTxTimeout = 10 * time.Second // TODO: configurable? select { - case deliverTxResMsg := <-deliverTxResCh: + case deliverTxResMsg := <-deliverTxResCh: // The tx was included in a block. deliverTxRes := deliverTxResMsg.(types.EventDataTx) - // The tx was included in a block. - deliverTxR := deliverTxRes.Result - logger.Info("DeliverTx passed ", "tx", cmn.HexBytes(tx), "response", deliverTxR) return &ctypes.ResultBroadcastTxCommit{ - CheckTx: *checkTxR, - DeliverTx: deliverTxR, + CheckTx: *checkTxRes, + DeliverTx: deliverTxRes.Result, Hash: tx.Hash(), Height: deliverTxRes.Height, }, nil - case <-timer.C: - logger.Error("failed to include tx") + case <-time.After(deliverTxTimeout): + err = errors.New("Timed out waiting for tx to be included in a block") + logger.Error("Error on broadcastTxCommit", "err", err) return &ctypes.ResultBroadcastTxCommit{ - CheckTx: *checkTxR, + CheckTx: *checkTxRes, DeliverTx: abci.ResponseDeliverTx{}, Hash: tx.Hash(), - }, fmt.Errorf("Timed out waiting for transaction to be included in a block") + }, err } } From bb0e17dbf0ad57daba985123fa4532ebd61ba3ca Mon Sep 17 00:00:00 2001 From: Zach Date: Wed, 14 Nov 2018 05:17:07 -0500 Subject: [PATCH 071/267] arm: add install script, fix Makefile (#2824) * be like the SDK makefile * arm: add install script, fix Makefile * ... --- .circleci/config.yml | 1 + Makefile | 5 ++- scripts/install/install_tendermint_arm.sh | 46 +++++++++++++++++++++++ 3 files changed, 50 insertions(+), 2 deletions(-) create mode 100644 scripts/install/install_tendermint_arm.sh diff --git a/.circleci/config.yml b/.circleci/config.yml index 55a3da4f..0de4a179 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -92,6 +92,7 @@ jobs: command: | export PATH="$GOBIN:$PATH" make get_tools + make get_dev_tools - run: name: dependencies command: | diff --git a/Makefile b/Makefile index 4390b1fb..294a319b 100644 --- a/Makefile +++ b/Makefile @@ -17,7 +17,6 @@ all: check build test install check: check_tools get_vendor_deps - ######################################## ### Build Tendermint @@ -79,6 +78,8 @@ check_tools: get_tools: @echo "--> Installing tools" ./scripts/get_tools.sh + +get_dev_tools: @echo "--> Downloading linters (this may take awhile)" $(GOPATH)/src/github.com/alecthomas/gometalinter/scripts/install.sh -b $(GOBIN) @@ -327,4 +328,4 @@ build-slate: # To avoid unintended conflicts with file names, always add to .PHONY # unless there is a reason not to. # https://www.gnu.org/software/make/manual/html_node/Phony-Targets.html -.PHONY: check build build_race build_abci dist install install_abci check_dep check_tools get_tools update_tools get_vendor_deps draw_deps get_protoc protoc_abci protoc_libs gen_certs clean_certs grpc_dbserver test_cover test_apps test_persistence test_p2p test test_race test_integrations test_release test100 vagrant_test fmt rpc-docs build-linux localnet-start localnet-stop build-docker build-docker-localnode sentry-start sentry-config sentry-stop build-slate protoc_grpc protoc_all +.PHONY: check build build_race build_abci dist install install_abci check_dep check_tools get_tools get_dev_tools update_tools get_vendor_deps draw_deps get_protoc protoc_abci protoc_libs gen_certs clean_certs grpc_dbserver test_cover test_apps test_persistence test_p2p test test_race test_integrations test_release test100 vagrant_test fmt rpc-docs build-linux localnet-start localnet-stop build-docker build-docker-localnode sentry-start sentry-config sentry-stop build-slate protoc_grpc protoc_all diff --git a/scripts/install/install_tendermint_arm.sh b/scripts/install/install_tendermint_arm.sh new file mode 100644 index 00000000..df27fbfc --- /dev/null +++ b/scripts/install/install_tendermint_arm.sh @@ -0,0 +1,46 @@ +#!/usr/bin/env bash + +# XXX: this script is intended to be run from +# a fresh Digital Ocean droplet with Ubuntu + +# upon its completion, you must either reset +# your terminal or run `source ~/.profile` + +# as written, this script will install +# tendermint core from master branch +REPO=github.com/tendermint/tendermint + +# change this to a specific release or branch +BRANCH=master + +sudo apt-get update -y + +# get and unpack golang +curl -O https://storage.googleapis.com/golang/go1.11.2.linux-armv6l.tar.gz +tar -xvf go1.11.2.linux-armv6l.tar.gz + +# move go folder and add go binary to path +sudo mv go /usr/local +echo "export PATH=\$PATH:/usr/local/go/bin" >> ~/.profile + +# create the goApps directory, set GOPATH, and put it on PATH +mkdir goApps +echo "export GOPATH=$HOME/goApps" >> ~/.profile +echo "export PATH=\$PATH:\$GOPATH/bin" >> ~/.profile +source ~/.profile + +# get the code and move into repo +go get $REPO +cd "$GOPATH/src/$REPO" + +# build & install +git checkout $BRANCH +# XXX: uncomment if branch isn't master +# git fetch origin $BRANCH +make get_tools +make get_vendor_deps +make install + +# the binary is located in $GOPATH/bin +# run `source ~/.profile` or reset your terminal +# to persist the changes From 27fcf96556fbd07160aa2a42cea2867448b72633 Mon Sep 17 00:00:00 2001 From: Zach Date: Wed, 14 Nov 2018 05:34:10 -0500 Subject: [PATCH 072/267] update genesis docs, closes #2814 (#2831) --- docs/tendermint-core/using-tendermint.md | 52 ++++++++++-------------- types/genesis.go | 3 ++ 2 files changed, 25 insertions(+), 30 deletions(-) diff --git a/docs/tendermint-core/using-tendermint.md b/docs/tendermint-core/using-tendermint.md index 5ee18361..c9915042 100644 --- a/docs/tendermint-core/using-tendermint.md +++ b/docs/tendermint-core/using-tendermint.md @@ -60,42 +60,34 @@ definition](https://github.com/tendermint/tendermint/blob/master/types/genesis.g ``` { - "genesis_time": "2018-07-09T22:43:06.255718641Z", - "chain_id": "chain-IAkWsK", + "genesis_time": "2018-11-13T18:11:50.277637Z", + "chain_id": "test-chain-s4ui7D", + "consensus_params": { + "block_size": { + "max_bytes": "22020096", + "max_gas": "-1" + }, + "evidence": { + "max_age": "100000" + }, + "validator": { + "pub_key_types": [ + "ed25519" + ] + } + }, "validators": [ { + "address": "39C04A480B54AB258A45355A5E48ADDED9956C65", "pub_key": { "type": "tendermint/PubKeyEd25519", - "value": "oX8HhKsErMluxI0QWNSR8djQMSupDvHdAYrHwP7n73k=" + "value": "DMEMMj1+thrkUCGocbvvKzXeaAtRslvX9MWtB+smuIA=" }, - "power": "1", - "name": "node0" - }, - { - "pub_key": { - "type": "tendermint/PubKeyEd25519", - "value": "UZNSJA9zmeFQj36Rs296lY+WFQ4Rt6s7snPpuKypl5I=" - }, - "power": "1", - "name": "node1" - }, - { - "pub_key": { - "type": "tendermint/PubKeyEd25519", - "value": "i9GrM6/MHB4zjCelMZBUYHNXYIzl4n0RkDCVmmLhS/o=" - }, - "power": "1", - "name": "node2" - }, - { - "pub_key": { - "type": "tendermint/PubKeyEd25519", - "value": "0qq7954l87trEqbQV9c7d1gurnjTGMxreXc848ZZ5aw=" - }, - "power": "1", - "name": "node3" + "power": "10", + "name": "" } - ] + ], + "app_hash": "" } ``` diff --git a/types/genesis.go b/types/genesis.go index 8684eb33..54b81e9e 100644 --- a/types/genesis.go +++ b/types/genesis.go @@ -19,6 +19,9 @@ const ( //------------------------------------------------------------ // core types for a genesis definition +// NOTE: any changes to the genesis definition should +// be reflected in the documentation: +// docs/tendermint-core/using-tendermint.md // GenesisValidator is an initial validator. type GenesisValidator struct { From 3af11c43f234c65460532933fdaf624bf3e8b66f Mon Sep 17 00:00:00 2001 From: Zach Date: Wed, 14 Nov 2018 05:52:01 -0500 Subject: [PATCH 073/267] cleanup ecosystem docs (#2829) --- docs/app-dev/ecosystem.json | 30 ------------------------------ docs/app-dev/ecosystem.md | 10 ---------- 2 files changed, 40 deletions(-) diff --git a/docs/app-dev/ecosystem.json b/docs/app-dev/ecosystem.json index 1c2bc2b2..f66ed28b 100644 --- a/docs/app-dev/ecosystem.json +++ b/docs/app-dev/ecosystem.json @@ -175,35 +175,5 @@ "language": "Javascript", "author": "Dennis McKinnon" } - ], - "deploymentTools": [ - { - "name": "mintnet-kubernetes", - "url": "https://github.com/tendermint/tools", - "technology": "Docker and Kubernetes", - "author": "Tendermint", - "description": "Deploy a Tendermint test network using Google's kubernetes" - }, - { - "name": "terraforce", - "url": "https://github.com/tendermint/tools", - "technology": "Terraform", - "author": "Tendermint", - "description": "Terraform + our custom terraforce tool; deploy a production Tendermint network with load balancing over multiple AWS availability zones" - }, - { - "name": "ansible-tendermint", - "url": "https://github.com/tendermint/tools", - "technology": "Ansible", - "author": "Tendermint", - "description": "Ansible playbooks + Tendermint" - }, - { - "name": "brooklyn-tendermint", - "url": "https://github.com/cloudsoft/brooklyn-tendermint", - "technology": "Clocker for Apache Brooklyn ", - "author": "Cloudsoft", - "description": "Deploy a tendermint test network in docker containers " - } ] } diff --git a/docs/app-dev/ecosystem.md b/docs/app-dev/ecosystem.md index 7960e6c0..e51ca430 100644 --- a/docs/app-dev/ecosystem.md +++ b/docs/app-dev/ecosystem.md @@ -9,13 +9,3 @@ We thank the community for their contributions thus far and welcome the addition of new projects. A pull request can be submitted to [this file](https://github.com/tendermint/tendermint/blob/master/docs/app-dev/ecosystem.json) to include your project. - -## Other Tools - -See [deploy testnets](./deploy-testnets) for information about all -the tools built by Tendermint. We have Kubernetes, Ansible, and -Terraform integrations. - -For upgrading from older to newer versions of tendermint and to migrate -your chain data, see [tm-migrator](https://github.com/hxzqlh/tm-tools) -written by @hxzqlh. From 6353862ac00cefe9e2710cc6763789a83d9f069a Mon Sep 17 00:00:00 2001 From: Hleb Albau Date: Wed, 14 Nov 2018 15:47:41 +0300 Subject: [PATCH 074/267] 2582 Enable CORS on RPC API (#2800) --- CHANGELOG_PENDING.md | 2 ++ Gopkg.lock | 8 ++++++++ Gopkg.toml | 4 ++++ config/config.go | 23 +++++++++++++++++++++-- config/toml.go | 11 +++++++++++ node/node.go | 16 ++++++++++++++-- rpc/client/rpc_test.go | 18 +++++++++++++++++- rpc/core/doc.go | 5 ++++- rpc/lib/server/http_server.go | 5 ----- rpc/test/helpers.go | 1 + 10 files changed, 82 insertions(+), 11 deletions(-) diff --git a/CHANGELOG_PENDING.md b/CHANGELOG_PENDING.md index ea7d97c9..cd778335 100644 --- a/CHANGELOG_PENDING.md +++ b/CHANGELOG_PENDING.md @@ -22,6 +22,8 @@ Friendly reminder, we have a [bug bounty program](https://hackerone.com/tendermi ### FEATURES: +- [rpc] [\#2582](https://github.com/tendermint/tendermint/issues/2582) Enable CORS on RPC API + ### IMPROVEMENTS: ### BUG FIXES: diff --git a/Gopkg.lock b/Gopkg.lock index 229ed3f9..8695205e 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -128,6 +128,14 @@ revision = "ea4d1f681babbce9545c9c5f3d5194a789c89f5b" version = "v1.2.0" +[[projects]] + digest = "1:b0c25f00bad20d783d259af2af8666969e2fc343fa0dc9efe52936bbd67fb758" + name = "github.com/rs/cors" + packages = ["."] + pruneopts = "UT" + revision = "9a47f48565a795472d43519dd49aac781f3034fb" + version = "v1.6.0" + [[projects]] digest = "1:ea40c24cdbacd054a6ae9de03e62c5f252479b96c716375aace5c120d68647c8" name = "github.com/hashicorp/hcl" diff --git a/Gopkg.toml b/Gopkg.toml index a0ffb920..1ab6a18e 100644 --- a/Gopkg.toml +++ b/Gopkg.toml @@ -40,6 +40,10 @@ name = "github.com/gorilla/websocket" version = "=1.2.0" +[[constraint]] + name = "github.com/rs/cors" + version = "1.6.0" + [[constraint]] name = "github.com/pkg/errors" version = "=0.8.0" diff --git a/config/config.go b/config/config.go index fa618211..ea6582c0 100644 --- a/config/config.go +++ b/config/config.go @@ -242,6 +242,18 @@ type RPCConfig struct { // TCP or UNIX socket address for the RPC server to listen on ListenAddress string `mapstructure:"laddr"` + // A list of origins a cross-domain request can be executed from. + // If the special '*' value is present in the list, all origins will be allowed. + // An origin may contain a wildcard (*) to replace 0 or more characters (i.e.: http://*.domain.com). + // Only one wildcard can be used per origin. + CORSAllowedOrigins []string `mapstructure:"cors_allowed_origins"` + + // A list of methods the client is allowed to use with cross-domain requests. + CORSAllowedMethods []string `mapstructure:"cors_allowed_methods"` + + // A list of non simple headers the client is allowed to use with cross-domain requests. + CORSAllowedHeaders []string `mapstructure:"cors_allowed_headers"` + // TCP or UNIX socket address for the gRPC server to listen on // NOTE: This server only supports /broadcast_tx_commit GRPCListenAddress string `mapstructure:"grpc_laddr"` @@ -269,8 +281,10 @@ type RPCConfig struct { // DefaultRPCConfig returns a default configuration for the RPC server func DefaultRPCConfig() *RPCConfig { return &RPCConfig{ - ListenAddress: "tcp://0.0.0.0:26657", - + ListenAddress: "tcp://0.0.0.0:26657", + CORSAllowedOrigins: []string{}, + CORSAllowedMethods: []string{"HEAD", "GET", "POST"}, + CORSAllowedHeaders: []string{"Origin", "Accept", "Content-Type", "X-Requested-With", "X-Server-Time"}, GRPCListenAddress: "", GRPCMaxOpenConnections: 900, @@ -300,6 +314,11 @@ func (cfg *RPCConfig) ValidateBasic() error { return nil } +// IsCorsEnabled returns true if cross-origin resource sharing is enabled. +func (cfg *RPCConfig) IsCorsEnabled() bool { + return len(cfg.CORSAllowedOrigins) != 0 +} + //----------------------------------------------------------------------------- // P2PConfig diff --git a/config/toml.go b/config/toml.go index d73b9c81..89be3783 100644 --- a/config/toml.go +++ b/config/toml.go @@ -119,6 +119,17 @@ filter_peers = {{ .BaseConfig.FilterPeers }} # TCP or UNIX socket address for the RPC server to listen on laddr = "{{ .RPC.ListenAddress }}" +# A list of origins a cross-domain request can be executed from +# Default value '[]' disables cors support +# Use '["*"]' to allow any origin +cors_allowed_origins = "{{ .RPC.CORSAllowedOrigins }}" + +# A list of methods the client is allowed to use with cross-domain requests +cors_allowed_methods = "{{ .RPC.CORSAllowedMethods }}" + +# A list of non simple headers the client is allowed to use with cross-domain requests +cors_allowed_headers = "{{ .RPC.CORSAllowedHeaders }}" + # TCP or UNIX socket address for the gRPC server to listen on # NOTE: This server only supports /broadcast_tx_commit grpc_laddr = "{{ .RPC.GRPCListenAddress }}" diff --git a/node/node.go b/node/node.go index 0652a392..796bbc2a 100644 --- a/node/node.go +++ b/node/node.go @@ -13,8 +13,9 @@ import ( "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus/promhttp" + "github.com/rs/cors" - amino "github.com/tendermint/go-amino" + "github.com/tendermint/go-amino" abci "github.com/tendermint/tendermint/abci/types" bc "github.com/tendermint/tendermint/blockchain" cfg "github.com/tendermint/tendermint/config" @@ -651,9 +652,20 @@ func (n *Node) startRPC() ([]net.Listener, error) { wm.SetLogger(rpcLogger.With("protocol", "websocket")) mux.HandleFunc("/websocket", wm.WebsocketHandler) rpcserver.RegisterRPCFuncs(mux, rpccore.Routes, coreCodec, rpcLogger) + + var rootHandler http.Handler = mux + if n.config.RPC.IsCorsEnabled() { + corsMiddleware := cors.New(cors.Options{ + AllowedOrigins: n.config.RPC.CORSAllowedOrigins, + AllowedMethods: n.config.RPC.CORSAllowedMethods, + AllowedHeaders: n.config.RPC.CORSAllowedHeaders, + }) + rootHandler = corsMiddleware.Handler(mux) + } + listener, err := rpcserver.StartHTTPServer( listenAddr, - mux, + rootHandler, rpcLogger, rpcserver.Config{MaxOpenConnections: n.config.RPC.MaxOpenConnections}, ) diff --git a/rpc/client/rpc_test.go b/rpc/client/rpc_test.go index 602525b5..217971fd 100644 --- a/rpc/client/rpc_test.go +++ b/rpc/client/rpc_test.go @@ -2,6 +2,7 @@ package client_test import ( "fmt" + "net/http" "strings" "testing" @@ -11,7 +12,7 @@ import ( abci "github.com/tendermint/tendermint/abci/types" "github.com/tendermint/tendermint/rpc/client" - rpctest "github.com/tendermint/tendermint/rpc/test" + "github.com/tendermint/tendermint/rpc/test" "github.com/tendermint/tendermint/types" ) @@ -32,6 +33,21 @@ func GetClients() []client.Client { } } +func TestCorsEnabled(t *testing.T) { + origin := rpctest.GetConfig().RPC.CORSAllowedOrigins[0] + remote := strings.Replace(rpctest.GetConfig().RPC.ListenAddress, "tcp", "http", -1) + + req, err := http.NewRequest("GET", remote, nil) + require.Nil(t, err, "%+v", err) + req.Header.Set("Origin", origin) + c := &http.Client{} + resp, err := c.Do(req) + defer resp.Body.Close() + + require.Nil(t, err, "%+v", err) + assert.Equal(t, resp.Header.Get("Access-Control-Allow-Origin"), origin) +} + // Make sure status is correct (we connect properly) func TestStatus(t *testing.T) { for i, c := range GetClients() { diff --git a/rpc/core/doc.go b/rpc/core/doc.go index 5378dde2..ec79c8e1 100644 --- a/rpc/core/doc.go +++ b/rpc/core/doc.go @@ -12,7 +12,10 @@ See it here: https://github.com/tendermint/tendermint/tree/master/rpc/lib ## Configuration -Set the `laddr` config parameter under `[rpc]` table in the `$TMHOME/config/config.toml` file or the `--rpc.laddr` command-line flag to the desired protocol://host:port setting. Default: `tcp://0.0.0.0:26657`. +RPC can be configured by tuning parameters under `[rpc]` table in the `$TMHOME/config/config.toml` file or by using the `--rpc.X` command-line flags. + +Default rpc listen address is `tcp://0.0.0.0:26657`. To set another address, set the `laddr` config parameter to desired value. +CORS (Cross-Origin Resource Sharing) can be enabled by setting `cors_allowed_origins`, `cors_allowed_methods`, `cors_allowed_headers` config parameters. ## Arguments diff --git a/rpc/lib/server/http_server.go b/rpc/lib/server/http_server.go index 6de376c2..8cacaeef 100644 --- a/rpc/lib/server/http_server.go +++ b/rpc/lib/server/http_server.go @@ -151,11 +151,6 @@ func RecoverAndLogHandler(handler http.Handler, logger log.Logger) http.Handler rww := &ResponseWriterWrapper{-1, w} begin := time.Now() - // Common headers - origin := r.Header.Get("Origin") - rww.Header().Set("Access-Control-Allow-Origin", origin) - rww.Header().Set("Access-Control-Allow-Credentials", "true") - rww.Header().Set("Access-Control-Expose-Headers", "X-Server-Time") rww.Header().Set("X-Server-Time", fmt.Sprintf("%v", begin.Unix())) defer func() { diff --git a/rpc/test/helpers.go b/rpc/test/helpers.go index 0a9cd984..e68ec149 100644 --- a/rpc/test/helpers.go +++ b/rpc/test/helpers.go @@ -84,6 +84,7 @@ func GetConfig() *cfg.Config { tm, rpc, grpc := makeAddrs() globalConfig.P2P.ListenAddress = tm globalConfig.RPC.ListenAddress = rpc + globalConfig.RPC.CORSAllowedOrigins = []string{"https://tendermint.com/"} globalConfig.RPC.GRPCListenAddress = grpc globalConfig.TxIndex.IndexTags = "app.creator,tx.height" // see kvstore application } From 814a88a69bae783ed825b227e366947c45b8b2b3 Mon Sep 17 00:00:00 2001 From: zramsay Date: Wed, 14 Nov 2018 15:41:14 -0500 Subject: [PATCH 075/267] more maintainable/useful install scripts --- scripts/install/install_tendermint_arm.sh | 12 +++++++----- scripts/install/install_tendermint_bsd.sh | 8 +++++--- scripts/install/install_tendermint_ubuntu.sh | 12 +++++++----- 3 files changed, 19 insertions(+), 13 deletions(-) diff --git a/scripts/install/install_tendermint_arm.sh b/scripts/install/install_tendermint_arm.sh index df27fbfc..2e8d50ae 100644 --- a/scripts/install/install_tendermint_arm.sh +++ b/scripts/install/install_tendermint_arm.sh @@ -13,19 +13,21 @@ REPO=github.com/tendermint/tendermint # change this to a specific release or branch BRANCH=master +GO_VERSION=1.11.2 + sudo apt-get update -y # get and unpack golang -curl -O https://storage.googleapis.com/golang/go1.11.2.linux-armv6l.tar.gz -tar -xvf go1.11.2.linux-armv6l.tar.gz +curl -O https://storage.googleapis.com/golang/go$GO_VERSION.linux-armv6l.tar.gz +tar -xvf go$GO_VERSION.linux-armv6l.tar.gz # move go folder and add go binary to path sudo mv go /usr/local echo "export PATH=\$PATH:/usr/local/go/bin" >> ~/.profile -# create the goApps directory, set GOPATH, and put it on PATH -mkdir goApps -echo "export GOPATH=$HOME/goApps" >> ~/.profile +# create the go directory, set GOPATH, and put it on PATH +mkdir go +echo "export GOPATH=$HOME/go" >> ~/.profile echo "export PATH=\$PATH:\$GOPATH/bin" >> ~/.profile source ~/.profile diff --git a/scripts/install/install_tendermint_bsd.sh b/scripts/install/install_tendermint_bsd.sh index 5b30eab3..0f7ef9b5 100644 --- a/scripts/install/install_tendermint_bsd.sh +++ b/scripts/install/install_tendermint_bsd.sh @@ -14,6 +14,9 @@ # change this to a specific release or branch set BRANCH=master +set REPO=github.com/tendermint/tendermint + +set GO_VERSION=1.11.2 sudo pkg update @@ -21,8 +24,8 @@ sudo pkg install -y gmake sudo pkg install -y git # get and unpack golang -curl -O https://storage.googleapis.com/golang/go1.11.freebsd-amd64.tar.gz -tar -xvf go1.11.freebsd-amd64.tar.gz +curl -O https://storage.googleapis.com/golang/go$GO_VERSION.freebsd-amd64.tar.gz +tar -xvf go$GO_VERSION.freebsd-amd64.tar.gz # move go folder and add go binary to path sudo mv go /usr/local @@ -38,7 +41,6 @@ echo "set path=($path $GOPATH/bin)" >> ~/.tcshrc source ~/.tcshrc # get the code and move into repo -set REPO=github.com/tendermint/tendermint go get $REPO cd "$GOPATH/src/$REPO" diff --git a/scripts/install/install_tendermint_ubuntu.sh b/scripts/install/install_tendermint_ubuntu.sh index 29e97508..91ca1598 100644 --- a/scripts/install/install_tendermint_ubuntu.sh +++ b/scripts/install/install_tendermint_ubuntu.sh @@ -13,20 +13,22 @@ REPO=github.com/tendermint/tendermint # change this to a specific release or branch BRANCH=master +GO_VERSION=1.11.2 + sudo apt-get update -y sudo apt-get install -y make # get and unpack golang -curl -O https://storage.googleapis.com/golang/go1.11.linux-amd64.tar.gz -tar -xvf go1.11.linux-amd64.tar.gz +curl -O https://storage.googleapis.com/golang/go$GO_VERSION.linux-amd64.tar.gz +tar -xvf go$GO_VERSION.linux-amd64.tar.gz # move go folder and add go binary to path sudo mv go /usr/local echo "export PATH=\$PATH:/usr/local/go/bin" >> ~/.profile -# create the goApps directory, set GOPATH, and put it on PATH -mkdir goApps -echo "export GOPATH=$HOME/goApps" >> ~/.profile +# create the go directory, set GOPATH, and put it on PATH +mkdir go +echo "export GOPATH=$HOME/go" >> ~/.profile echo "export PATH=\$PATH:\$GOPATH/bin" >> ~/.profile source ~/.profile From 1ce24a62828e9920efa7d7741b5f63f908c7c706 Mon Sep 17 00:00:00 2001 From: zramsay Date: Wed, 14 Nov 2018 15:48:55 -0500 Subject: [PATCH 076/267] docs: update config: ref #2800 & #2837 --- docs/tendermint-core/configuration.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/docs/tendermint-core/configuration.md b/docs/tendermint-core/configuration.md index 8b3c3c22..7052ca50 100644 --- a/docs/tendermint-core/configuration.md +++ b/docs/tendermint-core/configuration.md @@ -68,6 +68,17 @@ filter_peers = false # TCP or UNIX socket address for the RPC server to listen on laddr = "tcp://0.0.0.0:26657" +# A list of origins a cross-domain request can be executed from +# Default value '[]' disables cors support +# Use '["*"]' to allow any origin +cors_allowed_origins = "[]" + +# A list of methods the client is allowed to use with cross-domain requests +cors_allowed_methods = "[HEAD GET POST]" + +# A list of non simple headers the client is allowed to use with cross-domain requests +cors_allowed_headers = "[Origin Accept Content-Type X-Requested-With X-Server-Time]" + # TCP or UNIX socket address for the gRPC server to listen on # NOTE: This server only supports /broadcast_tx_commit grpc_laddr = "" From a70a53254d55016ad597016856facfb7ceea7fed Mon Sep 17 00:00:00 2001 From: Zeyu Zhu Date: Thu, 15 Nov 2018 23:57:13 +0800 Subject: [PATCH 077/267] Optimize: using parameters in func (#2845) Signed-off-by: Zeyu Zhu --- state/store.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/state/store.go b/state/store.go index 7a0ef255..086dcdf5 100644 --- a/state/store.go +++ b/state/store.go @@ -96,7 +96,7 @@ func saveState(db dbm.DB, state State, key []byte) { saveValidatorsInfo(db, nextHeight+1, state.LastHeightValidatorsChanged, state.NextValidators) // Save next consensus params. saveConsensusParamsInfo(db, nextHeight, state.LastHeightConsensusParamsChanged, state.ConsensusParams) - db.SetSync(stateKey, state.Bytes()) + db.SetSync(key, state.Bytes()) } //------------------------------------------------------------------------ From a0412357c17095ef600b0c909b343c04fcd0b20c Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Thu, 15 Nov 2018 12:04:47 -0500 Subject: [PATCH 078/267] update some top-level markdown files (#2841) * update some top-level markdown files * Update README.md Co-Authored-By: ebuchman --- CHANGELOG_PENDING.md | 21 +++++---------------- CODE_OF_CONDUCT.md | 2 +- CONTRIBUTING.md | 40 ++++++++++++++++++++++++++-------------- README.md | 15 ++++++++++----- SECURITY.md | 3 ++- 5 files changed, 44 insertions(+), 37 deletions(-) diff --git a/CHANGELOG_PENDING.md b/CHANGELOG_PENDING.md index cd778335..0216dfb8 100644 --- a/CHANGELOG_PENDING.md +++ b/CHANGELOG_PENDING.md @@ -2,24 +2,12 @@ ## v0.26.2 -*TBA* +*November 15th, 2018* 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 - -* Apps - -* Go API - -* Blockchain Protocol - -* P2P Protocol - ### FEATURES: - [rpc] [\#2582](https://github.com/tendermint/tendermint/issues/2582) Enable CORS on RPC API @@ -28,6 +16,7 @@ Friendly reminder, we have a [bug bounty program](https://hackerone.com/tendermi ### BUG FIXES: -- [abci] unlock mutex in localClient so even when app panics (e.g. during CheckTx), consensus continue working -- [abci] fix DATA RACE in localClient -- [rpc] drain channel before calling Unsubscribe(All) in /broadcast_tx_commit +- [abci] \#2748 Unlock mutex in localClient so even when app panics (e.g. during CheckTx), consensus continue working +- [abci] \#2748 Fix DATA RACE in localClient +- [amino] \#2822 Update to v0.14.1 to support compiling on 32-bit platforms +- [rpc] \#2748 Drain channel before calling Unsubscribe(All) in `/broadcast_tx_commit` diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md index d47c0f15..7088fca4 100644 --- a/CODE_OF_CONDUCT.md +++ b/CODE_OF_CONDUCT.md @@ -6,7 +6,7 @@ This code of conduct applies to all projects run by the Tendermint/COSMOS team a # Conduct -## Contact: adrian@tendermint.com +## Contact: conduct@tendermint.com * We are committed to providing a friendly, safe and welcoming environment for all, regardless of level of experience, gender, gender identity and expression, sexual orientation, disability, personal appearance, body size, race, ethnicity, age, religion, nationality, or other similar characteristic. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 3500732f..a0057aae 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -27,8 +27,8 @@ Of course, replace `ebuchman` with your git handle. To pull in updates from the origin repo, run - * `git fetch upstream` - * `git rebase upstream/master` (or whatever branch you want) + * `git fetch upstream` + * `git rebase upstream/master` (or whatever branch you want) Please don't make Pull Requests to `master`. @@ -50,6 +50,11 @@ as apps, tools, and the core, should use dep. Run `dep status` to get a list of vendor dependencies that may not be up-to-date. +When updating dependencies, please only update the particular dependencies you +need. Instead of running `dep ensure -update`, which will update anything, +specify exactly the dependency you want to update, eg. +`dep ensure -update github.com/tendermint/go-amino`. + ## Vagrant If you are a [Vagrant](https://www.vagrantup.com/) user, you can get started @@ -73,34 +78,41 @@ tested by circle using `go test -v -race ./...`. If not, they will need a `circle.yml`. Ideally, every repo has a `Makefile` that defines `make test` and includes its continuous integration status using a badge in the `README.md`. +## Changelog + ## Branching Model and Release -User-facing repos should adhere to the branching model: http://nvie.com/posts/a-successful-git-branching-model/. -That is, these repos should be well versioned, and any merge to master requires a version bump and tagged release. - -Libraries need not follow the model strictly, but would be wise to, -especially `go-p2p` and `go-rpc`, as their versions are referenced in tendermint core. +All repos should adhere to the branching model: http://nvie.com/posts/a-successful-git-branching-model/. +This means that all pull-requests should be made against develop. Any merge to +master constitutes a tagged release. ### Development Procedure: - the latest state of development is on `develop` - `develop` must never fail `make test` -- no --force onto `develop` (except when reverting a broken commit, which should seldom happen) +- never --force onto `develop` (except when reverting a broken commit, which should seldom happen) - create a development branch either on github.com/tendermint/tendermint, or your fork (using `git remote add origin`) -- before submitting a pull request, begin `git rebase` on top of `develop` +- make changes and update the `CHANGELOG_PENDING.md` to record your change +- before submitting a pull request, run `git rebase` on top of the latest `develop` ### Pull Merge Procedure: -- ensure pull branch is rebased on develop +- ensure pull branch is based on a recent develop - run `make test` to ensure that all tests pass - merge pull request -- the `unstable` branch may be used to aggregate pull merges before testing once -- push master may request that pull requests be rebased on top of `unstable` +- the `unstable` branch may be used to aggregate pull merges before fixing tests ### Release Procedure: - start on `develop` - run integration tests (see `test_integrations` in Makefile) -- prepare changelog/release issue +- prepare changelog: + - copy `CHANGELOG_PENDING.md` to `CHANGELOG.md` + - run `python ./scripts/linkify_changelog.py CHANGELOG.md` to add links for + all issues + - run `bash ./scripts/authors.sh` to get a list of authors since the latest + release, and add the github aliases of external contributors to the top of + the changelog. To lookup an alias from an email, try `bash + ./scripts/authors.sh ` - bump versions -- push to release-vX.X.X to run the extended integration tests on the CI +- push to release/vX.X.X to run the extended integration tests on the CI - merge to master - merge master back to develop diff --git a/README.md b/README.md index 328557ae..7c386ec3 100644 --- a/README.md +++ b/README.md @@ -41,7 +41,7 @@ please [contact us](mailto:partners@tendermint.com) and [join the chat](https:// ## Security To report a security vulnerability, see our [bug bounty -program](https://tendermint.com/security). +program](https://hackerone.com/tendermint) For examples of the kinds of bugs we're looking for, see [SECURITY.md](SECURITY.md) @@ -51,14 +51,18 @@ Requirement|Notes ---|--- Go version | Go1.10 or higher -## Install +## Documentation + +Complete documentation can be found on the [website](https://tendermint.com/docs/). + +### Install See the [install instructions](/docs/introduction/install.md) -## Quick Start +### Quick Start -- [Single node](/docs/tendermint-core/using-tendermint.md) -- [Local cluster using docker-compose](/networks/local) +- [Single node](/docs/introduction/quick-start.md) +- [Local cluster using docker-compose](/docs/networks/docker-compose.md) - [Remote cluster using terraform and ansible](/docs/networks/terraform-and-ansible.md) - [Join the Cosmos testnet](https://cosmos.network/testnet) @@ -91,6 +95,7 @@ Additional documentation is found [here](/docs/tools). ### Research +* [The latest gossip on BFT consensus](https://arxiv.org/abs/1807.04938) * [Master's Thesis on Tendermint](https://atrium.lib.uoguelph.ca/xmlui/handle/10214/9769) * [Original Whitepaper](https://tendermint.com/static/docs/tendermint.pdf) * [Blog](https://blog.cosmos.network/tendermint/home) diff --git a/SECURITY.md b/SECURITY.md index 8b979378..8a373a29 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -1,7 +1,8 @@ # Security As part of our [Coordinated Vulnerability Disclosure -Policy](https://tendermint.com/security), we operate a bug bounty. +Policy](https://tendermint.com/security), we operate a [bug +bounty](https://hackerone.com/tendermint). See the policy for more details on submissions and rewards. Here is a list of examples of the kinds of bugs we're most interested in: From 9973decff9d6abb23350351f59dc8409611b5683 Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Thu, 15 Nov 2018 12:16:24 -0500 Subject: [PATCH 079/267] changelog, versionbump (#2850) --- CHANGELOG.md | 19 +++++++++++++++++++ CHANGELOG_PENDING.md | 27 +++++++++++++++++---------- version/version.go | 2 +- 3 files changed, 37 insertions(+), 11 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 89c5cd8f..bfdb9a50 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,24 @@ # Changelog +## v0.26.2 + +*November 15th, 2018* + +Special thanks to external contributors on this release: @hleb-albau, @zhuzeyu + +Friendly reminder, we have a [bug bounty program](https://hackerone.com/tendermint). + +### FEATURES: + +- [rpc] [\#2582](https://github.com/tendermint/tendermint/issues/2582) Enable CORS on RPC API (@hleb-albau) + +### BUG FIXES: + +- [abci] [\#2748](https://github.com/tendermint/tendermint/issues/2748) Unlock mutex in localClient so even when app panics (e.g. during CheckTx), consensus continue working +- [abci] [\#2748](https://github.com/tendermint/tendermint/issues/2748) Fix DATA RACE in localClient +- [amino] [\#2822](https://github.com/tendermint/tendermint/issues/2822) Update to v0.14.1 to support compiling on 32-bit platforms +- [rpc] [\#2748](https://github.com/tendermint/tendermint/issues/2748) Drain channel before calling Unsubscribe(All) in `/broadcast_tx_commit` + ## v0.26.1 *November 11, 2018* diff --git a/CHANGELOG_PENDING.md b/CHANGELOG_PENDING.md index 0216dfb8..6494867d 100644 --- a/CHANGELOG_PENDING.md +++ b/CHANGELOG_PENDING.md @@ -1,22 +1,29 @@ # Pending -## v0.26.2 +## v0.26.3 -*November 15th, 2018* +*TBD* Special thanks to external contributors on this release: -Friendly reminder, we have a [bug bounty program](https://hackerone.com/tendermint). +Friendly reminder, we have a [bug bounty +program](https://hackerone.com/tendermint). + +### BREAKING CHANGES: + +* CLI/RPC/Config + +* Apps + +* Go API + +* Blockchain Protocol + +* P2P Protocol + ### FEATURES: -- [rpc] [\#2582](https://github.com/tendermint/tendermint/issues/2582) Enable CORS on RPC API - ### IMPROVEMENTS: ### BUG FIXES: - -- [abci] \#2748 Unlock mutex in localClient so even when app panics (e.g. during CheckTx), consensus continue working -- [abci] \#2748 Fix DATA RACE in localClient -- [amino] \#2822 Update to v0.14.1 to support compiling on 32-bit platforms -- [rpc] \#2748 Drain channel before calling Unsubscribe(All) in `/broadcast_tx_commit` diff --git a/version/version.go b/version/version.go index b7a72a7f..aae54512 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.1" + TMCoreSemVer = "0.26.2" // ABCISemVer is the semantic version of the ABCI library ABCISemVer = "0.15.0" From e93b492efe77fbe9987a4f794b907f7535b64a46 Mon Sep 17 00:00:00 2001 From: Anton Kaliaev Date: Thu, 15 Nov 2018 23:40:18 +0400 Subject: [PATCH 080/267] do not pin deps to exact versions (#2844) because - they are locked in .lock file already - individual dependencies can be updated with `dep ensure -update XXX` - review process (and ^^^) should help us prevent accidental updates Closes #2798 --- Gopkg.lock | 23 +++++++++++++---------- Gopkg.toml | 46 ++++++++++++++++++++++------------------------ 2 files changed, 35 insertions(+), 34 deletions(-) diff --git a/Gopkg.lock b/Gopkg.lock index 8695205e..0c4779c8 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -128,14 +128,6 @@ revision = "ea4d1f681babbce9545c9c5f3d5194a789c89f5b" version = "v1.2.0" -[[projects]] - digest = "1:b0c25f00bad20d783d259af2af8666969e2fc343fa0dc9efe52936bbd67fb758" - name = "github.com/rs/cors" - packages = ["."] - pruneopts = "UT" - revision = "9a47f48565a795472d43519dd49aac781f3034fb" - version = "v1.6.0" - [[projects]] digest = "1:ea40c24cdbacd054a6ae9de03e62c5f252479b96c716375aace5c120d68647c8" name = "github.com/hashicorp/hcl" @@ -226,14 +218,16 @@ version = "v1.0.0" [[projects]] - digest = "1:c1a04665f9613e082e1209cf288bf64f4068dcd6c87a64bf1c4ff006ad422ba0" + digest = "1:26663fafdea73a38075b07e8e9d82fc0056379d2be8bb4e13899e8fda7c7dd23" name = "github.com/prometheus/client_golang" packages = [ "prometheus", + "prometheus/internal", "prometheus/promhttp", ] pruneopts = "UT" - revision = "ae27198cdd90bf12cd134ad79d1366a6cf49f632" + revision = "abad2d1bd44235a26707c172eab6bca5bf2dbad3" + version = "v0.9.1" [[projects]] branch = "master" @@ -275,6 +269,14 @@ pruneopts = "UT" revision = "e2704e165165ec55d062f5919b4b29494e9fa790" +[[projects]] + digest = "1:b0c25f00bad20d783d259af2af8666969e2fc343fa0dc9efe52936bbd67fb758" + name = "github.com/rs/cors" + packages = ["."] + pruneopts = "UT" + revision = "9a47f48565a795472d43519dd49aac781f3034fb" + version = "v1.6.0" + [[projects]] digest = "1:6a4a11ba764a56d2758899ec6f3848d24698d48442ebce85ee7a3f63284526cd" name = "github.com/spf13/afero" @@ -524,6 +526,7 @@ "github.com/prometheus/client_golang/prometheus", "github.com/prometheus/client_golang/prometheus/promhttp", "github.com/rcrowley/go-metrics", + "github.com/rs/cors", "github.com/spf13/cobra", "github.com/spf13/viper", "github.com/stretchr/testify/assert", diff --git a/Gopkg.toml b/Gopkg.toml index 1ab6a18e..cc5021c9 100644 --- a/Gopkg.toml +++ b/Gopkg.toml @@ -20,57 +20,60 @@ # unused-packages = true # ########################################################### -# NOTE: All packages should be pinned to specific versions. -# Packages without releases must pin to a commit. - +# Allow only patch releases for serialization libraries [[constraint]] - name = "github.com/go-kit/kit" - version = "=0.6.0" + name = "github.com/tendermint/go-amino" + version = "~0.14.1" [[constraint]] name = "github.com/gogo/protobuf" - version = "=1.1.1" + version = "~1.1.1" [[constraint]] name = "github.com/golang/protobuf" - version = "=1.1.0" + version = "~1.1.0" + +# Allow only minor releases for other libraries +[[constraint]] + name = "github.com/go-kit/kit" + version = "^0.6.0" [[constraint]] name = "github.com/gorilla/websocket" - version = "=1.2.0" + version = "^1.2.0" [[constraint]] name = "github.com/rs/cors" - version = "1.6.0" + version = "^1.6.0" [[constraint]] name = "github.com/pkg/errors" - version = "=0.8.0" + version = "^0.8.0" [[constraint]] name = "github.com/spf13/cobra" - version = "=0.0.1" + version = "^0.0.1" [[constraint]] name = "github.com/spf13/viper" - version = "=1.0.0" + version = "^1.0.0" [[constraint]] name = "github.com/stretchr/testify" - version = "=1.2.1" - -[[constraint]] - name = "github.com/tendermint/go-amino" - version = "v0.14.1" + version = "^1.2.1" [[constraint]] name = "google.golang.org/grpc" - version = "=1.13.0" + version = "~1.13.0" [[constraint]] name = "github.com/fortytw2/leaktest" - version = "=1.2.0" + version = "^1.2.0" + +[[constraint]] + name = "github.com/prometheus/client_golang" + version = "^0.9.1" ################################### ## Some repos dont have releases. @@ -94,11 +97,6 @@ name = "github.com/tendermint/btcd" revision = "e5840949ff4fff0c56f9b6a541e22b63581ea9df" -# Haven't made a release since 2016. -[[constraint]] - name = "github.com/prometheus/client_golang" - revision = "ae27198cdd90bf12cd134ad79d1366a6cf49f632" - [[constraint]] name = "github.com/rcrowley/go-metrics" revision = "e2704e165165ec55d062f5919b4b29494e9fa790" From be8c2d501866fe75dc250182f4a92183be0ae595 Mon Sep 17 00:00:00 2001 From: Zach Date: Thu, 15 Nov 2018 14:41:40 -0500 Subject: [PATCH 081/267] use a github link (#2849) --- docs/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/README.md b/docs/README.md index c3293547..ae4b4731 100644 --- a/docs/README.md +++ b/docs/README.md @@ -21,7 +21,7 @@ For more details on using Tendermint, see the respective documentation for ## Contribute -To contribute to the documentation, see [this file](./DOCS_README.md) for details of the build process and +To contribute to the documentation, see [this file](https://github.com/tendermint/tendermint/blob/master/docs/DOCS_README.md) for details of the build process and considerations when making changes. ## Version From b646437ec761107b9bde415a6bf85b60cc01405b Mon Sep 17 00:00:00 2001 From: Alessio Treglia Date: Thu, 15 Nov 2018 20:33:04 +0000 Subject: [PATCH 082/267] Decouple StartHTTP{,AndTLS}Server from Listen() (#2791) * Decouple StartHTTP{,AndTLS}Server from Listen() This should help solve cosmos/cosmos-sdk#2715 * Fix small mistake * Update StartGRPCServer * s/rpc/rpcserver/ * Start grpccore.StartGRPCServer in a goroutine * Reinstate l.Close() * Fix rpc/lib/test/main.go * Update code comment * update changelog and comments * fix tm-monitor. more comments --- CHANGELOG_PENDING.md | 3 + lite/proxy/proxy.go | 47 ++++++------ node/node.go | 25 +++---- rpc/grpc/client_server.go | 27 ++----- rpc/lib/doc.go | 9 +-- rpc/lib/rpc_test.go | 19 ++--- rpc/lib/server/http_server.go | 112 +++++++++++++---------------- rpc/lib/server/http_server_test.go | 14 ++-- rpc/lib/test/main.go | 4 +- tools/tm-monitor/main.go | 9 +-- tools/tm-monitor/rpc.go | 8 ++- 11 files changed, 126 insertions(+), 151 deletions(-) diff --git a/CHANGELOG_PENDING.md b/CHANGELOG_PENDING.md index 6494867d..eea80c57 100644 --- a/CHANGELOG_PENDING.md +++ b/CHANGELOG_PENDING.md @@ -16,6 +16,9 @@ program](https://hackerone.com/tendermint). * Apps * Go API + - [rpc] \#2791 Functions that start HTTP servers are now blocking: + - Impacts: StartHTTPServer, StartHTTPAndTLSServer, and StartGRPCServer, + - These functions now take a `net.Listener` instead of an address * Blockchain Protocol diff --git a/lite/proxy/proxy.go b/lite/proxy/proxy.go index ffd9db1d..d7ffb27d 100644 --- a/lite/proxy/proxy.go +++ b/lite/proxy/proxy.go @@ -9,7 +9,7 @@ import ( rpcclient "github.com/tendermint/tendermint/rpc/client" "github.com/tendermint/tendermint/rpc/core" ctypes "github.com/tendermint/tendermint/rpc/core/types" - rpc "github.com/tendermint/tendermint/rpc/lib/server" + rpcserver "github.com/tendermint/tendermint/rpc/lib/server" ) const ( @@ -19,6 +19,7 @@ const ( // StartProxy will start the websocket manager on the client, // set up the rpc routes to proxy via the given client, // and start up an http/rpc server on the location given by bind (eg. :1234) +// NOTE: This function blocks - you may want to call it in a go-routine. func StartProxy(c rpcclient.Client, listenAddr string, logger log.Logger, maxOpenConnections int) error { err := c.Start() if err != nil { @@ -31,47 +32,49 @@ func StartProxy(c rpcclient.Client, listenAddr string, logger log.Logger, maxOpe // build the handler... mux := http.NewServeMux() - rpc.RegisterRPCFuncs(mux, r, cdc, logger) + rpcserver.RegisterRPCFuncs(mux, r, cdc, logger) - wm := rpc.NewWebsocketManager(r, cdc, rpc.EventSubscriber(c)) + wm := rpcserver.NewWebsocketManager(r, cdc, rpcserver.EventSubscriber(c)) wm.SetLogger(logger) core.SetLogger(logger) mux.HandleFunc(wsEndpoint, wm.WebsocketHandler) - _, err = rpc.StartHTTPServer(listenAddr, mux, logger, rpc.Config{MaxOpenConnections: maxOpenConnections}) - - return err + l, err := rpcserver.Listen(listenAddr, rpcserver.Config{MaxOpenConnections: maxOpenConnections}) + if err != nil { + return err + } + return rpcserver.StartHTTPServer(l, mux, logger) } // RPCRoutes just routes everything to the given client, as if it were // a tendermint fullnode. // // if we want security, the client must implement it as a secure client -func RPCRoutes(c rpcclient.Client) map[string]*rpc.RPCFunc { +func RPCRoutes(c rpcclient.Client) map[string]*rpcserver.RPCFunc { - return map[string]*rpc.RPCFunc{ + return map[string]*rpcserver.RPCFunc{ // Subscribe/unsubscribe are reserved for websocket events. // We can just use the core tendermint impl, which uses the // EventSwitch we registered in NewWebsocketManager above - "subscribe": rpc.NewWSRPCFunc(core.Subscribe, "query"), - "unsubscribe": rpc.NewWSRPCFunc(core.Unsubscribe, "query"), + "subscribe": rpcserver.NewWSRPCFunc(core.Subscribe, "query"), + "unsubscribe": rpcserver.NewWSRPCFunc(core.Unsubscribe, "query"), // info API - "status": rpc.NewRPCFunc(c.Status, ""), - "blockchain": rpc.NewRPCFunc(c.BlockchainInfo, "minHeight,maxHeight"), - "genesis": rpc.NewRPCFunc(c.Genesis, ""), - "block": rpc.NewRPCFunc(c.Block, "height"), - "commit": rpc.NewRPCFunc(c.Commit, "height"), - "tx": rpc.NewRPCFunc(c.Tx, "hash,prove"), - "validators": rpc.NewRPCFunc(c.Validators, ""), + "status": rpcserver.NewRPCFunc(c.Status, ""), + "blockchain": rpcserver.NewRPCFunc(c.BlockchainInfo, "minHeight,maxHeight"), + "genesis": rpcserver.NewRPCFunc(c.Genesis, ""), + "block": rpcserver.NewRPCFunc(c.Block, "height"), + "commit": rpcserver.NewRPCFunc(c.Commit, "height"), + "tx": rpcserver.NewRPCFunc(c.Tx, "hash,prove"), + "validators": rpcserver.NewRPCFunc(c.Validators, ""), // broadcast API - "broadcast_tx_commit": rpc.NewRPCFunc(c.BroadcastTxCommit, "tx"), - "broadcast_tx_sync": rpc.NewRPCFunc(c.BroadcastTxSync, "tx"), - "broadcast_tx_async": rpc.NewRPCFunc(c.BroadcastTxAsync, "tx"), + "broadcast_tx_commit": rpcserver.NewRPCFunc(c.BroadcastTxCommit, "tx"), + "broadcast_tx_sync": rpcserver.NewRPCFunc(c.BroadcastTxSync, "tx"), + "broadcast_tx_async": rpcserver.NewRPCFunc(c.BroadcastTxAsync, "tx"), // abci API - "abci_query": rpc.NewRPCFunc(c.ABCIQuery, "path,data,prove"), - "abci_info": rpc.NewRPCFunc(c.ABCIInfo, ""), + "abci_query": rpcserver.NewRPCFunc(c.ABCIQuery, "path,data,prove"), + "abci_info": rpcserver.NewRPCFunc(c.ABCIInfo, ""), } } diff --git a/node/node.go b/node/node.go index 796bbc2a..f1da1df0 100644 --- a/node/node.go +++ b/node/node.go @@ -653,6 +653,14 @@ func (n *Node) startRPC() ([]net.Listener, error) { mux.HandleFunc("/websocket", wm.WebsocketHandler) rpcserver.RegisterRPCFuncs(mux, rpccore.Routes, coreCodec, rpcLogger) + listener, err := rpcserver.Listen( + listenAddr, + rpcserver.Config{MaxOpenConnections: n.config.RPC.MaxOpenConnections}, + ) + if err != nil { + return nil, err + } + var rootHandler http.Handler = mux if n.config.RPC.IsCorsEnabled() { corsMiddleware := cors.New(cors.Options{ @@ -663,30 +671,23 @@ func (n *Node) startRPC() ([]net.Listener, error) { rootHandler = corsMiddleware.Handler(mux) } - listener, err := rpcserver.StartHTTPServer( - listenAddr, + go rpcserver.StartHTTPServer( + listener, rootHandler, rpcLogger, - rpcserver.Config{MaxOpenConnections: n.config.RPC.MaxOpenConnections}, ) - if err != nil { - return nil, err - } listeners[i] = listener } // we expose a simplified api over grpc for convenience to app devs grpcListenAddr := n.config.RPC.GRPCListenAddress if grpcListenAddr != "" { - listener, err := grpccore.StartGRPCServer( - grpcListenAddr, - grpccore.Config{ - MaxOpenConnections: n.config.RPC.GRPCMaxOpenConnections, - }, - ) + listener, err := rpcserver.Listen( + grpcListenAddr, rpcserver.Config{MaxOpenConnections: n.config.RPC.GRPCMaxOpenConnections}) if err != nil { return nil, err } + go grpccore.StartGRPCServer(listener) listeners = append(listeners, listener) } diff --git a/rpc/grpc/client_server.go b/rpc/grpc/client_server.go index c8898968..2bc89864 100644 --- a/rpc/grpc/client_server.go +++ b/rpc/grpc/client_server.go @@ -1,12 +1,9 @@ package core_grpc import ( - "fmt" "net" - "strings" "time" - "golang.org/x/net/netutil" "google.golang.org/grpc" cmn "github.com/tendermint/tendermint/libs/common" @@ -17,28 +14,12 @@ type Config struct { MaxOpenConnections int } -// StartGRPCServer starts a new gRPC BroadcastAPIServer, listening on -// protoAddr, in a goroutine. Returns a listener and an error, if it fails to -// parse an address. -func StartGRPCServer(protoAddr string, config Config) (net.Listener, error) { - parts := strings.SplitN(protoAddr, "://", 2) - if len(parts) != 2 { - return nil, fmt.Errorf("Invalid listen address for grpc server (did you forget a tcp:// prefix?) : %s", protoAddr) - } - proto, addr := parts[0], parts[1] - ln, err := net.Listen(proto, addr) - if err != nil { - return nil, err - } - if config.MaxOpenConnections > 0 { - ln = netutil.LimitListener(ln, config.MaxOpenConnections) - } - +// StartGRPCServer starts a new gRPC BroadcastAPIServer using the given net.Listener. +// NOTE: This function blocks - you may want to call it in a go-routine. +func StartGRPCServer(ln net.Listener) error { grpcServer := grpc.NewServer() RegisterBroadcastAPIServer(grpcServer, &broadcastAPI{}) - go grpcServer.Serve(ln) // nolint: errcheck - - return ln, nil + return grpcServer.Serve(ln) } // StartGRPCClient dials the gRPC server using protoAddr and returns a new diff --git a/rpc/lib/doc.go b/rpc/lib/doc.go index dbdb362d..aa9638bf 100644 --- a/rpc/lib/doc.go +++ b/rpc/lib/doc.go @@ -70,12 +70,9 @@ // wm := rpcserver.NewWebsocketManager(Routes) // mux.HandleFunc("/websocket", wm.WebsocketHandler) // logger := log.NewTMLogger(log.NewSyncWriter(os.Stdout)) -// go func() { -// _, err := rpcserver.StartHTTPServer("0.0.0.0:8008", mux, logger) -// if err != nil { -// panic(err) -// } -// }() +// listener, err := rpc.Listen("0.0.0.0:8080", rpcserver.Config{}) +// if err != nil { panic(err) } +// go rpcserver.StartHTTPServer(listener, mux, logger) // // Note that unix sockets are supported as well (eg. `/path/to/socket` instead of `0.0.0.0:8008`) // Now see all available endpoints by sending a GET request to `0.0.0.0:8008`. diff --git a/rpc/lib/rpc_test.go b/rpc/lib/rpc_test.go index 3d76db32..11b73ef1 100644 --- a/rpc/lib/rpc_test.go +++ b/rpc/lib/rpc_test.go @@ -121,12 +121,11 @@ func setup() { wm := server.NewWebsocketManager(Routes, RoutesCdc, server.ReadWait(5*time.Second), server.PingPeriod(1*time.Second)) wm.SetLogger(tcpLogger) mux.HandleFunc(websocketEndpoint, wm.WebsocketHandler) - go func() { - _, err := server.StartHTTPServer(tcpAddr, mux, tcpLogger, server.Config{}) - if err != nil { - panic(err) - } - }() + listener1, err := server.Listen(tcpAddr, server.Config{}) + if err != nil { + panic(err) + } + go server.StartHTTPServer(listener1, mux, tcpLogger) unixLogger := logger.With("socket", "unix") mux2 := http.NewServeMux() @@ -134,12 +133,8 @@ func setup() { wm = server.NewWebsocketManager(Routes, RoutesCdc) wm.SetLogger(unixLogger) mux2.HandleFunc(websocketEndpoint, wm.WebsocketHandler) - go func() { - _, err := server.StartHTTPServer(unixAddr, mux2, unixLogger, server.Config{}) - if err != nil { - panic(err) - } - }() + listener2, err := server.Listen(unixAddr, server.Config{}) + go server.StartHTTPServer(listener2, mux2, unixLogger) // wait for servers to start time.Sleep(time.Second * 2) diff --git a/rpc/lib/server/http_server.go b/rpc/lib/server/http_server.go index 8cacaeef..3e7632e2 100644 --- a/rpc/lib/server/http_server.go +++ b/rpc/lib/server/http_server.go @@ -29,90 +29,46 @@ const ( maxBodyBytes = int64(1000000) // 1MB ) -// StartHTTPServer starts an HTTP server on listenAddr with the given handler. +// StartHTTPServer takes a listener and starts an HTTP server with the given handler. // It wraps handler with RecoverAndLogHandler. -func StartHTTPServer( - listenAddr string, - handler http.Handler, - logger log.Logger, - config Config, -) (listener net.Listener, err error) { - var proto, addr string - parts := strings.SplitN(listenAddr, "://", 2) - if len(parts) != 2 { - return nil, errors.Errorf( - "Invalid listening address %s (use fully formed addresses, including the tcp:// or unix:// prefix)", - listenAddr, - ) - } - proto, addr = parts[0], parts[1] +// NOTE: This function blocks - you may want to call it in a go-routine. +func StartHTTPServer(listener net.Listener, handler http.Handler, logger log.Logger) error { + err := http.Serve( + listener, + RecoverAndLogHandler(maxBytesHandler{h: handler, n: maxBodyBytes}, logger), + ) + logger.Info("RPC HTTP server stopped", "err", err) - logger.Info(fmt.Sprintf("Starting RPC HTTP server on %s", listenAddr)) - listener, err = net.Listen(proto, addr) - if err != nil { - return nil, errors.Errorf("Failed to listen on %v: %v", listenAddr, err) - } - if config.MaxOpenConnections > 0 { - listener = netutil.LimitListener(listener, config.MaxOpenConnections) - } - - go func() { - err := http.Serve( - listener, - RecoverAndLogHandler(maxBytesHandler{h: handler, n: maxBodyBytes}, logger), - ) - logger.Info("RPC HTTP server stopped", "err", err) - }() - return listener, nil + return err } -// StartHTTPAndTLSServer starts an HTTPS server on listenAddr with the given -// handler. +// StartHTTPAndTLSServer takes a listener and starts an HTTPS server with the given handler. // It wraps handler with RecoverAndLogHandler. +// NOTE: This function blocks - you may want to call it in a go-routine. func StartHTTPAndTLSServer( - listenAddr string, + listener net.Listener, handler http.Handler, certFile, keyFile string, logger log.Logger, - config Config, -) (listener net.Listener, err error) { - var proto, addr string - parts := strings.SplitN(listenAddr, "://", 2) - if len(parts) != 2 { - return nil, errors.Errorf( - "Invalid listening address %s (use fully formed addresses, including the tcp:// or unix:// prefix)", - listenAddr, - ) - } - proto, addr = parts[0], parts[1] - +) error { logger.Info( fmt.Sprintf( "Starting RPC HTTPS server on %s (cert: %q, key: %q)", - listenAddr, + listener.Addr(), certFile, keyFile, ), ) - listener, err = net.Listen(proto, addr) - if err != nil { - return nil, errors.Errorf("Failed to listen on %v: %v", listenAddr, err) - } - if config.MaxOpenConnections > 0 { - listener = netutil.LimitListener(listener, config.MaxOpenConnections) - } - - err = http.ServeTLS( + if err := http.ServeTLS( listener, RecoverAndLogHandler(maxBytesHandler{h: handler, n: maxBodyBytes}, logger), certFile, keyFile, - ) - if err != nil { + ); err != nil { logger.Error("RPC HTTPS server stopped", "err", err) - return nil, err + return err } - return listener, nil + return nil } func WriteRPCResponseHTTPError( @@ -213,3 +169,35 @@ func (h maxBytesHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { r.Body = http.MaxBytesReader(w, r.Body, h.n) h.h.ServeHTTP(w, r) } + +// MustListen starts a new net.Listener on the given address. +// It panics in case of error. +func MustListen(addr string, config Config) net.Listener { + l, err := Listen(addr, config) + if err != nil { + panic(fmt.Errorf("Listen() failed: %v", err)) + } + return l +} + +// Listen starts a new net.Listener on the given address. +// It returns an error if the address is invalid or the call to Listen() fails. +func Listen(addr string, config Config) (listener net.Listener, err error) { + parts := strings.SplitN(addr, "://", 2) + if len(parts) != 2 { + return nil, errors.Errorf( + "Invalid listening address %s (use fully formed addresses, including the tcp:// or unix:// prefix)", + addr, + ) + } + proto, addr := parts[0], parts[1] + listener, err = net.Listen(proto, addr) + if err != nil { + return nil, errors.Errorf("Failed to listen on %v: %v", addr, err) + } + if config.MaxOpenConnections > 0 { + listener = netutil.LimitListener(listener, config.MaxOpenConnections) + } + + return listener, nil +} diff --git a/rpc/lib/server/http_server_test.go b/rpc/lib/server/http_server_test.go index 73ebc2e7..6b852afa 100644 --- a/rpc/lib/server/http_server_test.go +++ b/rpc/lib/server/http_server_test.go @@ -30,11 +30,10 @@ func TestMaxOpenConnections(t *testing.T) { time.Sleep(10 * time.Millisecond) fmt.Fprint(w, "some body") }) - l, err := StartHTTPServer("tcp://127.0.0.1:0", mux, log.TestingLogger(), Config{MaxOpenConnections: max}) - if err != nil { - t.Fatal(err) - } + l, err := Listen("tcp://127.0.0.1:0", Config{MaxOpenConnections: max}) + require.NoError(t, err) defer l.Close() + go StartHTTPServer(l, mux, log.TestingLogger()) // Make N GET calls to the server. attempts := max * 2 @@ -67,11 +66,14 @@ func TestMaxOpenConnections(t *testing.T) { func TestStartHTTPAndTLSServer(t *testing.T) { // set up fixtures listenerAddr := "tcp://0.0.0.0:0" + listener, err := Listen(listenerAddr, Config{MaxOpenConnections: 1}) + require.NoError(t, err) mux := http.NewServeMux() mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {}) // test failure - gotListener, err := StartHTTPAndTLSServer(listenerAddr, mux, "", "", log.TestingLogger(), Config{MaxOpenConnections: 1}) - require.Nil(t, gotListener) + err = StartHTTPAndTLSServer(listener, mux, "", "", log.TestingLogger()) require.IsType(t, (*os.PathError)(nil), err) + + // TODO: test that starting the server can actually work } diff --git a/rpc/lib/test/main.go b/rpc/lib/test/main.go index 544284b9..0a9684d7 100644 --- a/rpc/lib/test/main.go +++ b/rpc/lib/test/main.go @@ -28,11 +28,11 @@ func main() { cdc := amino.NewCodec() logger := log.NewTMLogger(log.NewSyncWriter(os.Stdout)) rpcserver.RegisterRPCFuncs(mux, routes, cdc, logger) - _, err := rpcserver.StartHTTPServer("0.0.0.0:8008", mux, logger, rpcserver.Config{}) + listener, err := rpcserver.Listen("0.0.0.0:8008", rpcserver.Config{}) if err != nil { cmn.Exit(err.Error()) } - + go rpcserver.StartHTTPServer(listener, mux, logger) // Wait forever cmn.TrapSignal(func() { }) diff --git a/tools/tm-monitor/main.go b/tools/tm-monitor/main.go index 32897b97..6e4aea5f 100644 --- a/tools/tm-monitor/main.go +++ b/tools/tm-monitor/main.go @@ -48,13 +48,13 @@ Examples: logger = log.NewTMLogger(log.NewSyncWriter(os.Stdout)) } - m := startMonitor(flag.Arg(0)) + monitor := startMonitor(flag.Arg(0)) - startRPC(listenAddr, m, logger) + listener := startRPC(listenAddr, monitor, logger) var ton *Ton if !noton { - ton = NewTon(m) + ton = NewTon(monitor) ton.Start() } @@ -62,7 +62,8 @@ Examples: if !noton { ton.Stop() } - m.Stop() + monitor.Stop() + listener.Close() }) } diff --git a/tools/tm-monitor/rpc.go b/tools/tm-monitor/rpc.go index ab62e046..1a08a9ec 100644 --- a/tools/tm-monitor/rpc.go +++ b/tools/tm-monitor/rpc.go @@ -2,6 +2,7 @@ package main import ( "errors" + "net" "net/http" "github.com/tendermint/tendermint/libs/log" @@ -9,16 +10,19 @@ import ( monitor "github.com/tendermint/tendermint/tools/tm-monitor/monitor" ) -func startRPC(listenAddr string, m *monitor.Monitor, logger log.Logger) { +func startRPC(listenAddr string, m *monitor.Monitor, logger log.Logger) net.Listener { routes := routes(m) mux := http.NewServeMux() wm := rpc.NewWebsocketManager(routes, nil) mux.HandleFunc("/websocket", wm.WebsocketHandler) rpc.RegisterRPCFuncs(mux, routes, cdc, logger) - if _, err := rpc.StartHTTPServer(listenAddr, mux, logger, rpc.Config{}); err != nil { + listener, err := rpc.Listen(listenAddr, rpc.Config{}) + if err != nil { panic(err) } + go rpc.StartHTTPServer(listener, mux, logger) + return listener } func routes(m *monitor.Monitor) map[string]*rpc.RPCFunc { From 06225e332eaeb63eceb3aacb11dccf48b0cbf42f Mon Sep 17 00:00:00 2001 From: Anton Kaliaev Date: Fri, 16 Nov 2018 03:05:06 +0400 Subject: [PATCH 083/267] Config option for JSON output formatter (#2843) * Introduce a structured logging option * rename StructuredLog to LogFormat * add changelog entry * move log_format under log_level --- CHANGELOG_PENDING.md | 2 ++ cmd/tendermint/commands/root.go | 3 +++ config/config.go | 23 +++++++++++++++++++++++ config/toml.go | 3 +++ docs/tendermint-core/configuration.md | 3 +++ 5 files changed, 34 insertions(+) diff --git a/CHANGELOG_PENDING.md b/CHANGELOG_PENDING.md index eea80c57..63b27711 100644 --- a/CHANGELOG_PENDING.md +++ b/CHANGELOG_PENDING.md @@ -26,6 +26,8 @@ program](https://hackerone.com/tendermint). ### FEATURES: +- [log] new `log_format` config option, which can be set to 'plain' for colored + text or 'json' for JSON output ### IMPROVEMENTS: diff --git a/cmd/tendermint/commands/root.go b/cmd/tendermint/commands/root.go index 89ffbe74..6d79f75c 100644 --- a/cmd/tendermint/commands/root.go +++ b/cmd/tendermint/commands/root.go @@ -54,6 +54,9 @@ var RootCmd = &cobra.Command{ if err != nil { return err } + if config.LogFormat == cfg.LogFormatJSON { + logger = log.NewTMJSONLogger(log.NewSyncWriter(os.Stdout)) + } logger, err = tmflags.ParseLogLevel(config.LogLevel, logger, cfg.DefaultLogLevel()) if err != nil { return err diff --git a/config/config.go b/config/config.go index ea6582c0..23b03399 100644 --- a/config/config.go +++ b/config/config.go @@ -14,6 +14,11 @@ const ( FuzzModeDrop = iota // FuzzModeDelay is a mode in which we randomly sleep FuzzModeDelay + + // LogFormatPlain is a format for colored text + LogFormatPlain = "plain" + // LogFormatJSON is a format for json output + LogFormatJSON = "json" ) // NOTE: Most of the structs & relevant comments + the @@ -94,6 +99,9 @@ func (cfg *Config) SetRoot(root string) *Config { // ValidateBasic performs basic validation (checking param bounds, etc.) and // returns an error if any check fails. func (cfg *Config) ValidateBasic() error { + if err := cfg.BaseConfig.ValidateBasic(); err != nil { + return err + } if err := cfg.RPC.ValidateBasic(); err != nil { return errors.Wrap(err, "Error in [rpc] section") } @@ -145,6 +153,9 @@ type BaseConfig struct { // Output level for logging LogLevel string `mapstructure:"log_level"` + // Output format: 'plain' (colored text) or 'json' + LogFormat string `mapstructure:"log_format"` + // Path to the JSON file containing the initial validator set and other meta data Genesis string `mapstructure:"genesis_file"` @@ -179,6 +190,7 @@ func DefaultBaseConfig() BaseConfig { ProxyApp: "tcp://127.0.0.1:26658", ABCI: "socket", LogLevel: DefaultPackageLogLevels(), + LogFormat: LogFormatPlain, ProfListenAddress: "", FastSync: true, FilterPeers: false, @@ -221,6 +233,17 @@ func (cfg BaseConfig) DBDir() string { return rootify(cfg.DBPath, cfg.RootDir) } +// ValidateBasic performs basic validation (checking param bounds, etc.) and +// returns an error if any check fails. +func (cfg BaseConfig) ValidateBasic() error { + switch cfg.LogFormat { + case LogFormatPlain, LogFormatJSON: + default: + return errors.New("unknown log_format (must be 'plain' or 'json')") + } + return nil +} + // DefaultLogLevel returns a default log level of "error" func DefaultLogLevel() string { return "error" diff --git a/config/toml.go b/config/toml.go index 89be3783..6f0578e4 100644 --- a/config/toml.go +++ b/config/toml.go @@ -86,6 +86,9 @@ db_dir = "{{ js .BaseConfig.DBPath }}" # Output level for logging, including package level options log_level = "{{ .BaseConfig.LogLevel }}" +# Output format: 'plain' (colored text) or 'json' +log_format = "{{ .BaseConfig.LogFormat }}" + ##### additional base config options ##### # Path to the JSON file containing the initial validator set and other meta data diff --git a/docs/tendermint-core/configuration.md b/docs/tendermint-core/configuration.md index 7052ca50..13894a30 100644 --- a/docs/tendermint-core/configuration.md +++ b/docs/tendermint-core/configuration.md @@ -39,6 +39,9 @@ db_dir = "data" # Output level for logging log_level = "state:info,*:error" +# Output format: 'plain' (colored text) or 'json' +log_format = "plain" + ##### additional base config options ##### # The ID of the chain to join (should be signed with every transaction and vote) From c033975a533a2c3055b304b76bb312cd0f07ce27 Mon Sep 17 00:00:00 2001 From: Zach Date: Thu, 15 Nov 2018 18:08:24 -0500 Subject: [PATCH 084/267] docs ADR (#2828) * wip * use same ADR template as SDK * finish docs adr * lil fixes --- docs/architecture/adr-035-documentation.md | 40 ++++++++++++++++++++++ docs/architecture/adr-template.md | 23 +++++++++++-- 2 files changed, 60 insertions(+), 3 deletions(-) create mode 100644 docs/architecture/adr-035-documentation.md diff --git a/docs/architecture/adr-035-documentation.md b/docs/architecture/adr-035-documentation.md new file mode 100644 index 00000000..92cb0791 --- /dev/null +++ b/docs/architecture/adr-035-documentation.md @@ -0,0 +1,40 @@ +# ADR 035: Documentation + +Author: @zramsay (Zach Ramsay) + +## Changelog + +### November 2nd 2018 + +- initial write-up + +## Context + +The Tendermint documentation has undergone several changes until settling on the current model. Originally, the documentation was hosted on the website and had to be updated asynchronously from the code. Along with the other repositories requiring documentation, the whole stack moved to using Read The Docs to automatically generate, publish, and host the documentation. This, however, was insufficient; the RTD site had advertisement, it wasn't easily accessible to devs, didn't collect metrics, was another set of external links, etc. + +## Decision + +For two reasons, the decision was made to use VuePress: + +1) ability to get metrics (implemented on both Tendermint and SDK) +2) host the documentation on the website as a `/docs` endpoint. + +This is done while maintaining synchrony between the docs and code, i.e., the website is built whenever the docs are updated. + +## Status + +The two points above have been implemented; the `config.js` has a Google Analytics identifier and the documentation workflow has been up and running largely without problems for several months. Details about the documentation build & workflow can be found [here](../DOCS_README.md) + +## Consequences + +Because of the organizational seperation between Tendermint & Cosmos, there is a challenge of "what goes where" for certain aspects of documentation. + +### Positive + +This architecture is largely positive relative to prior docs arrangements. + +### Negative + +A significant portion of the docs automation / build process is in private repos with limited access/visibility to devs. However, these tasks are handled by the SRE team. + +### Neutral diff --git a/docs/architecture/adr-template.md b/docs/architecture/adr-template.md index 4879afc4..28a5ecfb 100644 --- a/docs/architecture/adr-template.md +++ b/docs/architecture/adr-template.md @@ -1,19 +1,36 @@ -# ADR 000: Template for an ADR - -Author: +# ADR {ADR-NUMBER}: {TITLE} ## Changelog +* {date}: {changelog} ## Context +> This section contains all the context one needs to understand the current state, and why there is a problem. It should be as succinct as possible and introduce the high level idea behind the solution. ## Decision +> This section explains all of the details of the proposed solution, including implementation details. +It should also describe affects / corollary items that may need to be changed as a part of this. +If the proposed change will be large, please also indicate a way to do the change to maximize ease of review. +(e.g. the optimal split of things to do between separate PR's) + ## Status +> A decision may be "proposed" if it hasn't been agreed upon yet, or "accepted" once it is agreed upon. If a later ADR changes or reverses a decision, it may be marked as "deprecated" or "superseded" with a reference to its replacement. + +{Deprecated|Proposed|Accepted} + ## Consequences +> This section describes the consequences, after applying the decision. All consequences should be summarized here, not just the "positive" ones. + ### Positive ### Negative ### Neutral + +## References + +> Are there any relevant PR comments, issues that led up to this, or articles referrenced for why we made the given design choice? If so link them here! + +* {reference link} From a676c7167854b45ce8a23ccc44292cb00d1509cd Mon Sep 17 00:00:00 2001 From: kevlubkcm <36485490+kevlubkcm@users.noreply.github.com> Date: Thu, 15 Nov 2018 18:40:42 -0500 Subject: [PATCH 085/267] [R4R] Add proposer to NewRound event and proposal info to CompleteProposal event (#2767) * add proposer info to EventCompleteProposal * create separate EventData structure for CompleteProposal * cant us rs.Proposal to get BlockID because it is not guaranteed to be set yet * copying RoundState isnt helping us here * add Step back to make compatible with original RoundState event. update changelog * add NewRound event * fix test * remove unneeded RoundState * put height round step into a struct * pull out ValidatorInfo struct. add ensureProposal assert * remove height-round-state sub-struct refactor * minor fixes from review --- CHANGELOG_PENDING.md | 2 ++ consensus/common_test.go | 64 +++++++++++++++++++++++++++++++--- consensus/mempool_test.go | 6 ++-- consensus/state.go | 6 ++-- consensus/state_test.go | 7 ++-- consensus/types/round_state.go | 36 +++++++++++++++++-- types/event_bus.go | 4 +-- types/event_bus_test.go | 4 +-- types/events.go | 23 ++++++++++++ 9 files changed, 132 insertions(+), 20 deletions(-) diff --git a/CHANGELOG_PENDING.md b/CHANGELOG_PENDING.md index 63b27711..19ee4e7d 100644 --- a/CHANGELOG_PENDING.md +++ b/CHANGELOG_PENDING.md @@ -29,6 +29,8 @@ program](https://hackerone.com/tendermint). - [log] new `log_format` config option, which can be set to 'plain' for colored text or 'json' for JSON output +- [types] \#2767 New event types EventDataNewRound (with ProposerInfo) and EventDataCompleteProposal (with BlockID). (@kevlubkcm) + ### IMPROVEMENTS: ### BUG FIXES: diff --git a/consensus/common_test.go b/consensus/common_test.go index 4f48f442..46be5cbd 100644 --- a/consensus/common_test.go +++ b/consensus/common_test.go @@ -405,8 +405,24 @@ func ensureNewVote(voteCh <-chan interface{}, height int64, round int) { } func ensureNewRound(roundCh <-chan interface{}, height int64, round int) { - ensureNewEvent(roundCh, height, round, ensureTimeout, - "Timeout expired while waiting for NewRound event") + select { + case <-time.After(ensureTimeout): + panic("Timeout expired while waiting for NewRound event") + case ev := <-roundCh: + rs, ok := ev.(types.EventDataNewRound) + if !ok { + panic( + fmt.Sprintf( + "expected a EventDataNewRound, got %v.Wrong subscription channel?", + reflect.TypeOf(rs))) + } + if rs.Height != height { + panic(fmt.Sprintf("expected height %v, got %v", height, rs.Height)) + } + if rs.Round != round { + panic(fmt.Sprintf("expected round %v, got %v", round, rs.Round)) + } + } } func ensureNewTimeout(timeoutCh <-chan interface{}, height int64, round int, timeout int64) { @@ -416,8 +432,24 @@ func ensureNewTimeout(timeoutCh <-chan interface{}, height int64, round int, tim } func ensureNewProposal(proposalCh <-chan interface{}, height int64, round int) { - ensureNewEvent(proposalCh, height, round, ensureTimeout, - "Timeout expired while waiting for NewProposal event") + select { + case <-time.After(ensureTimeout): + panic("Timeout expired while waiting for NewProposal event") + case ev := <-proposalCh: + rs, ok := ev.(types.EventDataCompleteProposal) + if !ok { + panic( + fmt.Sprintf( + "expected a EventDataCompleteProposal, got %v.Wrong subscription channel?", + reflect.TypeOf(rs))) + } + if rs.Height != height { + panic(fmt.Sprintf("expected height %v, got %v", height, rs.Height)) + } + if rs.Round != round { + panic(fmt.Sprintf("expected round %v, got %v", round, rs.Round)) + } + } } func ensureNewValidBlock(validBlockCh <-chan interface{}, height int64, round int) { @@ -492,6 +524,30 @@ func ensureVote(voteCh <-chan interface{}, height int64, round int, } } +func ensureProposal(proposalCh <-chan interface{}, height int64, round int, propId types.BlockID) { + select { + case <-time.After(ensureTimeout): + panic("Timeout expired while waiting for NewProposal event") + case ev := <-proposalCh: + rs, ok := ev.(types.EventDataCompleteProposal) + if !ok { + panic( + fmt.Sprintf( + "expected a EventDataCompleteProposal, got %v.Wrong subscription channel?", + reflect.TypeOf(rs))) + } + if rs.Height != height { + panic(fmt.Sprintf("expected height %v, got %v", height, rs.Height)) + } + if rs.Round != round { + panic(fmt.Sprintf("expected round %v, got %v", round, rs.Round)) + } + if !rs.BlockID.Equals(propId) { + panic("Proposed block does not match expected block") + } + } +} + func ensurePrecommit(voteCh <-chan interface{}, height int64, round int) { ensureVote(voteCh, height, round, types.PrecommitType) } diff --git a/consensus/mempool_test.go b/consensus/mempool_test.go index 3dc1cd5f..6d36d1e7 100644 --- a/consensus/mempool_test.go +++ b/consensus/mempool_test.go @@ -71,18 +71,18 @@ func TestMempoolProgressInHigherRound(t *testing.T) { } startTestRound(cs, height, round) - ensureNewRoundStep(newRoundCh, height, round) // first round at first height + ensureNewRound(newRoundCh, height, round) // first round at first height ensureNewEventOnChannel(newBlockCh) // first block gets committed height = height + 1 // moving to the next height round = 0 - ensureNewRoundStep(newRoundCh, height, round) // first round at next height + ensureNewRound(newRoundCh, height, round) // first round at next height deliverTxsRange(cs, 0, 1) // we deliver txs, but dont set a proposal so we get the next round ensureNewTimeout(timeoutCh, height, round, cs.config.TimeoutPropose.Nanoseconds()) round = round + 1 // moving to the next round - ensureNewRoundStep(newRoundCh, height, round) // wait for the next round + ensureNewRound(newRoundCh, height, round) // wait for the next round ensureNewEventOnChannel(newBlockCh) // now we can commit the block } diff --git a/consensus/state.go b/consensus/state.go index e8603011..110a0e9f 100644 --- a/consensus/state.go +++ b/consensus/state.go @@ -772,7 +772,7 @@ func (cs *ConsensusState) enterNewRound(height int64, round int) { cs.Votes.SetRound(round + 1) // also track next round (round+1) to allow round-skipping cs.triggeredTimeoutPrecommit = false - cs.eventBus.PublishEventNewRound(cs.RoundStateEvent()) + cs.eventBus.PublishEventNewRound(cs.NewRoundEvent()) cs.metrics.Rounds.Set(float64(round)) // Wait for txs to be available in the mempool @@ -1404,7 +1404,7 @@ func (cs *ConsensusState) defaultSetProposal(proposal *types.Proposal) error { return nil } - // Verify POLRound, which must be -1 or in range [0, proposal.Round). + // Verify POLRound, which must be -1 or in range [0, proposal.Round). if proposal.POLRound < -1 || (proposal.POLRound >= 0 && proposal.POLRound >= proposal.Round) { return ErrInvalidProposalPOLRound @@ -1462,7 +1462,7 @@ func (cs *ConsensusState) addProposalBlockPart(msg *BlockPartMessage, peerID p2p } // NOTE: it's possible to receive complete proposal blocks for future rounds without having the proposal cs.Logger.Info("Received complete proposal block", "height", cs.ProposalBlock.Height, "hash", cs.ProposalBlock.Hash()) - cs.eventBus.PublishEventCompleteProposal(cs.RoundStateEvent()) + cs.eventBus.PublishEventCompleteProposal(cs.CompleteProposalEvent()) // Update Valid* if we can. prevotes := cs.Votes.Prevotes(cs.Round) diff --git a/consensus/state_test.go b/consensus/state_test.go index 9bf4fada..ddab6404 100644 --- a/consensus/state_test.go +++ b/consensus/state_test.go @@ -197,9 +197,8 @@ func TestStateBadProposal(t *testing.T) { stateHash[0] = byte((stateHash[0] + 1) % 255) propBlock.AppHash = stateHash propBlockParts := propBlock.MakePartSet(partSize) - proposal := types.NewProposal( - vs2.Height, round, -1, - types.BlockID{propBlock.Hash(), propBlockParts.Header()}) + blockID := types.BlockID{propBlock.Hash(), propBlockParts.Header()} + proposal := types.NewProposal(vs2.Height, round, -1, blockID) if err := vs2.SignProposal(config.ChainID(), proposal); err != nil { t.Fatal("failed to sign bad proposal", err) } @@ -213,7 +212,7 @@ func TestStateBadProposal(t *testing.T) { startTestRound(cs1, height, round) // wait for proposal - ensureNewProposal(proposalCh, height, round) + ensureProposal(proposalCh, height, round, blockID) // wait for prevote ensurePrevote(voteCh, height, round) diff --git a/consensus/types/round_state.go b/consensus/types/round_state.go index ef423611..6359a655 100644 --- a/consensus/types/round_state.go +++ b/consensus/types/round_state.go @@ -112,18 +112,50 @@ func (rs *RoundState) RoundStateSimple() RoundStateSimple { } } +// NewRoundEvent returns the RoundState with proposer information as an event. +func (rs *RoundState) NewRoundEvent() types.EventDataNewRound { + addr := rs.Validators.GetProposer().Address + idx, _ := rs.Validators.GetByAddress(addr) + + return types.EventDataNewRound{ + Height: rs.Height, + Round: rs.Round, + Step: rs.Step.String(), + Proposer: types.ValidatorInfo{ + Address: addr, + Index: idx, + }, + } +} + +// CompleteProposalEvent returns information about a proposed block as an event. +func (rs *RoundState) CompleteProposalEvent() types.EventDataCompleteProposal { + // We must construct BlockID from ProposalBlock and ProposalBlockParts + // cs.Proposal is not guaranteed to be set when this function is called + blockId := types.BlockID{ + Hash: rs.ProposalBlock.Hash(), + PartsHeader: rs.ProposalBlockParts.Header(), + } + + return types.EventDataCompleteProposal{ + Height: rs.Height, + Round: rs.Round, + Step: rs.Step.String(), + BlockID: blockId, + } +} + // RoundStateEvent returns the H/R/S of the RoundState as an event. func (rs *RoundState) RoundStateEvent() types.EventDataRoundState { // copy the RoundState. // TODO: if we want to avoid this, we may need synchronous events after all rsCopy := *rs - edrs := types.EventDataRoundState{ + return types.EventDataRoundState{ Height: rs.Height, Round: rs.Round, Step: rs.Step.String(), RoundState: &rsCopy, } - return edrs } // String returns a string diff --git a/types/event_bus.go b/types/event_bus.go index 65206e93..fbe5ac47 100644 --- a/types/event_bus.go +++ b/types/event_bus.go @@ -136,11 +136,11 @@ func (b *EventBus) PublishEventTimeoutWait(data EventDataRoundState) error { return b.Publish(EventTimeoutWait, data) } -func (b *EventBus) PublishEventNewRound(data EventDataRoundState) error { +func (b *EventBus) PublishEventNewRound(data EventDataNewRound) error { return b.Publish(EventNewRound, data) } -func (b *EventBus) PublishEventCompleteProposal(data EventDataRoundState) error { +func (b *EventBus) PublishEventCompleteProposal(data EventDataCompleteProposal) error { return b.Publish(EventCompleteProposal, data) } diff --git a/types/event_bus_test.go b/types/event_bus_test.go index f0e825d5..4056dacd 100644 --- a/types/event_bus_test.go +++ b/types/event_bus_test.go @@ -96,9 +96,9 @@ func TestEventBusPublish(t *testing.T) { require.NoError(t, err) err = eventBus.PublishEventTimeoutWait(EventDataRoundState{}) require.NoError(t, err) - err = eventBus.PublishEventNewRound(EventDataRoundState{}) + err = eventBus.PublishEventNewRound(EventDataNewRound{}) require.NoError(t, err) - err = eventBus.PublishEventCompleteProposal(EventDataRoundState{}) + err = eventBus.PublishEventCompleteProposal(EventDataCompleteProposal{}) require.NoError(t, err) err = eventBus.PublishEventPolka(EventDataRoundState{}) require.NoError(t, err) diff --git a/types/events.go b/types/events.go index 33aa712e..2f9dc76e 100644 --- a/types/events.go +++ b/types/events.go @@ -43,6 +43,8 @@ func RegisterEventDatas(cdc *amino.Codec) { cdc.RegisterConcrete(EventDataNewBlockHeader{}, "tendermint/event/NewBlockHeader", nil) cdc.RegisterConcrete(EventDataTx{}, "tendermint/event/Tx", nil) cdc.RegisterConcrete(EventDataRoundState{}, "tendermint/event/RoundState", nil) + cdc.RegisterConcrete(EventDataNewRound{}, "tendermint/event/NewRound", nil) + cdc.RegisterConcrete(EventDataCompleteProposal{}, "tendermint/event/CompleteProposal", nil) cdc.RegisterConcrete(EventDataVote{}, "tendermint/event/Vote", nil) cdc.RegisterConcrete(EventDataProposalHeartbeat{}, "tendermint/event/ProposalHeartbeat", nil) cdc.RegisterConcrete(EventDataValidatorSetUpdates{}, "tendermint/event/ValidatorSetUpdates", nil) @@ -80,6 +82,27 @@ type EventDataRoundState struct { RoundState interface{} `json:"-"` } +type ValidatorInfo struct { + Address Address `json:"address"` + Index int `json:"index"` +} + +type EventDataNewRound struct { + Height int64 `json:"height"` + Round int `json:"round"` + Step string `json:"step"` + + Proposer ValidatorInfo `json:"proposer"` +} + +type EventDataCompleteProposal struct { + Height int64 `json:"height"` + Round int `json:"round"` + Step string `json:"step"` + + BlockID BlockID `json:"block_id"` +} + type EventDataVote struct { Vote *Vote } From d8ab8509de625c222f23d4f54ec817f3ddfcb83e Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Fri, 16 Nov 2018 02:37:58 -0500 Subject: [PATCH 086/267] p2p: log 'Send failed' on Debug (#2857) --- CHANGELOG_PENDING.md | 5 ++++- p2p/conn/connection.go | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/CHANGELOG_PENDING.md b/CHANGELOG_PENDING.md index 19ee4e7d..c57f1993 100644 --- a/CHANGELOG_PENDING.md +++ b/CHANGELOG_PENDING.md @@ -26,11 +26,14 @@ program](https://hackerone.com/tendermint). ### FEATURES: -- [log] new `log_format` config option, which can be set to 'plain' for colored + +- [log] \#2843 New `log_format` config option, which can be set to 'plain' for colored text or 'json' for JSON output - [types] \#2767 New event types EventDataNewRound (with ProposerInfo) and EventDataCompleteProposal (with BlockID). (@kevlubkcm) ### IMPROVEMENTS: +- [p2p] \#2857 "Send failed" is logged at debug level instead of error. + ### BUG FIXES: diff --git a/p2p/conn/connection.go b/p2p/conn/connection.go index 80fc53dd..89282b00 100644 --- a/p2p/conn/connection.go +++ b/p2p/conn/connection.go @@ -269,7 +269,7 @@ func (c *MConnection) Send(chID byte, msgBytes []byte) bool { default: } } else { - c.Logger.Error("Send failed", "channel", chID, "conn", c, "msgBytes", fmt.Sprintf("%X", msgBytes)) + c.Logger.Debug("Send failed", "channel", chID, "conn", c, "msgBytes", fmt.Sprintf("%X", msgBytes)) } return success } From e6a0d098e83c9c40742f80e914f80cdd0b134d13 Mon Sep 17 00:00:00 2001 From: Anton Kaliaev Date: Fri, 16 Nov 2018 21:58:30 +0400 Subject: [PATCH 087/267] small fixes to spec & http_server & Vagrantfile (#2859) * Vagrantfile: install dev_tools Follow-up on https://github.com/tendermint/tendermint/pull/2824 * update consensus params spec * fix test name * rpc_test: panic if failed to start listener also - remove http_server#MustListen - align StartHTTPServer and StartHTTPAndTLSServer functions * dep: allow minor releases for grpc --- Gopkg.toml | 2 +- Vagrantfile | 2 +- docs/spec/blockchain/state.md | 43 +++++++++++++---------------------- rpc/lib/rpc_test.go | 3 +++ rpc/lib/server/http_server.go | 32 ++++++-------------------- types/part_set_test.go | 4 +--- 6 files changed, 29 insertions(+), 57 deletions(-) diff --git a/Gopkg.toml b/Gopkg.toml index cc5021c9..c5e625e9 100644 --- a/Gopkg.toml +++ b/Gopkg.toml @@ -65,7 +65,7 @@ [[constraint]] name = "google.golang.org/grpc" - version = "~1.13.0" + version = "^1.13.0" [[constraint]] name = "github.com/fortytw2/leaktest" diff --git a/Vagrantfile b/Vagrantfile index 320f3b1c..f058d78e 100644 --- a/Vagrantfile +++ b/Vagrantfile @@ -53,6 +53,6 @@ Vagrant.configure("2") do |config| # get all deps and tools, ready to install/test su - vagrant -c 'source /home/vagrant/.bash_profile' - su - vagrant -c 'cd /home/vagrant/go/src/github.com/tendermint/tendermint && make get_tools && make get_vendor_deps' + su - vagrant -c 'cd /home/vagrant/go/src/github.com/tendermint/tendermint && make get_tools && make get_dev_tools && make get_vendor_deps' SHELL end diff --git a/docs/spec/blockchain/state.md b/docs/spec/blockchain/state.md index 502f9d69..0a07890f 100644 --- a/docs/spec/blockchain/state.md +++ b/docs/spec/blockchain/state.md @@ -79,31 +79,25 @@ func TotalVotingPower(vals []Validators) int64{ ConsensusParams define various limits for blockchain data structures. Like validator sets, they are set during genesis and can be updated by the application through ABCI. -``` +```go type ConsensusParams struct { BlockSize - TxSize - BlockGossip - EvidenceParams + Evidence + Validator } type BlockSize struct { - MaxBytes int + MaxBytes int64 MaxGas int64 } -type TxSize struct { - MaxBytes int - MaxGas int64 -} - -type BlockGossip struct { - BlockPartSizeBytes int -} - -type EvidenceParams struct { +type Evidence struct { MaxAge int64 } + +type Validator struct { + PubKeyTypes []string +} ``` #### BlockSize @@ -115,20 +109,15 @@ otherwise. Blocks should additionally be limited by the amount of "gas" consumed by the transactions in the block, though this is not yet implemented. -#### TxSize - -These parameters are not yet enforced and may disappear. See [issue -#2347](https://github.com/tendermint/tendermint/issues/2347). - -#### BlockGossip - -When gossipping blocks in the consensus, they are first split into parts. The -size of each part is `ConsensusParams.BlockGossip.BlockPartSizeBytes`. - -#### EvidenceParams +#### Evidence For evidence in a block to be valid, it must satisfy: ``` -block.Header.Height - evidence.Height < ConsensusParams.EvidenceParams.MaxAge +block.Header.Height - evidence.Height < ConsensusParams.Evidence.MaxAge ``` + +#### Validator + +Validators from genesis file and `ResponseEndBlock` must have pubkeys of type ∈ +`ConsensusParams.Validator.PubKeyTypes`. diff --git a/rpc/lib/rpc_test.go b/rpc/lib/rpc_test.go index 11b73ef1..794ab462 100644 --- a/rpc/lib/rpc_test.go +++ b/rpc/lib/rpc_test.go @@ -134,6 +134,9 @@ func setup() { wm.SetLogger(unixLogger) mux2.HandleFunc(websocketEndpoint, wm.WebsocketHandler) listener2, err := server.Listen(unixAddr, server.Config{}) + if err != nil { + panic(err) + } go server.StartHTTPServer(listener2, mux2, unixLogger) // wait for servers to start diff --git a/rpc/lib/server/http_server.go b/rpc/lib/server/http_server.go index 3e7632e2..a5f8692f 100644 --- a/rpc/lib/server/http_server.go +++ b/rpc/lib/server/http_server.go @@ -33,12 +33,12 @@ const ( // It wraps handler with RecoverAndLogHandler. // NOTE: This function blocks - you may want to call it in a go-routine. func StartHTTPServer(listener net.Listener, handler http.Handler, logger log.Logger) error { + logger.Info(fmt.Sprintf("Starting RPC HTTP server on %s", listener.Addr())) err := http.Serve( listener, RecoverAndLogHandler(maxBytesHandler{h: handler, n: maxBodyBytes}, logger), ) logger.Info("RPC HTTP server stopped", "err", err) - return err } @@ -51,24 +51,16 @@ func StartHTTPAndTLSServer( certFile, keyFile string, logger log.Logger, ) error { - logger.Info( - fmt.Sprintf( - "Starting RPC HTTPS server on %s (cert: %q, key: %q)", - listener.Addr(), - certFile, - keyFile, - ), - ) - if err := http.ServeTLS( + logger.Info(fmt.Sprintf("Starting RPC HTTPS server on %s (cert: %q, key: %q)", + listener.Addr(), certFile, keyFile)) + err := http.ServeTLS( listener, RecoverAndLogHandler(maxBytesHandler{h: handler, n: maxBodyBytes}, logger), certFile, keyFile, - ); err != nil { - logger.Error("RPC HTTPS server stopped", "err", err) - return err - } - return nil + ) + logger.Info("RPC HTTPS server stopped", "err", err) + return err } func WriteRPCResponseHTTPError( @@ -170,16 +162,6 @@ func (h maxBytesHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { h.h.ServeHTTP(w, r) } -// MustListen starts a new net.Listener on the given address. -// It panics in case of error. -func MustListen(addr string, config Config) net.Listener { - l, err := Listen(addr, config) - if err != nil { - panic(fmt.Errorf("Listen() failed: %v", err)) - } - return l -} - // Listen starts a new net.Listener on the given address. // It returns an error if the address is invalid or the call to Listen() fails. func Listen(addr string, config Config) (listener net.Listener, err error) { diff --git a/types/part_set_test.go b/types/part_set_test.go index e597088c..daa2fa5c 100644 --- a/types/part_set_test.go +++ b/types/part_set_test.go @@ -84,8 +84,7 @@ func TestWrongProof(t *testing.T) { } } -func TestPartSetHeaderSetValidateBasic(t *testing.T) { - +func TestPartSetHeaderValidateBasic(t *testing.T) { testCases := []struct { testName string malleatePartSetHeader func(*PartSetHeader) @@ -107,7 +106,6 @@ func TestPartSetHeaderSetValidateBasic(t *testing.T) { } func TestPartValidateBasic(t *testing.T) { - testCases := []struct { testName string malleatePart func(*Part) From b90e06a37cb341b2afb885ba99b74657daa6817f Mon Sep 17 00:00:00 2001 From: krhubert Date: Fri, 16 Nov 2018 23:36:42 +0100 Subject: [PATCH 088/267] More verbose error log (#2864) --- abci/client/grpc_client.go | 2 +- abci/client/socket_client.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/abci/client/grpc_client.go b/abci/client/grpc_client.go index d5cd233a..94aabc5e 100644 --- a/abci/client/grpc_client.go +++ b/abci/client/grpc_client.go @@ -54,7 +54,7 @@ RETRY_LOOP: if cli.mustConnect { return err } - cli.Logger.Error(fmt.Sprintf("abci.grpcClient failed to connect to %v. Retrying...\n", cli.addr)) + cli.Logger.Error(fmt.Sprintf("abci.grpcClient failed to connect to %v. Retrying...\n", cli.addr), "err", err) time.Sleep(time.Second * dialRetryIntervalSeconds) continue RETRY_LOOP } diff --git a/abci/client/socket_client.go b/abci/client/socket_client.go index 531d12bc..56267660 100644 --- a/abci/client/socket_client.go +++ b/abci/client/socket_client.go @@ -67,7 +67,7 @@ RETRY_LOOP: if cli.mustConnect { return err } - cli.Logger.Error(fmt.Sprintf("abci.socketClient failed to connect to %v. Retrying...", cli.addr)) + cli.Logger.Error(fmt.Sprintf("abci.socketClient failed to connect to %v. Retrying...", cli.addr), "err", err) time.Sleep(time.Second * dialRetryIntervalSeconds) continue RETRY_LOOP } From 2cfdef61111a98b894df305430b12e7f5cb971fe Mon Sep 17 00:00:00 2001 From: Daniil Lashin Date: Sat, 17 Nov 2018 01:38:22 +0300 Subject: [PATCH 089/267] Make "Update to validators" msg value pretty (#2848) * Make "Update to validators" msg value pretty #2765 * New format for logging validator updates * Refactor logging validator updates * Fix changelog item * fix merge conflict --- CHANGELOG_PENDING.md | 1 + state/execution.go | 38 +++++++++++++++++++++++--------------- state/execution_test.go | 2 +- state/state_test.go | 7 ++++--- 4 files changed, 29 insertions(+), 19 deletions(-) diff --git a/CHANGELOG_PENDING.md b/CHANGELOG_PENDING.md index c57f1993..7c7e0717 100644 --- a/CHANGELOG_PENDING.md +++ b/CHANGELOG_PENDING.md @@ -34,6 +34,7 @@ program](https://hackerone.com/tendermint). ### IMPROVEMENTS: +- [state] \#2765 Make "Update to validators" msg value pretty (@danil-lashin) - [p2p] \#2857 "Send failed" is logged at debug level instead of error. ### BUG FIXES: diff --git a/state/execution.go b/state/execution.go index 4f5a1545..9aa714eb 100644 --- a/state/execution.go +++ b/state/execution.go @@ -2,6 +2,7 @@ package state import ( "fmt" + "strings" "time" abci "github.com/tendermint/tendermint/abci/types" @@ -107,7 +108,7 @@ func (blockExec *BlockExecutor) ApplyBlock(state State, blockID types.BlockID, b fail.Fail() // XXX // Update the state with the block and responses. - state, err = updateState(state, blockID, &block.Header, abciResponses) + state, err = updateState(blockExec.logger, state, blockID, &block.Header, abciResponses) if err != nil { return state, fmt.Errorf("Commit failed for application: %v", err) } @@ -254,12 +255,6 @@ func execBlockOnProxyApp( logger.Info("Executed block", "height", block.Height, "validTxs", validTxs, "invalidTxs", invalidTxs) - valUpdates := abciResponses.EndBlock.ValidatorUpdates - if len(valUpdates) > 0 { - // TODO: cleanup the formatting - logger.Info("Updates to validators", "updates", valUpdates) - } - return abciResponses, nil } @@ -315,16 +310,16 @@ func getBeginBlockValidatorInfo(block *types.Block, lastValSet *types.ValidatorS // If more or equal than 1/3 of total voting power changed in one block, then // a light client could never prove the transition externally. See // ./lite/doc.go for details on how a light client tracks validators. -func updateValidators(currentSet *types.ValidatorSet, abciUpdates []abci.ValidatorUpdate) error { +func updateValidators(currentSet *types.ValidatorSet, abciUpdates []abci.ValidatorUpdate) ([]*types.Validator, error) { updates, err := types.PB2TM.ValidatorUpdates(abciUpdates) if err != nil { - return err + return nil, err } // these are tendermint types now for _, valUpdate := range updates { if valUpdate.VotingPower < 0 { - return fmt.Errorf("Voting power can't be negative %v", valUpdate) + return nil, fmt.Errorf("Voting power can't be negative %v", valUpdate) } address := valUpdate.Address @@ -333,27 +328,28 @@ func updateValidators(currentSet *types.ValidatorSet, abciUpdates []abci.Validat // remove val _, removed := currentSet.Remove(address) if !removed { - return fmt.Errorf("Failed to remove validator %X", address) + return nil, fmt.Errorf("Failed to remove validator %X", address) } } else if val == nil { // add val added := currentSet.Add(valUpdate) if !added { - return fmt.Errorf("Failed to add new validator %v", valUpdate) + return nil, fmt.Errorf("Failed to add new validator %v", valUpdate) } } else { // update val updated := currentSet.Update(valUpdate) if !updated { - return fmt.Errorf("Failed to update validator %X to %v", address, valUpdate) + return nil, fmt.Errorf("Failed to update validator %X to %v", address, valUpdate) } } } - return nil + return updates, nil } // updateState returns a new State updated according to the header and responses. func updateState( + logger log.Logger, state State, blockID types.BlockID, header *types.Header, @@ -367,12 +363,14 @@ func updateState( // Update the validator set with the latest abciResponses. lastHeightValsChanged := state.LastHeightValidatorsChanged if len(abciResponses.EndBlock.ValidatorUpdates) > 0 { - err := updateValidators(nValSet, abciResponses.EndBlock.ValidatorUpdates) + validatorUpdates, err := updateValidators(nValSet, abciResponses.EndBlock.ValidatorUpdates) if err != nil { return state, fmt.Errorf("Error changing validator set: %v", err) } // Change results from this height but only applies to the next next height. lastHeightValsChanged = header.Height + 1 + 1 + + logger.Info("Updates to validators", "updates", makeValidatorUpdatesLogString(validatorUpdates)) } // Update validator accums and set state variables. @@ -466,3 +464,13 @@ func ExecCommitBlock( // ResponseCommit has no error or log, just data return res.Data, nil } + +// Make pretty string for validatorUpdates logging +func makeValidatorUpdatesLogString(vals []*types.Validator) string { + chunks := make([]string, len(vals)) + for i, val := range vals { + chunks[i] = fmt.Sprintf("%s:%d", val.Address, val.VotingPower) + } + + return strings.Join(chunks, ",") +} diff --git a/state/execution_test.go b/state/execution_test.go index 273e9ebe..41d9a484 100644 --- a/state/execution_test.go +++ b/state/execution_test.go @@ -218,7 +218,7 @@ func TestUpdateValidators(t *testing.T) { for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { - err := updateValidators(tc.currentSet, tc.abciUpdates) + _, err := updateValidators(tc.currentSet, tc.abciUpdates) if tc.shouldErr { assert.Error(t, err) } else { diff --git a/state/state_test.go b/state/state_test.go index 88200e17..17293f6f 100644 --- a/state/state_test.go +++ b/state/state_test.go @@ -3,6 +3,7 @@ package state import ( "bytes" "fmt" + "github.com/tendermint/tendermint/libs/log" "testing" "github.com/stretchr/testify/assert" @@ -228,7 +229,7 @@ func TestOneValidatorChangesSaveLoad(t *testing.T) { power++ } header, blockID, responses := makeHeaderPartsResponsesValPowerChange(state, i, power) - state, err = updateState(state, blockID, &header, responses) + state, err = updateState(log.TestingLogger(), state, blockID, &header, responses) assert.Nil(t, err) nextHeight := state.LastBlockHeight + 1 saveValidatorsInfo(stateDB, nextHeight+1, state.LastHeightValidatorsChanged, state.NextValidators) @@ -280,7 +281,7 @@ func TestManyValidatorChangesSaveLoad(t *testing.T) { // Save state etc. var err error - state, err = updateState(state, blockID, &header, responses) + state, err = updateState(log.TestingLogger(), state, blockID, &header, responses) require.Nil(t, err) nextHeight := state.LastBlockHeight + 1 saveValidatorsInfo(stateDB, nextHeight+1, state.LastHeightValidatorsChanged, state.NextValidators) @@ -359,7 +360,7 @@ func TestConsensusParamsChangesSaveLoad(t *testing.T) { cp = params[changeIndex] } header, blockID, responses := makeHeaderPartsResponsesParams(state, i, cp) - state, err = updateState(state, blockID, &header, responses) + state, err = updateState(log.TestingLogger(), state, blockID, &header, responses) require.Nil(t, err) nextHeight := state.LastBlockHeight + 1 From 0d5e0d2f1326e61c81a417c76fee6f83ffb44e14 Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Fri, 16 Nov 2018 17:44:19 -0500 Subject: [PATCH 090/267] p2p/conn: FlushStop. Use in pex. Closes #2092 (#2802) * p2p/conn: FlushStop. Use in pex. Closes #2092 In seed mode, we call StopPeer immediately after Send. Since flushing msgs to the peer happens in the background, the peer connection is often closed before the messages are actually sent out. The new FlushStop method allows all msgs to first be written and flushed out on the conn before it is closed. * fix dummy peer * typo * fixes from review * more comments * ensure pex doesn't call FlushStop more than once FlushStop is not safe to call more than once, but we call it from Receive in a go-routine so Receive doesn't block. To ensure we only call it once, we use the lastReceivedRequests map - if an entry already exists, then FlushStop should already have been called and we can return. --- blockchain/reactor_test.go | 1 + p2p/conn/connection.go | 64 +++++++++++++++++++++++++++++++++---- p2p/conn/connection_test.go | 37 +++++++++++++++++++++ p2p/dummy/peer.go | 5 +++ p2p/peer.go | 10 ++++++ p2p/peer_set_test.go | 1 + p2p/pex/pex_reactor.go | 33 +++++++++++++------ p2p/pex/pex_reactor_test.go | 1 + 8 files changed, 135 insertions(+), 17 deletions(-) diff --git a/blockchain/reactor_test.go b/blockchain/reactor_test.go index fca063e0..9b26f919 100644 --- a/blockchain/reactor_test.go +++ b/blockchain/reactor_test.go @@ -197,6 +197,7 @@ func (tp *bcrTestPeer) TrySend(chID byte, msgBytes []byte) bool { return true } +func (tp *bcrTestPeer) FlushStop() {} func (tp *bcrTestPeer) Send(chID byte, msgBytes []byte) bool { return tp.TrySend(chID, msgBytes) } func (tp *bcrTestPeer) NodeInfo() p2p.NodeInfo { return p2p.DefaultNodeInfo{} } func (tp *bcrTestPeer) Status() p2p.ConnectionStatus { return p2p.ConnectionStatus{} } diff --git a/p2p/conn/connection.go b/p2p/conn/connection.go index 89282b00..c6aad038 100644 --- a/p2p/conn/connection.go +++ b/p2p/conn/connection.go @@ -84,7 +84,11 @@ type MConnection struct { errored uint32 config MConnConfig - quit chan struct{} + // Closing quitSendRoutine will cause + // doneSendRoutine to close. + quitSendRoutine chan struct{} + doneSendRoutine chan struct{} + flushTimer *cmn.ThrottleTimer // flush writes as necessary but throttled. pingTimer *cmn.RepeatTimer // send pings periodically @@ -190,7 +194,8 @@ func (c *MConnection) OnStart() error { if err := c.BaseService.OnStart(); err != nil { return err } - c.quit = make(chan struct{}) + c.quitSendRoutine = make(chan struct{}) + c.doneSendRoutine = make(chan struct{}) c.flushTimer = cmn.NewThrottleTimer("flush", c.config.FlushThrottle) c.pingTimer = cmn.NewRepeatTimer("ping", c.config.PingInterval) c.pongTimeoutCh = make(chan bool, 1) @@ -200,15 +205,59 @@ func (c *MConnection) OnStart() error { return nil } -// OnStop implements BaseService -func (c *MConnection) OnStop() { +// FlushStop replicates the logic of OnStop. +// It additionally ensures that all successful +// .Send() calls will get flushed before closing +// the connection. +// NOTE: it is not safe to call this method more than once. +func (c *MConnection) FlushStop() { c.BaseService.OnStop() c.flushTimer.Stop() c.pingTimer.Stop() c.chStatsTimer.Stop() - if c.quit != nil { - close(c.quit) + if c.quitSendRoutine != nil { + close(c.quitSendRoutine) + // wait until the sendRoutine exits + // so we dont race on calling sendSomePacketMsgs + <-c.doneSendRoutine } + + // Send and flush all pending msgs. + // By now, IsRunning == false, + // so any concurrent attempts to send will fail. + // Since sendRoutine has exited, we can call this + // safely + eof := c.sendSomePacketMsgs() + for !eof { + eof = c.sendSomePacketMsgs() + } + c.flush() + + // Now we can close the connection + c.conn.Close() // nolint: errcheck + + // We can't close pong safely here because + // recvRoutine may write to it after we've stopped. + // Though it doesn't need to get closed at all, + // we close it @ recvRoutine. + + // c.Stop() +} + +// OnStop implements BaseService +func (c *MConnection) OnStop() { + select { + case <-c.quitSendRoutine: + // already quit via FlushStop + return + default: + } + + c.BaseService.OnStop() + c.flushTimer.Stop() + c.pingTimer.Stop() + c.chStatsTimer.Stop() + close(c.quitSendRoutine) c.conn.Close() // nolint: errcheck // We can't close pong safely here because @@ -365,7 +414,8 @@ FOR_LOOP: } c.sendMonitor.Update(int(_n)) c.flush() - case <-c.quit: + case <-c.quitSendRoutine: + close(c.doneSendRoutine) break FOR_LOOP case <-c.send: // Send some PacketMsgs diff --git a/p2p/conn/connection_test.go b/p2p/conn/connection_test.go index 59fe0d1d..a757f07a 100644 --- a/p2p/conn/connection_test.go +++ b/p2p/conn/connection_test.go @@ -36,6 +36,43 @@ func createMConnectionWithCallbacks(conn net.Conn, onReceive func(chID byte, msg return c } +func TestMConnectionSendFlushStop(t *testing.T) { + server, client := NetPipe() + defer server.Close() // nolint: errcheck + defer client.Close() // nolint: errcheck + + clientConn := createTestMConnection(client) + err := clientConn.Start() + require.Nil(t, err) + defer clientConn.Stop() + + msg := []byte("abc") + assert.True(t, clientConn.Send(0x01, msg)) + + aminoMsgLength := 14 + + // start the reader in a new routine, so we can flush + errCh := make(chan error) + go func() { + msgB := make([]byte, aminoMsgLength) + _, err := server.Read(msgB) + if err != nil { + t.Fatal(err) + } + errCh <- err + }() + + // stop the conn - it should flush all conns + clientConn.FlushStop() + + timer := time.NewTimer(3 * time.Second) + select { + case <-errCh: + case <-timer.C: + t.Error("timed out waiting for msgs to be read") + } +} + func TestMConnectionSend(t *testing.T) { server, client := NetPipe() defer server.Close() // nolint: errcheck diff --git a/p2p/dummy/peer.go b/p2p/dummy/peer.go index 65ff65fb..71def27e 100644 --- a/p2p/dummy/peer.go +++ b/p2p/dummy/peer.go @@ -25,6 +25,11 @@ func NewPeer() *peer { return p } +// FlushStop just calls Stop. +func (p *peer) FlushStop() { + p.Stop() +} + // ID always returns dummy. func (p *peer) ID() p2p.ID { return p2p.ID("dummy") diff --git a/p2p/peer.go b/p2p/peer.go index e98c16d2..6417948d 100644 --- a/p2p/peer.go +++ b/p2p/peer.go @@ -17,6 +17,7 @@ const metricsTickerDuration = 10 * time.Second // Peer is an interface representing a peer connected on a reactor. type Peer interface { cmn.Service + FlushStop() ID() ID // peer's cryptographic ID RemoteIP() net.IP // remote IP of the connection @@ -184,6 +185,15 @@ func (p *peer) OnStart() error { return nil } +// FlushStop mimics OnStop but additionally ensures that all successful +// .Send() calls will get flushed before closing the connection. +// NOTE: it is not safe to call this method more than once. +func (p *peer) FlushStop() { + p.metricsTicker.Stop() + p.BaseService.OnStop() + p.mconn.FlushStop() // stop everything and close the conn +} + // OnStop implements BaseService. func (p *peer) OnStop() { p.metricsTicker.Stop() diff --git a/p2p/peer_set_test.go b/p2p/peer_set_test.go index daa9b2c8..04b877b0 100644 --- a/p2p/peer_set_test.go +++ b/p2p/peer_set_test.go @@ -18,6 +18,7 @@ type mockPeer struct { id ID } +func (mp *mockPeer) FlushStop() { mp.Stop() } func (mp *mockPeer) TrySend(chID byte, msgBytes []byte) bool { return true } func (mp *mockPeer) Send(chID byte, msgBytes []byte) bool { return true } func (mp *mockPeer) NodeInfo() NodeInfo { return DefaultNodeInfo{} } diff --git a/p2p/pex/pex_reactor.go b/p2p/pex/pex_reactor.go index 85d292b0..057aadaa 100644 --- a/p2p/pex/pex_reactor.go +++ b/p2p/pex/pex_reactor.go @@ -208,25 +208,38 @@ func (r *PEXReactor) Receive(chID byte, src Peer, msgBytes []byte) { switch msg := msg.(type) { case *pexRequestMessage: - // Check we're not receiving too many requests - if err := r.receiveRequest(src); err != nil { - r.Switch.StopPeerForError(src, err) - return - } - // Seeds disconnect after sending a batch of addrs - // NOTE: this is a prime candidate for amplification attacks + // NOTE: this is a prime candidate for amplification attacks, // so it's important we // 1) restrict how frequently peers can request // 2) limit the output size - if r.config.SeedMode { + + // If we're a seed and this is an inbound peer, + // respond once and disconnect. + if r.config.SeedMode && !src.IsOutbound() { + id := string(src.ID()) + v := r.lastReceivedRequests.Get(id) + if v != nil { + // FlushStop/StopPeer are already + // running in a go-routine. + return + } + r.lastReceivedRequests.Set(id, time.Now()) + + // Send addrs and disconnect r.SendAddrs(src, r.book.GetSelectionWithBias(biasToSelectNewPeers)) go func() { - // TODO Fix properly #2092 - time.Sleep(time.Second * 5) + // In a go-routine so it doesn't block .Receive. + src.FlushStop() r.Switch.StopPeerGracefully(src) }() + } else { + // Check we're not receiving requests too frequently. + if err := r.receiveRequest(src); err != nil { + r.Switch.StopPeerForError(src, err) + return + } r.SendAddrs(src, r.book.GetSelection()) } diff --git a/p2p/pex/pex_reactor_test.go b/p2p/pex/pex_reactor_test.go index 9d3f49bb..8f3ceb89 100644 --- a/p2p/pex/pex_reactor_test.go +++ b/p2p/pex/pex_reactor_test.go @@ -387,6 +387,7 @@ func newMockPeer() mockPeer { return mp } +func (mp mockPeer) FlushStop() { mp.Stop() } func (mp mockPeer) ID() p2p.ID { return mp.addr.ID } func (mp mockPeer) IsOutbound() bool { return mp.outbound } func (mp mockPeer) IsPersistent() bool { return mp.persistent } From 60018d614897fb79a5221e5ae090911c767d76ab Mon Sep 17 00:00:00 2001 From: Anton Kaliaev Date: Sat, 17 Nov 2018 03:17:07 +0400 Subject: [PATCH 091/267] comment out until someone decides to tackle #2285 (#2760) current code results in panic and we certainly don't want that. https://github.com/tendermint/tendermint/pull/2286#issuecomment-418281846 --- libs/autofile/autofile.go | 15 +++++----- libs/autofile/autofile_test.go | 51 +++++++++++++++++----------------- libs/db/fsdb.go | 15 +++++----- libs/errors/errors.go | 37 +++++++++++------------- 4 files changed, 55 insertions(+), 63 deletions(-) diff --git a/libs/autofile/autofile.go b/libs/autofile/autofile.go index a1e2f49e..e428e26c 100644 --- a/libs/autofile/autofile.go +++ b/libs/autofile/autofile.go @@ -8,7 +8,6 @@ import ( "time" cmn "github.com/tendermint/tendermint/libs/common" - "github.com/tendermint/tendermint/libs/errors" ) /* AutoFile usage @@ -157,13 +156,13 @@ func (af *AutoFile) openFile() error { if err != nil { return err } - fileInfo, err := file.Stat() - if err != nil { - return err - } - if fileInfo.Mode() != autoFilePerms { - return errors.NewErrPermissionsChanged(file.Name(), fileInfo.Mode(), autoFilePerms) - } + // fileInfo, err := file.Stat() + // if err != nil { + // return err + // } + // if fileInfo.Mode() != autoFilePerms { + // return errors.NewErrPermissionsChanged(file.Name(), fileInfo.Mode(), autoFilePerms) + // } af.file = file return nil } diff --git a/libs/autofile/autofile_test.go b/libs/autofile/autofile_test.go index 0b3521c2..9903f1e6 100644 --- a/libs/autofile/autofile_test.go +++ b/libs/autofile/autofile_test.go @@ -10,7 +10,6 @@ import ( "github.com/stretchr/testify/require" cmn "github.com/tendermint/tendermint/libs/common" - "github.com/tendermint/tendermint/libs/errors" ) func TestSIGHUP(t *testing.T) { @@ -58,32 +57,32 @@ func TestSIGHUP(t *testing.T) { } } -// Manually modify file permissions, close, and reopen using autofile: -// We expect the file permissions to be changed back to the intended perms. -func TestOpenAutoFilePerms(t *testing.T) { - file, err := ioutil.TempFile("", "permission_test") - require.NoError(t, err) - err = file.Close() - require.NoError(t, err) - name := file.Name() +// // Manually modify file permissions, close, and reopen using autofile: +// // We expect the file permissions to be changed back to the intended perms. +// func TestOpenAutoFilePerms(t *testing.T) { +// file, err := ioutil.TempFile("", "permission_test") +// require.NoError(t, err) +// err = file.Close() +// require.NoError(t, err) +// name := file.Name() - // open and change permissions - af, err := OpenAutoFile(name) - require.NoError(t, err) - err = af.file.Chmod(0755) - require.NoError(t, err) - err = af.Close() - require.NoError(t, err) +// // open and change permissions +// af, err := OpenAutoFile(name) +// require.NoError(t, err) +// err = af.file.Chmod(0755) +// require.NoError(t, err) +// err = af.Close() +// require.NoError(t, err) - // reopen and expect an ErrPermissionsChanged as Cause - af, err = OpenAutoFile(name) - require.Error(t, err) - if e, ok := err.(*errors.ErrPermissionsChanged); ok { - t.Logf("%v", e) - } else { - t.Errorf("unexpected error %v", e) - } -} +// // reopen and expect an ErrPermissionsChanged as Cause +// af, err = OpenAutoFile(name) +// require.Error(t, err) +// if e, ok := err.(*errors.ErrPermissionsChanged); ok { +// t.Logf("%v", e) +// } else { +// t.Errorf("unexpected error %v", e) +// } +// } func TestAutoFileSize(t *testing.T) { // First, create an AutoFile writing to a tempfile dir @@ -120,4 +119,4 @@ func TestAutoFileSize(t *testing.T) { // Cleanup _ = os.Remove(f.Name()) -} +} \ No newline at end of file diff --git a/libs/db/fsdb.go b/libs/db/fsdb.go index 92c059d4..b1d40c7b 100644 --- a/libs/db/fsdb.go +++ b/libs/db/fsdb.go @@ -12,7 +12,6 @@ import ( "github.com/pkg/errors" cmn "github.com/tendermint/tendermint/libs/common" - tmerrors "github.com/tendermint/tendermint/libs/errors" ) const ( @@ -207,13 +206,13 @@ func write(path string, d []byte) error { return err } defer f.Close() - fInfo, err := f.Stat() - if err != nil { - return err - } - if fInfo.Mode() != keyPerm { - return tmerrors.NewErrPermissionsChanged(f.Name(), keyPerm, fInfo.Mode()) - } + // fInfo, err := f.Stat() + // if err != nil { + // return err + // } + // if fInfo.Mode() != keyPerm { + // return tmerrors.NewErrPermissionsChanged(f.Name(), keyPerm, fInfo.Mode()) + // } _, err = f.Write(d) if err != nil { return err diff --git a/libs/errors/errors.go b/libs/errors/errors.go index ae5d9439..a0338278 100644 --- a/libs/errors/errors.go +++ b/libs/errors/errors.go @@ -1,26 +1,21 @@ // Package errors contains errors that are thrown across packages. package errors -import ( - "fmt" - "os" -) +// // ErrPermissionsChanged occurs if the file permission have changed since the file was created. +// type ErrPermissionsChanged struct { +// name string +// got, want os.FileMode +// } -// ErrPermissionsChanged occurs if the file permission have changed since the file was created. -type ErrPermissionsChanged struct { - name string - got, want os.FileMode -} +// func NewErrPermissionsChanged(name string, got, want os.FileMode) *ErrPermissionsChanged { +// return &ErrPermissionsChanged{name: name, got: got, want: want} +// } -func NewErrPermissionsChanged(name string, got, want os.FileMode) *ErrPermissionsChanged { - return &ErrPermissionsChanged{name: name, got: got, want: want} -} - -func (e ErrPermissionsChanged) Error() string { - return fmt.Sprintf( - "file: [%v]\nexpected file permissions: %v, got: %v", - e.name, - e.want, - e.got, - ) -} +// func (e ErrPermissionsChanged) Error() string { +// return fmt.Sprintf( +// "file: [%v]\nexpected file permissions: %v, got: %v", +// e.name, +// e.want, +// e.got, +// ) +// } From e6fc10faf698d2ad7eba97d4c9f5b8a1b505da64 Mon Sep 17 00:00:00 2001 From: Zaki Manian Date: Sat, 17 Nov 2018 00:10:22 -0800 Subject: [PATCH 092/267] R4R: Add timeouts to http servers (#2780) * Replaces our current http servers where connections stay open forever with ones with timeouts to prevent file descriptor exhaustion * Use the correct handler * Put in go routines * fix err * changelog * rpc: export Read/WriteTimeout The `broadcast_tx_commit` endpoint has it's own timeout. If this is longer than the http server's WriteTimeout, the user will receive an error. Here, we export the WriteTimeout and set the broadcast_tx_commit timeout to be less than it. In the future, we should use a config struct for the timeouts to avoid the need to export. The broadcast_tx_commit timeout may also become configurable, but we must check that it's less than the server's WriteTimeout. --- CHANGELOG_PENDING.md | 1 + rpc/core/mempool.go | 4 +++- rpc/core/pipe.go | 5 ++--- rpc/lib/server/http_server.go | 38 +++++++++++++++++++++++++---------- 4 files changed, 33 insertions(+), 15 deletions(-) diff --git a/CHANGELOG_PENDING.md b/CHANGELOG_PENDING.md index 7c7e0717..f49ddd72 100644 --- a/CHANGELOG_PENDING.md +++ b/CHANGELOG_PENDING.md @@ -34,6 +34,7 @@ program](https://hackerone.com/tendermint). ### IMPROVEMENTS: +- [rpc] \#2780 Add read and write timeouts to HTTP servers - [state] \#2765 Make "Update to validators" msg value pretty (@danil-lashin) - [p2p] \#2857 "Send failed" is logged at debug level instead of error. diff --git a/rpc/core/mempool.go b/rpc/core/mempool.go index 59877492..7b3c368a 100644 --- a/rpc/core/mempool.go +++ b/rpc/core/mempool.go @@ -9,6 +9,7 @@ import ( abci "github.com/tendermint/tendermint/abci/types" ctypes "github.com/tendermint/tendermint/rpc/core/types" + rpcserver "github.com/tendermint/tendermint/rpc/lib/server" "github.com/tendermint/tendermint/types" ) @@ -194,7 +195,8 @@ func BroadcastTxCommit(tx types.Tx) (*ctypes.ResultBroadcastTxCommit, error) { } // Wait for the tx to be included in a block or timeout. - var deliverTxTimeout = 10 * time.Second // TODO: configurable? + // TODO: configurable? + var deliverTxTimeout = rpcserver.WriteTimeout / 2 select { case deliverTxResMsg := <-deliverTxResCh: // The tx was included in a block. deliverTxRes := deliverTxResMsg.(types.EventDataTx) diff --git a/rpc/core/pipe.go b/rpc/core/pipe.go index 188ea1c3..ae8ae056 100644 --- a/rpc/core/pipe.go +++ b/rpc/core/pipe.go @@ -1,8 +1,6 @@ package core import ( - "time" - "github.com/tendermint/tendermint/consensus" crypto "github.com/tendermint/tendermint/crypto" dbm "github.com/tendermint/tendermint/libs/db" @@ -10,6 +8,7 @@ import ( mempl "github.com/tendermint/tendermint/mempool" "github.com/tendermint/tendermint/p2p" "github.com/tendermint/tendermint/proxy" + rpcserver "github.com/tendermint/tendermint/rpc/lib/server" sm "github.com/tendermint/tendermint/state" "github.com/tendermint/tendermint/state/txindex" "github.com/tendermint/tendermint/types" @@ -21,7 +20,7 @@ const ( maxPerPage = 100 ) -var subscribeTimeout = 5 * time.Second +var subscribeTimeout = rpcserver.WriteTimeout / 2 //---------------------------------------------- // These interfaces are used by RPC and must be thread safe diff --git a/rpc/lib/server/http_server.go b/rpc/lib/server/http_server.go index a5f8692f..1fd422a9 100644 --- a/rpc/lib/server/http_server.go +++ b/rpc/lib/server/http_server.go @@ -27,6 +27,17 @@ const ( // maxBodyBytes controls the maximum number of bytes the // server will read parsing the request body. maxBodyBytes = int64(1000000) // 1MB + + // same as the net/http default + maxHeaderBytes = 1 << 20 + + // Timeouts for reading/writing to the http connection. + // Public so handlers can read them - + // /broadcast_tx_commit has it's own timeout, which should + // be less than the WriteTimeout here. + // TODO: use a config instead. + ReadTimeout = 3 * time.Second + WriteTimeout = 20 * time.Second ) // StartHTTPServer takes a listener and starts an HTTP server with the given handler. @@ -34,10 +45,13 @@ const ( // NOTE: This function blocks - you may want to call it in a go-routine. func StartHTTPServer(listener net.Listener, handler http.Handler, logger log.Logger) error { logger.Info(fmt.Sprintf("Starting RPC HTTP server on %s", listener.Addr())) - err := http.Serve( - listener, - RecoverAndLogHandler(maxBytesHandler{h: handler, n: maxBodyBytes}, logger), - ) + s := &http.Server{ + Handler: RecoverAndLogHandler(maxBytesHandler{h: handler, n: maxBodyBytes}, logger), + ReadTimeout: ReadTimeout, + WriteTimeout: WriteTimeout, + MaxHeaderBytes: maxHeaderBytes, + } + err := s.Serve(listener) logger.Info("RPC HTTP server stopped", "err", err) return err } @@ -53,13 +67,15 @@ func StartHTTPAndTLSServer( ) error { logger.Info(fmt.Sprintf("Starting RPC HTTPS server on %s (cert: %q, key: %q)", listener.Addr(), certFile, keyFile)) - err := http.ServeTLS( - listener, - RecoverAndLogHandler(maxBytesHandler{h: handler, n: maxBodyBytes}, logger), - certFile, - keyFile, - ) - logger.Info("RPC HTTPS server stopped", "err", err) + s := &http.Server{ + Handler: RecoverAndLogHandler(maxBytesHandler{h: handler, n: maxBodyBytes}, logger), + ReadTimeout: ReadTimeout, + WriteTimeout: WriteTimeout, + MaxHeaderBytes: maxHeaderBytes, + } + err := s.ServeTLS(listener, certFile, keyFile) + + logger.Error("RPC HTTPS server stopped", "err", err) return err } From 6168b404a773a9b18fa9c0940d1606c40378f26c Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Sat, 17 Nov 2018 03:16:49 -0500 Subject: [PATCH 093/267] p2p: NewMultiplexTransport takes an MConnConfig (#2869) * p2p: NewMultiplexTransport takes an MConnConfig * changelog * move test func to test file --- CHANGELOG_PENDING.md | 2 ++ node/node.go | 3 ++- p2p/peer.go | 4 ---- p2p/switch.go | 21 +++++++++++---------- p2p/test_util.go | 5 ++--- p2p/transport.go | 10 ++++------ p2p/transport_test.go | 29 +++++++++++++++++++++-------- 7 files changed, 42 insertions(+), 32 deletions(-) diff --git a/CHANGELOG_PENDING.md b/CHANGELOG_PENDING.md index f49ddd72..cb3d7606 100644 --- a/CHANGELOG_PENDING.md +++ b/CHANGELOG_PENDING.md @@ -39,3 +39,5 @@ program](https://hackerone.com/tendermint). - [p2p] \#2857 "Send failed" is logged at debug level instead of error. ### BUG FIXES: + +- [p2p] \#2869 Set connection config properly instead of always using default diff --git a/node/node.go b/node/node.go index f1da1df0..bfd8d02e 100644 --- a/node/node.go +++ b/node/node.go @@ -371,7 +371,8 @@ func NewNode(config *cfg.Config, // Setup Transport. var ( - transport = p2p.NewMultiplexTransport(nodeInfo, *nodeKey) + mConnConfig = p2p.MConnConfig(config.P2P) + transport = p2p.NewMultiplexTransport(nodeInfo, *nodeKey, mConnConfig) connFilters = []p2p.ConnFilterFunc{} peerFilters = []p2p.PeerFilterFunc{} ) diff --git a/p2p/peer.go b/p2p/peer.go index 6417948d..da301d49 100644 --- a/p2p/peer.go +++ b/p2p/peer.go @@ -8,7 +8,6 @@ import ( cmn "github.com/tendermint/tendermint/libs/common" "github.com/tendermint/tendermint/libs/log" - "github.com/tendermint/tendermint/config" tmconn "github.com/tendermint/tendermint/p2p/conn" ) @@ -42,7 +41,6 @@ type Peer interface { type peerConn struct { outbound bool persistent bool - config *config.P2PConfig conn net.Conn // source connection originalAddr *NetAddress // nil for inbound connections @@ -53,7 +51,6 @@ type peerConn struct { func newPeerConn( outbound, persistent bool, - config *config.P2PConfig, conn net.Conn, originalAddr *NetAddress, ) peerConn { @@ -61,7 +58,6 @@ func newPeerConn( return peerConn{ outbound: outbound, persistent: persistent, - config: config, conn: conn, originalAddr: originalAddr, } diff --git a/p2p/switch.go b/p2p/switch.go index b70900ea..4996ebd9 100644 --- a/p2p/switch.go +++ b/p2p/switch.go @@ -27,6 +27,17 @@ const ( reconnectBackOffBaseSeconds = 3 ) +// MConnConfig returns an MConnConfig with fields updated +// from the P2PConfig. +func MConnConfig(cfg *config.P2PConfig) conn.MConnConfig { + mConfig := conn.DefaultMConnConfig() + mConfig.FlushThrottle = cfg.FlushThrottleTimeout + mConfig.SendRate = cfg.SendRate + mConfig.RecvRate = cfg.RecvRate + mConfig.MaxPacketMsgPayloadSize = cfg.MaxPacketMsgPayloadSize + return mConfig +} + //----------------------------------------------------------------------------- // An AddrBook represents an address book from the pex package, which is used @@ -70,8 +81,6 @@ type Switch struct { filterTimeout time.Duration peerFilters []PeerFilterFunc - mConfig conn.MConnConfig - rng *cmn.Rand // seed for randomizing dial times and orders metrics *Metrics @@ -102,14 +111,6 @@ func NewSwitch( // Ensure we have a completely undeterministic PRNG. sw.rng = cmn.NewRand() - mConfig := conn.DefaultMConnConfig() - mConfig.FlushThrottle = cfg.FlushThrottleTimeout - mConfig.SendRate = cfg.SendRate - mConfig.RecvRate = cfg.RecvRate - mConfig.MaxPacketMsgPayloadSize = cfg.MaxPacketMsgPayloadSize - - sw.mConfig = mConfig - sw.BaseService = *cmn.NewBaseService(nil, "P2P Switch", sw) for _, option := range options { diff --git a/p2p/test_util.go b/p2p/test_util.go index d72c0c76..b8a34600 100644 --- a/p2p/test_util.go +++ b/p2p/test_util.go @@ -135,7 +135,7 @@ func (sw *Switch) addPeerWithConnection(conn net.Conn) error { p := newPeer( pc, - sw.mConfig, + MConnConfig(sw.config), ni, sw.reactorsByCh, sw.chDescs, @@ -175,7 +175,7 @@ func MakeSwitch( } nodeInfo := testNodeInfo(nodeKey.ID(), fmt.Sprintf("node%d", i)) - t := NewMultiplexTransport(nodeInfo, nodeKey) + t := NewMultiplexTransport(nodeInfo, nodeKey, MConnConfig(cfg)) addr := nodeInfo.NetAddress() if err := t.Listen(*addr); err != nil { @@ -232,7 +232,6 @@ func testPeerConn( // Only the information we already have return peerConn{ - config: cfg, outbound: outbound, persistent: persistent, conn: conn, diff --git a/p2p/transport.go b/p2p/transport.go index 0b9b436f..b16db54d 100644 --- a/p2p/transport.go +++ b/p2p/transport.go @@ -6,7 +6,6 @@ import ( "net" "time" - "github.com/tendermint/tendermint/config" "github.com/tendermint/tendermint/crypto" "github.com/tendermint/tendermint/p2p/conn" ) @@ -129,11 +128,10 @@ type MultiplexTransport struct { nodeKey NodeKey resolver IPResolver - // TODO(xla): Those configs are still needed as we parameterise peerConn and + // TODO(xla): This config is still needed as we parameterise peerConn and // peer currently. All relevant configuration should be refactored into options // with sane defaults. - mConfig conn.MConnConfig - p2pConfig config.P2PConfig + mConfig conn.MConnConfig } // Test multiplexTransport for interface completeness. @@ -144,6 +142,7 @@ var _ transportLifecycle = (*MultiplexTransport)(nil) func NewMultiplexTransport( nodeInfo NodeInfo, nodeKey NodeKey, + mConfig conn.MConnConfig, ) *MultiplexTransport { return &MultiplexTransport{ acceptc: make(chan accept), @@ -151,7 +150,7 @@ func NewMultiplexTransport( dialTimeout: defaultDialTimeout, filterTimeout: defaultFilterTimeout, handshakeTimeout: defaultHandshakeTimeout, - mConfig: conn.DefaultMConnConfig(), + mConfig: mConfig, nodeInfo: nodeInfo, nodeKey: nodeKey, conns: NewConnSet(), @@ -405,7 +404,6 @@ func (mt *MultiplexTransport) wrapPeer( peerConn := newPeerConn( cfg.outbound, cfg.persistent, - &mt.p2pConfig, c, dialedAddr, ) diff --git a/p2p/transport_test.go b/p2p/transport_test.go index 8a5c06bc..182b2889 100644 --- a/p2p/transport_test.go +++ b/p2p/transport_test.go @@ -9,6 +9,7 @@ import ( "time" "github.com/tendermint/tendermint/crypto/ed25519" + "github.com/tendermint/tendermint/p2p/conn" ) var defaultNodeName = "host_peer" @@ -17,8 +18,20 @@ func emptyNodeInfo() NodeInfo { return DefaultNodeInfo{} } +// newMultiplexTransport returns a tcp connected multiplexed peer +// using the default MConnConfig. It's a convenience function used +// for testing. +func newMultiplexTransport( + nodeInfo NodeInfo, + nodeKey NodeKey, +) *MultiplexTransport { + return NewMultiplexTransport( + nodeInfo, nodeKey, conn.DefaultMConnConfig(), + ) +} + func TestTransportMultiplexConnFilter(t *testing.T) { - mt := NewMultiplexTransport( + mt := newMultiplexTransport( emptyNodeInfo(), NodeKey{ PrivKey: ed25519.GenPrivKey(), @@ -75,7 +88,7 @@ func TestTransportMultiplexConnFilter(t *testing.T) { } func TestTransportMultiplexConnFilterTimeout(t *testing.T) { - mt := NewMultiplexTransport( + mt := newMultiplexTransport( emptyNodeInfo(), NodeKey{ PrivKey: ed25519.GenPrivKey(), @@ -140,7 +153,7 @@ func TestTransportMultiplexAcceptMultiple(t *testing.T) { go func() { var ( pv = ed25519.GenPrivKey() - dialer = NewMultiplexTransport( + dialer = newMultiplexTransport( testNodeInfo(PubKeyToID(pv.PubKey()), defaultNodeName), NodeKey{ PrivKey: pv, @@ -261,7 +274,7 @@ func TestTransportMultiplexAcceptNonBlocking(t *testing.T) { <-slowc var ( - dialer = NewMultiplexTransport( + dialer = newMultiplexTransport( fastNodeInfo, NodeKey{ PrivKey: fastNodePV, @@ -307,7 +320,7 @@ func TestTransportMultiplexValidateNodeInfo(t *testing.T) { go func() { var ( pv = ed25519.GenPrivKey() - dialer = NewMultiplexTransport( + dialer = newMultiplexTransport( testNodeInfo(PubKeyToID(pv.PubKey()), ""), // Should not be empty NodeKey{ PrivKey: pv, @@ -350,7 +363,7 @@ func TestTransportMultiplexRejectMissmatchID(t *testing.T) { errc := make(chan error) go func() { - dialer := NewMultiplexTransport( + dialer := newMultiplexTransport( testNodeInfo( PubKeyToID(ed25519.GenPrivKey().PubKey()), "dialer", ), @@ -396,7 +409,7 @@ func TestTransportMultiplexRejectIncompatible(t *testing.T) { go func() { var ( pv = ed25519.GenPrivKey() - dialer = NewMultiplexTransport( + dialer = newMultiplexTransport( testNodeInfoWithNetwork(PubKeyToID(pv.PubKey()), "dialer", "incompatible-network"), NodeKey{ PrivKey: pv, @@ -553,7 +566,7 @@ func TestTransportHandshake(t *testing.T) { func testSetupMultiplexTransport(t *testing.T) *MultiplexTransport { var ( pv = ed25519.GenPrivKey() - mt = NewMultiplexTransport( + mt = newMultiplexTransport( testNodeInfo( PubKeyToID(pv.PubKey()), "transport", ), From 1466a2cc9f54fba194cc09e22c8bbeecd3e1b459 Mon Sep 17 00:00:00 2001 From: srmo Date: Sat, 17 Nov 2018 21:23:39 +0100 Subject: [PATCH 094/267] #2815 do not broadcast heartbeat proposal when we are non-validator (#2819) * #2815 do not broadcast heartbeat proposal when we are non-validator * #2815 adding preliminary changelog entry * #2815 cosmetics and added test * #2815 missed a little detail - it's enough to call getAddress() once here * #2815 remove debug logging from tests * #2815 OK. I seem to be doing something fundamentally wrong here * #2815 next iteration of proposalHeartbeat tests - try and use "ensure" pattern in common_test * 2815 incorporate review comments --- CHANGELOG_PENDING.md | 2 +- consensus/common_test.go | 14 ++++++++++++++ consensus/reactor.go | 2 +- consensus/state.go | 8 +++++++- consensus/state_test.go | 29 +++++++++++++++++++++++++++++ 5 files changed, 52 insertions(+), 3 deletions(-) diff --git a/CHANGELOG_PENDING.md b/CHANGELOG_PENDING.md index cb3d7606..07f26e3b 100644 --- a/CHANGELOG_PENDING.md +++ b/CHANGELOG_PENDING.md @@ -39,5 +39,5 @@ program](https://hackerone.com/tendermint). - [p2p] \#2857 "Send failed" is logged at debug level instead of error. ### BUG FIXES: - +- [consensus] \#2819 Don't send proposalHearbeat if not a validator - [p2p] \#2869 Set connection config properly instead of always using default diff --git a/consensus/common_test.go b/consensus/common_test.go index 46be5cbd..8a2d8a42 100644 --- a/consensus/common_test.go +++ b/consensus/common_test.go @@ -425,6 +425,20 @@ func ensureNewRound(roundCh <-chan interface{}, height int64, round int) { } } +func ensureProposalHeartbeat(heartbeatCh <-chan interface{}) { + select { + case <-time.After(ensureTimeout): + panic("Timeout expired while waiting for ProposalHeartbeat event") + case ev := <-heartbeatCh: + heartbeat, ok := ev.(types.EventDataProposalHeartbeat) + if !ok { + panic(fmt.Sprintf("expected a *types.EventDataProposalHeartbeat, "+ + "got %v. wrong subscription channel?", + reflect.TypeOf(heartbeat))) + } + } +} + func ensureNewTimeout(timeoutCh <-chan interface{}, height int64, round int, timeout int64) { timeoutDuration := time.Duration(timeout*3) * time.Nanosecond ensureNewEvent(timeoutCh, height, round, timeoutDuration, diff --git a/consensus/reactor.go b/consensus/reactor.go index 1768a8f0..b3298e9d 100644 --- a/consensus/reactor.go +++ b/consensus/reactor.go @@ -402,7 +402,7 @@ func (conR *ConsensusReactor) unsubscribeFromBroadcastEvents() { func (conR *ConsensusReactor) broadcastProposalHeartbeatMessage(hb *types.Heartbeat) { conR.Logger.Debug("Broadcasting proposal heartbeat message", - "height", hb.Height, "round", hb.Round, "sequence", hb.Sequence) + "height", hb.Height, "round", hb.Round, "sequence", hb.Sequence, "address", hb.ValidatorAddress) msg := &ProposalHeartbeatMessage{hb} conR.Switch.Broadcast(StateChannel, cdc.MustMarshalBinaryBare(msg)) } diff --git a/consensus/state.go b/consensus/state.go index 110a0e9f..0f7b56bc 100644 --- a/consensus/state.go +++ b/consensus/state.go @@ -802,8 +802,14 @@ func (cs *ConsensusState) needProofBlock(height int64) bool { } func (cs *ConsensusState) proposalHeartbeat(height int64, round int) { - counter := 0 + logger := cs.Logger.With("height", height, "round", round) addr := cs.privValidator.GetAddress() + + if !cs.Validators.HasAddress(addr) { + logger.Debug("Not sending proposalHearbeat. This node is not a validator", "addr", addr, "vals", cs.Validators) + return + } + counter := 0 valIndex, _ := cs.Validators.GetByAddress(addr) chainID := cs.state.ChainID for { diff --git a/consensus/state_test.go b/consensus/state_test.go index ddab6404..19dde053 100644 --- a/consensus/state_test.go +++ b/consensus/state_test.go @@ -11,6 +11,8 @@ import ( "github.com/stretchr/testify/require" cstypes "github.com/tendermint/tendermint/consensus/types" + tmevents "github.com/tendermint/tendermint/libs/events" + cmn "github.com/tendermint/tendermint/libs/common" "github.com/tendermint/tendermint/libs/log" tmpubsub "github.com/tendermint/tendermint/libs/pubsub" @@ -1027,6 +1029,33 @@ func TestSetValidBlockOnDelayedPrevote(t *testing.T) { assert.True(t, rs.ValidRound == round) } +// regression for #2518 +func TestNoHearbeatWhenNotValidator(t *testing.T) { + cs, _ := randConsensusState(4) + cs.Validators = types.NewValidatorSet(nil) // make sure we are not in the validator set + + cs.evsw.AddListenerForEvent("testing", types.EventProposalHeartbeat, + func(data tmevents.EventData) { + t.Errorf("Should not have broadcasted heartbeat") + }) + go cs.proposalHeartbeat(10, 1) + + cs.Stop() + + // if a faulty implementation sends an event, we should wait here a little bit to make sure we don't miss it by prematurely leaving the test method + time.Sleep((proposalHeartbeatIntervalSeconds + 1) * time.Second) +} + +// regression for #2518 +func TestHearbeatWhenWeAreValidator(t *testing.T) { + cs, _ := randConsensusState(4) + heartbeatCh := subscribe(cs.eventBus, types.EventQueryProposalHeartbeat) + + go cs.proposalHeartbeat(10, 1) + ensureProposalHeartbeat(heartbeatCh) + +} + // What we want: // P0 miss to lock B as Proposal Block is missing, but set valid block to B after // receiving delayed Block Proposal. From ccf6b2b51286193b9b9c582c202589facc4d053a Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Sat, 17 Nov 2018 16:04:05 -0500 Subject: [PATCH 095/267] Bucky/v0.26.3 (#2872) * update CONTRIBUTING with notes on CHANGELOG * update changelog * changelog and version --- CHANGELOG.md | 47 ++++++++++++++++++++++++++++++++++++++ CHANGELOG_PENDING.md | 17 +------------- CONTRIBUTING.md | 54 ++++++++++++++++++++++++++++++++++++-------- version/version.go | 2 +- 4 files changed, 93 insertions(+), 27 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bfdb9a50..e8cb63ba 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,52 @@ # Changelog +## v0.26.3 + +*November 17th, 2018* + +Special thanks to external contributors on this release: +@danil-lashin, @kevlubkcm, @krhubert, @srmo + +Friendly reminder, we have a [bug bounty +program](https://hackerone.com/tendermint). + +### BREAKING CHANGES: + +* Go API + - [rpc] [\#2791](https://github.com/tendermint/tendermint/issues/2791) Functions that start HTTP servers are now blocking: + - Impacts `StartHTTPServer`, `StartHTTPAndTLSServer`, and `StartGRPCServer` + - These functions now take a `net.Listener` instead of an address + - [rpc] [\#2767](https://github.com/tendermint/tendermint/issues/2767) Subscribing to events + `NewRound` and `CompleteProposal` return new types `EventDataNewRound` and + `EventDataCompleteProposal`, respectively, instead of the generic `EventDataRoundState`. (@kevlubkcm) + +### FEATURES: + +- [log] [\#2843](https://github.com/tendermint/tendermint/issues/2843) New `log_format` config option, which can be set to 'plain' for colored + text or 'json' for JSON output +- [types] [\#2767](https://github.com/tendermint/tendermint/issues/2767) New event types EventDataNewRound (with ProposerInfo) and EventDataCompleteProposal (with BlockID). (@kevlubkcm) + +### IMPROVEMENTS: + +- [dep] [\#2844](https://github.com/tendermint/tendermint/issues/2844) Dependencies are no longer pinned to an exact version in the + Gopkg.toml: + - Serialization libs are allowed to vary by patch release + - Other libs are allowed to vary by minor release +- [p2p] [\#2857](https://github.com/tendermint/tendermint/issues/2857) "Send failed" is logged at debug level instead of error. +- [rpc] [\#2780](https://github.com/tendermint/tendermint/issues/2780) Add read and write timeouts to HTTP servers +- [state] [\#2848](https://github.com/tendermint/tendermint/issues/2848) Make "Update to validators" msg value pretty (@danil-lashin) + +### BUG FIXES: +- [consensus] [\#2819](https://github.com/tendermint/tendermint/issues/2819) Don't send proposalHearbeat if not a validator +- [docs] [\#2859](https://github.com/tendermint/tendermint/issues/2859) Fix ConsensusParams details in spec +- [libs/autofile] [\#2760](https://github.com/tendermint/tendermint/issues/2760) Comment out autofile permissions check - should fix + running Tendermint on Windows +- [p2p] [\#2869](https://github.com/tendermint/tendermint/issues/2869) Set connection config properly instead of always using default +- [p2p/pex] [\#2802](https://github.com/tendermint/tendermint/issues/2802) Seed mode fixes: + - Only disconnect from inbound peers + - Use FlushStop instead of Sleep to ensure all messages are sent before + disconnecting + ## v0.26.2 *November 15th, 2018* diff --git a/CHANGELOG_PENDING.md b/CHANGELOG_PENDING.md index 07f26e3b..fd340d4d 100644 --- a/CHANGELOG_PENDING.md +++ b/CHANGELOG_PENDING.md @@ -1,6 +1,6 @@ # Pending -## v0.26.3 +## v0.26.4 *TBD* @@ -16,28 +16,13 @@ program](https://hackerone.com/tendermint). * Apps * Go API - - [rpc] \#2791 Functions that start HTTP servers are now blocking: - - Impacts: StartHTTPServer, StartHTTPAndTLSServer, and StartGRPCServer, - - These functions now take a `net.Listener` instead of an address * Blockchain Protocol * P2P Protocol - ### FEATURES: -- [log] \#2843 New `log_format` config option, which can be set to 'plain' for colored - text or 'json' for JSON output - -- [types] \#2767 New event types EventDataNewRound (with ProposerInfo) and EventDataCompleteProposal (with BlockID). (@kevlubkcm) - ### IMPROVEMENTS: -- [rpc] \#2780 Add read and write timeouts to HTTP servers -- [state] \#2765 Make "Update to validators" msg value pretty (@danil-lashin) -- [p2p] \#2857 "Send failed" is logged at debug level instead of error. - ### BUG FIXES: -- [consensus] \#2819 Don't send proposalHearbeat if not a validator -- [p2p] \#2869 Set connection config properly instead of always using default diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index a0057aae..3dab3b8a 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -69,17 +69,40 @@ vagrant ssh make test ``` -## Testing - -All repos should be hooked up to [CircleCI](https://circleci.com/). - -If they have `.go` files in the root directory, they will be automatically -tested by circle using `go test -v -race ./...`. If not, they will need a -`circle.yml`. Ideally, every repo has a `Makefile` that defines `make test` and -includes its continuous integration status using a badge in the `README.md`. - ## Changelog +Every fix, improvement, feature, or breaking change should be made in a +pull-request that includes an update to the `CHANGELOG_PENDING.md` file. + +Changelog entries should be formatted as follows: + +``` +- [module] \#xxx Some description about the change (@contributor) +``` + +Here, `module` is the part of the code that changed (typically a +top-level Go package), `xxx` is the pull-request number, and `contributor` +is the author/s of the change. + +It's also acceptable for `xxx` to refer to the relevent issue number, but pull-request +numbers are preferred. +Note this means pull-requests should be opened first so the changelog can then +be updated with the pull-request's number. +There is no need to include the full link, as this will be added +automatically during release. But please include the backslash and pound, eg. `\#2313`. + +Changelog entries should be ordered alphabetically according to the +`module`, and numerically according to the pull-request number. + +Changes with multiple classifications should be doubly included (eg. a bug fix +that is also a breaking change should be recorded under both). + +Breaking changes are further subdivided according to the APIs/users they impact. +Any change that effects multiple APIs/users should be recorded multiply - for +instance, a change to the `Blockchain Protocol` that removes a field from the +header should also be recorded under `CLI/RPC/Config` since the field will be +removed from the header in rpc responses as well. + ## Branching Model and Release All repos should adhere to the branching model: http://nvie.com/posts/a-successful-git-branching-model/. @@ -104,13 +127,14 @@ master constitutes a tagged release. - start on `develop` - run integration tests (see `test_integrations` in Makefile) - prepare changelog: - - copy `CHANGELOG_PENDING.md` to `CHANGELOG.md` + - copy `CHANGELOG_PENDING.md` to top of `CHANGELOG.md` - run `python ./scripts/linkify_changelog.py CHANGELOG.md` to add links for all issues - run `bash ./scripts/authors.sh` to get a list of authors since the latest release, and add the github aliases of external contributors to the top of the changelog. To lookup an alias from an email, try `bash ./scripts/authors.sh ` + - reset the `CHANGELOG_PENDING.md` - bump versions - push to release/vX.X.X to run the extended integration tests on the CI - merge to master @@ -127,3 +151,13 @@ master constitutes a tagged release. - merge hotfix-vX.X.X to master - merge hotfix-vX.X.X to develop - delete the hotfix-vX.X.X branch + + +## Testing + +All repos should be hooked up to [CircleCI](https://circleci.com/). + +If they have `.go` files in the root directory, they will be automatically +tested by circle using `go test -v -race ./...`. If not, they will need a +`circle.yml`. Ideally, every repo has a `Makefile` that defines `make test` and +includes its continuous integration status using a badge in the `README.md`. diff --git a/version/version.go b/version/version.go index aae54512..aa52a82e 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.2" + TMCoreSemVer = "0.26.3" // ABCISemVer is the semantic version of the ABCI library ABCISemVer = "0.15.0" From fd8d1d6b69a84f7c817056fbc3d490d13570b1f8 Mon Sep 17 00:00:00 2001 From: cong Date: Tue, 20 Nov 2018 00:15:23 +0800 Subject: [PATCH 096/267] add BlockTimeIota to the config.toml (#2878) Refs #2877 --- CHANGELOG_PENDING.md | 2 ++ config/toml.go | 3 +++ docs/tendermint-core/configuration.md | 3 +++ 3 files changed, 8 insertions(+) diff --git a/CHANGELOG_PENDING.md b/CHANGELOG_PENDING.md index fd340d4d..b499ab40 100644 --- a/CHANGELOG_PENDING.md +++ b/CHANGELOG_PENDING.md @@ -25,4 +25,6 @@ program](https://hackerone.com/tendermint). ### IMPROVEMENTS: +- [config] \#2877 add blocktime_iota to the config.toml (@ackratos) + ### BUG FIXES: diff --git a/config/toml.go b/config/toml.go index 6f0578e4..21e017b4 100644 --- a/config/toml.go +++ b/config/toml.go @@ -260,6 +260,9 @@ create_empty_blocks_interval = "{{ .Consensus.CreateEmptyBlocksInterval }}" peer_gossip_sleep_duration = "{{ .Consensus.PeerGossipSleepDuration }}" peer_query_maj23_sleep_duration = "{{ .Consensus.PeerQueryMaj23SleepDuration }}" +# Block time parameters. Corresponds to the minimum time increment between consecutive blocks. +blocktime_iota = "{{ .Consensus.BlockTimeIota }}" + ##### transactions indexer configuration options ##### [tx_index] diff --git a/docs/tendermint-core/configuration.md b/docs/tendermint-core/configuration.md index 13894a30..7d1a562e 100644 --- a/docs/tendermint-core/configuration.md +++ b/docs/tendermint-core/configuration.md @@ -203,6 +203,9 @@ create_empty_blocks_interval = "0s" peer_gossip_sleep_duration = "100ms" peer_query_maj23_sleep_duration = "2000ms" +# Block time parameters. Corresponds to the minimum time increment between consecutive blocks. +blocktime_iota = "1000ms" + ##### transactions indexer configuration options ##### [tx_index] From 7b883a5457c3b2fe6930fc74f21ce18f05464bec Mon Sep 17 00:00:00 2001 From: Anton Kaliaev Date: Tue, 20 Nov 2018 10:27:58 +0400 Subject: [PATCH 097/267] docs/install: prepend cp to /usr/local with sudo (#2885) Closes #2884 --- docs/introduction/install.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/introduction/install.md b/docs/introduction/install.md index f3498514..c3395eff 100644 --- a/docs/introduction/install.md +++ b/docs/introduction/install.md @@ -95,9 +95,9 @@ wget https://github.com/google/leveldb/archive/v1.20.tar.gz && \ tar -zxvf v1.20.tar.gz && \ cd leveldb-1.20/ && \ make && \ - cp -r out-static/lib* out-shared/lib* /usr/local/lib/ && \ + sudo cp -r out-static/lib* out-shared/lib* /usr/local/lib/ && \ cd include/ && \ - cp -r leveldb /usr/local/include/ && \ + sudo cp -r leveldb /usr/local/include/ && \ sudo ldconfig && \ rm -f v1.20.tar.gz ``` @@ -109,8 +109,8 @@ Set database backend to cleveldb: db_backend = "cleveldb" ``` -To build Tendermint, run +To install Tendermint, run ``` -CGO_LDFLAGS="-lsnappy" go build -ldflags "-X github.com/tendermint/tendermint/version.GitCommit=`git rev-parse --short=8 HEAD`" -tags "tendermint gcc" -o build/tendermint ./cmd/tendermint/ +CGO_LDFLAGS="-lsnappy" go install -ldflags "-X github.com/tendermint/tendermint/version.GitCommit=`git rev-parse --short=8 HEAD`" -tags "tendermint gcc" -o build/tendermint ./cmd/tendermint/ ``` From e9efbfe26779dffbda34e797d4b5e8c1cdb6f64c Mon Sep 17 00:00:00 2001 From: Anton Kaliaev Date: Mon, 19 Nov 2018 16:16:33 +0400 Subject: [PATCH 098/267] refactor mempool.Update - rename filterTxs to removeTxs - move txsMap into removeTxs func - rename goodTxs to txsLeft --- mempool/mempool.go | 44 ++++++++++++++++++++++---------------------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/mempool/mempool.go b/mempool/mempool.go index 0bdb4714..136f7abf 100644 --- a/mempool/mempool.go +++ b/mempool/mempool.go @@ -534,12 +534,6 @@ func (mem *Mempool) Update( preCheck PreCheckFunc, postCheck PostCheckFunc, ) error { - // First, create a lookup map of txns in new txs. - txsMap := make(map[string]struct{}, len(txs)) - for _, tx := range txs { - txsMap[string(tx)] = struct{}{} - } - // Set height mem.height = height mem.notifiedTxsAvailable = false @@ -551,12 +545,13 @@ func (mem *Mempool) Update( mem.postCheck = postCheck } - // Remove transactions that are already in txs. - goodTxs := mem.filterTxs(txsMap) + // Remove committed transactions. + txsLeft := mem.removeTxs(txs) + // Recheck mempool txs if any txs were committed in the block - if mem.config.Recheck && len(goodTxs) > 0 { - mem.logger.Info("Recheck txs", "numtxs", len(goodTxs), "height", height) - mem.recheckTxs(goodTxs) + if mem.config.Recheck && len(txsLeft) > 0 { + mem.logger.Info("Recheck txs", "numtxs", len(txsLeft), "height", height) + mem.recheckTxs(txsLeft) // At this point, mem.txs are being rechecked. // mem.recheckCursor re-scans mem.txs and possibly removes some txs. // Before mem.Reap(), we should wait for mem.recheckCursor to be nil. @@ -568,12 +563,18 @@ func (mem *Mempool) Update( return nil } -func (mem *Mempool) filterTxs(blockTxsMap map[string]struct{}) []types.Tx { - goodTxs := make([]types.Tx, 0, mem.txs.Len()) +func (mem *Mempool) removeTxs(txs types.Txs) []types.Tx { + // Build a map for faster lookups. + txsMap := make(map[string]struct{}, len(txs)) + for _, tx := range txs { + txsMap[string(tx)] = struct{}{} + } + + txsLeft := make([]types.Tx, 0, mem.txs.Len()) for e := mem.txs.Front(); e != nil; e = e.Next() { memTx := e.Value.(*mempoolTx) - // Remove the tx if it's alredy in a block. - if _, ok := blockTxsMap[string(memTx.tx)]; ok { + // Remove the tx if it's already in a block. + if _, ok := txsMap[string(memTx.tx)]; ok { // remove from clist mem.txs.Remove(e) e.DetachPrev() @@ -581,15 +582,14 @@ func (mem *Mempool) filterTxs(blockTxsMap map[string]struct{}) []types.Tx { // NOTE: we don't remove committed txs from the cache. continue } - // Good tx! - goodTxs = append(goodTxs, memTx.tx) + txsLeft = append(txsLeft, memTx.tx) } - return goodTxs + return txsLeft } -// NOTE: pass in goodTxs because mem.txs can mutate concurrently. -func (mem *Mempool) recheckTxs(goodTxs []types.Tx) { - if len(goodTxs) == 0 { +// NOTE: pass in txs because mem.txs can mutate concurrently. +func (mem *Mempool) recheckTxs(txs []types.Tx) { + if len(txs) == 0 { return } atomic.StoreInt32(&mem.rechecking, 1) @@ -598,7 +598,7 @@ func (mem *Mempool) recheckTxs(goodTxs []types.Tx) { // Push txs to proxyAppConn // NOTE: resCb() may be called concurrently. - for _, tx := range goodTxs { + for _, tx := range txs { mem.proxyAppConn.CheckTxAsync(tx) } mem.proxyAppConn.FlushAsync() From 2d525bf2b8267cbcebd5ce3899a78684ddbeeff4 Mon Sep 17 00:00:00 2001 From: Anton Kaliaev Date: Mon, 19 Nov 2018 16:22:17 +0400 Subject: [PATCH 099/267] mempool: add txs from Update to cache We should add txs that come in from mempool.Update to the mempool's cache, so that they never hit a potentially expensive check tx. Originally posted by @ValarDragon in #2846 https://github.com/tendermint/tendermint/issues/2846#issuecomment-439216656 Refs #2855 --- CHANGELOG_PENDING.md | 1 + mempool/mempool.go | 5 +++++ mempool/mempool_test.go | 11 +++++++++++ 3 files changed, 17 insertions(+) diff --git a/CHANGELOG_PENDING.md b/CHANGELOG_PENDING.md index b499ab40..de4930be 100644 --- a/CHANGELOG_PENDING.md +++ b/CHANGELOG_PENDING.md @@ -26,5 +26,6 @@ program](https://hackerone.com/tendermint). ### IMPROVEMENTS: - [config] \#2877 add blocktime_iota to the config.toml (@ackratos) +- [mempool] \#2855 add txs from Update to cache ### BUG FIXES: diff --git a/mempool/mempool.go b/mempool/mempool.go index 136f7abf..6f8ee021 100644 --- a/mempool/mempool.go +++ b/mempool/mempool.go @@ -545,6 +545,11 @@ func (mem *Mempool) Update( mem.postCheck = postCheck } + // Add committed transactions to cache (if missing). + for _, tx := range txs { + _ = mem.cache.Push(tx) + } + // Remove committed transactions. txsLeft := mem.removeTxs(txs) diff --git a/mempool/mempool_test.go b/mempool/mempool_test.go index d7ab8273..15bfaa25 100644 --- a/mempool/mempool_test.go +++ b/mempool/mempool_test.go @@ -163,6 +163,17 @@ func TestMempoolFilters(t *testing.T) { } } +func TestMempoolUpdateAddsTxsToCache(t *testing.T) { + app := kvstore.NewKVStoreApplication() + cc := proxy.NewLocalClientCreator(app) + mempool := newMempoolWithApp(cc) + mempool.Update(1, []types.Tx{[]byte{0x01}}, nil, nil) + err := mempool.CheckTx([]byte{0x01}, nil) + if assert.Error(t, err) { + assert.Equal(t, ErrTxInCache, err) + } +} + func TestTxsAvailable(t *testing.T) { app := kvstore.NewKVStoreApplication() cc := proxy.NewLocalClientCreator(app) From 1610a05cbdd47f5cf5f37ef261473d25e32f87fb Mon Sep 17 00:00:00 2001 From: Dev Ojha Date: Wed, 21 Nov 2018 00:33:41 -0600 Subject: [PATCH 100/267] Remove counter from every mempoolTx (#2891) Within every tx in the mempool, we store a 64 bit counter, as an index for when it was inserted into the mempool. This counter doesn't really serve any purpose. It was likely added for debugging at one point, Removing the counter reclaims memory, which enables greater mempool sizes / mitigates resources at the same size. Closes #2835 --- CHANGELOG_PENDING.md | 1 + mempool/mempool.go | 6 ------ 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/CHANGELOG_PENDING.md b/CHANGELOG_PENDING.md index de4930be..aa42e372 100644 --- a/CHANGELOG_PENDING.md +++ b/CHANGELOG_PENDING.md @@ -27,5 +27,6 @@ program](https://hackerone.com/tendermint). - [config] \#2877 add blocktime_iota to the config.toml (@ackratos) - [mempool] \#2855 add txs from Update to cache +- [mempool] \#2835 Remove local int64 counter from being stored in every tx ### BUG FIXES: diff --git a/mempool/mempool.go b/mempool/mempool.go index 6f8ee021..8f70ec6c 100644 --- a/mempool/mempool.go +++ b/mempool/mempool.go @@ -131,7 +131,6 @@ type Mempool struct { proxyMtx sync.Mutex proxyAppConn proxy.AppConnMempool txs *clist.CList // concurrent linked-list of good txs - counter int64 // simple incrementing counter height int64 // the last block Update()'d to rechecking int32 // for re-checking filtered txs on Update() recheckCursor *clist.CElement // next expected response @@ -167,7 +166,6 @@ func NewMempool( config: config, proxyAppConn: proxyAppConn, txs: clist.New(), - counter: 0, height: height, rechecking: 0, recheckCursor: nil, @@ -365,9 +363,7 @@ func (mem *Mempool) resCbNormal(req *abci.Request, res *abci.Response) { postCheckErr = mem.postCheck(tx, r.CheckTx) } if (r.CheckTx.Code == abci.CodeTypeOK) && postCheckErr == nil { - mem.counter++ memTx := &mempoolTx{ - counter: mem.counter, height: mem.height, gasWanted: r.CheckTx.GasWanted, tx: tx, @@ -378,7 +374,6 @@ func (mem *Mempool) resCbNormal(req *abci.Request, res *abci.Response) { "res", r, "height", memTx.height, "total", mem.Size(), - "counter", memTx.counter, ) mem.metrics.TxSizeBytes.Observe(float64(len(tx))) mem.notifyTxsAvailable() @@ -613,7 +608,6 @@ func (mem *Mempool) recheckTxs(txs []types.Tx) { // mempoolTx is a transaction that successfully ran type mempoolTx struct { - counter int64 // a simple incrementing counter height int64 // height that this tx had been validated in gasWanted int64 // amount of gas this tx states it will require tx types.Tx // From 42592d9ae0c088aefa3fd7fbe74a2897f03f881a Mon Sep 17 00:00:00 2001 From: Jae Kwon Date: Tue, 20 Nov 2018 22:43:02 -0800 Subject: [PATCH 101/267] IncrementAccum upon RPC /validators; Sanity checks and comments (#2808) --- CHANGELOG_PENDING.md | 2 ++ state/state.go | 3 ++- state/state_test.go | 24 +++++++++++++++++++++++- state/store.go | 22 +++++++++++++++------- types/validator_set.go | 4 ++++ types/validator_set_test.go | 15 ++++++++++++++- 6 files changed, 60 insertions(+), 10 deletions(-) diff --git a/CHANGELOG_PENDING.md b/CHANGELOG_PENDING.md index aa42e372..4361afac 100644 --- a/CHANGELOG_PENDING.md +++ b/CHANGELOG_PENDING.md @@ -30,3 +30,5 @@ program](https://hackerone.com/tendermint). - [mempool] \#2835 Remove local int64 counter from being stored in every tx ### BUG FIXES: + +- [rpc] \#2808 RPC validators calls IncrementAccum if necessary diff --git a/state/state.go b/state/state.go index 0dbd718d..451d6544 100644 --- a/state/state.go +++ b/state/state.go @@ -64,7 +64,8 @@ type State struct { // Validators are persisted to the database separately every time they change, // so we can query for historical validator sets. // Note that if s.LastBlockHeight causes a valset change, - // we set s.LastHeightValidatorsChanged = s.LastBlockHeight + 1 + // we set s.LastHeightValidatorsChanged = s.LastBlockHeight + 1 + 1 + // Extra +1 due to nextValSet delay. NextValidators *types.ValidatorSet Validators *types.ValidatorSet LastValidators *types.ValidatorSet diff --git a/state/state_test.go b/state/state_test.go index 17293f6f..50346025 100644 --- a/state/state_test.go +++ b/state/state_test.go @@ -3,9 +3,10 @@ package state import ( "bytes" "fmt" - "github.com/tendermint/tendermint/libs/log" "testing" + "github.com/tendermint/tendermint/libs/log" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" abci "github.com/tendermint/tendermint/abci/types" @@ -260,6 +261,27 @@ func TestOneValidatorChangesSaveLoad(t *testing.T) { } } +func TestStoreLoadValidatorsIncrementsAccum(t *testing.T) { + const valSetSize = 2 + tearDown, stateDB, state := setupTestCase(t) + state.Validators = genValSet(valSetSize) + state.NextValidators = state.Validators.CopyIncrementAccum(1) + SaveState(stateDB, state) + defer tearDown(t) + + nextHeight := state.LastBlockHeight + 1 + + v0, err := LoadValidators(stateDB, nextHeight) + assert.Nil(t, err) + acc0 := v0.Validators[0].Accum + + v1, err := LoadValidators(stateDB, nextHeight+1) + assert.Nil(t, err) + acc1 := v1.Validators[0].Accum + + assert.NotEqual(t, acc1, acc0, "expected Accum value to change between heights") +} + // TestValidatorChangesSaveLoad tests saving and loading a validator set with // changes. func TestManyValidatorChangesSaveLoad(t *testing.T) { diff --git a/state/store.go b/state/store.go index 086dcdf5..0effe38a 100644 --- a/state/store.go +++ b/state/store.go @@ -89,7 +89,9 @@ func saveState(db dbm.DB, state State, key []byte) { nextHeight := state.LastBlockHeight + 1 // If first block, save validators for block 1. if nextHeight == 1 { - lastHeightVoteChanged := int64(1) // Due to Tendermint validator set changes being delayed 1 block. + // This extra logic due to Tendermint validator set changes being delayed 1 block. + // It may get overwritten due to InitChain validator updates. + lastHeightVoteChanged := int64(1) saveValidatorsInfo(db, nextHeight, lastHeightVoteChanged, state.Validators) } // Save next validators. @@ -191,12 +193,14 @@ func LoadValidators(db dbm.DB, height int64) (*types.ValidatorSet, error) { ), ) } + valInfo2.ValidatorSet.IncrementAccum(int(height - valInfo.LastHeightChanged)) // mutate valInfo = valInfo2 } return valInfo.ValidatorSet, nil } +// CONTRACT: Returned ValidatorsInfo can be mutated. func loadValidatorsInfo(db dbm.DB, height int64) *ValidatorsInfo { buf := db.Get(calcValidatorsKey(height)) if len(buf) == 0 { @@ -215,18 +219,22 @@ func loadValidatorsInfo(db dbm.DB, height int64) *ValidatorsInfo { return v } -// saveValidatorsInfo persists the validator set for the next block to disk. +// saveValidatorsInfo persists the validator set. +// `height` is the effective height for which the validator is responsible for signing. // It should be called from s.Save(), right before the state itself is persisted. // If the validator set did not change after processing the latest block, // only the last height for which the validators changed is persisted. -func saveValidatorsInfo(db dbm.DB, nextHeight, changeHeight int64, valSet *types.ValidatorSet) { - valInfo := &ValidatorsInfo{ - LastHeightChanged: changeHeight, +func saveValidatorsInfo(db dbm.DB, height, lastHeightChanged int64, valSet *types.ValidatorSet) { + if lastHeightChanged > height { + panic("LastHeightChanged cannot be greater than ValidatorsInfo height") } - if changeHeight == nextHeight { + valInfo := &ValidatorsInfo{ + LastHeightChanged: lastHeightChanged, + } + if lastHeightChanged == height { valInfo.ValidatorSet = valSet } - db.Set(calcValidatorsKey(nextHeight), valInfo.Bytes()) + db.Set(calcValidatorsKey(height), valInfo.Bytes()) } //----------------------------------------------------------------------------- diff --git a/types/validator_set.go b/types/validator_set.go index ab030d1b..f5e57077 100644 --- a/types/validator_set.go +++ b/types/validator_set.go @@ -62,7 +62,11 @@ func (vals *ValidatorSet) CopyIncrementAccum(times int) *ValidatorSet { // IncrementAccum increments accum of each validator and updates the // proposer. Panics if validator set is empty. +// `times` must be positive. func (vals *ValidatorSet) IncrementAccum(times int) { + if times <= 0 { + panic("Cannot call IncrementAccum with non-positive times") + } // Add VotingPower * times to each validator and order into heap. validatorsHeap := cmn.NewHeap() diff --git a/types/validator_set_test.go b/types/validator_set_test.go index aad9d85a..81124637 100644 --- a/types/validator_set_test.go +++ b/types/validator_set_test.go @@ -86,6 +86,19 @@ func TestCopy(t *testing.T) { } } +// Test that IncrementAccum requires positive times. +func TestIncrementAccumPositiveTimes(t *testing.T) { + vset := NewValidatorSet([]*Validator{ + newValidator([]byte("foo"), 1000), + newValidator([]byte("bar"), 300), + newValidator([]byte("baz"), 330), + }) + + assert.Panics(t, func() { vset.IncrementAccum(-1) }) + assert.Panics(t, func() { vset.IncrementAccum(0) }) + vset.IncrementAccum(1) +} + func BenchmarkValidatorSetCopy(b *testing.B) { b.StopTimer() vset := NewValidatorSet([]*Validator{}) @@ -239,7 +252,7 @@ func TestProposerSelection3(t *testing.T) { mod := (cmn.RandInt() % 5) + 1 if cmn.RandInt()%mod > 0 { // sometimes its up to 5 - times = cmn.RandInt() % 5 + times = (cmn.RandInt() % 4) + 1 } vset.IncrementAccum(times) From 72f86b5192a513d6eefd21d80f446f2771394717 Mon Sep 17 00:00:00 2001 From: Joe Bowman Date: Wed, 21 Nov 2018 06:45:20 +0000 Subject: [PATCH 102/267] [pv] add ability to use ipc validator (#2866) Ref #2827 (I have since seen #2847 which is a fix for the same issue; this PR has tests and docs too ;) ) --- CHANGELOG_PENDING.md | 3 +- docs/architecture/adr-008-priv-validator.md | 20 ++-- node/node.go | 65 +++++++++---- node/node_test.go | 102 ++++++++++++++++++++ 4 files changed, 162 insertions(+), 28 deletions(-) diff --git a/CHANGELOG_PENDING.md b/CHANGELOG_PENDING.md index 4361afac..919569d4 100644 --- a/CHANGELOG_PENDING.md +++ b/CHANGELOG_PENDING.md @@ -2,8 +2,6 @@ ## v0.26.4 -*TBD* - Special thanks to external contributors on this release: Friendly reminder, we have a [bug bounty @@ -28,6 +26,7 @@ program](https://hackerone.com/tendermint). - [config] \#2877 add blocktime_iota to the config.toml (@ackratos) - [mempool] \#2855 add txs from Update to cache - [mempool] \#2835 Remove local int64 counter from being stored in every tx +- [node] \#2827 add ability to instantiate IPCVal (@joe-bowman) ### BUG FIXES: diff --git a/docs/architecture/adr-008-priv-validator.md b/docs/architecture/adr-008-priv-validator.md index 94e882af..a8499465 100644 --- a/docs/architecture/adr-008-priv-validator.md +++ b/docs/architecture/adr-008-priv-validator.md @@ -5,14 +5,17 @@ implementations: - FilePV uses an unencrypted private key in a "priv_validator.json" file - no configuration required (just `tendermint init`). -- SocketPV uses a socket to send signing requests to another process - user is - responsible for starting that process themselves. +- TCPVal and IPCVal use TCP and Unix sockets respectively to send signing requests + to another process - the user is responsible for starting that process themselves. -The SocketPV address can be provided via flags at the command line - doing so -will cause Tendermint to ignore any "priv_validator.json" file and to listen on -the given address for incoming connections from an external priv_validator -process. It will halt any operation until at least one external process -succesfully connected. +Both TCPVal and IPCVal addresses can be provided via flags at the command line +or in the configuration file; TCPVal addresses must be of the form +`tcp://:` and IPCVal addresses `unix:///path/to/file.sock` - +doing so will cause Tendermint to ignore any private validator files. + +TCPVal will listen on the given address for incoming connections from an external +private validator process. It will halt any operation until at least one external +process successfully connected. The external priv_validator process will dial the address to connect to Tendermint, and then Tendermint will send requests on the ensuing connection to @@ -21,6 +24,9 @@ but the Tendermint process makes all requests. In a later stage we're going to support multiple validators for fault tolerance. To prevent double signing they need to be synced, which is deferred to an external solution (see #1185). +Conversely, IPCVal will make an outbound connection to an existing socket opened +by the external validator process. + In addition, Tendermint will provide implementations that can be run in that external process. These include: diff --git a/node/node.go b/node/node.go index bfd8d02e..a15dc248 100644 --- a/node/node.go +++ b/node/node.go @@ -148,6 +148,44 @@ type Node struct { prometheusSrv *http.Server } +func createExternalPrivValidator(listenAddr string, logger log.Logger) (types.PrivValidator, error) { + protocol, address := cmn.ProtocolAndAddress(listenAddr) + + var pvsc types.PrivValidator + + switch (protocol) { + case "unix": + pvsc = privval.NewIPCVal( + logger.With("module", "privval"), + address, + ) + + case "tcp": + // TODO: persist this key so external signer + // can actually authenticate us + pvsc = privval.NewTCPVal( + logger.With("module", "privval"), + listenAddr, + ed25519.GenPrivKey(), + ) + + default: + return nil, fmt.Errorf( + "Error creating private validator: expected either tcp or unix "+ + "protocols, got %s", + protocol, + ) + } + + pvServ, _ := pvsc.(cmn.Service) + if err := pvServ.Start(); err != nil { + return nil, fmt.Errorf("Error starting private validator client: %v", err) + } + + return pvsc, nil + +} + // NewNode returns a new, ready to go, Tendermint Node. func NewNode(config *cfg.Config, privValidator types.PrivValidator, @@ -220,25 +258,13 @@ func NewNode(config *cfg.Config, ) } - // If an address is provided, listen on the socket for a - // connection from an external signing process. if config.PrivValidatorListenAddr != "" { - var ( - // TODO: persist this key so external signer - // can actually authenticate us - privKey = ed25519.GenPrivKey() - pvsc = privval.NewTCPVal( - logger.With("module", "privval"), - config.PrivValidatorListenAddr, - privKey, - ) - ) - - if err := pvsc.Start(); err != nil { - return nil, fmt.Errorf("Error starting private validator client: %v", err) + // If an address is provided, listen on the socket for a + // connection from an external signing process. + privValidator, err = createExternalPrivValidator(config.PrivValidatorListenAddr, logger) + if err != nil { + return nil, err } - - privValidator = pvsc } // Decide whether to fast-sync or not @@ -600,9 +626,10 @@ func (n *Node) OnStop() { } } - if pvsc, ok := n.privValidator.(*privval.TCPVal); ok { + + if pvsc, ok := n.privValidator.(cmn.Service); ok { if err := pvsc.Stop(); err != nil { - n.Logger.Error("Error stopping priv validator socket client", "err", err) + n.Logger.Error("Error stopping priv validator client", "err", err) } } diff --git a/node/node_test.go b/node/node_test.go index 3a33e6bb..180f5d9c 100644 --- a/node/node_test.go +++ b/node/node_test.go @@ -7,19 +7,24 @@ import ( "syscall" "testing" "time" + "net" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" "github.com/tendermint/tendermint/abci/example/kvstore" "github.com/tendermint/tendermint/libs/log" "github.com/tendermint/tendermint/p2p" sm "github.com/tendermint/tendermint/state" "github.com/tendermint/tendermint/version" + "github.com/tendermint/tendermint/crypto/ed25519" cfg "github.com/tendermint/tendermint/config" + cmn "github.com/tendermint/tendermint/libs/common" "github.com/tendermint/tendermint/types" tmtime "github.com/tendermint/tendermint/types/time" + "github.com/tendermint/tendermint/privval" ) func TestNodeStartStop(t *testing.T) { @@ -113,3 +118,100 @@ func TestNodeSetAppVersion(t *testing.T) { // check version is set in node info assert.Equal(t, n.nodeInfo.(p2p.DefaultNodeInfo).ProtocolVersion.App, appVersion) } + +func TestNodeSetPrivValTCP(t *testing.T) { + addr := "tcp://" + testFreeAddr(t) + + rs := privval.NewRemoteSigner( + log.TestingLogger(), + cmn.RandStr(12), + addr, + types.NewMockPV(), + ed25519.GenPrivKey(), + ) + privval.RemoteSignerConnDeadline(5 * time.Millisecond)(rs) + privval.RemoteSignerConnRetries(1e6)(rs) + + config := cfg.ResetTestRoot("node_priv_val_tcp_test") + config.BaseConfig.PrivValidatorListenAddr = addr + + // kick off remote signer routine, and then start TM. + go func(rs *privval.RemoteSigner) { + rs.Start() + defer rs.Stop() + time.Sleep(100 * time.Millisecond) + }(rs) + + n, err := DefaultNewNode(config, log.TestingLogger()) + + assert.NoError(t, err, "expected no err on DefaultNewNode") + + assert.IsType(t, &privval.TCPVal{}, n.PrivValidator()) +} + +func TestNodeSetPrivValTCPNoPrefix(t *testing.T) { + addr := "tcp://" + testFreeAddr(t) + + rs := privval.NewRemoteSigner( + log.TestingLogger(), + cmn.RandStr(12), + addr, + types.NewMockPV(), + ed25519.GenPrivKey(), + ) + privval.RemoteSignerConnDeadline(5 * time.Millisecond)(rs) + privval.RemoteSignerConnRetries(1e6)(rs) + config := cfg.ResetTestRoot("node_priv_val_tcp_test") + config.BaseConfig.PrivValidatorListenAddr = addr + + // kick off remote signer routine, and then start TM. + go func(rs *privval.RemoteSigner) { + rs.Start() + defer rs.Stop() + time.Sleep(100 * time.Millisecond) + }(rs) + + n, err := DefaultNewNode(config, log.TestingLogger()) + + assert.NoError(t, err, "expected no err on DefaultNewNode") + assert.IsType(t, &privval.TCPVal{}, n.PrivValidator()) +} + +func TestNodeSetPrivValIPC(t *testing.T) { + tmpfile := "/tmp/kms." + cmn.RandStr(6) + ".sock" + defer os.Remove(tmpfile) // clean up + addr := "unix://" + tmpfile + + rs := privval.NewIPCRemoteSigner( + log.TestingLogger(), + cmn.RandStr(12), + tmpfile, + types.NewMockPV(), + ) + + privval.IPCRemoteSignerConnDeadline(3 * time.Second)(rs) + + // kick off remote signer routine, and then start TM. + go func(rs *privval.IPCRemoteSigner) { + rs.Start() + defer rs.Stop() + time.Sleep(500 * time.Millisecond) + }(rs) + + config := cfg.ResetTestRoot("node_priv_val_tcp_test") + config.BaseConfig.PrivValidatorListenAddr = addr + n, err := DefaultNewNode(config, log.TestingLogger()) + + assert.NoError(t, err, "expected no err on DefaultNewNode") + assert.IsType(t, &privval.IPCVal{}, n.PrivValidator()) +} + + +// testFreeAddr claims a free port so we don't block on listener being ready. +func testFreeAddr(t *testing.T) string { + ln, err := net.Listen("tcp", "127.0.0.1:0") + require.NoError(t, err) + defer ln.Close() + + return fmt.Sprintf("127.0.0.1:%d", ln.Addr().(*net.TCPAddr).Port) +} From b487feba4269660b18252695df9e2e79ca635724 Mon Sep 17 00:00:00 2001 From: Anton Kaliaev Date: Wed, 21 Nov 2018 21:24:13 +0400 Subject: [PATCH 103/267] node: refactor privValidator ext client code & tests (#2895) * update ConsensusState#OnStop comment * consensus: set logger for WAL in tests * refactor privValidator client code and tests follow-up on https://github.com/tendermint/tendermint/pull/2866 --- CHANGELOG_PENDING.md | 2 + consensus/replay_test.go | 17 ++---- consensus/state.go | 3 +- consensus/wal_test.go | 29 +++++------ node/node.go | 86 ++++++++++++++---------------- node/node_test.go | 109 ++++++++++++++++----------------------- privval/ipc_server.go | 1 + 7 files changed, 105 insertions(+), 142 deletions(-) diff --git a/CHANGELOG_PENDING.md b/CHANGELOG_PENDING.md index 919569d4..0aa93afb 100644 --- a/CHANGELOG_PENDING.md +++ b/CHANGELOG_PENDING.md @@ -2,6 +2,8 @@ ## v0.26.4 +*TBD* + Special thanks to external contributors on this release: Friendly reminder, we have a [bug bounty diff --git a/consensus/replay_test.go b/consensus/replay_test.go index 70c4ba33..c261426c 100644 --- a/consensus/replay_test.go +++ b/consensus/replay_test.go @@ -315,28 +315,21 @@ func testHandshakeReplay(t *testing.T, nBlocks int, mode uint) { config := ResetConfig("proxy_test_") walBody, err := WALWithNBlocks(NUM_BLOCKS) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) walFile := tempWALWithData(walBody) config.Consensus.SetWalFile(walFile) privVal := privval.LoadFilePV(config.PrivValidatorFile()) wal, err := NewWAL(walFile) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) wal.SetLogger(log.TestingLogger()) - if err := wal.Start(); err != nil { - t.Fatal(err) - } + err = wal.Start() + require.NoError(t, err) defer wal.Stop() chain, commits, err := makeBlockchainFromWAL(wal) - if err != nil { - t.Fatalf(err.Error()) - } + require.NoError(t, err) stateDB, state, store := stateAndStore(config, privVal.GetPubKey(), kvstore.ProtocolVersion) store.chain = chain diff --git a/consensus/state.go b/consensus/state.go index 0f7b56bc..4b7aec2a 100644 --- a/consensus/state.go +++ b/consensus/state.go @@ -324,10 +324,11 @@ func (cs *ConsensusState) startRoutines(maxSteps int) { go cs.receiveRoutine(maxSteps) } -// OnStop implements cmn.Service. It stops all routines and waits for the WAL to finish. +// OnStop implements cmn.Service. func (cs *ConsensusState) OnStop() { cs.evsw.Stop() cs.timeoutTicker.Stop() + // WAL is stopped in receiveRoutine. } // Wait waits for the the main routine to return. diff --git a/consensus/wal_test.go b/consensus/wal_test.go index c45f6ace..c056f201 100644 --- a/consensus/wal_test.go +++ b/consensus/wal_test.go @@ -7,13 +7,13 @@ import ( "io/ioutil" "os" "path/filepath" - // "sync" "testing" "time" "github.com/tendermint/tendermint/consensus/types" "github.com/tendermint/tendermint/libs/autofile" + "github.com/tendermint/tendermint/libs/log" tmtypes "github.com/tendermint/tendermint/types" tmtime "github.com/tendermint/tendermint/types/time" @@ -23,29 +23,27 @@ import ( func TestWALTruncate(t *testing.T) { walDir, err := ioutil.TempDir("", "wal") - if err != nil { - panic(fmt.Errorf("failed to create temp WAL file: %v", err)) - } + require.NoError(t, err) defer os.RemoveAll(walDir) walFile := filepath.Join(walDir, "wal") //this magic number 4K can truncate the content when RotateFile. defaultHeadSizeLimit(10M) is hard to simulate. //this magic number 1 * time.Millisecond make RotateFile check frequently. defaultGroupCheckDuration(5s) is hard to simulate. - wal, err := NewWAL(walFile, autofile.GroupHeadSizeLimit(4096), autofile.GroupCheckDuration(1*time.Millisecond)) - if err != nil { - t.Fatal(err) - } - - wal.Start() + wal, err := NewWAL(walFile, + autofile.GroupHeadSizeLimit(4096), + autofile.GroupCheckDuration(1*time.Millisecond), + ) + require.NoError(t, err) + wal.SetLogger(log.TestingLogger()) + err = wal.Start() + require.NoError(t, err) defer wal.Stop() //60 block's size nearly 70K, greater than group's headBuf size(4096 * 10), when headBuf is full, truncate content will Flush to the file. //at this time, RotateFile is called, truncate content exist in each file. err = WALGenerateNBlocks(wal.Group(), 60) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) time.Sleep(1 * time.Millisecond) //wait groupCheckDuration, make sure RotateFile run @@ -99,9 +97,8 @@ func TestWALSearchForEndHeight(t *testing.T) { walFile := tempWALWithData(walBody) wal, err := NewWAL(walFile) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) + wal.SetLogger(log.TestingLogger()) h := int64(3) gr, found, err := wal.SearchForEndHeight(h, &WALSearchOptions{}) diff --git a/node/node.go b/node/node.go index a15dc248..8e41dfd1 100644 --- a/node/node.go +++ b/node/node.go @@ -3,7 +3,6 @@ package node import ( "bytes" "context" - "errors" "fmt" "net" "net/http" @@ -11,11 +10,12 @@ import ( "strings" "time" + "github.com/pkg/errors" "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus/promhttp" "github.com/rs/cors" - "github.com/tendermint/go-amino" + amino "github.com/tendermint/go-amino" abci "github.com/tendermint/tendermint/abci/types" bc "github.com/tendermint/tendermint/blockchain" cfg "github.com/tendermint/tendermint/config" @@ -148,44 +148,6 @@ type Node struct { prometheusSrv *http.Server } -func createExternalPrivValidator(listenAddr string, logger log.Logger) (types.PrivValidator, error) { - protocol, address := cmn.ProtocolAndAddress(listenAddr) - - var pvsc types.PrivValidator - - switch (protocol) { - case "unix": - pvsc = privval.NewIPCVal( - logger.With("module", "privval"), - address, - ) - - case "tcp": - // TODO: persist this key so external signer - // can actually authenticate us - pvsc = privval.NewTCPVal( - logger.With("module", "privval"), - listenAddr, - ed25519.GenPrivKey(), - ) - - default: - return nil, fmt.Errorf( - "Error creating private validator: expected either tcp or unix "+ - "protocols, got %s", - protocol, - ) - } - - pvServ, _ := pvsc.(cmn.Service) - if err := pvServ.Start(); err != nil { - return nil, fmt.Errorf("Error starting private validator client: %v", err) - } - - return pvsc, nil - -} - // NewNode returns a new, ready to go, Tendermint Node. func NewNode(config *cfg.Config, privValidator types.PrivValidator, @@ -259,11 +221,12 @@ func NewNode(config *cfg.Config, } if config.PrivValidatorListenAddr != "" { - // If an address is provided, listen on the socket for a - // connection from an external signing process. - privValidator, err = createExternalPrivValidator(config.PrivValidatorListenAddr, logger) + // If an address is provided, listen on the socket for a connection from an + // external signing process. + // FIXME: we should start services inside OnStart + privValidator, err = createAndStartPrivValidatorSocketClient(config.PrivValidatorListenAddr, logger) if err != nil { - return nil, err + return nil, errors.Wrap(err, "Error with private validator socket client") } } @@ -626,11 +589,8 @@ func (n *Node) OnStop() { } } - if pvsc, ok := n.privValidator.(cmn.Service); ok { - if err := pvsc.Stop(); err != nil { - n.Logger.Error("Error stopping priv validator client", "err", err) - } + pvsc.Stop() } if n.prometheusSrv != nil { @@ -884,6 +844,36 @@ func saveGenesisDoc(db dbm.DB, genDoc *types.GenesisDoc) { db.SetSync(genesisDocKey, bytes) } +func createAndStartPrivValidatorSocketClient( + listenAddr string, + logger log.Logger, +) (types.PrivValidator, error) { + var pvsc types.PrivValidator + + protocol, address := cmn.ProtocolAndAddress(listenAddr) + switch protocol { + case "unix": + pvsc = privval.NewIPCVal(logger.With("module", "privval"), address) + case "tcp": + // TODO: persist this key so external signer + // can actually authenticate us + pvsc = privval.NewTCPVal(logger.With("module", "privval"), listenAddr, ed25519.GenPrivKey()) + default: + return nil, fmt.Errorf( + "Wrong listen address: expected either 'tcp' or 'unix' protocols, got %s", + protocol, + ) + } + + if pvsc, ok := pvsc.(cmn.Service); ok { + if err := pvsc.Start(); err != nil { + return nil, errors.Wrap(err, "failed to start") + } + } + + return pvsc, nil +} + // splitAndTrimEmpty slices s into all subslices separated by sep and returns a // slice of the string s with all leading and trailing Unicode code points // contained in cutset removed. If sep is empty, SplitAndTrim splits after each diff --git a/node/node_test.go b/node/node_test.go index 180f5d9c..4d0019ea 100644 --- a/node/node_test.go +++ b/node/node_test.go @@ -3,28 +3,28 @@ package node import ( "context" "fmt" + "net" "os" "syscall" "testing" "time" - "net" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/tendermint/tendermint/abci/example/kvstore" + "github.com/tendermint/tendermint/crypto/ed25519" + cmn "github.com/tendermint/tendermint/libs/common" "github.com/tendermint/tendermint/libs/log" "github.com/tendermint/tendermint/p2p" sm "github.com/tendermint/tendermint/state" "github.com/tendermint/tendermint/version" - "github.com/tendermint/tendermint/crypto/ed25519" cfg "github.com/tendermint/tendermint/config" - cmn "github.com/tendermint/tendermint/libs/common" "github.com/tendermint/tendermint/types" - tmtime "github.com/tendermint/tendermint/types/time" "github.com/tendermint/tendermint/privval" + tmtime "github.com/tendermint/tendermint/types/time" ) func TestNodeStartStop(t *testing.T) { @@ -32,17 +32,16 @@ func TestNodeStartStop(t *testing.T) { // create & start node n, err := DefaultNewNode(config, log.TestingLogger()) - assert.NoError(t, err, "expected no err on DefaultNewNode") - err1 := n.Start() - if err1 != nil { - t.Error(err1) - } + require.NoError(t, err) + err = n.Start() + require.NoError(t, err) + t.Logf("Started node %v", n.sw.NodeInfo()) // wait for the node to produce a block blockCh := make(chan interface{}) err = n.EventBus().Subscribe(context.Background(), "node_test", types.EventQueryNewBlock, blockCh) - assert.NoError(t, err) + require.NoError(t, err) select { case <-blockCh: case <-time.After(10 * time.Second): @@ -94,7 +93,7 @@ func TestNodeDelayedStop(t *testing.T) { // create & start node n, err := DefaultNewNode(config, log.TestingLogger()) n.GenesisDoc().GenesisTime = now.Add(5 * time.Second) - assert.NoError(t, err) + require.NoError(t, err) n.Start() startTime := tmtime.Now() @@ -106,7 +105,7 @@ func TestNodeSetAppVersion(t *testing.T) { // create & start node n, err := DefaultNewNode(config, log.TestingLogger()) - assert.NoError(t, err, "expected no err on DefaultNewNode") + require.NoError(t, err) // default config uses the kvstore app var appVersion version.Protocol = kvstore.ProtocolVersion @@ -122,91 +121,71 @@ func TestNodeSetAppVersion(t *testing.T) { func TestNodeSetPrivValTCP(t *testing.T) { addr := "tcp://" + testFreeAddr(t) + config := cfg.ResetTestRoot("node_priv_val_tcp_test") + config.BaseConfig.PrivValidatorListenAddr = addr + rs := privval.NewRemoteSigner( log.TestingLogger(), - cmn.RandStr(12), + config.ChainID(), addr, types.NewMockPV(), ed25519.GenPrivKey(), ) privval.RemoteSignerConnDeadline(5 * time.Millisecond)(rs) - privval.RemoteSignerConnRetries(1e6)(rs) - - config := cfg.ResetTestRoot("node_priv_val_tcp_test") - config.BaseConfig.PrivValidatorListenAddr = addr - - // kick off remote signer routine, and then start TM. - go func(rs *privval.RemoteSigner) { - rs.Start() - defer rs.Stop() - time.Sleep(100 * time.Millisecond) - }(rs) + go func() { + err := rs.Start() + if err != nil { + panic(err) + } + }() + defer rs.Stop() n, err := DefaultNewNode(config, log.TestingLogger()) - - assert.NoError(t, err, "expected no err on DefaultNewNode") - + require.NoError(t, err) assert.IsType(t, &privval.TCPVal{}, n.PrivValidator()) } -func TestNodeSetPrivValTCPNoPrefix(t *testing.T) { - addr := "tcp://" + testFreeAddr(t) +// address without a protocol must result in error +func TestPrivValidatorListenAddrNoProtocol(t *testing.T) { + addrNoPrefix := testFreeAddr(t) - rs := privval.NewRemoteSigner( - log.TestingLogger(), - cmn.RandStr(12), - addr, - types.NewMockPV(), - ed25519.GenPrivKey(), - ) - privval.RemoteSignerConnDeadline(5 * time.Millisecond)(rs) - privval.RemoteSignerConnRetries(1e6)(rs) config := cfg.ResetTestRoot("node_priv_val_tcp_test") - config.BaseConfig.PrivValidatorListenAddr = addr + config.BaseConfig.PrivValidatorListenAddr = addrNoPrefix - // kick off remote signer routine, and then start TM. - go func(rs *privval.RemoteSigner) { - rs.Start() - defer rs.Stop() - time.Sleep(100 * time.Millisecond) - }(rs) - - n, err := DefaultNewNode(config, log.TestingLogger()) - - assert.NoError(t, err, "expected no err on DefaultNewNode") - assert.IsType(t, &privval.TCPVal{}, n.PrivValidator()) + _, err := DefaultNewNode(config, log.TestingLogger()) + assert.Error(t, err) } func TestNodeSetPrivValIPC(t *testing.T) { tmpfile := "/tmp/kms." + cmn.RandStr(6) + ".sock" defer os.Remove(tmpfile) // clean up - addr := "unix://" + tmpfile + + config := cfg.ResetTestRoot("node_priv_val_tcp_test") + config.BaseConfig.PrivValidatorListenAddr = "unix://" + tmpfile rs := privval.NewIPCRemoteSigner( log.TestingLogger(), - cmn.RandStr(12), + config.ChainID(), tmpfile, types.NewMockPV(), ) - privval.IPCRemoteSignerConnDeadline(3 * time.Second)(rs) - // kick off remote signer routine, and then start TM. - go func(rs *privval.IPCRemoteSigner) { - rs.Start() - defer rs.Stop() - time.Sleep(500 * time.Millisecond) - }(rs) + done := make(chan struct{}) + go func() { + defer close(done) + n, err := DefaultNewNode(config, log.TestingLogger()) + require.NoError(t, err) + assert.IsType(t, &privval.IPCVal{}, n.PrivValidator()) + }() - config := cfg.ResetTestRoot("node_priv_val_tcp_test") - config.BaseConfig.PrivValidatorListenAddr = addr - n, err := DefaultNewNode(config, log.TestingLogger()) + err := rs.Start() + require.NoError(t, err) + defer rs.Stop() - assert.NoError(t, err, "expected no err on DefaultNewNode") - assert.IsType(t, &privval.IPCVal{}, n.PrivValidator()) + <-done } - // testFreeAddr claims a free port so we don't block on listener being ready. func testFreeAddr(t *testing.T) string { ln, err := net.Listen("tcp", "127.0.0.1:0") diff --git a/privval/ipc_server.go b/privval/ipc_server.go index d3907cbd..ba957477 100644 --- a/privval/ipc_server.go +++ b/privval/ipc_server.go @@ -69,6 +69,7 @@ func (rs *IPCRemoteSigner) OnStart() error { for { conn, err := rs.listener.AcceptUnix() if err != nil { + rs.Logger.Error("AcceptUnix", "err", err) return } go rs.handleConnection(conn) From b12488b5f12894efb6ee454ea4649b2fbf9e4b51 Mon Sep 17 00:00:00 2001 From: Tomas Tauber Date: Mon, 26 Nov 2018 12:33:40 +0800 Subject: [PATCH 104/267] Handling integer IDs in JSON-RPC requests -- fixes #2366 (#2811) * Fixed accepting integer IDs in requests for Tendermint RPC server (#2366) * added a wrapper interface `jsonrpcid` that represents both string and int IDs in JSON-RPC requests/responses + custom JSON unmarshallers * changed client-side code in RPC that uses it * added extra tests for integer IDs * updated CHANGELOG_PENDING, as suggested by PR instructions * addressed PR comments * added table driven tests for request type marshalling/unmarshalling * expanded handler test to check IDs * changed pending changelog note * changed json rpc request/response unmarshalling to use empty interfaces and type switches on ID * some cleanup --- CHANGELOG_PENDING.md | 1 + rpc/core/events.go | 3 +- rpc/lib/client/http_client.go | 2 +- rpc/lib/client/ws_client.go | 4 +- rpc/lib/server/handlers.go | 20 +++--- rpc/lib/server/handlers_test.go | 76 ++++++++++++++++++---- rpc/lib/server/http_server.go | 2 +- rpc/lib/types/types.go | 111 ++++++++++++++++++++++++++++---- rpc/lib/types/types_test.go | 57 ++++++++++++---- tools/tm-bench/transacter.go | 2 +- 10 files changed, 223 insertions(+), 55 deletions(-) diff --git a/CHANGELOG_PENDING.md b/CHANGELOG_PENDING.md index 0aa93afb..5bf7d8a0 100644 --- a/CHANGELOG_PENDING.md +++ b/CHANGELOG_PENDING.md @@ -33,3 +33,4 @@ program](https://hackerone.com/tendermint). ### BUG FIXES: - [rpc] \#2808 RPC validators calls IncrementAccum if necessary +- [rpc] \#2811 Allow integer IDs in JSON-RPC requests diff --git a/rpc/core/events.go b/rpc/core/events.go index 6f679e33..e7456f35 100644 --- a/rpc/core/events.go +++ b/rpc/core/events.go @@ -2,6 +2,7 @@ package core import ( "context" + "fmt" "github.com/pkg/errors" @@ -104,7 +105,7 @@ func Subscribe(wsCtx rpctypes.WSRPCContext, query string) (*ctypes.ResultSubscri go func() { for event := range ch { tmResult := &ctypes.ResultEvent{query, event.(tmtypes.TMEventData)} - wsCtx.TryWriteRPCResponse(rpctypes.NewRPCSuccessResponse(wsCtx.Codec(), wsCtx.Request.ID+"#event", tmResult)) + wsCtx.TryWriteRPCResponse(rpctypes.NewRPCSuccessResponse(wsCtx.Codec(), rpctypes.JSONRPCStringID(fmt.Sprintf("%v#event", wsCtx.Request.ID)), tmResult)) } }() diff --git a/rpc/lib/client/http_client.go b/rpc/lib/client/http_client.go index bd440289..21be5fe0 100644 --- a/rpc/lib/client/http_client.go +++ b/rpc/lib/client/http_client.go @@ -99,7 +99,7 @@ func NewJSONRPCClient(remote string) *JSONRPCClient { } func (c *JSONRPCClient) Call(method string, params map[string]interface{}, result interface{}) (interface{}, error) { - request, err := types.MapToRequest(c.cdc, "jsonrpc-client", method, params) + request, err := types.MapToRequest(c.cdc, types.JSONRPCStringID("jsonrpc-client"), method, params) if err != nil { return nil, err } diff --git a/rpc/lib/client/ws_client.go b/rpc/lib/client/ws_client.go index 6da996e2..b183118d 100644 --- a/rpc/lib/client/ws_client.go +++ b/rpc/lib/client/ws_client.go @@ -214,7 +214,7 @@ func (c *WSClient) Send(ctx context.Context, request types.RPCRequest) error { // Call the given method. See Send description. func (c *WSClient) Call(ctx context.Context, method string, params map[string]interface{}) error { - request, err := types.MapToRequest(c.cdc, "ws-client", method, params) + request, err := types.MapToRequest(c.cdc, types.JSONRPCStringID("ws-client"), method, params) if err != nil { return err } @@ -224,7 +224,7 @@ func (c *WSClient) Call(ctx context.Context, method string, params map[string]in // CallWithArrayParams the given method with params in a form of array. See // Send description. func (c *WSClient) CallWithArrayParams(ctx context.Context, method string, params []interface{}) error { - request, err := types.ArrayToRequest(c.cdc, "ws-client", method, params) + request, err := types.ArrayToRequest(c.cdc, types.JSONRPCStringID("ws-client"), method, params) if err != nil { return err } diff --git a/rpc/lib/server/handlers.go b/rpc/lib/server/handlers.go index 3ec5f81e..edab88fe 100644 --- a/rpc/lib/server/handlers.go +++ b/rpc/lib/server/handlers.go @@ -103,7 +103,7 @@ func makeJSONRPCHandler(funcMap map[string]*RPCFunc, cdc *amino.Codec, logger lo return func(w http.ResponseWriter, r *http.Request) { b, err := ioutil.ReadAll(r.Body) if err != nil { - WriteRPCResponseHTTP(w, types.RPCInvalidRequestError("", errors.Wrap(err, "Error reading request body"))) + WriteRPCResponseHTTP(w, types.RPCInvalidRequestError(types.JSONRPCStringID(""), errors.Wrap(err, "Error reading request body"))) return } // if its an empty request (like from a browser), @@ -116,12 +116,12 @@ func makeJSONRPCHandler(funcMap map[string]*RPCFunc, cdc *amino.Codec, logger lo var request types.RPCRequest err = json.Unmarshal(b, &request) if err != nil { - WriteRPCResponseHTTP(w, types.RPCParseError("", errors.Wrap(err, "Error unmarshalling request"))) + WriteRPCResponseHTTP(w, types.RPCParseError(types.JSONRPCStringID(""), errors.Wrap(err, "Error unmarshalling request"))) return } // A Notification is a Request object without an "id" member. // The Server MUST NOT reply to a Notification, including those that are within a batch request. - if request.ID == "" { + if request.ID == types.JSONRPCStringID("") { logger.Debug("HTTPJSONRPC received a notification, skipping... (please send a non-empty ID if you want to call a method)") return } @@ -255,7 +255,7 @@ func makeHTTPHandler(rpcFunc *RPCFunc, cdc *amino.Codec, logger log.Logger) func // Exception for websocket endpoints if rpcFunc.ws { return func(w http.ResponseWriter, r *http.Request) { - WriteRPCResponseHTTP(w, types.RPCMethodNotFoundError("")) + WriteRPCResponseHTTP(w, types.RPCMethodNotFoundError(types.JSONRPCStringID(""))) } } // All other endpoints @@ -263,17 +263,17 @@ func makeHTTPHandler(rpcFunc *RPCFunc, cdc *amino.Codec, logger log.Logger) func logger.Debug("HTTP HANDLER", "req", r) args, err := httpParamsToArgs(rpcFunc, cdc, r) if err != nil { - WriteRPCResponseHTTP(w, types.RPCInvalidParamsError("", errors.Wrap(err, "Error converting http params to arguments"))) + WriteRPCResponseHTTP(w, types.RPCInvalidParamsError(types.JSONRPCStringID(""), errors.Wrap(err, "Error converting http params to arguments"))) return } returns := rpcFunc.f.Call(args) logger.Info("HTTPRestRPC", "method", r.URL.Path, "args", args, "returns", returns) result, err := unreflectResult(returns) if err != nil { - WriteRPCResponseHTTP(w, types.RPCInternalError("", err)) + WriteRPCResponseHTTP(w, types.RPCInternalError(types.JSONRPCStringID(""), err)) return } - WriteRPCResponseHTTP(w, types.NewRPCSuccessResponse(cdc, "", result)) + WriteRPCResponseHTTP(w, types.NewRPCSuccessResponse(cdc, types.JSONRPCStringID(""), result)) } } @@ -580,7 +580,7 @@ func (wsc *wsConnection) readRoutine() { err = fmt.Errorf("WSJSONRPC: %v", r) } wsc.Logger.Error("Panic in WSJSONRPC handler", "err", err, "stack", string(debug.Stack())) - wsc.WriteRPCResponse(types.RPCInternalError("unknown", err)) + wsc.WriteRPCResponse(types.RPCInternalError(types.JSONRPCStringID("unknown"), err)) go wsc.readRoutine() } else { wsc.baseConn.Close() // nolint: errcheck @@ -615,13 +615,13 @@ func (wsc *wsConnection) readRoutine() { var request types.RPCRequest err = json.Unmarshal(in, &request) if err != nil { - wsc.WriteRPCResponse(types.RPCParseError("", errors.Wrap(err, "Error unmarshaling request"))) + wsc.WriteRPCResponse(types.RPCParseError(types.JSONRPCStringID(""), errors.Wrap(err, "Error unmarshaling request"))) continue } // A Notification is a Request object without an "id" member. // The Server MUST NOT reply to a Notification, including those that are within a batch request. - if request.ID == "" { + if request.ID == types.JSONRPCStringID("") { wsc.Logger.Debug("WSJSONRPC received a notification, skipping... (please send a non-empty ID if you want to call a method)") continue } diff --git a/rpc/lib/server/handlers_test.go b/rpc/lib/server/handlers_test.go index 6004959a..b1d3c788 100644 --- a/rpc/lib/server/handlers_test.go +++ b/rpc/lib/server/handlers_test.go @@ -47,21 +47,22 @@ func statusOK(code int) bool { return code >= 200 && code <= 299 } func TestRPCParams(t *testing.T) { mux := testMux() tests := []struct { - payload string - wantErr string + payload string + wantErr string + expectedId interface{} }{ // bad - {`{"jsonrpc": "2.0", "id": "0"}`, "Method not found"}, - {`{"jsonrpc": "2.0", "method": "y", "id": "0"}`, "Method not found"}, - {`{"method": "c", "id": "0", "params": a}`, "invalid character"}, - {`{"method": "c", "id": "0", "params": ["a"]}`, "got 1"}, - {`{"method": "c", "id": "0", "params": ["a", "b"]}`, "invalid character"}, - {`{"method": "c", "id": "0", "params": [1, 1]}`, "of type string"}, + {`{"jsonrpc": "2.0", "id": "0"}`, "Method not found", types.JSONRPCStringID("0")}, + {`{"jsonrpc": "2.0", "method": "y", "id": "0"}`, "Method not found", types.JSONRPCStringID("0")}, + {`{"method": "c", "id": "0", "params": a}`, "invalid character", types.JSONRPCStringID("")}, // id not captured in JSON parsing failures + {`{"method": "c", "id": "0", "params": ["a"]}`, "got 1", types.JSONRPCStringID("0")}, + {`{"method": "c", "id": "0", "params": ["a", "b"]}`, "invalid character", types.JSONRPCStringID("0")}, + {`{"method": "c", "id": "0", "params": [1, 1]}`, "of type string", types.JSONRPCStringID("0")}, // good - {`{"jsonrpc": "2.0", "method": "c", "id": "0", "params": null}`, ""}, - {`{"method": "c", "id": "0", "params": {}}`, ""}, - {`{"method": "c", "id": "0", "params": ["a", "10"]}`, ""}, + {`{"jsonrpc": "2.0", "method": "c", "id": "0", "params": null}`, "", types.JSONRPCStringID("0")}, + {`{"method": "c", "id": "0", "params": {}}`, "", types.JSONRPCStringID("0")}, + {`{"method": "c", "id": "0", "params": ["a", "10"]}`, "", types.JSONRPCStringID("0")}, } for i, tt := range tests { @@ -80,7 +81,7 @@ func TestRPCParams(t *testing.T) { recv := new(types.RPCResponse) assert.Nil(t, json.Unmarshal(blob, recv), "#%d: expecting successful parsing of an RPCResponse:\nblob: %s", i, blob) assert.NotEqual(t, recv, new(types.RPCResponse), "#%d: not expecting a blank RPCResponse", i) - + assert.Equal(t, tt.expectedId, recv.ID, "#%d: expected ID not matched in RPCResponse", i) if tt.wantErr == "" { assert.Nil(t, recv.Error, "#%d: not expecting an error", i) } else { @@ -91,9 +92,56 @@ func TestRPCParams(t *testing.T) { } } +func TestJSONRPCID(t *testing.T) { + mux := testMux() + tests := []struct { + payload string + wantErr bool + expectedId interface{} + }{ + // good id + {`{"jsonrpc": "2.0", "method": "c", "id": "0", "params": ["a", "10"]}`, false, types.JSONRPCStringID("0")}, + {`{"jsonrpc": "2.0", "method": "c", "id": "abc", "params": ["a", "10"]}`, false, types.JSONRPCStringID("abc")}, + {`{"jsonrpc": "2.0", "method": "c", "id": 0, "params": ["a", "10"]}`, false, types.JSONRPCIntID(0)}, + {`{"jsonrpc": "2.0", "method": "c", "id": 1, "params": ["a", "10"]}`, false, types.JSONRPCIntID(1)}, + {`{"jsonrpc": "2.0", "method": "c", "id": 1.3, "params": ["a", "10"]}`, false, types.JSONRPCIntID(1)}, + {`{"jsonrpc": "2.0", "method": "c", "id": -1, "params": ["a", "10"]}`, false, types.JSONRPCIntID(-1)}, + {`{"jsonrpc": "2.0", "method": "c", "id": null, "params": ["a", "10"]}`, false, nil}, + + // bad id + {`{"jsonrpc": "2.0", "method": "c", "id": {}, "params": ["a", "10"]}`, true, nil}, + {`{"jsonrpc": "2.0", "method": "c", "id": [], "params": ["a", "10"]}`, true, nil}, + } + + for i, tt := range tests { + req, _ := http.NewRequest("POST", "http://localhost/", strings.NewReader(tt.payload)) + rec := httptest.NewRecorder() + mux.ServeHTTP(rec, req) + res := rec.Result() + // Always expecting back a JSONRPCResponse + assert.True(t, statusOK(res.StatusCode), "#%d: should always return 2XX", i) + blob, err := ioutil.ReadAll(res.Body) + if err != nil { + t.Errorf("#%d: err reading body: %v", i, err) + continue + } + + recv := new(types.RPCResponse) + err = json.Unmarshal(blob, recv) + assert.Nil(t, err, "#%d: expecting successful parsing of an RPCResponse:\nblob: %s", i, blob) + if !tt.wantErr { + assert.NotEqual(t, recv, new(types.RPCResponse), "#%d: not expecting a blank RPCResponse", i) + assert.Equal(t, tt.expectedId, recv.ID, "#%d: expected ID not matched in RPCResponse", i) + assert.Nil(t, recv.Error, "#%d: not expecting an error", i) + } else { + assert.True(t, recv.Error.Code < 0, "#%d: not expecting a positive JSONRPC code", i) + } + } +} + func TestRPCNotification(t *testing.T) { mux := testMux() - body := strings.NewReader(`{"jsonrpc": "2.0"}`) + body := strings.NewReader(`{"jsonrpc": "2.0", "id": ""}`) req, _ := http.NewRequest("POST", "http://localhost/", body) rec := httptest.NewRecorder() mux.ServeHTTP(rec, req) @@ -134,7 +182,7 @@ func TestWebsocketManagerHandler(t *testing.T) { } // check basic functionality works - req, err := types.MapToRequest(amino.NewCodec(), "TestWebsocketManager", "c", map[string]interface{}{"s": "a", "i": 10}) + req, err := types.MapToRequest(amino.NewCodec(), types.JSONRPCStringID("TestWebsocketManager"), "c", map[string]interface{}{"s": "a", "i": 10}) require.NoError(t, err) err = c.WriteJSON(req) require.NoError(t, err) diff --git a/rpc/lib/server/http_server.go b/rpc/lib/server/http_server.go index 1fd422a9..9db69b6f 100644 --- a/rpc/lib/server/http_server.go +++ b/rpc/lib/server/http_server.go @@ -132,7 +132,7 @@ func RecoverAndLogHandler(handler http.Handler, logger log.Logger) http.Handler "Panic in RPC HTTP handler", "err", e, "stack", string(debug.Stack()), ) - WriteRPCResponseHTTPError(rww, http.StatusInternalServerError, types.RPCInternalError("", e.(error))) + WriteRPCResponseHTTPError(rww, http.StatusInternalServerError, types.RPCInternalError(types.JSONRPCStringID(""), e.(error))) } } diff --git a/rpc/lib/types/types.go b/rpc/lib/types/types.go index fe9a9253..e0753a03 100644 --- a/rpc/lib/types/types.go +++ b/rpc/lib/types/types.go @@ -4,6 +4,7 @@ import ( "context" "encoding/json" "fmt" + "reflect" "strings" "github.com/pkg/errors" @@ -13,17 +14,75 @@ import ( tmpubsub "github.com/tendermint/tendermint/libs/pubsub" ) +// a wrapper to emulate a sum type: jsonrpcid = string | int +// TODO: refactor when Go 2.0 arrives https://github.com/golang/go/issues/19412 +type jsonrpcid interface { + isJSONRPCID() +} + +// JSONRPCStringID a wrapper for JSON-RPC string IDs +type JSONRPCStringID string + +func (JSONRPCStringID) isJSONRPCID() {} + +// JSONRPCIntID a wrapper for JSON-RPC integer IDs +type JSONRPCIntID int + +func (JSONRPCIntID) isJSONRPCID() {} + +func idFromInterface(idInterface interface{}) (jsonrpcid, error) { + switch id := idInterface.(type) { + case string: + return JSONRPCStringID(id), nil + case float64: + // json.Unmarshal uses float64 for all numbers + // (https://golang.org/pkg/encoding/json/#Unmarshal), + // but the JSONRPC2.0 spec says the id SHOULD NOT contain + // decimals - so we truncate the decimals here. + return JSONRPCIntID(int(id)), nil + default: + typ := reflect.TypeOf(id) + return nil, fmt.Errorf("JSON-RPC ID (%v) is of unknown type (%v)", id, typ) + } +} + //---------------------------------------- // REQUEST type RPCRequest struct { JSONRPC string `json:"jsonrpc"` - ID string `json:"id"` + ID jsonrpcid `json:"id"` Method string `json:"method"` Params json.RawMessage `json:"params"` // must be map[string]interface{} or []interface{} } -func NewRPCRequest(id string, method string, params json.RawMessage) RPCRequest { +// UnmarshalJSON custom JSON unmarshalling due to jsonrpcid being string or int +func (request *RPCRequest) UnmarshalJSON(data []byte) error { + unsafeReq := &struct { + JSONRPC string `json:"jsonrpc"` + ID interface{} `json:"id"` + Method string `json:"method"` + Params json.RawMessage `json:"params"` // must be map[string]interface{} or []interface{} + }{} + err := json.Unmarshal(data, &unsafeReq) + if err != nil { + return err + } + request.JSONRPC = unsafeReq.JSONRPC + request.Method = unsafeReq.Method + request.Params = unsafeReq.Params + if unsafeReq.ID == nil { + return nil + } + id, err := idFromInterface(unsafeReq.ID) + if err != nil { + return err + } + request.ID = id + return nil +} + +func NewRPCRequest(id jsonrpcid, method string, params json.RawMessage) RPCRequest { return RPCRequest{ JSONRPC: "2.0", ID: id, @@ -36,7 +95,7 @@ func (req RPCRequest) String() string { return fmt.Sprintf("[%s %s]", req.ID, req.Method) } -func MapToRequest(cdc *amino.Codec, id string, method string, params map[string]interface{}) (RPCRequest, error) { +func MapToRequest(cdc *amino.Codec, id jsonrpcid, method string, params map[string]interface{}) (RPCRequest, error) { var params_ = make(map[string]json.RawMessage, len(params)) for name, value := range params { valueJSON, err := cdc.MarshalJSON(value) @@ -53,7 +112,7 @@ func MapToRequest(cdc *amino.Codec, id string, method string, params map[string] return request, nil } -func ArrayToRequest(cdc *amino.Codec, id string, method string, params []interface{}) (RPCRequest, error) { +func ArrayToRequest(cdc *amino.Codec, id jsonrpcid, method string, params []interface{}) (RPCRequest, error) { var params_ = make([]json.RawMessage, len(params)) for i, value := range params { valueJSON, err := cdc.MarshalJSON(value) @@ -89,12 +148,38 @@ func (err RPCError) Error() string { type RPCResponse struct { JSONRPC string `json:"jsonrpc"` - ID string `json:"id"` + ID jsonrpcid `json:"id"` Result json.RawMessage `json:"result,omitempty"` Error *RPCError `json:"error,omitempty"` } -func NewRPCSuccessResponse(cdc *amino.Codec, id string, res interface{}) RPCResponse { +// UnmarshalJSON custom JSON unmarshalling due to jsonrpcid being string or int +func (response *RPCResponse) UnmarshalJSON(data []byte) error { + unsafeResp := &struct { + JSONRPC string `json:"jsonrpc"` + ID interface{} `json:"id"` + Result json.RawMessage `json:"result,omitempty"` + Error *RPCError `json:"error,omitempty"` + }{} + err := json.Unmarshal(data, &unsafeResp) + if err != nil { + return err + } + response.JSONRPC = unsafeResp.JSONRPC + response.Error = unsafeResp.Error + response.Result = unsafeResp.Result + if unsafeResp.ID == nil { + return nil + } + id, err := idFromInterface(unsafeResp.ID) + if err != nil { + return err + } + response.ID = id + return nil +} + +func NewRPCSuccessResponse(cdc *amino.Codec, id jsonrpcid, res interface{}) RPCResponse { var rawMsg json.RawMessage if res != nil { @@ -109,7 +194,7 @@ func NewRPCSuccessResponse(cdc *amino.Codec, id string, res interface{}) RPCResp return RPCResponse{JSONRPC: "2.0", ID: id, Result: rawMsg} } -func NewRPCErrorResponse(id string, code int, msg string, data string) RPCResponse { +func NewRPCErrorResponse(id jsonrpcid, code int, msg string, data string) RPCResponse { return RPCResponse{ JSONRPC: "2.0", ID: id, @@ -124,27 +209,27 @@ func (resp RPCResponse) String() string { return fmt.Sprintf("[%s %s]", resp.ID, resp.Error) } -func RPCParseError(id string, err error) RPCResponse { +func RPCParseError(id jsonrpcid, err error) RPCResponse { return NewRPCErrorResponse(id, -32700, "Parse error. Invalid JSON", err.Error()) } -func RPCInvalidRequestError(id string, err error) RPCResponse { +func RPCInvalidRequestError(id jsonrpcid, err error) RPCResponse { return NewRPCErrorResponse(id, -32600, "Invalid Request", err.Error()) } -func RPCMethodNotFoundError(id string) RPCResponse { +func RPCMethodNotFoundError(id jsonrpcid) RPCResponse { return NewRPCErrorResponse(id, -32601, "Method not found", "") } -func RPCInvalidParamsError(id string, err error) RPCResponse { +func RPCInvalidParamsError(id jsonrpcid, err error) RPCResponse { return NewRPCErrorResponse(id, -32602, "Invalid params", err.Error()) } -func RPCInternalError(id string, err error) RPCResponse { +func RPCInternalError(id jsonrpcid, err error) RPCResponse { return NewRPCErrorResponse(id, -32603, "Internal error", err.Error()) } -func RPCServerError(id string, err error) RPCResponse { +func RPCServerError(id jsonrpcid, err error) RPCResponse { return NewRPCErrorResponse(id, -32000, "Server error", err.Error()) } diff --git a/rpc/lib/types/types_test.go b/rpc/lib/types/types_test.go index 9dd1b7a1..3e885132 100644 --- a/rpc/lib/types/types_test.go +++ b/rpc/lib/types/types_test.go @@ -15,24 +15,57 @@ type SampleResult struct { Value string } +type responseTest struct { + id jsonrpcid + expected string +} + +var responseTests = []responseTest{ + {JSONRPCStringID("1"), `"1"`}, + {JSONRPCStringID("alphabet"), `"alphabet"`}, + {JSONRPCStringID(""), `""`}, + {JSONRPCStringID("àáâ"), `"àáâ"`}, + {JSONRPCIntID(-1), "-1"}, + {JSONRPCIntID(0), "0"}, + {JSONRPCIntID(1), "1"}, + {JSONRPCIntID(100), "100"}, +} + func TestResponses(t *testing.T) { assert := assert.New(t) cdc := amino.NewCodec() + for _, tt := range responseTests { + jsonid := tt.id + a := NewRPCSuccessResponse(cdc, jsonid, &SampleResult{"hello"}) + b, _ := json.Marshal(a) + s := fmt.Sprintf(`{"jsonrpc":"2.0","id":%v,"result":{"Value":"hello"}}`, tt.expected) + assert.Equal(string(s), string(b)) - a := NewRPCSuccessResponse(cdc, "1", &SampleResult{"hello"}) - b, _ := json.Marshal(a) - s := `{"jsonrpc":"2.0","id":"1","result":{"Value":"hello"}}` - assert.Equal(string(s), string(b)) + d := RPCParseError(jsonid, errors.New("Hello world")) + e, _ := json.Marshal(d) + f := fmt.Sprintf(`{"jsonrpc":"2.0","id":%v,"error":{"code":-32700,"message":"Parse error. Invalid JSON","data":"Hello world"}}`, tt.expected) + assert.Equal(string(f), string(e)) - d := RPCParseError("1", errors.New("Hello world")) - e, _ := json.Marshal(d) - f := `{"jsonrpc":"2.0","id":"1","error":{"code":-32700,"message":"Parse error. Invalid JSON","data":"Hello world"}}` - assert.Equal(string(f), string(e)) + g := RPCMethodNotFoundError(jsonid) + h, _ := json.Marshal(g) + i := fmt.Sprintf(`{"jsonrpc":"2.0","id":%v,"error":{"code":-32601,"message":"Method not found"}}`, tt.expected) + assert.Equal(string(h), string(i)) + } +} - g := RPCMethodNotFoundError("2") - h, _ := json.Marshal(g) - i := `{"jsonrpc":"2.0","id":"2","error":{"code":-32601,"message":"Method not found"}}` - assert.Equal(string(h), string(i)) +func TestUnmarshallResponses(t *testing.T) { + assert := assert.New(t) + cdc := amino.NewCodec() + for _, tt := range responseTests { + response := &RPCResponse{} + err := json.Unmarshal([]byte(fmt.Sprintf(`{"jsonrpc":"2.0","id":%v,"result":{"Value":"hello"}}`, tt.expected)), response) + assert.Nil(err) + a := NewRPCSuccessResponse(cdc, tt.id, &SampleResult{"hello"}) + assert.Equal(*response, a) + } + response := &RPCResponse{} + err := json.Unmarshal([]byte(`{"jsonrpc":"2.0","id":true,"result":{"Value":"hello"}}`), response) + assert.NotNil(err) } func TestRPCError(t *testing.T) { diff --git a/tools/tm-bench/transacter.go b/tools/tm-bench/transacter.go index 36cc761e..c20aa5b5 100644 --- a/tools/tm-bench/transacter.go +++ b/tools/tm-bench/transacter.go @@ -191,7 +191,7 @@ func (t *transacter) sendLoop(connIndex int) { c.SetWriteDeadline(now.Add(sendTimeout)) err = c.WriteJSON(rpctypes.RPCRequest{ JSONRPC: "2.0", - ID: "tm-bench", + ID: rpctypes.JSONRPCStringID("tm-bench"), Method: t.BroadcastTxMethod, Params: rawParamsJSON, }) From 98e442a8de4d5611fe866694aba3ff0e32540f04 Mon Sep 17 00:00:00 2001 From: Anton Kaliaev Date: Mon, 26 Nov 2018 08:34:22 +0400 Subject: [PATCH 105/267] return back initially allowed level if we encounter allowed key (#2889) Fixes #2868 where module=main setting overrides all others --- CHANGELOG_PENDING.md | 1 + libs/cli/flags/log_level_test.go | 2 +- libs/log/filter.go | 45 +++++++++++++++++++++++++++----- 3 files changed, 41 insertions(+), 7 deletions(-) diff --git a/CHANGELOG_PENDING.md b/CHANGELOG_PENDING.md index 5bf7d8a0..ad9a2f61 100644 --- a/CHANGELOG_PENDING.md +++ b/CHANGELOG_PENDING.md @@ -32,5 +32,6 @@ program](https://hackerone.com/tendermint). ### BUG FIXES: +- [log] \#2868 fix module=main setting overriding all others - [rpc] \#2808 RPC validators calls IncrementAccum if necessary - [rpc] \#2811 Allow integer IDs in JSON-RPC requests diff --git a/libs/cli/flags/log_level_test.go b/libs/cli/flags/log_level_test.go index 1503ec28..c4c1707b 100644 --- a/libs/cli/flags/log_level_test.go +++ b/libs/cli/flags/log_level_test.go @@ -51,7 +51,7 @@ func TestParseLogLevel(t *testing.T) { buf.Reset() - logger.With("module", "wire").Debug("Kingpin") + logger.With("module", "mempool").With("module", "wire").Debug("Kingpin") if have := strings.TrimSpace(buf.String()); c.expectedLogLines[0] != have { t.Errorf("\nwant '%s'\nhave '%s'\nlevel '%s'", c.expectedLogLines[0], have, c.lvl) } diff --git a/libs/log/filter.go b/libs/log/filter.go index 768c09b8..b71447ed 100644 --- a/libs/log/filter.go +++ b/libs/log/filter.go @@ -11,9 +11,10 @@ const ( ) type filter struct { - next Logger - allowed level // XOR'd levels for default case - allowedKeyvals map[keyval]level // When key-value match, use this level + next Logger + allowed level // XOR'd levels for default case + initiallyAllowed level // XOR'd levels for initial case + allowedKeyvals map[keyval]level // When key-value match, use this level } type keyval struct { @@ -33,6 +34,7 @@ func NewFilter(next Logger, options ...Option) Logger { for _, option := range options { option(l) } + l.initiallyAllowed = l.allowed return l } @@ -76,14 +78,45 @@ func (l *filter) Error(msg string, keyvals ...interface{}) { // logger = log.NewFilter(logger, log.AllowError(), log.AllowInfoWith("module", "crypto"), log.AllowNoneWith("user", "Sam")) // logger.With("user", "Sam").With("module", "crypto").Info("Hello") # produces "I... Hello module=crypto user=Sam" func (l *filter) With(keyvals ...interface{}) Logger { + keyInAllowedKeyvals := false + for i := len(keyvals) - 2; i >= 0; i -= 2 { for kv, allowed := range l.allowedKeyvals { - if keyvals[i] == kv.key && keyvals[i+1] == kv.value { - return &filter{next: l.next.With(keyvals...), allowed: allowed, allowedKeyvals: l.allowedKeyvals} + if keyvals[i] == kv.key { + keyInAllowedKeyvals = true + // Example: + // logger = log.NewFilter(logger, log.AllowError(), log.AllowInfoWith("module", "crypto")) + // logger.With("module", "crypto") + if keyvals[i+1] == kv.value { + return &filter{ + next: l.next.With(keyvals...), + allowed: allowed, // set the desired level + allowedKeyvals: l.allowedKeyvals, + initiallyAllowed: l.initiallyAllowed, + } + } } } } - return &filter{next: l.next.With(keyvals...), allowed: l.allowed, allowedKeyvals: l.allowedKeyvals} + + // Example: + // logger = log.NewFilter(logger, log.AllowError(), log.AllowInfoWith("module", "crypto")) + // logger.With("module", "main") + if keyInAllowedKeyvals { + return &filter{ + next: l.next.With(keyvals...), + allowed: l.initiallyAllowed, // return back to initially allowed + allowedKeyvals: l.allowedKeyvals, + initiallyAllowed: l.initiallyAllowed, + } + } + + return &filter{ + next: l.next.With(keyvals...), + allowed: l.allowed, // simply continue with the current level + allowedKeyvals: l.allowedKeyvals, + initiallyAllowed: l.initiallyAllowed, + } } //-------------------------------------------------------------------------------- From 56052c0a871a9598decb0e2aaf98fe86276d1d9d Mon Sep 17 00:00:00 2001 From: Ismail Khoffi Date: Mon, 26 Nov 2018 09:24:32 +0100 Subject: [PATCH 106/267] update encoding spec (#2903) Quick fix for #2902 --- docs/spec/blockchain/encoding.md | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/docs/spec/blockchain/encoding.md b/docs/spec/blockchain/encoding.md index f5120cdd..cb506739 100644 --- a/docs/spec/blockchain/encoding.md +++ b/docs/spec/blockchain/encoding.md @@ -59,22 +59,14 @@ You can simply use below table and concatenate Prefix || Length (of raw bytes) | | PubKeySecp256k1 | tendermint/PubKeySecp256k1 | 0xEB5AE987 | 0x21 | | | PrivKeyEd25519 | tendermint/PrivKeyEd25519 | 0xA3288910 | 0x40 | | | PrivKeySecp256k1 | tendermint/PrivKeySecp256k1 | 0xE1B0F79B | 0x20 | | -| SignatureEd25519 | tendermint/SignatureEd25519 | 0x2031EA53 | 0x40 | | -| SignatureSecp256k1 | tendermint/SignatureSecp256k1 | 0x7FC4A495 | variable | +| PubKeyMultisigThreshold | tendermint/PubKeyMultisigThreshold | 0x22C1F7E2 | variable | | -| +### Example -### Examples - -1. For example, the 33-byte (or 0x21-byte in hex) Secp256k1 pubkey +For example, the 33-byte (or 0x21-byte in hex) Secp256k1 pubkey `020BD40F225A57ED383B440CF073BC5539D0341F5767D2BF2D78406D00475A2EE9` would be encoded as - `EB5AE98221020BD40F225A57ED383B440CF073BC5539D0341F5767D2BF2D78406D00475A2EE9` - -2. For example, the variable size Secp256k1 signature (in this particular example 70 or 0x46 bytes) - `304402201CD4B8C764D2FD8AF23ECFE6666CA8A53886D47754D951295D2D311E1FEA33BF02201E0F906BB1CF2C30EAACFFB032A7129358AFF96B9F79B06ACFFB18AC90C2ADD7` - would be encoded as - `16E1FEEA46304402201CD4B8C764D2FD8AF23ECFE6666CA8A53886D47754D951295D2D311E1FEA33BF02201E0F906BB1CF2C30EAACFFB032A7129358AFF96B9F79B06ACFFB18AC90C2ADD7` + `EB5AE98721020BD40F225A57ED383B440CF073BC5539D0341F5767D2BF2D78406D00475A2EE9` ### Addresses From fe3b97fd668ea3b5364089cd17d03b4b34dc88c3 Mon Sep 17 00:00:00 2001 From: JamesRay <66258875@qq.com> Date: Mon, 26 Nov 2018 21:03:08 +0800 Subject: [PATCH 107/267] It's better read from genDoc than from state.validators when appHeight==0 in replay (#2893) * optimize addProposalBlockPart * optimize addProposalBlockPart * if ProposalBlockParts and LockedBlockParts both exist,let LockedBlockParts overwrite ProposalBlockParts. * fix tryAddBlock * broadcast lockedBlockParts in higher priority * when appHeight==0, it's better fetch genDoc than state.validators. --- consensus/replay.go | 7 ++++++- types/part_set.go | 3 +++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/consensus/replay.go b/consensus/replay.go index abc43eb5..c9a779e3 100644 --- a/consensus/replay.go +++ b/consensus/replay.go @@ -276,7 +276,12 @@ func (h *Handshaker) ReplayBlocks( // If appBlockHeight == 0 it means that we are at genesis and hence should send InitChain. if appBlockHeight == 0 { - nextVals := types.TM2PB.ValidatorUpdates(state.NextValidators) // state.Validators would work too. + validators := make([]*types.Validator, len(h.genDoc.Validators)) + for i, val := range h.genDoc.Validators { + validators[i] = types.NewValidator(val.PubKey, val.Power) + } + validatorSet := types.NewValidatorSet(validators) + nextVals := types.TM2PB.ValidatorUpdates(validatorSet) csParams := types.TM2PB.ConsensusParams(h.genDoc.ConsensusParams) req := abci.RequestInitChain{ Time: h.genDoc.GenesisTime, diff --git a/types/part_set.go b/types/part_set.go index af59851c..a040258d 100644 --- a/types/part_set.go +++ b/types/part_set.go @@ -200,6 +200,9 @@ func (ps *PartSet) Total() int { } func (ps *PartSet) AddPart(part *Part) (bool, error) { + if ps == nil { + return false, nil + } ps.mtx.Lock() defer ps.mtx.Unlock() From 47a0669d12c1b257651f48152b763d760439d648 Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Mon, 26 Nov 2018 15:31:11 -0500 Subject: [PATCH 108/267] Fix fast sync stack with wrong block #2457 (#2731) * fix fastsync may stuck by a wrong block * fixes from updates * fixes from review * Align spec with the changes * fmt --- CHANGELOG_PENDING.md | 1 + blockchain/pool.go | 37 ++- blockchain/pool_test.go | 57 +++- blockchain/reactor.go | 15 +- blockchain/reactor_test.go | 340 ++++++++++++++--------- blockchain/store_test.go | 23 +- consensus/mempool_test.go | 8 +- consensus/state_test.go | 2 +- docs/spec/reactors/block_sync/reactor.md | 53 ++-- evidence/reactor.go | 2 +- libs/autofile/autofile_test.go | 2 +- node/node_test.go | 10 +- types/events.go | 2 +- 13 files changed, 360 insertions(+), 192 deletions(-) diff --git a/CHANGELOG_PENDING.md b/CHANGELOG_PENDING.md index ad9a2f61..93dd5c5d 100644 --- a/CHANGELOG_PENDING.md +++ b/CHANGELOG_PENDING.md @@ -32,6 +32,7 @@ program](https://hackerone.com/tendermint). ### BUG FIXES: +- [blockchain] \#2731 Retry both blocks if either is bad to avoid getting stuck during fast sync (@goolAdapter) - [log] \#2868 fix module=main setting overriding all others - [rpc] \#2808 RPC validators calls IncrementAccum if necessary - [rpc] \#2811 Allow integer IDs in JSON-RPC requests diff --git a/blockchain/pool.go b/blockchain/pool.go index c7864a64..e6be3601 100644 --- a/blockchain/pool.go +++ b/blockchain/pool.go @@ -168,9 +168,12 @@ func (pool *BlockPool) IsCaughtUp() bool { return false } - // some conditions to determine if we're caught up - receivedBlockOrTimedOut := (pool.height > 0 || time.Since(pool.startTime) > 5*time.Second) - ourChainIsLongestAmongPeers := pool.maxPeerHeight == 0 || pool.height >= pool.maxPeerHeight + // Some conditions to determine if we're caught up. + // Ensures we've either received a block or waited some amount of time, + // and that we're synced to the highest known height. Note we use maxPeerHeight - 1 + // because to sync block H requires block H+1 to verify the LastCommit. + receivedBlockOrTimedOut := pool.height > 0 || time.Since(pool.startTime) > 5*time.Second + ourChainIsLongestAmongPeers := pool.maxPeerHeight == 0 || pool.height >= (pool.maxPeerHeight-1) isCaughtUp := receivedBlockOrTimedOut && ourChainIsLongestAmongPeers return isCaughtUp } @@ -252,7 +255,8 @@ func (pool *BlockPool) AddBlock(peerID p2p.ID, block *types.Block, blockSize int peer.decrPending(blockSize) } } else { - // Bad peer? + pool.Logger.Info("invalid peer", "peer", peerID, "blockHeight", block.Height) + pool.sendError(errors.New("invalid peer"), peerID) } } @@ -292,7 +296,7 @@ func (pool *BlockPool) RemovePeer(peerID p2p.ID) { func (pool *BlockPool) removePeer(peerID p2p.ID) { for _, requester := range pool.requesters { if requester.getPeerID() == peerID { - requester.redo() + requester.redo(peerID) } } delete(pool.peers, peerID) @@ -326,8 +330,11 @@ func (pool *BlockPool) makeNextRequester() { defer pool.mtx.Unlock() nextHeight := pool.height + pool.requestersLen() + if nextHeight > pool.maxPeerHeight { + return + } + request := newBPRequester(pool, nextHeight) - // request.SetLogger(pool.Logger.With("height", nextHeight)) pool.requesters[nextHeight] = request atomic.AddInt32(&pool.numPending, 1) @@ -453,7 +460,7 @@ type bpRequester struct { pool *BlockPool height int64 gotBlockCh chan struct{} - redoCh chan struct{} + redoCh chan p2p.ID //redo may send multitime, add peerId to identify repeat mtx sync.Mutex peerID p2p.ID @@ -465,7 +472,7 @@ func newBPRequester(pool *BlockPool, height int64) *bpRequester { pool: pool, height: height, gotBlockCh: make(chan struct{}, 1), - redoCh: make(chan struct{}, 1), + redoCh: make(chan p2p.ID, 1), peerID: "", block: nil, @@ -524,9 +531,9 @@ func (bpr *bpRequester) reset() { // Tells bpRequester to pick another peer and try again. // NOTE: Nonblocking, and does nothing if another redo // was already requested. -func (bpr *bpRequester) redo() { +func (bpr *bpRequester) redo(peerId p2p.ID) { select { - case bpr.redoCh <- struct{}{}: + case bpr.redoCh <- peerId: default: } } @@ -565,9 +572,13 @@ OUTER_LOOP: return case <-bpr.Quit(): return - case <-bpr.redoCh: - bpr.reset() - continue OUTER_LOOP + case peerID := <-bpr.redoCh: + if peerID == bpr.peerID { + bpr.reset() + continue OUTER_LOOP + } else { + continue WAIT_LOOP + } case <-bpr.gotBlockCh: // We got a block! // Continue the for-loop and wait til Quit. diff --git a/blockchain/pool_test.go b/blockchain/pool_test.go index 01187bcf..75a03f63 100644 --- a/blockchain/pool_test.go +++ b/blockchain/pool_test.go @@ -16,16 +16,52 @@ func init() { } type testPeer struct { - id p2p.ID - height int64 + id p2p.ID + height int64 + inputChan chan inputData //make sure each peer's data is sequential } -func makePeers(numPeers int, minHeight, maxHeight int64) map[p2p.ID]testPeer { - peers := make(map[p2p.ID]testPeer, numPeers) +type inputData struct { + t *testing.T + pool *BlockPool + request BlockRequest +} + +func (p testPeer) runInputRoutine() { + go func() { + for input := range p.inputChan { + p.simulateInput(input) + } + }() +} + +// Request desired, pretend like we got the block immediately. +func (p testPeer) simulateInput(input inputData) { + block := &types.Block{Header: types.Header{Height: input.request.Height}} + input.pool.AddBlock(input.request.PeerID, block, 123) + input.t.Logf("Added block from peer %v (height: %v)", input.request.PeerID, input.request.Height) +} + +type testPeers map[p2p.ID]testPeer + +func (ps testPeers) start() { + for _, v := range ps { + v.runInputRoutine() + } +} + +func (ps testPeers) stop() { + for _, v := range ps { + close(v.inputChan) + } +} + +func makePeers(numPeers int, minHeight, maxHeight int64) testPeers { + peers := make(testPeers, numPeers) for i := 0; i < numPeers; i++ { peerID := p2p.ID(cmn.RandStr(12)) height := minHeight + cmn.RandInt63n(maxHeight-minHeight) - peers[peerID] = testPeer{peerID, height} + peers[peerID] = testPeer{peerID, height, make(chan inputData, 10)} } return peers } @@ -45,6 +81,9 @@ func TestBasic(t *testing.T) { defer pool.Stop() + peers.start() + defer peers.stop() + // Introduce each peer. go func() { for _, peer := range peers { @@ -77,12 +116,8 @@ func TestBasic(t *testing.T) { if request.Height == 300 { return // Done! } - // Request desired, pretend like we got the block immediately. - go func() { - block := &types.Block{Header: types.Header{Height: request.Height}} - pool.AddBlock(request.PeerID, block, 123) - t.Logf("Added block from peer %v (height: %v)", request.PeerID, request.Height) - }() + + peers[request.PeerID].inputChan <- inputData{t, pool, request} } } } diff --git a/blockchain/reactor.go b/blockchain/reactor.go index 59318dcc..e62a9e4f 100644 --- a/blockchain/reactor.go +++ b/blockchain/reactor.go @@ -264,8 +264,12 @@ FOR_LOOP: bcR.Logger.Info("Time to switch to consensus reactor!", "height", height) bcR.pool.Stop() - conR := bcR.Switch.Reactor("CONSENSUS").(consensusReactor) - conR.SwitchToConsensus(state, blocksSynced) + conR, ok := bcR.Switch.Reactor("CONSENSUS").(consensusReactor) + if ok { + conR.SwitchToConsensus(state, blocksSynced) + } else { + // should only happen during testing + } break FOR_LOOP } @@ -314,6 +318,13 @@ FOR_LOOP: // still need to clean up the rest. bcR.Switch.StopPeerForError(peer, fmt.Errorf("BlockchainReactor validation error: %v", err)) } + peerID2 := bcR.pool.RedoRequest(second.Height) + peer2 := bcR.Switch.Peers().Get(peerID2) + if peer2 != nil && peer2 != peer { + // NOTE: we've already removed the peer's request, but we + // still need to clean up the rest. + bcR.Switch.StopPeerForError(peer2, fmt.Errorf("BlockchainReactor validation error: %v", err)) + } continue FOR_LOOP } else { bcR.pool.PopRequest() diff --git a/blockchain/reactor_test.go b/blockchain/reactor_test.go index 9b26f919..ac499efa 100644 --- a/blockchain/reactor_test.go +++ b/blockchain/reactor_test.go @@ -1,72 +1,151 @@ package blockchain import ( - "net" + "sort" "testing" + "time" + "github.com/stretchr/testify/assert" + + abci "github.com/tendermint/tendermint/abci/types" + cfg "github.com/tendermint/tendermint/config" cmn "github.com/tendermint/tendermint/libs/common" dbm "github.com/tendermint/tendermint/libs/db" "github.com/tendermint/tendermint/libs/log" - - cfg "github.com/tendermint/tendermint/config" "github.com/tendermint/tendermint/p2p" "github.com/tendermint/tendermint/proxy" sm "github.com/tendermint/tendermint/state" "github.com/tendermint/tendermint/types" + tmtime "github.com/tendermint/tendermint/types/time" ) -func makeStateAndBlockStore(logger log.Logger) (sm.State, *BlockStore) { - config := cfg.ResetTestRoot("blockchain_reactor_test") - // blockDB := dbm.NewDebugDB("blockDB", dbm.NewMemDB()) - // stateDB := dbm.NewDebugDB("stateDB", dbm.NewMemDB()) +var config *cfg.Config + +func randGenesisDoc(numValidators int, randPower bool, minPower int64) (*types.GenesisDoc, []types.PrivValidator) { + validators := make([]types.GenesisValidator, numValidators) + privValidators := make([]types.PrivValidator, numValidators) + for i := 0; i < numValidators; i++ { + val, privVal := types.RandValidator(randPower, minPower) + validators[i] = types.GenesisValidator{ + PubKey: val.PubKey, + Power: val.VotingPower, + } + privValidators[i] = privVal + } + sort.Sort(types.PrivValidatorsByAddress(privValidators)) + + return &types.GenesisDoc{ + GenesisTime: tmtime.Now(), + ChainID: config.ChainID(), + Validators: validators, + }, privValidators +} + +func makeVote(header *types.Header, blockID types.BlockID, valset *types.ValidatorSet, privVal types.PrivValidator) *types.Vote { + addr := privVal.GetAddress() + idx, _ := valset.GetByAddress(addr) + vote := &types.Vote{ + ValidatorAddress: addr, + ValidatorIndex: idx, + Height: header.Height, + Round: 1, + Timestamp: tmtime.Now(), + Type: types.PrecommitType, + BlockID: blockID, + } + + privVal.SignVote(header.ChainID, vote) + + return vote +} + +type BlockchainReactorPair struct { + reactor *BlockchainReactor + app proxy.AppConns +} + +func newBlockchainReactor(logger log.Logger, genDoc *types.GenesisDoc, privVals []types.PrivValidator, maxBlockHeight int64) BlockchainReactorPair { + if len(privVals) != 1 { + panic("only support one validator") + } + + app := &testApp{} + cc := proxy.NewLocalClientCreator(app) + proxyApp := proxy.NewAppConns(cc) + err := proxyApp.Start() + if err != nil { + panic(cmn.ErrorWrap(err, "error start app")) + } + blockDB := dbm.NewMemDB() stateDB := dbm.NewMemDB() blockStore := NewBlockStore(blockDB) - state, err := sm.LoadStateFromDBOrGenesisFile(stateDB, config.GenesisFile()) + + state, err := sm.LoadStateFromDBOrGenesisDoc(stateDB, genDoc) if err != nil { panic(cmn.ErrorWrap(err, "error constructing state from genesis file")) } - return state, blockStore -} -func newBlockchainReactor(logger log.Logger, maxBlockHeight int64) *BlockchainReactor { - state, blockStore := makeStateAndBlockStore(logger) - - // Make the blockchainReactor itself + // Make the BlockchainReactor itself. + // NOTE we have to create and commit the blocks first because + // pool.height is determined from the store. fastSync := true - var nilApp proxy.AppConnConsensus - blockExec := sm.NewBlockExecutor(dbm.NewMemDB(), log.TestingLogger(), nilApp, + blockExec := sm.NewBlockExecutor(dbm.NewMemDB(), log.TestingLogger(), proxyApp.Consensus(), sm.MockMempool{}, sm.MockEvidencePool{}) + // let's add some blocks in + for blockHeight := int64(1); blockHeight <= maxBlockHeight; blockHeight++ { + lastCommit := &types.Commit{} + if blockHeight > 1 { + lastBlockMeta := blockStore.LoadBlockMeta(blockHeight - 1) + lastBlock := blockStore.LoadBlock(blockHeight - 1) + + vote := makeVote(&lastBlock.Header, lastBlockMeta.BlockID, state.Validators, privVals[0]) + lastCommit = &types.Commit{Precommits: []*types.Vote{vote}, BlockID: lastBlockMeta.BlockID} + } + + thisBlock := makeBlock(blockHeight, state, lastCommit) + + thisParts := thisBlock.MakePartSet(types.BlockPartSizeBytes) + blockID := types.BlockID{thisBlock.Hash(), thisParts.Header()} + + state, err = blockExec.ApplyBlock(state, blockID, thisBlock) + if err != nil { + panic(cmn.ErrorWrap(err, "error apply block")) + } + + blockStore.SaveBlock(thisBlock, thisParts, lastCommit) + } + bcReactor := NewBlockchainReactor(state.Copy(), blockExec, blockStore, fastSync) bcReactor.SetLogger(logger.With("module", "blockchain")) - // Next: we need to set a switch in order for peers to be added in - bcReactor.Switch = p2p.NewSwitch(cfg.DefaultP2PConfig(), nil) - - // Lastly: let's add some blocks in - for blockHeight := int64(1); blockHeight <= maxBlockHeight; blockHeight++ { - firstBlock := makeBlock(blockHeight, state) - secondBlock := makeBlock(blockHeight+1, state) - firstParts := firstBlock.MakePartSet(types.BlockPartSizeBytes) - blockStore.SaveBlock(firstBlock, firstParts, secondBlock.LastCommit) - } - - return bcReactor + return BlockchainReactorPair{bcReactor, proxyApp} } func TestNoBlockResponse(t *testing.T) { - maxBlockHeight := int64(20) + config = cfg.ResetTestRoot("blockchain_reactor_test") + genDoc, privVals := randGenesisDoc(1, false, 30) - bcr := newBlockchainReactor(log.TestingLogger(), maxBlockHeight) - bcr.Start() - defer bcr.Stop() + maxBlockHeight := int64(65) - // Add some peers in - peer := newbcrTestPeer(p2p.ID(cmn.RandStr(12))) - bcr.AddPeer(peer) + reactorPairs := make([]BlockchainReactorPair, 2) - chID := byte(0x01) + reactorPairs[0] = newBlockchainReactor(log.TestingLogger(), genDoc, privVals, maxBlockHeight) + reactorPairs[1] = newBlockchainReactor(log.TestingLogger(), genDoc, privVals, 0) + + p2p.MakeConnectedSwitches(config.P2P, 2, func(i int, s *p2p.Switch) *p2p.Switch { + s.AddReactor("BLOCKCHAIN", reactorPairs[i].reactor) + return s + + }, p2p.Connect2Switches) + + defer func() { + for _, r := range reactorPairs { + r.reactor.Stop() + r.app.Stop() + } + }() tests := []struct { height int64 @@ -78,72 +157,100 @@ func TestNoBlockResponse(t *testing.T) { {100, false}, } - // receive a request message from peer, - // wait for our response to be received on the peer - for _, tt := range tests { - reqBlockMsg := &bcBlockRequestMessage{tt.height} - reqBlockBytes := cdc.MustMarshalBinaryBare(reqBlockMsg) - bcr.Receive(chID, peer, reqBlockBytes) - msg := peer.lastBlockchainMessage() + for { + if reactorPairs[1].reactor.pool.IsCaughtUp() { + break + } + time.Sleep(10 * time.Millisecond) + } + + assert.Equal(t, maxBlockHeight, reactorPairs[0].reactor.store.Height()) + + for _, tt := range tests { + block := reactorPairs[1].reactor.store.LoadBlock(tt.height) if tt.existent { - if blockMsg, ok := msg.(*bcBlockResponseMessage); !ok { - t.Fatalf("Expected to receive a block response for height %d", tt.height) - } else if blockMsg.Block.Height != tt.height { - t.Fatalf("Expected response to be for height %d, got %d", tt.height, blockMsg.Block.Height) - } + assert.True(t, block != nil) } else { - if noBlockMsg, ok := msg.(*bcNoBlockResponseMessage); !ok { - t.Fatalf("Expected to receive a no block response for height %d", tt.height) - } else if noBlockMsg.Height != tt.height { - t.Fatalf("Expected response to be for height %d, got %d", tt.height, noBlockMsg.Height) - } + assert.True(t, block == nil) } } } -/* // NOTE: This is too hard to test without // an easy way to add test peer to switch // or without significant refactoring of the module. // Alternatively we could actually dial a TCP conn but // that seems extreme. func TestBadBlockStopsPeer(t *testing.T) { - maxBlockHeight := int64(20) + config = cfg.ResetTestRoot("blockchain_reactor_test") + genDoc, privVals := randGenesisDoc(1, false, 30) - bcr := newBlockchainReactor(log.TestingLogger(), maxBlockHeight) - bcr.Start() - defer bcr.Stop() + maxBlockHeight := int64(148) - // Add some peers in - peer := newbcrTestPeer(p2p.ID(cmn.RandStr(12))) + otherChain := newBlockchainReactor(log.TestingLogger(), genDoc, privVals, maxBlockHeight) + defer func() { + otherChain.reactor.Stop() + otherChain.app.Stop() + }() - // XXX: This doesn't add the peer to anything, - // so it's hard to check that it's later removed - bcr.AddPeer(peer) - assert.True(t, bcr.Switch.Peers().Size() > 0) + reactorPairs := make([]BlockchainReactorPair, 4) - // send a bad block from the peer - // default blocks already dont have commits, so should fail - block := bcr.store.LoadBlock(3) - msg := &bcBlockResponseMessage{Block: block} - peer.Send(BlockchainChannel, struct{ BlockchainMessage }{msg}) + reactorPairs[0] = newBlockchainReactor(log.TestingLogger(), genDoc, privVals, maxBlockHeight) + reactorPairs[1] = newBlockchainReactor(log.TestingLogger(), genDoc, privVals, 0) + reactorPairs[2] = newBlockchainReactor(log.TestingLogger(), genDoc, privVals, 0) + reactorPairs[3] = newBlockchainReactor(log.TestingLogger(), genDoc, privVals, 0) - ticker := time.NewTicker(time.Millisecond * 10) - timer := time.NewTimer(time.Second * 2) -LOOP: - for { - select { - case <-ticker.C: - if bcr.Switch.Peers().Size() == 0 { - break LOOP - } - case <-timer.C: - t.Fatal("Timed out waiting to disconnect peer") + switches := p2p.MakeConnectedSwitches(config.P2P, 4, func(i int, s *p2p.Switch) *p2p.Switch { + s.AddReactor("BLOCKCHAIN", reactorPairs[i].reactor) + return s + + }, p2p.Connect2Switches) + + defer func() { + for _, r := range reactorPairs { + r.reactor.Stop() + r.app.Stop() } + }() + + for { + if reactorPairs[3].reactor.pool.IsCaughtUp() { + break + } + + time.Sleep(1 * time.Second) } + + //at this time, reactors[0-3] is the newest + assert.Equal(t, 3, reactorPairs[1].reactor.Switch.Peers().Size()) + + //mark reactorPairs[3] is an invalid peer + reactorPairs[3].reactor.store = otherChain.reactor.store + + lastReactorPair := newBlockchainReactor(log.TestingLogger(), genDoc, privVals, 0) + reactorPairs = append(reactorPairs, lastReactorPair) + + switches = append(switches, p2p.MakeConnectedSwitches(config.P2P, 1, func(i int, s *p2p.Switch) *p2p.Switch { + s.AddReactor("BLOCKCHAIN", reactorPairs[len(reactorPairs)-1].reactor) + return s + + }, p2p.Connect2Switches)...) + + for i := 0; i < len(reactorPairs)-1; i++ { + p2p.Connect2Switches(switches, i, len(reactorPairs)-1) + } + + for { + if lastReactorPair.reactor.pool.IsCaughtUp() || lastReactorPair.reactor.Switch.Peers().Size() == 0 { + break + } + + time.Sleep(1 * time.Second) + } + + assert.True(t, lastReactorPair.reactor.Switch.Peers().Size() < len(reactorPairs)-1) } -*/ //---------------------------------------------- // utility funcs @@ -155,56 +262,41 @@ func makeTxs(height int64) (txs []types.Tx) { return txs } -func makeBlock(height int64, state sm.State) *types.Block { - block, _ := state.MakeBlock(height, makeTxs(height), new(types.Commit), nil, state.Validators.GetProposer().Address) +func makeBlock(height int64, state sm.State, lastCommit *types.Commit) *types.Block { + block, _ := state.MakeBlock(height, makeTxs(height), lastCommit, nil, state.Validators.GetProposer().Address) return block } -// The Test peer -type bcrTestPeer struct { - cmn.BaseService - id p2p.ID - ch chan interface{} +type testApp struct { + abci.BaseApplication } -var _ p2p.Peer = (*bcrTestPeer)(nil) +var _ abci.Application = (*testApp)(nil) -func newbcrTestPeer(id p2p.ID) *bcrTestPeer { - bcr := &bcrTestPeer{ - id: id, - ch: make(chan interface{}, 2), - } - bcr.BaseService = *cmn.NewBaseService(nil, "bcrTestPeer", bcr) - return bcr +func (app *testApp) Info(req abci.RequestInfo) (resInfo abci.ResponseInfo) { + return abci.ResponseInfo{} } -func (tp *bcrTestPeer) lastBlockchainMessage() interface{} { return <-tp.ch } - -func (tp *bcrTestPeer) TrySend(chID byte, msgBytes []byte) bool { - var msg BlockchainMessage - err := cdc.UnmarshalBinaryBare(msgBytes, &msg) - if err != nil { - panic(cmn.ErrorWrap(err, "Error while trying to parse a BlockchainMessage")) - } - if _, ok := msg.(*bcStatusResponseMessage); ok { - // Discard status response messages since they skew our results - // We only want to deal with: - // + bcBlockResponseMessage - // + bcNoBlockResponseMessage - } else { - tp.ch <- msg - } - return true +func (app *testApp) BeginBlock(req abci.RequestBeginBlock) abci.ResponseBeginBlock { + return abci.ResponseBeginBlock{} } -func (tp *bcrTestPeer) FlushStop() {} -func (tp *bcrTestPeer) Send(chID byte, msgBytes []byte) bool { return tp.TrySend(chID, msgBytes) } -func (tp *bcrTestPeer) NodeInfo() p2p.NodeInfo { return p2p.DefaultNodeInfo{} } -func (tp *bcrTestPeer) Status() p2p.ConnectionStatus { return p2p.ConnectionStatus{} } -func (tp *bcrTestPeer) ID() p2p.ID { return tp.id } -func (tp *bcrTestPeer) IsOutbound() bool { return false } -func (tp *bcrTestPeer) IsPersistent() bool { return true } -func (tp *bcrTestPeer) Get(s string) interface{} { return s } -func (tp *bcrTestPeer) Set(string, interface{}) {} -func (tp *bcrTestPeer) RemoteIP() net.IP { return []byte{127, 0, 0, 1} } -func (tp *bcrTestPeer) OriginalAddr() *p2p.NetAddress { return nil } +func (app *testApp) EndBlock(req abci.RequestEndBlock) abci.ResponseEndBlock { + return abci.ResponseEndBlock{} +} + +func (app *testApp) DeliverTx(tx []byte) abci.ResponseDeliverTx { + return abci.ResponseDeliverTx{Tags: []cmn.KVPair{}} +} + +func (app *testApp) CheckTx(tx []byte) abci.ResponseCheckTx { + return abci.ResponseCheckTx{} +} + +func (app *testApp) Commit() abci.ResponseCommit { + return abci.ResponseCommit{} +} + +func (app *testApp) Query(reqQuery abci.RequestQuery) (resQuery abci.ResponseQuery) { + return +} diff --git a/blockchain/store_test.go b/blockchain/store_test.go index 9c8fdb23..a52039fa 100644 --- a/blockchain/store_test.go +++ b/blockchain/store_test.go @@ -9,13 +9,30 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + cfg "github.com/tendermint/tendermint/config" + cmn "github.com/tendermint/tendermint/libs/common" "github.com/tendermint/tendermint/libs/db" + dbm "github.com/tendermint/tendermint/libs/db" "github.com/tendermint/tendermint/libs/log" + sm "github.com/tendermint/tendermint/state" "github.com/tendermint/tendermint/types" tmtime "github.com/tendermint/tendermint/types/time" ) +func makeStateAndBlockStore(logger log.Logger) (sm.State, *BlockStore) { + config := cfg.ResetTestRoot("blockchain_reactor_test") + // blockDB := dbm.NewDebugDB("blockDB", dbm.NewMemDB()) + // stateDB := dbm.NewDebugDB("stateDB", dbm.NewMemDB()) + blockDB := dbm.NewMemDB() + stateDB := dbm.NewMemDB() + state, err := sm.LoadStateFromDBOrGenesisFile(stateDB, config.GenesisFile()) + if err != nil { + panic(cmn.ErrorWrap(err, "error constructing state from genesis file")) + } + return state, NewBlockStore(blockDB) +} + func TestLoadBlockStoreStateJSON(t *testing.T) { db := db.NewMemDB() @@ -65,7 +82,7 @@ func freshBlockStore() (*BlockStore, db.DB) { var ( state, _ = makeStateAndBlockStore(log.NewTMLogger(new(bytes.Buffer))) - block = makeBlock(1, state) + block = makeBlock(1, state, new(types.Commit)) partSet = block.MakePartSet(2) part1 = partSet.GetPart(0) part2 = partSet.GetPart(1) @@ -88,7 +105,7 @@ func TestBlockStoreSaveLoadBlock(t *testing.T) { } // save a block - block := makeBlock(bs.Height()+1, state) + block := makeBlock(bs.Height()+1, state, new(types.Commit)) validPartSet := block.MakePartSet(2) seenCommit := &types.Commit{Precommits: []*types.Vote{{Height: 10, Timestamp: tmtime.Now()}}} @@ -331,7 +348,7 @@ func TestLoadBlockMeta(t *testing.T) { func TestBlockFetchAtHeight(t *testing.T) { state, bs := makeStateAndBlockStore(log.NewTMLogger(new(bytes.Buffer))) require.Equal(t, bs.Height(), int64(0), "initially the height should be zero") - block := makeBlock(bs.Height()+1, state) + block := makeBlock(bs.Height()+1, state, new(types.Commit)) partSet := block.MakePartSet(2) seenCommit := &types.Commit{Precommits: []*types.Vote{{Height: 10, diff --git a/consensus/mempool_test.go b/consensus/mempool_test.go index 6d36d1e7..49ba74fe 100644 --- a/consensus/mempool_test.go +++ b/consensus/mempool_test.go @@ -72,18 +72,18 @@ func TestMempoolProgressInHigherRound(t *testing.T) { startTestRound(cs, height, round) ensureNewRound(newRoundCh, height, round) // first round at first height - ensureNewEventOnChannel(newBlockCh) // first block gets committed + ensureNewEventOnChannel(newBlockCh) // first block gets committed height = height + 1 // moving to the next height round = 0 ensureNewRound(newRoundCh, height, round) // first round at next height - deliverTxsRange(cs, 0, 1) // we deliver txs, but dont set a proposal so we get the next round + deliverTxsRange(cs, 0, 1) // we deliver txs, but dont set a proposal so we get the next round ensureNewTimeout(timeoutCh, height, round, cs.config.TimeoutPropose.Nanoseconds()) - round = round + 1 // moving to the next round + round = round + 1 // moving to the next round ensureNewRound(newRoundCh, height, round) // wait for the next round - ensureNewEventOnChannel(newBlockCh) // now we can commit the block + ensureNewEventOnChannel(newBlockCh) // now we can commit the block } func deliverTxsRange(cs *ConsensusState, start, end int) { diff --git a/consensus/state_test.go b/consensus/state_test.go index 19dde053..941a99cd 100644 --- a/consensus/state_test.go +++ b/consensus/state_test.go @@ -1043,7 +1043,7 @@ func TestNoHearbeatWhenNotValidator(t *testing.T) { cs.Stop() // if a faulty implementation sends an event, we should wait here a little bit to make sure we don't miss it by prematurely leaving the test method - time.Sleep((proposalHeartbeatIntervalSeconds + 1) * time.Second) + time.Sleep((proposalHeartbeatIntervalSeconds + 1) * time.Second) } // regression for #2518 diff --git a/docs/spec/reactors/block_sync/reactor.md b/docs/spec/reactors/block_sync/reactor.md index 045bbd40..91fd79b0 100644 --- a/docs/spec/reactors/block_sync/reactor.md +++ b/docs/spec/reactors/block_sync/reactor.md @@ -65,24 +65,24 @@ type Requester { mtx Mutex block Block height int64 - 
peerID p2p.ID - redoChannel chan struct{} + 
 peerID p2p.ID + redoChannel chan p2p.ID //redo may send multi-time; peerId is used to identify repeat } ``` -Pool is core data structure that stores last executed block (`height`), assignment of requests to peers (`requesters`), current height for each peer and number of pending requests for each peer (`peers`), maximum peer height, etc. +Pool is a core data structure that stores last executed block (`height`), assignment of requests to peers (`requesters`), current height for each peer and number of pending requests for each peer (`peers`), maximum peer height, etc. ```go type Pool { - mtx Mutex - requesters map[int64]*Requester - height int64 - peers map[p2p.ID]*Peer - maxPeerHeight int64 - numPending int32 - store BlockStore - requestsChannel chan<- BlockRequest - errorsChannel chan<- peerError + mtx Mutex + requesters map[int64]*Requester + height int64 + peers map[p2p.ID]*Peer + maxPeerHeight int64 + numPending int32 + store BlockStore + requestsChannel chan<- BlockRequest + errorsChannel chan<- peerError } ``` @@ -90,11 +90,11 @@ Peer data structure stores for each peer current `height` and number of pending ```go type Peer struct { - id p2p.ID - height int64 - numPending int32 - timeout *time.Timer - didTimeout bool + id p2p.ID + height int64 + numPending int32 + timeout *time.Timer + didTimeout bool } ``` @@ -169,11 +169,11 @@ Requester task is responsible for fetching a single block at position `height`. ```go fetchBlock(height, pool): - while true do + while true do { peerID = nil block = nil peer = pickAvailablePeer(height) - peerId = peer.id + peerID = peer.id enqueue BlockRequest(height, peerID) to pool.requestsChannel redo = false @@ -181,12 +181,15 @@ fetchBlock(height, pool): select { upon receiving Quit message do return - upon receiving message on redoChannel do - mtx.Lock() - pool.numPending++ - redo = true - mtx.UnLock() + upon receiving redo message with id on redoChannel do + if peerID == id { + mtx.Lock() + pool.numPending++ + redo = true + mtx.UnLock() + } } + } pickAvailablePeer(height): selectedPeer = nil @@ -244,7 +247,7 @@ createRequesters(pool): main(pool): create trySyncTicker with interval trySyncIntervalMS create statusUpdateTicker with interval statusUpdateIntervalSeconds - create switchToConsensusTicker with interbal switchToConsensusIntervalSeconds + create switchToConsensusTicker with interval switchToConsensusIntervalSeconds while true do select { diff --git a/evidence/reactor.go b/evidence/reactor.go index 48092fdf..6bb45e68 100644 --- a/evidence/reactor.go +++ b/evidence/reactor.go @@ -163,7 +163,7 @@ func (evR EvidenceReactor) checkSendEvidenceMessage(peer p2p.Peer, ev types.Evid // make sure the peer is up to date evHeight := ev.Height() peerState, ok := peer.Get(types.PeerStateKey).(PeerState) - if !ok { + if !ok { // Peer does not have a state yet. We set it in the consensus reactor, but // when we add peer in Switch, the order we call reactors#AddPeer is // different every time due to us using a map. Sometimes other reactors diff --git a/libs/autofile/autofile_test.go b/libs/autofile/autofile_test.go index 9903f1e6..d9c90309 100644 --- a/libs/autofile/autofile_test.go +++ b/libs/autofile/autofile_test.go @@ -119,4 +119,4 @@ func TestAutoFileSize(t *testing.T) { // Cleanup _ = os.Remove(f.Name()) -} \ No newline at end of file +} diff --git a/node/node_test.go b/node/node_test.go index 4d0019ea..e675eb9a 100644 --- a/node/node_test.go +++ b/node/node_test.go @@ -13,18 +13,16 @@ import ( "github.com/stretchr/testify/require" "github.com/tendermint/tendermint/abci/example/kvstore" + cfg "github.com/tendermint/tendermint/config" "github.com/tendermint/tendermint/crypto/ed25519" cmn "github.com/tendermint/tendermint/libs/common" "github.com/tendermint/tendermint/libs/log" "github.com/tendermint/tendermint/p2p" - sm "github.com/tendermint/tendermint/state" - "github.com/tendermint/tendermint/version" - - cfg "github.com/tendermint/tendermint/config" - "github.com/tendermint/tendermint/types" - "github.com/tendermint/tendermint/privval" + sm "github.com/tendermint/tendermint/state" + "github.com/tendermint/tendermint/types" tmtime "github.com/tendermint/tendermint/types/time" + "github.com/tendermint/tendermint/version" ) func TestNodeStartStop(t *testing.T) { diff --git a/types/events.go b/types/events.go index 2f9dc76e..c33b5978 100644 --- a/types/events.go +++ b/types/events.go @@ -100,7 +100,7 @@ type EventDataCompleteProposal struct { Round int `json:"round"` Step string `json:"step"` - BlockID BlockID `json:"block_id"` + BlockID BlockID `json:"block_id"` } type EventDataVote struct { From 99b9c9bf6061d631d655c06aa43aa770858bacf7 Mon Sep 17 00:00:00 2001 From: Jernej Kos Date: Tue, 27 Nov 2018 04:21:42 +0100 Subject: [PATCH 109/267] types: Emit tags from BeginBlock/EndBlock (#2747) This commit makes both EventNewBlock and EventNewBlockHeader emit tags on the event bus, so subscribers can use them in queries. --- CHANGELOG_PENDING.md | 2 + docs/spec/abci/abci.md | 4 +- state/execution.go | 17 ++++-- state/store.go | 5 +- tools/tm-monitor/monitor/node_test.go | 2 +- types/event_bus.go | 52 ++++++++++++----- types/event_bus_test.go | 84 +++++++++++++++++++++++++++ types/events.go | 7 +++ 8 files changed, 152 insertions(+), 21 deletions(-) diff --git a/CHANGELOG_PENDING.md b/CHANGELOG_PENDING.md index 93dd5c5d..04394bd5 100644 --- a/CHANGELOG_PENDING.md +++ b/CHANGELOG_PENDING.md @@ -23,6 +23,8 @@ program](https://hackerone.com/tendermint). ### FEATURES: +- [types] [\#1571](https://github.com/tendermint/tendermint/issues/1571) Enable subscription to tags emitted from `BeginBlock`/`EndBlock` (@kostko) + ### IMPROVEMENTS: - [config] \#2877 add blocktime_iota to the config.toml (@ackratos) diff --git a/docs/spec/abci/abci.md b/docs/spec/abci/abci.md index f057002e..b9dc744d 100644 --- a/docs/spec/abci/abci.md +++ b/docs/spec/abci/abci.md @@ -45,7 +45,9 @@ include a `Tags` field in their `Response*`. Each tag is key-value pair denoting something about what happened during the methods execution. Tags can be used to index transactions and blocks according to what happened -during their execution. +during their execution. Note that the set of tags returned for a block from +`BeginBlock` and `EndBlock` are merged. In case both methods return the same +tag, only the value defined in `EndBlock` is used. Keys and values in tags must be UTF-8 encoded strings (e.g. "account.owner": "Bob", "balance": "100.0", diff --git a/state/execution.go b/state/execution.go index 9aa714eb..b7c38f41 100644 --- a/state/execution.go +++ b/state/execution.go @@ -226,8 +226,9 @@ func execBlockOnProxyApp( commitInfo, byzVals := getBeginBlockValidatorInfo(block, lastValSet, stateDB) - // Begin block. - _, err := proxyAppConn.BeginBlockSync(abci.RequestBeginBlock{ + // Begin block + var err error + abciResponses.BeginBlock, err = proxyAppConn.BeginBlockSync(abci.RequestBeginBlock{ Hash: block.Hash(), Header: types.TM2PB.Header(&block.Header), LastCommitInfo: commitInfo, @@ -417,8 +418,16 @@ func updateState( // Fire TxEvent for every tx. // NOTE: if Tendermint crashes before commit, some or all of these events may be published again. func fireEvents(logger log.Logger, eventBus types.BlockEventPublisher, block *types.Block, abciResponses *ABCIResponses) { - eventBus.PublishEventNewBlock(types.EventDataNewBlock{block}) - eventBus.PublishEventNewBlockHeader(types.EventDataNewBlockHeader{block.Header}) + eventBus.PublishEventNewBlock(types.EventDataNewBlock{ + Block: block, + ResultBeginBlock: *abciResponses.BeginBlock, + ResultEndBlock: *abciResponses.EndBlock, + }) + eventBus.PublishEventNewBlockHeader(types.EventDataNewBlockHeader{ + Header: block.Header, + ResultBeginBlock: *abciResponses.BeginBlock, + ResultEndBlock: *abciResponses.EndBlock, + }) for i, tx := range block.Data.Txs { eventBus.PublishEventTx(types.EventDataTx{types.TxResult{ diff --git a/state/store.go b/state/store.go index 0effe38a..eb850fa7 100644 --- a/state/store.go +++ b/state/store.go @@ -107,8 +107,9 @@ func saveState(db dbm.DB, state State, key []byte) { // of the various ABCI calls during block processing. // It is persisted to disk for each height before calling Commit. type ABCIResponses struct { - DeliverTx []*abci.ResponseDeliverTx - EndBlock *abci.ResponseEndBlock + DeliverTx []*abci.ResponseDeliverTx + EndBlock *abci.ResponseEndBlock + BeginBlock *abci.ResponseBeginBlock } // NewABCIResponses returns a new ABCIResponses diff --git a/tools/tm-monitor/monitor/node_test.go b/tools/tm-monitor/monitor/node_test.go index 10c2a13f..0048e48f 100644 --- a/tools/tm-monitor/monitor/node_test.go +++ b/tools/tm-monitor/monitor/node_test.go @@ -34,7 +34,7 @@ func TestNodeNewBlockReceived(t *testing.T) { n.SendBlocksTo(blockCh) blockHeader := tmtypes.Header{Height: 5} - emMock.Call("eventCallback", &em.EventMetric{}, tmtypes.EventDataNewBlockHeader{blockHeader}) + emMock.Call("eventCallback", &em.EventMetric{}, tmtypes.EventDataNewBlockHeader{Header: blockHeader}) assert.Equal(t, int64(5), n.Height) assert.Equal(t, blockHeader, <-blockCh) diff --git a/types/event_bus.go b/types/event_bus.go index fbe5ac47..d941e9aa 100644 --- a/types/event_bus.go +++ b/types/event_bus.go @@ -71,12 +71,48 @@ func (b *EventBus) Publish(eventType string, eventData TMEventData) error { return nil } +func (b *EventBus) validateAndStringifyTags(tags []cmn.KVPair, logger log.Logger) map[string]string { + result := make(map[string]string) + for _, tag := range tags { + // basic validation + if len(tag.Key) == 0 { + logger.Debug("Got tag with an empty key (skipping)", "tag", tag) + continue + } + result[string(tag.Key)] = string(tag.Value) + } + return result +} + func (b *EventBus) PublishEventNewBlock(data EventDataNewBlock) error { - return b.Publish(EventNewBlock, data) + // no explicit deadline for publishing events + ctx := context.Background() + + resultTags := append(data.ResultBeginBlock.Tags, data.ResultEndBlock.Tags...) + tags := b.validateAndStringifyTags(resultTags, b.Logger.With("block", data.Block.StringShort())) + + // add predefined tags + logIfTagExists(EventTypeKey, tags, b.Logger) + tags[EventTypeKey] = EventNewBlock + + b.pubsub.PublishWithTags(ctx, data, tmpubsub.NewTagMap(tags)) + return nil } func (b *EventBus) PublishEventNewBlockHeader(data EventDataNewBlockHeader) error { - return b.Publish(EventNewBlockHeader, data) + // no explicit deadline for publishing events + ctx := context.Background() + + resultTags := append(data.ResultBeginBlock.Tags, data.ResultEndBlock.Tags...) + // TODO: Create StringShort method for Header and use it in logger. + tags := b.validateAndStringifyTags(resultTags, b.Logger.With("header", data.Header)) + + // add predefined tags + logIfTagExists(EventTypeKey, tags, b.Logger) + tags[EventTypeKey] = EventNewBlockHeader + + b.pubsub.PublishWithTags(ctx, data, tmpubsub.NewTagMap(tags)) + return nil } func (b *EventBus) PublishEventVote(data EventDataVote) error { @@ -94,17 +130,7 @@ func (b *EventBus) PublishEventTx(data EventDataTx) error { // no explicit deadline for publishing events ctx := context.Background() - tags := make(map[string]string) - - // validate and fill tags from tx result - for _, tag := range data.Result.Tags { - // basic validation - if len(tag.Key) == 0 { - b.Logger.Info("Got tag with an empty key (skipping)", "tag", tag, "tx", data.Tx) - continue - } - tags[string(tag.Key)] = string(tag.Value) - } + tags := b.validateAndStringifyTags(data.Result.Tags, b.Logger.With("tx", data.Tx)) // add predefined tags logIfTagExists(EventTypeKey, tags, b.Logger) diff --git a/types/event_bus_test.go b/types/event_bus_test.go index 4056dacd..0af11ebd 100644 --- a/types/event_bus_test.go +++ b/types/event_bus_test.go @@ -58,6 +58,90 @@ func TestEventBusPublishEventTx(t *testing.T) { } } +func TestEventBusPublishEventNewBlock(t *testing.T) { + eventBus := NewEventBus() + err := eventBus.Start() + require.NoError(t, err) + defer eventBus.Stop() + + block := MakeBlock(0, []Tx{}, nil, []Evidence{}) + resultBeginBlock := abci.ResponseBeginBlock{Tags: []cmn.KVPair{{Key: []byte("baz"), Value: []byte("1")}}} + resultEndBlock := abci.ResponseEndBlock{Tags: []cmn.KVPair{{Key: []byte("foz"), Value: []byte("2")}}} + + txEventsCh := make(chan interface{}) + + // PublishEventNewBlock adds the tm.event tag, so the query below should work + query := "tm.event='NewBlock' AND baz=1 AND foz=2" + err = eventBus.Subscribe(context.Background(), "test", tmquery.MustParse(query), txEventsCh) + require.NoError(t, err) + + done := make(chan struct{}) + go func() { + for e := range txEventsCh { + edt := e.(EventDataNewBlock) + assert.Equal(t, block, edt.Block) + assert.Equal(t, resultBeginBlock, edt.ResultBeginBlock) + assert.Equal(t, resultEndBlock, edt.ResultEndBlock) + close(done) + } + }() + + err = eventBus.PublishEventNewBlock(EventDataNewBlock{ + Block: block, + ResultBeginBlock: resultBeginBlock, + ResultEndBlock: resultEndBlock, + }) + assert.NoError(t, err) + + select { + case <-done: + case <-time.After(1 * time.Second): + t.Fatal("did not receive a block after 1 sec.") + } +} + +func TestEventBusPublishEventNewBlockHeader(t *testing.T) { + eventBus := NewEventBus() + err := eventBus.Start() + require.NoError(t, err) + defer eventBus.Stop() + + block := MakeBlock(0, []Tx{}, nil, []Evidence{}) + resultBeginBlock := abci.ResponseBeginBlock{Tags: []cmn.KVPair{{Key: []byte("baz"), Value: []byte("1")}}} + resultEndBlock := abci.ResponseEndBlock{Tags: []cmn.KVPair{{Key: []byte("foz"), Value: []byte("2")}}} + + txEventsCh := make(chan interface{}) + + // PublishEventNewBlockHeader adds the tm.event tag, so the query below should work + query := "tm.event='NewBlockHeader' AND baz=1 AND foz=2" + err = eventBus.Subscribe(context.Background(), "test", tmquery.MustParse(query), txEventsCh) + require.NoError(t, err) + + done := make(chan struct{}) + go func() { + for e := range txEventsCh { + edt := e.(EventDataNewBlockHeader) + assert.Equal(t, block.Header, edt.Header) + assert.Equal(t, resultBeginBlock, edt.ResultBeginBlock) + assert.Equal(t, resultEndBlock, edt.ResultEndBlock) + close(done) + } + }() + + err = eventBus.PublishEventNewBlockHeader(EventDataNewBlockHeader{ + Header: block.Header, + ResultBeginBlock: resultBeginBlock, + ResultEndBlock: resultEndBlock, + }) + assert.NoError(t, err) + + select { + case <-done: + case <-time.After(1 * time.Second): + t.Fatal("did not receive a block header after 1 sec.") + } +} + func TestEventBusPublish(t *testing.T) { eventBus := NewEventBus() err := eventBus.Start() diff --git a/types/events.go b/types/events.go index c33b5978..b22a1c8b 100644 --- a/types/events.go +++ b/types/events.go @@ -4,6 +4,7 @@ import ( "fmt" amino "github.com/tendermint/go-amino" + abci "github.com/tendermint/tendermint/abci/types" tmpubsub "github.com/tendermint/tendermint/libs/pubsub" tmquery "github.com/tendermint/tendermint/libs/pubsub/query" ) @@ -56,11 +57,17 @@ func RegisterEventDatas(cdc *amino.Codec) { type EventDataNewBlock struct { Block *Block `json:"block"` + + ResultBeginBlock abci.ResponseBeginBlock `json:"result_begin_block"` + ResultEndBlock abci.ResponseEndBlock `json:"result_end_block"` } // light weight event for benchmarking type EventDataNewBlockHeader struct { Header Header `json:"header"` + + ResultBeginBlock abci.ResponseBeginBlock `json:"result_begin_block"` + ResultEndBlock abci.ResponseEndBlock `json:"result_end_block"` } // All txs fire EventDataTx From 9570ac4d3ef6e007d4da30b2686b0f8d890c0d96 Mon Sep 17 00:00:00 2001 From: Anton Kaliaev Date: Tue, 27 Nov 2018 16:47:50 +0400 Subject: [PATCH 110/267] rpc: Fix tx.height range queries (#2899) Modify lookForHeight to return a height only there's a equal operator. Previously, it was returning a height even for range conditions: "height < 10000". Fixes #2759 --- CHANGELOG_PENDING.md | 3 ++- rpc/client/rpc_test.go | 13 ++++++++++--- state/txindex/kv/kv.go | 3 ++- 3 files changed, 14 insertions(+), 5 deletions(-) diff --git a/CHANGELOG_PENDING.md b/CHANGELOG_PENDING.md index 04394bd5..e1215f79 100644 --- a/CHANGELOG_PENDING.md +++ b/CHANGELOG_PENDING.md @@ -37,4 +37,5 @@ program](https://hackerone.com/tendermint). - [blockchain] \#2731 Retry both blocks if either is bad to avoid getting stuck during fast sync (@goolAdapter) - [log] \#2868 fix module=main setting overriding all others - [rpc] \#2808 RPC validators calls IncrementAccum if necessary -- [rpc] \#2811 Allow integer IDs in JSON-RPC requests +- [rpc] \#2759 fix tx.height range queries +- [rpc] \#2811 Allow integer IDs in JSON-RPC requests \ No newline at end of file diff --git a/rpc/client/rpc_test.go b/rpc/client/rpc_test.go index 217971fd..b07b74a3 100644 --- a/rpc/client/rpc_test.go +++ b/rpc/client/rpc_test.go @@ -370,20 +370,27 @@ func TestTxSearch(t *testing.T) { } // query by height - result, err = c.TxSearch(fmt.Sprintf("tx.height >= %d", txHeight), true, 1, 30) + result, err = c.TxSearch(fmt.Sprintf("tx.height=%d", txHeight), true, 1, 30) require.Nil(t, err, "%+v", err) require.Len(t, result.Txs, 1) - // we query for non existing tx + // query for non existing tx result, err = c.TxSearch(fmt.Sprintf("tx.hash='%X'", anotherTxHash), false, 1, 30) require.Nil(t, err, "%+v", err) require.Len(t, result.Txs, 0) - // we query using a tag (see kvstore application) + // query using a tag (see kvstore application) result, err = c.TxSearch("app.creator='Cosmoshi Netowoko'", false, 1, 30) require.Nil(t, err, "%+v", err) if len(result.Txs) == 0 { t.Fatal("expected a lot of transactions") } + + // query using a tag (see kvstore application) and height + result, err = c.TxSearch("app.creator='Cosmoshi Netowoko' AND tx.height<10000", true, 1, 30) + require.Nil(t, err, "%+v", err) + if len(result.Txs) == 0 { + t.Fatal("expected a lot of transactions") + } } } diff --git a/state/txindex/kv/kv.go b/state/txindex/kv/kv.go index 363ab119..1137853c 100644 --- a/state/txindex/kv/kv.go +++ b/state/txindex/kv/kv.go @@ -225,9 +225,10 @@ func lookForHash(conditions []query.Condition) (hash []byte, err error, ok bool) return } +// lookForHeight returns a height if there is an "height=X" condition. func lookForHeight(conditions []query.Condition) (height int64) { for _, c := range conditions { - if c.Tag == types.TxHeightKey { + if c.Tag == types.TxHeightKey && c.Op == query.OpEqual { return c.Operand.(int64) } } From 94e63be922d48869dd670059bcf619865ac8079e Mon Sep 17 00:00:00 2001 From: Anton Kaliaev Date: Tue, 27 Nov 2018 16:53:06 +0400 Subject: [PATCH 111/267] [indexer] order results by index if height is the same (#2900) Fixes #2775 --- CHANGELOG_PENDING.md | 1 + state/txindex/kv/kv.go | 5 ++++- state/txindex/kv/kv_test.go | 17 +++++++++++++++-- 3 files changed, 20 insertions(+), 3 deletions(-) diff --git a/CHANGELOG_PENDING.md b/CHANGELOG_PENDING.md index e1215f79..1bc1910a 100644 --- a/CHANGELOG_PENDING.md +++ b/CHANGELOG_PENDING.md @@ -37,5 +37,6 @@ program](https://hackerone.com/tendermint). - [blockchain] \#2731 Retry both blocks if either is bad to avoid getting stuck during fast sync (@goolAdapter) - [log] \#2868 fix module=main setting overriding all others - [rpc] \#2808 RPC validators calls IncrementAccum if necessary +- [kv indexer] \#2775 order results by index if height is the same - [rpc] \#2759 fix tx.height range queries - [rpc] \#2811 Allow integer IDs in JSON-RPC requests \ No newline at end of file diff --git a/state/txindex/kv/kv.go b/state/txindex/kv/kv.go index 1137853c..6082316b 100644 --- a/state/txindex/kv/kv.go +++ b/state/txindex/kv/kv.go @@ -207,8 +207,11 @@ func (txi *TxIndex) Search(q *query.Query) ([]*types.TxResult, error) { i++ } - // sort by height by default + // sort by height & index by default sort.Slice(results, func(i, j int) bool { + if results[i].Height == results[j].Height { + return results[i].Index < results[j].Index + } return results[i].Height < results[j].Height }) diff --git a/state/txindex/kv/kv_test.go b/state/txindex/kv/kv_test.go index 78a76168..6657e542 100644 --- a/state/txindex/kv/kv_test.go +++ b/state/txindex/kv/kv_test.go @@ -133,6 +133,7 @@ func TestTxSearchMultipleTxs(t *testing.T) { }) txResult.Tx = types.Tx("Bob's account") txResult.Height = 2 + txResult.Index = 1 err := indexer.Index(txResult) require.NoError(t, err) @@ -142,14 +143,26 @@ func TestTxSearchMultipleTxs(t *testing.T) { }) txResult2.Tx = types.Tx("Alice's account") txResult2.Height = 1 + txResult2.Index = 2 + err = indexer.Index(txResult2) require.NoError(t, err) + // indexed third (to test the order of transactions) + txResult3 := txResultWithTags([]cmn.KVPair{ + {Key: []byte("account.number"), Value: []byte("3")}, + }) + txResult3.Tx = types.Tx("Jack's account") + txResult3.Height = 1 + txResult3.Index = 1 + err = indexer.Index(txResult3) + require.NoError(t, err) + results, err := indexer.Search(query.MustParse("account.number >= 1")) assert.NoError(t, err) - require.Len(t, results, 2) - assert.Equal(t, []*types.TxResult{txResult2, txResult}, results) + require.Len(t, results, 3) + assert.Equal(t, []*types.TxResult{txResult3, txResult2, txResult}, results) } func TestIndexAllTags(t *testing.T) { From bef39f33462c0843db0fc82fdc14176e83ef69a1 Mon Sep 17 00:00:00 2001 From: nagarajmanjunath <38091008+nagarajmanjunath@users.noreply.github.com> Date: Tue, 27 Nov 2018 21:07:20 +0800 Subject: [PATCH 112/267] Updated Marshal and unmarshal methods to make compatible with protobuf (#2918) * upadtes in grpc Marshal and unmarshal * update comments --- consensus/types/peer_round_state.go | 28 ++++++++++++++++++++++++++++ consensus/types/round_state.go | 28 ++++++++++++++++++++++++++++ p2p/node_info.go | 28 ++++++++++++++++++++++++++++ types/block.go | 22 ++++++++++++++++++++++ types/block_meta.go | 28 ++++++++++++++++++++++++++++ 5 files changed, 134 insertions(+) diff --git a/consensus/types/peer_round_state.go b/consensus/types/peer_round_state.go index e42395bc..16e29294 100644 --- a/consensus/types/peer_round_state.go +++ b/consensus/types/peer_round_state.go @@ -55,3 +55,31 @@ func (prs PeerRoundState) StringIndented(indent string) string { indent, prs.CatchupCommit, prs.CatchupCommitRound, indent) } + +//----------------------------------------------------------- +// These methods are for Protobuf Compatibility + +// Size returns the size of the amino encoding, in bytes. +func (ps *PeerRoundState) Size() int { + bs, _ := ps.Marshal() + return len(bs) +} + +// Marshal returns the amino encoding. +func (ps *PeerRoundState) Marshal() ([]byte, error) { + return cdc.MarshalBinaryBare(ps) +} + +// MarshalTo calls Marshal and copies to the given buffer. +func (ps *PeerRoundState) MarshalTo(data []byte) (int, error) { + bs, err := ps.Marshal() + if err != nil { + return -1, err + } + return copy(data, bs), nil +} + +// Unmarshal deserializes from amino encoded form. +func (ps *PeerRoundState) Unmarshal(bs []byte) error { + return cdc.UnmarshalBinaryBare(bs, ps) +} diff --git a/consensus/types/round_state.go b/consensus/types/round_state.go index 6359a655..418f73a8 100644 --- a/consensus/types/round_state.go +++ b/consensus/types/round_state.go @@ -201,3 +201,31 @@ func (rs *RoundState) StringShort() string { return fmt.Sprintf(`RoundState{H:%v R:%v S:%v ST:%v}`, rs.Height, rs.Round, rs.Step, rs.StartTime) } + +//----------------------------------------------------------- +// These methods are for Protobuf Compatibility + +// Size returns the size of the amino encoding, in bytes. +func (rs *RoundStateSimple) Size() int { + bs, _ := rs.Marshal() + return len(bs) +} + +// Marshal returns the amino encoding. +func (rs *RoundStateSimple) Marshal() ([]byte, error) { + return cdc.MarshalBinaryBare(rs) +} + +// MarshalTo calls Marshal and copies to the given buffer. +func (rs *RoundStateSimple) MarshalTo(data []byte) (int, error) { + bs, err := rs.Marshal() + if err != nil { + return -1, err + } + return copy(data, bs), nil +} + +// Unmarshal deserializes from amino encoded form. +func (rs *RoundStateSimple) Unmarshal(bs []byte) error { + return cdc.UnmarshalBinaryBare(bs, rs) +} diff --git a/p2p/node_info.go b/p2p/node_info.go index c36d98d9..99daf7c4 100644 --- a/p2p/node_info.go +++ b/p2p/node_info.go @@ -230,3 +230,31 @@ func (info DefaultNodeInfo) NetAddress() *NetAddress { } return netAddr } + +//----------------------------------------------------------- +// These methods are for Protobuf Compatibility + +// Size returns the size of the amino encoding, in bytes. +func (info *DefaultNodeInfo) Size() int { + bs, _ := info.Marshal() + return len(bs) +} + +// Marshal returns the amino encoding. +func (info *DefaultNodeInfo) Marshal() ([]byte, error) { + return cdc.MarshalBinaryBare(info) +} + +// MarshalTo calls Marshal and copies to the given buffer. +func (info *DefaultNodeInfo) MarshalTo(data []byte) (int, error) { + bs, err := info.Marshal() + if err != nil { + return -1, err + } + return copy(data, bs), nil +} + +// Unmarshal deserializes from amino encoded form. +func (info *DefaultNodeInfo) Unmarshal(bs []byte) error { + return cdc.UnmarshalBinaryBare(bs, info) +} diff --git a/types/block.go b/types/block.go index b2cddb5e..15b88d81 100644 --- a/types/block.go +++ b/types/block.go @@ -275,6 +275,28 @@ func (b *Block) StringShort() string { return fmt.Sprintf("Block#%v", b.Hash()) } +//----------------------------------------------------------- +// These methods are for Protobuf Compatibility + +// Marshal returns the amino encoding. +func (b *Block) Marshal() ([]byte, error) { + return cdc.MarshalBinaryBare(b) +} + +// MarshalTo calls Marshal and copies to the given buffer. +func (b *Block) MarshalTo(data []byte) (int, error) { + bs, err := b.Marshal() + if err != nil { + return -1, err + } + return copy(data, bs), nil +} + +// Unmarshal deserializes from amino encoded form. +func (b *Block) Unmarshal(bs []byte) error { + return cdc.UnmarshalBinaryBare(bs, b) +} + //----------------------------------------------------------------------------- // MaxDataBytes returns the maximum size of block's data. diff --git a/types/block_meta.go b/types/block_meta.go index d8926af0..8297446a 100644 --- a/types/block_meta.go +++ b/types/block_meta.go @@ -13,3 +13,31 @@ func NewBlockMeta(block *Block, blockParts *PartSet) *BlockMeta { Header: block.Header, } } + +//----------------------------------------------------------- +// These methods are for Protobuf Compatibility + +// Size returns the size of the amino encoding, in bytes. +func (bm *BlockMeta) Size() int { + bs, _ := bm.Marshal() + return len(bs) +} + +// Marshal returns the amino encoding. +func (bm *BlockMeta) Marshal() ([]byte, error) { + return cdc.MarshalBinaryBare(bm) +} + +// MarshalTo calls Marshal and copies to the given buffer. +func (bm *BlockMeta) MarshalTo(data []byte) (int, error) { + bs, err := bm.Marshal() + if err != nil { + return -1, err + } + return copy(data, bs), nil +} + +// Unmarshal deserializes from amino encoded form. +func (bm *BlockMeta) Unmarshal(bs []byte) error { + return cdc.UnmarshalBinaryBare(bs, bm) +} From 92dc5fc77a4d072d59aee2dbbf06f50d16cfc167 Mon Sep 17 00:00:00 2001 From: Anton Kaliaev Date: Tue, 27 Nov 2018 17:12:28 +0400 Subject: [PATCH 113/267] don't return false positives when searching for a prefix of a tag value (#2919) Fixes #2908 --- CHANGELOG_PENDING.md | 1 + state/txindex/kv/kv.go | 4 ++-- state/txindex/kv/kv_test.go | 2 ++ 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/CHANGELOG_PENDING.md b/CHANGELOG_PENDING.md index 1bc1910a..d6fcc736 100644 --- a/CHANGELOG_PENDING.md +++ b/CHANGELOG_PENDING.md @@ -37,6 +37,7 @@ program](https://hackerone.com/tendermint). - [blockchain] \#2731 Retry both blocks if either is bad to avoid getting stuck during fast sync (@goolAdapter) - [log] \#2868 fix module=main setting overriding all others - [rpc] \#2808 RPC validators calls IncrementAccum if necessary +- [kv indexer] \#2908 don't return false positives when searching for a prefix of a tag value - [kv indexer] \#2775 order results by index if height is the same - [rpc] \#2759 fix tx.height range queries - [rpc] \#2811 Allow integer IDs in JSON-RPC requests \ No newline at end of file diff --git a/state/txindex/kv/kv.go b/state/txindex/kv/kv.go index 6082316b..a5913d5b 100644 --- a/state/txindex/kv/kv.go +++ b/state/txindex/kv/kv.go @@ -412,9 +412,9 @@ LOOP: 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) + key = fmt.Sprintf("%s/%v/%d/", c.Tag, c.Operand, height) } else { - key = fmt.Sprintf("%s/%v", c.Tag, c.Operand) + key = fmt.Sprintf("%s/%v/", c.Tag, c.Operand) } return []byte(key) } diff --git a/state/txindex/kv/kv_test.go b/state/txindex/kv/kv_test.go index 6657e542..7cf16dc5 100644 --- a/state/txindex/kv/kv_test.go +++ b/state/txindex/kv/kv_test.go @@ -73,6 +73,8 @@ func TestTxSearch(t *testing.T) { {"account.number = 1 AND account.owner = 'Ivan'", 1}, // search by exact match (two tags) {"account.number = 1 AND account.owner = 'Vlad'", 0}, + // search using a prefix of the stored value + {"account.owner = 'Iv'", 0}, // search by range {"account.number >= 1 AND account.number <= 5", 1}, // search by range (lower bound) From 1abf34aa9164893c0b837ccdaf13c7ab31fee751 Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Tue, 27 Nov 2018 08:43:21 -0500 Subject: [PATCH 114/267] Prepare v0.26.4 changelog (#2921) * prepare changelog * linkify changelog * changelog and version * update changelog --- CHANGELOG.md | 43 +++++++++++++++++++++++++++++++++++++++++++ CHANGELOG_PENDING.md | 17 +---------------- version/version.go | 2 +- 3 files changed, 45 insertions(+), 17 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e8cb63ba..c506a229 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,48 @@ # Changelog +## v0.26.4 + +*November 27th, 2018* + +Special thanks to external contributors on this release: +ackratos, goolAdapter, james-ray, joe-bowman, kostko, +nagarajmanjunath, tomtau + + +Friendly reminder, we have a [bug bounty +program](https://hackerone.com/tendermint). + +### FEATURES: + +- [rpc] [\#2747](https://github.com/tendermint/tendermint/issues/2747) Enable subscription to tags emitted from `BeginBlock`/`EndBlock` (@kostko) +- [types] [\#2747](https://github.com/tendermint/tendermint/issues/2747) Add `ResultBeginBlock` and `ResultEndBlock` fields to `EventDataNewBlock` + and `EventDataNewBlockHeader` to support subscriptions (@kostko) +- [types] [\#2918](https://github.com/tendermint/tendermint/issues/2918) Add Marshal, MarshalTo, Unmarshal methods to various structs + to support Protobuf compatibility (@nagarajmanjunath) + +### IMPROVEMENTS: + +- [config] [\#2877](https://github.com/tendermint/tendermint/issues/2877) Add `blocktime_iota` to the config.toml (@ackratos) + - NOTE: this should be a ConsensusParam, not part of the config, and will be + removed from the config at a later date + ([\#2920](https://github.com/tendermint/tendermint/issues/2920). +- [mempool] [\#2882](https://github.com/tendermint/tendermint/issues/2882) Add txs from Update to cache +- [mempool] [\#2891](https://github.com/tendermint/tendermint/issues/2891) Remove local int64 counter from being stored in every tx +- [node] [\#2866](https://github.com/tendermint/tendermint/issues/2866) Add ability to instantiate IPCVal (@joe-bowman) + +### BUG FIXES: + +- [blockchain] [\#2731](https://github.com/tendermint/tendermint/issues/2731) Retry both blocks if either is bad to avoid getting stuck during fast sync (@goolAdapter) +- [consensus] [\#2893](https://github.com/tendermint/tendermint/issues/2893) Use genDoc.Validators instead of state.NextValidators on replay when appHeight==0 (@james-ray) +- [log] [\#2868](https://github.com/tendermint/tendermint/issues/2868) Fix `module=main` setting overriding all others + - NOTE: this changes the default logging behaviour to be much less verbose. + Set `log_level="info"` to restore the previous behaviour. +- [rpc] [\#2808](https://github.com/tendermint/tendermint/issues/2808) Fix `accum` field in `/validators` by calling `IncrementAccum` if necessary +- [rpc] [\#2811](https://github.com/tendermint/tendermint/issues/2811) Allow integer IDs in JSON-RPC requests (@tomtau) +- [txindex/kv] [\#2759](https://github.com/tendermint/tendermint/issues/2759) Fix tx.height range queries +- [txindex/kv] [\#2775](https://github.com/tendermint/tendermint/issues/2775) Order tx results by index if height is the same +- [txindex/kv] [\#2908](https://github.com/tendermint/tendermint/issues/2908) Don't return false positives when searching for a prefix of a tag value + ## v0.26.3 *November 17th, 2018* diff --git a/CHANGELOG_PENDING.md b/CHANGELOG_PENDING.md index d6fcc736..2a2626a4 100644 --- a/CHANGELOG_PENDING.md +++ b/CHANGELOG_PENDING.md @@ -1,6 +1,6 @@ # Pending -## v0.26.4 +## v0.27.0 *TBD* @@ -23,21 +23,6 @@ program](https://hackerone.com/tendermint). ### FEATURES: -- [types] [\#1571](https://github.com/tendermint/tendermint/issues/1571) Enable subscription to tags emitted from `BeginBlock`/`EndBlock` (@kostko) - ### IMPROVEMENTS: -- [config] \#2877 add blocktime_iota to the config.toml (@ackratos) -- [mempool] \#2855 add txs from Update to cache -- [mempool] \#2835 Remove local int64 counter from being stored in every tx -- [node] \#2827 add ability to instantiate IPCVal (@joe-bowman) - ### BUG FIXES: - -- [blockchain] \#2731 Retry both blocks if either is bad to avoid getting stuck during fast sync (@goolAdapter) -- [log] \#2868 fix module=main setting overriding all others -- [rpc] \#2808 RPC validators calls IncrementAccum if necessary -- [kv indexer] \#2908 don't return false positives when searching for a prefix of a tag value -- [kv indexer] \#2775 order results by index if height is the same -- [rpc] \#2759 fix tx.height range queries -- [rpc] \#2811 Allow integer IDs in JSON-RPC requests \ No newline at end of file diff --git a/version/version.go b/version/version.go index aa52a82e..933328a6 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.3" + TMCoreSemVer = "0.26.4" // ABCISemVer is the semantic version of the ABCI library ABCISemVer = "0.15.0" From ef9902e602f113c0d54bc1f4ee44f0ff1cf6625b Mon Sep 17 00:00:00 2001 From: Anton Kaliaev Date: Wed, 28 Nov 2018 17:25:23 +0400 Subject: [PATCH 115/267] docs: small improvements (#2933) * update docs - make install_c cmd (install) - explain node IDs (quick-start) - update UPGRADING section (using-tendermint) * use git clone with JS example JS devs may not have Go installed and we should not force them to. * rewrite sentence --- Makefile | 5 ++++- docs/app-dev/abci-cli.md | 11 ++++------- docs/app-dev/getting-started.md | 18 ++++++++++-------- docs/introduction/install.md | 14 ++++++++++---- docs/introduction/quick-start.md | 21 ++++++++++++++++++--- docs/tendermint-core/using-tendermint.md | 22 ++++++++++------------ 6 files changed, 56 insertions(+), 35 deletions(-) diff --git a/Makefile b/Makefile index 294a319b..6f7874ee 100644 --- a/Makefile +++ b/Makefile @@ -32,6 +32,9 @@ build_race: install: CGO_ENABLED=0 go install $(BUILD_FLAGS) -tags $(BUILD_TAGS) ./cmd/tendermint +install_c: + CGO_ENABLED=1 go install $(BUILD_FLAGS) -tags "$(BUILD_TAGS) gcc" ./cmd/tendermint + ######################################## ### Protobuf @@ -328,4 +331,4 @@ build-slate: # To avoid unintended conflicts with file names, always add to .PHONY # unless there is a reason not to. # https://www.gnu.org/software/make/manual/html_node/Phony-Targets.html -.PHONY: check build build_race build_abci dist install install_abci check_dep check_tools get_tools get_dev_tools update_tools get_vendor_deps draw_deps get_protoc protoc_abci protoc_libs gen_certs clean_certs grpc_dbserver test_cover test_apps test_persistence test_p2p test test_race test_integrations test_release test100 vagrant_test fmt rpc-docs build-linux localnet-start localnet-stop build-docker build-docker-localnode sentry-start sentry-config sentry-stop build-slate protoc_grpc protoc_all +.PHONY: check build build_race build_abci dist install install_abci check_dep check_tools get_tools get_dev_tools update_tools get_vendor_deps draw_deps get_protoc protoc_abci protoc_libs gen_certs clean_certs grpc_dbserver test_cover test_apps test_persistence test_p2p test test_race test_integrations test_release test100 vagrant_test fmt rpc-docs build-linux localnet-start localnet-stop build-docker build-docker-localnode sentry-start sentry-config sentry-stop build-slate protoc_grpc protoc_all build_c install_c diff --git a/docs/app-dev/abci-cli.md b/docs/app-dev/abci-cli.md index 263c2c5e..ba6f0589 100644 --- a/docs/app-dev/abci-cli.md +++ b/docs/app-dev/abci-cli.md @@ -11,13 +11,10 @@ Make sure you [have Go installed](https://golang.org/doc/install). Next, install the `abci-cli` tool and example applications: ``` -go get github.com/tendermint/tendermint -``` - -to get vendored dependencies: - -``` -cd $GOPATH/src/github.com/tendermint/tendermint +mkdir -p $GOPATH/src/github.com/tendermint +cd $GOPATH/src/github.com/tendermint +git clone https://github.com/tendermint/tendermint.git +cd tendermint make get_tools make get_vendor_deps make install_abci diff --git a/docs/app-dev/getting-started.md b/docs/app-dev/getting-started.md index 14aa0dc3..d38615a2 100644 --- a/docs/app-dev/getting-started.md +++ b/docs/app-dev/getting-started.md @@ -252,12 +252,11 @@ we'll run a Javascript version of the `counter`. To run it, you'll need to [install node](https://nodejs.org/en/download/). You'll also need to fetch the relevant repository, from -[here](https://github.com/tendermint/js-abci) then install it. As go -devs, we keep all our code under the `$GOPATH`, so run: +[here](https://github.com/tendermint/js-abci), then install it: ``` -go get github.com/tendermint/js-abci &> /dev/null -cd $GOPATH/src/github.com/tendermint/js-abci/example +git clone https://github.com/tendermint/js-abci.git +cd js-abci/example npm install cd .. ``` @@ -276,13 +275,16 @@ tendermint node ``` Once again, you should see blocks streaming by - but now, our -application is written in javascript! Try sending some transactions, and +application is written in Javascript! Try sending some transactions, and like before - the results should be the same: ``` -curl localhost:26657/broadcast_tx_commit?tx=0x00 # ok -curl localhost:26657/broadcast_tx_commit?tx=0x05 # invalid nonce -curl localhost:26657/broadcast_tx_commit?tx=0x01 # ok +# ok +curl localhost:26657/broadcast_tx_commit?tx=0x00 +# invalid nonce +curl localhost:26657/broadcast_tx_commit?tx=0x05 +# ok +curl localhost:26657/broadcast_tx_commit?tx=0x01 ``` Neat, eh? diff --git a/docs/introduction/install.md b/docs/introduction/install.md index c3395eff..3005a734 100644 --- a/docs/introduction/install.md +++ b/docs/introduction/install.md @@ -79,11 +79,9 @@ make install Install [LevelDB](https://github.com/google/leveldb) (minimum version is 1.7). -Build Tendermint with C libraries: `make build_c`. - ### Ubuntu -Install LevelDB with snappy: +Install LevelDB with snappy (optionally): ``` sudo apt-get update @@ -112,5 +110,13 @@ db_backend = "cleveldb" To install Tendermint, run ``` -CGO_LDFLAGS="-lsnappy" go install -ldflags "-X github.com/tendermint/tendermint/version.GitCommit=`git rev-parse --short=8 HEAD`" -tags "tendermint gcc" -o build/tendermint ./cmd/tendermint/ +CGO_LDFLAGS="-lsnappy" make install_c ``` + +or run + +``` +CGO_LDFLAGS="-lsnappy" make build_c +``` + +to put the binary in `./build`. diff --git a/docs/introduction/quick-start.md b/docs/introduction/quick-start.md index 05facadf..b77e7273 100644 --- a/docs/introduction/quick-start.md +++ b/docs/introduction/quick-start.md @@ -40,7 +40,11 @@ These files are found in `$HOME/.tendermint`: ``` $ ls $HOME/.tendermint -config.toml data genesis.json priv_validator.json +config data + +$ ls $HOME/.tendermint/config/ + +config.toml genesis.json node_key.json priv_validator.json ``` For a single, local node, no further configuration is required. @@ -110,7 +114,18 @@ source ~/.profile This will install `go` and other dependencies, get the Tendermint source code, then compile the `tendermint` binary. -Next, use the `tendermint testnet` command to create four directories of config files (found in `./mytestnet`) and copy each directory to the relevant machine in the cloud, so that each machine has `$HOME/mytestnet/node[0-3]` directory. Then from each machine, run: +Next, use the `tendermint testnet` command to create four directories of config files (found in `./mytestnet`) and copy each directory to the relevant machine in the cloud, so that each machine has `$HOME/mytestnet/node[0-3]` directory. + +Before you can start the network, you'll need peers identifiers (IPs are not enough and can change). We'll refer to them as ID1, ID2, ID3, ID4. + +``` +tendermint show_node_id --home ./mytestnet/node0 +tendermint show_node_id --home ./mytestnet/node1 +tendermint show_node_id --home ./mytestnet/node2 +tendermint show_node_id --home ./mytestnet/node3 +``` + +Finally, from each machine, run: ``` tendermint node --home ./mytestnet/node0 --proxy_app=kvstore --p2p.persistent_peers="ID1@IP1:26656,ID2@IP2:26656,ID3@IP3:26656,ID4@IP4:26656" @@ -121,6 +136,6 @@ tendermint node --home ./mytestnet/node3 --proxy_app=kvstore --p2p.persistent_pe Note that after the third node is started, blocks will start to stream in because >2/3 of validators (defined in the `genesis.json`) have come online. -Seeds can also be specified in the `config.toml`. See [here](../tendermint-core/configuration.md) for more information about configuration options. +Persistent peers can also be specified in the `config.toml`. See [here](../tendermint-core/configuration.md) for more information about configuration options. Transactions can then be sent as covered in the single, local node example above. diff --git a/docs/tendermint-core/using-tendermint.md b/docs/tendermint-core/using-tendermint.md index c9915042..148c874c 100644 --- a/docs/tendermint-core/using-tendermint.md +++ b/docs/tendermint-core/using-tendermint.md @@ -519,18 +519,16 @@ developers guide](../app-dev/app-development.md) for more details. ### Local Network -To run a network locally, say on a single machine, you must change the -`_laddr` fields in the `config.toml` (or using the flags) so that the -listening addresses of the various sockets don't conflict. Additionally, -you must set `addr_book_strict=false` in the `config.toml`, otherwise -Tendermint's p2p library will deny making connections to peers with the -same IP address. +To run a network locally, say on a single machine, you must change the `_laddr` +fields in the `config.toml` (or using the flags) so that the listening +addresses of the various sockets don't conflict. Additionally, you must set +`addr_book_strict=false` in the `config.toml`, otherwise Tendermint's p2p +library will deny making connections to peers with the same IP address. ### Upgrading -The Tendermint development cycle currently includes a lot of breaking changes. -Upgrading from an old version to a new version usually means throwing -away the chain data. Try out the -[tm-migrate](https://github.com/hxzqlh/tm-tools) tool written by -[@hxzqlh](https://github.com/hxzqlh) if you are keen to preserve the -state of your chain when upgrading to newer versions. +See the +[UPGRADING.md](https://github.com/tendermint/tendermint/blob/master/UPGRADING.md) +guide. You may need to reset your chain between major breaking releases. +Although, we expect Tendermint to have fewer breaking releases in the future +(especially after 1.0 release). From 7213869fc6f8181673d450183878ab7952fce4c6 Mon Sep 17 00:00:00 2001 From: Daniil Lashin Date: Wed, 28 Nov 2018 16:32:16 +0300 Subject: [PATCH 116/267] Refactor updateState #2865 (#2929) * Refactor updateState #2865 * Apply suggestions from code review Co-Authored-By: danil-lashin * Apply suggestions from code review --- state/execution.go | 47 ++++++++++++++++++++--------------------- state/execution_test.go | 4 +++- state/state_test.go | 22 +++++++++++++------ 3 files changed, 41 insertions(+), 32 deletions(-) diff --git a/state/execution.go b/state/execution.go index b7c38f41..61a48d36 100644 --- a/state/execution.go +++ b/state/execution.go @@ -107,8 +107,18 @@ func (blockExec *BlockExecutor) ApplyBlock(state State, blockID types.BlockID, b fail.Fail() // XXX + // these are tendermint types now + validatorUpdates, err := types.PB2TM.ValidatorUpdates(abciResponses.EndBlock.ValidatorUpdates) + if err != nil { + return state, err + } + + if len(validatorUpdates) > 0 { + blockExec.logger.Info("Updates to validators", "updates", makeValidatorUpdatesLogString(validatorUpdates)) + } + // Update the state with the block and responses. - state, err = updateState(blockExec.logger, state, blockID, &block.Header, abciResponses) + state, err = updateState(state, blockID, &block.Header, abciResponses, validatorUpdates) if err != nil { return state, fmt.Errorf("Commit failed for application: %v", err) } @@ -132,7 +142,7 @@ func (blockExec *BlockExecutor) ApplyBlock(state State, blockID types.BlockID, b // Events are fired after everything else. // NOTE: if we crash between Commit and Save, events wont be fired during replay - fireEvents(blockExec.logger, blockExec.eventBus, block, abciResponses) + fireEvents(blockExec.logger, blockExec.eventBus, block, abciResponses, validatorUpdates) return state, nil } @@ -311,16 +321,10 @@ func getBeginBlockValidatorInfo(block *types.Block, lastValSet *types.ValidatorS // If more or equal than 1/3 of total voting power changed in one block, then // a light client could never prove the transition externally. See // ./lite/doc.go for details on how a light client tracks validators. -func updateValidators(currentSet *types.ValidatorSet, abciUpdates []abci.ValidatorUpdate) ([]*types.Validator, error) { - updates, err := types.PB2TM.ValidatorUpdates(abciUpdates) - if err != nil { - return nil, err - } - - // these are tendermint types now +func updateValidators(currentSet *types.ValidatorSet, updates []*types.Validator) error { for _, valUpdate := range updates { if valUpdate.VotingPower < 0 { - return nil, fmt.Errorf("Voting power can't be negative %v", valUpdate) + return fmt.Errorf("Voting power can't be negative %v", valUpdate) } address := valUpdate.Address @@ -329,32 +333,32 @@ func updateValidators(currentSet *types.ValidatorSet, abciUpdates []abci.Validat // remove val _, removed := currentSet.Remove(address) if !removed { - return nil, fmt.Errorf("Failed to remove validator %X", address) + return fmt.Errorf("Failed to remove validator %X", address) } } else if val == nil { // add val added := currentSet.Add(valUpdate) if !added { - return nil, fmt.Errorf("Failed to add new validator %v", valUpdate) + return fmt.Errorf("Failed to add new validator %v", valUpdate) } } else { // update val updated := currentSet.Update(valUpdate) if !updated { - return nil, fmt.Errorf("Failed to update validator %X to %v", address, valUpdate) + return fmt.Errorf("Failed to update validator %X to %v", address, valUpdate) } } } - return updates, nil + return nil } // updateState returns a new State updated according to the header and responses. func updateState( - logger log.Logger, state State, blockID types.BlockID, header *types.Header, abciResponses *ABCIResponses, + validatorUpdates []*types.Validator, ) (State, error) { // Copy the valset so we can apply changes from EndBlock @@ -364,14 +368,12 @@ func updateState( // Update the validator set with the latest abciResponses. lastHeightValsChanged := state.LastHeightValidatorsChanged if len(abciResponses.EndBlock.ValidatorUpdates) > 0 { - validatorUpdates, err := updateValidators(nValSet, abciResponses.EndBlock.ValidatorUpdates) + err := updateValidators(nValSet, validatorUpdates) if err != nil { return state, fmt.Errorf("Error changing validator set: %v", err) } // Change results from this height but only applies to the next next height. lastHeightValsChanged = header.Height + 1 + 1 - - logger.Info("Updates to validators", "updates", makeValidatorUpdatesLogString(validatorUpdates)) } // Update validator accums and set state variables. @@ -417,7 +419,7 @@ func updateState( // Fire NewBlock, NewBlockHeader. // Fire TxEvent for every tx. // NOTE: if Tendermint crashes before commit, some or all of these events may be published again. -func fireEvents(logger log.Logger, eventBus types.BlockEventPublisher, block *types.Block, abciResponses *ABCIResponses) { +func fireEvents(logger log.Logger, eventBus types.BlockEventPublisher, block *types.Block, abciResponses *ABCIResponses, validatorUpdates []*types.Validator) { eventBus.PublishEventNewBlock(types.EventDataNewBlock{ Block: block, ResultBeginBlock: *abciResponses.BeginBlock, @@ -438,12 +440,9 @@ func fireEvents(logger log.Logger, eventBus types.BlockEventPublisher, block *ty }}) } - abciValUpdates := abciResponses.EndBlock.ValidatorUpdates - if len(abciValUpdates) > 0 { - // if there were an error, we would've stopped in updateValidators - updates, _ := types.PB2TM.ValidatorUpdates(abciValUpdates) + if len(validatorUpdates) > 0 { eventBus.PublishEventValidatorSetUpdates( - types.EventDataValidatorSetUpdates{ValidatorUpdates: updates}) + types.EventDataValidatorSetUpdates{ValidatorUpdates: validatorUpdates}) } } diff --git a/state/execution_test.go b/state/execution_test.go index 41d9a484..03f52179 100644 --- a/state/execution_test.go +++ b/state/execution_test.go @@ -218,7 +218,9 @@ func TestUpdateValidators(t *testing.T) { for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { - _, err := updateValidators(tc.currentSet, tc.abciUpdates) + updates, err := types.PB2TM.ValidatorUpdates(tc.abciUpdates) + assert.NoError(t, err) + err = updateValidators(tc.currentSet, updates) if tc.shouldErr { assert.Error(t, err) } else { diff --git a/state/state_test.go b/state/state_test.go index 50346025..b2a6080b 100644 --- a/state/state_test.go +++ b/state/state_test.go @@ -5,12 +5,11 @@ import ( "fmt" "testing" - "github.com/tendermint/tendermint/libs/log" - "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + abci "github.com/tendermint/tendermint/abci/types" - crypto "github.com/tendermint/tendermint/crypto" + "github.com/tendermint/tendermint/crypto" "github.com/tendermint/tendermint/crypto/ed25519" cmn "github.com/tendermint/tendermint/libs/common" dbm "github.com/tendermint/tendermint/libs/db" @@ -223,6 +222,7 @@ func TestOneValidatorChangesSaveLoad(t *testing.T) { _, val := state.Validators.GetByIndex(0) power := val.VotingPower var err error + var validatorUpdates []*types.Validator for i := int64(1); i < highestHeight; i++ { // When we get to a change height, use the next pubkey. if changeIndex < len(changeHeights) && i == changeHeights[changeIndex] { @@ -230,8 +230,10 @@ func TestOneValidatorChangesSaveLoad(t *testing.T) { power++ } header, blockID, responses := makeHeaderPartsResponsesValPowerChange(state, i, power) - state, err = updateState(log.TestingLogger(), state, blockID, &header, responses) - assert.Nil(t, err) + validatorUpdates, err = types.PB2TM.ValidatorUpdates(responses.EndBlock.ValidatorUpdates) + require.NoError(t, err) + state, err = updateState(state, blockID, &header, responses, validatorUpdates) + require.NoError(t, err) nextHeight := state.LastBlockHeight + 1 saveValidatorsInfo(stateDB, nextHeight+1, state.LastHeightValidatorsChanged, state.NextValidators) } @@ -303,7 +305,10 @@ func TestManyValidatorChangesSaveLoad(t *testing.T) { // Save state etc. var err error - state, err = updateState(log.TestingLogger(), state, blockID, &header, responses) + var validatorUpdates []*types.Validator + validatorUpdates, err = types.PB2TM.ValidatorUpdates(responses.EndBlock.ValidatorUpdates) + require.NoError(t, err) + state, err = updateState(state, blockID, &header, responses, validatorUpdates) require.Nil(t, err) nextHeight := state.LastBlockHeight + 1 saveValidatorsInfo(stateDB, nextHeight+1, state.LastHeightValidatorsChanged, state.NextValidators) @@ -375,6 +380,7 @@ func TestConsensusParamsChangesSaveLoad(t *testing.T) { changeIndex := 0 cp := params[changeIndex] var err error + var validatorUpdates []*types.Validator for i := int64(1); i < highestHeight; i++ { // When we get to a change height, use the next params. if changeIndex < len(changeHeights) && i == changeHeights[changeIndex] { @@ -382,7 +388,9 @@ func TestConsensusParamsChangesSaveLoad(t *testing.T) { cp = params[changeIndex] } header, blockID, responses := makeHeaderPartsResponsesParams(state, i, cp) - state, err = updateState(log.TestingLogger(), state, blockID, &header, responses) + validatorUpdates, err = types.PB2TM.ValidatorUpdates(responses.EndBlock.ValidatorUpdates) + require.NoError(t, err) + state, err = updateState(state, blockID, &header, responses, validatorUpdates) require.Nil(t, err) nextHeight := state.LastBlockHeight + 1 From 416d143bf72237c026cc289e5505ad943b88944b Mon Sep 17 00:00:00 2001 From: Jae Kwon Date: Wed, 28 Nov 2018 22:49:24 +0900 Subject: [PATCH 117/267] R4R: Swap start/end in ReverseIterator (#2913) * Swap start/end in ReverseIterator * update CHANGELOG_PENDING * fixes from review --- CHANGELOG_PENDING.md | 2 ++ libs/db/backend_test.go | 26 +++++++++--------- libs/db/c_level_db.go | 11 ++++---- libs/db/fsdb.go | 6 ++--- libs/db/go_level_db.go | 11 ++++---- libs/db/mem_db.go | 2 +- libs/db/prefix_db.go | 29 ++------------------ libs/db/prefix_db_test.go | 57 ++++++++++++++++++++++++++++++++++----- libs/db/types.go | 6 ++--- libs/db/util.go | 47 +++++--------------------------- lite/dbprovider.go | 8 +++--- 11 files changed, 98 insertions(+), 107 deletions(-) diff --git a/CHANGELOG_PENDING.md b/CHANGELOG_PENDING.md index 2a2626a4..dfa2c1ce 100644 --- a/CHANGELOG_PENDING.md +++ b/CHANGELOG_PENDING.md @@ -17,6 +17,8 @@ program](https://hackerone.com/tendermint). * Go API +- [db] [\#2913](https://github.com/tendermint/tendermint/pull/2913) ReverseIterator API change -- start < end, and end is exclusive. + * Blockchain Protocol * P2P Protocol diff --git a/libs/db/backend_test.go b/libs/db/backend_test.go index 2aebde1c..fb2a3d0b 100644 --- a/libs/db/backend_test.go +++ b/libs/db/backend_test.go @@ -180,13 +180,13 @@ func testDBIterator(t *testing.T, backend DBBackendType) { verifyIterator(t, db.ReverseIterator(nil, nil), []int64{9, 8, 7, 5, 4, 3, 2, 1, 0}, "reverse iterator") verifyIterator(t, db.Iterator(nil, int642Bytes(0)), []int64(nil), "forward iterator to 0") - verifyIterator(t, db.ReverseIterator(nil, int642Bytes(10)), []int64(nil), "reverse iterator 10") + verifyIterator(t, db.ReverseIterator(int642Bytes(10), nil), []int64(nil), "reverse iterator from 10 (ex)") verifyIterator(t, db.Iterator(int642Bytes(0), nil), []int64{0, 1, 2, 3, 4, 5, 7, 8, 9}, "forward iterator from 0") verifyIterator(t, db.Iterator(int642Bytes(1), nil), []int64{1, 2, 3, 4, 5, 7, 8, 9}, "forward iterator from 1") - verifyIterator(t, db.ReverseIterator(int642Bytes(10), nil), []int64{9, 8, 7, 5, 4, 3, 2, 1, 0}, "reverse iterator from 10") - verifyIterator(t, db.ReverseIterator(int642Bytes(9), nil), []int64{9, 8, 7, 5, 4, 3, 2, 1, 0}, "reverse iterator from 9") - verifyIterator(t, db.ReverseIterator(int642Bytes(8), nil), []int64{8, 7, 5, 4, 3, 2, 1, 0}, "reverse iterator from 8") + verifyIterator(t, db.ReverseIterator(nil, int642Bytes(10)), []int64{9, 8, 7, 5, 4, 3, 2, 1, 0}, "reverse iterator from 10 (ex)") + verifyIterator(t, db.ReverseIterator(nil, int642Bytes(9)), []int64{8, 7, 5, 4, 3, 2, 1, 0}, "reverse iterator from 9 (ex)") + verifyIterator(t, db.ReverseIterator(nil, int642Bytes(8)), []int64{7, 5, 4, 3, 2, 1, 0}, "reverse iterator from 8 (ex)") verifyIterator(t, db.Iterator(int642Bytes(5), int642Bytes(6)), []int64{5}, "forward iterator from 5 to 6") verifyIterator(t, db.Iterator(int642Bytes(5), int642Bytes(7)), []int64{5}, "forward iterator from 5 to 7") @@ -195,20 +195,20 @@ func testDBIterator(t *testing.T, backend DBBackendType) { verifyIterator(t, db.Iterator(int642Bytes(6), int642Bytes(8)), []int64{7}, "forward iterator from 6 to 8") verifyIterator(t, db.Iterator(int642Bytes(7), int642Bytes(8)), []int64{7}, "forward iterator from 7 to 8") - verifyIterator(t, db.ReverseIterator(int642Bytes(5), int642Bytes(4)), []int64{5}, "reverse iterator from 5 to 4") - verifyIterator(t, db.ReverseIterator(int642Bytes(6), int642Bytes(4)), []int64{5}, "reverse iterator from 6 to 4") - verifyIterator(t, db.ReverseIterator(int642Bytes(7), int642Bytes(4)), []int64{7, 5}, "reverse iterator from 7 to 4") - verifyIterator(t, db.ReverseIterator(int642Bytes(6), int642Bytes(5)), []int64(nil), "reverse iterator from 6 to 5") - verifyIterator(t, db.ReverseIterator(int642Bytes(7), int642Bytes(5)), []int64{7}, "reverse iterator from 7 to 5") - verifyIterator(t, db.ReverseIterator(int642Bytes(7), int642Bytes(6)), []int64{7}, "reverse iterator from 7 to 6") + verifyIterator(t, db.ReverseIterator(int642Bytes(4), int642Bytes(5)), []int64{4}, "reverse iterator from 5 (ex) to 4") + verifyIterator(t, db.ReverseIterator(int642Bytes(4), int642Bytes(6)), []int64{5, 4}, "reverse iterator from 6 (ex) to 4") + verifyIterator(t, db.ReverseIterator(int642Bytes(4), int642Bytes(7)), []int64{5, 4}, "reverse iterator from 7 (ex) to 4") + verifyIterator(t, db.ReverseIterator(int642Bytes(5), int642Bytes(6)), []int64{5}, "reverse iterator from 6 (ex) to 5") + verifyIterator(t, db.ReverseIterator(int642Bytes(5), int642Bytes(7)), []int64{5}, "reverse iterator from 7 (ex) to 5") + verifyIterator(t, db.ReverseIterator(int642Bytes(6), int642Bytes(7)), []int64(nil), "reverse iterator from 7 (ex) to 6") verifyIterator(t, db.Iterator(int642Bytes(0), int642Bytes(1)), []int64{0}, "forward iterator from 0 to 1") - verifyIterator(t, db.ReverseIterator(int642Bytes(9), int642Bytes(8)), []int64{9}, "reverse iterator from 9 to 8") + verifyIterator(t, db.ReverseIterator(int642Bytes(8), int642Bytes(9)), []int64{8}, "reverse iterator from 9 (ex) to 8") verifyIterator(t, db.Iterator(int642Bytes(2), int642Bytes(4)), []int64{2, 3}, "forward iterator from 2 to 4") verifyIterator(t, db.Iterator(int642Bytes(4), int642Bytes(2)), []int64(nil), "forward iterator from 4 to 2") - verifyIterator(t, db.ReverseIterator(int642Bytes(4), int642Bytes(2)), []int64{4, 3}, "reverse iterator from 4 to 2") - verifyIterator(t, db.ReverseIterator(int642Bytes(2), int642Bytes(4)), []int64(nil), "reverse iterator from 2 to 4") + verifyIterator(t, db.ReverseIterator(int642Bytes(2), int642Bytes(4)), []int64{3, 2}, "reverse iterator from 4 (ex) to 2") + verifyIterator(t, db.ReverseIterator(int642Bytes(4), int642Bytes(2)), []int64(nil), "reverse iterator from 2 (ex) to 4") } diff --git a/libs/db/c_level_db.go b/libs/db/c_level_db.go index 30746126..7f74b2a7 100644 --- a/libs/db/c_level_db.go +++ b/libs/db/c_level_db.go @@ -205,13 +205,13 @@ type cLevelDBIterator struct { func newCLevelDBIterator(source *levigo.Iterator, start, end []byte, isReverse bool) *cLevelDBIterator { if isReverse { - if start == nil { + if end == nil { source.SeekToLast() } else { - source.Seek(start) + source.Seek(end) if source.Valid() { - soakey := source.Key() // start or after key - if bytes.Compare(start, soakey) < 0 { + eoakey := source.Key() // end or after key + if bytes.Compare(end, eoakey) <= 0 { source.Prev() } } else { @@ -255,10 +255,11 @@ func (itr cLevelDBIterator) Valid() bool { } // If key is end or past it, invalid. + var start = itr.start var end = itr.end var key = itr.source.Key() if itr.isReverse { - if end != nil && bytes.Compare(key, end) <= 0 { + if start != nil && bytes.Compare(key, start) < 0 { itr.isInvalid = true return false } diff --git a/libs/db/fsdb.go b/libs/db/fsdb.go index b1d40c7b..2d82e774 100644 --- a/libs/db/fsdb.go +++ b/libs/db/fsdb.go @@ -161,7 +161,7 @@ func (db *FSDB) MakeIterator(start, end []byte, isReversed bool) Iterator { // We need a copy of all of the keys. // Not the best, but probably not a bottleneck depending. - keys, err := list(db.dir, start, end, isReversed) + keys, err := list(db.dir, start, end) if err != nil { panic(errors.Wrapf(err, "Listing keys in %s", db.dir)) } @@ -229,7 +229,7 @@ func remove(path string) error { // List keys in a directory, stripping of escape sequences and dir portions. // CONTRACT: returns os errors directly without wrapping. -func list(dirPath string, start, end []byte, isReversed bool) ([]string, error) { +func list(dirPath string, start, end []byte) ([]string, error) { dir, err := os.Open(dirPath) if err != nil { return nil, err @@ -247,7 +247,7 @@ func list(dirPath string, start, end []byte, isReversed bool) ([]string, error) return nil, fmt.Errorf("Failed to unescape %s while listing", name) } key := unescapeKey([]byte(n)) - if IsKeyInDomain(key, start, end, isReversed) { + if IsKeyInDomain(key, start, end) { keys = append(keys, string(key)) } } diff --git a/libs/db/go_level_db.go b/libs/db/go_level_db.go index 8a488792..fd487a4d 100644 --- a/libs/db/go_level_db.go +++ b/libs/db/go_level_db.go @@ -213,13 +213,13 @@ var _ Iterator = (*goLevelDBIterator)(nil) func newGoLevelDBIterator(source iterator.Iterator, start, end []byte, isReverse bool) *goLevelDBIterator { if isReverse { - if start == nil { + if end == nil { source.Last() } else { - valid := source.Seek(start) + valid := source.Seek(end) if valid { - soakey := source.Key() // start or after key - if bytes.Compare(start, soakey) < 0 { + eoakey := source.Key() // end or after key + if bytes.Compare(end, eoakey) <= 0 { source.Prev() } } else { @@ -265,11 +265,12 @@ func (itr *goLevelDBIterator) Valid() bool { } // If key is end or past it, invalid. + var start = itr.start var end = itr.end var key = itr.source.Key() if itr.isReverse { - if end != nil && bytes.Compare(key, end) <= 0 { + if start != nil && bytes.Compare(key, start) < 0 { itr.isInvalid = true return false } diff --git a/libs/db/mem_db.go b/libs/db/mem_db.go index 58012301..ff516bc7 100644 --- a/libs/db/mem_db.go +++ b/libs/db/mem_db.go @@ -237,7 +237,7 @@ func (itr *memDBIterator) assertIsValid() { func (db *MemDB) getSortedKeys(start, end []byte, reverse bool) []string { keys := []string{} for key := range db.db { - inDomain := IsKeyInDomain([]byte(key), start, end, reverse) + inDomain := IsKeyInDomain([]byte(key), start, end) if inDomain { keys = append(keys, key) } diff --git a/libs/db/prefix_db.go b/libs/db/prefix_db.go index 9dc4ee97..40d72560 100644 --- a/libs/db/prefix_db.go +++ b/libs/db/prefix_db.go @@ -131,27 +131,13 @@ func (pdb *prefixDB) ReverseIterator(start, end []byte) Iterator { defer pdb.mtx.Unlock() var pstart, pend []byte - if start == nil { - // This may cause the underlying iterator to start with - // an item which doesn't start with prefix. We will skip - // that item later in this function. See 'skipOne'. - pstart = cpIncr(pdb.prefix) - } else { - pstart = append(cp(pdb.prefix), start...) - } + pstart = append(cp(pdb.prefix), start...) if end == nil { - // This may cause the underlying iterator to end with an - // item which doesn't start with prefix. The - // prefixIterator will terminate iteration - // automatically upon detecting this. - pend = cpDecr(pdb.prefix) + pend = cpIncr(pdb.prefix) } else { pend = append(cp(pdb.prefix), end...) } ritr := pdb.db.ReverseIterator(pstart, pend) - if start == nil { - skipOne(ritr, cpIncr(pdb.prefix)) - } return newPrefixIterator( pdb.prefix, start, @@ -310,7 +296,6 @@ func (itr *prefixIterator) Next() { } itr.source.Next() if !itr.source.Valid() || !bytes.HasPrefix(itr.source.Key(), itr.prefix) { - itr.source.Close() itr.valid = false return } @@ -345,13 +330,3 @@ func stripPrefix(key []byte, prefix []byte) (stripped []byte) { } return key[len(prefix):] } - -// If the first iterator item is skipKey, then -// skip it. -func skipOne(itr Iterator, skipKey []byte) { - if itr.Valid() { - if bytes.Equal(itr.Key(), skipKey) { - itr.Next() - } - } -} diff --git a/libs/db/prefix_db_test.go b/libs/db/prefix_db_test.go index 60809f15..e3e37c7d 100644 --- a/libs/db/prefix_db_test.go +++ b/libs/db/prefix_db_test.go @@ -113,8 +113,46 @@ func TestPrefixDBReverseIterator2(t *testing.T) { db := mockDBWithStuff() pdb := NewPrefixDB(db, bz("key")) + itr := pdb.ReverseIterator(bz(""), nil) + checkDomain(t, itr, bz(""), nil) + checkItem(t, itr, bz("3"), bz("value3")) + checkNext(t, itr, true) + checkItem(t, itr, bz("2"), bz("value2")) + checkNext(t, itr, true) + checkItem(t, itr, bz("1"), bz("value1")) + checkNext(t, itr, true) + checkItem(t, itr, bz(""), bz("value")) + checkNext(t, itr, false) + checkInvalid(t, itr) + itr.Close() +} + +func TestPrefixDBReverseIterator3(t *testing.T) { + db := mockDBWithStuff() + pdb := NewPrefixDB(db, bz("key")) + itr := pdb.ReverseIterator(nil, bz("")) checkDomain(t, itr, nil, bz("")) + checkInvalid(t, itr) + itr.Close() +} + +func TestPrefixDBReverseIterator4(t *testing.T) { + db := mockDBWithStuff() + pdb := NewPrefixDB(db, bz("key")) + + itr := pdb.ReverseIterator(bz(""), bz("")) + checkDomain(t, itr, bz(""), bz("")) + checkInvalid(t, itr) + itr.Close() +} + +func TestPrefixDBReverseIterator5(t *testing.T) { + db := mockDBWithStuff() + pdb := NewPrefixDB(db, bz("key")) + + itr := pdb.ReverseIterator(bz("1"), nil) + checkDomain(t, itr, bz("1"), nil) checkItem(t, itr, bz("3"), bz("value3")) checkNext(t, itr, true) checkItem(t, itr, bz("2"), bz("value2")) @@ -125,23 +163,30 @@ func TestPrefixDBReverseIterator2(t *testing.T) { itr.Close() } -func TestPrefixDBReverseIterator3(t *testing.T) { +func TestPrefixDBReverseIterator6(t *testing.T) { db := mockDBWithStuff() pdb := NewPrefixDB(db, bz("key")) - itr := pdb.ReverseIterator(bz(""), nil) - checkDomain(t, itr, bz(""), nil) - checkItem(t, itr, bz(""), bz("value")) + itr := pdb.ReverseIterator(bz("2"), nil) + checkDomain(t, itr, bz("2"), nil) + checkItem(t, itr, bz("3"), bz("value3")) + checkNext(t, itr, true) + checkItem(t, itr, bz("2"), bz("value2")) checkNext(t, itr, false) checkInvalid(t, itr) itr.Close() } -func TestPrefixDBReverseIterator4(t *testing.T) { +func TestPrefixDBReverseIterator7(t *testing.T) { db := mockDBWithStuff() pdb := NewPrefixDB(db, bz("key")) - itr := pdb.ReverseIterator(bz(""), bz("")) + itr := pdb.ReverseIterator(nil, bz("2")) + checkDomain(t, itr, nil, bz("2")) + checkItem(t, itr, bz("1"), bz("value1")) + checkNext(t, itr, true) + checkItem(t, itr, bz(""), bz("value")) + checkNext(t, itr, false) checkInvalid(t, itr) itr.Close() } diff --git a/libs/db/types.go b/libs/db/types.go index ad78859a..9b9c6d0b 100644 --- a/libs/db/types.go +++ b/libs/db/types.go @@ -34,9 +34,9 @@ type DB interface { Iterator(start, end []byte) Iterator // Iterate over a domain of keys in descending order. End is exclusive. - // Start must be greater than end, or the Iterator is invalid. - // If start is nil, iterates from the last/greatest item (inclusive). - // If end is nil, iterates up to the first/least item (inclusive). + // Start must be less than end, or the Iterator is invalid. + // If start is nil, iterates up to the first/least item (inclusive). + // If end is nil, iterates from the last/greatest item (inclusive). // CONTRACT: No writes may happen within a domain while an iterator exists over it. // CONTRACT: start, end readonly []byte ReverseIterator(start, end []byte) Iterator diff --git a/libs/db/util.go b/libs/db/util.go index 51277ac4..e927c354 100644 --- a/libs/db/util.go +++ b/libs/db/util.go @@ -33,46 +33,13 @@ func cpIncr(bz []byte) (ret []byte) { return nil } -// Returns a slice of the same length (big endian) -// except decremented by one. -// Returns nil on underflow (e.g. if bz bytes are all 0x00) -// CONTRACT: len(bz) > 0 -func cpDecr(bz []byte) (ret []byte) { - if len(bz) == 0 { - panic("cpDecr expects non-zero bz length") - } - ret = cp(bz) - for i := len(bz) - 1; i >= 0; i-- { - if ret[i] > byte(0x00) { - ret[i]-- - return - } - ret[i] = byte(0xFF) - if i == 0 { - // Underflow - return nil - } - } - return nil -} - // See DB interface documentation for more information. -func IsKeyInDomain(key, start, end []byte, isReverse bool) bool { - if !isReverse { - if bytes.Compare(key, start) < 0 { - return false - } - if end != nil && bytes.Compare(end, key) <= 0 { - return false - } - return true - } else { - if start != nil && bytes.Compare(start, key) < 0 { - return false - } - if end != nil && bytes.Compare(key, end) <= 0 { - return false - } - return true +func IsKeyInDomain(key, start, end []byte) bool { + if bytes.Compare(key, start) < 0 { + return false } + if end != nil && bytes.Compare(end, key) <= 0 { + return false + } + return true } diff --git a/lite/dbprovider.go b/lite/dbprovider.go index e0c4e65b..9f4b264f 100644 --- a/lite/dbprovider.go +++ b/lite/dbprovider.go @@ -105,8 +105,8 @@ func (dbp *DBProvider) LatestFullCommit(chainID string, minHeight, maxHeight int } itr := dbp.db.ReverseIterator( - signedHeaderKey(chainID, maxHeight), - signedHeaderKey(chainID, minHeight-1), + signedHeaderKey(chainID, minHeight), + append(signedHeaderKey(chainID, maxHeight), byte(0x00)), ) defer itr.Close() @@ -190,8 +190,8 @@ func (dbp *DBProvider) deleteAfterN(chainID string, after int) error { dbp.logger.Info("DBProvider.deleteAfterN()...", "chainID", chainID, "after", after) itr := dbp.db.ReverseIterator( - signedHeaderKey(chainID, 1<<63-1), - signedHeaderKey(chainID, 0), + signedHeaderKey(chainID, 1), + append(signedHeaderKey(chainID, 1<<63-1), byte(0x00)), ) defer itr.Close() From e291fbbebe2323b640ca2301a8afa70bee05526e Mon Sep 17 00:00:00 2001 From: srmo Date: Wed, 28 Nov 2018 14:52:35 +0100 Subject: [PATCH 118/267] 2871 remove proposalHeartbeat infrastructure (#2874) * 2871 remove proposalHeartbeat infrastructure * 2871 add preliminary changelog entry --- CHANGELOG_PENDING.md | 1 + consensus/common_test.go | 14 --- consensus/reactor.go | 38 +------ consensus/reactor_test.go | 15 +-- consensus/state.go | 42 +------ consensus/state_test.go | 29 ----- .../reactors/consensus/consensus-reactor.md | 5 +- docs/spec/reactors/consensus/consensus.md | 27 ----- privval/priv_validator.go | 13 --- privval/remote_signer.go | 48 -------- privval/tcp_test.go | 56 +--------- types/canonical.go | 22 ---- types/event_bus.go | 4 - types/event_bus_test.go | 4 +- types/events.go | 7 -- types/heartbeat.go | 83 -------------- types/heartbeat_test.go | 104 ------------------ types/priv_validator.go | 18 +-- types/signable.go | 4 +- types/signed_msg_type.go | 2 - 20 files changed, 15 insertions(+), 521 deletions(-) delete mode 100644 types/heartbeat.go delete mode 100644 types/heartbeat_test.go diff --git a/CHANGELOG_PENDING.md b/CHANGELOG_PENDING.md index dfa2c1ce..f4ade603 100644 --- a/CHANGELOG_PENDING.md +++ b/CHANGELOG_PENDING.md @@ -26,5 +26,6 @@ program](https://hackerone.com/tendermint). ### FEATURES: ### IMPROVEMENTS: +- [consensus] [\#2871](https://github.com/tendermint/tendermint/issues/2871) Remove *ProposalHeartbeat* infrastructure as it serves no real purpose ### BUG FIXES: diff --git a/consensus/common_test.go b/consensus/common_test.go index 8a2d8a42..46be5cbd 100644 --- a/consensus/common_test.go +++ b/consensus/common_test.go @@ -425,20 +425,6 @@ func ensureNewRound(roundCh <-chan interface{}, height int64, round int) { } } -func ensureProposalHeartbeat(heartbeatCh <-chan interface{}) { - select { - case <-time.After(ensureTimeout): - panic("Timeout expired while waiting for ProposalHeartbeat event") - case ev := <-heartbeatCh: - heartbeat, ok := ev.(types.EventDataProposalHeartbeat) - if !ok { - panic(fmt.Sprintf("expected a *types.EventDataProposalHeartbeat, "+ - "got %v. wrong subscription channel?", - reflect.TypeOf(heartbeat))) - } - } -} - func ensureNewTimeout(timeoutCh <-chan interface{}, height int64, round int, timeout int64) { timeoutDuration := time.Duration(timeout*3) * time.Nanosecond ensureNewEvent(timeoutCh, height, round, timeoutDuration, diff --git a/consensus/reactor.go b/consensus/reactor.go index b3298e9d..1f508319 100644 --- a/consensus/reactor.go +++ b/consensus/reactor.go @@ -8,7 +8,7 @@ import ( "github.com/pkg/errors" - amino "github.com/tendermint/go-amino" + "github.com/tendermint/go-amino" cstypes "github.com/tendermint/tendermint/consensus/types" cmn "github.com/tendermint/tendermint/libs/common" tmevents "github.com/tendermint/tendermint/libs/events" @@ -264,11 +264,6 @@ func (conR *ConsensusReactor) Receive(chID byte, src p2p.Peer, msgBytes []byte) BlockID: msg.BlockID, Votes: ourVotes, })) - case *ProposalHeartbeatMessage: - hb := msg.Heartbeat - conR.Logger.Debug("Received proposal heartbeat message", - "height", hb.Height, "round", hb.Round, "sequence", hb.Sequence, - "valIdx", hb.ValidatorIndex, "valAddr", hb.ValidatorAddress) default: conR.Logger.Error(fmt.Sprintf("Unknown message type %v", reflect.TypeOf(msg))) } @@ -369,8 +364,8 @@ func (conR *ConsensusReactor) FastSync() bool { //-------------------------------------- -// subscribeToBroadcastEvents subscribes for new round steps, votes and -// proposal heartbeats using internal pubsub defined on state to broadcast +// subscribeToBroadcastEvents subscribes for new round steps and votes +// using internal pubsub defined on state to broadcast // them to peers upon receiving. func (conR *ConsensusReactor) subscribeToBroadcastEvents() { const subscriber = "consensus-reactor" @@ -389,10 +384,6 @@ func (conR *ConsensusReactor) subscribeToBroadcastEvents() { conR.broadcastHasVoteMessage(data.(*types.Vote)) }) - conR.conS.evsw.AddListenerForEvent(subscriber, types.EventProposalHeartbeat, - func(data tmevents.EventData) { - conR.broadcastProposalHeartbeatMessage(data.(*types.Heartbeat)) - }) } func (conR *ConsensusReactor) unsubscribeFromBroadcastEvents() { @@ -400,13 +391,6 @@ func (conR *ConsensusReactor) unsubscribeFromBroadcastEvents() { conR.conS.evsw.RemoveListener(subscriber) } -func (conR *ConsensusReactor) broadcastProposalHeartbeatMessage(hb *types.Heartbeat) { - conR.Logger.Debug("Broadcasting proposal heartbeat message", - "height", hb.Height, "round", hb.Round, "sequence", hb.Sequence, "address", hb.ValidatorAddress) - msg := &ProposalHeartbeatMessage{hb} - conR.Switch.Broadcast(StateChannel, cdc.MustMarshalBinaryBare(msg)) -} - func (conR *ConsensusReactor) broadcastNewRoundStepMessage(rs *cstypes.RoundState) { nrsMsg := makeRoundStepMessage(rs) conR.Switch.Broadcast(StateChannel, cdc.MustMarshalBinaryBare(nrsMsg)) @@ -1387,7 +1371,6 @@ func RegisterConsensusMessages(cdc *amino.Codec) { cdc.RegisterConcrete(&HasVoteMessage{}, "tendermint/HasVote", nil) cdc.RegisterConcrete(&VoteSetMaj23Message{}, "tendermint/VoteSetMaj23", nil) cdc.RegisterConcrete(&VoteSetBitsMessage{}, "tendermint/VoteSetBits", nil) - cdc.RegisterConcrete(&ProposalHeartbeatMessage{}, "tendermint/ProposalHeartbeat", nil) } func decodeMsg(bz []byte) (msg ConsensusMessage, err error) { @@ -1664,18 +1647,3 @@ func (m *VoteSetBitsMessage) String() string { } //------------------------------------- - -// ProposalHeartbeatMessage is sent to signal that a node is alive and waiting for transactions for a proposal. -type ProposalHeartbeatMessage struct { - Heartbeat *types.Heartbeat -} - -// ValidateBasic performs basic validation. -func (m *ProposalHeartbeatMessage) ValidateBasic() error { - return m.Heartbeat.ValidateBasic() -} - -// String returns a string representation. -func (m *ProposalHeartbeatMessage) String() string { - return fmt.Sprintf("[HEARTBEAT %v]", m.Heartbeat) -} diff --git a/consensus/reactor_test.go b/consensus/reactor_test.go index 2758f3fa..1636785c 100644 --- a/consensus/reactor_test.go +++ b/consensus/reactor_test.go @@ -213,8 +213,8 @@ func (m *mockEvidencePool) Update(block *types.Block, state sm.State) { //------------------------------------ -// Ensure a testnet sends proposal heartbeats and makes blocks when there are txs -func TestReactorProposalHeartbeats(t *testing.T) { +// Ensure a testnet makes blocks when there are txs +func TestReactorCreatesBlockWhenEmptyBlocksFalse(t *testing.T) { N := 4 css := randConsensusNet(N, "consensus_reactor_test", newMockTickerFunc(true), newCounter, func(c *cfg.Config) { @@ -222,17 +222,6 @@ func TestReactorProposalHeartbeats(t *testing.T) { }) reactors, eventChans, eventBuses := startConsensusNet(t, css, N) defer stopConsensusNet(log.TestingLogger(), reactors, eventBuses) - heartbeatChans := make([]chan interface{}, N) - var err error - for i := 0; i < N; i++ { - heartbeatChans[i] = make(chan interface{}, 1) - err = eventBuses[i].Subscribe(context.Background(), testSubscriber, types.EventQueryProposalHeartbeat, heartbeatChans[i]) - require.NoError(t, err) - } - // wait till everyone sends a proposal heartbeat - timeoutWaitGroup(t, N, func(j int) { - <-heartbeatChans[j] - }, css) // send a tx if err := css[3].mempool.CheckTx([]byte{1, 2, 3}, nil); err != nil { diff --git a/consensus/state.go b/consensus/state.go index 4b7aec2a..71cf079a 100644 --- a/consensus/state.go +++ b/consensus/state.go @@ -22,13 +22,6 @@ import ( "github.com/tendermint/tendermint/types" ) -//----------------------------------------------------------------------------- -// Config - -const ( - proposalHeartbeatIntervalSeconds = 2 -) - //----------------------------------------------------------------------------- // Errors @@ -118,7 +111,7 @@ type ConsensusState struct { done chan struct{} // synchronous pubsub between consensus state and reactor. - // state only emits EventNewRoundStep, EventVote and EventProposalHeartbeat + // state only emits EventNewRoundStep and EventVote evsw tmevents.EventSwitch // for reporting metrics @@ -785,7 +778,6 @@ func (cs *ConsensusState) enterNewRound(height int64, round int) { cs.scheduleTimeout(cs.config.CreateEmptyBlocksInterval, height, round, cstypes.RoundStepNewRound) } - go cs.proposalHeartbeat(height, round) } else { cs.enterPropose(height, round) } @@ -802,38 +794,6 @@ func (cs *ConsensusState) needProofBlock(height int64) bool { return !bytes.Equal(cs.state.AppHash, lastBlockMeta.Header.AppHash) } -func (cs *ConsensusState) proposalHeartbeat(height int64, round int) { - logger := cs.Logger.With("height", height, "round", round) - addr := cs.privValidator.GetAddress() - - if !cs.Validators.HasAddress(addr) { - logger.Debug("Not sending proposalHearbeat. This node is not a validator", "addr", addr, "vals", cs.Validators) - return - } - counter := 0 - valIndex, _ := cs.Validators.GetByAddress(addr) - chainID := cs.state.ChainID - for { - rs := cs.GetRoundState() - // if we've already moved on, no need to send more heartbeats - if rs.Step > cstypes.RoundStepNewRound || rs.Round > round || rs.Height > height { - return - } - heartbeat := &types.Heartbeat{ - Height: rs.Height, - Round: rs.Round, - Sequence: counter, - ValidatorAddress: addr, - ValidatorIndex: valIndex, - } - cs.privValidator.SignHeartbeat(chainID, heartbeat) - cs.eventBus.PublishEventProposalHeartbeat(types.EventDataProposalHeartbeat{heartbeat}) - cs.evsw.FireEvent(types.EventProposalHeartbeat, heartbeat) - counter++ - time.Sleep(proposalHeartbeatIntervalSeconds * time.Second) - } -} - // Enter (CreateEmptyBlocks): from enterNewRound(height,round) // Enter (CreateEmptyBlocks, CreateEmptyBlocksInterval > 0 ): after enterNewRound(height,round), after timeout of CreateEmptyBlocksInterval // Enter (!CreateEmptyBlocks) : after enterNewRound(height,round), once txs are in the mempool diff --git a/consensus/state_test.go b/consensus/state_test.go index 941a99cd..ddab6404 100644 --- a/consensus/state_test.go +++ b/consensus/state_test.go @@ -11,8 +11,6 @@ import ( "github.com/stretchr/testify/require" cstypes "github.com/tendermint/tendermint/consensus/types" - tmevents "github.com/tendermint/tendermint/libs/events" - cmn "github.com/tendermint/tendermint/libs/common" "github.com/tendermint/tendermint/libs/log" tmpubsub "github.com/tendermint/tendermint/libs/pubsub" @@ -1029,33 +1027,6 @@ func TestSetValidBlockOnDelayedPrevote(t *testing.T) { assert.True(t, rs.ValidRound == round) } -// regression for #2518 -func TestNoHearbeatWhenNotValidator(t *testing.T) { - cs, _ := randConsensusState(4) - cs.Validators = types.NewValidatorSet(nil) // make sure we are not in the validator set - - cs.evsw.AddListenerForEvent("testing", types.EventProposalHeartbeat, - func(data tmevents.EventData) { - t.Errorf("Should not have broadcasted heartbeat") - }) - go cs.proposalHeartbeat(10, 1) - - cs.Stop() - - // if a faulty implementation sends an event, we should wait here a little bit to make sure we don't miss it by prematurely leaving the test method - time.Sleep((proposalHeartbeatIntervalSeconds + 1) * time.Second) -} - -// regression for #2518 -func TestHearbeatWhenWeAreValidator(t *testing.T) { - cs, _ := randConsensusState(4) - heartbeatCh := subscribe(cs.eventBus, types.EventQueryProposalHeartbeat) - - go cs.proposalHeartbeat(10, 1) - ensureProposalHeartbeat(heartbeatCh) - -} - // What we want: // P0 miss to lock B as Proposal Block is missing, but set valid block to B after // receiving delayed Block Proposal. diff --git a/docs/spec/reactors/consensus/consensus-reactor.md b/docs/spec/reactors/consensus/consensus-reactor.md index 23275b12..47c6949a 100644 --- a/docs/spec/reactors/consensus/consensus-reactor.md +++ b/docs/spec/reactors/consensus/consensus-reactor.md @@ -338,12 +338,11 @@ BlockID has seen +2/3 votes. This routine is based on the local RoundState (`rs` ## Broadcast routine -The Broadcast routine subscribes to an internal event bus to receive new round steps, votes messages and proposal -heartbeat messages, and broadcasts messages to peers upon receiving those events. +The Broadcast routine subscribes to an internal event bus to receive new round steps and votes messages, and broadcasts messages to peers upon receiving those +events. It broadcasts `NewRoundStepMessage` or `CommitStepMessage` upon new round state event. Note that broadcasting these messages does not depend on the PeerRoundState; it is sent on the StateChannel. Upon receiving VoteMessage it broadcasts `HasVoteMessage` message to its peers on the StateChannel. -`ProposalHeartbeatMessage` is sent the same way on the StateChannel. ## Channels diff --git a/docs/spec/reactors/consensus/consensus.md b/docs/spec/reactors/consensus/consensus.md index e5d1f4cc..55960874 100644 --- a/docs/spec/reactors/consensus/consensus.md +++ b/docs/spec/reactors/consensus/consensus.md @@ -89,33 +89,6 @@ type BlockPartMessage struct { } ``` -## ProposalHeartbeatMessage - -ProposalHeartbeatMessage is sent to signal that a node is alive and waiting for transactions -to be able to create a next block proposal. - -```go -type ProposalHeartbeatMessage struct { - Heartbeat Heartbeat -} -``` - -### Heartbeat - -Heartbeat contains validator information (address and index), -height, round and sequence number. It is signed by the private key of the validator. - -```go -type Heartbeat struct { - ValidatorAddress []byte - ValidatorIndex int - Height int64 - Round int - Sequence int - Signature Signature -} -``` - ## NewRoundStepMessage NewRoundStepMessage is sent for every step transition during the core consensus algorithm execution. diff --git a/privval/priv_validator.go b/privval/priv_validator.go index a13f5426..ba777e1f 100644 --- a/privval/priv_validator.go +++ b/privval/priv_validator.go @@ -290,19 +290,6 @@ func (pv *FilePV) saveSigned(height int64, round int, step int8, pv.save() } -// SignHeartbeat signs a canonical representation of the heartbeat, along with the chainID. -// Implements PrivValidator. -func (pv *FilePV) SignHeartbeat(chainID string, heartbeat *types.Heartbeat) error { - pv.mtx.Lock() - defer pv.mtx.Unlock() - sig, err := pv.PrivKey.Sign(heartbeat.SignBytes(chainID)) - if err != nil { - return err - } - heartbeat.Signature = sig - return nil -} - // String returns a string representation of the FilePV. func (pv *FilePV) String() string { return fmt.Sprintf("PrivValidator{%v LH:%v, LR:%v, LS:%v}", pv.GetAddress(), pv.LastHeight, pv.LastRound, pv.LastStep) diff --git a/privval/remote_signer.go b/privval/remote_signer.go index eacc840c..5d6339c3 100644 --- a/privval/remote_signer.go +++ b/privval/remote_signer.go @@ -125,35 +125,6 @@ func (sc *RemoteSignerClient) SignProposal( return nil } -// SignHeartbeat implements PrivValidator. -func (sc *RemoteSignerClient) SignHeartbeat( - chainID string, - heartbeat *types.Heartbeat, -) error { - sc.lock.Lock() - defer sc.lock.Unlock() - - err := writeMsg(sc.conn, &SignHeartbeatRequest{Heartbeat: heartbeat}) - if err != nil { - return err - } - - res, err := readMsg(sc.conn) - if err != nil { - return err - } - resp, ok := res.(*SignedHeartbeatResponse) - if !ok { - return ErrUnexpectedResponse - } - if resp.Error != nil { - return resp.Error - } - *heartbeat = *resp.Heartbeat - - return nil -} - // Ping is used to check connection health. func (sc *RemoteSignerClient) Ping() error { sc.lock.Lock() @@ -186,8 +157,6 @@ func RegisterRemoteSignerMsg(cdc *amino.Codec) { cdc.RegisterConcrete(&SignedVoteResponse{}, "tendermint/remotesigner/SignedVoteResponse", nil) cdc.RegisterConcrete(&SignProposalRequest{}, "tendermint/remotesigner/SignProposalRequest", nil) cdc.RegisterConcrete(&SignedProposalResponse{}, "tendermint/remotesigner/SignedProposalResponse", nil) - cdc.RegisterConcrete(&SignHeartbeatRequest{}, "tendermint/remotesigner/SignHeartbeatRequest", nil) - cdc.RegisterConcrete(&SignedHeartbeatResponse{}, "tendermint/remotesigner/SignedHeartbeatResponse", nil) cdc.RegisterConcrete(&PingRequest{}, "tendermint/remotesigner/PingRequest", nil) cdc.RegisterConcrete(&PingResponse{}, "tendermint/remotesigner/PingResponse", nil) } @@ -218,16 +187,6 @@ type SignedProposalResponse struct { Error *RemoteSignerError } -// SignHeartbeatRequest is a PrivValidatorSocket message containing a Heartbeat. -type SignHeartbeatRequest struct { - Heartbeat *types.Heartbeat -} - -type SignedHeartbeatResponse struct { - Heartbeat *types.Heartbeat - Error *RemoteSignerError -} - // PingRequest is a PrivValidatorSocket message to keep the connection alive. type PingRequest struct { } @@ -286,13 +245,6 @@ func handleRequest(req RemoteSignerMsg, chainID string, privVal types.PrivValida } else { res = &SignedProposalResponse{r.Proposal, nil} } - case *SignHeartbeatRequest: - err = privVal.SignHeartbeat(chainID, r.Heartbeat) - if err != nil { - res = &SignedHeartbeatResponse{nil, &RemoteSignerError{0, err.Error()}} - } else { - res = &SignedHeartbeatResponse{r.Heartbeat, nil} - } case *PingRequest: res = &PingResponse{} default: diff --git a/privval/tcp_test.go b/privval/tcp_test.go index 6549759d..d2489ad1 100644 --- a/privval/tcp_test.go +++ b/privval/tcp_test.go @@ -137,22 +137,6 @@ func TestSocketPVVoteKeepalive(t *testing.T) { assert.Equal(t, want.Signature, have.Signature) } -func TestSocketPVHeartbeat(t *testing.T) { - var ( - chainID = cmn.RandStr(12) - sc, rs = testSetupSocketPair(t, chainID, types.NewMockPV()) - - want = &types.Heartbeat{} - have = &types.Heartbeat{} - ) - defer sc.Stop() - defer rs.Stop() - - require.NoError(t, rs.privVal.SignHeartbeat(chainID, want)) - require.NoError(t, sc.SignHeartbeat(chainID, have)) - assert.Equal(t, want.Signature, have.Signature) -} - func TestSocketPVDeadline(t *testing.T) { var ( addr = testFreeAddr(t) @@ -301,32 +285,6 @@ func TestRemoteSignProposalErrors(t *testing.T) { require.Error(t, err) } -func TestRemoteSignHeartbeatErrors(t *testing.T) { - var ( - chainID = cmn.RandStr(12) - sc, rs = testSetupSocketPair(t, chainID, types.NewErroringMockPV()) - hb = &types.Heartbeat{} - ) - defer sc.Stop() - defer rs.Stop() - - err := writeMsg(sc.conn, &SignHeartbeatRequest{Heartbeat: hb}) - require.NoError(t, err) - - res, err := readMsg(sc.conn) - require.NoError(t, err) - - resp := *res.(*SignedHeartbeatResponse) - require.NotNil(t, resp.Error) - require.Equal(t, resp.Error.Description, types.ErroringMockPVErr.Error()) - - err = rs.privVal.SignHeartbeat(chainID, hb) - require.Error(t, err) - - err = sc.SignHeartbeat(chainID, hb) - require.Error(t, err) -} - func TestErrUnexpectedResponse(t *testing.T) { var ( addr = testFreeAddr(t) @@ -362,22 +320,12 @@ func TestErrUnexpectedResponse(t *testing.T) { require.NotNil(t, rsConn) <-readyc - // Heartbeat: - go func(errc chan error) { - errc <- sc.SignHeartbeat(chainID, &types.Heartbeat{}) - }(errc) - // read request and write wrong response: - go testReadWriteResponse(t, &SignedVoteResponse{}, rsConn) - err = <-errc - require.Error(t, err) - require.Equal(t, err, ErrUnexpectedResponse) - // Proposal: go func(errc chan error) { errc <- sc.SignProposal(chainID, &types.Proposal{}) }(errc) // read request and write wrong response: - go testReadWriteResponse(t, &SignedHeartbeatResponse{}, rsConn) + go testReadWriteResponse(t, &SignedVoteResponse{}, rsConn) err = <-errc require.Error(t, err) require.Equal(t, err, ErrUnexpectedResponse) @@ -387,7 +335,7 @@ func TestErrUnexpectedResponse(t *testing.T) { errc <- sc.SignVote(chainID, &types.Vote{}) }(errc) // read request and write wrong response: - go testReadWriteResponse(t, &SignedHeartbeatResponse{}, rsConn) + go testReadWriteResponse(t, &SignedProposalResponse{}, rsConn) err = <-errc require.Error(t, err) require.Equal(t, err, ErrUnexpectedResponse) diff --git a/types/canonical.go b/types/canonical.go index a4f6f214..eabd7684 100644 --- a/types/canonical.go +++ b/types/canonical.go @@ -41,16 +41,6 @@ type CanonicalVote struct { ChainID string } -type CanonicalHeartbeat struct { - Type byte - Height int64 `binary:"fixed64"` - Round int `binary:"fixed64"` - Sequence int `binary:"fixed64"` - ValidatorAddress Address - ValidatorIndex int - ChainID string -} - //----------------------------------- // Canonicalize the structs @@ -91,18 +81,6 @@ func CanonicalizeVote(chainID string, vote *Vote) CanonicalVote { } } -func CanonicalizeHeartbeat(chainID string, heartbeat *Heartbeat) CanonicalHeartbeat { - return CanonicalHeartbeat{ - Type: byte(HeartbeatType), - Height: heartbeat.Height, - Round: heartbeat.Round, - Sequence: heartbeat.Sequence, - ValidatorAddress: heartbeat.ValidatorAddress, - ValidatorIndex: heartbeat.ValidatorIndex, - ChainID: chainID, - } -} - // CanonicalTime can be used to stringify time in a canonical way. func CanonicalTime(t time.Time) string { // Note that sending time over amino resets it to diff --git a/types/event_bus.go b/types/event_bus.go index d941e9aa..055cbd3f 100644 --- a/types/event_bus.go +++ b/types/event_bus.go @@ -146,10 +146,6 @@ func (b *EventBus) PublishEventTx(data EventDataTx) error { return nil } -func (b *EventBus) PublishEventProposalHeartbeat(data EventDataProposalHeartbeat) error { - return b.Publish(EventProposalHeartbeat, data) -} - func (b *EventBus) PublishEventNewRoundStep(data EventDataRoundState) error { return b.Publish(EventNewRoundStep, data) } diff --git a/types/event_bus_test.go b/types/event_bus_test.go index 0af11ebd..6845927b 100644 --- a/types/event_bus_test.go +++ b/types/event_bus_test.go @@ -152,7 +152,7 @@ func TestEventBusPublish(t *testing.T) { err = eventBus.Subscribe(context.Background(), "test", tmquery.Empty{}, eventsCh) require.NoError(t, err) - const numEventsExpected = 15 + const numEventsExpected = 14 done := make(chan struct{}) go func() { numEvents := 0 @@ -172,8 +172,6 @@ func TestEventBusPublish(t *testing.T) { require.NoError(t, err) err = eventBus.PublishEventVote(EventDataVote{}) require.NoError(t, err) - err = eventBus.PublishEventProposalHeartbeat(EventDataProposalHeartbeat{}) - require.NoError(t, err) err = eventBus.PublishEventNewRoundStep(EventDataRoundState{}) require.NoError(t, err) err = eventBus.PublishEventTimeoutPropose(EventDataRoundState{}) diff --git a/types/events.go b/types/events.go index b22a1c8b..9b3b158d 100644 --- a/types/events.go +++ b/types/events.go @@ -18,7 +18,6 @@ const ( EventNewRound = "NewRound" EventNewRoundStep = "NewRoundStep" EventPolka = "Polka" - EventProposalHeartbeat = "ProposalHeartbeat" EventRelock = "Relock" EventTimeoutPropose = "TimeoutPropose" EventTimeoutWait = "TimeoutWait" @@ -47,7 +46,6 @@ func RegisterEventDatas(cdc *amino.Codec) { cdc.RegisterConcrete(EventDataNewRound{}, "tendermint/event/NewRound", nil) cdc.RegisterConcrete(EventDataCompleteProposal{}, "tendermint/event/CompleteProposal", nil) cdc.RegisterConcrete(EventDataVote{}, "tendermint/event/Vote", nil) - cdc.RegisterConcrete(EventDataProposalHeartbeat{}, "tendermint/event/ProposalHeartbeat", nil) cdc.RegisterConcrete(EventDataValidatorSetUpdates{}, "tendermint/event/ValidatorSetUpdates", nil) cdc.RegisterConcrete(EventDataString(""), "tendermint/event/ProposalString", nil) } @@ -75,10 +73,6 @@ type EventDataTx struct { TxResult } -type EventDataProposalHeartbeat struct { - Heartbeat *Heartbeat -} - // NOTE: This goes into the replay WAL type EventDataRoundState struct { Height int64 `json:"height"` @@ -143,7 +137,6 @@ var ( EventQueryNewRound = QueryForEvent(EventNewRound) EventQueryNewRoundStep = QueryForEvent(EventNewRoundStep) EventQueryPolka = QueryForEvent(EventPolka) - EventQueryProposalHeartbeat = QueryForEvent(EventProposalHeartbeat) EventQueryRelock = QueryForEvent(EventRelock) EventQueryTimeoutPropose = QueryForEvent(EventTimeoutPropose) EventQueryTimeoutWait = QueryForEvent(EventTimeoutWait) diff --git a/types/heartbeat.go b/types/heartbeat.go deleted file mode 100644 index 986e9384..00000000 --- a/types/heartbeat.go +++ /dev/null @@ -1,83 +0,0 @@ -package types - -import ( - "fmt" - - "github.com/pkg/errors" - "github.com/tendermint/tendermint/crypto" - cmn "github.com/tendermint/tendermint/libs/common" -) - -// Heartbeat is a simple vote-like structure so validators can -// alert others that they are alive and waiting for transactions. -// Note: We aren't adding ",omitempty" to Heartbeat's -// json field tags because we always want the JSON -// representation to be in its canonical form. -type Heartbeat struct { - ValidatorAddress Address `json:"validator_address"` - ValidatorIndex int `json:"validator_index"` - Height int64 `json:"height"` - Round int `json:"round"` - Sequence int `json:"sequence"` - Signature []byte `json:"signature"` -} - -// SignBytes returns the Heartbeat bytes for signing. -// It panics if the Heartbeat is nil. -func (heartbeat *Heartbeat) SignBytes(chainID string) []byte { - bz, err := cdc.MarshalBinaryLengthPrefixed(CanonicalizeHeartbeat(chainID, heartbeat)) - if err != nil { - panic(err) - } - return bz -} - -// Copy makes a copy of the Heartbeat. -func (heartbeat *Heartbeat) Copy() *Heartbeat { - if heartbeat == nil { - return nil - } - heartbeatCopy := *heartbeat - return &heartbeatCopy -} - -// String returns a string representation of the Heartbeat. -func (heartbeat *Heartbeat) String() string { - if heartbeat == nil { - return "nil-heartbeat" - } - - return fmt.Sprintf("Heartbeat{%v:%X %v/%02d (%v) %v}", - heartbeat.ValidatorIndex, cmn.Fingerprint(heartbeat.ValidatorAddress), - heartbeat.Height, heartbeat.Round, heartbeat.Sequence, - fmt.Sprintf("/%X.../", cmn.Fingerprint(heartbeat.Signature[:]))) -} - -// ValidateBasic performs basic validation. -func (heartbeat *Heartbeat) ValidateBasic() error { - if len(heartbeat.ValidatorAddress) != crypto.AddressSize { - return fmt.Errorf("Expected ValidatorAddress size to be %d bytes, got %d bytes", - crypto.AddressSize, - len(heartbeat.ValidatorAddress), - ) - } - if heartbeat.ValidatorIndex < 0 { - return errors.New("Negative ValidatorIndex") - } - if heartbeat.Height < 0 { - return errors.New("Negative Height") - } - if heartbeat.Round < 0 { - return errors.New("Negative Round") - } - if heartbeat.Sequence < 0 { - return errors.New("Negative Sequence") - } - if len(heartbeat.Signature) == 0 { - return errors.New("Signature is missing") - } - if len(heartbeat.Signature) > MaxSignatureSize { - return fmt.Errorf("Signature is too big (max: %d)", MaxSignatureSize) - } - return nil -} diff --git a/types/heartbeat_test.go b/types/heartbeat_test.go deleted file mode 100644 index 0951c7b9..00000000 --- a/types/heartbeat_test.go +++ /dev/null @@ -1,104 +0,0 @@ -package types - -import ( - "testing" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - "github.com/tendermint/tendermint/crypto/ed25519" - "github.com/tendermint/tendermint/crypto/secp256k1" -) - -func TestHeartbeatCopy(t *testing.T) { - hb := &Heartbeat{ValidatorIndex: 1, Height: 10, Round: 1} - hbCopy := hb.Copy() - require.Equal(t, hbCopy, hb, "heartbeat copy should be the same") - hbCopy.Round = hb.Round + 10 - require.NotEqual(t, hbCopy, hb, "heartbeat copy mutation should not change original") - - var nilHb *Heartbeat - nilHbCopy := nilHb.Copy() - require.Nil(t, nilHbCopy, "copy of nil should also return nil") -} - -func TestHeartbeatString(t *testing.T) { - var nilHb *Heartbeat - require.Contains(t, nilHb.String(), "nil", "expecting a string and no panic") - - hb := &Heartbeat{ValidatorIndex: 1, Height: 11, Round: 2} - require.Equal(t, "Heartbeat{1:000000000000 11/02 (0) /000000000000.../}", hb.String()) - - var key ed25519.PrivKeyEd25519 - sig, err := key.Sign([]byte("Tendermint")) - require.NoError(t, err) - hb.Signature = sig - require.Equal(t, "Heartbeat{1:000000000000 11/02 (0) /FF41E371B9BF.../}", hb.String()) -} - -func TestHeartbeatWriteSignBytes(t *testing.T) { - chainID := "test_chain_id" - - { - testHeartbeat := &Heartbeat{ValidatorIndex: 1, Height: 10, Round: 1} - signBytes := testHeartbeat.SignBytes(chainID) - expected, err := cdc.MarshalBinaryLengthPrefixed(CanonicalizeHeartbeat(chainID, testHeartbeat)) - require.NoError(t, err) - require.Equal(t, expected, signBytes, "Got unexpected sign bytes for Heartbeat") - } - - { - testHeartbeat := &Heartbeat{} - signBytes := testHeartbeat.SignBytes(chainID) - expected, err := cdc.MarshalBinaryLengthPrefixed(CanonicalizeHeartbeat(chainID, testHeartbeat)) - require.NoError(t, err) - require.Equal(t, expected, signBytes, "Got unexpected sign bytes for Heartbeat") - } - - require.Panics(t, func() { - var nilHb *Heartbeat - signBytes := nilHb.SignBytes(chainID) - require.Equal(t, string(signBytes), "null") - }) -} - -func TestHeartbeatValidateBasic(t *testing.T) { - testCases := []struct { - testName string - malleateHeartBeat func(*Heartbeat) - expectErr bool - }{ - {"Good HeartBeat", func(hb *Heartbeat) {}, false}, - {"Invalid address size", func(hb *Heartbeat) { - hb.ValidatorAddress = nil - }, true}, - {"Negative validator index", func(hb *Heartbeat) { - hb.ValidatorIndex = -1 - }, true}, - {"Negative height", func(hb *Heartbeat) { - hb.Height = -1 - }, true}, - {"Negative round", func(hb *Heartbeat) { - hb.Round = -1 - }, true}, - {"Negative sequence", func(hb *Heartbeat) { - hb.Sequence = -1 - }, true}, - {"Missing signature", func(hb *Heartbeat) { - hb.Signature = nil - }, true}, - {"Signature too big", func(hb *Heartbeat) { - hb.Signature = make([]byte, MaxSignatureSize+1) - }, true}, - } - for _, tc := range testCases { - t.Run(tc.testName, func(t *testing.T) { - hb := &Heartbeat{ - ValidatorAddress: secp256k1.GenPrivKey().PubKey().Address(), - Signature: make([]byte, 4), - ValidatorIndex: 1, Height: 10, Round: 1} - - tc.malleateHeartBeat(hb) - assert.Equal(t, tc.expectErr, hb.ValidateBasic() != nil, "Validate Basic had an unexpected result") - }) - } -} diff --git a/types/priv_validator.go b/types/priv_validator.go index 25be5220..ebd64446 100644 --- a/types/priv_validator.go +++ b/types/priv_validator.go @@ -10,14 +10,13 @@ import ( ) // PrivValidator defines the functionality of a local Tendermint validator -// that signs votes, proposals, and heartbeats, and never double signs. +// that signs votes and proposals, and never double signs. type PrivValidator interface { GetAddress() Address // redundant since .PubKey().Address() GetPubKey() crypto.PubKey SignVote(chainID string, vote *Vote) error SignProposal(chainID string, proposal *Proposal) error - SignHeartbeat(chainID string, heartbeat *Heartbeat) error } //---------------------------------------- @@ -84,16 +83,6 @@ func (pv *MockPV) SignProposal(chainID string, proposal *Proposal) error { return nil } -// signHeartbeat signs the heartbeat without any checking. -func (pv *MockPV) SignHeartbeat(chainID string, heartbeat *Heartbeat) error { - sig, err := pv.privKey.Sign(heartbeat.SignBytes(chainID)) - if err != nil { - return err - } - heartbeat.Signature = sig - return nil -} - // String returns a string representation of the MockPV. func (pv *MockPV) String() string { return fmt.Sprintf("MockPV{%v}", pv.GetAddress()) @@ -121,11 +110,6 @@ func (pv *erroringMockPV) SignProposal(chainID string, proposal *Proposal) error return ErroringMockPVErr } -// signHeartbeat signs the heartbeat without any checking. -func (pv *erroringMockPV) SignHeartbeat(chainID string, heartbeat *Heartbeat) error { - return ErroringMockPVErr -} - // NewErroringMockPV returns a MockPV that fails on each signing request. Again, for testing only. func NewErroringMockPV() *erroringMockPV { return &erroringMockPV{&MockPV{ed25519.GenPrivKey()}} diff --git a/types/signable.go b/types/signable.go index baabdff0..72d2ea9a 100644 --- a/types/signable.go +++ b/types/signable.go @@ -6,8 +6,8 @@ import ( ) var ( - // MaxSignatureSize is a maximum allowed signature size for the Heartbeat, - // Proposal and Vote. + // MaxSignatureSize is a maximum allowed signature size for the Proposal + // and Vote. // XXX: secp256k1 does not have Size nor MaxSize defined. MaxSignatureSize = cmn.MaxInt(ed25519.SignatureSize, 64) ) diff --git a/types/signed_msg_type.go b/types/signed_msg_type.go index 10e7c70c..301feec9 100644 --- a/types/signed_msg_type.go +++ b/types/signed_msg_type.go @@ -11,8 +11,6 @@ const ( // Proposals ProposalType SignedMsgType = 0x20 - // Heartbeat - HeartbeatType SignedMsgType = 0x30 ) // IsVoteTypeValid returns true if t is a valid vote type. From 4571f0fbe8b583c30310e9df5fd0d3d0be79992d Mon Sep 17 00:00:00 2001 From: Dev Ojha Date: Wed, 28 Nov 2018 06:09:27 -0800 Subject: [PATCH 119/267] Enforce validators can only use the correct pubkey type (#2739) * Enforce validators can only use the correct pubkey type * adapt to variable renames * Address comments from #2636 * separate updating and validation logic * update spec * Add test case for TestStringSliceEqual, clarify slice copying code * Address @ebuchman's comments * Split up testing validator update execution, and its validation --- CHANGELOG_PENDING.md | 1 + docs/spec/blockchain/state.md | 4 ++ libs/common/string.go | 13 ++++++ libs/common/string_test.go | 21 +++++++++ state/execution.go | 35 +++++++++++++-- state/execution_test.go | 83 ++++++++++++++++++++++++++++++----- types/params.go | 27 ++++++------ types/protobuf.go | 13 +++--- 8 files changed, 161 insertions(+), 36 deletions(-) diff --git a/CHANGELOG_PENDING.md b/CHANGELOG_PENDING.md index f4ade603..aef86c92 100644 --- a/CHANGELOG_PENDING.md +++ b/CHANGELOG_PENDING.md @@ -20,6 +20,7 @@ program](https://hackerone.com/tendermint). - [db] [\#2913](https://github.com/tendermint/tendermint/pull/2913) ReverseIterator API change -- start < end, and end is exclusive. * Blockchain Protocol + * [state] \#2714 Validators can now only use pubkeys allowed within ConsensusParams.ValidatorParams * P2P Protocol diff --git a/docs/spec/blockchain/state.md b/docs/spec/blockchain/state.md index 0a07890f..0b46e503 100644 --- a/docs/spec/blockchain/state.md +++ b/docs/spec/blockchain/state.md @@ -98,6 +98,10 @@ type Evidence struct { type Validator struct { PubKeyTypes []string } + +type ValidatorParams struct { + PubKeyTypes []string +} ``` #### BlockSize diff --git a/libs/common/string.go b/libs/common/string.go index e125043d..ddf350b1 100644 --- a/libs/common/string.go +++ b/libs/common/string.go @@ -61,3 +61,16 @@ func ASCIITrim(s string) string { } return string(r) } + +// StringSliceEqual checks if string slices a and b are equal +func StringSliceEqual(a, b []string) bool { + if len(a) != len(b) { + return false + } + for i := 0; i < len(a); i++ { + if a[i] != b[i] { + return false + } + } + return true +} diff --git a/libs/common/string_test.go b/libs/common/string_test.go index 5e7ae98c..35b6fafc 100644 --- a/libs/common/string_test.go +++ b/libs/common/string_test.go @@ -3,6 +3,8 @@ package common import ( "testing" + "github.com/stretchr/testify/require" + "github.com/stretchr/testify/assert" ) @@ -35,3 +37,22 @@ func TestASCIITrim(t *testing.T) { assert.Equal(t, ASCIITrim(" a "), "a") assert.Panics(t, func() { ASCIITrim("\xC2\xA2") }) } + +func TestStringSliceEqual(t *testing.T) { + tests := []struct { + a []string + b []string + want bool + }{ + {[]string{"hello", "world"}, []string{"hello", "world"}, true}, + {[]string{"test"}, []string{"test"}, true}, + {[]string{"test1"}, []string{"test2"}, false}, + {[]string{"hello", "world."}, []string{"hello", "world!"}, false}, + {[]string{"only 1 word"}, []string{"two", "words!"}, false}, + {[]string{"two", "words!"}, []string{"only 1 word"}, false}, + } + for i, tt := range tests { + require.Equal(t, tt.want, StringSliceEqual(tt.a, tt.b), + "StringSliceEqual failed on test %d", i) + } +} diff --git a/state/execution.go b/state/execution.go index 61a48d36..f1fc5e4a 100644 --- a/state/execution.go +++ b/state/execution.go @@ -107,12 +107,16 @@ func (blockExec *BlockExecutor) ApplyBlock(state State, blockID types.BlockID, b fail.Fail() // XXX - // these are tendermint types now - validatorUpdates, err := types.PB2TM.ValidatorUpdates(abciResponses.EndBlock.ValidatorUpdates) + // validate the validator updates and convert to tendermint types + abciValUpdates := abciResponses.EndBlock.ValidatorUpdates + err = validateValidatorUpdates(abciValUpdates, state.ConsensusParams.Validator) + if err != nil { + return state, fmt.Errorf("Error in validator updates: %v", err) + } + validatorUpdates, err := types.PB2TM.ValidatorUpdates(abciValUpdates) if err != nil { return state, err } - if len(validatorUpdates) > 0 { blockExec.logger.Info("Updates to validators", "updates", makeValidatorUpdatesLogString(validatorUpdates)) } @@ -318,17 +322,40 @@ func getBeginBlockValidatorInfo(block *types.Block, lastValSet *types.ValidatorS } +func validateValidatorUpdates(abciUpdates []abci.ValidatorUpdate, + params types.ValidatorParams) error { + for _, valUpdate := range abciUpdates { + if valUpdate.GetPower() < 0 { + return fmt.Errorf("Voting power can't be negative %v", valUpdate) + } else if valUpdate.GetPower() == 0 { + // continue, since this is deleting the validator, and thus there is no + // pubkey to check + continue + } + + // Check if validator's pubkey matches an ABCI type in the consensus params + thisKeyType := valUpdate.PubKey.Type + if !params.IsValidPubkeyType(thisKeyType) { + return fmt.Errorf("Validator %v is using pubkey %s, which is unsupported for consensus", + valUpdate, thisKeyType) + } + } + return nil +} + // If more or equal than 1/3 of total voting power changed in one block, then // a light client could never prove the transition externally. See // ./lite/doc.go for details on how a light client tracks validators. func updateValidators(currentSet *types.ValidatorSet, updates []*types.Validator) error { for _, valUpdate := range updates { + // should already have been checked if valUpdate.VotingPower < 0 { return fmt.Errorf("Voting power can't be negative %v", valUpdate) } address := valUpdate.Address _, val := currentSet.GetByAddress(address) + // valUpdate.VotingPower is ensured to be non-negative in validation method if valUpdate.VotingPower == 0 { // remove val _, removed := currentSet.Remove(address) @@ -367,7 +394,7 @@ func updateState( // Update the validator set with the latest abciResponses. lastHeightValsChanged := state.LastHeightValidatorsChanged - if len(abciResponses.EndBlock.ValidatorUpdates) > 0 { + if len(validatorUpdates) > 0 { err := updateValidators(nValSet, validatorUpdates) if err != nil { return state, fmt.Errorf("Error changing validator set: %v", err) diff --git a/state/execution_test.go b/state/execution_test.go index 03f52179..21df1ee5 100644 --- a/state/execution_test.go +++ b/state/execution_test.go @@ -12,6 +12,7 @@ import ( "github.com/tendermint/tendermint/abci/example/kvstore" abci "github.com/tendermint/tendermint/abci/types" "github.com/tendermint/tendermint/crypto/ed25519" + "github.com/tendermint/tendermint/crypto/secp256k1" cmn "github.com/tendermint/tendermint/libs/common" dbm "github.com/tendermint/tendermint/libs/db" "github.com/tendermint/tendermint/libs/log" @@ -152,6 +153,76 @@ func TestBeginBlockByzantineValidators(t *testing.T) { } } +func TestValidateValidatorUpdates(t *testing.T) { + pubkey1 := ed25519.GenPrivKey().PubKey() + pubkey2 := ed25519.GenPrivKey().PubKey() + + secpKey := secp256k1.GenPrivKey().PubKey() + + defaultValidatorParams := types.ValidatorParams{[]string{types.ABCIPubKeyTypeEd25519}} + + testCases := []struct { + name string + + abciUpdates []abci.ValidatorUpdate + validatorParams types.ValidatorParams + + shouldErr bool + }{ + { + "adding a validator is OK", + + []abci.ValidatorUpdate{{PubKey: types.TM2PB.PubKey(pubkey2), Power: 20}}, + defaultValidatorParams, + + false, + }, + { + "updating a validator is OK", + + []abci.ValidatorUpdate{{PubKey: types.TM2PB.PubKey(pubkey1), Power: 20}}, + defaultValidatorParams, + + false, + }, + { + "removing a validator is OK", + + []abci.ValidatorUpdate{{PubKey: types.TM2PB.PubKey(pubkey2), Power: 0}}, + defaultValidatorParams, + + false, + }, + { + "adding a validator with negative power results in error", + + []abci.ValidatorUpdate{{PubKey: types.TM2PB.PubKey(pubkey2), Power: -100}}, + defaultValidatorParams, + + true, + }, + { + "adding a validator with pubkey thats not in validator params results in error", + + []abci.ValidatorUpdate{{PubKey: types.TM2PB.PubKey(secpKey), Power: -100}}, + defaultValidatorParams, + + true, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + err := validateValidatorUpdates(tc.abciUpdates, tc.validatorParams) + if tc.shouldErr { + assert.Error(t, err) + } else { + assert.NoError(t, err) + } + }) + } +} + func TestUpdateValidators(t *testing.T) { pubkey1 := ed25519.GenPrivKey().PubKey() val1 := types.NewValidator(pubkey1, 10) @@ -194,7 +265,6 @@ func TestUpdateValidators(t *testing.T) { types.NewValidatorSet([]*types.Validator{val1}), false, }, - { "removing a non-existing validator results in error", @@ -204,16 +274,6 @@ func TestUpdateValidators(t *testing.T) { types.NewValidatorSet([]*types.Validator{val1}), true, }, - - { - "adding a validator with negative power results in error", - - types.NewValidatorSet([]*types.Validator{val1}), - []abci.ValidatorUpdate{{PubKey: types.TM2PB.PubKey(pubkey2), Power: -100}}, - - types.NewValidatorSet([]*types.Validator{val1}), - true, - }, } for _, tc := range testCases { @@ -224,6 +284,7 @@ func TestUpdateValidators(t *testing.T) { if tc.shouldErr { assert.Error(t, err) } else { + assert.NoError(t, err) require.Equal(t, tc.resultingSet.Size(), tc.currentSet.Size()) assert.Equal(t, tc.resultingSet.TotalVotingPower(), tc.currentSet.TotalVotingPower()) diff --git a/types/params.go b/types/params.go index 81cf429f..ec8a8f57 100644 --- a/types/params.go +++ b/types/params.go @@ -69,6 +69,15 @@ func DefaultValidatorParams() ValidatorParams { return ValidatorParams{[]string{ABCIPubKeyTypeEd25519}} } +func (params *ValidatorParams) IsValidPubkeyType(pubkeyType string) bool { + for i := 0; i < len(params.PubKeyTypes); i++ { + if params.PubKeyTypes[i] == pubkeyType { + return true + } + } + return false +} + // Validate validates the ConsensusParams to ensure all values are within their // allowed limits, and returns an error if they are not. func (params *ConsensusParams) Validate() error { @@ -124,19 +133,7 @@ func (params *ConsensusParams) Hash() []byte { func (params *ConsensusParams) Equals(params2 *ConsensusParams) bool { return params.BlockSize == params2.BlockSize && params.Evidence == params2.Evidence && - stringSliceEqual(params.Validator.PubKeyTypes, params2.Validator.PubKeyTypes) -} - -func stringSliceEqual(a, b []string) bool { - if len(a) != len(b) { - return false - } - for i := 0; i < len(a); i++ { - if a[i] != b[i] { - return false - } - } - return true + cmn.StringSliceEqual(params.Validator.PubKeyTypes, params2.Validator.PubKeyTypes) } // Update returns a copy of the params with updates from the non-zero fields of p2. @@ -157,7 +154,9 @@ func (params ConsensusParams) Update(params2 *abci.ConsensusParams) ConsensusPar res.Evidence.MaxAge = params2.Evidence.MaxAge } if params2.Validator != nil { - res.Validator.PubKeyTypes = params2.Validator.PubKeyTypes + // Copy params2.Validator.PubkeyTypes, and set result's value to the copy. + // This avoids having to initialize the slice to 0 values, and then write to it again. + res.Validator.PubKeyTypes = append([]string{}, params2.Validator.PubKeyTypes...) } return res } diff --git a/types/protobuf.go b/types/protobuf.go index 1535c1e3..0f0d25de 100644 --- a/types/protobuf.go +++ b/types/protobuf.go @@ -187,20 +187,19 @@ var PB2TM = pb2tm{} type pb2tm struct{} func (pb2tm) PubKey(pubKey abci.PubKey) (crypto.PubKey, error) { - // TODO: define these in crypto and use them - sizeEd := 32 - sizeSecp := 33 switch pubKey.Type { case ABCIPubKeyTypeEd25519: - if len(pubKey.Data) != sizeEd { - return nil, fmt.Errorf("Invalid size for PubKeyEd25519. Got %d, expected %d", len(pubKey.Data), sizeEd) + if len(pubKey.Data) != ed25519.PubKeyEd25519Size { + return nil, fmt.Errorf("Invalid size for PubKeyEd25519. Got %d, expected %d", + len(pubKey.Data), ed25519.PubKeyEd25519Size) } var pk ed25519.PubKeyEd25519 copy(pk[:], pubKey.Data) return pk, nil case ABCIPubKeyTypeSecp256k1: - if len(pubKey.Data) != sizeSecp { - return nil, fmt.Errorf("Invalid size for PubKeyEd25519. Got %d, expected %d", len(pubKey.Data), sizeSecp) + if len(pubKey.Data) != secp256k1.PubKeySecp256k1Size { + return nil, fmt.Errorf("Invalid size for PubKeySecp256k1. Got %d, expected %d", + len(pubKey.Data), secp256k1.PubKeySecp256k1Size) } var pk secp256k1.PubKeySecp256k1 copy(pk[:], pubKey.Data) From 3d15579e0c235e0c425405f409c844b16d369ec8 Mon Sep 17 00:00:00 2001 From: Zach Date: Wed, 28 Nov 2018 11:29:26 -0500 Subject: [PATCH 120/267] docs: fix js-abci example (#2935) --- docs/app-dev/getting-started.md | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/docs/app-dev/getting-started.md b/docs/app-dev/getting-started.md index d38615a2..5509a701 100644 --- a/docs/app-dev/getting-started.md +++ b/docs/app-dev/getting-started.md @@ -256,9 +256,8 @@ You'll also need to fetch the relevant repository, from ``` git clone https://github.com/tendermint/js-abci.git -cd js-abci/example -npm install -cd .. +cd js-abci +npm install abci ``` Kill the previous `counter` and `tendermint` processes. Now run the app: From 9adcfe2804f4679086793f09b816e0e4f9287fa1 Mon Sep 17 00:00:00 2001 From: Daniil Lashin Date: Wed, 28 Nov 2018 19:55:18 +0300 Subject: [PATCH 121/267] docs: add client.Start() to RPC WS examples (#2936) --- rpc/core/events.go | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/rpc/core/events.go b/rpc/core/events.go index e7456f35..98c81fac 100644 --- a/rpc/core/events.go +++ b/rpc/core/events.go @@ -54,11 +54,12 @@ import ( // import "github.com/tendermint/tendermint/types" // // client := client.NewHTTP("tcp://0.0.0.0:26657", "/websocket") +// err := client.Start() // ctx, cancel := context.WithTimeout(context.Background(), timeout) // defer cancel() // query := query.MustParse("tm.event = 'Tx' AND tx.height = 3") // txs := make(chan interface{}) -// err := client.Subscribe(ctx, "test-client", query, txs) +// err = client.Subscribe(ctx, "test-client", query, txs) // // go func() { // for e := range txs { @@ -116,7 +117,8 @@ func Subscribe(wsCtx rpctypes.WSRPCContext, query string) (*ctypes.ResultSubscri // // ```go // client := client.NewHTTP("tcp://0.0.0.0:26657", "/websocket") -// err := client.Unsubscribe("test-client", query) +// err := client.Start() +// err = client.Unsubscribe("test-client", query) // ``` // // > The above command returns JSON structured like this: @@ -155,7 +157,8 @@ func Unsubscribe(wsCtx rpctypes.WSRPCContext, query string) (*ctypes.ResultUnsub // // ```go // client := client.NewHTTP("tcp://0.0.0.0:26657", "/websocket") -// err := client.UnsubscribeAll("test-client") +// err := client.Start() +// err = client.UnsubscribeAll("test-client") // ``` // // > The above command returns JSON structured like this: From b11788d36d70df6756a886959c71291cb16ca4c5 Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Wed, 28 Nov 2018 13:09:29 -0500 Subject: [PATCH 122/267] types: NewValidatorSet doesn't panic on empty valz list (#2938) * types: NewValidatorSet doesn't panic on empty valz list * changelog --- CHANGELOG_PENDING.md | 2 ++ types/validator_set.go | 6 +++--- types/validator_set_test.go | 7 +++++-- 3 files changed, 10 insertions(+), 5 deletions(-) diff --git a/CHANGELOG_PENDING.md b/CHANGELOG_PENDING.md index aef86c92..7198e9d4 100644 --- a/CHANGELOG_PENDING.md +++ b/CHANGELOG_PENDING.md @@ -30,3 +30,5 @@ program](https://hackerone.com/tendermint). - [consensus] [\#2871](https://github.com/tendermint/tendermint/issues/2871) Remove *ProposalHeartbeat* infrastructure as it serves no real purpose ### BUG FIXES: +- [types] \#2938 Fix regression in v0.26.4 where we panic on empty + genDoc.Validators diff --git a/types/validator_set.go b/types/validator_set.go index f5e57077..d1bce28c 100644 --- a/types/validator_set.go +++ b/types/validator_set.go @@ -29,10 +29,10 @@ type ValidatorSet struct { totalVotingPower int64 } +// NewValidatorSet initializes a ValidatorSet by copying over the +// values from `valz`, a list of Validators. If valz is nil or empty, +// the new ValidatorSet will have an empty list of Validators. func NewValidatorSet(valz []*Validator) *ValidatorSet { - if valz != nil && len(valz) == 0 { - panic("validator set initialization slice cannot be an empty slice (but it can be nil)") - } validators := make([]*Validator, len(valz)) for i, val := range valz { validators[i] = val.Copy() diff --git a/types/validator_set_test.go b/types/validator_set_test.go index 81124637..d7a6a5d2 100644 --- a/types/validator_set_test.go +++ b/types/validator_set_test.go @@ -17,9 +17,12 @@ import ( ) func TestValidatorSetBasic(t *testing.T) { - assert.Panics(t, func() { NewValidatorSet([]*Validator{}) }) + // empty or nil validator lists are allowed, + // but attempting to IncrementAccum on them will panic. + vset := NewValidatorSet([]*Validator{}) + assert.Panics(t, func() { vset.IncrementAccum(1) }) - vset := NewValidatorSet(nil) + vset = NewValidatorSet(nil) assert.Panics(t, func() { vset.IncrementAccum(1) }) assert.EqualValues(t, vset, vset.Copy()) From 3f987adc921a2ed54baa6267cc969936f20c7d26 Mon Sep 17 00:00:00 2001 From: Ismail Khoffi Date: Wed, 28 Nov 2018 19:12:17 +0100 Subject: [PATCH 123/267] Set accum of freshly added validator -(total voting power) (#2785) * set the accum of a new validator to (-total voting power): - disincentivize validators to unbond, then rebon to reset their negative Accum to zero additional unrelated changes: - do not capitalize error msgs - fix typo * review comments: (re)capitalize errors & delete obsolete comments * More changes suggested by @melekes * WIP: do not batch clip (#2809) * substract avgAccum on each iteration - temporarily skip test * remove unused method safeMulClip / safeMul * always substract the avg accum - temp. skip another test * remove overflow / underflow tests & add tests for avgAccum: - add test for computeAvgAccum - as we substract the avgAccum now we will not trivially over/underflow * address @cwgoes' comments * shift by avg at the end of IncrementAccum * Add comment to MaxTotalVotingPower * Guard inputs to not exceed MaxTotalVotingPower * Address review comments: - do not fetch current validator from set again - update error message * Address a few review comments: - fix typo - extract variable * address more review comments: - clarify 1.125*totalVotingPower == totalVotingPower + (totalVotingPower >> 3) * review comments: panic instead of "clipping": - total voting power is guarded to not exceed MaxTotalVotingPower -> panic if this invariant is violated * fix failing test --- lite/doc.go | 2 +- state/execution.go | 40 ++++++++++-- types/signed_msg_type.go | 1 - types/validator.go | 4 +- types/validator_set.go | 125 ++++++++++++++++++++++-------------- types/validator_set_test.go | 83 +++++++++--------------- 6 files changed, 144 insertions(+), 111 deletions(-) diff --git a/lite/doc.go b/lite/doc.go index 00dcce68..f68798dc 100644 --- a/lite/doc.go +++ b/lite/doc.go @@ -121,7 +121,7 @@ If we cannot update directly from H -> H' because there was too much change to the validator set, then we can look for some Hm (H < Hm < H') with a validator set Vm. Then we try to update H -> Hm and then Hm -> H' in two steps. If one of these steps doesn't work, then we continue bisecting, until we eventually -have to externally validate the valdiator set changes at every block. +have to externally validate the validator set changes at every block. Since we never trust any server in this protocol, only the signatures themselves, it doesn't matter if the seed comes from a (possibly malicious) diff --git a/state/execution.go b/state/execution.go index f1fc5e4a..06f246f8 100644 --- a/state/execution.go +++ b/state/execution.go @@ -356,20 +356,48 @@ func updateValidators(currentSet *types.ValidatorSet, updates []*types.Validator address := valUpdate.Address _, val := currentSet.GetByAddress(address) // valUpdate.VotingPower is ensured to be non-negative in validation method - if valUpdate.VotingPower == 0 { - // remove val + if valUpdate.VotingPower == 0 { // remove val _, removed := currentSet.Remove(address) if !removed { return fmt.Errorf("Failed to remove validator %X", address) } - } else if val == nil { - // add val + } else if val == nil { // add val + // make sure we do not exceed MaxTotalVotingPower by adding this validator: + totalVotingPower := currentSet.TotalVotingPower() + updatedVotingPower := valUpdate.VotingPower + totalVotingPower + overflow := updatedVotingPower > types.MaxTotalVotingPower || updatedVotingPower < 0 + if overflow { + return fmt.Errorf( + "Failed to add new validator %v. Adding it would exceed max allowed total voting power %v", + valUpdate, + types.MaxTotalVotingPower) + } + // TODO: issue #1558 update spec according to the following: + // Set Accum to -C*totalVotingPower (with C ~= 1.125) to make sure validators can't + // unbond/rebond to reset their (potentially previously negative) Accum to zero. + // + // Contract: totalVotingPower < MaxTotalVotingPower to ensure Accum does + // not exceed the bounds of int64. + // + // Compute Accum = -1.125*totalVotingPower == -(totalVotingPower + (totalVotingPower >> 3)). + valUpdate.Accum = -(totalVotingPower + (totalVotingPower >> 3)) added := currentSet.Add(valUpdate) if !added { return fmt.Errorf("Failed to add new validator %v", valUpdate) } - } else { - // update val + } else { // update val + // make sure we do not exceed MaxTotalVotingPower by updating this validator: + totalVotingPower := currentSet.TotalVotingPower() + curVotingPower := val.VotingPower + updatedVotingPower := totalVotingPower - curVotingPower + valUpdate.VotingPower + overflow := updatedVotingPower > types.MaxTotalVotingPower || updatedVotingPower < 0 + if overflow { + return fmt.Errorf( + "Failed to update existing validator %v. Updating it would exceed max allowed total voting power %v", + valUpdate, + types.MaxTotalVotingPower) + } + updated := currentSet.Update(valUpdate) if !updated { return fmt.Errorf("Failed to update validator %X to %v", address, valUpdate) diff --git a/types/signed_msg_type.go b/types/signed_msg_type.go index 301feec9..6bd5f057 100644 --- a/types/signed_msg_type.go +++ b/types/signed_msg_type.go @@ -10,7 +10,6 @@ const ( // Proposals ProposalType SignedMsgType = 0x20 - ) // IsVoteTypeValid returns true if t is a valid vote type. diff --git a/types/validator.go b/types/validator.go index 4bfd78a6..cffc2854 100644 --- a/types/validator.go +++ b/types/validator.go @@ -81,13 +81,13 @@ func (v *Validator) Hash() []byte { // as its redundant with the pubkey. This also excludes accum which changes // every round. func (v *Validator) Bytes() []byte { - return cdcEncode((struct { + return cdcEncode(struct { PubKey crypto.PubKey VotingPower int64 }{ v.PubKey, v.VotingPower, - })) + }) } //---------------------------------------- diff --git a/types/validator_set.go b/types/validator_set.go index d1bce28c..36660885 100644 --- a/types/validator_set.go +++ b/types/validator_set.go @@ -4,6 +4,7 @@ import ( "bytes" "fmt" "math" + "math/big" "sort" "strings" @@ -11,6 +12,15 @@ import ( cmn "github.com/tendermint/tendermint/libs/common" ) +// The maximum allowed total voting power. +// We set the accum of freshly added validators to -1.125*totalVotingPower. +// To compute 1.125*totalVotingPower efficiently, we do: +// totalVotingPower + (totalVotingPower >> 3) because +// 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 + // ValidatorSet represent a set of *Validator at a given height. // The validators can be fetched by address or index. // The index is in order of .Address, so the indices are fixed @@ -68,26 +78,65 @@ func (vals *ValidatorSet) IncrementAccum(times int) { panic("Cannot call IncrementAccum with non-positive times") } - // Add VotingPower * times to each validator and order into heap. - validatorsHeap := cmn.NewHeap() + const shiftEveryNthIter = 10 + var proposer *Validator + // call IncrementAccum(1) times times: + for i := 0; i < times; i++ { + shiftByAvgAccum := i%shiftEveryNthIter == 0 + proposer = vals.incrementAccum(shiftByAvgAccum) + } + isShiftedAvgOnLastIter := (times-1)%shiftEveryNthIter == 0 + if !isShiftedAvgOnLastIter { + validatorsHeap := cmn.NewHeap() + vals.shiftByAvgAccum(validatorsHeap) + } + vals.Proposer = proposer +} + +func (vals *ValidatorSet) incrementAccum(subAvg bool) *Validator { for _, val := range vals.Validators { - // Check for overflow both multiplication and sum. - val.Accum = safeAddClip(val.Accum, safeMulClip(val.VotingPower, int64(times))) - validatorsHeap.PushComparable(val, accumComparable{val}) + // Check for overflow for sum. + val.Accum = safeAddClip(val.Accum, val.VotingPower) } - // Decrement the validator with most accum times times. - for i := 0; i < times; i++ { - mostest := validatorsHeap.Peek().(*Validator) - // mind underflow - mostest.Accum = safeSubClip(mostest.Accum, vals.TotalVotingPower()) - - if i == times-1 { - vals.Proposer = mostest - } else { - validatorsHeap.Update(mostest, accumComparable{mostest}) + validatorsHeap := cmn.NewHeap() + if subAvg { // shift by avg accum + vals.shiftByAvgAccum(validatorsHeap) + } else { // just update the heap + for _, val := range vals.Validators { + validatorsHeap.PushComparable(val, accumComparable{val}) } } + + // Decrement the validator with most accum: + mostest := validatorsHeap.Peek().(*Validator) + // mind underflow + mostest.Accum = safeSubClip(mostest.Accum, vals.TotalVotingPower()) + + return mostest +} + +func (vals *ValidatorSet) computeAvgAccum() int64 { + n := int64(len(vals.Validators)) + sum := big.NewInt(0) + for _, val := range vals.Validators { + sum.Add(sum, big.NewInt(val.Accum)) + } + avg := sum.Div(sum, big.NewInt(n)) + if avg.IsInt64() { + return avg.Int64() + } + + // this should never happen: each val.Accum is in bounds of int64 + panic(fmt.Sprintf("Cannot represent avg accum as an int64 %v", avg)) +} + +func (vals *ValidatorSet) shiftByAvgAccum(validatorsHeap *cmn.Heap) { + avgAccum := vals.computeAvgAccum() + for _, val := range vals.Validators { + val.Accum = safeSubClip(val.Accum, avgAccum) + validatorsHeap.PushComparable(val, accumComparable{val}) + } } // Copy each validator into a new ValidatorSet @@ -144,10 +193,18 @@ func (vals *ValidatorSet) Size() int { // TotalVotingPower returns the sum of the voting powers of all validators. func (vals *ValidatorSet) TotalVotingPower() int64 { if vals.totalVotingPower == 0 { + sum := int64(0) for _, val := range vals.Validators { // mind overflow - vals.totalVotingPower = safeAddClip(vals.totalVotingPower, val.VotingPower) + sum = safeAddClip(sum, val.VotingPower) } + if sum > MaxTotalVotingPower { + panic(fmt.Sprintf( + "Total voting power should be guarded to not exceed %v; got: %v", + MaxTotalVotingPower, + sum)) + } + vals.totalVotingPower = sum } return vals.totalVotingPower } @@ -308,7 +365,7 @@ func (vals *ValidatorSet) VerifyCommit(chainID string, blockID BlockID, height i return nil } return fmt.Errorf("Invalid commit -- insufficient voting power: got %v, needed %v", - talliedVotingPower, (vals.TotalVotingPower()*2/3 + 1)) + talliedVotingPower, vals.TotalVotingPower()*2/3+1) } // VerifyFutureCommit will check to see if the set would be valid with a different @@ -391,7 +448,7 @@ func (vals *ValidatorSet) VerifyFutureCommit(newSet *ValidatorSet, chainID strin if oldVotingPower <= oldVals.TotalVotingPower()*2/3 { return cmn.NewError("Invalid commit -- insufficient old voting power: got %v, needed %v", - oldVotingPower, (oldVals.TotalVotingPower()*2/3 + 1)) + oldVotingPower, oldVals.TotalVotingPower()*2/3+1) } return nil } @@ -405,7 +462,7 @@ func (vals *ValidatorSet) StringIndented(indent string) string { if vals == nil { return "nil-ValidatorSet" } - valStrings := []string{} + var valStrings []string vals.Iterate(func(index int, val *Validator) bool { valStrings = append(valStrings, val.String()) return false @@ -476,24 +533,7 @@ func RandValidatorSet(numValidators int, votingPower int64) (*ValidatorSet, []Pr } /////////////////////////////////////////////////////////////////////////////// -// Safe multiplication and addition/subtraction - -func safeMul(a, b int64) (int64, bool) { - if a == 0 || b == 0 { - return 0, false - } - if a == 1 { - return b, false - } - if b == 1 { - return a, false - } - if a == math.MinInt64 || b == math.MinInt64 { - return -1, true - } - c := a * b - return c, c/b != a -} +// Safe addition/subtraction func safeAdd(a, b int64) (int64, bool) { if b > 0 && a > math.MaxInt64-b { @@ -513,17 +553,6 @@ func safeSub(a, b int64) (int64, bool) { return a - b, false } -func safeMulClip(a, b int64) int64 { - c, overflow := safeMul(a, b) - if overflow { - if (a < 0 || b < 0) && !(a < 0 && b < 0) { - return math.MinInt64 - } - return math.MaxInt64 - } - return c -} - func safeAddClip(a, b int64) int64 { c, overflow := safeAdd(a, b) if overflow { diff --git a/types/validator_set_test.go b/types/validator_set_test.go index d7a6a5d2..094b2b7f 100644 --- a/types/validator_set_test.go +++ b/types/validator_set_test.go @@ -128,7 +128,7 @@ func TestProposerSelection1(t *testing.T) { newValidator([]byte("bar"), 300), newValidator([]byte("baz"), 330), }) - proposers := []string{} + var proposers []string for i := 0; i < 99; i++ { val := vset.GetProposer() proposers = append(proposers, string(val.Address)) @@ -305,53 +305,37 @@ func (valSet *ValidatorSet) fromBytes(b []byte) { //------------------------------------------------------------------- -func TestValidatorSetTotalVotingPowerOverflows(t *testing.T) { - vset := NewValidatorSet([]*Validator{ - {Address: []byte("a"), VotingPower: math.MaxInt64, Accum: 0}, - {Address: []byte("b"), VotingPower: math.MaxInt64, Accum: 0}, - {Address: []byte("c"), VotingPower: math.MaxInt64, Accum: 0}, - }) - - assert.EqualValues(t, math.MaxInt64, vset.TotalVotingPower()) -} - -func TestValidatorSetIncrementAccumOverflows(t *testing.T) { - // NewValidatorSet calls IncrementAccum(1) - vset := NewValidatorSet([]*Validator{ - // too much voting power - 0: {Address: []byte("a"), VotingPower: math.MaxInt64, Accum: 0}, - // too big accum - 1: {Address: []byte("b"), VotingPower: 10, Accum: math.MaxInt64}, - // almost too big accum - 2: {Address: []byte("c"), VotingPower: 10, Accum: math.MaxInt64 - 5}, - }) - - assert.Equal(t, int64(0), vset.Validators[0].Accum, "0") // because we decrement val with most voting power - assert.EqualValues(t, math.MaxInt64, vset.Validators[1].Accum, "1") - assert.EqualValues(t, math.MaxInt64, vset.Validators[2].Accum, "2") -} - -func TestValidatorSetIncrementAccumUnderflows(t *testing.T) { - // NewValidatorSet calls IncrementAccum(1) - vset := NewValidatorSet([]*Validator{ - 0: {Address: []byte("a"), VotingPower: math.MaxInt64, Accum: math.MinInt64}, - 1: {Address: []byte("b"), VotingPower: 1, Accum: math.MinInt64}, - }) - - vset.IncrementAccum(5) - - assert.EqualValues(t, math.MinInt64, vset.Validators[0].Accum, "0") - assert.EqualValues(t, math.MinInt64, vset.Validators[1].Accum, "1") -} - -func TestSafeMul(t *testing.T) { - f := func(a, b int64) bool { - c, overflow := safeMul(a, b) - return overflow || (!overflow && c == a*b) +func TestValidatorSetTotalVotingPowerPanicsOnOverflow(t *testing.T) { + // NewValidatorSet calls IncrementAccum which calls TotalVotingPower() + // which should panic on overflows: + shouldPanic := func() { + NewValidatorSet([]*Validator{ + {Address: []byte("a"), VotingPower: math.MaxInt64, Accum: 0}, + {Address: []byte("b"), VotingPower: math.MaxInt64, Accum: 0}, + {Address: []byte("c"), VotingPower: math.MaxInt64, Accum: 0}, + }) } - if err := quick.Check(f, nil); err != nil { - t.Error(err) + + assert.Panics(t, shouldPanic) +} + +func TestAvgAccum(t *testing.T) { + // Create Validator set without calling IncrementAccum: + tcs := []struct { + vs ValidatorSet + want int64 + }{ + 0: {ValidatorSet{Validators: []*Validator{{Accum: 0}, {Accum: 0}, {Accum: 0}}}, 0}, + 1: {ValidatorSet{Validators: []*Validator{{Accum: math.MaxInt64}, {Accum: 0}, {Accum: 0}}}, math.MaxInt64 / 3}, + 2: {ValidatorSet{Validators: []*Validator{{Accum: math.MaxInt64}, {Accum: 0}}}, math.MaxInt64 / 2}, + 3: {ValidatorSet{Validators: []*Validator{{Accum: math.MaxInt64}, {Accum: math.MaxInt64}}}, math.MaxInt64}, + 4: {ValidatorSet{Validators: []*Validator{{Accum: math.MinInt64}, {Accum: math.MinInt64}}}, math.MinInt64}, } + for i, tc := range tcs { + got := tc.vs.computeAvgAccum() + assert.Equal(t, tc.want, got, "test case: %v", i) + } + } func TestSafeAdd(t *testing.T) { @@ -364,13 +348,6 @@ func TestSafeAdd(t *testing.T) { } } -func TestSafeMulClip(t *testing.T) { - assert.EqualValues(t, math.MaxInt64, safeMulClip(math.MinInt64, math.MinInt64)) - assert.EqualValues(t, math.MinInt64, safeMulClip(math.MaxInt64, math.MinInt64)) - assert.EqualValues(t, math.MinInt64, safeMulClip(math.MinInt64, math.MaxInt64)) - assert.EqualValues(t, math.MaxInt64, safeMulClip(math.MaxInt64, 2)) -} - func TestSafeAddClip(t *testing.T) { assert.EqualValues(t, math.MaxInt64, safeAddClip(math.MaxInt64, 10)) assert.EqualValues(t, math.MaxInt64, safeAddClip(math.MaxInt64, math.MaxInt64)) From 403927608595bc2bec43595e51ab1714af50386c Mon Sep 17 00:00:00 2001 From: Dev Ojha Date: Wed, 28 Nov 2018 11:53:04 -0800 Subject: [PATCH 124/267] remove unnecessary "crypto" import alias (#2940) --- consensus/replay_test.go | 2 +- p2p/key.go | 2 +- p2p/peer_test.go | 2 +- p2p/pex/addrbook.go | 2 +- p2p/pex/pex_reactor_test.go | 2 +- p2p/test_util.go | 2 +- rpc/core/pipe.go | 2 +- rpc/core/types/responses.go | 2 +- tools/tm-monitor/monitor/node.go | 2 +- 9 files changed, 9 insertions(+), 9 deletions(-) diff --git a/consensus/replay_test.go b/consensus/replay_test.go index c261426c..7cd32c7a 100644 --- a/consensus/replay_test.go +++ b/consensus/replay_test.go @@ -17,7 +17,7 @@ import ( "github.com/tendermint/tendermint/abci/example/kvstore" abci "github.com/tendermint/tendermint/abci/types" - crypto "github.com/tendermint/tendermint/crypto" + "github.com/tendermint/tendermint/crypto" auto "github.com/tendermint/tendermint/libs/autofile" dbm "github.com/tendermint/tendermint/libs/db" "github.com/tendermint/tendermint/version" diff --git a/p2p/key.go b/p2p/key.go index fc64f27b..4e662f9f 100644 --- a/p2p/key.go +++ b/p2p/key.go @@ -6,7 +6,7 @@ import ( "fmt" "io/ioutil" - crypto "github.com/tendermint/tendermint/crypto" + "github.com/tendermint/tendermint/crypto" "github.com/tendermint/tendermint/crypto/ed25519" cmn "github.com/tendermint/tendermint/libs/common" ) diff --git a/p2p/peer_test.go b/p2p/peer_test.go index d3d9f0c7..e53d6013 100644 --- a/p2p/peer_test.go +++ b/p2p/peer_test.go @@ -10,7 +10,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - crypto "github.com/tendermint/tendermint/crypto" + "github.com/tendermint/tendermint/crypto" "github.com/tendermint/tendermint/crypto/ed25519" cmn "github.com/tendermint/tendermint/libs/common" "github.com/tendermint/tendermint/libs/log" diff --git a/p2p/pex/addrbook.go b/p2p/pex/addrbook.go index e2fcc043..d8954f23 100644 --- a/p2p/pex/addrbook.go +++ b/p2p/pex/addrbook.go @@ -13,7 +13,7 @@ import ( "sync" "time" - crypto "github.com/tendermint/tendermint/crypto" + "github.com/tendermint/tendermint/crypto" cmn "github.com/tendermint/tendermint/libs/common" "github.com/tendermint/tendermint/p2p" ) diff --git a/p2p/pex/pex_reactor_test.go b/p2p/pex/pex_reactor_test.go index 8f3ceb89..2e2f3f24 100644 --- a/p2p/pex/pex_reactor_test.go +++ b/p2p/pex/pex_reactor_test.go @@ -12,7 +12,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - crypto "github.com/tendermint/tendermint/crypto" + "github.com/tendermint/tendermint/crypto" "github.com/tendermint/tendermint/crypto/ed25519" cmn "github.com/tendermint/tendermint/libs/common" "github.com/tendermint/tendermint/libs/log" diff --git a/p2p/test_util.go b/p2p/test_util.go index b8a34600..ac29ecb4 100644 --- a/p2p/test_util.go +++ b/p2p/test_util.go @@ -5,7 +5,7 @@ import ( "net" "time" - crypto "github.com/tendermint/tendermint/crypto" + "github.com/tendermint/tendermint/crypto" "github.com/tendermint/tendermint/crypto/ed25519" cmn "github.com/tendermint/tendermint/libs/common" "github.com/tendermint/tendermint/libs/log" diff --git a/rpc/core/pipe.go b/rpc/core/pipe.go index ae8ae056..7f459654 100644 --- a/rpc/core/pipe.go +++ b/rpc/core/pipe.go @@ -2,7 +2,7 @@ package core import ( "github.com/tendermint/tendermint/consensus" - crypto "github.com/tendermint/tendermint/crypto" + "github.com/tendermint/tendermint/crypto" dbm "github.com/tendermint/tendermint/libs/db" "github.com/tendermint/tendermint/libs/log" mempl "github.com/tendermint/tendermint/mempool" diff --git a/rpc/core/types/responses.go b/rpc/core/types/responses.go index 07628d1c..af5c4947 100644 --- a/rpc/core/types/responses.go +++ b/rpc/core/types/responses.go @@ -5,7 +5,7 @@ import ( "time" abci "github.com/tendermint/tendermint/abci/types" - crypto "github.com/tendermint/tendermint/crypto" + "github.com/tendermint/tendermint/crypto" cmn "github.com/tendermint/tendermint/libs/common" "github.com/tendermint/tendermint/p2p" diff --git a/tools/tm-monitor/monitor/node.go b/tools/tm-monitor/monitor/node.go index 8bc15a15..1dc113a6 100644 --- a/tools/tm-monitor/monitor/node.go +++ b/tools/tm-monitor/monitor/node.go @@ -7,7 +7,7 @@ import ( "github.com/pkg/errors" - crypto "github.com/tendermint/tendermint/crypto" + "github.com/tendermint/tendermint/crypto" "github.com/tendermint/tendermint/libs/events" "github.com/tendermint/tendermint/libs/log" ctypes "github.com/tendermint/tendermint/rpc/core/types" From b30c34e713560d917119ceef0902fcacd4b2d015 Mon Sep 17 00:00:00 2001 From: Ismail Khoffi Date: Wed, 28 Nov 2018 21:35:09 +0100 Subject: [PATCH 125/267] rename Accum -> ProposerPriority: (#2932) - rename fields, methods, comments, tests --- consensus/state.go | 2 +- .../subscribing-to-events-via-websocket.md | 2 +- evidence/pool_test.go | 2 +- p2p/metrics.go | 2 +- rpc/core/consensus.go | 10 +-- state/execution.go | 14 ++-- state/state.go | 2 +- state/state_test.go | 16 ++--- state/store.go | 2 +- types/validator.go | 30 ++++---- types/validator_set.go | 72 +++++++++---------- types/validator_set_test.go | 64 ++++++++--------- 12 files changed, 109 insertions(+), 109 deletions(-) diff --git a/consensus/state.go b/consensus/state.go index 71cf079a..81bdce7d 100644 --- a/consensus/state.go +++ b/consensus/state.go @@ -745,7 +745,7 @@ func (cs *ConsensusState) enterNewRound(height int64, round int) { validators := cs.Validators if cs.Round < round { validators = validators.Copy() - validators.IncrementAccum(round - cs.Round) + validators.IncrementProposerPriority(round - cs.Round) } // Setup new round diff --git a/docs/app-dev/subscribing-to-events-via-websocket.md b/docs/app-dev/subscribing-to-events-via-websocket.md index 49954809..d745769c 100644 --- a/docs/app-dev/subscribing-to-events-via-websocket.md +++ b/docs/app-dev/subscribing-to-events-via-websocket.md @@ -54,7 +54,7 @@ Response: "value": "ww0z4WaZ0Xg+YI10w43wTWbBmM3dpVza4mmSQYsd0ck=" }, "voting_power": "10", - "accum": "0" + "proposer_priority": "0" } ] } diff --git a/evidence/pool_test.go b/evidence/pool_test.go index 4e69596b..0640c1da 100644 --- a/evidence/pool_test.go +++ b/evidence/pool_test.go @@ -35,7 +35,7 @@ func initializeValidatorState(valAddr []byte, height int64) dbm.DB { LastBlockHeight: 0, LastBlockTime: tmtime.Now(), Validators: valSet, - NextValidators: valSet.CopyIncrementAccum(1), + NextValidators: valSet.CopyIncrementProposerPriority(1), LastHeightValidatorsChanged: 1, ConsensusParams: types.ConsensusParams{ Evidence: types.EvidenceParams{ diff --git a/p2p/metrics.go b/p2p/metrics.go index ed26d119..b066fb31 100644 --- a/p2p/metrics.go +++ b/p2p/metrics.go @@ -62,7 +62,7 @@ func PrometheusMetrics(namespace string) *Metrics { // NopMetrics returns no-op Metrics. func NopMetrics() *Metrics { return &Metrics{ - Peers: discard.NewGauge(), + Peers: discard.NewGauge(), PeerReceiveBytesTotal: discard.NewCounter(), PeerSendBytesTotal: discard.NewCounter(), PeerPendingSendBytes: discard.NewGauge(), diff --git a/rpc/core/consensus.go b/rpc/core/consensus.go index 14662885..63a4dfe0 100644 --- a/rpc/core/consensus.go +++ b/rpc/core/consensus.go @@ -27,7 +27,7 @@ import ( // "result": { // "validators": [ // { -// "accum": "0", +// "proposer_priority": "0", // "voting_power": "10", // "pub_key": { // "data": "68DFDA7E50F82946E7E8546BED37944A422CD1B831E70DF66BA3B8430593944D", @@ -92,7 +92,7 @@ func Validators(heightPtr *int64) (*ctypes.ResultValidators, error) { // "value": "SBctdhRBcXtBgdI/8a/alTsUhGXqGs9k5ylV1u5iKHg=" // }, // "voting_power": "10", -// "accum": "0" +// "proposer_priority": "0" // } // ], // "proposer": { @@ -102,7 +102,7 @@ func Validators(heightPtr *int64) (*ctypes.ResultValidators, error) { // "value": "SBctdhRBcXtBgdI/8a/alTsUhGXqGs9k5ylV1u5iKHg=" // }, // "voting_power": "10", -// "accum": "0" +// "proposer_priority": "0" // } // }, // "proposal": null, @@ -138,7 +138,7 @@ func Validators(heightPtr *int64) (*ctypes.ResultValidators, error) { // "value": "SBctdhRBcXtBgdI/8a/alTsUhGXqGs9k5ylV1u5iKHg=" // }, // "voting_power": "10", -// "accum": "0" +// "proposer_priority": "0" // } // ], // "proposer": { @@ -148,7 +148,7 @@ func Validators(heightPtr *int64) (*ctypes.ResultValidators, error) { // "value": "SBctdhRBcXtBgdI/8a/alTsUhGXqGs9k5ylV1u5iKHg=" // }, // "voting_power": "10", -// "accum": "0" +// "proposer_priority": "0" // } // } // }, diff --git a/state/execution.go b/state/execution.go index 06f246f8..decadddf 100644 --- a/state/execution.go +++ b/state/execution.go @@ -373,14 +373,14 @@ func updateValidators(currentSet *types.ValidatorSet, updates []*types.Validator types.MaxTotalVotingPower) } // TODO: issue #1558 update spec according to the following: - // Set Accum to -C*totalVotingPower (with C ~= 1.125) to make sure validators can't - // unbond/rebond to reset their (potentially previously negative) Accum to zero. + // Set ProposerPriority to -C*totalVotingPower (with C ~= 1.125) to make sure validators can't + // unbond/rebond to reset their (potentially previously negative) ProposerPriority to zero. // - // Contract: totalVotingPower < MaxTotalVotingPower to ensure Accum does + // Contract: totalVotingPower < MaxTotalVotingPower to ensure ProposerPriority does // not exceed the bounds of int64. // - // Compute Accum = -1.125*totalVotingPower == -(totalVotingPower + (totalVotingPower >> 3)). - valUpdate.Accum = -(totalVotingPower + (totalVotingPower >> 3)) + // Compute ProposerPriority = -1.125*totalVotingPower == -(totalVotingPower + (totalVotingPower >> 3)). + valUpdate.ProposerPriority = -(totalVotingPower + (totalVotingPower >> 3)) added := currentSet.Add(valUpdate) if !added { return fmt.Errorf("Failed to add new validator %v", valUpdate) @@ -431,8 +431,8 @@ func updateState( lastHeightValsChanged = header.Height + 1 + 1 } - // Update validator accums and set state variables. - nValSet.IncrementAccum(1) + // Update validator proposer priority and set state variables. + nValSet.IncrementProposerPriority(1) // Update the params with the latest abciResponses. nextParams := state.ConsensusParams diff --git a/state/state.go b/state/state.go index 451d6544..b6253b64 100644 --- a/state/state.go +++ b/state/state.go @@ -226,7 +226,7 @@ func MakeGenesisState(genDoc *types.GenesisDoc) (State, error) { validators[i] = types.NewValidator(val.PubKey, val.Power) } validatorSet = types.NewValidatorSet(validators) - nextValidatorSet = types.NewValidatorSet(validators).CopyIncrementAccum(1) + nextValidatorSet = types.NewValidatorSet(validators).CopyIncrementProposerPriority(1) } return State{ diff --git a/state/state_test.go b/state/state_test.go index b2a6080b..2ca5f8b2 100644 --- a/state/state_test.go +++ b/state/state_test.go @@ -133,8 +133,8 @@ func TestABCIResponsesSaveLoad2(t *testing.T) { {Code: 383}, {Data: []byte("Gotcha!"), Tags: []cmn.KVPair{ - cmn.KVPair{Key: []byte("a"), Value: []byte("1")}, - cmn.KVPair{Key: []byte("build"), Value: []byte("stuff")}, + {Key: []byte("a"), Value: []byte("1")}, + {Key: []byte("build"), Value: []byte("stuff")}, }}, }, types.ABCIResults{ @@ -263,11 +263,11 @@ func TestOneValidatorChangesSaveLoad(t *testing.T) { } } -func TestStoreLoadValidatorsIncrementsAccum(t *testing.T) { +func TestStoreLoadValidatorsIncrementsProposerPriority(t *testing.T) { const valSetSize = 2 tearDown, stateDB, state := setupTestCase(t) state.Validators = genValSet(valSetSize) - state.NextValidators = state.Validators.CopyIncrementAccum(1) + state.NextValidators = state.Validators.CopyIncrementProposerPriority(1) SaveState(stateDB, state) defer tearDown(t) @@ -275,13 +275,13 @@ func TestStoreLoadValidatorsIncrementsAccum(t *testing.T) { v0, err := LoadValidators(stateDB, nextHeight) assert.Nil(t, err) - acc0 := v0.Validators[0].Accum + acc0 := v0.Validators[0].ProposerPriority v1, err := LoadValidators(stateDB, nextHeight+1) assert.Nil(t, err) - acc1 := v1.Validators[0].Accum + acc1 := v1.Validators[0].ProposerPriority - assert.NotEqual(t, acc1, acc0, "expected Accum value to change between heights") + assert.NotEqual(t, acc1, acc0, "expected ProposerPriority value to change between heights") } // TestValidatorChangesSaveLoad tests saving and loading a validator set with @@ -291,7 +291,7 @@ func TestManyValidatorChangesSaveLoad(t *testing.T) { tearDown, stateDB, state := setupTestCase(t) require.Equal(t, int64(0), state.LastBlockHeight) state.Validators = genValSet(valSetSize) - state.NextValidators = state.Validators.CopyIncrementAccum(1) + state.NextValidators = state.Validators.CopyIncrementProposerPriority(1) SaveState(stateDB, state) defer tearDown(t) diff --git a/state/store.go b/state/store.go index eb850fa7..6b01a829 100644 --- a/state/store.go +++ b/state/store.go @@ -194,7 +194,7 @@ func LoadValidators(db dbm.DB, height int64) (*types.ValidatorSet, error) { ), ) } - valInfo2.ValidatorSet.IncrementAccum(int(height - valInfo.LastHeightChanged)) // mutate + valInfo2.ValidatorSet.IncrementProposerPriority(int(height - valInfo.LastHeightChanged)) // mutate valInfo = valInfo2 } diff --git a/types/validator.go b/types/validator.go index cffc2854..b7c6c679 100644 --- a/types/validator.go +++ b/types/validator.go @@ -11,40 +11,40 @@ import ( ) // Volatile state for each Validator -// NOTE: The Accum is not included in Validator.Hash(); +// NOTE: The ProposerPriority is not included in Validator.Hash(); // make sure to update that method if changes are made here type Validator struct { Address Address `json:"address"` PubKey crypto.PubKey `json:"pub_key"` VotingPower int64 `json:"voting_power"` - Accum int64 `json:"accum"` + ProposerPriority int64 `json:"proposer_priority"` } func NewValidator(pubKey crypto.PubKey, votingPower int64) *Validator { return &Validator{ - Address: pubKey.Address(), - PubKey: pubKey, - VotingPower: votingPower, - Accum: 0, + Address: pubKey.Address(), + PubKey: pubKey, + VotingPower: votingPower, + ProposerPriority: 0, } } -// Creates a new copy of the validator so we can mutate accum. +// Creates a new copy of the validator so we can mutate ProposerPriority. // Panics if the validator is nil. func (v *Validator) Copy() *Validator { vCopy := *v return &vCopy } -// Returns the one with higher Accum. -func (v *Validator) CompareAccum(other *Validator) *Validator { +// Returns the one with higher ProposerPriority. +func (v *Validator) CompareProposerPriority(other *Validator) *Validator { if v == nil { return other } - if v.Accum > other.Accum { + if v.ProposerPriority > other.ProposerPriority { return v - } else if v.Accum < other.Accum { + } else if v.ProposerPriority < other.ProposerPriority { return other } else { result := bytes.Compare(v.Address, other.Address) @@ -67,19 +67,19 @@ func (v *Validator) String() string { v.Address, v.PubKey, v.VotingPower, - v.Accum) + v.ProposerPriority) } // Hash computes the unique ID of a validator with a given voting power. -// It excludes the Accum value, which changes with every round. +// It excludes the ProposerPriority value, which changes with every round. func (v *Validator) Hash() []byte { return tmhash.Sum(v.Bytes()) } // Bytes computes the unique encoding of a validator with a given voting power. // These are the bytes that gets hashed in consensus. It excludes address -// as its redundant with the pubkey. This also excludes accum which changes -// every round. +// as its redundant with the pubkey. This also excludes ProposerPriority +// which changes every round. func (v *Validator) Bytes() []byte { return cdcEncode(struct { PubKey crypto.PubKey diff --git a/types/validator_set.go b/types/validator_set.go index 36660885..8a62e14f 100644 --- a/types/validator_set.go +++ b/types/validator_set.go @@ -13,7 +13,7 @@ import ( ) // The maximum allowed total voting power. -// We set the accum of freshly added validators to -1.125*totalVotingPower. +// We set the ProposerPriority of freshly added validators to -1.125*totalVotingPower. // To compute 1.125*totalVotingPower efficiently, we do: // totalVotingPower + (totalVotingPower >> 3) because // x + (x >> 3) = x + x/8 = x * (1 + 0.125). @@ -25,9 +25,9 @@ const MaxTotalVotingPower = 8198552921648689607 // The validators can be fetched by address or index. // The index is in order of .Address, so the indices are fixed // for all rounds of a given blockchain height. -// On the other hand, the .AccumPower of each validator and +// On the other hand, the .ProposerPriority of each validator and // the designated .GetProposer() of a set changes every round, -// upon calling .IncrementAccum(). +// upon calling .IncrementProposerPriority(). // NOTE: Not goroutine-safe. // NOTE: All get/set to validators should copy the value for safety. type ValidatorSet struct { @@ -52,7 +52,7 @@ func NewValidatorSet(valz []*Validator) *ValidatorSet { Validators: validators, } if len(valz) > 0 { - vals.IncrementAccum(1) + vals.IncrementProposerPriority(1) } return vals @@ -63,79 +63,79 @@ func (vals *ValidatorSet) IsNilOrEmpty() bool { return vals == nil || len(vals.Validators) == 0 } -// Increment Accum and update the proposer on a copy, and return it. -func (vals *ValidatorSet) CopyIncrementAccum(times int) *ValidatorSet { +// Increment ProposerPriority and update the proposer on a copy, and return it. +func (vals *ValidatorSet) CopyIncrementProposerPriority(times int) *ValidatorSet { copy := vals.Copy() - copy.IncrementAccum(times) + copy.IncrementProposerPriority(times) return copy } -// IncrementAccum increments accum of each validator and updates the +// IncrementProposerPriority increments ProposerPriority of each validator and updates the // proposer. Panics if validator set is empty. // `times` must be positive. -func (vals *ValidatorSet) IncrementAccum(times int) { +func (vals *ValidatorSet) IncrementProposerPriority(times int) { if times <= 0 { - panic("Cannot call IncrementAccum with non-positive times") + panic("Cannot call IncrementProposerPriority with non-positive times") } const shiftEveryNthIter = 10 var proposer *Validator // call IncrementAccum(1) times times: for i := 0; i < times; i++ { - shiftByAvgAccum := i%shiftEveryNthIter == 0 - proposer = vals.incrementAccum(shiftByAvgAccum) + shiftByAvgProposerPriority := i%shiftEveryNthIter == 0 + proposer = vals.incrementProposerPriority(shiftByAvgProposerPriority) } isShiftedAvgOnLastIter := (times-1)%shiftEveryNthIter == 0 if !isShiftedAvgOnLastIter { validatorsHeap := cmn.NewHeap() - vals.shiftByAvgAccum(validatorsHeap) + vals.shiftByAvgProposerPriority(validatorsHeap) } vals.Proposer = proposer } -func (vals *ValidatorSet) incrementAccum(subAvg bool) *Validator { +func (vals *ValidatorSet) incrementProposerPriority(subAvg bool) *Validator { for _, val := range vals.Validators { // Check for overflow for sum. - val.Accum = safeAddClip(val.Accum, val.VotingPower) + val.ProposerPriority = safeAddClip(val.ProposerPriority, val.VotingPower) } validatorsHeap := cmn.NewHeap() - if subAvg { // shift by avg accum - vals.shiftByAvgAccum(validatorsHeap) + if subAvg { // shift by avg ProposerPriority + vals.shiftByAvgProposerPriority(validatorsHeap) } else { // just update the heap for _, val := range vals.Validators { - validatorsHeap.PushComparable(val, accumComparable{val}) + validatorsHeap.PushComparable(val, proposerPriorityComparable{val}) } } - // Decrement the validator with most accum: + // Decrement the validator with most ProposerPriority: mostest := validatorsHeap.Peek().(*Validator) // mind underflow - mostest.Accum = safeSubClip(mostest.Accum, vals.TotalVotingPower()) + mostest.ProposerPriority = safeSubClip(mostest.ProposerPriority, vals.TotalVotingPower()) return mostest } -func (vals *ValidatorSet) computeAvgAccum() int64 { +func (vals *ValidatorSet) computeAvgProposerPriority() int64 { n := int64(len(vals.Validators)) sum := big.NewInt(0) for _, val := range vals.Validators { - sum.Add(sum, big.NewInt(val.Accum)) + sum.Add(sum, big.NewInt(val.ProposerPriority)) } avg := sum.Div(sum, big.NewInt(n)) if avg.IsInt64() { return avg.Int64() } - // this should never happen: each val.Accum is in bounds of int64 - panic(fmt.Sprintf("Cannot represent avg accum as an int64 %v", avg)) + // this should never happen: each val.ProposerPriority is in bounds of int64 + panic(fmt.Sprintf("Cannot represent avg ProposerPriority as an int64 %v", avg)) } -func (vals *ValidatorSet) shiftByAvgAccum(validatorsHeap *cmn.Heap) { - avgAccum := vals.computeAvgAccum() +func (vals *ValidatorSet) shiftByAvgProposerPriority(validatorsHeap *cmn.Heap) { + avgProposerPriority := vals.computeAvgProposerPriority() for _, val := range vals.Validators { - val.Accum = safeSubClip(val.Accum, avgAccum) - validatorsHeap.PushComparable(val, accumComparable{val}) + val.ProposerPriority = safeSubClip(val.ProposerPriority, avgProposerPriority) + validatorsHeap.PushComparable(val, proposerPriorityComparable{val}) } } @@ -143,7 +143,7 @@ func (vals *ValidatorSet) shiftByAvgAccum(validatorsHeap *cmn.Heap) { func (vals *ValidatorSet) Copy() *ValidatorSet { validators := make([]*Validator, len(vals.Validators)) for i, val := range vals.Validators { - // NOTE: must copy, since IncrementAccum updates in place. + // NOTE: must copy, since IncrementProposerPriority updates in place. validators[i] = val.Copy() } return &ValidatorSet{ @@ -225,7 +225,7 @@ func (vals *ValidatorSet) findProposer() *Validator { var proposer *Validator for _, val := range vals.Validators { if proposer == nil || !bytes.Equal(val.Address, proposer.Address) { - proposer = proposer.CompareAccum(val) + proposer = proposer.CompareProposerPriority(val) } } return proposer @@ -500,16 +500,16 @@ func (valz ValidatorsByAddress) Swap(i, j int) { } //------------------------------------- -// Use with Heap for sorting validators by accum +// Use with Heap for sorting validators by ProposerPriority -type accumComparable struct { +type proposerPriorityComparable struct { *Validator } -// We want to find the validator with the greatest accum. -func (ac accumComparable) Less(o interface{}) bool { - other := o.(accumComparable).Validator - larger := ac.CompareAccum(other) +// We want to find the validator with the greatest ProposerPriority. +func (ac proposerPriorityComparable) Less(o interface{}) bool { + other := o.(proposerPriorityComparable).Validator + larger := ac.CompareProposerPriority(other) return bytes.Equal(larger.Address, ac.Address) } diff --git a/types/validator_set_test.go b/types/validator_set_test.go index 094b2b7f..c7fd42da 100644 --- a/types/validator_set_test.go +++ b/types/validator_set_test.go @@ -18,12 +18,12 @@ import ( func TestValidatorSetBasic(t *testing.T) { // empty or nil validator lists are allowed, - // but attempting to IncrementAccum on them will panic. + // but attempting to IncrementProposerPriority on them will panic. vset := NewValidatorSet([]*Validator{}) - assert.Panics(t, func() { vset.IncrementAccum(1) }) + assert.Panics(t, func() { vset.IncrementProposerPriority(1) }) vset = NewValidatorSet(nil) - assert.Panics(t, func() { vset.IncrementAccum(1) }) + assert.Panics(t, func() { vset.IncrementProposerPriority(1) }) assert.EqualValues(t, vset, vset.Copy()) assert.False(t, vset.HasAddress([]byte("some val"))) @@ -58,7 +58,7 @@ func TestValidatorSetBasic(t *testing.T) { assert.Equal(t, val.VotingPower, vset.TotalVotingPower()) assert.Equal(t, val, vset.GetProposer()) assert.NotNil(t, vset.Hash()) - assert.NotPanics(t, func() { vset.IncrementAccum(1) }) + assert.NotPanics(t, func() { vset.IncrementProposerPriority(1) }) // update assert.False(t, vset.Update(randValidator_())) @@ -89,17 +89,17 @@ func TestCopy(t *testing.T) { } } -// Test that IncrementAccum requires positive times. -func TestIncrementAccumPositiveTimes(t *testing.T) { +// Test that IncrementProposerPriority requires positive times. +func TestIncrementProposerPriorityPositiveTimes(t *testing.T) { vset := NewValidatorSet([]*Validator{ newValidator([]byte("foo"), 1000), newValidator([]byte("bar"), 300), newValidator([]byte("baz"), 330), }) - assert.Panics(t, func() { vset.IncrementAccum(-1) }) - assert.Panics(t, func() { vset.IncrementAccum(0) }) - vset.IncrementAccum(1) + assert.Panics(t, func() { vset.IncrementProposerPriority(-1) }) + assert.Panics(t, func() { vset.IncrementProposerPriority(0) }) + vset.IncrementProposerPriority(1) } func BenchmarkValidatorSetCopy(b *testing.B) { @@ -132,7 +132,7 @@ func TestProposerSelection1(t *testing.T) { for i := 0; i < 99; i++ { val := vset.GetProposer() proposers = append(proposers, string(val.Address)) - vset.IncrementAccum(1) + vset.IncrementProposerPriority(1) } expected := `foo baz foo bar foo foo baz foo bar foo foo baz foo foo bar foo baz foo foo bar foo foo baz foo bar foo foo baz foo bar foo foo baz foo foo bar foo baz foo foo bar foo baz foo foo bar foo baz foo foo bar foo baz foo foo foo baz bar foo foo foo baz foo bar foo foo baz foo bar foo foo baz foo bar foo foo baz foo bar foo foo baz foo foo bar foo baz foo foo bar foo baz foo foo bar foo baz foo foo` if expected != strings.Join(proposers, " ") { @@ -155,18 +155,18 @@ func TestProposerSelection2(t *testing.T) { if !bytes.Equal(prop.Address, valList[ii].Address) { t.Fatalf("(%d): Expected %X. Got %X", i, valList[ii].Address, prop.Address) } - vals.IncrementAccum(1) + vals.IncrementProposerPriority(1) } // One validator has more than the others, but not enough to propose twice in a row *val2 = *newValidator(addr2, 400) vals = NewValidatorSet(valList) - // vals.IncrementAccum(1) + // vals.IncrementProposerPriority(1) prop := vals.GetProposer() if !bytes.Equal(prop.Address, addr2) { t.Fatalf("Expected address with highest voting power to be first proposer. Got %X", prop.Address) } - vals.IncrementAccum(1) + vals.IncrementProposerPriority(1) prop = vals.GetProposer() if !bytes.Equal(prop.Address, addr0) { t.Fatalf("Expected smallest address to be validator. Got %X", prop.Address) @@ -179,12 +179,12 @@ func TestProposerSelection2(t *testing.T) { if !bytes.Equal(prop.Address, addr2) { t.Fatalf("Expected address with highest voting power to be first proposer. Got %X", prop.Address) } - vals.IncrementAccum(1) + vals.IncrementProposerPriority(1) prop = vals.GetProposer() if !bytes.Equal(prop.Address, addr2) { t.Fatalf("Expected address with highest voting power to be second proposer. Got %X", prop.Address) } - vals.IncrementAccum(1) + vals.IncrementProposerPriority(1) prop = vals.GetProposer() if !bytes.Equal(prop.Address, addr0) { t.Fatalf("Expected smallest address to be validator. Got %X", prop.Address) @@ -200,7 +200,7 @@ func TestProposerSelection2(t *testing.T) { prop := vals.GetProposer() ii := prop.Address[19] propCount[ii]++ - vals.IncrementAccum(1) + vals.IncrementProposerPriority(1) } if propCount[0] != 40*N { @@ -225,12 +225,12 @@ func TestProposerSelection3(t *testing.T) { proposerOrder := make([]*Validator, 4) for i := 0; i < 4; i++ { proposerOrder[i] = vset.GetProposer() - vset.IncrementAccum(1) + vset.IncrementProposerPriority(1) } // i for the loop // j for the times - // we should go in order for ever, despite some IncrementAccums with times > 1 + // we should go in order for ever, despite some IncrementProposerPriority with times > 1 var i, j int for ; i < 10000; i++ { got := vset.GetProposer().Address @@ -257,7 +257,7 @@ func TestProposerSelection3(t *testing.T) { // sometimes its up to 5 times = (cmn.RandInt() % 4) + 1 } - vset.IncrementAccum(times) + vset.IncrementProposerPriority(times) j += times } @@ -275,7 +275,7 @@ func randPubKey() crypto.PubKey { func randValidator_() *Validator { val := NewValidator(randPubKey(), cmn.RandInt64()) - val.Accum = cmn.RandInt64() + val.ProposerPriority = cmn.RandInt64() % MaxTotalVotingPower return val } @@ -306,33 +306,33 @@ func (valSet *ValidatorSet) fromBytes(b []byte) { //------------------------------------------------------------------- func TestValidatorSetTotalVotingPowerPanicsOnOverflow(t *testing.T) { - // NewValidatorSet calls IncrementAccum which calls TotalVotingPower() + // NewValidatorSet calls IncrementProposerPriority which calls TotalVotingPower() // which should panic on overflows: shouldPanic := func() { NewValidatorSet([]*Validator{ - {Address: []byte("a"), VotingPower: math.MaxInt64, Accum: 0}, - {Address: []byte("b"), VotingPower: math.MaxInt64, Accum: 0}, - {Address: []byte("c"), VotingPower: math.MaxInt64, Accum: 0}, + {Address: []byte("a"), VotingPower: math.MaxInt64, ProposerPriority: 0}, + {Address: []byte("b"), VotingPower: math.MaxInt64, ProposerPriority: 0}, + {Address: []byte("c"), VotingPower: math.MaxInt64, ProposerPriority: 0}, }) } assert.Panics(t, shouldPanic) } -func TestAvgAccum(t *testing.T) { - // Create Validator set without calling IncrementAccum: +func TestAvgProposerPriority(t *testing.T) { + // Create Validator set without calling IncrementProposerPriority: tcs := []struct { vs ValidatorSet want int64 }{ - 0: {ValidatorSet{Validators: []*Validator{{Accum: 0}, {Accum: 0}, {Accum: 0}}}, 0}, - 1: {ValidatorSet{Validators: []*Validator{{Accum: math.MaxInt64}, {Accum: 0}, {Accum: 0}}}, math.MaxInt64 / 3}, - 2: {ValidatorSet{Validators: []*Validator{{Accum: math.MaxInt64}, {Accum: 0}}}, math.MaxInt64 / 2}, - 3: {ValidatorSet{Validators: []*Validator{{Accum: math.MaxInt64}, {Accum: math.MaxInt64}}}, math.MaxInt64}, - 4: {ValidatorSet{Validators: []*Validator{{Accum: math.MinInt64}, {Accum: math.MinInt64}}}, math.MinInt64}, + 0: {ValidatorSet{Validators: []*Validator{{ProposerPriority: 0}, {ProposerPriority: 0}, {ProposerPriority: 0}}}, 0}, + 1: {ValidatorSet{Validators: []*Validator{{ProposerPriority: math.MaxInt64}, {ProposerPriority: 0}, {ProposerPriority: 0}}}, math.MaxInt64 / 3}, + 2: {ValidatorSet{Validators: []*Validator{{ProposerPriority: math.MaxInt64}, {ProposerPriority: 0}}}, math.MaxInt64 / 2}, + 3: {ValidatorSet{Validators: []*Validator{{ProposerPriority: math.MaxInt64}, {ProposerPriority: math.MaxInt64}}}, math.MaxInt64}, + 4: {ValidatorSet{Validators: []*Validator{{ProposerPriority: math.MinInt64}, {ProposerPriority: math.MinInt64}}}, math.MinInt64}, } for i, tc := range tcs { - got := tc.vs.computeAvgAccum() + got := tc.vs.computeAvgProposerPriority() assert.Equal(t, tc.want, got, "test case: %v", i) } From 380afaa678c70bd8cb261559cac17fb03a718c48 Mon Sep 17 00:00:00 2001 From: Anton Kaliaev Date: Thu, 29 Nov 2018 15:57:11 +0400 Subject: [PATCH 126/267] docs: update ecosystem.json: add Rust ABCI (#2945) --- docs/app-dev/ecosystem.json | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/docs/app-dev/ecosystem.json b/docs/app-dev/ecosystem.json index f66ed28b..3f0fa334 100644 --- a/docs/app-dev/ecosystem.json +++ b/docs/app-dev/ecosystem.json @@ -122,7 +122,7 @@ ], "abciServers": [ { - "name": "abci", + "name": "go-abci", "url": "https://github.com/tendermint/tendermint/tree/master/abci", "language": "Go", "author": "Tendermint" @@ -133,6 +133,12 @@ "language": "Javascript", "author": "Tendermint" }, + { + "name": "rust-tsp", + "url": "https://github.com/tendermint/rust-tsp", + "language": "Rust", + "author": "Tendermint" + }, { "name": "cpp-tmsp", "url": "https://github.com/mdyring/cpp-tmsp", @@ -164,7 +170,7 @@ "author": "Dave Bryson" }, { - "name": "tm-abci", + "name": "tm-abci (fork of py-abci with async IO)", "url": "https://github.com/SoftblocksCo/tm-abci", "language": "Python", "author": "Softblocks" From 44b769b1acd0b2aaa8c199b0885bc2811191fb2e Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Thu, 29 Nov 2018 08:26:12 -0500 Subject: [PATCH 127/267] types: ValidatorSet.Update preserves Accum (#2941) * types: ValidatorSet.Update preserves ProposerPriority This solves the other issue discovered as part of #2718, where Accum (now called ProposerPriority) is reset to 0 every time a validator is updated. * update changelog * add test * update comment * Update types/validator_set_test.go Co-Authored-By: ebuchman --- CHANGELOG_PENDING.md | 22 ++++++++++++++++++---- types/validator_set.go | 15 ++++++++++++--- types/validator_set_test.go | 9 ++++++++- 3 files changed, 38 insertions(+), 8 deletions(-) diff --git a/CHANGELOG_PENDING.md b/CHANGELOG_PENDING.md index 7198e9d4..b9a5454c 100644 --- a/CHANGELOG_PENDING.md +++ b/CHANGELOG_PENDING.md @@ -12,23 +12,37 @@ 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. + - [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 + - [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: -- [consensus] [\#2871](https://github.com/tendermint/tendermint/issues/2871) Remove *ProposalHeartbeat* infrastructure as it serves no real purpose ### 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/types/validator_set.go b/types/validator_set.go index 8a62e14f..c62261d2 100644 --- a/types/validator_set.go +++ b/types/validator_set.go @@ -80,7 +80,7 @@ func (vals *ValidatorSet) IncrementProposerPriority(times int) { const shiftEveryNthIter = 10 var proposer *Validator - // call IncrementAccum(1) times times: + // call IncrementProposerPriority(1) times times: for i := 0; i < times; i++ { shiftByAvgProposerPriority := i%shiftEveryNthIter == 0 proposer = vals.incrementProposerPriority(shiftByAvgProposerPriority) @@ -272,13 +272,22 @@ func (vals *ValidatorSet) Add(val *Validator) (added bool) { } } -// Update updates val and returns true. It returns false if val is not present -// in the set. +// Update updates the ValidatorSet by copying in the val. +// If the val is not found, it returns false; otherwise, +// it returns true. The val.ProposerPriority field is ignored +// and unchanged by this method. func (vals *ValidatorSet) Update(val *Validator) (updated bool) { index, sameVal := vals.GetByAddress(val.Address) if sameVal == nil { return false } + // Overwrite the ProposerPriority so it doesn't change. + // During block execution, the val passed in here comes + // from ABCI via PB2TM.ValidatorUpdates. Since ABCI + // doesn't know about ProposerPriority, PB2TM.ValidatorUpdates + // uses the default value of 0, which would cause issues for + // proposer selection every time a validator's voting power changes. + val.ProposerPriority = sameVal.ProposerPriority vals.Validators[index] = val.Copy() // Invalidate cache vals.Proposer = nil diff --git a/types/validator_set_test.go b/types/validator_set_test.go index c7fd42da..f0e41a1d 100644 --- a/types/validator_set_test.go +++ b/types/validator_set_test.go @@ -62,8 +62,15 @@ func TestValidatorSetBasic(t *testing.T) { // update assert.False(t, vset.Update(randValidator_())) - val.VotingPower = 100 + _, val = vset.GetByAddress(val.Address) + val.VotingPower += 100 + proposerPriority := val.ProposerPriority + // Mimic update from types.PB2TM.ValidatorUpdates which does not know about ProposerPriority + // and hence defaults to 0. + val.ProposerPriority = 0 assert.True(t, vset.Update(val)) + _, val = vset.GetByAddress(val.Address) + assert.Equal(t, proposerPriority, val.ProposerPriority) // remove val2, removed := vset.Remove(randValidator_().Address) From 725ed7969accb44e7d6004169ed7ede46d6d53df Mon Sep 17 00:00:00 2001 From: Ismail Khoffi Date: Thu, 29 Nov 2018 23:03:41 +0100 Subject: [PATCH 128/267] 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 129/267] 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 130/267] 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 131/267] 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 132/267] 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 133/267] 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 134/267] 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 135/267] 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 136/267] 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 137/267] 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 138/267] 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 139/267] 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) } From 68b467886a0453e447e125ba614357f2765d6856 Mon Sep 17 00:00:00 2001 From: Zach Date: Fri, 7 Dec 2018 10:41:19 -0500 Subject: [PATCH 140/267] docs: relative links in docs/spec/readme.md, js-amino lib (#2977) Co-Authored-By: zramsay --- docs/app-dev/ecosystem.json | 8 ++++++++ docs/spec/README.md | 30 +++++++++++++++--------------- 2 files changed, 23 insertions(+), 15 deletions(-) diff --git a/docs/app-dev/ecosystem.json b/docs/app-dev/ecosystem.json index 3f0fa334..57059701 100644 --- a/docs/app-dev/ecosystem.json +++ b/docs/app-dev/ecosystem.json @@ -181,5 +181,13 @@ "language": "Javascript", "author": "Dennis McKinnon" } + ], + "aminoLibraries": [ + { + "name": "JS-Amino", + "url": "https://github.com/TanNgocDo/Js-Amino", + "language": "Javascript", + "author": "TanNgocDo" + } ] } diff --git a/docs/spec/README.md b/docs/spec/README.md index 3e2c2bcd..7ec9387c 100644 --- a/docs/spec/README.md +++ b/docs/spec/README.md @@ -14,31 +14,31 @@ please submit them to our [bug bounty](https://tendermint.com/security)! ### Data Structures -- [Encoding and Digests](https://github.com/tendermint/tendermint/blob/master/docs/spec/blockchain/encoding.md) -- [Blockchain](https://github.com/tendermint/tendermint/blob/master/docs/spec/blockchain/blockchain.md) -- [State](https://github.com/tendermint/tendermint/blob/master/docs/spec/blockchain/state.md) +- [Encoding and Digests](./blockchain/encoding.md) +- [Blockchain](./blockchain/blockchain.md) +- [State](./blockchain/state.md) ### Consensus Protocol -- [Consensus Algorithm](/docs/spec/consensus/consensus.md) -- [Creating a proposal](/docs/spec/consensus/creating-proposal.md) -- [Time](/docs/spec/consensus/bft-time.md) -- [Light-Client](/docs/spec/consensus/light-client.md) +- [Consensus Algorithm](./consensus/consensus.md) +- [Creating a proposal](./consensus/creating-proposal.md) +- [Time](./consensus/bft-time.md) +- [Light-Client](./consensus/light-client.md) ### P2P and Network Protocols -- [The Base P2P Layer](https://github.com/tendermint/tendermint/tree/master/docs/spec/p2p): multiplex the protocols ("reactors") on authenticated and encrypted TCP connections -- [Peer Exchange (PEX)](https://github.com/tendermint/tendermint/tree/master/docs/spec/reactors/pex): gossip known peer addresses so peers can find each other -- [Block Sync](https://github.com/tendermint/tendermint/tree/master/docs/spec/reactors/block_sync): gossip blocks so peers can catch up quickly -- [Consensus](https://github.com/tendermint/tendermint/tree/master/docs/spec/reactors/consensus): gossip votes and block parts so new blocks can be committed -- [Mempool](https://github.com/tendermint/tendermint/tree/master/docs/spec/reactors/mempool): gossip transactions so they get included in blocks -- Evidence: Forthcoming, see [this issue](https://github.com/tendermint/tendermint/issues/2329). +- [The Base P2P Layer](./p2p/): multiplex the protocols ("reactors") on authenticated and encrypted TCP connections +- [Peer Exchange (PEX)](./reactors/pex/): gossip known peer addresses so peers can find each other +- [Block Sync](./reactors/block_sync/): gossip blocks so peers can catch up quickly +- [Consensus](./reactors/consensus/): gossip votes and block parts so new blocks can be committed +- [Mempool](./reactors/mempool/): gossip transactions so they get included in blocks +- [Evidence](./reactors/evidence/): sending invalid evidence will stop the peer ### Software -- [ABCI](/docs/spec/software/abci.md): Details about interactions between the +- [ABCI](./software/abci.md): Details about interactions between the application and consensus engine over ABCI -- [Write-Ahead Log](/docs/spec/software/wal.md): Details about how the consensus +- [Write-Ahead Log](./software/wal.md): Details about how the consensus engine preserves data and recovers from crash failures ## Overview From 41eaf0e31d3e9462b165378d8870ee7366ed7b38 Mon Sep 17 00:00:00 2001 From: Anton Kaliaev Date: Sun, 9 Dec 2018 22:29:51 +0400 Subject: [PATCH 141/267] turn off strict routability every time (#2983) previously, we're turning it off only when --populate-persistent-peers flag was used, which is obviously incorrect. Fixes https://github.com/cosmos/cosmos-sdk/issues/2983 --- cmd/tendermint/commands/testnet.go | 37 +++++++++++++++++------------- 1 file changed, 21 insertions(+), 16 deletions(-) diff --git a/cmd/tendermint/commands/testnet.go b/cmd/tendermint/commands/testnet.go index 0f7dd79a..7e5635ca 100644 --- a/cmd/tendermint/commands/testnet.go +++ b/cmd/tendermint/commands/testnet.go @@ -127,14 +127,31 @@ func testnetFiles(cmd *cobra.Command, args []string) error { } } + // Gather persistent peer addresses. + var ( + persistentPeers string + err error + ) if populatePersistentPeers { - err := populatePersistentPeersInConfigAndWriteIt(config) + persistentPeers, err = persistentPeersString(config) if err != nil { _ = os.RemoveAll(outputDir) return err } } + // Overwrite default config. + for i := 0; i < nValidators+nNonValidators; i++ { + nodeDir := filepath.Join(outputDir, fmt.Sprintf("%s%d", nodeDirPrefix, i)) + config.SetRoot(nodeDir) + config.P2P.AddrBookStrict = false + if populatePersistentPeers { + config.P2P.PersistentPeers = persistentPeers + } + + cfg.WriteConfigFile(filepath.Join(nodeDir, "config", "config.toml"), config) + } + fmt.Printf("Successfully initialized %v node directories\n", nValidators+nNonValidators) return nil } @@ -157,28 +174,16 @@ func hostnameOrIP(i int) string { return fmt.Sprintf("%s%d", hostnamePrefix, i) } -func populatePersistentPeersInConfigAndWriteIt(config *cfg.Config) error { +func persistentPeersString(config *cfg.Config) (string, error) { persistentPeers := make([]string, nValidators+nNonValidators) for i := 0; i < nValidators+nNonValidators; i++ { nodeDir := filepath.Join(outputDir, fmt.Sprintf("%s%d", nodeDirPrefix, i)) config.SetRoot(nodeDir) nodeKey, err := p2p.LoadNodeKey(config.NodeKeyFile()) if err != nil { - return err + return "", err } persistentPeers[i] = p2p.IDAddressString(nodeKey.ID(), fmt.Sprintf("%s:%d", hostnameOrIP(i), p2pPort)) } - persistentPeersList := strings.Join(persistentPeers, ",") - - for i := 0; i < nValidators+nNonValidators; i++ { - nodeDir := filepath.Join(outputDir, fmt.Sprintf("%s%d", nodeDirPrefix, i)) - config.SetRoot(nodeDir) - config.P2P.PersistentPeers = persistentPeersList - config.P2P.AddrBookStrict = false - - // overwrite default config - cfg.WriteConfigFile(filepath.Join(nodeDir, "config", "config.toml"), config) - } - - return nil + return strings.Join(persistentPeers, ","), nil } From d5d0d2bd778e2e330d156fc97a4ed6d45fe14991 Mon Sep 17 00:00:00 2001 From: Dev Ojha Date: Mon, 10 Dec 2018 09:56:49 -0800 Subject: [PATCH 142/267] Make mempool fail txs with negative gas wanted (#2994) This is only one part of #2989. We also need to fix the application, and add rules to consensus to ensure this. --- mempool/mempool.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/mempool/mempool.go b/mempool/mempool.go index 8f70ec6c..a64ce918 100644 --- a/mempool/mempool.go +++ b/mempool/mempool.go @@ -108,6 +108,10 @@ func PostCheckMaxGas(maxGas int64) PostCheckFunc { if maxGas == -1 { return nil } + if res.GasWanted < 0 { + return fmt.Errorf("gas wanted %d is negative", + res.GasWanted) + } if res.GasWanted > maxGas { return fmt.Errorf("gas wanted %d is greater than max gas %d", res.GasWanted, maxGas) From f69e2c6d6c32b4289631be509a15fea24080573f Mon Sep 17 00:00:00 2001 From: Anton Kaliaev Date: Tue, 11 Dec 2018 00:24:58 +0400 Subject: [PATCH 143/267] p2p: set MConnection#created during init (#2990) Fixes #2715 In crawlPeersRoutine, which is performed when seedMode is run, there is logic that disconnects the peer's state information at 3-hour intervals through the duration value. The duration value is calculated by referring to the created value of MConnection. When MConnection is created for the first time, the created value is not initiated, so it is not disconnected every 3 hours but every time it is disconnected. So, normal nodes are connected to seedNode and disconnected immediately, so address exchange does not work properly. https://github.com/tendermint/tendermint/blob/master/p2p/pex/pex_reactor.go#L629 This point is not work correctly. I think, https://github.com/tendermint/tendermint/blob/master/p2p/conn/connection.go#L148 created variable is missing the current time setting. --- CHANGELOG_PENDING.md | 3 ++- p2p/conn/connection.go | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG_PENDING.md b/CHANGELOG_PENDING.md index 74d81bd5..a1f5937b 100644 --- a/CHANGELOG_PENDING.md +++ b/CHANGELOG_PENDING.md @@ -21,4 +21,5 @@ 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 +- [kv indexer] \#2912 don't ignore key when executing CONTAINS +- [p2p] \#2715 fix a bug where seeds don't disconnect from a peer after 3h diff --git a/p2p/conn/connection.go b/p2p/conn/connection.go index c6aad038..fb20c477 100644 --- a/p2p/conn/connection.go +++ b/p2p/conn/connection.go @@ -160,6 +160,7 @@ func NewMConnectionWithConfig(conn net.Conn, chDescs []*ChannelDescriptor, onRec onReceive: onReceive, onError: onError, config: config, + created: time.Now(), } // Create channels From df32ea4be5ebb0684d17008451902b252a4673d2 Mon Sep 17 00:00:00 2001 From: Dev Ojha Date: Tue, 11 Dec 2018 00:17:21 -0800 Subject: [PATCH 144/267] Make testing logger that doesn't write to stdout (#2997) --- libs/log/testing_logger.go | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/libs/log/testing_logger.go b/libs/log/testing_logger.go index 81482bef..8914bd81 100644 --- a/libs/log/testing_logger.go +++ b/libs/log/testing_logger.go @@ -1,6 +1,7 @@ package log import ( + "io" "os" "testing" @@ -19,12 +20,22 @@ var ( // inside a test (not in the init func) because // verbose flag only set at the time of testing. func TestingLogger() Logger { + return TestingLoggerWithOutput(os.Stdout) +} + +// TestingLoggerWOutput returns a TMLogger which writes to (w io.Writer) if testing being run +// with the verbose (-v) flag, NopLogger otherwise. +// +// Note that the call to TestingLoggerWithOutput(w io.Writer) must be made +// inside a test (not in the init func) because +// verbose flag only set at the time of testing. +func TestingLoggerWithOutput(w io.Writer) Logger { if _testingLogger != nil { return _testingLogger } if testing.Verbose() { - _testingLogger = NewTMLogger(NewSyncWriter(os.Stdout)) + _testingLogger = NewTMLogger(NewSyncWriter(w)) } else { _testingLogger = NewNopLogger() } From 2594cec116fe1209a374b0e3f8cd5d62abe2e1e5 Mon Sep 17 00:00:00 2001 From: Daniil Lashin Date: Tue, 11 Dec 2018 11:41:02 +0300 Subject: [PATCH 145/267] add UnconfirmedTxs/NumUnconfirmedTxs methods to HTTP/Local clients (#2964) --- CHANGELOG_PENDING.md | 1 + rpc/client/httpclient.go | 18 ++++++++++++++++++ rpc/client/interface.go | 6 ++++++ rpc/client/localclient.go | 8 ++++++++ rpc/client/rpc_test.go | 36 ++++++++++++++++++++++++++++++++++++ 5 files changed, 69 insertions(+) diff --git a/CHANGELOG_PENDING.md b/CHANGELOG_PENDING.md index a1f5937b..e4105369 100644 --- a/CHANGELOG_PENDING.md +++ b/CHANGELOG_PENDING.md @@ -19,6 +19,7 @@ Special thanks to external contributors on this release: ### FEATURES: ### IMPROVEMENTS: +- [rpc] Add `UnconfirmedTxs(limit)` and `NumUnconfirmedTxs()` methods to HTTP/Local clients (@danil-lashin) ### BUG FIXES: - [kv indexer] \#2912 don't ignore key when executing CONTAINS diff --git a/rpc/client/httpclient.go b/rpc/client/httpclient.go index a1b59ffa..1a1d88c4 100644 --- a/rpc/client/httpclient.go +++ b/rpc/client/httpclient.go @@ -109,6 +109,24 @@ func (c *HTTP) broadcastTX(route string, tx types.Tx) (*ctypes.ResultBroadcastTx return result, nil } +func (c *HTTP) UnconfirmedTxs(limit int) (*ctypes.ResultUnconfirmedTxs, error) { + result := new(ctypes.ResultUnconfirmedTxs) + _, err := c.rpc.Call("unconfirmed_txs", map[string]interface{}{"limit": limit}, result) + if err != nil { + return nil, errors.Wrap(err, "unconfirmed_txs") + } + return result, nil +} + +func (c *HTTP) NumUnconfirmedTxs() (*ctypes.ResultUnconfirmedTxs, error) { + result := new(ctypes.ResultUnconfirmedTxs) + _, err := c.rpc.Call("num_unconfirmed_txs", map[string]interface{}{}, result) + if err != nil { + return nil, errors.Wrap(err, "num_unconfirmed_txs") + } + return result, nil +} + func (c *HTTP) NetInfo() (*ctypes.ResultNetInfo, error) { result := new(ctypes.ResultNetInfo) _, err := c.rpc.Call("net_info", map[string]interface{}{}, result) diff --git a/rpc/client/interface.go b/rpc/client/interface.go index f34410c5..7477225e 100644 --- a/rpc/client/interface.go +++ b/rpc/client/interface.go @@ -93,3 +93,9 @@ type NetworkClient interface { type EventsClient interface { types.EventBusSubscriber } + +// MempoolClient shows us data about current mempool state. +type MempoolClient interface { + UnconfirmedTxs(limit int) (*ctypes.ResultUnconfirmedTxs, error) + NumUnconfirmedTxs() (*ctypes.ResultUnconfirmedTxs, error) +} diff --git a/rpc/client/localclient.go b/rpc/client/localclient.go index 8d89b715..ba8fb3f1 100644 --- a/rpc/client/localclient.go +++ b/rpc/client/localclient.go @@ -76,6 +76,14 @@ func (Local) BroadcastTxSync(tx types.Tx) (*ctypes.ResultBroadcastTx, error) { return core.BroadcastTxSync(tx) } +func (Local) UnconfirmedTxs(limit int) (*ctypes.ResultUnconfirmedTxs, error) { + return core.UnconfirmedTxs(limit) +} + +func (Local) NumUnconfirmedTxs() (*ctypes.ResultUnconfirmedTxs, error) { + return core.NumUnconfirmedTxs() +} + func (Local) NetInfo() (*ctypes.ResultNetInfo, error) { return core.NetInfo() } diff --git a/rpc/client/rpc_test.go b/rpc/client/rpc_test.go index b07b74a3..fa5080f9 100644 --- a/rpc/client/rpc_test.go +++ b/rpc/client/rpc_test.go @@ -281,6 +281,42 @@ func TestBroadcastTxCommit(t *testing.T) { } } +func TestUnconfirmedTxs(t *testing.T) { + _, _, tx := MakeTxKV() + + mempool := node.MempoolReactor().Mempool + _ = mempool.CheckTx(tx, nil) + + for i, c := range GetClients() { + mc, ok := c.(client.MempoolClient) + require.True(t, ok, "%d", i) + txs, err := mc.UnconfirmedTxs(1) + require.Nil(t, err, "%d: %+v", i, err) + assert.Exactly(t, types.Txs{tx}, types.Txs(txs.Txs)) + } + + mempool.Flush() +} + +func TestNumUnconfirmedTxs(t *testing.T) { + _, _, tx := MakeTxKV() + + mempool := node.MempoolReactor().Mempool + _ = mempool.CheckTx(tx, nil) + mempoolSize := mempool.Size() + + for i, c := range GetClients() { + mc, ok := c.(client.MempoolClient) + require.True(t, ok, "%d", i) + res, err := mc.NumUnconfirmedTxs() + require.Nil(t, err, "%d: %+v", i, err) + + assert.Equal(t, mempoolSize, res.N) + } + + mempool.Flush() +} + func TestTx(t *testing.T) { // first we broadcast a tx c := getHTTPClient() From 8003786c9affff242861141bf7484aeb5796e42c Mon Sep 17 00:00:00 2001 From: Zach Date: Tue, 11 Dec 2018 13:21:54 -0500 Subject: [PATCH 146/267] docs: fixes from 'first time' review (#2999) --- docs/app-dev/app-architecture.md | 2 +- docs/app-dev/ecosystem.md | 6 ++---- docs/imgs/cosmos-tendermint-stack-4k.jpg | Bin 0 -> 640210 bytes docs/introduction/what-is-tendermint.md | 4 ---- 4 files changed, 3 insertions(+), 9 deletions(-) create mode 100644 docs/imgs/cosmos-tendermint-stack-4k.jpg diff --git a/docs/app-dev/app-architecture.md b/docs/app-dev/app-architecture.md index b141c0f3..943a3cd0 100644 --- a/docs/app-dev/app-architecture.md +++ b/docs/app-dev/app-architecture.md @@ -5,7 +5,7 @@ Tendermint blockchain application. The following diagram provides a superb example: - +![](../imgs/cosmos-tendermint-stack-4k.jpg) The end-user application here is the Cosmos Voyager, at the bottom left. Voyager communicates with a REST API exposed by a local Light-Client diff --git a/docs/app-dev/ecosystem.md b/docs/app-dev/ecosystem.md index e51ca430..c87d3658 100644 --- a/docs/app-dev/ecosystem.md +++ b/docs/app-dev/ecosystem.md @@ -1,11 +1,9 @@ # Ecosystem The growing list of applications built using various pieces of the -Tendermint stack can be found at: +Tendermint stack can be found at the [ecosystem page](https://tendermint.com/ecosystem). -- https://tendermint.com/ecosystem - -We thank the community for their contributions thus far and welcome the +We thank the community for their contributions and welcome the addition of new projects. A pull request can be submitted to [this file](https://github.com/tendermint/tendermint/blob/master/docs/app-dev/ecosystem.json) to include your project. diff --git a/docs/imgs/cosmos-tendermint-stack-4k.jpg b/docs/imgs/cosmos-tendermint-stack-4k.jpg new file mode 100644 index 0000000000000000000000000000000000000000..b10ffa6a22f733290755cd21e8f23e09baf259d3 GIT binary patch literal 640210 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�E`e7b~D*Fiwiu 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 RM~gCOBo;ovz-0V?698 Date: Wed, 12 Dec 2018 04:20:02 -0500 Subject: [PATCH 147/267] docs: enable full-text search (#3004) --- docs/.vuepress/config.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/docs/.vuepress/config.js b/docs/.vuepress/config.js index 9f8ddbc7..a1e150c3 100644 --- a/docs/.vuepress/config.js +++ b/docs/.vuepress/config.js @@ -8,7 +8,12 @@ module.exports = { lineNumbers: true }, themeConfig: { - lastUpdated: "Last Updated", + lastUpdated: true, + algolia: { + apiKey: '59f0e2deb984aa9cdf2b3a5fd24ac501', + indexName: 'tendermint', + debug: false + }, nav: [{ text: "Back to Tendermint", link: "https://tendermint.com" }], sidebar: [ { From bc2a9b20c0b10146a5711dd828db6d9eb0f4e96b Mon Sep 17 00:00:00 2001 From: Dev Ojha Date: Wed, 12 Dec 2018 01:31:35 -0800 Subject: [PATCH 148/267] mempool: add a comment and missing changelog entry (#2996) Refs #2994 --- CHANGELOG_PENDING.md | 1 + mempool/mempool.go | 10 +++++++--- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/CHANGELOG_PENDING.md b/CHANGELOG_PENDING.md index e4105369..4fb20cdf 100644 --- a/CHANGELOG_PENDING.md +++ b/CHANGELOG_PENDING.md @@ -23,4 +23,5 @@ Special thanks to external contributors on this release: ### BUG FIXES: - [kv indexer] \#2912 don't ignore key when executing CONTAINS +- [mempool] \#2994 Don't allow txs with negative gas wanted - [p2p] \#2715 fix a bug where seeds don't disconnect from a peer after 3h diff --git a/mempool/mempool.go b/mempool/mempool.go index a64ce918..40468315 100644 --- a/mempool/mempool.go +++ b/mempool/mempool.go @@ -490,11 +490,15 @@ func (mem *Mempool) ReapMaxBytesMaxGas(maxBytes, maxGas int64) types.Txs { return txs } totalBytes += int64(len(memTx.tx)) + aminoOverhead - // Check total gas requirement - if maxGas > -1 && totalGas+memTx.gasWanted > maxGas { + // Check total gas requirement. + // If maxGas is negative, skip this check. + // Since newTotalGas < masGas, which + // must be non-negative, it follows that this won't overflow. + newTotalGas := totalGas + memTx.gasWanted + if maxGas > -1 && newTotalGas > maxGas { return txs } - totalGas += memTx.gasWanted + totalGas = newTotalGas txs = append(txs, memTx.tx) } return txs From f7e463f6d31e3817416fd6d9b7b27497bcd38a0e Mon Sep 17 00:00:00 2001 From: mircea-c Date: Wed, 12 Dec 2018 04:48:53 -0500 Subject: [PATCH 149/267] circleci: add a job to automatically update docs (#3005) --- .circleci/config.yml | 22 ++++++++++++++++++++++ CHANGELOG_PENDING.md | 1 + 2 files changed, 23 insertions(+) diff --git a/.circleci/config.yml b/.circleci/config.yml index 0de4a179..0bbe76ff 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -7,6 +7,13 @@ defaults: &defaults environment: GOBIN: /tmp/workspace/bin +docs_update_config: &docs_update_config + working_directory: ~/repo + docker: + - image: tendermint/docs_deployment + environment: + AWS_REGION: us-east-1 + jobs: setup_dependencies: <<: *defaults @@ -339,10 +346,25 @@ jobs: name: upload command: bash .circleci/codecov.sh -f coverage.txt + deploy_docs: + <<: *docs_update_config + steps: + - checkout + - run: + name: Trigger website build + command: | + chamber exec tendermint -- start_website_build + workflows: version: 2 test-suite: jobs: + - deploy_docs: + filters: + branches: + only: + - master + - develop - setup_dependencies - lint: requires: diff --git a/CHANGELOG_PENDING.md b/CHANGELOG_PENDING.md index 4fb20cdf..3cc491fe 100644 --- a/CHANGELOG_PENDING.md +++ b/CHANGELOG_PENDING.md @@ -20,6 +20,7 @@ Special thanks to external contributors on this release: ### IMPROVEMENTS: - [rpc] Add `UnconfirmedTxs(limit)` and `NumUnconfirmedTxs()` methods to HTTP/Local clients (@danil-lashin) +- [ci/cd] Updated CircleCI job to trigger website build when docs are updated ### BUG FIXES: - [kv indexer] \#2912 don't ignore key when executing CONTAINS From 3fbe9f235a1cc994c0eee24f55ffae7fa952ce63 Mon Sep 17 00:00:00 2001 From: Zach Date: Fri, 14 Dec 2018 00:32:09 -0500 Subject: [PATCH 150/267] docs: add edit on Github links (#3014) --- docs/.vuepress/config.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/docs/.vuepress/config.js b/docs/.vuepress/config.js index a1e150c3..7c8aeee5 100644 --- a/docs/.vuepress/config.js +++ b/docs/.vuepress/config.js @@ -8,6 +8,11 @@ module.exports = { lineNumbers: true }, themeConfig: { + repo: "tendermint/tendermint", + editLinks: true, + docsDir: "docs", + docsBranch: "develop", + editLinkText: 'Edit this page on Github', lastUpdated: true, algolia: { apiKey: '59f0e2deb984aa9cdf2b3a5fd24ac501', From f5cca9f1215814301ea4996db747b64eb3926d84 Mon Sep 17 00:00:00 2001 From: Zach Date: Sat, 15 Dec 2018 14:01:28 -0500 Subject: [PATCH 151/267] docs: update DOCS_README (#3019) Co-Authored-By: zramsay --- docs/DOCS_README.md | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/docs/DOCS_README.md b/docs/DOCS_README.md index a7671c36..c91d0391 100644 --- a/docs/DOCS_README.md +++ b/docs/DOCS_README.md @@ -12,10 +12,10 @@ respectively. ## How It Works -There is a Jenkins job listening for changes in the `/docs` directory, on both +There is a CircleCI job listening for changes in the `/docs` directory, on both the `master` and `develop` branches. Any updates to files in this directory on those branches will automatically trigger a website deployment. Under the hood, -a private website repository has make targets consumed by a standard Jenkins task. +the private website repository has a `make build-docs` target consumed by a CircleCI job in that repo. ## README @@ -93,6 +93,10 @@ python -m SimpleHTTPServer 8080 then navigate to localhost:8080 in your browser. +## Search + +We are using [Algolia](https://www.algolia.com) to power full-text search. This uses a public API search-only key in the `config.js` as well as a [tendermint.json](https://github.com/algolia/docsearch-configs/blob/master/configs/tendermint.json) configuration file that we can update with PRs. + ## Consistency Because the build processes are identical (as is the information contained herein), this file should be kept in sync as From ae275d791e7e802b6fec9243af56c7a3b53f6a08 Mon Sep 17 00:00:00 2001 From: Anton Kaliaev Date: Sat, 15 Dec 2018 23:27:02 +0400 Subject: [PATCH 152/267] mempool: notifyTxsAvailable if there're txs left, but recheck=false (#2991) (left after committing a block) Fixes #2961 -------------- ORIGINAL ISSUE Tendermint version : 0.26.4-b771798d ABCI app : kv-store Environment: OS (e.g. from /etc/os-release): macOS 10.14.1 What happened: Set mempool.recheck = false and create empty block = false in config.toml. When transactions get added right between a new empty block is being proposed and committed, the proposer won't propose new block for that transactions immediately after. That transactions are stuck in the mempool until a new transaction is added and trigger the proposer. What you expected to happen: If there is a transaction left in the mempool, new block should be proposed immediately. Have you tried the latest version: yes How to reproduce it (as minimally and precisely as possible): Fire two transaction using broadcast_tx_sync with specific delay between them. (You may need to do it multiple time before the right delay is found, on my machine the delay is 0.98s) Logs (paste a small part showing an error (< 10 lines) or link a pastebin, gist, etc. containing more of the log file): https://pastebin.com/0Wt6uhPF Config (you can paste only the changes you've made): [mempool] recheck = false create_empty_block = false Anything else we need to know: In mempool.go, we found that proposer will immediately propose new block if Last committed block has some transaction (causing AppHash to changed) or mem.notifyTxsAvailable() is called. Our scenario is as followed. A transaction is fired, it will create 1 block with 1 tx (line 1-4 in the log) and 1 empty block. After the empty block is proposed but before it is committed, second transaction is fired and added to mempool. (line 8-16) Now, since the last committed block is empty and mem.notifyTxsAvailable() will be called only if mempool.recheck = true. The proposer won't immediately propose new block, causing the second transaction to stuck in mempool until another transaction is added to mempool and trigger mem.notifyTxsAvailable(). --- CHANGELOG_PENDING.md | 1 + mempool/mempool.go | 19 ++++++++++++------- 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/CHANGELOG_PENDING.md b/CHANGELOG_PENDING.md index 3cc491fe..bcbeb496 100644 --- a/CHANGELOG_PENDING.md +++ b/CHANGELOG_PENDING.md @@ -24,5 +24,6 @@ Special thanks to external contributors on this release: ### BUG FIXES: - [kv indexer] \#2912 don't ignore key when executing CONTAINS +- [mempool] \#2961 notifyTxsAvailable if there're txs left after committing a block, but recheck=false - [mempool] \#2994 Don't allow txs with negative gas wanted - [p2p] \#2715 fix a bug where seeds don't disconnect from a peer after 3h diff --git a/mempool/mempool.go b/mempool/mempool.go index 40468315..c5f966c4 100644 --- a/mempool/mempool.go +++ b/mempool/mempool.go @@ -556,13 +556,18 @@ func (mem *Mempool) Update( // Remove committed transactions. txsLeft := mem.removeTxs(txs) - // Recheck mempool txs if any txs were committed in the block - if mem.config.Recheck && len(txsLeft) > 0 { - mem.logger.Info("Recheck txs", "numtxs", len(txsLeft), "height", height) - mem.recheckTxs(txsLeft) - // At this point, mem.txs are being rechecked. - // mem.recheckCursor re-scans mem.txs and possibly removes some txs. - // Before mem.Reap(), we should wait for mem.recheckCursor to be nil. + // Either recheck non-committed txs to see if they became invalid + // or just notify there're some txs left. + if len(txsLeft) > 0 { + if mem.config.Recheck { + mem.logger.Info("Recheck txs", "numtxs", len(txsLeft), "height", height) + mem.recheckTxs(txsLeft) + // At this point, mem.txs are being rechecked. + // mem.recheckCursor re-scans mem.txs and possibly removes some txs. + // Before mem.Reap(), we should wait for mem.recheckCursor to be nil. + } else { + mem.notifyTxsAvailable() + } } // Update metrics From f82a8ff73a6352355c2f10809a1cce1b23349d20 Mon Sep 17 00:00:00 2001 From: JamesRay <66258875@qq.com> Date: Sun, 16 Dec 2018 03:33:30 +0800 Subject: [PATCH 153/267] During replay, when appHeight==0, only saveState if stateHeight is also 0 (#3006) * optimize addProposalBlockPart * optimize addProposalBlockPart * if ProposalBlockParts and LockedBlockParts both exist,let LockedBlockParts overwrite ProposalBlockParts. * fix tryAddBlock * broadcast lockedBlockParts in higher priority * when appHeight==0, it's better fetch genDoc than state.validators. * not save state if replay from height 1 * only save state if replay from height 1 when stateHeight is also 1 * only save state if replay from height 1 when stateHeight is also 1 * only save state if replay from height 0 when stateHeight is also 0 * handshake info's response version only update when stateHeight==0 * save the handshake responseInfo appVersion --- CHANGELOG_PENDING.md | 1 + consensus/replay.go | 37 ++++++++++++++++++++----------------- 2 files changed, 21 insertions(+), 17 deletions(-) diff --git a/CHANGELOG_PENDING.md b/CHANGELOG_PENDING.md index bcbeb496..98acb141 100644 --- a/CHANGELOG_PENDING.md +++ b/CHANGELOG_PENDING.md @@ -27,3 +27,4 @@ Special thanks to external contributors on this release: - [mempool] \#2961 notifyTxsAvailable if there're txs left after committing a block, but recheck=false - [mempool] \#2994 Don't allow txs with negative gas wanted - [p2p] \#2715 fix a bug where seeds don't disconnect from a peer after 3h +- [replay] \#3006 saveState only when stateHeight is also 0 diff --git a/consensus/replay.go b/consensus/replay.go index ba118e66..16b66e86 100644 --- a/consensus/replay.go +++ b/consensus/replay.go @@ -11,7 +11,6 @@ import ( "time" abci "github.com/tendermint/tendermint/abci/types" - "github.com/tendermint/tendermint/version" //auto "github.com/tendermint/tendermint/libs/autofile" cmn "github.com/tendermint/tendermint/libs/common" dbm "github.com/tendermint/tendermint/libs/db" @@ -20,6 +19,7 @@ import ( "github.com/tendermint/tendermint/proxy" sm "github.com/tendermint/tendermint/state" "github.com/tendermint/tendermint/types" + "github.com/tendermint/tendermint/version" ) var crc32c = crc32.MakeTable(crc32.Castagnoli) @@ -247,6 +247,7 @@ func (h *Handshaker) Handshake(proxyApp proxy.AppConns) error { // Set AppVersion on the state. h.initialState.Version.Consensus.App = version.Protocol(res.AppVersion) + sm.SaveState(h.stateDB, h.initialState) // Replay blocks up to the latest in the blockstore. _, err = h.ReplayBlocks(h.initialState, appHash, blockHeight, proxyApp) @@ -295,25 +296,27 @@ func (h *Handshaker) ReplayBlocks( return nil, err } - // If the app returned validators or consensus params, update the state. - if len(res.Validators) > 0 { - vals, err := types.PB2TM.ValidatorUpdates(res.Validators) - if err != nil { - return nil, err + if stateBlockHeight == 0 { //we only update state when we are in initial state + // If the app returned validators or consensus params, update the state. + if len(res.Validators) > 0 { + vals, err := types.PB2TM.ValidatorUpdates(res.Validators) + if err != nil { + return nil, err + } + 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") + } } - 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) } + sm.SaveState(h.stateDB, state) } - - if res.ConsensusParams != nil { - state.ConsensusParams = types.PB2TM.ConsensusParams(res.ConsensusParams) - } - sm.SaveState(h.stateDB, state) } // First handle edge cases and constraints on the storeBlockHeight. From 7c9e767e1fc2fa3f84d50a7313e20ddbff174b46 Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Sat, 15 Dec 2018 15:26:27 -0500 Subject: [PATCH 154/267] 2980 fix cors (#3021) * config: cors options are arrays of strings, not strings Fixes #2980 * docs: update tendermint-core/configuration.html page * set allow_duplicate_ip to false * in `tendermint testnet`, set allow_duplicate_ip to true Refs #2712 * fixes after Ismail's review * Revert "set allow_duplicate_ip to false" This reverts commit 24c1094ebcf2bd35f2642a44d7a1e5fb5c178fb1. --- CHANGELOG_PENDING.md | 1 + config/config.go | 8 ++-- config/toml.go | 16 ++++---- docs/tendermint-core/configuration.md | 56 ++++++++++++++++----------- 4 files changed, 46 insertions(+), 35 deletions(-) diff --git a/CHANGELOG_PENDING.md b/CHANGELOG_PENDING.md index 98acb141..9854bce5 100644 --- a/CHANGELOG_PENDING.md +++ b/CHANGELOG_PENDING.md @@ -23,6 +23,7 @@ Special thanks to external contributors on this release: - [ci/cd] Updated CircleCI job to trigger website build when docs are updated ### BUG FIXES: +- [config] \#2980 fix cors options formatting - [kv indexer] \#2912 don't ignore key when executing CONTAINS - [mempool] \#2961 notifyTxsAvailable if there're txs left after committing a block, but recheck=false - [mempool] \#2994 Don't allow txs with negative gas wanted diff --git a/config/config.go b/config/config.go index 23b03399..2b9c8758 100644 --- a/config/config.go +++ b/config/config.go @@ -283,7 +283,7 @@ type RPCConfig struct { // Maximum number of simultaneous connections. // Does not include RPC (HTTP&WebSocket) connections. See max_open_connections - // If you want to accept more significant number than the default, make sure + // If you want to accept a larger number than the default, make sure // you increase your OS limits. // 0 - unlimited. GRPCMaxOpenConnections int `mapstructure:"grpc_max_open_connections"` @@ -293,7 +293,7 @@ type RPCConfig struct { // Maximum number of simultaneous connections (including WebSocket). // Does not include gRPC connections. See grpc_max_open_connections - // If you want to accept more significant number than the default, make sure + // If you want to accept a larger number than the default, make sure // you increase your OS limits. // 0 - unlimited. // Should be < {ulimit -Sn} - {MaxNumInboundPeers} - {MaxNumOutboundPeers} - {N of wal, db and other open files} @@ -774,12 +774,12 @@ type InstrumentationConfig struct { PrometheusListenAddr string `mapstructure:"prometheus_listen_addr"` // Maximum number of simultaneous connections. - // If you want to accept more significant number than the default, make sure + // If you want to accept a larger number than the default, make sure // you increase your OS limits. // 0 - unlimited. MaxOpenConnections int `mapstructure:"max_open_connections"` - // Tendermint instrumentation namespace. + // Instrumentation namespace. Namespace string `mapstructure:"namespace"` } diff --git a/config/toml.go b/config/toml.go index 21e017b4..9aa30451 100644 --- a/config/toml.go +++ b/config/toml.go @@ -125,13 +125,13 @@ laddr = "{{ .RPC.ListenAddress }}" # A list of origins a cross-domain request can be executed from # Default value '[]' disables cors support # Use '["*"]' to allow any origin -cors_allowed_origins = "{{ .RPC.CORSAllowedOrigins }}" +cors_allowed_origins = [{{ range .RPC.CORSAllowedOrigins }}{{ printf "%q, " . }}{{end}}] # A list of methods the client is allowed to use with cross-domain requests -cors_allowed_methods = "{{ .RPC.CORSAllowedMethods }}" +cors_allowed_methods = [{{ range .RPC.CORSAllowedMethods }}{{ printf "%q, " . }}{{end}}] # A list of non simple headers the client is allowed to use with cross-domain requests -cors_allowed_headers = "{{ .RPC.CORSAllowedHeaders }}" +cors_allowed_headers = [{{ range .RPC.CORSAllowedHeaders }}{{ printf "%q, " . }}{{end}}] # TCP or UNIX socket address for the gRPC server to listen on # NOTE: This server only supports /broadcast_tx_commit @@ -139,7 +139,7 @@ grpc_laddr = "{{ .RPC.GRPCListenAddress }}" # Maximum number of simultaneous connections. # Does not include RPC (HTTP&WebSocket) connections. See max_open_connections -# If you want to accept more significant number than the default, make sure +# If you want to accept a larger number than the default, make sure # you increase your OS limits. # 0 - unlimited. # Should be < {ulimit -Sn} - {MaxNumInboundPeers} - {MaxNumOutboundPeers} - {N of wal, db and other open files} @@ -151,7 +151,7 @@ unsafe = {{ .RPC.Unsafe }} # Maximum number of simultaneous connections (including WebSocket). # Does not include gRPC connections. See grpc_max_open_connections -# If you want to accept more significant number than the default, make sure +# If you want to accept a larger number than the default, make sure # you increase your OS limits. # 0 - unlimited. # Should be < {ulimit -Sn} - {MaxNumInboundPeers} - {MaxNumOutboundPeers} - {N of wal, db and other open files} @@ -269,8 +269,8 @@ blocktime_iota = "{{ .Consensus.BlockTimeIota }}" # What indexer to use for transactions # # Options: -# 1) "null" (default) -# 2) "kv" - the simplest possible indexer, backed by key-value storage (defaults to levelDB; see DBBackend). +# 1) "null" +# 2) "kv" (default) - the simplest possible indexer, backed by key-value storage (defaults to levelDB; see DBBackend). indexer = "{{ .TxIndex.Indexer }}" # Comma-separated list of tags to index (by default the only tag is "tx.hash") @@ -302,7 +302,7 @@ prometheus = {{ .Instrumentation.Prometheus }} prometheus_listen_addr = "{{ .Instrumentation.PrometheusListenAddr }}" # Maximum number of simultaneous connections. -# If you want to accept more significant number than the default, make sure +# If you want to accept a larger number than the default, make sure # you increase your OS limits. # 0 - unlimited. max_open_connections = {{ .Instrumentation.MaxOpenConnections }} diff --git a/docs/tendermint-core/configuration.md b/docs/tendermint-core/configuration.md index 7d1a562e..b61ebe73 100644 --- a/docs/tendermint-core/configuration.md +++ b/docs/tendermint-core/configuration.md @@ -36,22 +36,26 @@ db_backend = "leveldb" # Database directory db_dir = "data" -# Output level for logging -log_level = "state:info,*:error" +# Output level for logging, including package level options +log_level = "main:info,state:info,*:error" # Output format: 'plain' (colored text) or 'json' log_format = "plain" ##### additional base config options ##### -# The ID of the chain to join (should be signed with every transaction and vote) -chain_id = "" - # Path to the JSON file containing the initial validator set and other meta data -genesis_file = "genesis.json" +genesis_file = "config/genesis.json" # Path to the JSON file containing the private key to use as a validator in the consensus protocol -priv_validator_file = "priv_validator.json" +priv_validator_file = "config/priv_validator.json" + +# TCP or UNIX socket address for Tendermint to listen on for +# connections from an external PrivValidator process +priv_validator_laddr = "" + +# Path to the JSON file containing the private key to use for node authentication in the p2p protocol +node_key_file = "config/node_key.json" # Mechanism to connect to the ABCI application: socket | grpc abci = "socket" @@ -74,13 +78,13 @@ laddr = "tcp://0.0.0.0:26657" # A list of origins a cross-domain request can be executed from # Default value '[]' disables cors support # Use '["*"]' to allow any origin -cors_allowed_origins = "[]" +cors_allowed_origins = [] # A list of methods the client is allowed to use with cross-domain requests -cors_allowed_methods = "[HEAD GET POST]" +cors_allowed_methods = [HEAD GET POST] # A list of non simple headers the client is allowed to use with cross-domain requests -cors_allowed_headers = "[Origin Accept Content-Type X-Requested-With X-Server-Time]" +cors_allowed_headers = [Origin Accept Content-Type X-Requested-With X-Server-Time] # TCP or UNIX socket address for the gRPC server to listen on # NOTE: This server only supports /broadcast_tx_commit @@ -88,7 +92,7 @@ grpc_laddr = "" # Maximum number of simultaneous connections. # Does not include RPC (HTTP&WebSocket) connections. See max_open_connections -# If you want to accept more significant number than the default, make sure +# If you want to accept a larger number than the default, make sure # you increase your OS limits. # 0 - unlimited. # Should be < {ulimit -Sn} - {MaxNumInboundPeers} - {MaxNumOutboundPeers} - {N of wal, db and other open files} @@ -100,7 +104,7 @@ unsafe = false # Maximum number of simultaneous connections (including WebSocket). # Does not include gRPC connections. See grpc_max_open_connections -# If you want to accept more significant number than the default, make sure +# If you want to accept a larger number than the default, make sure # you increase your OS limits. # 0 - unlimited. # Should be < {ulimit -Sn} - {MaxNumInboundPeers} - {MaxNumOutboundPeers} - {N of wal, db and other open files} @@ -113,6 +117,12 @@ max_open_connections = 900 # Address to listen for incoming connections laddr = "tcp://0.0.0.0:26656" +# Address to advertise to peers for them to dial +# If empty, will use the same port as the laddr, +# and will introspect on the listener or use UPnP +# to figure out the address. +external_address = "" + # Comma separated list of seed nodes to connect to seeds = "" @@ -123,7 +133,7 @@ persistent_peers = "" upnp = false # Path to address book -addr_book_file = "addrbook.json" +addr_book_file = "config/addrbook.json" # Set true for strict address routability rules # Set false for private or local networks @@ -171,26 +181,26 @@ dial_timeout = "3s" recheck = true broadcast = true -wal_dir = "data/mempool.wal" +wal_dir = "" # size of the mempool -size = 100000 +size = 5000 # size of the cache (used to filter transactions we saw earlier) -cache_size = 100000 +cache_size = 10000 ##### consensus configuration options ##### [consensus] wal_file = "data/cs.wal/wal" -timeout_propose = "3000ms" +timeout_propose = "3s" timeout_propose_delta = "500ms" -timeout_prevote = "1000ms" +timeout_prevote = "1s" timeout_prevote_delta = "500ms" -timeout_precommit = "1000ms" +timeout_precommit = "1s" timeout_precommit_delta = "500ms" -timeout_commit = "1000ms" +timeout_commit = "1s" # Make progress as soon as we have all the precommits (as if TimeoutCommit = 0) skip_timeout_commit = false @@ -201,10 +211,10 @@ create_empty_blocks_interval = "0s" # Reactor sleep duration parameters peer_gossip_sleep_duration = "100ms" -peer_query_maj23_sleep_duration = "2000ms" +peer_query_maj23_sleep_duration = "2s" # Block time parameters. Corresponds to the minimum time increment between consecutive blocks. -blocktime_iota = "1000ms" +blocktime_iota = "1s" ##### transactions indexer configuration options ##### [tx_index] @@ -245,7 +255,7 @@ prometheus = false prometheus_listen_addr = ":26660" # Maximum number of simultaneous connections. -# If you want to accept a more significant number than the default, make sure +# If you want to accept a larger number than the default, make sure # you increase your OS limits. # 0 - unlimited. max_open_connections = 3 From a75dab492ce74759f32065533df63c45b1d7fd4e Mon Sep 17 00:00:00 2001 From: Hleb Albau Date: Sat, 15 Dec 2018 23:32:35 +0300 Subject: [PATCH 155/267] #2980 fix cors doc (#3013) --- docs/tendermint-core/configuration.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/tendermint-core/configuration.md b/docs/tendermint-core/configuration.md index b61ebe73..e66dcf51 100644 --- a/docs/tendermint-core/configuration.md +++ b/docs/tendermint-core/configuration.md @@ -81,10 +81,10 @@ laddr = "tcp://0.0.0.0:26657" cors_allowed_origins = [] # A list of methods the client is allowed to use with cross-domain requests -cors_allowed_methods = [HEAD GET POST] +cors_allowed_methods = ["HEAD", "GET", "POST"] # A list of non simple headers the client is allowed to use with cross-domain requests -cors_allowed_headers = [Origin Accept Content-Type X-Requested-With X-Server-Time] +cors_allowed_headers = ["Origin", "Accept", "Content-Type", "X-Requested-With", "X-Server-Time"] # TCP or UNIX socket address for the gRPC server to listen on # NOTE: This server only supports /broadcast_tx_commit From b53a2712df582d7ea4b3cf5aca0427e2e1b07751 Mon Sep 17 00:00:00 2001 From: Anton Kaliaev Date: Sun, 16 Dec 2018 00:38:13 +0400 Subject: [PATCH 156/267] docs: networks/docker-compose: small fixes (#3017) --- Makefile | 1 + docs/networks/docker-compose.md | 66 ++++++++++++++++++--------------- 2 files changed, 37 insertions(+), 30 deletions(-) diff --git a/Makefile b/Makefile index 6f7874ee..d0f8c439 100644 --- a/Makefile +++ b/Makefile @@ -294,6 +294,7 @@ build-linux: build-docker-localnode: cd networks/local make + cd - # Run a 4-node testnet locally localnet-start: localnet-stop diff --git a/docs/networks/docker-compose.md b/docs/networks/docker-compose.md index a1924eb9..52616b3d 100644 --- a/docs/networks/docker-compose.md +++ b/docs/networks/docker-compose.md @@ -1,20 +1,17 @@ # Docker Compose -With Docker Compose, we can spin up local testnets in a single command: - -``` -make localnet-start -``` +With Docker Compose, you can spin up local testnets with a single command. ## Requirements -- [Install tendermint](/docs/install.md) -- [Install docker](https://docs.docker.com/engine/installation/) -- [Install docker-compose](https://docs.docker.com/compose/install/) +1. [Install tendermint](/docs/install.md) +2. [Install docker](https://docs.docker.com/engine/installation/) +3. [Install docker-compose](https://docs.docker.com/compose/install/) ## Build -Build the `tendermint` binary and the `tendermint/localnode` docker image. +Build the `tendermint` binary and, optionally, the `tendermint/localnode` +docker image. Note the binary will be mounted into the container so it can be updated without rebuilding the image. @@ -25,11 +22,10 @@ cd $GOPATH/src/github.com/tendermint/tendermint # Build the linux binary in ./build make build-linux -# Build tendermint/localnode image +# (optionally) Build tendermint/localnode image make build-docker-localnode ``` - ## Run a testnet To start a 4 node testnet run: @@ -38,9 +34,13 @@ To start a 4 node testnet run: make localnet-start ``` -The nodes bind their RPC servers to ports 26657, 26660, 26662, and 26664 on the host. +The nodes bind their RPC servers to ports 26657, 26660, 26662, and 26664 on the +host. + This file creates a 4-node network using the localnode image. -The nodes of the network expose their P2P and RPC endpoints to the host machine on ports 26656-26657, 26659-26660, 26661-26662, and 26663-26664 respectively. + +The nodes of the network expose their P2P and RPC endpoints to the host machine +on ports 26656-26657, 26659-26660, 26661-26662, and 26663-26664 respectively. To update the binary, just rebuild it and restart the nodes: @@ -52,34 +52,40 @@ make localnet-start ## Configuration -The `make localnet-start` creates files for a 4-node testnet in `./build` by calling the `tendermint testnet` command. +The `make localnet-start` creates files for a 4-node testnet in `./build` by +calling the `tendermint testnet` command. -The `./build` directory is mounted to the `/tendermint` mount point to attach the binary and config files to the container. +The `./build` directory is mounted to the `/tendermint` mount point to attach +the binary and config files to the container. -For instance, to create a single node testnet: +To change the number of validators / non-validators change the `localnet-start` Makefile target: + +``` +localnet-start: localnet-stop + @if ! [ -f build/node0/config/genesis.json ]; then docker run --rm -v $(CURDIR)/build:/tendermint:Z tendermint/localnode testnet --v 5 --n 3 --o . --populate-persistent-peers --starting-ip-address 192.167.10.2 ; fi + docker-compose up +``` + +The command now will generate config files for 5 validators and 3 +non-validators network. + +Before running it, don't forget to cleanup the old files: ``` cd $GOPATH/src/github.com/tendermint/tendermint # Clear the build folder -rm -rf ./build - -# Build binary -make build-linux - -# Create configuration -docker run -e LOG="stdout" -v `pwd`/build:/tendermint tendermint/localnode testnet --o . --v 1 - -#Run the node -docker run -v `pwd`/build:/tendermint tendermint/localnode - +rm -rf ./build/node* ``` ## Logging -Log is saved under the attached volume, in the `tendermint.log` file. If the `LOG` environment variable is set to `stdout` at start, the log is not saved, but printed on the screen. +Log is saved under the attached volume, in the `tendermint.log` file. If the +`LOG` environment variable is set to `stdout` at start, the log is not saved, +but printed on the screen. ## Special binaries -If you have multiple binaries with different names, you can specify which one to run with the BINARY environment variable. The path of the binary is relative to the attached volume. - +If you have multiple binaries with different names, you can specify which one +to run with the `BINARY` environment variable. The path of the binary is relative +to the attached volume. From e4806f980bfd25eae9d60b9cda7d26ec8b2c68a8 Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Sat, 15 Dec 2018 15:58:09 -0500 Subject: [PATCH 157/267] Bucky/v0.27.1 (#3022) * update changelog * linkify * changelog and version --- CHANGELOG.md | 26 ++++++++++++++++++++++++++ CHANGELOG_PENDING.md | 10 +--------- version/version.go | 2 +- 3 files changed, 28 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2ec6b1d9..d7675d29 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,31 @@ # Changelog +## v0.27.1 + +*December 15th, 2018* + +Special thanks to external contributors on this release: +@danil-lashin, @hleb-albau, @james-ray, @leo-xinwang + +### FEATURES: +- [rpc] [\#2964](https://github.com/tendermint/tendermint/issues/2964) Add `UnconfirmedTxs(limit)` and `NumUnconfirmedTxs()` methods to HTTP/Local clients (@danil-lashin) +- [docs] [\#3004](https://github.com/tendermint/tendermint/issues/3004) Enable full-text search on docs pages + +### IMPROVEMENTS: +- [consensus] [\#2971](https://github.com/tendermint/tendermint/issues/2971) Return error if ValidatorSet is empty after InitChain + (@leo-xinwang) +- [ci/cd] [\#3005](https://github.com/tendermint/tendermint/issues/3005) Updated CircleCI job to trigger website build when docs are updated +- [docs] Various updates + +### BUG FIXES: +- [cmd] [\#2983](https://github.com/tendermint/tendermint/issues/2983) `testnet` command always sets `addr_book_strict = false` +- [config] [\#2980](https://github.com/tendermint/tendermint/issues/2980) Fix CORS options formatting +- [kv indexer] [\#2912](https://github.com/tendermint/tendermint/issues/2912) Don't ignore key when executing CONTAINS +- [mempool] [\#2961](https://github.com/tendermint/tendermint/issues/2961) Call `notifyTxsAvailable` if there're txs left after committing a block, but recheck=false +- [mempool] [\#2994](https://github.com/tendermint/tendermint/issues/2994) Reject txs with negative GasWanted +- [p2p] [\#2990](https://github.com/tendermint/tendermint/issues/2990) Fix a bug where seeds don't disconnect from a peer after 3h +- [consensus] [\#3006](https://github.com/tendermint/tendermint/issues/3006) Save state after InitChain only when stateHeight is also 0 (@james-ray) + ## v0.27.0 *December 5th, 2018* diff --git a/CHANGELOG_PENDING.md b/CHANGELOG_PENDING.md index 9854bce5..e9a1d52f 100644 --- a/CHANGELOG_PENDING.md +++ b/CHANGELOG_PENDING.md @@ -1,4 +1,4 @@ -## v0.27.1 +## v0.27.2 *TBD* @@ -19,13 +19,5 @@ Special thanks to external contributors on this release: ### FEATURES: ### IMPROVEMENTS: -- [rpc] Add `UnconfirmedTxs(limit)` and `NumUnconfirmedTxs()` methods to HTTP/Local clients (@danil-lashin) -- [ci/cd] Updated CircleCI job to trigger website build when docs are updated ### BUG FIXES: -- [config] \#2980 fix cors options formatting -- [kv indexer] \#2912 don't ignore key when executing CONTAINS -- [mempool] \#2961 notifyTxsAvailable if there're txs left after committing a block, but recheck=false -- [mempool] \#2994 Don't allow txs with negative gas wanted -- [p2p] \#2715 fix a bug where seeds don't disconnect from a peer after 3h -- [replay] \#3006 saveState only when stateHeight is also 0 diff --git a/version/version.go b/version/version.go index 921e1430..3c1e2f31 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.27.0" + TMCoreSemVer = "0.27.1" // ABCISemVer is the semantic version of the ABCI library ABCISemVer = "0.15.0" From 9a6dd96cba96155cfbb590cc522b943d6deda499 Mon Sep 17 00:00:00 2001 From: Jae Kwon Date: Sun, 16 Dec 2018 09:27:16 -0800 Subject: [PATCH 158/267] Revert to using defers in addrbook. (#3025) * Revert to using defers in addrbook. ValidateBasic->Validate since it requires DNS * Update CHANGELOG_PENDING --- CHANGELOG_PENDING.md | 2 ++ node/node.go | 5 +++++ p2p/netaddress.go | 6 ++++++ p2p/node_info.go | 6 +++--- p2p/node_info_test.go | 6 +++--- p2p/pex/addrbook.go | 25 ++++++++++++++++++------- p2p/test_util.go | 2 +- p2p/transport.go | 2 +- 8 files changed, 39 insertions(+), 15 deletions(-) diff --git a/CHANGELOG_PENDING.md b/CHANGELOG_PENDING.md index e9a1d52f..6742f4e8 100644 --- a/CHANGELOG_PENDING.md +++ b/CHANGELOG_PENDING.md @@ -21,3 +21,5 @@ Special thanks to external contributors on this release: ### IMPROVEMENTS: ### BUG FIXES: + +- [\#3025](https://github.com/tendermint/tendermint/pull/3025) - Revert to using defers in addrbook. Fixes deadlocks in pex and consensus upon invalid ExternalAddr/ListenAddr configuration. diff --git a/node/node.go b/node/node.go index b56a3594..1bb195fb 100644 --- a/node/node.go +++ b/node/node.go @@ -817,6 +817,11 @@ func makeNodeInfo( nodeInfo.ListenAddr = lAddr + err := nodeInfo.Validate() + if err != nil { + panic(err) + } + return nodeInfo } diff --git a/p2p/netaddress.go b/p2p/netaddress.go index f60271bc..5534ded9 100644 --- a/p2p/netaddress.go +++ b/p2p/netaddress.go @@ -175,6 +175,9 @@ func (na *NetAddress) Same(other interface{}) bool { // String representation: @: func (na *NetAddress) String() string { + if na == nil { + return "" + } if na.str == "" { addrStr := na.DialString() if na.ID != "" { @@ -186,6 +189,9 @@ func (na *NetAddress) String() string { } func (na *NetAddress) DialString() string { + if na == nil { + return "" + } return net.JoinHostPort( na.IP.String(), strconv.FormatUint(uint64(na.Port), 10), diff --git a/p2p/node_info.go b/p2p/node_info.go index 99daf7c4..3e02e9c1 100644 --- a/p2p/node_info.go +++ b/p2p/node_info.go @@ -36,7 +36,7 @@ type nodeInfoAddress interface { // nodeInfoTransport validates a nodeInfo and checks // our compatibility with it. It's for use in the handshake. type nodeInfoTransport interface { - ValidateBasic() error + Validate() error CompatibleWith(other NodeInfo) error } @@ -103,7 +103,7 @@ func (info DefaultNodeInfo) ID() ID { return info.ID_ } -// ValidateBasic checks the self-reported DefaultNodeInfo is safe. +// Validate checks the self-reported DefaultNodeInfo is safe. // It returns an error if there // are too many Channels, if there are any duplicate Channels, // if the ListenAddr is malformed, or if the ListenAddr is a host name @@ -116,7 +116,7 @@ func (info DefaultNodeInfo) ID() ID { // International clients could then use punycode (or we could use // url-encoding), and we just need to be careful with how we handle that in our // clients. (e.g. off by default). -func (info DefaultNodeInfo) ValidateBasic() error { +func (info DefaultNodeInfo) Validate() error { // ID is already validated. diff --git a/p2p/node_info_test.go b/p2p/node_info_test.go index c9a72dbc..19567d2b 100644 --- a/p2p/node_info_test.go +++ b/p2p/node_info_test.go @@ -12,7 +12,7 @@ func TestNodeInfoValidate(t *testing.T) { // empty fails ni := DefaultNodeInfo{} - assert.Error(t, ni.ValidateBasic()) + assert.Error(t, ni.Validate()) channels := make([]byte, maxNumChannels) for i := 0; i < maxNumChannels; i++ { @@ -68,13 +68,13 @@ func TestNodeInfoValidate(t *testing.T) { // test case passes ni = testNodeInfo(nodeKey.ID(), name).(DefaultNodeInfo) ni.Channels = channels - assert.NoError(t, ni.ValidateBasic()) + assert.NoError(t, ni.Validate()) for _, tc := range testCases { ni := testNodeInfo(nodeKey.ID(), name).(DefaultNodeInfo) ni.Channels = channels tc.malleateNodeInfo(&ni) - err := ni.ValidateBasic() + err := ni.Validate() if tc.expectErr { assert.Error(t, err, tc.testName) } else { diff --git a/p2p/pex/addrbook.go b/p2p/pex/addrbook.go index d8954f23..cfeefb34 100644 --- a/p2p/pex/addrbook.go +++ b/p2p/pex/addrbook.go @@ -162,26 +162,29 @@ func (a *addrBook) FilePath() string { // AddOurAddress one of our addresses. func (a *addrBook) AddOurAddress(addr *p2p.NetAddress) { - a.Logger.Info("Add our address to book", "addr", addr) a.mtx.Lock() + defer a.mtx.Unlock() + + a.Logger.Info("Add our address to book", "addr", addr) a.ourAddrs[addr.String()] = struct{}{} - a.mtx.Unlock() } // OurAddress returns true if it is our address. func (a *addrBook) OurAddress(addr *p2p.NetAddress) bool { a.mtx.Lock() + defer a.mtx.Unlock() + _, ok := a.ourAddrs[addr.String()] - a.mtx.Unlock() return ok } func (a *addrBook) AddPrivateIDs(IDs []string) { a.mtx.Lock() + defer a.mtx.Unlock() + for _, id := range IDs { a.privateIDs[p2p.ID(id)] = struct{}{} } - a.mtx.Unlock() } // AddAddress implements AddrBook @@ -191,6 +194,7 @@ func (a *addrBook) AddPrivateIDs(IDs []string) { func (a *addrBook) AddAddress(addr *p2p.NetAddress, src *p2p.NetAddress) error { a.mtx.Lock() defer a.mtx.Unlock() + return a.addAddress(addr, src) } @@ -198,6 +202,7 @@ func (a *addrBook) AddAddress(addr *p2p.NetAddress, src *p2p.NetAddress) error { func (a *addrBook) RemoveAddress(addr *p2p.NetAddress) { a.mtx.Lock() defer a.mtx.Unlock() + ka := a.addrLookup[addr.ID] if ka == nil { return @@ -211,14 +216,16 @@ func (a *addrBook) RemoveAddress(addr *p2p.NetAddress) { func (a *addrBook) IsGood(addr *p2p.NetAddress) bool { a.mtx.Lock() defer a.mtx.Unlock() + return a.addrLookup[addr.ID].isOld() } // HasAddress returns true if the address is in the book. func (a *addrBook) HasAddress(addr *p2p.NetAddress) bool { a.mtx.Lock() + defer a.mtx.Unlock() + ka := a.addrLookup[addr.ID] - a.mtx.Unlock() return ka != nil } @@ -292,6 +299,7 @@ func (a *addrBook) PickAddress(biasTowardsNewAddrs int) *p2p.NetAddress { func (a *addrBook) MarkGood(addr *p2p.NetAddress) { a.mtx.Lock() defer a.mtx.Unlock() + ka := a.addrLookup[addr.ID] if ka == nil { return @@ -306,6 +314,7 @@ func (a *addrBook) MarkGood(addr *p2p.NetAddress) { func (a *addrBook) MarkAttempt(addr *p2p.NetAddress) { a.mtx.Lock() defer a.mtx.Unlock() + ka := a.addrLookup[addr.ID] if ka == nil { return @@ -461,12 +470,13 @@ ADDRS_LOOP: // ListOfKnownAddresses returns the new and old addresses. func (a *addrBook) ListOfKnownAddresses() []*knownAddress { - addrs := []*knownAddress{} a.mtx.Lock() + defer a.mtx.Unlock() + + addrs := []*knownAddress{} for _, addr := range a.addrLookup { addrs = append(addrs, addr.copy()) } - a.mtx.Unlock() return addrs } @@ -476,6 +486,7 @@ func (a *addrBook) ListOfKnownAddresses() []*knownAddress { func (a *addrBook) Size() int { a.mtx.Lock() defer a.mtx.Unlock() + return a.size() } diff --git a/p2p/test_util.go b/p2p/test_util.go index 44f27be4..ea788b79 100644 --- a/p2p/test_util.go +++ b/p2p/test_util.go @@ -24,7 +24,7 @@ type mockNodeInfo struct { func (ni mockNodeInfo) ID() ID { return ni.addr.ID } func (ni mockNodeInfo) NetAddress() *NetAddress { return ni.addr } -func (ni mockNodeInfo) ValidateBasic() error { return nil } +func (ni mockNodeInfo) Validate() error { return nil } func (ni mockNodeInfo) CompatibleWith(other NodeInfo) error { return nil } func AddPeerToSwitch(sw *Switch, peer Peer) { diff --git a/p2p/transport.go b/p2p/transport.go index b16db54d..69fab312 100644 --- a/p2p/transport.go +++ b/p2p/transport.go @@ -350,7 +350,7 @@ func (mt *MultiplexTransport) upgrade( } } - if err := nodeInfo.ValidateBasic(); err != nil { + if err := nodeInfo.Validate(); err != nil { return nil, nil, ErrRejected{ conn: c, err: err, From b3141d7d02d5c3e9175eb18e34518227e7a2840a Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Sun, 16 Dec 2018 14:05:58 -0500 Subject: [PATCH 159/267] makeNodeInfo returns error (#3029) * makeNodeInfo returns error * version and changelog --- CHANGELOG.md | 12 ++++++++++++ CHANGELOG_PENDING.md | 3 +-- node/node.go | 35 ++++++++++++++++------------------- version/version.go | 2 +- 4 files changed, 30 insertions(+), 22 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d7675d29..3fb471a0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,17 @@ # Changelog +## v0.27.2 + +*December 16th, 2018* + +### IMPROVEMENTS: + +- [node] [\#3025](https://github.com/tendermint/tendermint/issues/3025) Validate NodeInfo addresses on startup. + +### BUG FIXES: + +- [p2p] [\#3025](https://github.com/tendermint/tendermint/pull/3025) Revert to using defers in addrbook. Fixes deadlocks in pex and consensus upon invalid ExternalAddr/ListenAddr configuration. + ## v0.27.1 *December 15th, 2018* diff --git a/CHANGELOG_PENDING.md b/CHANGELOG_PENDING.md index 6742f4e8..335c6732 100644 --- a/CHANGELOG_PENDING.md +++ b/CHANGELOG_PENDING.md @@ -1,4 +1,4 @@ -## v0.27.2 +## v0.27.3 *TBD* @@ -22,4 +22,3 @@ Special thanks to external contributors on this release: ### BUG FIXES: -- [\#3025](https://github.com/tendermint/tendermint/pull/3025) - Revert to using defers in addrbook. Fixes deadlocks in pex and consensus upon invalid ExternalAddr/ListenAddr configuration. diff --git a/node/node.go b/node/node.go index 1bb195fb..993f1cd1 100644 --- a/node/node.go +++ b/node/node.go @@ -348,20 +348,21 @@ func NewNode(config *cfg.Config, indexerService := txindex.NewIndexerService(txIndexer, eventBus) indexerService.SetLogger(logger.With("module", "txindex")) - var ( - p2pLogger = logger.With("module", "p2p") - nodeInfo = makeNodeInfo( - config, - nodeKey.ID(), - txIndexer, - genDoc.ChainID, - p2p.NewProtocolVersion( - version.P2PProtocol, // global - state.Version.Consensus.Block, - state.Version.Consensus.App, - ), - ) + p2pLogger := logger.With("module", "p2p") + nodeInfo, err := makeNodeInfo( + config, + nodeKey.ID(), + txIndexer, + genDoc.ChainID, + p2p.NewProtocolVersion( + version.P2PProtocol, // global + state.Version.Consensus.Block, + state.Version.Consensus.App, + ), ) + if err != nil { + return nil, err + } // Setup Transport. var ( @@ -782,7 +783,7 @@ func makeNodeInfo( txIndexer txindex.TxIndexer, chainID string, protocolVersion p2p.ProtocolVersion, -) p2p.NodeInfo { +) (p2p.NodeInfo, error) { txIndexerStatus := "on" if _, ok := txIndexer.(*null.TxIndex); ok { txIndexerStatus = "off" @@ -818,11 +819,7 @@ func makeNodeInfo( nodeInfo.ListenAddr = lAddr err := nodeInfo.Validate() - if err != nil { - panic(err) - } - - return nodeInfo + return nodeInfo, err } //------------------------------------------------------------------------------ diff --git a/version/version.go b/version/version.go index 3c1e2f31..caf6c73f 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.27.1" + TMCoreSemVer = "0.27.2" // ABCISemVer is the semantic version of the ABCI library ABCISemVer = "0.15.0" From 0533c73a50e1634cf7c60eb608ced25dbfd5fd4b Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Sun, 16 Dec 2018 14:19:38 -0500 Subject: [PATCH 160/267] crypto: revert to mainline Go crypto lib (#3027) * crypto: revert to mainline Go crypto lib We used to use a fork for a modified bcrypt so we could pass our own randomness but this was largely unecessary, unused, and a burden. So now we just use the mainline Go crypto lib. * changelog * fix tests * version and changelog --- CHANGELOG.md | 21 ++++++++++++++++----- CHANGELOG_PENDING.md | 2 +- Gopkg.lock | 5 ++--- Gopkg.toml | 3 +-- crypto/armor/armor.go | 2 +- crypto/ed25519/ed25519.go | 2 +- crypto/hash.go | 2 +- crypto/secp256k1/secp256k1.go | 2 +- crypto/xchacha20poly1305/xchachapoly.go | 2 +- crypto/xsalsa20symmetric/symmetric.go | 2 +- crypto/xsalsa20symmetric/symmetric_test.go | 6 ++---- p2p/conn/secret_connection.go | 1 - version/version.go | 2 +- 13 files changed, 29 insertions(+), 23 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3fb471a0..0397ebdb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,16 @@ # Changelog +## v0.27.3 + +*December 16th, 2018* + +### BREAKING CHANGES: + +* Go API + +- [dep] [\#3027](https://github.com/tendermint/tendermint/issues/3027) Revert to mainline Go crypto library, eliminating the modified + `bcrypt.GenerateFromPassword` + ## v0.27.2 *December 16th, 2018* @@ -84,17 +95,17 @@ message. ### 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 +- [node] [\#2959](https://github.com/tendermint/tendermint/issues/2959) Allow node to start even if software's BlockProtocol is different from state's BlockProtocol -- [pex] \#2959 Pex reactor logger uses `module=pex` +- [pex] [\#2959](https://github.com/tendermint/tendermint/issues/2959) Pex reactor logger uses `module=pex` ### BUG FIXES: -- [p2p] \#2968 Panic on transport error rather than continuing to run but not +- [p2p] [\#2968](https://github.com/tendermint/tendermint/issues/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 +- [p2p] [\#2969](https://github.com/tendermint/tendermint/issues/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) +- [rpc] [\#2408](https://github.com/tendermint/tendermint/issues/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 diff --git a/CHANGELOG_PENDING.md b/CHANGELOG_PENDING.md index 335c6732..2283ff37 100644 --- a/CHANGELOG_PENDING.md +++ b/CHANGELOG_PENDING.md @@ -1,4 +1,4 @@ -## v0.27.3 +## v0.27.4 *TBD* diff --git a/Gopkg.lock b/Gopkg.lock index 0c4779c8..76d6fcb9 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -376,7 +376,7 @@ version = "v0.14.1" [[projects]] - digest = "1:72b71e3a29775e5752ed7a8012052a3dee165e27ec18cedddae5288058f09acf" + digest = "1:00d2b3e64cdc3fa69aa250dfbe4cc38c4837d4f37e62279be2ae52107ffbbb44" name = "golang.org/x/crypto" packages = [ "bcrypt", @@ -397,8 +397,7 @@ "salsa20/salsa", ] pruneopts = "UT" - revision = "3764759f34a542a3aef74d6b02e35be7ab893bba" - source = "github.com/tendermint/crypto" + revision = "505ab145d0a99da450461ae2c1a9f6cd10d1f447" [[projects]] digest = "1:d36f55a999540d29b6ea3c2ea29d71c76b1d9853fdcd3e5c5cb4836f2ba118f1" diff --git a/Gopkg.toml b/Gopkg.toml index c5e625e9..16c1b463 100644 --- a/Gopkg.toml +++ b/Gopkg.toml @@ -81,8 +81,7 @@ [[constraint]] name = "golang.org/x/crypto" - source = "github.com/tendermint/crypto" - revision = "3764759f34a542a3aef74d6b02e35be7ab893bba" + revision = "505ab145d0a99da450461ae2c1a9f6cd10d1f447" [[override]] name = "github.com/jmhodges/levigo" diff --git a/crypto/armor/armor.go b/crypto/armor/armor.go index e3b29a97..c15d070e 100644 --- a/crypto/armor/armor.go +++ b/crypto/armor/armor.go @@ -5,7 +5,7 @@ import ( "fmt" "io/ioutil" - "golang.org/x/crypto/openpgp/armor" // forked to github.com/tendermint/crypto + "golang.org/x/crypto/openpgp/armor" ) func EncodeArmor(blockType string, headers map[string]string, data []byte) string { diff --git a/crypto/ed25519/ed25519.go b/crypto/ed25519/ed25519.go index e077cbda..0c659e73 100644 --- a/crypto/ed25519/ed25519.go +++ b/crypto/ed25519/ed25519.go @@ -7,7 +7,7 @@ import ( "io" amino "github.com/tendermint/go-amino" - "golang.org/x/crypto/ed25519" // forked to github.com/tendermint/crypto + "golang.org/x/crypto/ed25519" "github.com/tendermint/tendermint/crypto" "github.com/tendermint/tendermint/crypto/tmhash" diff --git a/crypto/hash.go b/crypto/hash.go index a384bbb5..c1fb41f7 100644 --- a/crypto/hash.go +++ b/crypto/hash.go @@ -3,7 +3,7 @@ package crypto import ( "crypto/sha256" - "golang.org/x/crypto/ripemd160" // forked to github.com/tendermint/crypto + "golang.org/x/crypto/ripemd160" ) func Sha256(bytes []byte) []byte { diff --git a/crypto/secp256k1/secp256k1.go b/crypto/secp256k1/secp256k1.go index 784409f3..7fc46d63 100644 --- a/crypto/secp256k1/secp256k1.go +++ b/crypto/secp256k1/secp256k1.go @@ -9,7 +9,7 @@ import ( secp256k1 "github.com/tendermint/btcd/btcec" amino "github.com/tendermint/go-amino" - "golang.org/x/crypto/ripemd160" // forked to github.com/tendermint/crypto + "golang.org/x/crypto/ripemd160" "github.com/tendermint/tendermint/crypto" ) diff --git a/crypto/xchacha20poly1305/xchachapoly.go b/crypto/xchacha20poly1305/xchachapoly.go index 115c9190..c7a175b5 100644 --- a/crypto/xchacha20poly1305/xchachapoly.go +++ b/crypto/xchacha20poly1305/xchachapoly.go @@ -8,7 +8,7 @@ import ( "errors" "fmt" - "golang.org/x/crypto/chacha20poly1305" // forked to github.com/tendermint/crypto + "golang.org/x/crypto/chacha20poly1305" ) // Implements crypto.AEAD diff --git a/crypto/xsalsa20symmetric/symmetric.go b/crypto/xsalsa20symmetric/symmetric.go index c51e2459..3228a935 100644 --- a/crypto/xsalsa20symmetric/symmetric.go +++ b/crypto/xsalsa20symmetric/symmetric.go @@ -4,7 +4,7 @@ import ( "errors" "fmt" - "golang.org/x/crypto/nacl/secretbox" // forked to github.com/tendermint/crypto + "golang.org/x/crypto/nacl/secretbox" "github.com/tendermint/tendermint/crypto" cmn "github.com/tendermint/tendermint/libs/common" diff --git a/crypto/xsalsa20symmetric/symmetric_test.go b/crypto/xsalsa20symmetric/symmetric_test.go index e9adf728..bca0b336 100644 --- a/crypto/xsalsa20symmetric/symmetric_test.go +++ b/crypto/xsalsa20symmetric/symmetric_test.go @@ -6,7 +6,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "golang.org/x/crypto/bcrypt" // forked to github.com/tendermint/crypto + "golang.org/x/crypto/bcrypt" "github.com/tendermint/tendermint/crypto" ) @@ -30,9 +30,7 @@ func TestSimpleWithKDF(t *testing.T) { plaintext := []byte("sometext") secretPass := []byte("somesecret") - salt := []byte("somesaltsomesalt") // len 16 - // NOTE: we use a fork of x/crypto so we can inject our own randomness for salt - secret, err := bcrypt.GenerateFromPassword(salt, secretPass, 12) + secret, err := bcrypt.GenerateFromPassword(secretPass, 12) if err != nil { t.Error(err) } diff --git a/p2p/conn/secret_connection.go b/p2p/conn/secret_connection.go index 1dc66aff..d1b6bce6 100644 --- a/p2p/conn/secret_connection.go +++ b/p2p/conn/secret_connection.go @@ -10,7 +10,6 @@ import ( "net" "time" - // forked to github.com/tendermint/crypto "golang.org/x/crypto/chacha20poly1305" "golang.org/x/crypto/curve25519" "golang.org/x/crypto/nacl/box" diff --git a/version/version.go b/version/version.go index caf6c73f..ace1b41d 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.27.2" + TMCoreSemVer = "0.27.3" // ABCISemVer is the semantic version of the ABCI library ABCISemVer = "0.15.0" From 0ff715125bf6bfbee99928ad6561245b177d21f4 Mon Sep 17 00:00:00 2001 From: Zach Date: Sun, 16 Dec 2018 23:34:13 -0500 Subject: [PATCH 161/267] fix docs / proxy app (#2988) * fix docs / proxy app, closes #2986 * counter_serial * review comments * list all possible options * add changelog entries --- CHANGELOG_PENDING.md | 2 ++ abci/cmd/abci-cli/abci-cli.go | 22 +--------------------- cmd/tendermint/commands/run_node.go | 2 +- docs/tendermint-core/using-tendermint.md | 2 +- proxy/client.go | 11 ++++++----- 5 files changed, 11 insertions(+), 28 deletions(-) diff --git a/CHANGELOG_PENDING.md b/CHANGELOG_PENDING.md index 2283ff37..022965bb 100644 --- a/CHANGELOG_PENDING.md +++ b/CHANGELOG_PENDING.md @@ -7,6 +7,8 @@ Special thanks to external contributors on this release: ### BREAKING CHANGES: * CLI/RPC/Config +- [cli] Removed `node` `--proxy_app=dummy` option. Use `kvstore` (`persistent_kvstore`) instead. +- [cli] Renamed `node` `--proxy_app=nilapp` to `--proxy_app=noop`. * Apps diff --git a/abci/cmd/abci-cli/abci-cli.go b/abci/cmd/abci-cli/abci-cli.go index 50972ec3..cc3f9c45 100644 --- a/abci/cmd/abci-cli/abci-cli.go +++ b/abci/cmd/abci-cli/abci-cli.go @@ -58,7 +58,7 @@ var RootCmd = &cobra.Command{ PersistentPreRunE: func(cmd *cobra.Command, args []string) error { switch cmd.Use { - case "counter", "kvstore", "dummy": // for the examples apps, don't pre-run + case "counter", "kvstore": // for the examples apps, don't pre-run return nil case "version": // skip running for version command return nil @@ -127,10 +127,6 @@ func addCounterFlags() { counterCmd.PersistentFlags().BoolVarP(&flagSerial, "serial", "", false, "enforce incrementing (serial) transactions") } -func addDummyFlags() { - dummyCmd.PersistentFlags().StringVarP(&flagPersist, "persist", "", "", "directory to use for a database") -} - func addKVStoreFlags() { kvstoreCmd.PersistentFlags().StringVarP(&flagPersist, "persist", "", "", "directory to use for a database") } @@ -152,10 +148,6 @@ func addCommands() { // examples addCounterFlags() RootCmd.AddCommand(counterCmd) - // deprecated, left for backwards compatibility - addDummyFlags() - RootCmd.AddCommand(dummyCmd) - // replaces dummy, see issue #196 addKVStoreFlags() RootCmd.AddCommand(kvstoreCmd) } @@ -291,18 +283,6 @@ var counterCmd = &cobra.Command{ }, } -// deprecated, left for backwards compatibility -var dummyCmd = &cobra.Command{ - Use: "dummy", - Deprecated: "use: [abci-cli kvstore] instead", - Short: "ABCI demo example", - Long: "ABCI demo example", - Args: cobra.ExactArgs(0), - RunE: func(cmd *cobra.Command, args []string) error { - return cmdKVStore(cmd, args) - }, -} - var kvstoreCmd = &cobra.Command{ Use: "kvstore", Short: "ABCI demo example", diff --git a/cmd/tendermint/commands/run_node.go b/cmd/tendermint/commands/run_node.go index 6dabacb1..ef205aa6 100644 --- a/cmd/tendermint/commands/run_node.go +++ b/cmd/tendermint/commands/run_node.go @@ -24,7 +24,7 @@ func AddNodeFlags(cmd *cobra.Command) { cmd.Flags().Bool("fast_sync", config.FastSync, "Fast blockchain syncing") // abci flags - cmd.Flags().String("proxy_app", config.ProxyApp, "Proxy app address, or 'nilapp' or 'kvstore' for local testing.") + cmd.Flags().String("proxy_app", config.ProxyApp, "Proxy app address, or one of: 'kvstore', 'persistent_kvstore', 'counter', 'counter_serial' or 'noop' for local testing.") cmd.Flags().String("abci", config.ABCI, "Specify abci transport (socket | grpc)") // rpc flags diff --git a/docs/tendermint-core/using-tendermint.md b/docs/tendermint-core/using-tendermint.md index 148c874c..2ca8c9e9 100644 --- a/docs/tendermint-core/using-tendermint.md +++ b/docs/tendermint-core/using-tendermint.md @@ -113,7 +113,7 @@ blocks are produced regularly, even if there are no transactions. See _No Empty Blocks_, below, to modify this setting. Tendermint supports in-process versions of the `counter`, `kvstore` and -`nil` apps that ship as examples with `abci-cli`. It's easy to compile +`noop` apps that ship as examples with `abci-cli`. It's easy to compile your own app in-process with Tendermint if it's written in Go. If your app is not written in Go, simply run it in another process, and use the `--proxy_app` flag to specify the address of the socket it is listening diff --git a/proxy/client.go b/proxy/client.go index 87f4e716..c5ee5fe1 100644 --- a/proxy/client.go +++ b/proxy/client.go @@ -6,6 +6,7 @@ import ( "github.com/pkg/errors" abcicli "github.com/tendermint/tendermint/abci/client" + "github.com/tendermint/tendermint/abci/example/counter" "github.com/tendermint/tendermint/abci/example/kvstore" "github.com/tendermint/tendermint/abci/types" ) @@ -64,15 +65,15 @@ func (r *remoteClientCreator) NewABCIClient() (abcicli.Client, error) { func DefaultClientCreator(addr, transport, dbDir string) ClientCreator { switch addr { + case "counter": + return NewLocalClientCreator(counter.NewCounterApplication(false)) + case "counter_serial": + return NewLocalClientCreator(counter.NewCounterApplication(true)) case "kvstore": - fallthrough - case "dummy": return NewLocalClientCreator(kvstore.NewKVStoreApplication()) case "persistent_kvstore": - fallthrough - case "persistent_dummy": return NewLocalClientCreator(kvstore.NewPersistentKVStoreApplication(dbDir)) - case "nilapp": + case "noop": return NewLocalClientCreator(types.NewBaseApplication()) default: mustConnect := false // loop retrying From a06912b5793787df769c2991270d86129d243349 Mon Sep 17 00:00:00 2001 From: Anton Kaliaev Date: Mon, 17 Dec 2018 20:35:05 +0400 Subject: [PATCH 162/267] mempool: move tx to back, not front (#3036) because we pop txs from the front if the cache is full Refs #3035 --- mempool/mempool.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mempool/mempool.go b/mempool/mempool.go index c5f966c4..3a1921bc 100644 --- a/mempool/mempool.go +++ b/mempool/mempool.go @@ -676,7 +676,7 @@ func (cache *mapTxCache) Push(tx types.Tx) bool { // Use the tx hash in the cache txHash := sha256.Sum256(tx) if moved, exists := cache.map_[txHash]; exists { - cache.list.MoveToFront(moved) + cache.list.MoveToBack(moved) return false } From 2182f6a7022366c52769c3fe8b073b8e4a9101b9 Mon Sep 17 00:00:00 2001 From: Zach Date: Mon, 17 Dec 2018 11:51:53 -0500 Subject: [PATCH 163/267] update go version & other cleanup (#3018) * update go version & other cleanup * fix lints * go one.eleven.four * keep circle on 1.11.3 for now --- .circleci/config.yml | 2 +- README.md | 2 +- blockchain/reactor.go | 6 +- docs/package-lock.json | 4670 ------------------ docs/package.json | 40 - docs/yarn.lock | 2611 ---------- scripts/install/install_tendermint_arm.sh | 10 +- scripts/install/install_tendermint_bsd.sh | 2 +- scripts/install/install_tendermint_ubuntu.sh | 2 +- types/tx_test.go | 4 +- 10 files changed, 8 insertions(+), 7341 deletions(-) delete mode 100644 docs/package-lock.json delete mode 100644 docs/package.json delete mode 100644 docs/yarn.lock diff --git a/.circleci/config.yml b/.circleci/config.yml index 0bbe76ff..dcc0e289 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -3,7 +3,7 @@ version: 2 defaults: &defaults working_directory: /go/src/github.com/tendermint/tendermint docker: - - image: circleci/golang:1.10.3 + - image: circleci/golang:1.11.3 environment: GOBIN: /tmp/workspace/bin diff --git a/README.md b/README.md index 7c386ec3..6e5c9e9a 100644 --- a/README.md +++ b/README.md @@ -49,7 +49,7 @@ For examples of the kinds of bugs we're looking for, see [SECURITY.md](SECURITY. Requirement|Notes ---|--- -Go version | Go1.10 or higher +Go version | Go1.11.4 or higher ## Documentation diff --git a/blockchain/reactor.go b/blockchain/reactor.go index e62a9e4f..bed082cd 100644 --- a/blockchain/reactor.go +++ b/blockchain/reactor.go @@ -432,11 +432,7 @@ type bcBlockResponseMessage struct { // ValidateBasic performs basic validation. func (m *bcBlockResponseMessage) ValidateBasic() error { - if err := m.Block.ValidateBasic(); err != nil { - return err - } - - return nil + return m.Block.ValidateBasic() } func (m *bcBlockResponseMessage) String() string { diff --git a/docs/package-lock.json b/docs/package-lock.json deleted file mode 100644 index 3449eda1..00000000 --- a/docs/package-lock.json +++ /dev/null @@ -1,4670 +0,0 @@ -{ - "name": "tendermint", - "version": "0.0.1", - "lockfileVersion": 1, - "requires": true, - "dependencies": { - "@azu/format-text": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@azu/format-text/-/format-text-1.0.1.tgz", - "integrity": "sha1-aWc1CpRkD2sChVFpvYl85U1s6+I=" - }, - "@azu/style-format": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@azu/style-format/-/style-format-1.0.0.tgz", - "integrity": "sha1-5wGH+Khi4ZGxvObAJo8TrNOlayA=", - "requires": { - "@azu/format-text": "^1.0.1" - } - }, - "@sindresorhus/is": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-0.7.0.tgz", - "integrity": "sha512-ONhaKPIufzzrlNbqtWFFd+jlnemX6lJAgq9ZeiZtS7I1PIf/la7CW4m83rTXRnVnsMbW2k56pGYu7AUFJD9Pow==" - }, - "@textlint/ast-node-types": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/@textlint/ast-node-types/-/ast-node-types-4.0.3.tgz", - "integrity": "sha512-mkkqbuxZkCESmMCrVN5QEgmFqBJAcoAGIaZaQfziqKAyCQBLLgKVJzeFuup9mDm9mvCTKekhLk9yIaEFc8EFxA==" - }, - "@textlint/ast-traverse": { - "version": "2.0.9", - "resolved": "https://registry.npmjs.org/@textlint/ast-traverse/-/ast-traverse-2.0.9.tgz", - "integrity": "sha512-E2neVj65wyadt3hr9R+DHW01dG4dNOMmFRab7Bph/rkDDeK85w/6RNJgIt9vBCPtt7a4bndTj1oZrK6wDZAEtQ==", - "requires": { - "@textlint/ast-node-types": "^4.0.3" - } - }, - "@textlint/feature-flag": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/@textlint/feature-flag/-/feature-flag-3.0.5.tgz", - "integrity": "sha512-hXTDGvltgiUtJs7QhALSILNE+g0cdY4CyqHR2r5+EmiYbS3NuqWVLn3GZYUPWXl9rVDky/IpR+6DF0uLJF8m8Q==", - "requires": { - "map-like": "^2.0.0" - } - }, - "@textlint/fixer-formatter": { - "version": "3.0.8", - "resolved": "https://registry.npmjs.org/@textlint/fixer-formatter/-/fixer-formatter-3.0.8.tgz", - "integrity": "sha512-LTHcCLTyESdz90NGYzrYC0juSqLzGBc5VMMRO8Xvz3fapBya/Sn5ncgvsHqnKY0OIbV/IdOT54G2F46D8R6P9Q==", - "requires": { - "@textlint/kernel": "^3.0.0", - "chalk": "^1.1.3", - "debug": "^2.1.0", - "diff": "^2.2.2", - "interop-require": "^1.0.0", - "is-file": "^1.0.0", - "string-width": "^1.0.1", - "text-table": "^0.2.0", - "try-resolve": "^1.0.1" - }, - "dependencies": { - "@textlint/kernel": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@textlint/kernel/-/kernel-3.0.0.tgz", - "integrity": "sha512-SxHWr6VAD/SdqTCy1uB03bFLbGYbhZeQTeUuIJE6s1pD7wtQ1+Y1n8nx9I9m7nqGZi5eYuVA6WnpvCq10USz+w==", - "requires": { - "@textlint/ast-node-types": "^4.0.3", - "@textlint/ast-traverse": "^2.0.9", - "@textlint/feature-flag": "^3.0.5", - "@types/bluebird": "^3.5.18", - "bluebird": "^3.5.1", - "debug": "^2.6.6", - "deep-equal": "^1.0.1", - "map-like": "^2.0.0", - "object-assign": "^4.1.1", - "structured-source": "^3.0.2" - } - }, - "ansi-styles": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", - "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=" - }, - "chalk": { - "version": "1.1.3", - "resolved": "http://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", - "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", - "requires": { - "ansi-styles": "^2.2.1", - "escape-string-regexp": "^1.0.2", - "has-ansi": "^2.0.0", - "strip-ansi": "^3.0.0", - "supports-color": "^2.0.0" - } - }, - "supports-color": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", - "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=" - } - } - }, - "@textlint/kernel": { - "version": "2.0.9", - "resolved": "https://registry.npmjs.org/@textlint/kernel/-/kernel-2.0.9.tgz", - "integrity": "sha512-0237/9yDIlSVaH0pcVAxm0rV1xF96UpjXUXoBRdciWnf2+O0tWQEeBC9B2/B2jLw9Ha0zGlK+q+bLREpXB97Cw==", - "requires": { - "@textlint/ast-node-types": "^4.0.2", - "@textlint/ast-traverse": "^2.0.8", - "@textlint/feature-flag": "^3.0.4", - "@types/bluebird": "^3.5.18", - "bluebird": "^3.5.1", - "debug": "^2.6.6", - "deep-equal": "^1.0.1", - "object-assign": "^4.1.1", - "structured-source": "^3.0.2" - } - }, - "@textlint/linter-formatter": { - "version": "3.0.8", - "resolved": "https://registry.npmjs.org/@textlint/linter-formatter/-/linter-formatter-3.0.8.tgz", - "integrity": "sha512-hayZi4ybj01Km9Soi34cT8EkmEcqGgQKHu1tvPQVd8S2zaE3m/8nmf6qhwAo/HAwMzbIj0XxdV8nVuiUfz8ADQ==", - "requires": { - "@azu/format-text": "^1.0.1", - "@azu/style-format": "^1.0.0", - "@textlint/kernel": "^3.0.0", - "chalk": "^1.0.0", - "concat-stream": "^1.5.1", - "js-yaml": "^3.2.4", - "optionator": "^0.8.1", - "pluralize": "^2.0.0", - "string-width": "^1.0.1", - "string.prototype.padstart": "^3.0.0", - "strip-ansi": "^3.0.1", - "table": "^3.7.8", - "text-table": "^0.2.0", - "try-resolve": "^1.0.1", - "xml-escape": "^1.0.0" - }, - "dependencies": { - "@textlint/kernel": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@textlint/kernel/-/kernel-3.0.0.tgz", - "integrity": "sha512-SxHWr6VAD/SdqTCy1uB03bFLbGYbhZeQTeUuIJE6s1pD7wtQ1+Y1n8nx9I9m7nqGZi5eYuVA6WnpvCq10USz+w==", - "requires": { - "@textlint/ast-node-types": "^4.0.3", - "@textlint/ast-traverse": "^2.0.9", - "@textlint/feature-flag": "^3.0.5", - "@types/bluebird": "^3.5.18", - "bluebird": "^3.5.1", - "debug": "^2.6.6", - "deep-equal": "^1.0.1", - "map-like": "^2.0.0", - "object-assign": "^4.1.1", - "structured-source": "^3.0.2" - } - }, - "ansi-styles": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", - "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=" - }, - "chalk": { - "version": "1.1.3", - "resolved": "http://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", - "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", - "requires": { - "ansi-styles": "^2.2.1", - "escape-string-regexp": "^1.0.2", - "has-ansi": "^2.0.0", - "strip-ansi": "^3.0.0", - "supports-color": "^2.0.0" - } - }, - "supports-color": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", - "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=" - } - } - }, - "@textlint/markdown-to-ast": { - "version": "6.0.9", - "resolved": "https://registry.npmjs.org/@textlint/markdown-to-ast/-/markdown-to-ast-6.0.9.tgz", - "integrity": "sha512-hfAWBvTeUGh5t5kTn2U3uP3qOSM1BSrxzl1jF3nn0ywfZXpRBZr5yRjXnl4DzIYawCtZOshmRi/tI3/x4TE1jQ==", - "requires": { - "@textlint/ast-node-types": "^4.0.3", - "debug": "^2.1.3", - "remark-frontmatter": "^1.2.0", - "remark-parse": "^5.0.0", - "structured-source": "^3.0.2", - "traverse": "^0.6.6", - "unified": "^6.1.6" - } - }, - "@textlint/text-to-ast": { - "version": "3.0.9", - "resolved": "https://registry.npmjs.org/@textlint/text-to-ast/-/text-to-ast-3.0.9.tgz", - "integrity": "sha512-0Vycl2XtGv3pUtUNkBn9M/e3jBAtmlh7STUa3GuiyATXg49PsqqX7c8NxGPrNqMvDYCJ3ZubBx8GSEyra6ZWFw==", - "requires": { - "@textlint/ast-node-types": "^4.0.3" - } - }, - "@textlint/textlint-plugin-markdown": { - "version": "4.0.10", - "resolved": "https://registry.npmjs.org/@textlint/textlint-plugin-markdown/-/textlint-plugin-markdown-4.0.10.tgz", - "integrity": "sha512-HIV2UAhjnt9/tJQbuXkrD3CRiEFRtNpYoQEZCNCwd1nBMWUypAFthL9jT1KJ8tagOF7wEiGMB19QfDxiNQ+6mw==", - "requires": { - "@textlint/markdown-to-ast": "^6.0.8" - } - }, - "@textlint/textlint-plugin-text": { - "version": "3.0.10", - "resolved": "https://registry.npmjs.org/@textlint/textlint-plugin-text/-/textlint-plugin-text-3.0.10.tgz", - "integrity": "sha512-GSw9vsuKt7E85jDSFEXT0VYZo4C3e8XFFrSWYqXlwPKl/oQ/WHQfMg7GM288uGoEaMzbKEfBtpdwdZqTjGHOQA==", - "requires": { - "@textlint/text-to-ast": "^3.0.8" - } - }, - "@types/bluebird": { - "version": "3.5.24", - "resolved": "https://registry.npmjs.org/@types/bluebird/-/bluebird-3.5.24.tgz", - "integrity": "sha512-YeQoDpq4Lm8ppSBqAnAeF/xy1cYp/dMTif2JFcvmAbETMRlvKHT2iLcWu+WyYiJO3b3Ivokwo7EQca/xfLVJmg==" - }, - "adverb-where": { - "version": "0.0.9", - "resolved": "https://registry.npmjs.org/adverb-where/-/adverb-where-0.0.9.tgz", - "integrity": "sha1-CcXN3Y1QO5/l924LjcXHCo8ZPjQ=" - }, - "aggregate-error": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-1.0.0.tgz", - "integrity": "sha1-iINE2tAiCnLjr1CQYRf0h3GSX6w=", - "requires": { - "clean-stack": "^1.0.0", - "indent-string": "^3.0.0" - } - }, - "ajv": { - "version": "5.5.2", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-5.5.2.tgz", - "integrity": "sha1-c7Xuyj+rZT49P5Qis0GtQiBdyWU=", - "requires": { - "co": "^4.6.0", - "fast-deep-equal": "^1.0.0", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.3.0" - } - }, - "ajv-keywords": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-1.5.1.tgz", - "integrity": "sha1-MU3QpLM2j609/NxU7eYXG4htrzw=" - }, - "ansi-regex": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=" - }, - "ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "requires": { - "color-convert": "^1.9.0" - } - }, - "anymatch": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-1.3.2.tgz", - "integrity": "sha512-0XNayC8lTHQ2OI8aljNCN3sSx6hsr/1+rlcDAotXJR7C1oZZHCNsfpbKwMjRA3Uqb5tF1Rae2oloTr4xpq+WjA==", - "requires": { - "micromatch": "^2.1.5", - "normalize-path": "^2.0.0" - } - }, - "argparse": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", - "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", - "requires": { - "sprintf-js": "~1.0.2" - } - }, - "arr-diff": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-2.0.0.tgz", - "integrity": "sha1-jzuCf5Vai9ZpaX5KQlasPOrjVs8=", - "requires": { - "arr-flatten": "^1.0.1" - } - }, - "arr-flatten": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/arr-flatten/-/arr-flatten-1.1.0.tgz", - "integrity": "sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg==" - }, - "arr-union": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-3.1.0.tgz", - "integrity": "sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ=" - }, - "array-union": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/array-union/-/array-union-1.0.2.tgz", - "integrity": "sha1-mjRBDk9OPaI96jdb5b5w8kd47Dk=", - "requires": { - "array-uniq": "^1.0.1" - } - }, - "array-uniq": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/array-uniq/-/array-uniq-1.0.3.tgz", - "integrity": "sha1-r2rId6Jcx/dOBYiUdThY39sk/bY=" - }, - "array-unique": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.2.1.tgz", - "integrity": "sha1-odl8yvy8JiXMcPrc6zalDFiwGlM=" - }, - "arrify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz", - "integrity": "sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0=" - }, - "asn1": { - "version": "0.2.4", - "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz", - "integrity": "sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==", - "requires": { - "safer-buffer": "~2.1.0" - } - }, - "assert-plus": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", - "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" - }, - "assign-symbols": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz", - "integrity": "sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c=" - }, - "async-each": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/async-each/-/async-each-1.0.1.tgz", - "integrity": "sha1-GdOGodntxufByF04iu28xW0zYC0=" - }, - "asynckit": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" - }, - "atob": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz", - "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==" - }, - "aws-sign2": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", - "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=" - }, - "aws4": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.8.0.tgz", - "integrity": "sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ==" - }, - "bail": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/bail/-/bail-1.0.3.tgz", - "integrity": "sha512-1X8CnjFVQ+a+KW36uBNMTU5s8+v5FzeqrP7hTG5aTb4aPreSbZJlhwPon9VKMuEVgV++JM+SQrALY3kr7eswdg==" - }, - "balanced-match": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", - "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" - }, - "base": { - "version": "0.11.2", - "resolved": "https://registry.npmjs.org/base/-/base-0.11.2.tgz", - "integrity": "sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg==", - "requires": { - "cache-base": "^1.0.1", - "class-utils": "^0.3.5", - "component-emitter": "^1.2.1", - "define-property": "^1.0.0", - "isobject": "^3.0.1", - "mixin-deep": "^1.2.0", - "pascalcase": "^0.1.1" - }, - "dependencies": { - "define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", - "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", - "requires": { - "is-descriptor": "^1.0.0" - } - }, - "is-accessor-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", - "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-data-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", - "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-descriptor": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", - "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", - "requires": { - "is-accessor-descriptor": "^1.0.0", - "is-data-descriptor": "^1.0.0", - "kind-of": "^6.0.2" - } - }, - "isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=" - }, - "kind-of": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", - "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==" - } - } - }, - "bcrypt-pbkdf": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", - "integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=", - "optional": true, - "requires": { - "tweetnacl": "^0.14.3" - } - }, - "binary-extensions": { - "version": "1.12.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-1.12.0.tgz", - "integrity": "sha512-DYWGk01lDcxeS/K9IHPGWfT8PsJmbXRtRd2Sx72Tnb8pcYZQFF1oSDb8hJtS1vhp212q1Rzi5dUf9+nq0o9UIg==" - }, - "bluebird": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.5.2.tgz", - "integrity": "sha512-dhHTWMI7kMx5whMQntl7Vr9C6BvV10lFXDAasnqnrMYhXVCzzk6IO9Fo2L75jXHT07WrOngL1WDXOp+yYS91Yg==" - }, - "boundary": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/boundary/-/boundary-1.0.1.tgz", - "integrity": "sha1-TWfcJgLAzBbdm85+v4fpSCkPWBI=" - }, - "brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "requires": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "braces": { - "version": "1.8.5", - "resolved": "https://registry.npmjs.org/braces/-/braces-1.8.5.tgz", - "integrity": "sha1-uneWLhLf+WnWt2cR6RS3N4V79qc=", - "requires": { - "expand-range": "^1.8.1", - "preserve": "^0.2.0", - "repeat-element": "^1.1.2" - } - }, - "buffer-from": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", - "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==" - }, - "builtin-modules": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-1.1.1.tgz", - "integrity": "sha1-Jw8HbFpywC9bZaR9+Uxf46J4iS8=" - }, - "cache-base": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/cache-base/-/cache-base-1.0.1.tgz", - "integrity": "sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ==", - "requires": { - "collection-visit": "^1.0.0", - "component-emitter": "^1.2.1", - "get-value": "^2.0.6", - "has-value": "^1.0.0", - "isobject": "^3.0.1", - "set-value": "^2.0.0", - "to-object-path": "^0.3.0", - "union-value": "^1.0.0", - "unset-value": "^1.0.0" - }, - "dependencies": { - "isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=" - } - } - }, - "cacheable-request": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-2.1.4.tgz", - "integrity": "sha1-DYCIAbY0KtM8kd+dC0TcCbkeXD0=", - "requires": { - "clone-response": "1.0.2", - "get-stream": "3.0.0", - "http-cache-semantics": "3.8.1", - "keyv": "3.0.0", - "lowercase-keys": "1.0.0", - "normalize-url": "2.0.1", - "responselike": "1.0.2" - }, - "dependencies": { - "lowercase-keys": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-1.0.0.tgz", - "integrity": "sha1-TjNms55/VFfjXxMkvfb4jQv8cwY=" - } - } - }, - "camelcase": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-4.1.0.tgz", - "integrity": "sha1-1UVjW+HjPFQmScaRc+Xeas+uNN0=" - }, - "capture-stack-trace": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/capture-stack-trace/-/capture-stack-trace-1.0.1.tgz", - "integrity": "sha512-mYQLZnx5Qt1JgB1WEiMCf2647plpGeQ2NMR/5L0HNZzGQo4fuSPnK+wjfPnKZV0aiJDgzmWqqkV/g7JD+DW0qw==" - }, - "caseless": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", - "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=" - }, - "ccount": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/ccount/-/ccount-1.0.3.tgz", - "integrity": "sha512-Jt9tIBkRc9POUof7QA/VwWd+58fKkEEfI+/t1/eOlxKM7ZhrczNzMFefge7Ai+39y1pR/pP6cI19guHy3FSLmw==" - }, - "chalk": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.1.tgz", - "integrity": "sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==", - "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - } - }, - "character-entities": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/character-entities/-/character-entities-1.2.2.tgz", - "integrity": "sha512-sMoHX6/nBiy3KKfC78dnEalnpn0Az0oSNvqUWYTtYrhRI5iUIYsROU48G+E+kMFQzqXaJ8kHJZ85n7y6/PHgwQ==" - }, - "character-entities-html4": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/character-entities-html4/-/character-entities-html4-1.1.2.tgz", - "integrity": "sha512-sIrXwyna2+5b0eB9W149izTPJk/KkJTg6mEzDGibwBUkyH1SbDa+nf515Ppdi3MaH35lW0JFJDWeq9Luzes1Iw==" - }, - "character-entities-legacy": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-1.1.2.tgz", - "integrity": "sha512-9NB2VbXtXYWdXzqrvAHykE/f0QJxzaKIpZ5QzNZrrgQ7Iyxr2vnfS8fCBNVW9nUEZE0lo57nxKRqnzY/dKrwlA==" - }, - "character-reference-invalid": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/character-reference-invalid/-/character-reference-invalid-1.1.2.tgz", - "integrity": "sha512-7I/xceXfKyUJmSAn/jw8ve/9DyOP7XxufNYLI9Px7CmsKgEUaZLUTax6nZxGQtaoiZCjpu6cHPj20xC/vqRReQ==" - }, - "charenc": { - "version": "0.0.2", - "resolved": "https://registry.npmjs.org/charenc/-/charenc-0.0.2.tgz", - "integrity": "sha1-wKHS86cJLgN3S/qD8UwPxXkKhmc=" - }, - "chokidar": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-1.7.0.tgz", - "integrity": "sha1-eY5ol3gVHIB2tLNg5e3SjNortGg=", - "requires": { - "anymatch": "^1.3.0", - "async-each": "^1.0.0", - "fsevents": "^1.0.0", - "glob-parent": "^2.0.0", - "inherits": "^2.0.1", - "is-binary-path": "^1.0.0", - "is-glob": "^2.0.0", - "path-is-absolute": "^1.0.0", - "readdirp": "^2.0.0" - } - }, - "circular-json": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/circular-json/-/circular-json-0.3.3.tgz", - "integrity": "sha512-UZK3NBx2Mca+b5LsG7bY183pHWt5Y1xts4P3Pz7ENTwGVnJOUWbRb3ocjvX7hx9tq/yTAdclXm9sZ38gNuem4A==" - }, - "class-utils": { - "version": "0.3.6", - "resolved": "https://registry.npmjs.org/class-utils/-/class-utils-0.3.6.tgz", - "integrity": "sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg==", - "requires": { - "arr-union": "^3.1.0", - "define-property": "^0.2.5", - "isobject": "^3.0.0", - "static-extend": "^0.1.1" - }, - "dependencies": { - "define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "requires": { - "is-descriptor": "^0.1.0" - } - }, - "isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=" - } - } - }, - "clean-stack": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-1.3.0.tgz", - "integrity": "sha1-noIVAa6XmYbEax1m0tQy2y/UrjE=" - }, - "clone-response": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/clone-response/-/clone-response-1.0.2.tgz", - "integrity": "sha1-0dyXOSAxTfZ/vrlCI7TuNQI56Ws=", - "requires": { - "mimic-response": "^1.0.0" - } - }, - "co": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", - "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=" - }, - "code-point-at": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", - "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=" - }, - "collapse-white-space": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/collapse-white-space/-/collapse-white-space-1.0.4.tgz", - "integrity": "sha512-YfQ1tAUZm561vpYD+5eyWN8+UsceQbSrqqlc/6zDY2gtAE+uZLSdkkovhnGpmCThsvKBFakq4EdY/FF93E8XIw==" - }, - "collection-visit": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/collection-visit/-/collection-visit-1.0.0.tgz", - "integrity": "sha1-S8A3PBZLwykbTTaMgpzxqApZ3KA=", - "requires": { - "map-visit": "^1.0.0", - "object-visit": "^1.0.0" - } - }, - "color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "requires": { - "color-name": "1.1.3" - } - }, - "color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" - }, - "combined-stream": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.7.tgz", - "integrity": "sha512-brWl9y6vOB1xYPZcpZde3N9zDByXTosAeMDo4p1wzo6UMOX4vumB+TP1RZ76sfE6Md68Q0NJSrE/gbezd4Ul+w==", - "requires": { - "delayed-stream": "~1.0.0" - } - }, - "component-emitter": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.2.1.tgz", - "integrity": "sha1-E3kY1teCg/ffemt8WmPhQOaUJeY=" - }, - "concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" - }, - "concat-stream": { - "version": "1.6.2", - "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", - "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", - "requires": { - "buffer-from": "^1.0.0", - "inherits": "^2.0.3", - "readable-stream": "^2.2.2", - "typedarray": "^0.0.6" - } - }, - "copy-descriptor": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/copy-descriptor/-/copy-descriptor-0.1.1.tgz", - "integrity": "sha1-Z29us8OZl8LuGsOpJP1hJHSPV40=" - }, - "core-util-is": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", - "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" - }, - "create-error-class": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/create-error-class/-/create-error-class-3.0.2.tgz", - "integrity": "sha1-Br56vvlHo/FKMP1hBnHUAbyot7Y=", - "requires": { - "capture-stack-trace": "^1.0.0" - } - }, - "crypt": { - "version": "0.0.2", - "resolved": "https://registry.npmjs.org/crypt/-/crypt-0.0.2.tgz", - "integrity": "sha1-iNf/fsDfuG9xPch7u0LQRNPmxBs=" - }, - "dashdash": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", - "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", - "requires": { - "assert-plus": "^1.0.0" - } - }, - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "requires": { - "ms": "2.0.0" - } - }, - "decode-uri-component": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz", - "integrity": "sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU=" - }, - "decompress-response": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-3.3.0.tgz", - "integrity": "sha1-gKTdMjdIOEv6JICDYirt7Jgq3/M=", - "requires": { - "mimic-response": "^1.0.0" - } - }, - "deep-equal": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-1.0.1.tgz", - "integrity": "sha1-9dJgKStmDghO/0zbyfCK0yR0SLU=" - }, - "deep-extend": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", - "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==" - }, - "deep-is": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz", - "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=" - }, - "define-properties": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", - "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==", - "requires": { - "object-keys": "^1.0.12" - } - }, - "define-property": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-2.0.2.tgz", - "integrity": "sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ==", - "requires": { - "is-descriptor": "^1.0.2", - "isobject": "^3.0.1" - }, - "dependencies": { - "is-accessor-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", - "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-data-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", - "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-descriptor": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", - "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", - "requires": { - "is-accessor-descriptor": "^1.0.0", - "is-data-descriptor": "^1.0.0", - "kind-of": "^6.0.2" - } - }, - "isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=" - }, - "kind-of": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", - "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==" - } - } - }, - "del": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/del/-/del-2.2.2.tgz", - "integrity": "sha1-wSyYHQZ4RshLyvhiz/kw2Qf/0ag=", - "requires": { - "globby": "^5.0.0", - "is-path-cwd": "^1.0.0", - "is-path-in-cwd": "^1.0.0", - "object-assign": "^4.0.1", - "pify": "^2.0.0", - "pinkie-promise": "^2.0.0", - "rimraf": "^2.2.8" - }, - "dependencies": { - "pify": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", - "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=" - } - } - }, - "delayed-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=" - }, - "diff": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/diff/-/diff-2.2.3.tgz", - "integrity": "sha1-YOr9DSjukG5Oj/ClLBIpUhAzv5k=" - }, - "dns-packet": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/dns-packet/-/dns-packet-1.3.1.tgz", - "integrity": "sha512-0UxfQkMhYAUaZI+xrNZOz/as5KgDU0M/fQ9b6SpkyLbk3GEswDi6PADJVaYJradtRVsRIlF1zLyOodbcTCDzUg==", - "requires": { - "ip": "^1.1.0", - "safe-buffer": "^5.0.1" - } - }, - "dns-socket": { - "version": "1.6.3", - "resolved": "https://registry.npmjs.org/dns-socket/-/dns-socket-1.6.3.tgz", - "integrity": "sha512-/mUy3VGqIP69dAZjh2xxHXcpK9wk2Len1Dxz8mWAdrIgFC8tnR/aQAyU4a+UTXzOcTvEvGBdp1zFiwnpWKaXng==", - "requires": { - "dns-packet": "^1.1.0" - } - }, - "duplexer3": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/duplexer3/-/duplexer3-0.1.4.tgz", - "integrity": "sha1-7gHdHKwO08vH/b6jfcCo8c4ALOI=" - }, - "e-prime": { - "version": "0.10.2", - "resolved": "https://registry.npmjs.org/e-prime/-/e-prime-0.10.2.tgz", - "integrity": "sha1-6pN165hWNt6IATx6n7EprZ4V7/g=" - }, - "ecc-jsbn": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", - "integrity": "sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=", - "optional": true, - "requires": { - "jsbn": "~0.1.0", - "safer-buffer": "^2.1.0" - } - }, - "error-ex": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", - "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", - "requires": { - "is-arrayish": "^0.2.1" - } - }, - "es-abstract": { - "version": "1.12.0", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.12.0.tgz", - "integrity": "sha512-C8Fx/0jFmV5IPoMOFPA9P9G5NtqW+4cOPit3MIuvR2t7Ag2K15EJTpxnHAYTzL+aYQJIESYeXZmDBfOBE1HcpA==", - "requires": { - "es-to-primitive": "^1.1.1", - "function-bind": "^1.1.1", - "has": "^1.0.1", - "is-callable": "^1.1.3", - "is-regex": "^1.0.4" - } - }, - "es-to-primitive": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.1.1.tgz", - "integrity": "sha1-RTVSSKiJeQNLZ5Lhm7gfK3l13Q0=", - "requires": { - "is-callable": "^1.1.1", - "is-date-object": "^1.0.1", - "is-symbol": "^1.0.1" - } - }, - "escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=" - }, - "esprima": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", - "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==" - }, - "expand-brackets": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-0.1.5.tgz", - "integrity": "sha1-3wcoTjQqgHzXM6xa9yQR5YHRF3s=", - "requires": { - "is-posix-bracket": "^0.1.0" - } - }, - "expand-range": { - "version": "1.8.2", - "resolved": "https://registry.npmjs.org/expand-range/-/expand-range-1.8.2.tgz", - "integrity": "sha1-opnv/TNf4nIeuujiV+x5ZE/IUzc=", - "requires": { - "fill-range": "^2.1.0" - } - }, - "extend": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", - "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==" - }, - "extend-shallow": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", - "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=", - "requires": { - "assign-symbols": "^1.0.0", - "is-extendable": "^1.0.1" - }, - "dependencies": { - "is-extendable": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", - "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", - "requires": { - "is-plain-object": "^2.0.4" - } - } - } - }, - "extglob": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/extglob/-/extglob-0.3.2.tgz", - "integrity": "sha1-Lhj/PS9JqydlzskCPwEdqo2DSaE=", - "requires": { - "is-extglob": "^1.0.0" - } - }, - "extsprintf": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", - "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=" - }, - "fast-deep-equal": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-1.1.0.tgz", - "integrity": "sha1-wFNHeBfIa1HaqFPIHgWbcz0CNhQ=" - }, - "fast-json-stable-stringify": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz", - "integrity": "sha1-1RQsDK7msRifh9OnYREGT4bIu/I=" - }, - "fast-levenshtein": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", - "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=" - }, - "fault": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/fault/-/fault-1.0.2.tgz", - "integrity": "sha512-o2eo/X2syzzERAtN5LcGbiVQ0WwZSlN3qLtadwAz3X8Bu+XWD16dja/KMsjZLiQr+BLGPDnHGkc4yUJf1Xpkpw==", - "requires": { - "format": "^0.2.2" - } - }, - "file-entry-cache": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-2.0.0.tgz", - "integrity": "sha1-w5KZDD5oR4PYOLjISkXYoEhFg2E=", - "requires": { - "flat-cache": "^1.2.1", - "object-assign": "^4.0.1" - } - }, - "filename-regex": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/filename-regex/-/filename-regex-2.0.1.tgz", - "integrity": "sha1-wcS5vuPglyXdsQa3XB4wH+LxiyY=" - }, - "fill-range": { - "version": "2.2.4", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-2.2.4.tgz", - "integrity": "sha512-cnrcCbj01+j2gTG921VZPnHbjmdAf8oQV/iGeV2kZxGSyfYjjTyY79ErsK1WJWMpw6DaApEX72binqJE+/d+5Q==", - "requires": { - "is-number": "^2.1.0", - "isobject": "^2.0.0", - "randomatic": "^3.0.0", - "repeat-element": "^1.1.2", - "repeat-string": "^1.5.2" - } - }, - "find-up": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", - "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=", - "requires": { - "locate-path": "^2.0.0" - } - }, - "flat-cache": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-1.3.0.tgz", - "integrity": "sha1-0wMLMrOBVPTjt+nHCfSQ9++XxIE=", - "requires": { - "circular-json": "^0.3.1", - "del": "^2.0.2", - "graceful-fs": "^4.1.2", - "write": "^0.2.1" - } - }, - "fn-name": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/fn-name/-/fn-name-2.0.1.tgz", - "integrity": "sha1-UhTXU3pNBqSjAcDMJi/rhBiAAuc=" - }, - "for-in": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", - "integrity": "sha1-gQaNKVqBQuwKxybG4iAMMPttXoA=" - }, - "for-own": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/for-own/-/for-own-0.1.5.tgz", - "integrity": "sha1-UmXGgaTylNq78XyVCbZ2OqhFEM4=", - "requires": { - "for-in": "^1.0.1" - } - }, - "forever-agent": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", - "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=" - }, - "form-data": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.2.tgz", - "integrity": "sha1-SXBJi+YEwgwAXU9cI67NIda0kJk=", - "requires": { - "asynckit": "^0.4.0", - "combined-stream": "1.0.6", - "mime-types": "^2.1.12" - }, - "dependencies": { - "combined-stream": { - "version": "1.0.6", - "resolved": "http://registry.npmjs.org/combined-stream/-/combined-stream-1.0.6.tgz", - "integrity": "sha1-cj599ugBrFYTETp+RFqbactjKBg=", - "requires": { - "delayed-stream": "~1.0.0" - } - } - } - }, - "format": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/format/-/format-0.2.2.tgz", - "integrity": "sha1-1hcBB+nv3E7TDJ3DkBbflCtctYs=" - }, - "fragment-cache": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/fragment-cache/-/fragment-cache-0.2.1.tgz", - "integrity": "sha1-QpD60n8T6Jvn8zeZxrxaCr//DRk=", - "requires": { - "map-cache": "^0.2.2" - } - }, - "from2": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/from2/-/from2-2.3.0.tgz", - "integrity": "sha1-i/tVAr3kpNNs/e6gB/zKIdfjgq8=", - "requires": { - "inherits": "^2.0.1", - "readable-stream": "^2.0.0" - } - }, - "fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" - }, - "fsevents": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.4.tgz", - "integrity": "sha512-z8H8/diyk76B7q5wg+Ud0+CqzcAF3mBBI/bA5ne5zrRUUIvNkJY//D3BqyH571KuAC4Nr7Rw7CjWX4r0y9DvNg==", - "optional": true, - "requires": { - "nan": "^2.9.2", - "node-pre-gyp": "^0.10.0" - }, - "dependencies": { - "abbrev": { - "version": "1.1.1", - "bundled": true, - "optional": true - }, - "ansi-regex": { - "version": "2.1.1", - "bundled": true - }, - "aproba": { - "version": "1.2.0", - "bundled": true, - "optional": true - }, - "are-we-there-yet": { - "version": "1.1.4", - "bundled": true, - "optional": true, - "requires": { - "delegates": "^1.0.0", - "readable-stream": "^2.0.6" - } - }, - "balanced-match": { - "version": "1.0.0", - "bundled": true - }, - "brace-expansion": { - "version": "1.1.11", - "bundled": true, - "requires": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "chownr": { - "version": "1.0.1", - "bundled": true, - "optional": true - }, - "code-point-at": { - "version": "1.1.0", - "bundled": true - }, - "concat-map": { - "version": "0.0.1", - "bundled": true - }, - "console-control-strings": { - "version": "1.1.0", - "bundled": true - }, - "core-util-is": { - "version": "1.0.2", - "bundled": true, - "optional": true - }, - "debug": { - "version": "2.6.9", - "bundled": true, - "optional": true, - "requires": { - "ms": "2.0.0" - } - }, - "deep-extend": { - "version": "0.5.1", - "bundled": true, - "optional": true - }, - "delegates": { - "version": "1.0.0", - "bundled": true, - "optional": true - }, - "detect-libc": { - "version": "1.0.3", - "bundled": true, - "optional": true - }, - "fs-minipass": { - "version": "1.2.5", - "bundled": true, - "optional": true, - "requires": { - "minipass": "^2.2.1" - } - }, - "fs.realpath": { - "version": "1.0.0", - "bundled": true, - "optional": true - }, - "gauge": { - "version": "2.7.4", - "bundled": true, - "optional": true, - "requires": { - "aproba": "^1.0.3", - "console-control-strings": "^1.0.0", - "has-unicode": "^2.0.0", - "object-assign": "^4.1.0", - "signal-exit": "^3.0.0", - "string-width": "^1.0.1", - "strip-ansi": "^3.0.1", - "wide-align": "^1.1.0" - } - }, - "glob": { - "version": "7.1.2", - "bundled": true, - "optional": true, - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - }, - "has-unicode": { - "version": "2.0.1", - "bundled": true, - "optional": true - }, - "iconv-lite": { - "version": "0.4.21", - "bundled": true, - "optional": true, - "requires": { - "safer-buffer": "^2.1.0" - } - }, - "ignore-walk": { - "version": "3.0.1", - "bundled": true, - "optional": true, - "requires": { - "minimatch": "^3.0.4" - } - }, - "inflight": { - "version": "1.0.6", - "bundled": true, - "optional": true, - "requires": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "inherits": { - "version": "2.0.3", - "bundled": true - }, - "ini": { - "version": "1.3.5", - "bundled": true, - "optional": true - }, - "is-fullwidth-code-point": { - "version": "1.0.0", - "bundled": true, - "requires": { - "number-is-nan": "^1.0.0" - } - }, - "isarray": { - "version": "1.0.0", - "bundled": true, - "optional": true - }, - "minimatch": { - "version": "3.0.4", - "bundled": true, - "requires": { - "brace-expansion": "^1.1.7" - } - }, - "minimist": { - "version": "0.0.8", - "bundled": true - }, - "minipass": { - "version": "2.2.4", - "bundled": true, - "requires": { - "safe-buffer": "^5.1.1", - "yallist": "^3.0.0" - } - }, - "minizlib": { - "version": "1.1.0", - "bundled": true, - "optional": true, - "requires": { - "minipass": "^2.2.1" - } - }, - "mkdirp": { - "version": "0.5.1", - "bundled": true, - "requires": { - "minimist": "0.0.8" - } - }, - "ms": { - "version": "2.0.0", - "bundled": true, - "optional": true - }, - "needle": { - "version": "2.2.0", - "bundled": true, - "optional": true, - "requires": { - "debug": "^2.1.2", - "iconv-lite": "^0.4.4", - "sax": "^1.2.4" - } - }, - "node-pre-gyp": { - "version": "0.10.0", - "bundled": true, - "optional": true, - "requires": { - "detect-libc": "^1.0.2", - "mkdirp": "^0.5.1", - "needle": "^2.2.0", - "nopt": "^4.0.1", - "npm-packlist": "^1.1.6", - "npmlog": "^4.0.2", - "rc": "^1.1.7", - "rimraf": "^2.6.1", - "semver": "^5.3.0", - "tar": "^4" - } - }, - "nopt": { - "version": "4.0.1", - "bundled": true, - "optional": true, - "requires": { - "abbrev": "1", - "osenv": "^0.1.4" - } - }, - "npm-bundled": { - "version": "1.0.3", - "bundled": true, - "optional": true - }, - "npm-packlist": { - "version": "1.1.10", - "bundled": true, - "optional": true, - "requires": { - "ignore-walk": "^3.0.1", - "npm-bundled": "^1.0.1" - } - }, - "npmlog": { - "version": "4.1.2", - "bundled": true, - "optional": true, - "requires": { - "are-we-there-yet": "~1.1.2", - "console-control-strings": "~1.1.0", - "gauge": "~2.7.3", - "set-blocking": "~2.0.0" - } - }, - "number-is-nan": { - "version": "1.0.1", - "bundled": true - }, - "object-assign": { - "version": "4.1.1", - "bundled": true, - "optional": true - }, - "once": { - "version": "1.4.0", - "bundled": true, - "requires": { - "wrappy": "1" - } - }, - "os-homedir": { - "version": "1.0.2", - "bundled": true, - "optional": true - }, - "os-tmpdir": { - "version": "1.0.2", - "bundled": true, - "optional": true - }, - "osenv": { - "version": "0.1.5", - "bundled": true, - "optional": true, - "requires": { - "os-homedir": "^1.0.0", - "os-tmpdir": "^1.0.0" - } - }, - "path-is-absolute": { - "version": "1.0.1", - "bundled": true, - "optional": true - }, - "process-nextick-args": { - "version": "2.0.0", - "bundled": true, - "optional": true - }, - "rc": { - "version": "1.2.7", - "bundled": true, - "optional": true, - "requires": { - "deep-extend": "^0.5.1", - "ini": "~1.3.0", - "minimist": "^1.2.0", - "strip-json-comments": "~2.0.1" - }, - "dependencies": { - "minimist": { - "version": "1.2.0", - "bundled": true, - "optional": true - } - } - }, - "readable-stream": { - "version": "2.3.6", - "bundled": true, - "optional": true, - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "rimraf": { - "version": "2.6.2", - "bundled": true, - "optional": true, - "requires": { - "glob": "^7.0.5" - } - }, - "safe-buffer": { - "version": "5.1.1", - "bundled": true - }, - "safer-buffer": { - "version": "2.1.2", - "bundled": true, - "optional": true - }, - "sax": { - "version": "1.2.4", - "bundled": true, - "optional": true - }, - "semver": { - "version": "5.5.0", - "bundled": true, - "optional": true - }, - "set-blocking": { - "version": "2.0.0", - "bundled": true, - "optional": true - }, - "signal-exit": { - "version": "3.0.2", - "bundled": true, - "optional": true - }, - "string-width": { - "version": "1.0.2", - "bundled": true, - "requires": { - "code-point-at": "^1.0.0", - "is-fullwidth-code-point": "^1.0.0", - "strip-ansi": "^3.0.0" - } - }, - "string_decoder": { - "version": "1.1.1", - "bundled": true, - "optional": true, - "requires": { - "safe-buffer": "~5.1.0" - } - }, - "strip-ansi": { - "version": "3.0.1", - "bundled": true, - "requires": { - "ansi-regex": "^2.0.0" - } - }, - "strip-json-comments": { - "version": "2.0.1", - "bundled": true, - "optional": true - }, - "tar": { - "version": "4.4.1", - "bundled": true, - "optional": true, - "requires": { - "chownr": "^1.0.1", - "fs-minipass": "^1.2.5", - "minipass": "^2.2.4", - "minizlib": "^1.1.0", - "mkdirp": "^0.5.0", - "safe-buffer": "^5.1.1", - "yallist": "^3.0.2" - } - }, - "util-deprecate": { - "version": "1.0.2", - "bundled": true, - "optional": true - }, - "wide-align": { - "version": "1.1.2", - "bundled": true, - "optional": true, - "requires": { - "string-width": "^1.0.2" - } - }, - "wrappy": { - "version": "1.0.2", - "bundled": true - }, - "yallist": { - "version": "3.0.2", - "bundled": true - } - } - }, - "function-bind": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" - }, - "get-stdin": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-5.0.1.tgz", - "integrity": "sha1-Ei4WFZHiH/TFJTAwVpPyDmOTo5g=" - }, - "get-stream": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz", - "integrity": "sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ=" - }, - "get-value": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/get-value/-/get-value-2.0.6.tgz", - "integrity": "sha1-3BXKHGcjh8p2vTesCjlbogQqLCg=" - }, - "getpass": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", - "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", - "requires": { - "assert-plus": "^1.0.0" - } - }, - "glob": { - "version": "7.1.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.3.tgz", - "integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==", - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - }, - "glob-base": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/glob-base/-/glob-base-0.3.0.tgz", - "integrity": "sha1-27Fk9iIbHAscz4Kuoyi0l98Oo8Q=", - "requires": { - "glob-parent": "^2.0.0", - "is-glob": "^2.0.0" - } - }, - "glob-parent": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-2.0.0.tgz", - "integrity": "sha1-gTg9ctsFT8zPUzbaqQLxgvbtuyg=", - "requires": { - "is-glob": "^2.0.0" - } - }, - "globby": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/globby/-/globby-5.0.0.tgz", - "integrity": "sha1-69hGZ8oNuzMLmbz8aOrCvFQ3Dg0=", - "requires": { - "array-union": "^1.0.1", - "arrify": "^1.0.0", - "glob": "^7.0.3", - "object-assign": "^4.0.1", - "pify": "^2.0.0", - "pinkie-promise": "^2.0.0" - }, - "dependencies": { - "pify": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", - "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=" - } - } - }, - "got": { - "version": "6.7.1", - "resolved": "http://registry.npmjs.org/got/-/got-6.7.1.tgz", - "integrity": "sha1-JAzQV4WpoY5WHcG0S0HHY+8ejbA=", - "requires": { - "create-error-class": "^3.0.0", - "duplexer3": "^0.1.4", - "get-stream": "^3.0.0", - "is-redirect": "^1.0.0", - "is-retry-allowed": "^1.0.0", - "is-stream": "^1.0.0", - "lowercase-keys": "^1.0.0", - "safe-buffer": "^5.0.1", - "timed-out": "^4.0.0", - "unzip-response": "^2.0.1", - "url-parse-lax": "^1.0.0" - } - }, - "graceful-fs": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.11.tgz", - "integrity": "sha1-Dovf5NHduIVNZOBOp8AOKgJuVlg=" - }, - "har-schema": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", - "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=" - }, - "har-validator": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.0.tgz", - "integrity": "sha512-+qnmNjI4OfH2ipQ9VQOw23bBd/ibtfbVdK2fYbY4acTDqKTW/YDp9McimZdDbG8iV9fZizUqQMD5xvriB146TA==", - "requires": { - "ajv": "^5.3.0", - "har-schema": "^2.0.0" - } - }, - "has": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", - "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", - "requires": { - "function-bind": "^1.1.1" - } - }, - "has-ansi": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", - "integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=", - "requires": { - "ansi-regex": "^2.0.0" - } - }, - "has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=" - }, - "has-symbol-support-x": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/has-symbol-support-x/-/has-symbol-support-x-1.4.2.tgz", - "integrity": "sha512-3ToOva++HaW+eCpgqZrCfN51IPB+7bJNVT6CUATzueB5Heb8o6Nam0V3HG5dlDvZU1Gn5QLcbahiKw/XVk5JJw==" - }, - "has-symbols": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.0.tgz", - "integrity": "sha1-uhqPGvKg/DllD1yFA2dwQSIGO0Q=" - }, - "has-to-string-tag-x": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/has-to-string-tag-x/-/has-to-string-tag-x-1.4.1.tgz", - "integrity": "sha512-vdbKfmw+3LoOYVr+mtxHaX5a96+0f3DljYd8JOqvOLsf5mw2Otda2qCDT9qRqLAhrjyQ0h7ual5nOiASpsGNFw==", - "requires": { - "has-symbol-support-x": "^1.4.1" - } - }, - "has-value": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-value/-/has-value-1.0.0.tgz", - "integrity": "sha1-GLKB2lhbHFxR3vJMkw7SmgvmsXc=", - "requires": { - "get-value": "^2.0.6", - "has-values": "^1.0.0", - "isobject": "^3.0.0" - }, - "dependencies": { - "isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=" - } - } - }, - "has-values": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-values/-/has-values-1.0.0.tgz", - "integrity": "sha1-lbC2P+whRmGab+V/51Yo1aOe/k8=", - "requires": { - "is-number": "^3.0.0", - "kind-of": "^4.0.0" - }, - "dependencies": { - "is-number": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", - "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "kind-of": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-4.0.0.tgz", - "integrity": "sha1-IIE989cSkosgc3hpGkUGb65y3Vc=", - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "hosted-git-info": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.7.1.tgz", - "integrity": "sha512-7T/BxH19zbcCTa8XkMlbK5lTo1WtgkFi3GvdWEyNuc4Vex7/9Dqbnpsf4JMydcfj9HCg4zUWFTL3Za6lapg5/w==" - }, - "http-cache-semantics": { - "version": "3.8.1", - "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-3.8.1.tgz", - "integrity": "sha512-5ai2iksyV8ZXmnZhHH4rWPoxxistEexSi5936zIQ1bnNTW5VnA85B6P/VpXiRM017IgRvb2kKo1a//y+0wSp3w==" - }, - "http-signature": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", - "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=", - "requires": { - "assert-plus": "^1.0.0", - "jsprim": "^1.2.2", - "sshpk": "^1.7.0" - } - }, - "ignore": { - "version": "3.3.10", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-3.3.10.tgz", - "integrity": "sha512-Pgs951kaMm5GXP7MOvxERINe3gsaVjUWFm+UZPSq9xYriQAksyhg0csnS0KXSNRD5NmNdapXEpjxG49+AKh/ug==" - }, - "indent-string": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-3.2.0.tgz", - "integrity": "sha1-Sl/W0nzDMvN+VBmlBNu4NxBckok=" - }, - "inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", - "requires": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "inherits": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" - }, - "ini": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz", - "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==" - }, - "interop-require": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/interop-require/-/interop-require-1.0.0.tgz", - "integrity": "sha1-5TEDZ5lEyI1+YQW2Kp9EdceDlx4=" - }, - "into-stream": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/into-stream/-/into-stream-3.1.0.tgz", - "integrity": "sha1-lvsKk2wSur1v8XUqF9BWFqvQlMY=", - "requires": { - "from2": "^2.1.1", - "p-is-promise": "^1.1.0" - } - }, - "ip": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/ip/-/ip-1.1.5.tgz", - "integrity": "sha1-vd7XARQpCCjAoDnnLvJfWq7ENUo=" - }, - "ip-regex": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/ip-regex/-/ip-regex-2.1.0.tgz", - "integrity": "sha1-+ni/XS5pE8kRzp+BnuUUa7bYROk=" - }, - "is-absolute-url": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-absolute-url/-/is-absolute-url-2.1.0.tgz", - "integrity": "sha1-UFMN+4T8yap9vnhS6Do3uTufKqY=" - }, - "is-accessor-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", - "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", - "requires": { - "kind-of": "^3.0.2" - } - }, - "is-alphabetical": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-1.0.2.tgz", - "integrity": "sha512-V0xN4BYezDHcBSKb1QHUFMlR4as/XEuCZBzMJUU4n7+Cbt33SmUnSol+pnXFvLxSHNq2CemUXNdaXV6Flg7+xg==" - }, - "is-alphanumeric": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-alphanumeric/-/is-alphanumeric-1.0.0.tgz", - "integrity": "sha1-Spzvcdr0wAHB2B1j0UDPU/1oifQ=" - }, - "is-alphanumerical": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-alphanumerical/-/is-alphanumerical-1.0.2.tgz", - "integrity": "sha512-pyfU/0kHdISIgslFfZN9nfY1Gk3MquQgUm1mJTjdkEPpkAKNWuBTSqFwewOpR7N351VkErCiyV71zX7mlQQqsg==", - "requires": { - "is-alphabetical": "^1.0.0", - "is-decimal": "^1.0.0" - } - }, - "is-arrayish": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", - "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=" - }, - "is-binary-path": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-1.0.1.tgz", - "integrity": "sha1-dfFmQrSA8YenEcgUFh/TpKdlWJg=", - "requires": { - "binary-extensions": "^1.0.0" - } - }, - "is-buffer": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", - "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==" - }, - "is-builtin-module": { - "version": "1.0.0", - "resolved": "http://registry.npmjs.org/is-builtin-module/-/is-builtin-module-1.0.0.tgz", - "integrity": "sha1-VAVy0096wxGfj3bDDLwbHgN6/74=", - "requires": { - "builtin-modules": "^1.0.0" - } - }, - "is-callable": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.1.4.tgz", - "integrity": "sha512-r5p9sxJjYnArLjObpjA4xu5EKI3CuKHkJXMhT7kwbpUyIFD1n5PMAsoPvWnvtZiNz7LjkYDRZhd7FlI0eMijEA==" - }, - "is-data-descriptor": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", - "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", - "requires": { - "kind-of": "^3.0.2" - } - }, - "is-date-object": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.1.tgz", - "integrity": "sha1-mqIOtq7rv/d/vTPnTKAbM1gdOhY=" - }, - "is-decimal": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-decimal/-/is-decimal-1.0.2.tgz", - "integrity": "sha512-TRzl7mOCchnhchN+f3ICUCzYvL9ul7R+TYOsZ8xia++knyZAJfv/uA1FvQXsAnYIl1T3B2X5E/J7Wb1QXiIBXg==" - }, - "is-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", - "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", - "requires": { - "is-accessor-descriptor": "^0.1.6", - "is-data-descriptor": "^0.1.4", - "kind-of": "^5.0.0" - }, - "dependencies": { - "kind-of": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", - "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==" - } - } - }, - "is-dotfile": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/is-dotfile/-/is-dotfile-1.0.3.tgz", - "integrity": "sha1-pqLzL/0t+wT1yiXs0Pa4PPeYoeE=" - }, - "is-empty": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/is-empty/-/is-empty-1.2.0.tgz", - "integrity": "sha1-3pu1snhzigWgsJpX4ftNSjQan2s=" - }, - "is-equal-shallow": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/is-equal-shallow/-/is-equal-shallow-0.1.3.tgz", - "integrity": "sha1-IjgJj8Ih3gvPpdnqxMRdY4qhxTQ=", - "requires": { - "is-primitive": "^2.0.0" - } - }, - "is-extendable": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", - "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=" - }, - "is-extglob": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-1.0.0.tgz", - "integrity": "sha1-rEaBd8SUNAWgkvyPKXYMb/xiBsA=" - }, - "is-file": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-file/-/is-file-1.0.0.tgz", - "integrity": "sha1-KKRM+9nT2xkwRfIrZfzo7fliBZY=" - }, - "is-fullwidth-code-point": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", - "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", - "requires": { - "number-is-nan": "^1.0.0" - } - }, - "is-glob": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-2.0.1.tgz", - "integrity": "sha1-0Jb5JqPe1WAPP9/ZEZjLCIjC2GM=", - "requires": { - "is-extglob": "^1.0.0" - } - }, - "is-hexadecimal": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-hexadecimal/-/is-hexadecimal-1.0.2.tgz", - "integrity": "sha512-but/G3sapV3MNyqiDBLrOi4x8uCIw0RY3o/Vb5GT0sMFHrVV7731wFSVy41T5FO1og7G0gXLJh0MkgPRouko/A==" - }, - "is-hidden": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/is-hidden/-/is-hidden-1.1.1.tgz", - "integrity": "sha512-175UKecS8+U4hh2PSY0j4xnm2GKYzvSKnbh+naC93JjuBA7LgIo6YxlbcsSo6seFBdQO3RuIcH980yvqqD/2cA==" - }, - "is-ip": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-ip/-/is-ip-2.0.0.tgz", - "integrity": "sha1-aO6gfooKCpTC0IDdZ0xzGrKkYas=", - "requires": { - "ip-regex": "^2.0.0" - } - }, - "is-number": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-2.1.0.tgz", - "integrity": "sha1-Afy7s5NGOlSPL0ZszhbezknbkI8=", - "requires": { - "kind-of": "^3.0.2" - } - }, - "is-object": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-object/-/is-object-1.0.1.tgz", - "integrity": "sha1-iVJojF7C/9awPsyF52ngKQMINHA=" - }, - "is-online": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-online/-/is-online-7.0.0.tgz", - "integrity": "sha1-fiQIwK4efje6jVC9sjcmDTK/2W4=", - "requires": { - "got": "^6.7.1", - "p-any": "^1.0.0", - "p-timeout": "^1.0.0", - "public-ip": "^2.3.0" - } - }, - "is-path-cwd": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-path-cwd/-/is-path-cwd-1.0.0.tgz", - "integrity": "sha1-0iXsIxMuie3Tj9p2dHLmLmXxEG0=" - }, - "is-path-in-cwd": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-path-in-cwd/-/is-path-in-cwd-1.0.1.tgz", - "integrity": "sha512-FjV1RTW48E7CWM7eE/J2NJvAEEVektecDBVBE5Hh3nM1Jd0kvhHtX68Pr3xsDf857xt3Y4AkwVULK1Vku62aaQ==", - "requires": { - "is-path-inside": "^1.0.0" - } - }, - "is-path-inside": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-1.0.1.tgz", - "integrity": "sha1-jvW33lBDej/cprToZe96pVy0gDY=", - "requires": { - "path-is-inside": "^1.0.1" - } - }, - "is-plain-obj": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz", - "integrity": "sha1-caUMhCnfync8kqOQpKA7OfzVHT4=" - }, - "is-plain-object": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", - "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", - "requires": { - "isobject": "^3.0.1" - }, - "dependencies": { - "isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=" - } - } - }, - "is-posix-bracket": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/is-posix-bracket/-/is-posix-bracket-0.1.1.tgz", - "integrity": "sha1-MzTceXdDaOkvAW5vvAqI9c1ua8Q=" - }, - "is-primitive": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-primitive/-/is-primitive-2.0.0.tgz", - "integrity": "sha1-IHurkWOEmcB7Kt8kCkGochADRXU=" - }, - "is-redirect": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-redirect/-/is-redirect-1.0.0.tgz", - "integrity": "sha1-HQPd7VO9jbDzDCbk+V02/HyH3CQ=" - }, - "is-regex": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.0.4.tgz", - "integrity": "sha1-VRdIm1RwkbCTDglWVM7SXul+lJE=", - "requires": { - "has": "^1.0.1" - } - }, - "is-relative-url": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-relative-url/-/is-relative-url-2.0.0.tgz", - "integrity": "sha1-cpAtf+BLPUeS59sV+duEtyBMnO8=", - "requires": { - "is-absolute-url": "^2.0.0" - } - }, - "is-retry-allowed": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-retry-allowed/-/is-retry-allowed-1.1.0.tgz", - "integrity": "sha1-EaBgVotnM5REAz0BJaYaINVk+zQ=" - }, - "is-stream": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", - "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=" - }, - "is-symbol": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.1.tgz", - "integrity": "sha1-PMWfAAJRlLarLjjbrmaJJWtmBXI=" - }, - "is-typedarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", - "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=" - }, - "is-utf8": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/is-utf8/-/is-utf8-0.2.1.tgz", - "integrity": "sha1-Sw2hRCEE0bM2NA6AeX6GXPOffXI=" - }, - "is-whitespace-character": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-whitespace-character/-/is-whitespace-character-1.0.2.tgz", - "integrity": "sha512-SzM+T5GKUCtLhlHFKt2SDAX2RFzfS6joT91F2/WSi9LxgFdsnhfPK/UIA+JhRR2xuyLdrCys2PiFDrtn1fU5hQ==" - }, - "is-windows": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", - "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==" - }, - "is-word-character": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-word-character/-/is-word-character-1.0.2.tgz", - "integrity": "sha512-T3FlsX8rCHAH8e7RE7PfOPZVFQlcV3XRF9eOOBQ1uf70OxO7CjjSOjeImMPCADBdYWcStAbVbYvJ1m2D3tb+EA==" - }, - "isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" - }, - "isemail": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/isemail/-/isemail-3.1.3.tgz", - "integrity": "sha512-5xbsG5wYADIcB+mfLsd+nst1V/D+I7EU7LEZPo2GOIMu4JzfcRs5yQoypP4avA7QtUqgxYLKBYNv4IdzBmbhdw==", - "requires": { - "punycode": "2.x.x" - } - }, - "isobject": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz", - "integrity": "sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=", - "requires": { - "isarray": "1.0.0" - } - }, - "isstream": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", - "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=" - }, - "isurl": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isurl/-/isurl-1.0.0.tgz", - "integrity": "sha512-1P/yWsxPlDtn7QeRD+ULKQPaIaN6yF368GZ2vDfv0AL0NwpStafjWCDDdn0k8wgFMWpVAqG7oJhxHnlud42i9w==", - "requires": { - "has-to-string-tag-x": "^1.2.0", - "is-object": "^1.0.1" - } - }, - "js-yaml": { - "version": "3.12.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.12.0.tgz", - "integrity": "sha512-PIt2cnwmPfL4hKNwqeiuz4bKfnzHTBv6HyVgjahA6mPLwPDzjDWrplJBMjHUFxku/N3FlmrbyPclad+I+4mJ3A==", - "requires": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" - } - }, - "jsbn": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", - "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=", - "optional": true - }, - "json-buffer": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.0.tgz", - "integrity": "sha1-Wx85evx11ne96Lz8Dkfh+aPZqJg=" - }, - "json-parse-better-errors": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", - "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==" - }, - "json-schema": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", - "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=" - }, - "json-schema-traverse": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.3.1.tgz", - "integrity": "sha1-NJptRMU6Ud6JtAgFxdXlm0F9M0A=" - }, - "json-stable-stringify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json-stable-stringify/-/json-stable-stringify-1.0.1.tgz", - "integrity": "sha1-mnWdOcXy/1A/1TAGRu1EX4jE+a8=", - "requires": { - "jsonify": "~0.0.0" - } - }, - "json-stringify-safe": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", - "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=" - }, - "json5": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/json5/-/json5-0.5.1.tgz", - "integrity": "sha1-Hq3nrMASA0rYTiOWdn6tn6VJWCE=" - }, - "jsonify": { - "version": "0.0.0", - "resolved": "https://registry.npmjs.org/jsonify/-/jsonify-0.0.0.tgz", - "integrity": "sha1-LHS27kHZPKUbe1qu6PUDYx0lKnM=" - }, - "jsprim": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", - "integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=", - "requires": { - "assert-plus": "1.0.0", - "extsprintf": "1.3.0", - "json-schema": "0.2.3", - "verror": "1.10.0" - } - }, - "keyv": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/keyv/-/keyv-3.0.0.tgz", - "integrity": "sha512-eguHnq22OE3uVoSYG0LVWNP+4ppamWr9+zWBe1bsNcovIMy6huUJFPgy4mGwCd/rnl3vOLGW1MTlu4c57CT1xA==", - "requires": { - "json-buffer": "3.0.0" - } - }, - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "requires": { - "is-buffer": "^1.1.5" - } - }, - "levn": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", - "integrity": "sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4=", - "requires": { - "prelude-ls": "~1.1.2", - "type-check": "~0.3.2" - } - }, - "link-check": { - "version": "4.4.4", - "resolved": "https://registry.npmjs.org/link-check/-/link-check-4.4.4.tgz", - "integrity": "sha512-yvowNBZEMOFH9nGLiJ5/YV68PBMVTo4opC2SzcACO8g4gSPTB9Rwa5GIziOX9Z5Er3Yf01DHoOyVV2LeApIw8w==", - "requires": { - "is-relative-url": "^2.0.0", - "isemail": "^3.1.2", - "ms": "^2.1.1", - "request": "^2.87.0" - }, - "dependencies": { - "ms": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", - "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==" - } - } - }, - "load-json-file": { - "version": "1.1.0", - "resolved": "http://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz", - "integrity": "sha1-lWkFcI1YtLq0wiYbBPWfMcmTdMA=", - "requires": { - "graceful-fs": "^4.1.2", - "parse-json": "^2.2.0", - "pify": "^2.0.0", - "pinkie-promise": "^2.0.0", - "strip-bom": "^2.0.0" - }, - "dependencies": { - "parse-json": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz", - "integrity": "sha1-9ID0BDTvgHQfhGkJn43qGPVaTck=", - "requires": { - "error-ex": "^1.2.0" - } - }, - "pify": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", - "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=" - } - } - }, - "load-plugin": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/load-plugin/-/load-plugin-2.2.2.tgz", - "integrity": "sha512-FYzamtURIJefQykZGtiClYuZkJBUKzmx8Tc74y8JGAulDzbzVm/C+w/MbAljHRr+REL0cRzy3WgnHE+T8gce5g==", - "requires": { - "npm-prefix": "^1.2.0", - "resolve-from": "^4.0.0" - } - }, - "locate-path": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz", - "integrity": "sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=", - "requires": { - "p-locate": "^2.0.0", - "path-exists": "^3.0.0" - } - }, - "lodash": { - "version": "4.17.11", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz", - "integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==" - }, - "log-symbols": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-1.0.2.tgz", - "integrity": "sha1-N2/3tY6jCGoPCfrMdGF+ylAeGhg=", - "requires": { - "chalk": "^1.0.0" - }, - "dependencies": { - "ansi-styles": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", - "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=" - }, - "chalk": { - "version": "1.1.3", - "resolved": "http://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", - "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", - "requires": { - "ansi-styles": "^2.2.1", - "escape-string-regexp": "^1.0.2", - "has-ansi": "^2.0.0", - "strip-ansi": "^3.0.0", - "supports-color": "^2.0.0" - } - }, - "supports-color": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", - "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=" - } - } - }, - "longest-streak": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/longest-streak/-/longest-streak-2.0.2.tgz", - "integrity": "sha512-TmYTeEYxiAmSVdpbnQDXGtvYOIRsCMg89CVZzwzc2o7GFL1CjoiRPjH5ec0NFAVlAx3fVof9dX/t6KKRAo2OWA==" - }, - "lowercase-keys": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-1.0.1.tgz", - "integrity": "sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA==" - }, - "map-cache": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/map-cache/-/map-cache-0.2.2.tgz", - "integrity": "sha1-wyq9C9ZSXZsFFkW7TyasXcmKDb8=" - }, - "map-like": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/map-like/-/map-like-2.0.0.tgz", - "integrity": "sha1-lEltSa0zPA3DI0snrbvR6FNZU7Q=" - }, - "map-visit": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/map-visit/-/map-visit-1.0.0.tgz", - "integrity": "sha1-7Nyo8TFE5mDxtb1B8S80edmN+48=", - "requires": { - "object-visit": "^1.0.0" - } - }, - "markdown-escapes": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/markdown-escapes/-/markdown-escapes-1.0.2.tgz", - "integrity": "sha512-lbRZ2mE3Q9RtLjxZBZ9+IMl68DKIXaVAhwvwn9pmjnPLS0h/6kyBMgNhqi1xFJ/2yv6cSyv0jbiZavZv93JkkA==" - }, - "markdown-extensions": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/markdown-extensions/-/markdown-extensions-1.1.1.tgz", - "integrity": "sha512-WWC0ZuMzCyDHYCasEGs4IPvLyTGftYwh6wIEOULOF0HXcqZlhwRzrK0w2VUlxWA98xnvb/jszw4ZSkJ6ADpM6Q==" - }, - "markdown-table": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/markdown-table/-/markdown-table-1.1.2.tgz", - "integrity": "sha512-NcWuJFHDA8V3wkDgR/j4+gZx+YQwstPgfQDV8ndUeWWzta3dnDTBxpVzqS9lkmJAuV5YX35lmyojl6HO5JXAgw==" - }, - "math-random": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/math-random/-/math-random-1.0.1.tgz", - "integrity": "sha1-izqsWIuKZuSXXjzepn97sylgH6w=" - }, - "md5": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/md5/-/md5-2.2.1.tgz", - "integrity": "sha1-U6s41f48iJG6RlMp6iP6wFQBJvk=", - "requires": { - "charenc": "~0.0.1", - "crypt": "~0.0.1", - "is-buffer": "~1.1.1" - } - }, - "mdast-util-compact": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/mdast-util-compact/-/mdast-util-compact-1.0.2.tgz", - "integrity": "sha512-d2WS98JSDVbpSsBfVvD9TaDMlqPRz7ohM/11G0rp5jOBb5q96RJ6YLszQ/09AAixyzh23FeIpCGqfaamEADtWg==", - "requires": { - "unist-util-visit": "^1.1.0" - } - }, - "micromatch": { - "version": "2.3.11", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-2.3.11.tgz", - "integrity": "sha1-hmd8l9FyCzY0MdBNDRUpO9OMFWU=", - "requires": { - "arr-diff": "^2.0.0", - "array-unique": "^0.2.1", - "braces": "^1.8.2", - "expand-brackets": "^0.1.4", - "extglob": "^0.3.1", - "filename-regex": "^2.0.0", - "is-extglob": "^1.0.0", - "is-glob": "^2.0.1", - "kind-of": "^3.0.2", - "normalize-path": "^2.0.1", - "object.omit": "^2.0.0", - "parse-glob": "^3.0.4", - "regex-cache": "^0.4.2" - } - }, - "mime-db": { - "version": "1.36.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.36.0.tgz", - "integrity": "sha512-L+xvyD9MkoYMXb1jAmzI/lWYAxAMCPvIBSWur0PZ5nOf5euahRLVqH//FKW9mWp2lkqUgYiXPgkzfMUFi4zVDw==" - }, - "mime-types": { - "version": "2.1.20", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.20.tgz", - "integrity": "sha512-HrkrPaP9vGuWbLK1B1FfgAkbqNjIuy4eHlIYnFi7kamZyLLrGlo2mpcx0bBmNpKqBtYtAfGbodDddIgddSJC2A==", - "requires": { - "mime-db": "~1.36.0" - } - }, - "mimic-response": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.1.tgz", - "integrity": "sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==" - }, - "minimatch": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", - "requires": { - "brace-expansion": "^1.1.7" - } - }, - "minimist": { - "version": "1.2.0", - "resolved": "http://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", - "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=" - }, - "mixin-deep": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.1.tgz", - "integrity": "sha512-8ZItLHeEgaqEvd5lYBXfm4EZSFCX29Jb9K+lAHhDKzReKBQKj3R+7NOF6tjqYi9t4oI8VUfaWITJQm86wnXGNQ==", - "requires": { - "for-in": "^1.0.2", - "is-extendable": "^1.0.1" - }, - "dependencies": { - "is-extendable": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", - "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", - "requires": { - "is-plain-object": "^2.0.4" - } - } - } - }, - "mkdirp": { - "version": "0.5.1", - "resolved": "http://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", - "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", - "requires": { - "minimist": "0.0.8" - }, - "dependencies": { - "minimist": { - "version": "0.0.8", - "resolved": "http://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", - "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=" - } - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" - }, - "nan": { - "version": "2.11.0", - "resolved": "https://registry.npmjs.org/nan/-/nan-2.11.0.tgz", - "integrity": "sha512-F4miItu2rGnV2ySkXOQoA8FKz/SR2Q2sWP0sbTxNxz/tuokeC8WxOhPMcwi0qIyGtVn/rrSeLbvVkznqCdwYnw==", - "optional": true - }, - "nanomatch": { - "version": "1.2.13", - "resolved": "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.13.tgz", - "integrity": "sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA==", - "requires": { - "arr-diff": "^4.0.0", - "array-unique": "^0.3.2", - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "fragment-cache": "^0.2.1", - "is-windows": "^1.0.2", - "kind-of": "^6.0.2", - "object.pick": "^1.3.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.1" - }, - "dependencies": { - "arr-diff": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", - "integrity": "sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA=" - }, - "array-unique": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", - "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=" - }, - "kind-of": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", - "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==" - } - } - }, - "nlcst-to-string": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/nlcst-to-string/-/nlcst-to-string-2.0.2.tgz", - "integrity": "sha512-DV7wVvMcAsmZ5qEwvX1JUNF4lKkAAKbChwNlIH7NLsPR7LWWoeIt53YlZ5CQH5KDXEXQ9Xa3mw0PbPewymrtew==" - }, - "no-cliches": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/no-cliches/-/no-cliches-0.1.0.tgz", - "integrity": "sha1-9OuBpVH+zegT+MYR415kpRGNw4w=" - }, - "normalize-package-data": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.4.0.tgz", - "integrity": "sha512-9jjUFbTPfEy3R/ad/2oNbKtW9Hgovl5O1FvFWKkKblNXoN/Oou6+9+KKohPK13Yc3/TyunyWhJp6gvRNR/PPAw==", - "requires": { - "hosted-git-info": "^2.1.4", - "is-builtin-module": "^1.0.0", - "semver": "2 || 3 || 4 || 5", - "validate-npm-package-license": "^3.0.1" - } - }, - "normalize-path": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", - "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=", - "requires": { - "remove-trailing-separator": "^1.0.1" - } - }, - "normalize-url": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-2.0.1.tgz", - "integrity": "sha512-D6MUW4K/VzoJ4rJ01JFKxDrtY1v9wrgzCX5f2qj/lzH1m/lW6MhUZFKerVsnyjOhOsYzI9Kqqak+10l4LvLpMw==", - "requires": { - "prepend-http": "^2.0.0", - "query-string": "^5.0.1", - "sort-keys": "^2.0.0" - }, - "dependencies": { - "prepend-http": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-2.0.0.tgz", - "integrity": "sha1-6SQ0v6XqjBn0HN/UAddBo8gZ2Jc=" - } - } - }, - "npm-prefix": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/npm-prefix/-/npm-prefix-1.2.0.tgz", - "integrity": "sha1-5hlFX3B0ulTMZtbQ033Z8b5ry8A=", - "requires": { - "rc": "^1.1.0", - "shellsubstitute": "^1.1.0", - "untildify": "^2.1.0" - } - }, - "number-is-nan": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", - "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=" - }, - "oauth-sign": { - "version": "0.9.0", - "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", - "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==" - }, - "object-assign": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=" - }, - "object-copy": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/object-copy/-/object-copy-0.1.0.tgz", - "integrity": "sha1-fn2Fi3gb18mRpBupde04EnVOmYw=", - "requires": { - "copy-descriptor": "^0.1.0", - "define-property": "^0.2.5", - "kind-of": "^3.0.3" - }, - "dependencies": { - "define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "requires": { - "is-descriptor": "^0.1.0" - } - } - } - }, - "object-keys": { - "version": "1.0.12", - "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.0.12.tgz", - "integrity": "sha512-FTMyFUm2wBcGHnH2eXmz7tC6IwlqQZ6mVZ+6dm6vZ4IQIHjs6FdNsQBuKGPuUUUY6NfJw2PshC08Tn6LzLDOag==" - }, - "object-visit": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/object-visit/-/object-visit-1.0.1.tgz", - "integrity": "sha1-95xEk68MU3e1n+OdOV5BBC3QRbs=", - "requires": { - "isobject": "^3.0.0" - }, - "dependencies": { - "isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=" - } - } - }, - "object.assign": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.0.tgz", - "integrity": "sha512-exHJeq6kBKj58mqGyTQ9DFvrZC/eR6OwxzoM9YRoGBqrXYonaFyGiFMuc9VZrXf7DarreEwMpurG3dd+CNyW5w==", - "requires": { - "define-properties": "^1.1.2", - "function-bind": "^1.1.1", - "has-symbols": "^1.0.0", - "object-keys": "^1.0.11" - } - }, - "object.omit": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/object.omit/-/object.omit-2.0.1.tgz", - "integrity": "sha1-Gpx0SCnznbuFjHbKNXmuKlTr0fo=", - "requires": { - "for-own": "^0.1.4", - "is-extendable": "^0.1.1" - } - }, - "object.pick": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/object.pick/-/object.pick-1.3.0.tgz", - "integrity": "sha1-h6EKxMFpS9Lhy/U1kaZhQftd10c=", - "requires": { - "isobject": "^3.0.1" - }, - "dependencies": { - "isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=" - } - } - }, - "once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", - "requires": { - "wrappy": "1" - } - }, - "optionator": { - "version": "0.8.2", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.2.tgz", - "integrity": "sha1-NkxeQJ0/TWMB1sC0wFu6UBgK62Q=", - "requires": { - "deep-is": "~0.1.3", - "fast-levenshtein": "~2.0.4", - "levn": "~0.3.0", - "prelude-ls": "~1.1.2", - "type-check": "~0.3.2", - "wordwrap": "~1.0.0" - } - }, - "os-homedir": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", - "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=" - }, - "p-any": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/p-any/-/p-any-1.1.0.tgz", - "integrity": "sha512-Ef0tVa4CZ5pTAmKn+Cg3w8ABBXh+hHO1aV8281dKOoUHfX+3tjG2EaFcC+aZyagg9b4EYGsHEjz21DnEE8Og2g==", - "requires": { - "p-some": "^2.0.0" - } - }, - "p-cancelable": { - "version": "0.4.1", - "resolved": "http://registry.npmjs.org/p-cancelable/-/p-cancelable-0.4.1.tgz", - "integrity": "sha512-HNa1A8LvB1kie7cERyy21VNeHb2CWJJYqyyC2o3klWFfMGlFmWv2Z7sFgZH8ZiaYL95ydToKTFVXgMV/Os0bBQ==" - }, - "p-finally": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", - "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=" - }, - "p-is-promise": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/p-is-promise/-/p-is-promise-1.1.0.tgz", - "integrity": "sha1-nJRWmJ6fZYgBewQ01WCXZ1w9oF4=" - }, - "p-limit": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", - "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==", - "requires": { - "p-try": "^1.0.0" - } - }, - "p-locate": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz", - "integrity": "sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=", - "requires": { - "p-limit": "^1.1.0" - } - }, - "p-some": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/p-some/-/p-some-2.0.1.tgz", - "integrity": "sha1-Zdh8ixVO289SIdFnd4ttLhUPbwY=", - "requires": { - "aggregate-error": "^1.0.0" - } - }, - "p-timeout": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-1.2.1.tgz", - "integrity": "sha1-XrOzU7f86Z8QGhA4iAuwVOu+o4Y=", - "requires": { - "p-finally": "^1.0.0" - } - }, - "p-try": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", - "integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=" - }, - "parse-entities": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/parse-entities/-/parse-entities-1.1.2.tgz", - "integrity": "sha512-5N9lmQ7tmxfXf+hO3X6KRG6w7uYO/HL9fHalSySTdyn63C3WNvTM/1R8tn1u1larNcEbo3Slcy2bsVDQqvEpUg==", - "requires": { - "character-entities": "^1.0.0", - "character-entities-legacy": "^1.0.0", - "character-reference-invalid": "^1.0.0", - "is-alphanumerical": "^1.0.0", - "is-decimal": "^1.0.0", - "is-hexadecimal": "^1.0.0" - } - }, - "parse-glob": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/parse-glob/-/parse-glob-3.0.4.tgz", - "integrity": "sha1-ssN2z7EfNVE7rdFz7wu246OIORw=", - "requires": { - "glob-base": "^0.3.0", - "is-dotfile": "^1.0.0", - "is-extglob": "^1.0.0", - "is-glob": "^2.0.0" - } - }, - "parse-json": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", - "integrity": "sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA=", - "requires": { - "error-ex": "^1.3.1", - "json-parse-better-errors": "^1.0.1" - } - }, - "pascalcase": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/pascalcase/-/pascalcase-0.1.1.tgz", - "integrity": "sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ=" - }, - "passive-voice": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/passive-voice/-/passive-voice-0.1.0.tgz", - "integrity": "sha1-Fv+RrkC6DpLEPmcXY/3IQqcCcLE=" - }, - "path-exists": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", - "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=" - }, - "path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=" - }, - "path-is-inside": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/path-is-inside/-/path-is-inside-1.0.2.tgz", - "integrity": "sha1-NlQX3t5EQw0cEa9hAn+s8HS9/FM=" - }, - "path-to-glob-pattern": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/path-to-glob-pattern/-/path-to-glob-pattern-1.0.2.tgz", - "integrity": "sha1-Rz5qOikqnRP7rj7czuctO6uoxhk=" - }, - "path-type": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-1.1.0.tgz", - "integrity": "sha1-WcRPfuSR2nBNpBXaWkBwuk+P5EE=", - "requires": { - "graceful-fs": "^4.1.2", - "pify": "^2.0.0", - "pinkie-promise": "^2.0.0" - }, - "dependencies": { - "pify": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", - "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=" - } - } - }, - "performance-now": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", - "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=" - }, - "pify": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", - "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=" - }, - "pinkie": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz", - "integrity": "sha1-clVrgM+g1IqXToDnckjoDtT3+HA=" - }, - "pinkie-promise": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz", - "integrity": "sha1-ITXW36ejWMBprJsXh3YogihFD/o=", - "requires": { - "pinkie": "^2.0.0" - } - }, - "pluralize": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/pluralize/-/pluralize-2.0.0.tgz", - "integrity": "sha1-crcmqm+sHt7uQiVsfY3CVrM1Z38=" - }, - "posix-character-classes": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/posix-character-classes/-/posix-character-classes-0.1.1.tgz", - "integrity": "sha1-AerA/jta9xoqbAL+q7jB/vfgDqs=" - }, - "prelude-ls": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", - "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=" - }, - "prepend-http": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-1.0.4.tgz", - "integrity": "sha1-1PRWKwzjaW5BrFLQ4ALlemNdxtw=" - }, - "preserve": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/preserve/-/preserve-0.2.0.tgz", - "integrity": "sha1-gV7R9uvGWSb4ZbMQwHE7yzMVzks=" - }, - "prettier": { - "version": "1.14.3", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-1.14.3.tgz", - "integrity": "sha512-qZDVnCrnpsRJJq5nSsiHCE3BYMED2OtsI+cmzIzF1QIfqm5ALf8tEJcO27zV1gKNKRPdhjO0dNWnrzssDQ1tFg==" - }, - "process-nextick-args": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz", - "integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==" - }, - "psl": { - "version": "1.1.29", - "resolved": "https://registry.npmjs.org/psl/-/psl-1.1.29.tgz", - "integrity": "sha512-AeUmQ0oLN02flVHXWh9sSJF7mcdFq0ppid/JkErufc3hGIV/AMa8Fo9VgDo/cT2jFdOWoFvHp90qqBH54W+gjQ==" - }, - "public-ip": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/public-ip/-/public-ip-2.4.0.tgz", - "integrity": "sha512-74cIy+T2cDmt+Z71AfVipH2q6qqZITPyNGszKV86OGDYIRvti1m8zg4GOaiTPCLgEIWnToKYXbhEnMiZWHPEUA==", - "requires": { - "dns-socket": "^1.6.2", - "got": "^8.0.0", - "is-ip": "^2.0.0", - "pify": "^3.0.0" - }, - "dependencies": { - "got": { - "version": "8.3.2", - "resolved": "https://registry.npmjs.org/got/-/got-8.3.2.tgz", - "integrity": "sha512-qjUJ5U/hawxosMryILofZCkm3C84PLJS/0grRIpjAwu+Lkxxj5cxeCU25BG0/3mDSpXKTyZr8oh8wIgLaH0QCw==", - "requires": { - "@sindresorhus/is": "^0.7.0", - "cacheable-request": "^2.1.1", - "decompress-response": "^3.3.0", - "duplexer3": "^0.1.4", - "get-stream": "^3.0.0", - "into-stream": "^3.1.0", - "is-retry-allowed": "^1.1.0", - "isurl": "^1.0.0-alpha5", - "lowercase-keys": "^1.0.0", - "mimic-response": "^1.0.0", - "p-cancelable": "^0.4.0", - "p-timeout": "^2.0.1", - "pify": "^3.0.0", - "safe-buffer": "^5.1.1", - "timed-out": "^4.0.1", - "url-parse-lax": "^3.0.0", - "url-to-options": "^1.0.1" - } - }, - "p-timeout": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-2.0.1.tgz", - "integrity": "sha512-88em58dDVB/KzPEx1X0N3LwFfYZPyDc4B6eF38M1rk9VTZMbxXXgjugz8mmwpS9Ox4BDZ+t6t3QP5+/gazweIA==", - "requires": { - "p-finally": "^1.0.0" - } - }, - "prepend-http": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-2.0.0.tgz", - "integrity": "sha1-6SQ0v6XqjBn0HN/UAddBo8gZ2Jc=" - }, - "url-parse-lax": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/url-parse-lax/-/url-parse-lax-3.0.0.tgz", - "integrity": "sha1-FrXK/Afb42dsGxmZF3gj1lA6yww=", - "requires": { - "prepend-http": "^2.0.0" - } - } - } - }, - "punycode": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", - "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==" - }, - "qs": { - "version": "6.5.2", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", - "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==" - }, - "query-string": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/query-string/-/query-string-5.1.1.tgz", - "integrity": "sha512-gjWOsm2SoGlgLEdAGt7a6slVOk9mGiXmPFMqrEhLQ68rhQuBnpfs3+EmlvqKyxnCo9/PPlF+9MtY02S1aFg+Jw==", - "requires": { - "decode-uri-component": "^0.2.0", - "object-assign": "^4.1.0", - "strict-uri-encode": "^1.0.0" - } - }, - "randomatic": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/randomatic/-/randomatic-3.1.0.tgz", - "integrity": "sha512-KnGPVE0lo2WoXxIZ7cPR8YBpiol4gsSuOwDSg410oHh80ZMp5EiypNqL2K4Z77vJn6lB5rap7IkAmcUlalcnBQ==", - "requires": { - "is-number": "^4.0.0", - "kind-of": "^6.0.0", - "math-random": "^1.0.1" - }, - "dependencies": { - "is-number": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-4.0.0.tgz", - "integrity": "sha512-rSklcAIlf1OmFdyAqbnWTLVelsQ58uvZ66S/ZyawjWqIviTWCjg2PzVGw8WUA+nNuPTqb4wgA+NszrJ+08LlgQ==" - }, - "kind-of": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", - "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==" - } - } - }, - "rc": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", - "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", - "requires": { - "deep-extend": "^0.6.0", - "ini": "~1.3.0", - "minimist": "^1.2.0", - "strip-json-comments": "~2.0.1" - } - }, - "rc-config-loader": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/rc-config-loader/-/rc-config-loader-2.0.2.tgz", - "integrity": "sha512-Nx9SNM47eNRqe0TdntOY600qWb8NDh+xU9sv5WnTscEtzfTB0ukihlqwuCLPteyJksvZ0sEVPoySNE01TKrmTQ==", - "requires": { - "debug": "^3.1.0", - "js-yaml": "^3.12.0", - "json5": "^1.0.1", - "object-assign": "^4.1.0", - "object-keys": "^1.0.12", - "path-exists": "^3.0.0", - "require-from-string": "^2.0.2" - }, - "dependencies": { - "debug": { - "version": "3.2.5", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.5.tgz", - "integrity": "sha512-D61LaDQPQkxJ5AUM2mbSJRbPkNs/TmdmOeLAi1hgDkpDfIfetSrjmWhccwtuResSwMbACjx/xXQofvM9CE/aeg==", - "requires": { - "ms": "^2.1.1" - } - }, - "json5": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", - "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", - "requires": { - "minimist": "^1.2.0" - } - }, - "ms": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", - "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==" - } - } - }, - "read-pkg": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-1.1.0.tgz", - "integrity": "sha1-9f+qXs0pyzHAR0vKfXVra7KePyg=", - "requires": { - "load-json-file": "^1.0.0", - "normalize-package-data": "^2.3.2", - "path-type": "^1.0.0" - } - }, - "read-pkg-up": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-3.0.0.tgz", - "integrity": "sha1-PtSWaF26D4/hGNBpHcUfSh/5bwc=", - "requires": { - "find-up": "^2.0.0", - "read-pkg": "^3.0.0" - }, - "dependencies": { - "load-json-file": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-4.0.0.tgz", - "integrity": "sha1-L19Fq5HjMhYjT9U62rZo607AmTs=", - "requires": { - "graceful-fs": "^4.1.2", - "parse-json": "^4.0.0", - "pify": "^3.0.0", - "strip-bom": "^3.0.0" - } - }, - "path-type": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-3.0.0.tgz", - "integrity": "sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg==", - "requires": { - "pify": "^3.0.0" - } - }, - "read-pkg": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-3.0.0.tgz", - "integrity": "sha1-nLxoaXj+5l0WwA4rGcI3/Pbjg4k=", - "requires": { - "load-json-file": "^4.0.0", - "normalize-package-data": "^2.3.2", - "path-type": "^3.0.0" - } - }, - "strip-bom": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", - "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=" - } - } - }, - "readable-stream": { - "version": "2.3.6", - "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", - "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "readdirp": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-2.2.1.tgz", - "integrity": "sha512-1JU/8q+VgFZyxwrJ+SVIOsh+KywWGpds3NTqikiKpDMZWScmAYyKIgqkO+ARvNWJfXeXR1zxz7aHF4u4CyH6vQ==", - "requires": { - "graceful-fs": "^4.1.11", - "micromatch": "^3.1.10", - "readable-stream": "^2.0.2" - }, - "dependencies": { - "arr-diff": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", - "integrity": "sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA=" - }, - "array-unique": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", - "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=" - }, - "braces": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", - "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", - "requires": { - "arr-flatten": "^1.1.0", - "array-unique": "^0.3.2", - "extend-shallow": "^2.0.1", - "fill-range": "^4.0.0", - "isobject": "^3.0.1", - "repeat-element": "^1.1.2", - "snapdragon": "^0.8.1", - "snapdragon-node": "^2.0.1", - "split-string": "^3.0.2", - "to-regex": "^3.0.1" - }, - "dependencies": { - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "requires": { - "is-extendable": "^0.1.0" - } - } - } - }, - "expand-brackets": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz", - "integrity": "sha1-t3c14xXOMPa27/D4OwQVGiJEliI=", - "requires": { - "debug": "^2.3.3", - "define-property": "^0.2.5", - "extend-shallow": "^2.0.1", - "posix-character-classes": "^0.1.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.1" - }, - "dependencies": { - "define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "requires": { - "is-descriptor": "^0.1.0" - } - }, - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "requires": { - "is-extendable": "^0.1.0" - } - }, - "is-accessor-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", - "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "is-data-descriptor": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", - "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "is-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", - "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", - "requires": { - "is-accessor-descriptor": "^0.1.6", - "is-data-descriptor": "^0.1.4", - "kind-of": "^5.0.0" - } - }, - "kind-of": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", - "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==" - } - } - }, - "extglob": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz", - "integrity": "sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==", - "requires": { - "array-unique": "^0.3.2", - "define-property": "^1.0.0", - "expand-brackets": "^2.1.4", - "extend-shallow": "^2.0.1", - "fragment-cache": "^0.2.1", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.1" - }, - "dependencies": { - "define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", - "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", - "requires": { - "is-descriptor": "^1.0.0" - } - }, - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "requires": { - "is-extendable": "^0.1.0" - } - } - } - }, - "fill-range": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", - "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", - "requires": { - "extend-shallow": "^2.0.1", - "is-number": "^3.0.0", - "repeat-string": "^1.6.1", - "to-regex-range": "^2.1.0" - }, - "dependencies": { - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "requires": { - "is-extendable": "^0.1.0" - } - } - } - }, - "is-accessor-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", - "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-data-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", - "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-descriptor": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", - "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", - "requires": { - "is-accessor-descriptor": "^1.0.0", - "is-data-descriptor": "^1.0.0", - "kind-of": "^6.0.2" - } - }, - "is-number": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", - "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=" - }, - "kind-of": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", - "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==" - }, - "micromatch": { - "version": "3.1.10", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", - "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", - "requires": { - "arr-diff": "^4.0.0", - "array-unique": "^0.3.2", - "braces": "^2.3.1", - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "extglob": "^2.0.4", - "fragment-cache": "^0.2.1", - "kind-of": "^6.0.2", - "nanomatch": "^1.2.9", - "object.pick": "^1.3.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.2" - } - } - } - }, - "regex-cache": { - "version": "0.4.4", - "resolved": "https://registry.npmjs.org/regex-cache/-/regex-cache-0.4.4.tgz", - "integrity": "sha512-nVIZwtCjkC9YgvWkpM55B5rBhBYRZhAaJbgcFYXXsHnbZ9UZI9nnVWYZpBlCqv9ho2eZryPnWrZGsOdPwVWXWQ==", - "requires": { - "is-equal-shallow": "^0.1.3" - } - }, - "regex-not": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/regex-not/-/regex-not-1.0.2.tgz", - "integrity": "sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A==", - "requires": { - "extend-shallow": "^3.0.2", - "safe-regex": "^1.1.0" - } - }, - "remark": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/remark/-/remark-9.0.0.tgz", - "integrity": "sha512-amw8rGdD5lHbMEakiEsllmkdBP+/KpjW/PRK6NSGPZKCQowh0BT4IWXDAkRMyG3SB9dKPXWMviFjNusXzXNn3A==", - "requires": { - "remark-parse": "^5.0.0", - "remark-stringify": "^5.0.0", - "unified": "^6.0.0" - } - }, - "remark-cli": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/remark-cli/-/remark-cli-5.0.0.tgz", - "integrity": "sha512-+j0tza5XZ/XHfity3mg5GJFezRt5hS+ybC7/LDItmOAA8u8gRgB51B+/m5U3yT6RLlhefdqkMGKZnZMcamnvsQ==", - "requires": { - "markdown-extensions": "^1.1.0", - "remark": "^9.0.0", - "unified-args": "^5.0.0" - } - }, - "remark-frontmatter": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/remark-frontmatter/-/remark-frontmatter-1.2.1.tgz", - "integrity": "sha512-PEXZFO3jrB+E0G6ZIsV8GOED1gPHQF5hgedJQJ8SbsLRQv4KKrFj3A+huaeu0qtzTScdxPeDTacQ9gkV4vIarA==", - "requires": { - "fault": "^1.0.1", - "xtend": "^4.0.1" - } - }, - "remark-lint-no-dead-urls": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/remark-lint-no-dead-urls/-/remark-lint-no-dead-urls-0.3.0.tgz", - "integrity": "sha512-eG+vVrNui7zeBmU6fsjIi8rwXriuyNhNcmJDQ7M5oaxCluWbH5bt6Yi/JNsabYE39dFdlVbw9JM3cLjaJv2hQw==", - "requires": { - "is-online": "^7.0.0", - "is-relative-url": "^2.0.0", - "link-check": "^4.1.0", - "unified-lint-rule": "^1.0.1", - "unist-util-visit": "^1.1.3" - } - }, - "remark-lint-write-good": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/remark-lint-write-good/-/remark-lint-write-good-1.0.3.tgz", - "integrity": "sha512-d4D4VrAklAx2ONhpXoQnt0YrJFpJBE5XEeCyDGjPhm4DkIoLOmHWZEjxl1HvdrpGXLb/KfYU4lJPeyxlKiDhVA==", - "requires": { - "nlcst-to-string": "^2.0.0", - "unified-lint-rule": "^1.0.1", - "unist-util-visit": "^1.1.1", - "write-good": "^0.11.1" - } - }, - "remark-parse": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/remark-parse/-/remark-parse-5.0.0.tgz", - "integrity": "sha512-b3iXszZLH1TLoyUzrATcTQUZrwNl1rE70rVdSruJFlDaJ9z5aMkhrG43Pp68OgfHndL/ADz6V69Zow8cTQu+JA==", - "requires": { - "collapse-white-space": "^1.0.2", - "is-alphabetical": "^1.0.0", - "is-decimal": "^1.0.0", - "is-whitespace-character": "^1.0.0", - "is-word-character": "^1.0.0", - "markdown-escapes": "^1.0.0", - "parse-entities": "^1.1.0", - "repeat-string": "^1.5.4", - "state-toggle": "^1.0.0", - "trim": "0.0.1", - "trim-trailing-lines": "^1.0.0", - "unherit": "^1.0.4", - "unist-util-remove-position": "^1.0.0", - "vfile-location": "^2.0.0", - "xtend": "^4.0.1" - } - }, - "remark-stringify": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/remark-stringify/-/remark-stringify-5.0.0.tgz", - "integrity": "sha512-Ws5MdA69ftqQ/yhRF9XhVV29mhxbfGhbz0Rx5bQH+oJcNhhSM6nCu1EpLod+DjrFGrU0BMPs+czVmJZU7xiS7w==", - "requires": { - "ccount": "^1.0.0", - "is-alphanumeric": "^1.0.0", - "is-decimal": "^1.0.0", - "is-whitespace-character": "^1.0.0", - "longest-streak": "^2.0.1", - "markdown-escapes": "^1.0.0", - "markdown-table": "^1.1.0", - "mdast-util-compact": "^1.0.0", - "parse-entities": "^1.0.2", - "repeat-string": "^1.5.4", - "state-toggle": "^1.0.0", - "stringify-entities": "^1.0.1", - "unherit": "^1.0.4", - "xtend": "^4.0.1" - } - }, - "remove-trailing-separator": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz", - "integrity": "sha1-wkvOKig62tW8P1jg1IJJuSN52O8=" - }, - "repeat-element": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.3.tgz", - "integrity": "sha512-ahGq0ZnV5m5XtZLMb+vP76kcAM5nkLqk0lpqAuojSKGgQtn4eRi4ZZGm2olo2zKFH+sMsWaqOCW1dqAnOru72g==" - }, - "repeat-string": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", - "integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc=" - }, - "replace-ext": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/replace-ext/-/replace-ext-1.0.0.tgz", - "integrity": "sha1-3mMSg3P8v3w8z6TeWkgMRaZ5WOs=" - }, - "request": { - "version": "2.88.0", - "resolved": "https://registry.npmjs.org/request/-/request-2.88.0.tgz", - "integrity": "sha512-NAqBSrijGLZdM0WZNsInLJpkJokL72XYjUpnB0iwsRgxh7dB6COrHnTBNwN0E+lHDAJzu7kLAkDeY08z2/A0hg==", - "requires": { - "aws-sign2": "~0.7.0", - "aws4": "^1.8.0", - "caseless": "~0.12.0", - "combined-stream": "~1.0.6", - "extend": "~3.0.2", - "forever-agent": "~0.6.1", - "form-data": "~2.3.2", - "har-validator": "~5.1.0", - "http-signature": "~1.2.0", - "is-typedarray": "~1.0.0", - "isstream": "~0.1.2", - "json-stringify-safe": "~5.0.1", - "mime-types": "~2.1.19", - "oauth-sign": "~0.9.0", - "performance-now": "^2.1.0", - "qs": "~6.5.2", - "safe-buffer": "^5.1.2", - "tough-cookie": "~2.4.3", - "tunnel-agent": "^0.6.0", - "uuid": "^3.3.2" - } - }, - "require-from-string": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", - "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==" - }, - "resolve-from": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", - "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==" - }, - "resolve-url": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz", - "integrity": "sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo=" - }, - "responselike": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/responselike/-/responselike-1.0.2.tgz", - "integrity": "sha1-kYcg7ztjHFZCvgaPFa3lpG9Loec=", - "requires": { - "lowercase-keys": "^1.0.0" - } - }, - "ret": { - "version": "0.1.15", - "resolved": "https://registry.npmjs.org/ret/-/ret-0.1.15.tgz", - "integrity": "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==" - }, - "rimraf": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.2.tgz", - "integrity": "sha512-lreewLK/BlghmxtfH36YYVg1i8IAce4TI7oao75I1g245+6BctqTVQiBP3YUJ9C6DQOXJmkYR9X9fCLtCOJc5w==", - "requires": { - "glob": "^7.0.5" - } - }, - "safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" - }, - "safe-regex": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz", - "integrity": "sha1-QKNmnzsHfR6UPURinhV91IAjvy4=", - "requires": { - "ret": "~0.1.10" - } - }, - "safer-buffer": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" - }, - "semver": { - "version": "5.5.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.5.1.tgz", - "integrity": "sha512-PqpAxfrEhlSUWge8dwIp4tZnQ25DIOthpiaHNIthsjEFQD6EvqUKUDM7L8O2rShkFccYo1VjJR0coWfNkCubRw==" - }, - "set-value": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.0.tgz", - "integrity": "sha512-hw0yxk9GT/Hr5yJEYnHNKYXkIA8mVJgd9ditYZCe16ZczcaELYYcfvaXesNACk2O8O0nTiPQcQhGUQj8JLzeeg==", - "requires": { - "extend-shallow": "^2.0.1", - "is-extendable": "^0.1.1", - "is-plain-object": "^2.0.3", - "split-string": "^3.0.1" - }, - "dependencies": { - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "requires": { - "is-extendable": "^0.1.0" - } - } - } - }, - "shellsubstitute": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/shellsubstitute/-/shellsubstitute-1.2.0.tgz", - "integrity": "sha1-5PcCpQxRiw9v6YRRiQ1wWvKba3A=" - }, - "slice-ansi": { - "version": "0.0.4", - "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-0.0.4.tgz", - "integrity": "sha1-7b+JA/ZvfOL46v1s7tZeJkyDGzU=" - }, - "sliced": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/sliced/-/sliced-1.0.1.tgz", - "integrity": "sha1-CzpmK10Ewxd7GSa+qCsD+Dei70E=" - }, - "snapdragon": { - "version": "0.8.2", - "resolved": "https://registry.npmjs.org/snapdragon/-/snapdragon-0.8.2.tgz", - "integrity": "sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg==", - "requires": { - "base": "^0.11.1", - "debug": "^2.2.0", - "define-property": "^0.2.5", - "extend-shallow": "^2.0.1", - "map-cache": "^0.2.2", - "source-map": "^0.5.6", - "source-map-resolve": "^0.5.0", - "use": "^3.1.0" - }, - "dependencies": { - "define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "requires": { - "is-descriptor": "^0.1.0" - } - }, - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "requires": { - "is-extendable": "^0.1.0" - } - } - } - }, - "snapdragon-node": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/snapdragon-node/-/snapdragon-node-2.1.1.tgz", - "integrity": "sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw==", - "requires": { - "define-property": "^1.0.0", - "isobject": "^3.0.0", - "snapdragon-util": "^3.0.1" - }, - "dependencies": { - "define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", - "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", - "requires": { - "is-descriptor": "^1.0.0" - } - }, - "is-accessor-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", - "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-data-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", - "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-descriptor": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", - "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", - "requires": { - "is-accessor-descriptor": "^1.0.0", - "is-data-descriptor": "^1.0.0", - "kind-of": "^6.0.2" - } - }, - "isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=" - }, - "kind-of": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", - "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==" - } - } - }, - "snapdragon-util": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/snapdragon-util/-/snapdragon-util-3.0.1.tgz", - "integrity": "sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ==", - "requires": { - "kind-of": "^3.2.0" - } - }, - "sort-keys": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/sort-keys/-/sort-keys-2.0.0.tgz", - "integrity": "sha1-ZYU1WEhh7JfXMNbPQYIuH1ZoQSg=", - "requires": { - "is-plain-obj": "^1.0.0" - } - }, - "source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=" - }, - "source-map-resolve": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.5.2.tgz", - "integrity": "sha512-MjqsvNwyz1s0k81Goz/9vRBe9SZdB09Bdw+/zYyO+3CuPk6fouTaxscHkgtE8jKvf01kVfl8riHzERQ/kefaSA==", - "requires": { - "atob": "^2.1.1", - "decode-uri-component": "^0.2.0", - "resolve-url": "^0.2.1", - "source-map-url": "^0.4.0", - "urix": "^0.1.0" - } - }, - "source-map-url": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/source-map-url/-/source-map-url-0.4.0.tgz", - "integrity": "sha1-PpNdfd1zYxuXZZlW1VEo6HtQhKM=" - }, - "spdx-correct": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.0.0.tgz", - "integrity": "sha512-N19o9z5cEyc8yQQPukRCZ9EUmb4HUpnrmaL/fxS2pBo2jbfcFRVuFZ/oFC+vZz0MNNk0h80iMn5/S6qGZOL5+g==", - "requires": { - "spdx-expression-parse": "^3.0.0", - "spdx-license-ids": "^3.0.0" - } - }, - "spdx-exceptions": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.1.0.tgz", - "integrity": "sha512-4K1NsmrlCU1JJgUrtgEeTVyfx8VaYea9J9LvARxhbHtVtohPs/gFGG5yy49beySjlIMhhXZ4QqujIZEfS4l6Cg==" - }, - "spdx-expression-parse": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.0.tgz", - "integrity": "sha512-Yg6D3XpRD4kkOmTpdgbUiEJFKghJH03fiC1OPll5h/0sO6neh2jqRDVHOQ4o/LMea0tgCkbMgea5ip/e+MkWyg==", - "requires": { - "spdx-exceptions": "^2.1.0", - "spdx-license-ids": "^3.0.0" - } - }, - "spdx-license-ids": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.1.tgz", - "integrity": "sha512-TfOfPcYGBB5sDuPn3deByxPhmfegAhpDYKSOXZQN81Oyrrif8ZCodOLzK3AesELnCx03kikhyDwh0pfvvQvF8w==" - }, - "split-lines": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/split-lines/-/split-lines-2.0.0.tgz", - "integrity": "sha512-gaIdhbqxkB5/VflPXsJwZvEzh/kdwiRPF9iqpkxX4us+lzB8INedFwjCyo6vwuz5x2Ddlnav2zh270CEjCG8mA==" - }, - "split-string": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz", - "integrity": "sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw==", - "requires": { - "extend-shallow": "^3.0.0" - } - }, - "sprintf-js": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=" - }, - "sshpk": { - "version": "1.14.2", - "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.14.2.tgz", - "integrity": "sha1-xvxhZIo9nE52T9P8306hBeSSupg=", - "requires": { - "asn1": "~0.2.3", - "assert-plus": "^1.0.0", - "bcrypt-pbkdf": "^1.0.0", - "dashdash": "^1.12.0", - "ecc-jsbn": "~0.1.1", - "getpass": "^0.1.1", - "jsbn": "~0.1.0", - "safer-buffer": "^2.0.2", - "tweetnacl": "~0.14.0" - } - }, - "state-toggle": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/state-toggle/-/state-toggle-1.0.1.tgz", - "integrity": "sha512-Qe8QntFrrpWTnHwvwj2FZTgv+PKIsp0B9VxLzLLbSpPXWOgRgc5LVj/aTiSfK1RqIeF9jeC1UeOH8Q8y60A7og==" - }, - "static-extend": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/static-extend/-/static-extend-0.1.2.tgz", - "integrity": "sha1-YICcOcv/VTNyJv1eC1IPNB8ftcY=", - "requires": { - "define-property": "^0.2.5", - "object-copy": "^0.1.0" - }, - "dependencies": { - "define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "requires": { - "is-descriptor": "^0.1.0" - } - } - } - }, - "strict-uri-encode": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/strict-uri-encode/-/strict-uri-encode-1.1.0.tgz", - "integrity": "sha1-J5siXfHVgrH1TmWt3UNS4Y+qBxM=" - }, - "string-width": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", - "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", - "requires": { - "code-point-at": "^1.0.0", - "is-fullwidth-code-point": "^1.0.0", - "strip-ansi": "^3.0.0" - } - }, - "string.prototype.padstart": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/string.prototype.padstart/-/string.prototype.padstart-3.0.0.tgz", - "integrity": "sha1-W8+tOfRkm7LQMSkuGbzwtRDUskI=", - "requires": { - "define-properties": "^1.1.2", - "es-abstract": "^1.4.3", - "function-bind": "^1.0.2" - } - }, - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "requires": { - "safe-buffer": "~5.1.0" - } - }, - "stringify-entities": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/stringify-entities/-/stringify-entities-1.3.2.tgz", - "integrity": "sha512-nrBAQClJAPN2p+uGCVJRPIPakKeKWZ9GtBCmormE7pWOSlHat7+x5A8gx85M7HM5Dt0BP3pP5RhVW77WdbJJ3A==", - "requires": { - "character-entities-html4": "^1.0.0", - "character-entities-legacy": "^1.0.0", - "is-alphanumerical": "^1.0.0", - "is-hexadecimal": "^1.0.0" - } - }, - "strip-ansi": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", - "requires": { - "ansi-regex": "^2.0.0" - } - }, - "strip-bom": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-2.0.0.tgz", - "integrity": "sha1-YhmoVhZSBJHzV4i9vxRHqZx+aw4=", - "requires": { - "is-utf8": "^0.2.0" - } - }, - "strip-json-comments": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", - "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=" - }, - "structured-source": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/structured-source/-/structured-source-3.0.2.tgz", - "integrity": "sha1-3YAkJeD1PcSm56yjdSkBoczaevU=", - "requires": { - "boundary": "^1.0.1" - } - }, - "supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "requires": { - "has-flag": "^3.0.0" - } - }, - "table": { - "version": "3.8.3", - "resolved": "http://registry.npmjs.org/table/-/table-3.8.3.tgz", - "integrity": "sha1-K7xULw/amGGnVdOUf+/Ys/UThV8=", - "requires": { - "ajv": "^4.7.0", - "ajv-keywords": "^1.0.0", - "chalk": "^1.1.1", - "lodash": "^4.0.0", - "slice-ansi": "0.0.4", - "string-width": "^2.0.0" - }, - "dependencies": { - "ajv": { - "version": "4.11.8", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-4.11.8.tgz", - "integrity": "sha1-gv+wKynmYq5TvcIK8VlHcGc5xTY=", - "requires": { - "co": "^4.6.0", - "json-stable-stringify": "^1.0.1" - } - }, - "ansi-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", - "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=" - }, - "ansi-styles": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", - "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=" - }, - "chalk": { - "version": "1.1.3", - "resolved": "http://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", - "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", - "requires": { - "ansi-styles": "^2.2.1", - "escape-string-regexp": "^1.0.2", - "has-ansi": "^2.0.0", - "strip-ansi": "^3.0.0", - "supports-color": "^2.0.0" - } - }, - "is-fullwidth-code-point": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=" - }, - "string-width": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", - "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", - "requires": { - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^4.0.0" - }, - "dependencies": { - "strip-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", - "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", - "requires": { - "ansi-regex": "^3.0.0" - } - } - } - }, - "supports-color": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", - "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=" - } - } - }, - "text-table": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", - "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=" - }, - "textlint": { - "version": "10.2.1", - "resolved": "https://registry.npmjs.org/textlint/-/textlint-10.2.1.tgz", - "integrity": "sha512-tjSvxRZ7iewPmw0ShIA5IIZNJM9m157K1hGXE9wGxALcSb+xOZ0oLPv1HN7z0UzqOuMNqYyeN7mi4N0IplLkYA==", - "requires": { - "@textlint/ast-node-types": "^4.0.2", - "@textlint/ast-traverse": "^2.0.8", - "@textlint/feature-flag": "^3.0.4", - "@textlint/fixer-formatter": "^3.0.7", - "@textlint/kernel": "^2.0.9", - "@textlint/linter-formatter": "^3.0.7", - "@textlint/textlint-plugin-markdown": "^4.0.10", - "@textlint/textlint-plugin-text": "^3.0.10", - "@types/bluebird": "^3.5.18", - "bluebird": "^3.0.5", - "debug": "^2.1.0", - "deep-equal": "^1.0.1", - "file-entry-cache": "^2.0.0", - "get-stdin": "^5.0.1", - "glob": "^7.1.1", - "interop-require": "^1.0.0", - "is-file": "^1.0.0", - "log-symbols": "^1.0.2", - "map-like": "^2.0.0", - "md5": "^2.2.1", - "mkdirp": "^0.5.0", - "object-assign": "^4.0.1", - "optionator": "^0.8.0", - "path-to-glob-pattern": "^1.0.2", - "rc-config-loader": "^2.0.1", - "read-pkg": "^1.1.0", - "read-pkg-up": "^3.0.0", - "structured-source": "^3.0.2", - "try-resolve": "^1.0.1", - "unique-concat": "^0.2.2" - } - }, - "textlint-rule-helper": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/textlint-rule-helper/-/textlint-rule-helper-2.0.0.tgz", - "integrity": "sha1-lctGlslcQljS4zienmS4SflyE4I=", - "requires": { - "unist-util-visit": "^1.1.0" - } - }, - "textlint-rule-stop-words": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/textlint-rule-stop-words/-/textlint-rule-stop-words-1.0.5.tgz", - "integrity": "sha512-sttfqpFX3ji4AD4eF3gpiCH+csqsaztO0V2koWVYhrHyPjUL4cPlB1I/H4Fa7G3Ik35dBA0q5Tf+88A0vO9erQ==", - "requires": { - "lodash": "^4.17.10", - "split-lines": "^2.0.0", - "textlint-rule-helper": "^2.0.0" - } - }, - "timed-out": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/timed-out/-/timed-out-4.0.1.tgz", - "integrity": "sha1-8y6srFoXW+ol1/q1Zas+2HQe9W8=" - }, - "to-object-path": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/to-object-path/-/to-object-path-0.3.0.tgz", - "integrity": "sha1-KXWIt7Dn4KwI4E5nL4XB9JmeF68=", - "requires": { - "kind-of": "^3.0.2" - } - }, - "to-regex": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/to-regex/-/to-regex-3.0.2.tgz", - "integrity": "sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw==", - "requires": { - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "regex-not": "^1.0.2", - "safe-regex": "^1.1.0" - } - }, - "to-regex-range": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", - "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=", - "requires": { - "is-number": "^3.0.0", - "repeat-string": "^1.6.1" - }, - "dependencies": { - "is-number": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", - "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", - "requires": { - "kind-of": "^3.0.2" - } - } - } - }, - "to-vfile": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/to-vfile/-/to-vfile-2.2.0.tgz", - "integrity": "sha512-saGC8/lWdGrEoBMLUtgzhRHWAkQMP8gdldA3MOAUhBwTGEb1RSMVcflHGSx4ZJsdEZ9o1qDBCPp47LCPrbZWow==", - "requires": { - "is-buffer": "^1.1.4", - "vfile": "^2.0.0", - "x-is-function": "^1.0.4" - } - }, - "too-wordy": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/too-wordy/-/too-wordy-0.1.4.tgz", - "integrity": "sha1-jnsgp7ek2Pw3WfTgDEkpmT0bEvA=" - }, - "tough-cookie": { - "version": "2.4.3", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.4.3.tgz", - "integrity": "sha512-Q5srk/4vDM54WJsJio3XNn6K2sCG+CQ8G5Wz6bZhRZoAe/+TxjWB/GlFAnYEbkYVlON9FMk/fE3h2RLpPXo4lQ==", - "requires": { - "psl": "^1.1.24", - "punycode": "^1.4.1" - }, - "dependencies": { - "punycode": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", - "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=" - } - } - }, - "traverse": { - "version": "0.6.6", - "resolved": "https://registry.npmjs.org/traverse/-/traverse-0.6.6.tgz", - "integrity": "sha1-y99WD9e5r2MlAv7UD5GMFX6pcTc=" - }, - "trim": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/trim/-/trim-0.0.1.tgz", - "integrity": "sha1-WFhUf2spB1fulczMZm+1AITEYN0=" - }, - "trim-trailing-lines": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/trim-trailing-lines/-/trim-trailing-lines-1.1.1.tgz", - "integrity": "sha512-bWLv9BbWbbd7mlqqs2oQYnLD/U/ZqeJeJwbO0FG2zA1aTq+HTvxfHNKFa/HGCVyJpDiioUYaBhfiT6rgk+l4mg==" - }, - "trough": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/trough/-/trough-1.0.3.tgz", - "integrity": "sha512-fwkLWH+DimvA4YCy+/nvJd61nWQQ2liO/nF/RjkTpiOGi+zxZzVkhb1mvbHIIW4b/8nDsYI8uTmAlc0nNkRMOw==" - }, - "try-resolve": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/try-resolve/-/try-resolve-1.0.1.tgz", - "integrity": "sha1-z95vq9ctY+V5fPqrhzq76OcA6RI=" - }, - "tunnel-agent": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", - "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", - "requires": { - "safe-buffer": "^5.0.1" - } - }, - "tweetnacl": { - "version": "0.14.5", - "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", - "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=", - "optional": true - }, - "type-check": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", - "integrity": "sha1-WITKtRLPHTVeP7eE8wgEsrUg23I=", - "requires": { - "prelude-ls": "~1.1.2" - } - }, - "typedarray": { - "version": "0.0.6", - "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", - "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=" - }, - "unherit": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/unherit/-/unherit-1.1.1.tgz", - "integrity": "sha512-+XZuV691Cn4zHsK0vkKYwBEwB74T3IZIcxrgn2E4rKwTfFyI1zCh7X7grwh9Re08fdPlarIdyWgI8aVB3F5A5g==", - "requires": { - "inherits": "^2.0.1", - "xtend": "^4.0.1" - } - }, - "unified": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/unified/-/unified-6.2.0.tgz", - "integrity": "sha512-1k+KPhlVtqmG99RaTbAv/usu85fcSRu3wY8X+vnsEhIxNP5VbVIDiXnLqyKIG+UMdyTg0ZX9EI6k2AfjJkHPtA==", - "requires": { - "bail": "^1.0.0", - "extend": "^3.0.0", - "is-plain-obj": "^1.1.0", - "trough": "^1.0.0", - "vfile": "^2.0.0", - "x-is-string": "^0.1.0" - } - }, - "unified-args": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/unified-args/-/unified-args-5.1.0.tgz", - "integrity": "sha512-IR8bS/qrfOMuIYrLlaXt+3L6cvDHv5YbBfYNVGBLbShUjE9vpbnUiPFMc/XKtH6oAGrD/m8lvVwCHDsFGBBzJA==", - "requires": { - "camelcase": "^4.0.0", - "chalk": "^2.0.0", - "chokidar": "^1.5.1", - "json5": "^0.5.1", - "minimist": "^1.2.0", - "text-table": "^0.2.0", - "unified-engine": "^5.1.0" - } - }, - "unified-engine": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/unified-engine/-/unified-engine-5.1.0.tgz", - "integrity": "sha512-N7b7HG6doQUtkWr+kH35tfUhfc9QiYeiZGG6TcZlexSURf4xRUpYKBbc2f67qJF5oPmn6mMkImkdhr31Q6saoA==", - "requires": { - "concat-stream": "^1.5.1", - "debug": "^3.1.0", - "fault": "^1.0.0", - "fn-name": "^2.0.1", - "glob": "^7.0.3", - "ignore": "^3.2.0", - "is-empty": "^1.0.0", - "is-hidden": "^1.0.1", - "is-object": "^1.0.1", - "js-yaml": "^3.6.1", - "load-plugin": "^2.0.0", - "parse-json": "^4.0.0", - "to-vfile": "^2.0.0", - "trough": "^1.0.0", - "unist-util-inspect": "^4.1.2", - "vfile-reporter": "^4.0.0", - "vfile-statistics": "^1.1.0", - "x-is-function": "^1.0.4", - "x-is-string": "^0.1.0", - "xtend": "^4.0.1" - }, - "dependencies": { - "debug": { - "version": "3.2.5", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.5.tgz", - "integrity": "sha512-D61LaDQPQkxJ5AUM2mbSJRbPkNs/TmdmOeLAi1hgDkpDfIfetSrjmWhccwtuResSwMbACjx/xXQofvM9CE/aeg==", - "requires": { - "ms": "^2.1.1" - } - }, - "ms": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", - "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==" - } - } - }, - "unified-lint-rule": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/unified-lint-rule/-/unified-lint-rule-1.0.3.tgz", - "integrity": "sha512-6z+HH3mtlFdj/w3MaQpObrZAd9KRiro370GxBFh13qkV8LYR21lLozA4iQiZPhe7KuX/lHewoGOEgQ4AWrAR3Q==", - "requires": { - "wrapped": "^1.0.1" - } - }, - "union-value": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.0.tgz", - "integrity": "sha1-XHHDTLW61dzr4+oM0IIHulqhrqQ=", - "requires": { - "arr-union": "^3.1.0", - "get-value": "^2.0.6", - "is-extendable": "^0.1.1", - "set-value": "^0.4.3" - }, - "dependencies": { - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "requires": { - "is-extendable": "^0.1.0" - } - }, - "set-value": { - "version": "0.4.3", - "resolved": "https://registry.npmjs.org/set-value/-/set-value-0.4.3.tgz", - "integrity": "sha1-fbCPnT0i3H945Trzw79GZuzfzPE=", - "requires": { - "extend-shallow": "^2.0.1", - "is-extendable": "^0.1.1", - "is-plain-object": "^2.0.1", - "to-object-path": "^0.3.0" - } - } - } - }, - "unique-concat": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/unique-concat/-/unique-concat-0.2.2.tgz", - "integrity": "sha1-khD5vcqsxeHjkpSQ18AZ35bxhxI=" - }, - "unist-util-inspect": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/unist-util-inspect/-/unist-util-inspect-4.1.3.tgz", - "integrity": "sha512-Fv9R88ZBbDp7mHN+wsbxS1r8VW3unyhZh/F18dcJRQsg0+g3DxNQnMS+AEG/uotB8Md+HMK/TfzSU5lUDWxkZg==", - "requires": { - "is-empty": "^1.0.0" - } - }, - "unist-util-is": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-2.1.2.tgz", - "integrity": "sha512-YkXBK/H9raAmG7KXck+UUpnKiNmUdB+aBGrknfQ4EreE1banuzrKABx3jP6Z5Z3fMSPMQQmeXBlKpCbMwBkxVw==" - }, - "unist-util-remove-position": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/unist-util-remove-position/-/unist-util-remove-position-1.1.2.tgz", - "integrity": "sha512-XxoNOBvq1WXRKXxgnSYbtCF76TJrRoe5++pD4cCBsssSiWSnPEktyFrFLE8LTk3JW5mt9hB0Sk5zn4x/JeWY7Q==", - "requires": { - "unist-util-visit": "^1.1.0" - } - }, - "unist-util-stringify-position": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-1.1.2.tgz", - "integrity": "sha512-pNCVrk64LZv1kElr0N1wPiHEUoXNVFERp+mlTg/s9R5Lwg87f9bM/3sQB99w+N9D/qnM9ar3+AKDBwo/gm/iQQ==" - }, - "unist-util-visit": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-1.4.0.tgz", - "integrity": "sha512-FiGu34ziNsZA3ZUteZxSFaczIjGmksfSgdKqBfOejrrfzyUy5b7YrlzT1Bcvi+djkYDituJDy2XB7tGTeBieKw==", - "requires": { - "unist-util-visit-parents": "^2.0.0" - } - }, - "unist-util-visit-parents": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-2.0.1.tgz", - "integrity": "sha512-6B0UTiMfdWql4cQ03gDTCSns+64Zkfo2OCbK31Ov0uMizEz+CJeAp0cgZVb5Fhmcd7Bct2iRNywejT0orpbqUA==", - "requires": { - "unist-util-is": "^2.1.2" - } - }, - "unset-value": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/unset-value/-/unset-value-1.0.0.tgz", - "integrity": "sha1-g3aHP30jNRef+x5vw6jtDfyKtVk=", - "requires": { - "has-value": "^0.3.1", - "isobject": "^3.0.0" - }, - "dependencies": { - "has-value": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/has-value/-/has-value-0.3.1.tgz", - "integrity": "sha1-ex9YutpiyoJ+wKIHgCVlSEWZXh8=", - "requires": { - "get-value": "^2.0.3", - "has-values": "^0.1.4", - "isobject": "^2.0.0" - }, - "dependencies": { - "isobject": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz", - "integrity": "sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=", - "requires": { - "isarray": "1.0.0" - } - } - } - }, - "has-values": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/has-values/-/has-values-0.1.4.tgz", - "integrity": "sha1-bWHeldkd/Km5oCCJrThL/49it3E=" - }, - "isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=" - } - } - }, - "untildify": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/untildify/-/untildify-2.1.0.tgz", - "integrity": "sha1-F+soB5h/dpUunASF/DEdBqgmouA=", - "requires": { - "os-homedir": "^1.0.0" - } - }, - "unzip-response": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/unzip-response/-/unzip-response-2.0.1.tgz", - "integrity": "sha1-0vD3N9FrBhXnKmk17QQhRXLVb5c=" - }, - "urix": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/urix/-/urix-0.1.0.tgz", - "integrity": "sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI=" - }, - "url-parse-lax": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/url-parse-lax/-/url-parse-lax-1.0.0.tgz", - "integrity": "sha1-evjzA2Rem9eaJy56FKxovAYJ2nM=", - "requires": { - "prepend-http": "^1.0.1" - } - }, - "url-to-options": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/url-to-options/-/url-to-options-1.0.1.tgz", - "integrity": "sha1-FQWgOiiaSMvXpDTvuu7FBV9WM6k=" - }, - "use": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/use/-/use-3.1.1.tgz", - "integrity": "sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==" - }, - "util-deprecate": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" - }, - "uuid": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz", - "integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==" - }, - "validate-npm-package-license": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", - "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", - "requires": { - "spdx-correct": "^3.0.0", - "spdx-expression-parse": "^3.0.0" - } - }, - "verror": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", - "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", - "requires": { - "assert-plus": "^1.0.0", - "core-util-is": "1.0.2", - "extsprintf": "^1.2.0" - } - }, - "vfile": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/vfile/-/vfile-2.3.0.tgz", - "integrity": "sha512-ASt4mBUHcTpMKD/l5Q+WJXNtshlWxOogYyGYYrg4lt/vuRjC1EFQtlAofL5VmtVNIZJzWYFJjzGWZ0Gw8pzW1w==", - "requires": { - "is-buffer": "^1.1.4", - "replace-ext": "1.0.0", - "unist-util-stringify-position": "^1.0.0", - "vfile-message": "^1.0.0" - } - }, - "vfile-location": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/vfile-location/-/vfile-location-2.0.3.tgz", - "integrity": "sha512-zM5/l4lfw1CBoPx3Jimxoc5RNDAHHpk6AM6LM0pTIkm5SUSsx8ZekZ0PVdf0WEZ7kjlhSt7ZlqbRL6Cd6dBs6A==" - }, - "vfile-message": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-1.0.1.tgz", - "integrity": "sha512-vSGCkhNvJzO6VcWC6AlJW4NtYOVtS+RgCaqFIYUjoGIlHnFL+i0LbtYvonDWOMcB97uTPT4PRsyYY7REWC9vug==", - "requires": { - "unist-util-stringify-position": "^1.1.1" - } - }, - "vfile-reporter": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/vfile-reporter/-/vfile-reporter-4.0.0.tgz", - "integrity": "sha1-6m8K4TQvSEFXOYXgX5QXNvJ96do=", - "requires": { - "repeat-string": "^1.5.0", - "string-width": "^1.0.0", - "supports-color": "^4.1.0", - "unist-util-stringify-position": "^1.0.0", - "vfile-statistics": "^1.1.0" - }, - "dependencies": { - "has-flag": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-2.0.0.tgz", - "integrity": "sha1-6CB68cx7MNRGzHC3NLXovhj4jVE=" - }, - "supports-color": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-4.5.0.tgz", - "integrity": "sha1-vnoN5ITexcXN34s9WRJQRJEvY1s=", - "requires": { - "has-flag": "^2.0.0" - } - } - } - }, - "vfile-statistics": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/vfile-statistics/-/vfile-statistics-1.1.1.tgz", - "integrity": "sha512-dxUM6IYvGChHuwMT3dseyU5BHprNRXzAV0OHx1A769lVGsTiT50kU7BbpRFV+IE6oWmU+PwHdsTKfXhnDIRIgQ==" - }, - "weasel-words": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/weasel-words/-/weasel-words-0.1.1.tgz", - "integrity": "sha1-cTeUZYXHP+RIggE4U70ADF1oek4=" - }, - "wordwrap": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", - "integrity": "sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus=" - }, - "wrapped": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/wrapped/-/wrapped-1.0.1.tgz", - "integrity": "sha1-x4PZ2Aeyc+mwHoUWgKk4yHyQckI=", - "requires": { - "co": "3.1.0", - "sliced": "^1.0.1" - }, - "dependencies": { - "co": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/co/-/co-3.1.0.tgz", - "integrity": "sha1-TqVOpaCJOBUxheFSEMaNkJK8G3g=" - } - } - }, - "wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" - }, - "write": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/write/-/write-0.2.1.tgz", - "integrity": "sha1-X8A4KOJkzqP+kUVUdvejxWbLB1c=", - "requires": { - "mkdirp": "^0.5.1" - } - }, - "write-good": { - "version": "0.11.3", - "resolved": "https://registry.npmjs.org/write-good/-/write-good-0.11.3.tgz", - "integrity": "sha512-fDKIHO5wCzTLCOGNJl1rzzJrZlTIzfZl8msOoJQZzRhYo0X/tFTm4+2B1zTibFYK01Nnd1kLZBjj4xjcFLePNQ==", - "requires": { - "adverb-where": "0.0.9", - "e-prime": "^0.10.2", - "no-cliches": "^0.1.0", - "object.assign": "^4.0.4", - "passive-voice": "^0.1.0", - "too-wordy": "^0.1.4", - "weasel-words": "^0.1.1" - } - }, - "x-is-function": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/x-is-function/-/x-is-function-1.0.4.tgz", - "integrity": "sha1-XSlNw9Joy90GJYDgxd93o5HR+h4=" - }, - "x-is-string": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/x-is-string/-/x-is-string-0.1.0.tgz", - "integrity": "sha1-R0tQhlrzpJqcRlfwWs0UVFj3fYI=" - }, - "xml-escape": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/xml-escape/-/xml-escape-1.1.0.tgz", - "integrity": "sha1-OQTBQ/qOs6ADDsZG0pAqLxtwbEQ=" - }, - "xtend": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz", - "integrity": "sha1-pcbVMr5lbiPbgg77lDofBJmNY68=" - } - } -} diff --git a/docs/package.json b/docs/package.json deleted file mode 100644 index d45ba539..00000000 --- a/docs/package.json +++ /dev/null @@ -1,40 +0,0 @@ -{ - "dependencies": { - "prettier": "^1.13.7", - "remark-cli": "^5.0.0", - "remark-lint-no-dead-urls": "^0.3.0", - "remark-lint-write-good": "^1.0.3", - "textlint": "^10.2.1", - "textlint-rule-stop-words": "^1.0.3" - }, - "name": "tendermint", - "description": "Tendermint Core Documentation", - "version": "0.0.1", - "main": "README.md", - "devDependencies": {}, - "scripts": { - "lint:json": "prettier \"**/*.json\" --write", - "lint:md": "prettier \"**/*.md\" --write && remark . && textlint \"md/**\"", - "lint": "yarn lint:json && yarn lint:md" - }, - "repository": { - "type": "git", - "url": "git+https://github.com/tendermint/tendermint.git" - }, - "keywords": [ - "tendermint", - "blockchain" - ], - "author": "Tendermint", - "license": "ISC", - "bugs": { - "url": "https://github.com/tendermint/tendermint/issues" - }, - "homepage": "https://tendermint.com/docs/", - "remarkConfig": { - "plugins": [ - "remark-lint-no-dead-urls", - "remark-lint-write-good" - ] - } -} diff --git a/docs/yarn.lock b/docs/yarn.lock deleted file mode 100644 index 4f453ed4..00000000 --- a/docs/yarn.lock +++ /dev/null @@ -1,2611 +0,0 @@ -# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. -# yarn lockfile v1 - - -"@azu/format-text@^1.0.1": - version "1.0.1" - resolved "https://registry.yarnpkg.com/@azu/format-text/-/format-text-1.0.1.tgz#6967350a94640f6b02855169bd897ce54d6cebe2" - -"@azu/style-format@^1.0.0": - version "1.0.0" - resolved "https://registry.yarnpkg.com/@azu/style-format/-/style-format-1.0.0.tgz#e70187f8a862e191b1bce6c0268f13acd3a56b20" - dependencies: - "@azu/format-text" "^1.0.1" - -"@sindresorhus/is@^0.7.0": - version "0.7.0" - resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-0.7.0.tgz#9a06f4f137ee84d7df0460c1fdb1135ffa6c50fd" - -"@textlint/ast-node-types@^4.0.2", "@textlint/ast-node-types@^4.0.3": - version "4.0.3" - resolved "https://registry.yarnpkg.com/@textlint/ast-node-types/-/ast-node-types-4.0.3.tgz#b51c87bb86022323f764fbdc976b173f19261cc5" - -"@textlint/ast-traverse@^2.0.8", "@textlint/ast-traverse@^2.0.9": - version "2.0.9" - resolved "https://registry.yarnpkg.com/@textlint/ast-traverse/-/ast-traverse-2.0.9.tgz#4bf427cf01b7195013e75d27540a77ad68c363d9" - dependencies: - "@textlint/ast-node-types" "^4.0.3" - -"@textlint/feature-flag@^3.0.4", "@textlint/feature-flag@^3.0.5": - version "3.0.5" - resolved "https://registry.yarnpkg.com/@textlint/feature-flag/-/feature-flag-3.0.5.tgz#3783e0f2661053d2a74fdad775993395a2d530b4" - dependencies: - map-like "^2.0.0" - -"@textlint/fixer-formatter@^3.0.7": - version "3.0.8" - resolved "https://registry.yarnpkg.com/@textlint/fixer-formatter/-/fixer-formatter-3.0.8.tgz#90ef804c60b9e694c8c048a06febbf1f331abd49" - dependencies: - "@textlint/kernel" "^3.0.0" - chalk "^1.1.3" - debug "^2.1.0" - diff "^2.2.2" - interop-require "^1.0.0" - is-file "^1.0.0" - string-width "^1.0.1" - text-table "^0.2.0" - try-resolve "^1.0.1" - -"@textlint/kernel@^2.0.9": - version "2.0.9" - resolved "https://registry.yarnpkg.com/@textlint/kernel/-/kernel-2.0.9.tgz#a4471b7969e192551230c35ea9fae32d80128ee0" - dependencies: - "@textlint/ast-node-types" "^4.0.2" - "@textlint/ast-traverse" "^2.0.8" - "@textlint/feature-flag" "^3.0.4" - "@types/bluebird" "^3.5.18" - bluebird "^3.5.1" - debug "^2.6.6" - deep-equal "^1.0.1" - object-assign "^4.1.1" - structured-source "^3.0.2" - -"@textlint/kernel@^3.0.0": - version "3.0.0" - resolved "https://registry.yarnpkg.com/@textlint/kernel/-/kernel-3.0.0.tgz#ba10962acff64f17b9e5fce8089a40f1f8880dcd" - dependencies: - "@textlint/ast-node-types" "^4.0.3" - "@textlint/ast-traverse" "^2.0.9" - "@textlint/feature-flag" "^3.0.5" - "@types/bluebird" "^3.5.18" - bluebird "^3.5.1" - debug "^2.6.6" - deep-equal "^1.0.1" - map-like "^2.0.0" - object-assign "^4.1.1" - structured-source "^3.0.2" - -"@textlint/linter-formatter@^3.0.7": - version "3.0.8" - resolved "https://registry.yarnpkg.com/@textlint/linter-formatter/-/linter-formatter-3.0.8.tgz#030aa03ff3d85dda94ca9fa9e6bf824f9c1cb7ef" - dependencies: - "@azu/format-text" "^1.0.1" - "@azu/style-format" "^1.0.0" - "@textlint/kernel" "^3.0.0" - chalk "^1.0.0" - concat-stream "^1.5.1" - js-yaml "^3.2.4" - optionator "^0.8.1" - pluralize "^2.0.0" - string-width "^1.0.1" - string.prototype.padstart "^3.0.0" - strip-ansi "^3.0.1" - table "^3.7.8" - text-table "^0.2.0" - try-resolve "^1.0.1" - xml-escape "^1.0.0" - -"@textlint/markdown-to-ast@^6.0.8": - version "6.0.9" - resolved "https://registry.yarnpkg.com/@textlint/markdown-to-ast/-/markdown-to-ast-6.0.9.tgz#e7c89e5ad15d17dcd8e5a62758358936827658fa" - dependencies: - "@textlint/ast-node-types" "^4.0.3" - debug "^2.1.3" - remark-frontmatter "^1.2.0" - remark-parse "^5.0.0" - structured-source "^3.0.2" - traverse "^0.6.6" - unified "^6.1.6" - -"@textlint/text-to-ast@^3.0.8": - version "3.0.9" - resolved "https://registry.yarnpkg.com/@textlint/text-to-ast/-/text-to-ast-3.0.9.tgz#dcb63f09cc79ea2096fc823c3b6cd07c79a060b5" - dependencies: - "@textlint/ast-node-types" "^4.0.3" - -"@textlint/textlint-plugin-markdown@^4.0.10": - version "4.0.10" - resolved "https://registry.yarnpkg.com/@textlint/textlint-plugin-markdown/-/textlint-plugin-markdown-4.0.10.tgz#a99b4a308067597e89439a9e87bc1c4a7f4d076b" - dependencies: - "@textlint/markdown-to-ast" "^6.0.8" - -"@textlint/textlint-plugin-text@^3.0.10": - version "3.0.10" - resolved "https://registry.yarnpkg.com/@textlint/textlint-plugin-text/-/textlint-plugin-text-3.0.10.tgz#619600bdc352d33a68e7a73d77d58b0c52b2a44f" - dependencies: - "@textlint/text-to-ast" "^3.0.8" - -"@types/bluebird@^3.5.18": - version "3.5.23" - resolved "https://registry.yarnpkg.com/@types/bluebird/-/bluebird-3.5.23.tgz#e805da976b76892b2b2e50eec29e84914c730670" - -abbrev@1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.1.1.tgz#f8f2c887ad10bf67f634f005b6987fed3179aac8" - -adverb-where@0.0.9: - version "0.0.9" - resolved "https://registry.yarnpkg.com/adverb-where/-/adverb-where-0.0.9.tgz#09c5cddd8d503b9fe5f76e0b8dc5c70a8f193e34" - -aggregate-error@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/aggregate-error/-/aggregate-error-1.0.0.tgz#888344dad0220a72e3af50906117f48771925fac" - dependencies: - clean-stack "^1.0.0" - indent-string "^3.0.0" - -ajv-keywords@^1.0.0: - version "1.5.1" - resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-1.5.1.tgz#314dd0a4b3368fad3dfcdc54ede6171b886daf3c" - -ajv@^4.7.0: - version "4.11.8" - resolved "https://registry.yarnpkg.com/ajv/-/ajv-4.11.8.tgz#82ffb02b29e662ae53bdc20af15947706739c536" - dependencies: - co "^4.6.0" - json-stable-stringify "^1.0.1" - -ajv@^5.1.0: - version "5.5.2" - resolved "https://registry.yarnpkg.com/ajv/-/ajv-5.5.2.tgz#73b5eeca3fab653e3d3f9422b341ad42205dc965" - dependencies: - co "^4.6.0" - fast-deep-equal "^1.0.0" - fast-json-stable-stringify "^2.0.0" - json-schema-traverse "^0.3.0" - -ansi-regex@^2.0.0: - version "2.1.1" - resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-2.1.1.tgz#c3b33ab5ee360d86e0e628f0468ae7ef27d654df" - -ansi-regex@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-3.0.0.tgz#ed0317c322064f79466c02966bddb605ab37d998" - -ansi-styles@^2.2.1: - version "2.2.1" - resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-2.2.1.tgz#b432dd3358b634cf75e1e4664368240533c1ddbe" - -ansi-styles@^3.2.1: - version "3.2.1" - resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d" - dependencies: - color-convert "^1.9.0" - -anymatch@^1.3.0: - version "1.3.2" - resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-1.3.2.tgz#553dcb8f91e3c889845dfdba34c77721b90b9d7a" - dependencies: - micromatch "^2.1.5" - normalize-path "^2.0.0" - -aproba@^1.0.3: - version "1.2.0" - resolved "https://registry.yarnpkg.com/aproba/-/aproba-1.2.0.tgz#6802e6264efd18c790a1b0d517f0f2627bf2c94a" - -are-we-there-yet@~1.1.2: - version "1.1.5" - resolved "https://registry.yarnpkg.com/are-we-there-yet/-/are-we-there-yet-1.1.5.tgz#4b35c2944f062a8bfcda66410760350fe9ddfc21" - dependencies: - delegates "^1.0.0" - readable-stream "^2.0.6" - -argparse@^1.0.7: - version "1.0.10" - resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911" - dependencies: - sprintf-js "~1.0.2" - -arr-diff@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/arr-diff/-/arr-diff-2.0.0.tgz#8f3b827f955a8bd669697e4a4256ac3ceae356cf" - dependencies: - arr-flatten "^1.0.1" - -arr-flatten@^1.0.1: - version "1.1.0" - resolved "https://registry.yarnpkg.com/arr-flatten/-/arr-flatten-1.1.0.tgz#36048bbff4e7b47e136644316c99669ea5ae91f1" - -array-iterate@^1.0.0: - version "1.1.2" - resolved "https://registry.yarnpkg.com/array-iterate/-/array-iterate-1.1.2.tgz#f66a57e84426f8097f4197fbb6c051b8e5cdf7d8" - -array-union@^1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/array-union/-/array-union-1.0.2.tgz#9a34410e4f4e3da23dea375be5be70f24778ec39" - dependencies: - array-uniq "^1.0.1" - -array-uniq@^1.0.1: - version "1.0.3" - resolved "https://registry.yarnpkg.com/array-uniq/-/array-uniq-1.0.3.tgz#af6ac877a25cc7f74e058894753858dfdb24fdb6" - -array-unique@^0.2.1: - version "0.2.1" - resolved "https://registry.yarnpkg.com/array-unique/-/array-unique-0.2.1.tgz#a1d97ccafcbc2625cc70fadceb36a50c58b01a53" - -arrify@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/arrify/-/arrify-1.0.1.tgz#898508da2226f380df904728456849c1501a4b0d" - -asn1@~0.2.3: - version "0.2.4" - resolved "https://registry.yarnpkg.com/asn1/-/asn1-0.2.4.tgz#8d2475dfab553bb33e77b54e59e880bb8ce23136" - dependencies: - safer-buffer "~2.1.0" - -assert-plus@1.0.0, assert-plus@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/assert-plus/-/assert-plus-1.0.0.tgz#f12e0f3c5d77b0b1cdd9146942e4e96c1e4dd525" - -async-each@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/async-each/-/async-each-1.0.1.tgz#19d386a1d9edc6e7c1c85d388aedbcc56d33602d" - -asynckit@^0.4.0: - version "0.4.0" - resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" - -aws-sign2@~0.7.0: - version "0.7.0" - resolved "https://registry.yarnpkg.com/aws-sign2/-/aws-sign2-0.7.0.tgz#b46e890934a9591f2d2f6f86d7e6a9f1b3fe76a8" - -aws4@^1.6.0: - version "1.8.0" - resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.8.0.tgz#f0e003d9ca9e7f59c7a508945d7b2ef9a04a542f" - -bail@^1.0.0: - version "1.0.3" - resolved "https://registry.yarnpkg.com/bail/-/bail-1.0.3.tgz#63cfb9ddbac829b02a3128cd53224be78e6c21a3" - -balanced-match@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767" - -bcrypt-pbkdf@^1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz#a4301d389b6a43f9b67ff3ca11a3f6637e360e9e" - dependencies: - tweetnacl "^0.14.3" - -binary-extensions@^1.0.0: - version "1.11.0" - resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-1.11.0.tgz#46aa1751fb6a2f93ee5e689bb1087d4b14c6c205" - -bluebird@^3.0.5, bluebird@^3.5.1: - version "3.5.1" - resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.5.1.tgz#d9551f9de98f1fcda1e683d17ee91a0602ee2eb9" - -boundary@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/boundary/-/boundary-1.0.1.tgz#4d67dc2602c0cc16dd9bce7ebf87e948290f5812" - -brace-expansion@^1.1.7: - version "1.1.11" - resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" - dependencies: - balanced-match "^1.0.0" - concat-map "0.0.1" - -braces@^1.8.2: - version "1.8.5" - resolved "https://registry.yarnpkg.com/braces/-/braces-1.8.5.tgz#ba77962e12dff969d6b76711e914b737857bf6a7" - dependencies: - expand-range "^1.8.1" - preserve "^0.2.0" - repeat-element "^1.1.2" - -buffer-from@^1.0.0: - version "1.1.1" - resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.1.tgz#32713bc028f75c02fdb710d7c7bcec1f2c6070ef" - -builtin-modules@^1.0.0: - version "1.1.1" - resolved "https://registry.yarnpkg.com/builtin-modules/-/builtin-modules-1.1.1.tgz#270f076c5a72c02f5b65a47df94c5fe3a278892f" - -cacheable-request@^2.1.1: - version "2.1.4" - resolved "https://registry.yarnpkg.com/cacheable-request/-/cacheable-request-2.1.4.tgz#0d808801b6342ad33c91df9d0b44dc09b91e5c3d" - dependencies: - clone-response "1.0.2" - get-stream "3.0.0" - http-cache-semantics "3.8.1" - keyv "3.0.0" - lowercase-keys "1.0.0" - normalize-url "2.0.1" - responselike "1.0.2" - -camelcase@^4.0.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-4.1.0.tgz#d545635be1e33c542649c69173e5de6acfae34dd" - -capture-stack-trace@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/capture-stack-trace/-/capture-stack-trace-1.0.0.tgz#4a6fa07399c26bba47f0b2496b4d0fb408c5550d" - -caseless@~0.12.0: - version "0.12.0" - resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc" - -ccount@^1.0.0: - version "1.0.3" - resolved "https://registry.yarnpkg.com/ccount/-/ccount-1.0.3.tgz#f1cec43f332e2ea5a569fd46f9f5bde4e6102aff" - -chalk@^1.0.0, chalk@^1.1.1, chalk@^1.1.3: - version "1.1.3" - resolved "https://registry.yarnpkg.com/chalk/-/chalk-1.1.3.tgz#a8115c55e4a702fe4d150abd3872822a7e09fc98" - dependencies: - ansi-styles "^2.2.1" - escape-string-regexp "^1.0.2" - has-ansi "^2.0.0" - strip-ansi "^3.0.0" - supports-color "^2.0.0" - -chalk@^2.0.0: - version "2.4.1" - resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.1.tgz#18c49ab16a037b6eb0152cc83e3471338215b66e" - dependencies: - ansi-styles "^3.2.1" - escape-string-regexp "^1.0.5" - supports-color "^5.3.0" - -character-entities-html4@^1.0.0: - version "1.1.2" - resolved "https://registry.yarnpkg.com/character-entities-html4/-/character-entities-html4-1.1.2.tgz#c44fdde3ce66b52e8d321d6c1bf46101f0150610" - -character-entities-legacy@^1.0.0: - version "1.1.2" - resolved "https://registry.yarnpkg.com/character-entities-legacy/-/character-entities-legacy-1.1.2.tgz#7c6defb81648498222c9855309953d05f4d63a9c" - -character-entities@^1.0.0: - version "1.2.2" - resolved "https://registry.yarnpkg.com/character-entities/-/character-entities-1.2.2.tgz#58c8f371c0774ef0ba9b2aca5f00d8f100e6e363" - -character-reference-invalid@^1.0.0: - version "1.1.2" - resolved "https://registry.yarnpkg.com/character-reference-invalid/-/character-reference-invalid-1.1.2.tgz#21e421ad3d84055952dab4a43a04e73cd425d3ed" - -charenc@~0.0.1: - version "0.0.2" - resolved "https://registry.yarnpkg.com/charenc/-/charenc-0.0.2.tgz#c0a1d2f3a7092e03774bfa83f14c0fc5790a8667" - -chokidar@^1.5.1: - version "1.7.0" - resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-1.7.0.tgz#798e689778151c8076b4b360e5edd28cda2bb468" - dependencies: - anymatch "^1.3.0" - async-each "^1.0.0" - glob-parent "^2.0.0" - inherits "^2.0.1" - is-binary-path "^1.0.0" - is-glob "^2.0.0" - path-is-absolute "^1.0.0" - readdirp "^2.0.0" - optionalDependencies: - fsevents "^1.0.0" - -chownr@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.0.1.tgz#e2a75042a9551908bebd25b8523d5f9769d79181" - -circular-json@^0.3.1: - version "0.3.3" - resolved "https://registry.yarnpkg.com/circular-json/-/circular-json-0.3.3.tgz#815c99ea84f6809529d2f45791bdf82711352d66" - -clean-stack@^1.0.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/clean-stack/-/clean-stack-1.3.0.tgz#9e821501ae979986c46b1d66d2d432db2fd4ae31" - -clone-response@1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/clone-response/-/clone-response-1.0.2.tgz#d1dc973920314df67fbeb94223b4ee350239e96b" - dependencies: - mimic-response "^1.0.0" - -co@3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/co/-/co-3.1.0.tgz#4ea54ea5a08938153185e15210c68d9092bc1b78" - -co@^4.6.0: - version "4.6.0" - resolved "https://registry.yarnpkg.com/co/-/co-4.6.0.tgz#6ea6bdf3d853ae54ccb8e47bfa0bf3f9031fb184" - -code-point-at@^1.0.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/code-point-at/-/code-point-at-1.1.0.tgz#0d070b4d043a5bea33a2f1a40e2edb3d9a4ccf77" - -collapse-white-space@^1.0.2: - version "1.0.4" - resolved "https://registry.yarnpkg.com/collapse-white-space/-/collapse-white-space-1.0.4.tgz#ce05cf49e54c3277ae573036a26851ba430a0091" - -color-convert@^1.9.0: - version "1.9.2" - resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.2.tgz#49881b8fba67df12a96bdf3f56c0aab9e7913147" - dependencies: - color-name "1.1.1" - -color-name@1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.1.tgz#4b1415304cf50028ea81643643bd82ea05803689" - -combined-stream@1.0.6, combined-stream@~1.0.5: - version "1.0.6" - resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.6.tgz#723e7df6e801ac5613113a7e445a9b69cb632818" - dependencies: - delayed-stream "~1.0.0" - -concat-map@0.0.1: - version "0.0.1" - resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" - -concat-stream@^1.5.1: - version "1.6.2" - resolved "https://registry.yarnpkg.com/concat-stream/-/concat-stream-1.6.2.tgz#904bdf194cd3122fc675c77fc4ac3d4ff0fd1a34" - dependencies: - buffer-from "^1.0.0" - inherits "^2.0.3" - readable-stream "^2.2.2" - typedarray "^0.0.6" - -console-control-strings@^1.0.0, console-control-strings@~1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/console-control-strings/-/console-control-strings-1.1.0.tgz#3d7cf4464db6446ea644bf4b39507f9851008e8e" - -core-util-is@1.0.2, core-util-is@~1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" - -create-error-class@^3.0.0: - version "3.0.2" - resolved "https://registry.yarnpkg.com/create-error-class/-/create-error-class-3.0.2.tgz#06be7abef947a3f14a30fd610671d401bca8b7b6" - dependencies: - capture-stack-trace "^1.0.0" - -crypt@~0.0.1: - version "0.0.2" - resolved "https://registry.yarnpkg.com/crypt/-/crypt-0.0.2.tgz#88d7ff7ec0dfb86f713dc87bbb42d044d3e6c41b" - -dashdash@^1.12.0: - version "1.14.1" - resolved "https://registry.yarnpkg.com/dashdash/-/dashdash-1.14.1.tgz#853cfa0f7cbe2fed5de20326b8dd581035f6e2f0" - dependencies: - assert-plus "^1.0.0" - -debug@^2.1.0, debug@^2.1.2, debug@^2.1.3, debug@^2.6.6: - version "2.6.9" - resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" - dependencies: - ms "2.0.0" - -debug@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/debug/-/debug-3.1.0.tgz#5bb5a0672628b64149566ba16819e61518c67261" - dependencies: - ms "2.0.0" - -decode-uri-component@^0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/decode-uri-component/-/decode-uri-component-0.2.0.tgz#eb3913333458775cb84cd1a1fae062106bb87545" - -decompress-response@^3.3.0: - version "3.3.0" - resolved "https://registry.yarnpkg.com/decompress-response/-/decompress-response-3.3.0.tgz#80a4dd323748384bfa248083622aedec982adff3" - dependencies: - mimic-response "^1.0.0" - -deep-equal@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/deep-equal/-/deep-equal-1.0.1.tgz#f5d260292b660e084eff4cdbc9f08ad3247448b5" - -deep-extend@^0.6.0: - version "0.6.0" - resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.6.0.tgz#c4fa7c95404a17a9c3e8ca7e1537312b736330ac" - -deep-is@~0.1.3: - version "0.1.3" - resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.3.tgz#b369d6fb5dbc13eecf524f91b070feedc357cf34" - -define-properties@^1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.1.2.tgz#83a73f2fea569898fb737193c8f873caf6d45c94" - dependencies: - foreach "^2.0.5" - object-keys "^1.0.8" - -del@^2.0.2: - version "2.2.2" - resolved "https://registry.yarnpkg.com/del/-/del-2.2.2.tgz#c12c981d067846c84bcaf862cff930d907ffd1a8" - dependencies: - globby "^5.0.0" - is-path-cwd "^1.0.0" - is-path-in-cwd "^1.0.0" - object-assign "^4.0.1" - pify "^2.0.0" - pinkie-promise "^2.0.0" - rimraf "^2.2.8" - -delayed-stream@~1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" - -delegates@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/delegates/-/delegates-1.0.0.tgz#84c6e159b81904fdca59a0ef44cd870d31250f9a" - -detect-libc@^1.0.2: - version "1.0.3" - resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-1.0.3.tgz#fa137c4bd698edf55cd5cd02ac559f91a4c4ba9b" - -diff@^2.2.2: - version "2.2.3" - resolved "https://registry.yarnpkg.com/diff/-/diff-2.2.3.tgz#60eafd0d28ee906e4e8ff0a52c1229521033bf99" - -dns-packet@^1.1.0: - version "1.3.1" - resolved "https://registry.yarnpkg.com/dns-packet/-/dns-packet-1.3.1.tgz#12aa426981075be500b910eedcd0b47dd7deda5a" - dependencies: - ip "^1.1.0" - safe-buffer "^5.0.1" - -dns-socket@^1.6.2: - version "1.6.3" - resolved "https://registry.yarnpkg.com/dns-socket/-/dns-socket-1.6.3.tgz#5268724fad4aa46ad9c5ca4ffcd16e1de5342aab" - dependencies: - dns-packet "^1.1.0" - -duplexer3@^0.1.4: - version "0.1.4" - resolved "https://registry.yarnpkg.com/duplexer3/-/duplexer3-0.1.4.tgz#ee01dd1cac0ed3cbc7fdbea37dc0a8f1ce002ce2" - -e-prime@^0.10.2: - version "0.10.2" - resolved "https://registry.yarnpkg.com/e-prime/-/e-prime-0.10.2.tgz#ea9375eb985636de88013c7a9fb129ad9e15eff8" - -ecc-jsbn@~0.1.1: - version "0.1.2" - resolved "https://registry.yarnpkg.com/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz#3a83a904e54353287874c564b7549386849a98c9" - dependencies: - jsbn "~0.1.0" - safer-buffer "^2.1.0" - -error-ex@^1.2.0, error-ex@^1.3.1: - version "1.3.2" - resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.2.tgz#b4ac40648107fdcdcfae242f428bea8a14d4f1bf" - dependencies: - is-arrayish "^0.2.1" - -es-abstract@^1.4.3: - version "1.12.0" - resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.12.0.tgz#9dbbdd27c6856f0001421ca18782d786bf8a6165" - dependencies: - es-to-primitive "^1.1.1" - function-bind "^1.1.1" - has "^1.0.1" - is-callable "^1.1.3" - is-regex "^1.0.4" - -es-to-primitive@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/es-to-primitive/-/es-to-primitive-1.1.1.tgz#45355248a88979034b6792e19bb81f2b7975dd0d" - dependencies: - is-callable "^1.1.1" - is-date-object "^1.0.1" - is-symbol "^1.0.1" - -escape-string-regexp@^1.0.2, escape-string-regexp@^1.0.5: - version "1.0.5" - resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" - -esprima@^4.0.0: - version "4.0.1" - resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" - -expand-brackets@^0.1.4: - version "0.1.5" - resolved "https://registry.yarnpkg.com/expand-brackets/-/expand-brackets-0.1.5.tgz#df07284e342a807cd733ac5af72411e581d1177b" - dependencies: - is-posix-bracket "^0.1.0" - -expand-range@^1.8.1: - version "1.8.2" - resolved "https://registry.yarnpkg.com/expand-range/-/expand-range-1.8.2.tgz#a299effd335fe2721ebae8e257ec79644fc85337" - dependencies: - fill-range "^2.1.0" - -extend@^3.0.0, extend@~3.0.1: - version "3.0.2" - resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa" - -extglob@^0.3.1: - version "0.3.2" - resolved "https://registry.yarnpkg.com/extglob/-/extglob-0.3.2.tgz#2e18ff3d2f49ab2765cec9023f011daa8d8349a1" - dependencies: - is-extglob "^1.0.0" - -extsprintf@1.3.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.3.0.tgz#96918440e3041a7a414f8c52e3c574eb3c3e1e05" - -extsprintf@^1.2.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.4.0.tgz#e2689f8f356fad62cca65a3a91c5df5f9551692f" - -fast-deep-equal@^1.0.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-1.1.0.tgz#c053477817c86b51daa853c81e059b733d023614" - -fast-json-stable-stringify@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz#d5142c0caee6b1189f87d3a76111064f86c8bbf2" - -fast-levenshtein@~2.0.4: - version "2.0.6" - resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" - -fault@^1.0.0, fault@^1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/fault/-/fault-1.0.2.tgz#c3d0fec202f172a3a4d414042ad2bb5e2a3ffbaa" - dependencies: - format "^0.2.2" - -file-entry-cache@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-2.0.0.tgz#c392990c3e684783d838b8c84a45d8a048458361" - dependencies: - flat-cache "^1.2.1" - object-assign "^4.0.1" - -filename-regex@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/filename-regex/-/filename-regex-2.0.1.tgz#c1c4b9bee3e09725ddb106b75c1e301fe2f18b26" - -fill-range@^2.1.0: - version "2.2.4" - resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-2.2.4.tgz#eb1e773abb056dcd8df2bfdf6af59b8b3a936565" - dependencies: - is-number "^2.1.0" - isobject "^2.0.0" - randomatic "^3.0.0" - repeat-element "^1.1.2" - repeat-string "^1.5.2" - -find-up@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/find-up/-/find-up-2.1.0.tgz#45d1b7e506c717ddd482775a2b77920a3c0c57a7" - dependencies: - locate-path "^2.0.0" - -flat-cache@^1.2.1: - version "1.3.0" - resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-1.3.0.tgz#d3030b32b38154f4e3b7e9c709f490f7ef97c481" - dependencies: - circular-json "^0.3.1" - del "^2.0.2" - graceful-fs "^4.1.2" - write "^0.2.1" - -fn-name@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/fn-name/-/fn-name-2.0.1.tgz#5214d7537a4d06a4a301c0cc262feb84188002e7" - -for-in@^1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/for-in/-/for-in-1.0.2.tgz#81068d295a8142ec0ac726c6e2200c30fb6d5e80" - -for-own@^0.1.4: - version "0.1.5" - resolved "https://registry.yarnpkg.com/for-own/-/for-own-0.1.5.tgz#5265c681a4f294dabbf17c9509b6763aa84510ce" - dependencies: - for-in "^1.0.1" - -foreach@^2.0.5: - version "2.0.5" - resolved "https://registry.yarnpkg.com/foreach/-/foreach-2.0.5.tgz#0bee005018aeb260d0a3af3ae658dd0136ec1b99" - -forever-agent@~0.6.1: - version "0.6.1" - resolved "https://registry.yarnpkg.com/forever-agent/-/forever-agent-0.6.1.tgz#fbc71f0c41adeb37f96c577ad1ed42d8fdacca91" - -form-data@~2.3.1: - version "2.3.2" - resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.3.2.tgz#4970498be604c20c005d4f5c23aecd21d6b49099" - dependencies: - asynckit "^0.4.0" - combined-stream "1.0.6" - mime-types "^2.1.12" - -format@^0.2.2: - version "0.2.2" - resolved "https://registry.yarnpkg.com/format/-/format-0.2.2.tgz#d6170107e9efdc4ed30c9dc39016df942b5cb58b" - -from2@^2.1.1: - version "2.3.0" - resolved "https://registry.yarnpkg.com/from2/-/from2-2.3.0.tgz#8bfb5502bde4a4d36cfdeea007fcca21d7e382af" - dependencies: - inherits "^2.0.1" - readable-stream "^2.0.0" - -fs-minipass@^1.2.5: - version "1.2.5" - resolved "https://registry.yarnpkg.com/fs-minipass/-/fs-minipass-1.2.5.tgz#06c277218454ec288df77ada54a03b8702aacb9d" - dependencies: - minipass "^2.2.1" - -fs.realpath@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" - -fsevents@^1.0.0: - version "1.2.4" - resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-1.2.4.tgz#f41dcb1af2582af3692da36fc55cbd8e1041c426" - dependencies: - nan "^2.9.2" - node-pre-gyp "^0.10.0" - -function-bind@^1.0.2, function-bind@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d" - -gauge@~2.7.3: - version "2.7.4" - resolved "https://registry.yarnpkg.com/gauge/-/gauge-2.7.4.tgz#2c03405c7538c39d7eb37b317022e325fb018bf7" - dependencies: - aproba "^1.0.3" - console-control-strings "^1.0.0" - has-unicode "^2.0.0" - object-assign "^4.1.0" - signal-exit "^3.0.0" - string-width "^1.0.1" - strip-ansi "^3.0.1" - wide-align "^1.1.0" - -get-stdin@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/get-stdin/-/get-stdin-5.0.1.tgz#122e161591e21ff4c52530305693f20e6393a398" - -get-stream@3.0.0, get-stream@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-3.0.0.tgz#8e943d1358dc37555054ecbe2edb05aa174ede14" - -getpass@^0.1.1: - version "0.1.7" - resolved "https://registry.yarnpkg.com/getpass/-/getpass-0.1.7.tgz#5eff8e3e684d569ae4cb2b1282604e8ba62149fa" - dependencies: - assert-plus "^1.0.0" - -glob-base@^0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/glob-base/-/glob-base-0.3.0.tgz#dbb164f6221b1c0b1ccf82aea328b497df0ea3c4" - dependencies: - glob-parent "^2.0.0" - is-glob "^2.0.0" - -glob-parent@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-2.0.0.tgz#81383d72db054fcccf5336daa902f182f6edbb28" - dependencies: - is-glob "^2.0.0" - -glob@^7.0.3, glob@^7.0.5, glob@^7.1.1: - version "7.1.2" - resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.2.tgz#c19c9df9a028702d678612384a6552404c636d15" - dependencies: - fs.realpath "^1.0.0" - inflight "^1.0.4" - inherits "2" - minimatch "^3.0.4" - once "^1.3.0" - path-is-absolute "^1.0.0" - -globby@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/globby/-/globby-5.0.0.tgz#ebd84667ca0dbb330b99bcfc68eac2bc54370e0d" - dependencies: - array-union "^1.0.1" - arrify "^1.0.0" - glob "^7.0.3" - object-assign "^4.0.1" - pify "^2.0.0" - pinkie-promise "^2.0.0" - -got@^6.7.1: - version "6.7.1" - resolved "https://registry.yarnpkg.com/got/-/got-6.7.1.tgz#240cd05785a9a18e561dc1b44b41c763ef1e8db0" - dependencies: - create-error-class "^3.0.0" - duplexer3 "^0.1.4" - get-stream "^3.0.0" - is-redirect "^1.0.0" - is-retry-allowed "^1.0.0" - is-stream "^1.0.0" - lowercase-keys "^1.0.0" - safe-buffer "^5.0.1" - timed-out "^4.0.0" - unzip-response "^2.0.1" - url-parse-lax "^1.0.0" - -got@^8.0.0: - version "8.3.2" - resolved "https://registry.yarnpkg.com/got/-/got-8.3.2.tgz#1d23f64390e97f776cac52e5b936e5f514d2e937" - dependencies: - "@sindresorhus/is" "^0.7.0" - cacheable-request "^2.1.1" - decompress-response "^3.3.0" - duplexer3 "^0.1.4" - get-stream "^3.0.0" - into-stream "^3.1.0" - is-retry-allowed "^1.1.0" - isurl "^1.0.0-alpha5" - lowercase-keys "^1.0.0" - mimic-response "^1.0.0" - p-cancelable "^0.4.0" - p-timeout "^2.0.1" - pify "^3.0.0" - safe-buffer "^5.1.1" - timed-out "^4.0.1" - url-parse-lax "^3.0.0" - url-to-options "^1.0.1" - -graceful-fs@^4.1.2: - version "4.1.11" - resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.1.11.tgz#0e8bdfe4d1ddb8854d64e04ea7c00e2a026e5658" - -har-schema@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/har-schema/-/har-schema-2.0.0.tgz#a94c2224ebcac04782a0d9035521f24735b7ec92" - -har-validator@~5.0.3: - version "5.0.3" - resolved "https://registry.yarnpkg.com/har-validator/-/har-validator-5.0.3.tgz#ba402c266194f15956ef15e0fcf242993f6a7dfd" - dependencies: - ajv "^5.1.0" - har-schema "^2.0.0" - -has-ansi@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/has-ansi/-/has-ansi-2.0.0.tgz#34f5049ce1ecdf2b0649af3ef24e45ed35416d91" - dependencies: - ansi-regex "^2.0.0" - -has-flag@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-2.0.0.tgz#e8207af1cc7b30d446cc70b734b5e8be18f88d51" - -has-flag@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" - -has-symbol-support-x@^1.4.1: - version "1.4.2" - resolved "https://registry.yarnpkg.com/has-symbol-support-x/-/has-symbol-support-x-1.4.2.tgz#1409f98bc00247da45da67cee0a36f282ff26455" - -has-symbols@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.0.tgz#ba1a8f1af2a0fc39650f5c850367704122063b44" - -has-to-string-tag-x@^1.2.0: - version "1.4.1" - resolved "https://registry.yarnpkg.com/has-to-string-tag-x/-/has-to-string-tag-x-1.4.1.tgz#a045ab383d7b4b2012a00148ab0aa5f290044d4d" - dependencies: - has-symbol-support-x "^1.4.1" - -has-unicode@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/has-unicode/-/has-unicode-2.0.1.tgz#e0e6fe6a28cf51138855e086d1691e771de2a8b9" - -has@^1.0.1: - version "1.0.3" - resolved "https://registry.yarnpkg.com/has/-/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796" - dependencies: - function-bind "^1.1.1" - -hosted-git-info@^2.1.4: - version "2.7.1" - resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.7.1.tgz#97f236977bd6e125408930ff6de3eec6281ec047" - -http-cache-semantics@3.8.1: - version "3.8.1" - resolved "https://registry.yarnpkg.com/http-cache-semantics/-/http-cache-semantics-3.8.1.tgz#39b0e16add9b605bf0a9ef3d9daaf4843b4cacd2" - -http-signature@~1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/http-signature/-/http-signature-1.2.0.tgz#9aecd925114772f3d95b65a60abb8f7c18fbace1" - dependencies: - assert-plus "^1.0.0" - jsprim "^1.2.2" - sshpk "^1.7.0" - -iconv-lite@^0.4.4: - version "0.4.23" - resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.23.tgz#297871f63be507adcfbfca715d0cd0eed84e9a63" - dependencies: - safer-buffer ">= 2.1.2 < 3" - -ignore-walk@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/ignore-walk/-/ignore-walk-3.0.1.tgz#a83e62e7d272ac0e3b551aaa82831a19b69f82f8" - dependencies: - minimatch "^3.0.4" - -ignore@^3.2.0: - version "3.3.10" - resolved "https://registry.yarnpkg.com/ignore/-/ignore-3.3.10.tgz#0a97fb876986e8081c631160f8f9f389157f0043" - -indent-string@^3.0.0: - version "3.2.0" - resolved "https://registry.yarnpkg.com/indent-string/-/indent-string-3.2.0.tgz#4a5fd6d27cc332f37e5419a504dbb837105c9289" - -inflight@^1.0.4: - version "1.0.6" - resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" - dependencies: - once "^1.3.0" - wrappy "1" - -inherits@2, inherits@^2.0.1, inherits@^2.0.3, inherits@~2.0.3: - version "2.0.3" - resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" - -ini@~1.3.0: - version "1.3.5" - resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.5.tgz#eee25f56db1c9ec6085e0c22778083f596abf927" - -interop-require@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/interop-require/-/interop-require-1.0.0.tgz#e53103679944c88d7e6105b62a9f4475c783971e" - -into-stream@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/into-stream/-/into-stream-3.1.0.tgz#96fb0a936c12babd6ff1752a17d05616abd094c6" - dependencies: - from2 "^2.1.1" - p-is-promise "^1.1.0" - -ip-regex@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/ip-regex/-/ip-regex-2.1.0.tgz#fa78bf5d2e6913c911ce9f819ee5146bb6d844e9" - -ip@^1.1.0: - version "1.1.5" - resolved "https://registry.yarnpkg.com/ip/-/ip-1.1.5.tgz#bdded70114290828c0a039e72ef25f5aaec4354a" - -is-absolute-url@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/is-absolute-url/-/is-absolute-url-2.1.0.tgz#50530dfb84fcc9aa7dbe7852e83a37b93b9f2aa6" - -is-alphabetical@^1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/is-alphabetical/-/is-alphabetical-1.0.2.tgz#1fa6e49213cb7885b75d15862fb3f3d96c884f41" - -is-alphanumeric@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/is-alphanumeric/-/is-alphanumeric-1.0.0.tgz#4a9cef71daf4c001c1d81d63d140cf53fd6889f4" - -is-alphanumerical@^1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/is-alphanumerical/-/is-alphanumerical-1.0.2.tgz#1138e9ae5040158dc6ff76b820acd6b7a181fd40" - dependencies: - is-alphabetical "^1.0.0" - is-decimal "^1.0.0" - -is-arrayish@^0.2.1: - version "0.2.1" - resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d" - -is-binary-path@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-1.0.1.tgz#75f16642b480f187a711c814161fd3a4a7655898" - dependencies: - binary-extensions "^1.0.0" - -is-buffer@^1.1.4, is-buffer@^1.1.5, is-buffer@~1.1.1: - version "1.1.6" - resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.6.tgz#efaa2ea9daa0d7ab2ea13a97b2b8ad51fefbe8be" - -is-builtin-module@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/is-builtin-module/-/is-builtin-module-1.0.0.tgz#540572d34f7ac3119f8f76c30cbc1b1e037affbe" - dependencies: - builtin-modules "^1.0.0" - -is-callable@^1.1.1, is-callable@^1.1.3: - version "1.1.4" - resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.1.4.tgz#1e1adf219e1eeb684d691f9d6a05ff0d30a24d75" - -is-date-object@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/is-date-object/-/is-date-object-1.0.1.tgz#9aa20eb6aeebbff77fbd33e74ca01b33581d3a16" - -is-decimal@^1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/is-decimal/-/is-decimal-1.0.2.tgz#894662d6a8709d307f3a276ca4339c8fa5dff0ff" - -is-dotfile@^1.0.0: - version "1.0.3" - resolved "https://registry.yarnpkg.com/is-dotfile/-/is-dotfile-1.0.3.tgz#a6a2f32ffd2dfb04f5ca25ecd0f6b83cf798a1e1" - -is-empty@^1.0.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/is-empty/-/is-empty-1.2.0.tgz#de9bb5b278738a05a0b09a57e1fb4d4a341a9f6b" - -is-equal-shallow@^0.1.3: - version "0.1.3" - resolved "https://registry.yarnpkg.com/is-equal-shallow/-/is-equal-shallow-0.1.3.tgz#2238098fc221de0bcfa5d9eac4c45d638aa1c534" - dependencies: - is-primitive "^2.0.0" - -is-extendable@^0.1.1: - version "0.1.1" - resolved "https://registry.yarnpkg.com/is-extendable/-/is-extendable-0.1.1.tgz#62b110e289a471418e3ec36a617d472e301dfc89" - -is-extglob@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-1.0.0.tgz#ac468177c4943405a092fc8f29760c6ffc6206c0" - -is-file@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/is-file/-/is-file-1.0.0.tgz#28a44cfbd9d3db193045f22b65fce8edf9620596" - -is-fullwidth-code-point@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz#ef9e31386f031a7f0d643af82fde50c457ef00cb" - dependencies: - number-is-nan "^1.0.0" - -is-fullwidth-code-point@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz#a3b30a5c4f199183167aaab93beefae3ddfb654f" - -is-glob@^2.0.0, is-glob@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-2.0.1.tgz#d096f926a3ded5600f3fdfd91198cb0888c2d863" - dependencies: - is-extglob "^1.0.0" - -is-hexadecimal@^1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/is-hexadecimal/-/is-hexadecimal-1.0.2.tgz#b6e710d7d07bb66b98cb8cece5c9b4921deeb835" - -is-hidden@^1.0.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/is-hidden/-/is-hidden-1.1.1.tgz#82ee6a93aeef3fb007ad5b9457c0584d45329f38" - -is-ip@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/is-ip/-/is-ip-2.0.0.tgz#68eea07e8a0a0a94c2d080dd674c731ab2a461ab" - dependencies: - ip-regex "^2.0.0" - -is-number@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/is-number/-/is-number-2.1.0.tgz#01fcbbb393463a548f2f466cce16dece49db908f" - dependencies: - kind-of "^3.0.2" - -is-number@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/is-number/-/is-number-4.0.0.tgz#0026e37f5454d73e356dfe6564699867c6a7f0ff" - -is-object@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/is-object/-/is-object-1.0.1.tgz#8952688c5ec2ffd6b03ecc85e769e02903083470" - -is-online@^7.0.0: - version "7.0.0" - resolved "https://registry.yarnpkg.com/is-online/-/is-online-7.0.0.tgz#7e2408c0ae1e7e37ba8d50bdb237260d32bfd96e" - dependencies: - got "^6.7.1" - p-any "^1.0.0" - p-timeout "^1.0.0" - public-ip "^2.3.0" - -is-path-cwd@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/is-path-cwd/-/is-path-cwd-1.0.0.tgz#d225ec23132e89edd38fda767472e62e65f1106d" - -is-path-in-cwd@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/is-path-in-cwd/-/is-path-in-cwd-1.0.1.tgz#5ac48b345ef675339bd6c7a48a912110b241cf52" - dependencies: - is-path-inside "^1.0.0" - -is-path-inside@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/is-path-inside/-/is-path-inside-1.0.1.tgz#8ef5b7de50437a3fdca6b4e865ef7aa55cb48036" - dependencies: - path-is-inside "^1.0.1" - -is-plain-obj@^1.0.0, is-plain-obj@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-1.1.0.tgz#71a50c8429dfca773c92a390a4a03b39fcd51d3e" - -is-posix-bracket@^0.1.0: - version "0.1.1" - resolved "https://registry.yarnpkg.com/is-posix-bracket/-/is-posix-bracket-0.1.1.tgz#3334dc79774368e92f016e6fbc0a88f5cd6e6bc4" - -is-primitive@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/is-primitive/-/is-primitive-2.0.0.tgz#207bab91638499c07b2adf240a41a87210034575" - -is-redirect@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/is-redirect/-/is-redirect-1.0.0.tgz#1d03dded53bd8db0f30c26e4f95d36fc7c87dc24" - -is-regex@^1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.0.4.tgz#5517489b547091b0930e095654ced25ee97e9491" - dependencies: - has "^1.0.1" - -is-relative-url@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/is-relative-url/-/is-relative-url-2.0.0.tgz#72902d7fe04b3d4792e7db15f9db84b7204c9cef" - dependencies: - is-absolute-url "^2.0.0" - -is-retry-allowed@^1.0.0, is-retry-allowed@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/is-retry-allowed/-/is-retry-allowed-1.1.0.tgz#11a060568b67339444033d0125a61a20d564fb34" - -is-stream@^1.0.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44" - -is-symbol@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/is-symbol/-/is-symbol-1.0.1.tgz#3cc59f00025194b6ab2e38dbae6689256b660572" - -is-typedarray@~1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a" - -is-utf8@^0.2.0: - version "0.2.1" - resolved "https://registry.yarnpkg.com/is-utf8/-/is-utf8-0.2.1.tgz#4b0da1442104d1b336340e80797e865cf39f7d72" - -is-whitespace-character@^1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/is-whitespace-character/-/is-whitespace-character-1.0.2.tgz#ede53b4c6f6fb3874533751ec9280d01928d03ed" - -is-word-character@^1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/is-word-character/-/is-word-character-1.0.2.tgz#46a5dac3f2a1840898b91e576cd40d493f3ae553" - -isarray@1.0.0, isarray@~1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" - -isemail@^3.1.2: - version "3.1.3" - resolved "https://registry.yarnpkg.com/isemail/-/isemail-3.1.3.tgz#64f37fc113579ea12523165c3ebe3a71a56ce571" - dependencies: - punycode "2.x.x" - -isobject@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/isobject/-/isobject-2.1.0.tgz#f065561096a3f1da2ef46272f815c840d87e0c89" - dependencies: - isarray "1.0.0" - -isstream@~0.1.2: - version "0.1.2" - resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a" - -isurl@^1.0.0-alpha5: - version "1.0.0" - resolved "https://registry.yarnpkg.com/isurl/-/isurl-1.0.0.tgz#b27f4f49f3cdaa3ea44a0a5b7f3462e6edc39d67" - dependencies: - has-to-string-tag-x "^1.2.0" - is-object "^1.0.1" - -js-yaml@^3.12.0, js-yaml@^3.2.4, js-yaml@^3.6.1: - version "3.12.0" - resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.12.0.tgz#eaed656ec8344f10f527c6bfa1b6e2244de167d1" - dependencies: - argparse "^1.0.7" - esprima "^4.0.0" - -jsbn@~0.1.0: - version "0.1.1" - resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-0.1.1.tgz#a5e654c2e5a2deb5f201d96cefbca80c0ef2f513" - -json-buffer@3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/json-buffer/-/json-buffer-3.0.0.tgz#5b1f397afc75d677bde8bcfc0e47e1f9a3d9a898" - -json-parse-better-errors@^1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz#bb867cfb3450e69107c131d1c514bab3dc8bcaa9" - -json-schema-traverse@^0.3.0: - version "0.3.1" - resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.3.1.tgz#349a6d44c53a51de89b40805c5d5e59b417d3340" - -json-schema@0.2.3: - version "0.2.3" - resolved "https://registry.yarnpkg.com/json-schema/-/json-schema-0.2.3.tgz#b480c892e59a2f05954ce727bd3f2a4e882f9e13" - -json-stable-stringify@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/json-stable-stringify/-/json-stable-stringify-1.0.1.tgz#9a759d39c5f2ff503fd5300646ed445f88c4f9af" - dependencies: - jsonify "~0.0.0" - -json-stringify-safe@~5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb" - -json5@^0.5.1: - version "0.5.1" - resolved "https://registry.yarnpkg.com/json5/-/json5-0.5.1.tgz#1eade7acc012034ad84e2396767ead9fa5495821" - -json5@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/json5/-/json5-1.0.1.tgz#779fb0018604fa854eacbf6252180d83543e3dbe" - dependencies: - minimist "^1.2.0" - -jsonify@~0.0.0: - version "0.0.0" - resolved "https://registry.yarnpkg.com/jsonify/-/jsonify-0.0.0.tgz#2c74b6ee41d93ca51b7b5aaee8f503631d252a73" - -jsprim@^1.2.2: - version "1.4.1" - resolved "https://registry.yarnpkg.com/jsprim/-/jsprim-1.4.1.tgz#313e66bc1e5cc06e438bc1b7499c2e5c56acb6a2" - dependencies: - assert-plus "1.0.0" - extsprintf "1.3.0" - json-schema "0.2.3" - verror "1.10.0" - -keyv@3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/keyv/-/keyv-3.0.0.tgz#44923ba39e68b12a7cec7df6c3268c031f2ef373" - dependencies: - json-buffer "3.0.0" - -kind-of@^3.0.2: - version "3.2.2" - resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-3.2.2.tgz#31ea21a734bab9bbb0f32466d893aea51e4a3c64" - dependencies: - is-buffer "^1.1.5" - -kind-of@^6.0.0: - version "6.0.2" - resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-6.0.2.tgz#01146b36a6218e64e58f3a8d66de5d7fc6f6d051" - -levn@~0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/levn/-/levn-0.3.0.tgz#3b09924edf9f083c0490fdd4c0bc4421e04764ee" - dependencies: - prelude-ls "~1.1.2" - type-check "~0.3.2" - -link-check@^4.1.0: - version "4.4.4" - resolved "https://registry.yarnpkg.com/link-check/-/link-check-4.4.4.tgz#08dbb881b70c23f1c173889c3a34d682c2e68c1a" - dependencies: - is-relative-url "^2.0.0" - isemail "^3.1.2" - ms "^2.1.1" - request "^2.87.0" - -load-json-file@^1.0.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/load-json-file/-/load-json-file-1.1.0.tgz#956905708d58b4bab4c2261b04f59f31c99374c0" - dependencies: - graceful-fs "^4.1.2" - parse-json "^2.2.0" - pify "^2.0.0" - pinkie-promise "^2.0.0" - strip-bom "^2.0.0" - -load-json-file@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/load-json-file/-/load-json-file-4.0.0.tgz#2f5f45ab91e33216234fd53adab668eb4ec0993b" - dependencies: - graceful-fs "^4.1.2" - parse-json "^4.0.0" - pify "^3.0.0" - strip-bom "^3.0.0" - -load-plugin@^2.0.0: - version "2.2.2" - resolved "https://registry.yarnpkg.com/load-plugin/-/load-plugin-2.2.2.tgz#ebc7599491ff33e5077719fbe051d5725a9f7a89" - dependencies: - npm-prefix "^1.2.0" - resolve-from "^4.0.0" - -locate-path@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-2.0.0.tgz#2b568b265eec944c6d9c0de9c3dbbbca0354cd8e" - dependencies: - p-locate "^2.0.0" - path-exists "^3.0.0" - -lodash@^4.0.0, lodash@^4.17.4: - version "4.17.10" - resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.10.tgz#1b7793cf7259ea38fb3661d4d38b3260af8ae4e7" - -log-symbols@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-1.0.2.tgz#376ff7b58ea3086a0f09facc74617eca501e1a18" - dependencies: - chalk "^1.0.0" - -longest-streak@^2.0.1: - version "2.0.2" - resolved "https://registry.yarnpkg.com/longest-streak/-/longest-streak-2.0.2.tgz#2421b6ba939a443bb9ffebf596585a50b4c38e2e" - -lowercase-keys@1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/lowercase-keys/-/lowercase-keys-1.0.0.tgz#4e3366b39e7f5457e35f1324bdf6f88d0bfc7306" - -lowercase-keys@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/lowercase-keys/-/lowercase-keys-1.0.1.tgz#6f9e30b47084d971a7c820ff15a6c5167b74c26f" - -map-like@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/map-like/-/map-like-2.0.0.tgz#94496d49ad333c0dc3234b27adbbd1e8535953b4" - -markdown-escapes@^1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/markdown-escapes/-/markdown-escapes-1.0.2.tgz#e639cbde7b99c841c0bacc8a07982873b46d2122" - -markdown-extensions@^1.1.0: - version "1.1.1" - resolved "https://registry.yarnpkg.com/markdown-extensions/-/markdown-extensions-1.1.1.tgz#fea03b539faeaee9b4ef02a3769b455b189f7fc3" - -markdown-table@^1.1.0: - version "1.1.2" - resolved "https://registry.yarnpkg.com/markdown-table/-/markdown-table-1.1.2.tgz#c78db948fa879903a41bce522e3b96f801c63786" - -math-random@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/math-random/-/math-random-1.0.1.tgz#8b3aac588b8a66e4975e3cdea67f7bb329601fac" - -md5@^2.2.1: - version "2.2.1" - resolved "https://registry.yarnpkg.com/md5/-/md5-2.2.1.tgz#53ab38d5fe3c8891ba465329ea23fac0540126f9" - dependencies: - charenc "~0.0.1" - crypt "~0.0.1" - is-buffer "~1.1.1" - -mdast-util-compact@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/mdast-util-compact/-/mdast-util-compact-1.0.1.tgz#cdb5f84e2b6a2d3114df33bd05d9cb32e3c4083a" - dependencies: - unist-util-modify-children "^1.0.0" - unist-util-visit "^1.1.0" - -micromatch@^2.1.5: - version "2.3.11" - resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-2.3.11.tgz#86677c97d1720b363431d04d0d15293bd38c1565" - dependencies: - arr-diff "^2.0.0" - array-unique "^0.2.1" - braces "^1.8.2" - expand-brackets "^0.1.4" - extglob "^0.3.1" - filename-regex "^2.0.0" - is-extglob "^1.0.0" - is-glob "^2.0.1" - kind-of "^3.0.2" - normalize-path "^2.0.1" - object.omit "^2.0.0" - parse-glob "^3.0.4" - regex-cache "^0.4.2" - -mime-db@~1.35.0: - version "1.35.0" - resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.35.0.tgz#0569d657466491283709663ad379a99b90d9ab47" - -mime-types@^2.1.12, mime-types@~2.1.17: - version "2.1.19" - resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.19.tgz#71e464537a7ef81c15f2db9d97e913fc0ff606f0" - dependencies: - mime-db "~1.35.0" - -mimic-response@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-1.0.1.tgz#4923538878eef42063cb8a3e3b0798781487ab1b" - -minimatch@^3.0.2, minimatch@^3.0.4: - version "3.0.4" - resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" - dependencies: - brace-expansion "^1.1.7" - -minimist@0.0.8: - version "0.0.8" - resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.8.tgz#857fcabfc3397d2625b8228262e86aa7a011b05d" - -minimist@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.0.tgz#a35008b20f41383eec1fb914f4cd5df79a264284" - -minipass@^2.2.1, minipass@^2.3.3: - version "2.3.3" - resolved "https://registry.yarnpkg.com/minipass/-/minipass-2.3.3.tgz#a7dcc8b7b833f5d368759cce544dccb55f50f233" - dependencies: - safe-buffer "^5.1.2" - yallist "^3.0.0" - -minizlib@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/minizlib/-/minizlib-1.1.0.tgz#11e13658ce46bc3a70a267aac58359d1e0c29ceb" - dependencies: - minipass "^2.2.1" - -mkdirp@^0.5.0, mkdirp@^0.5.1: - version "0.5.1" - resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903" - dependencies: - minimist "0.0.8" - -ms@2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" - -ms@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.1.tgz#30a5864eb3ebb0a66f2ebe6d727af06a09d86e0a" - -nan@^2.9.2: - version "2.10.0" - resolved "https://registry.yarnpkg.com/nan/-/nan-2.10.0.tgz#96d0cd610ebd58d4b4de9cc0c6828cda99c7548f" - -needle@^2.2.1: - version "2.2.1" - resolved "https://registry.yarnpkg.com/needle/-/needle-2.2.1.tgz#b5e325bd3aae8c2678902fa296f729455d1d3a7d" - dependencies: - debug "^2.1.2" - iconv-lite "^0.4.4" - sax "^1.2.4" - -nlcst-to-string@^2.0.0: - version "2.0.2" - resolved "https://registry.yarnpkg.com/nlcst-to-string/-/nlcst-to-string-2.0.2.tgz#7125af4d4d369850c697192a658f01f36af9937b" - -no-cliches@^0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/no-cliches/-/no-cliches-0.1.0.tgz#f4eb81a551fecde813f8c611e35e64a5118dc38c" - -node-pre-gyp@^0.10.0: - version "0.10.3" - resolved "https://registry.yarnpkg.com/node-pre-gyp/-/node-pre-gyp-0.10.3.tgz#3070040716afdc778747b61b6887bf78880b80fc" - dependencies: - detect-libc "^1.0.2" - mkdirp "^0.5.1" - needle "^2.2.1" - nopt "^4.0.1" - npm-packlist "^1.1.6" - npmlog "^4.0.2" - rc "^1.2.7" - rimraf "^2.6.1" - semver "^5.3.0" - tar "^4" - -nopt@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/nopt/-/nopt-4.0.1.tgz#d0d4685afd5415193c8c7505602d0d17cd64474d" - dependencies: - abbrev "1" - osenv "^0.1.4" - -normalize-package-data@^2.3.2: - version "2.4.0" - resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-2.4.0.tgz#12f95a307d58352075a04907b84ac8be98ac012f" - dependencies: - hosted-git-info "^2.1.4" - is-builtin-module "^1.0.0" - semver "2 || 3 || 4 || 5" - validate-npm-package-license "^3.0.1" - -normalize-path@^2.0.0, normalize-path@^2.0.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-2.1.1.tgz#1ab28b556e198363a8c1a6f7e6fa20137fe6aed9" - dependencies: - remove-trailing-separator "^1.0.1" - -normalize-url@2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-2.0.1.tgz#835a9da1551fa26f70e92329069a23aa6574d7e6" - dependencies: - prepend-http "^2.0.0" - query-string "^5.0.1" - sort-keys "^2.0.0" - -npm-bundled@^1.0.1: - version "1.0.3" - resolved "https://registry.yarnpkg.com/npm-bundled/-/npm-bundled-1.0.3.tgz#7e71703d973af3370a9591bafe3a63aca0be2308" - -npm-packlist@^1.1.6: - version "1.1.11" - resolved "https://registry.yarnpkg.com/npm-packlist/-/npm-packlist-1.1.11.tgz#84e8c683cbe7867d34b1d357d893ce29e28a02de" - dependencies: - ignore-walk "^3.0.1" - npm-bundled "^1.0.1" - -npm-prefix@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/npm-prefix/-/npm-prefix-1.2.0.tgz#e619455f7074ba54cc66d6d0d37dd9f1be6bcbc0" - dependencies: - rc "^1.1.0" - shellsubstitute "^1.1.0" - untildify "^2.1.0" - -npmlog@^4.0.2: - version "4.1.2" - resolved "https://registry.yarnpkg.com/npmlog/-/npmlog-4.1.2.tgz#08a7f2a8bf734604779a9efa4ad5cc717abb954b" - dependencies: - are-we-there-yet "~1.1.2" - console-control-strings "~1.1.0" - gauge "~2.7.3" - set-blocking "~2.0.0" - -number-is-nan@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/number-is-nan/-/number-is-nan-1.0.1.tgz#097b602b53422a522c1afb8790318336941a011d" - -oauth-sign@~0.8.2: - version "0.8.2" - resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.8.2.tgz#46a6ab7f0aead8deae9ec0565780b7d4efeb9d43" - -object-assign@^4.0.1, object-assign@^4.1.0, object-assign@^4.1.1: - version "4.1.1" - resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" - -object-keys@^1.0.11, object-keys@^1.0.12, object-keys@^1.0.8: - version "1.0.12" - resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.0.12.tgz#09c53855377575310cca62f55bb334abff7b3ed2" - -object.assign@^4.0.4: - version "4.1.0" - resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.1.0.tgz#968bf1100d7956bb3ca086f006f846b3bc4008da" - dependencies: - define-properties "^1.1.2" - function-bind "^1.1.1" - has-symbols "^1.0.0" - object-keys "^1.0.11" - -object.omit@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/object.omit/-/object.omit-2.0.1.tgz#1a9c744829f39dbb858c76ca3579ae2a54ebd1fa" - dependencies: - for-own "^0.1.4" - is-extendable "^0.1.1" - -once@^1.3.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" - dependencies: - wrappy "1" - -optionator@^0.8.0, optionator@^0.8.1: - version "0.8.2" - resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.8.2.tgz#364c5e409d3f4d6301d6c0b4c05bba50180aeb64" - dependencies: - deep-is "~0.1.3" - fast-levenshtein "~2.0.4" - levn "~0.3.0" - prelude-ls "~1.1.2" - type-check "~0.3.2" - wordwrap "~1.0.0" - -os-homedir@^1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/os-homedir/-/os-homedir-1.0.2.tgz#ffbc4988336e0e833de0c168c7ef152121aa7fb3" - -os-tmpdir@^1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274" - -osenv@^0.1.4: - version "0.1.5" - resolved "https://registry.yarnpkg.com/osenv/-/osenv-0.1.5.tgz#85cdfafaeb28e8677f416e287592b5f3f49ea410" - dependencies: - os-homedir "^1.0.0" - os-tmpdir "^1.0.0" - -p-any@^1.0.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/p-any/-/p-any-1.1.0.tgz#1d03835c7eed1e34b8e539c47b7b60d0d015d4e1" - dependencies: - p-some "^2.0.0" - -p-cancelable@^0.4.0: - version "0.4.1" - resolved "https://registry.yarnpkg.com/p-cancelable/-/p-cancelable-0.4.1.tgz#35f363d67d52081c8d9585e37bcceb7e0bbcb2a0" - -p-finally@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/p-finally/-/p-finally-1.0.0.tgz#3fbcfb15b899a44123b34b6dcc18b724336a2cae" - -p-is-promise@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/p-is-promise/-/p-is-promise-1.1.0.tgz#9c9456989e9f6588017b0434d56097675c3da05e" - -p-limit@^1.1.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-1.3.0.tgz#b86bd5f0c25690911c7590fcbfc2010d54b3ccb8" - dependencies: - p-try "^1.0.0" - -p-locate@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-2.0.0.tgz#20a0103b222a70c8fd39cc2e580680f3dde5ec43" - dependencies: - p-limit "^1.1.0" - -p-some@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/p-some/-/p-some-2.0.1.tgz#65d87c8b154edbcf5221d167778b6d2e150f6f06" - dependencies: - aggregate-error "^1.0.0" - -p-timeout@^1.0.0: - version "1.2.1" - resolved "https://registry.yarnpkg.com/p-timeout/-/p-timeout-1.2.1.tgz#5eb3b353b7fce99f101a1038880bb054ebbea386" - dependencies: - p-finally "^1.0.0" - -p-timeout@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/p-timeout/-/p-timeout-2.0.1.tgz#d8dd1979595d2dc0139e1fe46b8b646cb3cdf038" - dependencies: - p-finally "^1.0.0" - -p-try@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/p-try/-/p-try-1.0.0.tgz#cbc79cdbaf8fd4228e13f621f2b1a237c1b207b3" - -parse-entities@^1.0.2, parse-entities@^1.1.0: - version "1.1.2" - resolved "https://registry.yarnpkg.com/parse-entities/-/parse-entities-1.1.2.tgz#9eaf719b29dc3bd62246b4332009072e01527777" - dependencies: - character-entities "^1.0.0" - character-entities-legacy "^1.0.0" - character-reference-invalid "^1.0.0" - is-alphanumerical "^1.0.0" - is-decimal "^1.0.0" - is-hexadecimal "^1.0.0" - -parse-glob@^3.0.4: - version "3.0.4" - resolved "https://registry.yarnpkg.com/parse-glob/-/parse-glob-3.0.4.tgz#b2c376cfb11f35513badd173ef0bb6e3a388391c" - dependencies: - glob-base "^0.3.0" - is-dotfile "^1.0.0" - is-extglob "^1.0.0" - is-glob "^2.0.0" - -parse-json@^2.2.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-2.2.0.tgz#f480f40434ef80741f8469099f8dea18f55a4dc9" - dependencies: - error-ex "^1.2.0" - -parse-json@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-4.0.0.tgz#be35f5425be1f7f6c747184f98a788cb99477ee0" - dependencies: - error-ex "^1.3.1" - json-parse-better-errors "^1.0.1" - -passive-voice@^0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/passive-voice/-/passive-voice-0.1.0.tgz#16ff91ae40ba0e92c43e671763fdc842a70270b1" - -path-exists@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-3.0.0.tgz#ce0ebeaa5f78cb18925ea7d810d7b59b010fd515" - -path-is-absolute@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" - -path-is-inside@^1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/path-is-inside/-/path-is-inside-1.0.2.tgz#365417dede44430d1c11af61027facf074bdfc53" - -path-to-glob-pattern@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/path-to-glob-pattern/-/path-to-glob-pattern-1.0.2.tgz#473e6a3a292a9d13fbae3edccee72d3baba8c619" - -path-type@^1.0.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/path-type/-/path-type-1.1.0.tgz#59c44f7ee491da704da415da5a4070ba4f8fe441" - dependencies: - graceful-fs "^4.1.2" - pify "^2.0.0" - pinkie-promise "^2.0.0" - -path-type@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/path-type/-/path-type-3.0.0.tgz#cef31dc8e0a1a3bb0d105c0cd97cf3bf47f4e36f" - dependencies: - pify "^3.0.0" - -performance-now@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-2.1.0.tgz#6309f4e0e5fa913ec1c69307ae364b4b377c9e7b" - -pify@^2.0.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/pify/-/pify-2.3.0.tgz#ed141a6ac043a849ea588498e7dca8b15330e90c" - -pify@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/pify/-/pify-3.0.0.tgz#e5a4acd2c101fdf3d9a4d07f0dbc4db49dd28176" - -pinkie-promise@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/pinkie-promise/-/pinkie-promise-2.0.1.tgz#2135d6dfa7a358c069ac9b178776288228450ffa" - dependencies: - pinkie "^2.0.0" - -pinkie@^2.0.0: - version "2.0.4" - resolved "https://registry.yarnpkg.com/pinkie/-/pinkie-2.0.4.tgz#72556b80cfa0d48a974e80e77248e80ed4f7f870" - -pluralize@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/pluralize/-/pluralize-2.0.0.tgz#72b726aa6fac1edeee42256c7d8dc256b335677f" - -prelude-ls@~1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.1.2.tgz#21932a549f5e52ffd9a827f570e04be62a97da54" - -prepend-http@^1.0.1: - version "1.0.4" - resolved "https://registry.yarnpkg.com/prepend-http/-/prepend-http-1.0.4.tgz#d4f4562b0ce3696e41ac52d0e002e57a635dc6dc" - -prepend-http@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/prepend-http/-/prepend-http-2.0.0.tgz#e92434bfa5ea8c19f41cdfd401d741a3c819d897" - -preserve@^0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/preserve/-/preserve-0.2.0.tgz#815ed1f6ebc65926f865b310c0713bcb3315ce4b" - -prettier@^1.13.7: - version "1.14.2" - resolved "https://registry.yarnpkg.com/prettier/-/prettier-1.14.2.tgz#0ac1c6e1a90baa22a62925f41963c841983282f9" - -process-nextick-args@~2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.0.tgz#a37d732f4271b4ab1ad070d35508e8290788ffaa" - -public-ip@^2.3.0: - version "2.4.0" - resolved "https://registry.yarnpkg.com/public-ip/-/public-ip-2.4.0.tgz#f00c028a15366d8c798e47efab6acd09a17666da" - dependencies: - dns-socket "^1.6.2" - got "^8.0.0" - is-ip "^2.0.0" - pify "^3.0.0" - -punycode@2.x.x: - version "2.1.1" - resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec" - -punycode@^1.4.1: - version "1.4.1" - resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.4.1.tgz#c0d5a63b2718800ad8e1eb0fa5269c84dd41845e" - -qs@~6.5.1: - version "6.5.2" - resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.2.tgz#cb3ae806e8740444584ef154ce8ee98d403f3e36" - -query-string@^5.0.1: - version "5.1.1" - resolved "https://registry.yarnpkg.com/query-string/-/query-string-5.1.1.tgz#a78c012b71c17e05f2e3fa2319dd330682efb3cb" - dependencies: - decode-uri-component "^0.2.0" - object-assign "^4.1.0" - strict-uri-encode "^1.0.0" - -randomatic@^3.0.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/randomatic/-/randomatic-3.1.0.tgz#36f2ca708e9e567f5ed2ec01949026d50aa10116" - dependencies: - is-number "^4.0.0" - kind-of "^6.0.0" - math-random "^1.0.1" - -rc-config-loader@^2.0.1: - version "2.0.2" - resolved "https://registry.yarnpkg.com/rc-config-loader/-/rc-config-loader-2.0.2.tgz#46eb2f98fb5b2aa7b1119d66c0554de5133f1bc1" - dependencies: - debug "^3.1.0" - js-yaml "^3.12.0" - json5 "^1.0.1" - object-assign "^4.1.0" - object-keys "^1.0.12" - path-exists "^3.0.0" - require-from-string "^2.0.2" - -rc@^1.1.0, rc@^1.2.7: - version "1.2.8" - resolved "https://registry.yarnpkg.com/rc/-/rc-1.2.8.tgz#cd924bf5200a075b83c188cd6b9e211b7fc0d3ed" - dependencies: - deep-extend "^0.6.0" - ini "~1.3.0" - minimist "^1.2.0" - strip-json-comments "~2.0.1" - -read-pkg-up@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/read-pkg-up/-/read-pkg-up-3.0.0.tgz#3ed496685dba0f8fe118d0691dc51f4a1ff96f07" - dependencies: - find-up "^2.0.0" - read-pkg "^3.0.0" - -read-pkg@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/read-pkg/-/read-pkg-1.1.0.tgz#f5ffaa5ecd29cb31c0474bca7d756b6bb29e3f28" - dependencies: - load-json-file "^1.0.0" - normalize-package-data "^2.3.2" - path-type "^1.0.0" - -read-pkg@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/read-pkg/-/read-pkg-3.0.0.tgz#9cbc686978fee65d16c00e2b19c237fcf6e38389" - dependencies: - load-json-file "^4.0.0" - normalize-package-data "^2.3.2" - path-type "^3.0.0" - -readable-stream@^2.0.0, readable-stream@^2.0.2, readable-stream@^2.0.6, readable-stream@^2.2.2: - version "2.3.6" - resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.6.tgz#b11c27d88b8ff1fbe070643cf94b0c79ae1b0aaf" - dependencies: - core-util-is "~1.0.0" - inherits "~2.0.3" - isarray "~1.0.0" - process-nextick-args "~2.0.0" - safe-buffer "~5.1.1" - string_decoder "~1.1.1" - util-deprecate "~1.0.1" - -readdirp@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-2.1.0.tgz#4ed0ad060df3073300c48440373f72d1cc642d78" - dependencies: - graceful-fs "^4.1.2" - minimatch "^3.0.2" - readable-stream "^2.0.2" - set-immediate-shim "^1.0.1" - -regex-cache@^0.4.2: - version "0.4.4" - resolved "https://registry.yarnpkg.com/regex-cache/-/regex-cache-0.4.4.tgz#75bdc58a2a1496cec48a12835bc54c8d562336dd" - dependencies: - is-equal-shallow "^0.1.3" - -remark-cli@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/remark-cli/-/remark-cli-5.0.0.tgz#9feefd06474f3d0ff132df21b5334c546df12ab6" - dependencies: - markdown-extensions "^1.1.0" - remark "^9.0.0" - unified-args "^5.0.0" - -remark-frontmatter@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/remark-frontmatter/-/remark-frontmatter-1.2.0.tgz#67905d178c0fe531ed12c57b98759f101fc2c1b5" - dependencies: - fault "^1.0.1" - xtend "^4.0.1" - -remark-lint-no-dead-urls@^0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/remark-lint-no-dead-urls/-/remark-lint-no-dead-urls-0.3.0.tgz#b640ecbb4ccaf780afe28c8d13e79f5dc6769449" - dependencies: - is-online "^7.0.0" - is-relative-url "^2.0.0" - link-check "^4.1.0" - unified-lint-rule "^1.0.1" - unist-util-visit "^1.1.3" - -remark-lint-write-good@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/remark-lint-write-good/-/remark-lint-write-good-1.0.3.tgz#daa4cf122212cfa06e437702ef7b43a12875bd5d" - dependencies: - nlcst-to-string "^2.0.0" - unified-lint-rule "^1.0.1" - unist-util-visit "^1.1.1" - write-good "^0.11.1" - -remark-parse@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/remark-parse/-/remark-parse-5.0.0.tgz#4c077f9e499044d1d5c13f80d7a98cf7b9285d95" - dependencies: - collapse-white-space "^1.0.2" - is-alphabetical "^1.0.0" - is-decimal "^1.0.0" - is-whitespace-character "^1.0.0" - is-word-character "^1.0.0" - markdown-escapes "^1.0.0" - parse-entities "^1.1.0" - repeat-string "^1.5.4" - state-toggle "^1.0.0" - trim "0.0.1" - trim-trailing-lines "^1.0.0" - unherit "^1.0.4" - unist-util-remove-position "^1.0.0" - vfile-location "^2.0.0" - xtend "^4.0.1" - -remark-stringify@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/remark-stringify/-/remark-stringify-5.0.0.tgz#336d3a4d4a6a3390d933eeba62e8de4bd280afba" - dependencies: - ccount "^1.0.0" - is-alphanumeric "^1.0.0" - is-decimal "^1.0.0" - is-whitespace-character "^1.0.0" - longest-streak "^2.0.1" - markdown-escapes "^1.0.0" - markdown-table "^1.1.0" - mdast-util-compact "^1.0.0" - parse-entities "^1.0.2" - repeat-string "^1.5.4" - state-toggle "^1.0.0" - stringify-entities "^1.0.1" - unherit "^1.0.4" - xtend "^4.0.1" - -remark@^9.0.0: - version "9.0.0" - resolved "https://registry.yarnpkg.com/remark/-/remark-9.0.0.tgz#c5cfa8ec535c73a67c4b0f12bfdbd3a67d8b2f60" - dependencies: - remark-parse "^5.0.0" - remark-stringify "^5.0.0" - unified "^6.0.0" - -remove-trailing-separator@^1.0.1: - version "1.1.0" - resolved "https://registry.yarnpkg.com/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz#c24bce2a283adad5bc3f58e0d48249b92379d8ef" - -repeat-element@^1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/repeat-element/-/repeat-element-1.1.2.tgz#ef089a178d1483baae4d93eb98b4f9e4e11d990a" - -repeat-string@^1.5.0, repeat-string@^1.5.2, repeat-string@^1.5.4: - version "1.6.1" - resolved "https://registry.yarnpkg.com/repeat-string/-/repeat-string-1.6.1.tgz#8dcae470e1c88abc2d600fff4a776286da75e637" - -replace-ext@1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/replace-ext/-/replace-ext-1.0.0.tgz#de63128373fcbf7c3ccfa4de5a480c45a67958eb" - -request@^2.87.0: - version "2.87.0" - resolved "https://registry.yarnpkg.com/request/-/request-2.87.0.tgz#32f00235cd08d482b4d0d68db93a829c0ed5756e" - dependencies: - aws-sign2 "~0.7.0" - aws4 "^1.6.0" - caseless "~0.12.0" - combined-stream "~1.0.5" - extend "~3.0.1" - forever-agent "~0.6.1" - form-data "~2.3.1" - har-validator "~5.0.3" - http-signature "~1.2.0" - is-typedarray "~1.0.0" - isstream "~0.1.2" - json-stringify-safe "~5.0.1" - mime-types "~2.1.17" - oauth-sign "~0.8.2" - performance-now "^2.1.0" - qs "~6.5.1" - safe-buffer "^5.1.1" - tough-cookie "~2.3.3" - tunnel-agent "^0.6.0" - uuid "^3.1.0" - -require-from-string@^2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/require-from-string/-/require-from-string-2.0.2.tgz#89a7fdd938261267318eafe14f9c32e598c36909" - -resolve-from@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6" - -responselike@1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/responselike/-/responselike-1.0.2.tgz#918720ef3b631c5642be068f15ade5a46f4ba1e7" - dependencies: - lowercase-keys "^1.0.0" - -rimraf@^2.2.8, rimraf@^2.6.1: - version "2.6.2" - resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.6.2.tgz#2ed8150d24a16ea8651e6d6ef0f47c4158ce7a36" - dependencies: - glob "^7.0.5" - -safe-buffer@^5.0.1, safe-buffer@^5.1.1, safe-buffer@^5.1.2, safe-buffer@~5.1.0, safe-buffer@~5.1.1: - version "5.1.2" - resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" - -"safer-buffer@>= 2.1.2 < 3", safer-buffer@^2.0.2, safer-buffer@^2.1.0, safer-buffer@~2.1.0: - version "2.1.2" - resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" - -sax@^1.2.4: - version "1.2.4" - resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9" - -"semver@2 || 3 || 4 || 5", semver@^5.3.0: - version "5.5.0" - resolved "https://registry.yarnpkg.com/semver/-/semver-5.5.0.tgz#dc4bbc7a6ca9d916dee5d43516f0092b58f7b8ab" - -set-blocking@~2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7" - -set-immediate-shim@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/set-immediate-shim/-/set-immediate-shim-1.0.1.tgz#4b2b1b27eb808a9f8dcc481a58e5e56f599f3f61" - -shellsubstitute@^1.1.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/shellsubstitute/-/shellsubstitute-1.2.0.tgz#e4f702a50c518b0f6fe98451890d705af29b6b70" - -signal-exit@^3.0.0: - version "3.0.2" - resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.2.tgz#b5fdc08f1287ea1178628e415e25132b73646c6d" - -slice-ansi@0.0.4: - version "0.0.4" - resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-0.0.4.tgz#edbf8903f66f7ce2f8eafd6ceed65e264c831b35" - -sliced@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/sliced/-/sliced-1.0.1.tgz#0b3a662b5d04c3177b1926bea82b03f837a2ef41" - -sort-keys@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/sort-keys/-/sort-keys-2.0.0.tgz#658535584861ec97d730d6cf41822e1f56684128" - dependencies: - is-plain-obj "^1.0.0" - -spdx-correct@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/spdx-correct/-/spdx-correct-3.0.0.tgz#05a5b4d7153a195bc92c3c425b69f3b2a9524c82" - dependencies: - spdx-expression-parse "^3.0.0" - spdx-license-ids "^3.0.0" - -spdx-exceptions@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/spdx-exceptions/-/spdx-exceptions-2.1.0.tgz#2c7ae61056c714a5b9b9b2b2af7d311ef5c78fe9" - -spdx-expression-parse@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/spdx-expression-parse/-/spdx-expression-parse-3.0.0.tgz#99e119b7a5da00e05491c9fa338b7904823b41d0" - dependencies: - spdx-exceptions "^2.1.0" - spdx-license-ids "^3.0.0" - -spdx-license-ids@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/spdx-license-ids/-/spdx-license-ids-3.0.0.tgz#7a7cd28470cc6d3a1cfe6d66886f6bc430d3ac87" - -split-lines@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/split-lines/-/split-lines-1.1.0.tgz#3abba8f598614142f9db8d27ab6ab875662a1e09" - -sprintf-js@~1.0.2: - version "1.0.3" - resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" - -sshpk@^1.7.0: - version "1.14.2" - resolved "https://registry.yarnpkg.com/sshpk/-/sshpk-1.14.2.tgz#c6fc61648a3d9c4e764fd3fcdf4ea105e492ba98" - dependencies: - asn1 "~0.2.3" - assert-plus "^1.0.0" - dashdash "^1.12.0" - getpass "^0.1.1" - safer-buffer "^2.0.2" - optionalDependencies: - bcrypt-pbkdf "^1.0.0" - ecc-jsbn "~0.1.1" - jsbn "~0.1.0" - tweetnacl "~0.14.0" - -state-toggle@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/state-toggle/-/state-toggle-1.0.1.tgz#c3cb0974f40a6a0f8e905b96789eb41afa1cde3a" - -strict-uri-encode@^1.0.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/strict-uri-encode/-/strict-uri-encode-1.1.0.tgz#279b225df1d582b1f54e65addd4352e18faa0713" - -string-width@^1.0.0, string-width@^1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/string-width/-/string-width-1.0.2.tgz#118bdf5b8cdc51a2a7e70d211e07e2b0b9b107d3" - dependencies: - code-point-at "^1.0.0" - is-fullwidth-code-point "^1.0.0" - strip-ansi "^3.0.0" - -"string-width@^1.0.2 || 2", string-width@^2.0.0: - version "2.1.1" - resolved "https://registry.yarnpkg.com/string-width/-/string-width-2.1.1.tgz#ab93f27a8dc13d28cac815c462143a6d9012ae9e" - dependencies: - is-fullwidth-code-point "^2.0.0" - strip-ansi "^4.0.0" - -string.prototype.padstart@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/string.prototype.padstart/-/string.prototype.padstart-3.0.0.tgz#5bcfad39f4649bb2d031292e19bcf0b510d4b242" - dependencies: - define-properties "^1.1.2" - es-abstract "^1.4.3" - function-bind "^1.0.2" - -string_decoder@~1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8" - dependencies: - safe-buffer "~5.1.0" - -stringify-entities@^1.0.1: - version "1.3.2" - resolved "https://registry.yarnpkg.com/stringify-entities/-/stringify-entities-1.3.2.tgz#a98417e5471fd227b3e45d3db1861c11caf668f7" - dependencies: - character-entities-html4 "^1.0.0" - character-entities-legacy "^1.0.0" - is-alphanumerical "^1.0.0" - is-hexadecimal "^1.0.0" - -strip-ansi@^3.0.0, strip-ansi@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-3.0.1.tgz#6a385fb8853d952d5ff05d0e8aaf94278dc63dcf" - dependencies: - ansi-regex "^2.0.0" - -strip-ansi@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-4.0.0.tgz#a8479022eb1ac368a871389b635262c505ee368f" - dependencies: - ansi-regex "^3.0.0" - -strip-bom@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-2.0.0.tgz#6219a85616520491f35788bdbf1447a99c7e6b0e" - dependencies: - is-utf8 "^0.2.0" - -strip-bom@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-3.0.0.tgz#2334c18e9c759f7bdd56fdef7e9ae3d588e68ed3" - -strip-json-comments@~2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a" - -structured-source@^3.0.2: - version "3.0.2" - resolved "https://registry.yarnpkg.com/structured-source/-/structured-source-3.0.2.tgz#dd802425e0f53dc4a6e7aca3752901a1ccda7af5" - dependencies: - boundary "^1.0.1" - -supports-color@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-2.0.0.tgz#535d045ce6b6363fa40117084629995e9df324c7" - -supports-color@^4.1.0: - version "4.5.0" - resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-4.5.0.tgz#be7a0de484dec5c5cddf8b3d59125044912f635b" - dependencies: - has-flag "^2.0.0" - -supports-color@^5.3.0: - version "5.4.0" - resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.4.0.tgz#1c6b337402c2137605efe19f10fec390f6faab54" - dependencies: - has-flag "^3.0.0" - -table@^3.7.8: - version "3.8.3" - resolved "https://registry.yarnpkg.com/table/-/table-3.8.3.tgz#2bbc542f0fda9861a755d3947fefd8b3f513855f" - dependencies: - ajv "^4.7.0" - ajv-keywords "^1.0.0" - chalk "^1.1.1" - lodash "^4.0.0" - slice-ansi "0.0.4" - string-width "^2.0.0" - -tar@^4: - version "4.4.6" - resolved "https://registry.yarnpkg.com/tar/-/tar-4.4.6.tgz#63110f09c00b4e60ac8bcfe1bf3c8660235fbc9b" - dependencies: - chownr "^1.0.1" - fs-minipass "^1.2.5" - minipass "^2.3.3" - minizlib "^1.1.0" - mkdirp "^0.5.0" - safe-buffer "^5.1.2" - yallist "^3.0.2" - -text-table@^0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4" - -textlint-rule-helper@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/textlint-rule-helper/-/textlint-rule-helper-2.0.0.tgz#95cb4696c95c4258d2e3389e9e64b849f9721382" - dependencies: - unist-util-visit "^1.1.0" - -textlint-rule-stop-words@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/textlint-rule-stop-words/-/textlint-rule-stop-words-1.0.3.tgz#fe2f40cbe5837331b2a09fdec57cc71758093bf0" - dependencies: - lodash "^4.17.4" - split-lines "^1.1.0" - textlint-rule-helper "^2.0.0" - -textlint@^10.2.1: - version "10.2.1" - resolved "https://registry.yarnpkg.com/textlint/-/textlint-10.2.1.tgz#ee22b7967d59cef7c74a04a5f4e8883134e5c79d" - dependencies: - "@textlint/ast-node-types" "^4.0.2" - "@textlint/ast-traverse" "^2.0.8" - "@textlint/feature-flag" "^3.0.4" - "@textlint/fixer-formatter" "^3.0.7" - "@textlint/kernel" "^2.0.9" - "@textlint/linter-formatter" "^3.0.7" - "@textlint/textlint-plugin-markdown" "^4.0.10" - "@textlint/textlint-plugin-text" "^3.0.10" - "@types/bluebird" "^3.5.18" - bluebird "^3.0.5" - debug "^2.1.0" - deep-equal "^1.0.1" - file-entry-cache "^2.0.0" - get-stdin "^5.0.1" - glob "^7.1.1" - interop-require "^1.0.0" - is-file "^1.0.0" - log-symbols "^1.0.2" - map-like "^2.0.0" - md5 "^2.2.1" - mkdirp "^0.5.0" - object-assign "^4.0.1" - optionator "^0.8.0" - path-to-glob-pattern "^1.0.2" - rc-config-loader "^2.0.1" - read-pkg "^1.1.0" - read-pkg-up "^3.0.0" - structured-source "^3.0.2" - try-resolve "^1.0.1" - unique-concat "^0.2.2" - -timed-out@^4.0.0, timed-out@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/timed-out/-/timed-out-4.0.1.tgz#f32eacac5a175bea25d7fab565ab3ed8741ef56f" - -to-vfile@^2.0.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/to-vfile/-/to-vfile-2.2.0.tgz#342d1705e6df526d569b1fc8bfa29f1f36d6c416" - dependencies: - is-buffer "^1.1.4" - vfile "^2.0.0" - x-is-function "^1.0.4" - -too-wordy@^0.1.4: - version "0.1.4" - resolved "https://registry.yarnpkg.com/too-wordy/-/too-wordy-0.1.4.tgz#8e7b20a7b7a4d8fc3759f4e00c4929993d1b12f0" - -tough-cookie@~2.3.3: - version "2.3.4" - resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.3.4.tgz#ec60cee38ac675063ffc97a5c18970578ee83655" - dependencies: - punycode "^1.4.1" - -traverse@^0.6.6: - version "0.6.6" - resolved "https://registry.yarnpkg.com/traverse/-/traverse-0.6.6.tgz#cbdf560fd7b9af632502fed40f918c157ea97137" - -trim-trailing-lines@^1.0.0: - version "1.1.1" - resolved "https://registry.yarnpkg.com/trim-trailing-lines/-/trim-trailing-lines-1.1.1.tgz#e0ec0810fd3c3f1730516b45f49083caaf2774d9" - -trim@0.0.1: - version "0.0.1" - resolved "https://registry.yarnpkg.com/trim/-/trim-0.0.1.tgz#5858547f6b290757ee95cccc666fb50084c460dd" - -trough@^1.0.0: - version "1.0.3" - resolved "https://registry.yarnpkg.com/trough/-/trough-1.0.3.tgz#e29bd1614c6458d44869fc28b255ab7857ef7c24" - -try-resolve@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/try-resolve/-/try-resolve-1.0.1.tgz#cfde6fabd72d63e5797cfaab873abbe8e700e912" - -tunnel-agent@^0.6.0: - version "0.6.0" - resolved "https://registry.yarnpkg.com/tunnel-agent/-/tunnel-agent-0.6.0.tgz#27a5dea06b36b04a0a9966774b290868f0fc40fd" - dependencies: - safe-buffer "^5.0.1" - -tweetnacl@^0.14.3, tweetnacl@~0.14.0: - version "0.14.5" - resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-0.14.5.tgz#5ae68177f192d4456269d108afa93ff8743f4f64" - -type-check@~0.3.2: - version "0.3.2" - resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.3.2.tgz#5884cab512cf1d355e3fb784f30804b2b520db72" - dependencies: - prelude-ls "~1.1.2" - -typedarray@^0.0.6: - version "0.0.6" - resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777" - -unherit@^1.0.4: - version "1.1.1" - resolved "https://registry.yarnpkg.com/unherit/-/unherit-1.1.1.tgz#132748da3e88eab767e08fabfbb89c5e9d28628c" - dependencies: - inherits "^2.0.1" - xtend "^4.0.1" - -unified-args@^5.0.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/unified-args/-/unified-args-5.1.0.tgz#1889200e072998a662e6e84d817d6f4b5f448dd1" - dependencies: - camelcase "^4.0.0" - chalk "^2.0.0" - chokidar "^1.5.1" - json5 "^0.5.1" - minimist "^1.2.0" - text-table "^0.2.0" - unified-engine "^5.1.0" - -unified-engine@^5.1.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/unified-engine/-/unified-engine-5.1.0.tgz#30db83bcc76c821f773bb5a8a491aa0e2471e3d1" - dependencies: - concat-stream "^1.5.1" - debug "^3.1.0" - fault "^1.0.0" - fn-name "^2.0.1" - glob "^7.0.3" - ignore "^3.2.0" - is-empty "^1.0.0" - is-hidden "^1.0.1" - is-object "^1.0.1" - js-yaml "^3.6.1" - load-plugin "^2.0.0" - parse-json "^4.0.0" - to-vfile "^2.0.0" - trough "^1.0.0" - unist-util-inspect "^4.1.2" - vfile-reporter "^4.0.0" - vfile-statistics "^1.1.0" - x-is-function "^1.0.4" - x-is-string "^0.1.0" - xtend "^4.0.1" - -unified-lint-rule@^1.0.1: - version "1.0.3" - resolved "https://registry.yarnpkg.com/unified-lint-rule/-/unified-lint-rule-1.0.3.tgz#e302b0c4a7ac428c0980e049a500e59528001299" - dependencies: - wrapped "^1.0.1" - -unified@^6.0.0, unified@^6.1.6: - version "6.2.0" - resolved "https://registry.yarnpkg.com/unified/-/unified-6.2.0.tgz#7fbd630f719126d67d40c644b7e3f617035f6dba" - dependencies: - bail "^1.0.0" - extend "^3.0.0" - is-plain-obj "^1.1.0" - trough "^1.0.0" - vfile "^2.0.0" - x-is-string "^0.1.0" - -unique-concat@^0.2.2: - version "0.2.2" - resolved "https://registry.yarnpkg.com/unique-concat/-/unique-concat-0.2.2.tgz#9210f9bdcaacc5e1e3929490d7c019df96f18712" - -unist-util-inspect@^4.1.2: - version "4.1.3" - resolved "https://registry.yarnpkg.com/unist-util-inspect/-/unist-util-inspect-4.1.3.tgz#39470e6d77485db285966df78431219aa1287822" - dependencies: - is-empty "^1.0.0" - -unist-util-is@^2.1.2: - version "2.1.2" - resolved "https://registry.yarnpkg.com/unist-util-is/-/unist-util-is-2.1.2.tgz#1193fa8f2bfbbb82150633f3a8d2eb9a1c1d55db" - -unist-util-modify-children@^1.0.0: - version "1.1.2" - resolved "https://registry.yarnpkg.com/unist-util-modify-children/-/unist-util-modify-children-1.1.2.tgz#c7f1b91712554ee59c47a05b551ed3e052a4e2d1" - dependencies: - array-iterate "^1.0.0" - -unist-util-remove-position@^1.0.0: - version "1.1.2" - resolved "https://registry.yarnpkg.com/unist-util-remove-position/-/unist-util-remove-position-1.1.2.tgz#86b5dad104d0bbfbeb1db5f5c92f3570575c12cb" - dependencies: - unist-util-visit "^1.1.0" - -unist-util-stringify-position@^1.0.0, unist-util-stringify-position@^1.1.1: - version "1.1.2" - resolved "https://registry.yarnpkg.com/unist-util-stringify-position/-/unist-util-stringify-position-1.1.2.tgz#3f37fcf351279dcbca7480ab5889bb8a832ee1c6" - -unist-util-visit-parents@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/unist-util-visit-parents/-/unist-util-visit-parents-2.0.1.tgz#63fffc8929027bee04bfef7d2cce474f71cb6217" - dependencies: - unist-util-is "^2.1.2" - -unist-util-visit@^1.1.0, unist-util-visit@^1.1.1, unist-util-visit@^1.1.3: - version "1.4.0" - resolved "https://registry.yarnpkg.com/unist-util-visit/-/unist-util-visit-1.4.0.tgz#1cb763647186dc26f5e1df5db6bd1e48b3cc2fb1" - dependencies: - unist-util-visit-parents "^2.0.0" - -untildify@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/untildify/-/untildify-2.1.0.tgz#17eb2807987f76952e9c0485fc311d06a826a2e0" - dependencies: - os-homedir "^1.0.0" - -unzip-response@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/unzip-response/-/unzip-response-2.0.1.tgz#d2f0f737d16b0615e72a6935ed04214572d56f97" - -url-parse-lax@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/url-parse-lax/-/url-parse-lax-1.0.0.tgz#7af8f303645e9bd79a272e7a14ac68bc0609da73" - dependencies: - prepend-http "^1.0.1" - -url-parse-lax@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/url-parse-lax/-/url-parse-lax-3.0.0.tgz#16b5cafc07dbe3676c1b1999177823d6503acb0c" - dependencies: - prepend-http "^2.0.0" - -url-to-options@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/url-to-options/-/url-to-options-1.0.1.tgz#1505a03a289a48cbd7a434efbaeec5055f5633a9" - -util-deprecate@~1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" - -uuid@^3.1.0: - version "3.3.2" - resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.3.2.tgz#1b4af4955eb3077c501c23872fc6513811587131" - -validate-npm-package-license@^3.0.1: - version "3.0.4" - resolved "https://registry.yarnpkg.com/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz#fc91f6b9c7ba15c857f4cb2c5defeec39d4f410a" - dependencies: - spdx-correct "^3.0.0" - spdx-expression-parse "^3.0.0" - -verror@1.10.0: - version "1.10.0" - resolved "https://registry.yarnpkg.com/verror/-/verror-1.10.0.tgz#3a105ca17053af55d6e270c1f8288682e18da400" - dependencies: - assert-plus "^1.0.0" - core-util-is "1.0.2" - extsprintf "^1.2.0" - -vfile-location@^2.0.0: - version "2.0.3" - resolved "https://registry.yarnpkg.com/vfile-location/-/vfile-location-2.0.3.tgz#083ba80e50968e8d420be49dd1ea9a992131df77" - -vfile-message@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/vfile-message/-/vfile-message-1.0.1.tgz#51a2ccd8a6b97a7980bb34efb9ebde9632e93677" - dependencies: - unist-util-stringify-position "^1.1.1" - -vfile-reporter@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/vfile-reporter/-/vfile-reporter-4.0.0.tgz#ea6f0ae1342f4841573985e05f941736f27de9da" - dependencies: - repeat-string "^1.5.0" - string-width "^1.0.0" - supports-color "^4.1.0" - unist-util-stringify-position "^1.0.0" - vfile-statistics "^1.1.0" - -vfile-statistics@^1.1.0: - version "1.1.1" - resolved "https://registry.yarnpkg.com/vfile-statistics/-/vfile-statistics-1.1.1.tgz#a22fd4eb844c9eaddd781ad3b3246db88375e2e3" - -vfile@^2.0.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/vfile/-/vfile-2.3.0.tgz#e62d8e72b20e83c324bc6c67278ee272488bf84a" - dependencies: - is-buffer "^1.1.4" - replace-ext "1.0.0" - unist-util-stringify-position "^1.0.0" - vfile-message "^1.0.0" - -weasel-words@^0.1.1: - version "0.1.1" - resolved "https://registry.yarnpkg.com/weasel-words/-/weasel-words-0.1.1.tgz#7137946585c73fe44882013853bd000c5d687a4e" - -wide-align@^1.1.0: - version "1.1.3" - resolved "https://registry.yarnpkg.com/wide-align/-/wide-align-1.1.3.tgz#ae074e6bdc0c14a431e804e624549c633b000457" - dependencies: - string-width "^1.0.2 || 2" - -wordwrap@~1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-1.0.0.tgz#27584810891456a4171c8d0226441ade90cbcaeb" - -wrapped@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/wrapped/-/wrapped-1.0.1.tgz#c783d9d807b273e9b01e851680a938c87c907242" - dependencies: - co "3.1.0" - sliced "^1.0.1" - -wrappy@1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" - -write-good@^0.11.1: - version "0.11.3" - resolved "https://registry.yarnpkg.com/write-good/-/write-good-0.11.3.tgz#8eeb5da9a8e155dafb1325d27eba33cb67d24d8c" - dependencies: - adverb-where "0.0.9" - e-prime "^0.10.2" - no-cliches "^0.1.0" - object.assign "^4.0.4" - passive-voice "^0.1.0" - too-wordy "^0.1.4" - weasel-words "^0.1.1" - -write@^0.2.1: - version "0.2.1" - resolved "https://registry.yarnpkg.com/write/-/write-0.2.1.tgz#5fc03828e264cea3fe91455476f7a3c566cb0757" - dependencies: - mkdirp "^0.5.1" - -x-is-function@^1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/x-is-function/-/x-is-function-1.0.4.tgz#5d294dc3d268cbdd062580e0c5df77a391d1fa1e" - -x-is-string@^0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/x-is-string/-/x-is-string-0.1.0.tgz#474b50865af3a49a9c4657f05acd145458f77d82" - -xml-escape@^1.0.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/xml-escape/-/xml-escape-1.1.0.tgz#3904c143fa8eb3a0030ec646d2902a2f1b706c44" - -xtend@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.1.tgz#a5c6d532be656e23db820efb943a1f04998d63af" - -yallist@^3.0.0, yallist@^3.0.2: - version "3.0.2" - resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.0.2.tgz#8452b4bb7e83c7c188d8041c1a837c773d6d8bb9" diff --git a/scripts/install/install_tendermint_arm.sh b/scripts/install/install_tendermint_arm.sh index 2e8d50ae..06a20f14 100644 --- a/scripts/install/install_tendermint_arm.sh +++ b/scripts/install/install_tendermint_arm.sh @@ -1,19 +1,11 @@ #!/usr/bin/env bash -# XXX: this script is intended to be run from -# a fresh Digital Ocean droplet with Ubuntu - -# upon its completion, you must either reset -# your terminal or run `source ~/.profile` - -# as written, this script will install -# tendermint core from master branch REPO=github.com/tendermint/tendermint # change this to a specific release or branch BRANCH=master -GO_VERSION=1.11.2 +GO_VERSION=1.11.4 sudo apt-get update -y diff --git a/scripts/install/install_tendermint_bsd.sh b/scripts/install/install_tendermint_bsd.sh index 0f7ef9b5..c3834058 100644 --- a/scripts/install/install_tendermint_bsd.sh +++ b/scripts/install/install_tendermint_bsd.sh @@ -16,7 +16,7 @@ set BRANCH=master set REPO=github.com/tendermint/tendermint -set GO_VERSION=1.11.2 +set GO_VERSION=1.11.4 sudo pkg update diff --git a/scripts/install/install_tendermint_ubuntu.sh b/scripts/install/install_tendermint_ubuntu.sh index 91ca1598..59ab7a0a 100644 --- a/scripts/install/install_tendermint_ubuntu.sh +++ b/scripts/install/install_tendermint_ubuntu.sh @@ -13,7 +13,7 @@ REPO=github.com/tendermint/tendermint # change this to a specific release or branch BRANCH=master -GO_VERSION=1.11.2 +GO_VERSION=1.11.4 sudo apt-get update -y sudo apt-get install -y make diff --git a/types/tx_test.go b/types/tx_test.go index 3afaaccc..5cdadce5 100644 --- a/types/tx_test.go +++ b/types/tx_test.go @@ -103,9 +103,9 @@ func TestComputeTxsOverhead(t *testing.T) { }{ {Txs{[]byte{6, 6, 6, 6, 6, 6}}, 2}, // one 21 Mb transaction: - {Txs{make([]byte, 22020096, 22020096)}, 5}, + {Txs{make([]byte, 22020096)}, 5}, // two 21Mb/2 sized transactions: - {Txs{make([]byte, 11010048, 11010048), make([]byte, 11010048, 11010048)}, 10}, + {Txs{make([]byte, 11010048), make([]byte, 11010048)}, 10}, {Txs{[]byte{1, 2, 3}, []byte{1, 2, 3}, []byte{4, 5, 6}}, 6}, {Txs{[]byte{100, 5, 64}, []byte{42, 116, 118}, []byte{6, 6, 6}, []byte{6, 6, 6}}, 8}, } From 4d8f29f79c194bd2ba5648a81b1c14074f4cc1f8 Mon Sep 17 00:00:00 2001 From: Anton Kaliaev Date: Mon, 17 Dec 2018 20:52:33 +0400 Subject: [PATCH 164/267] set allow_duplicate_ip to false (#2992) * config: cors options are arrays of strings, not strings Fixes #2980 * docs: update tendermint-core/configuration.html page * set allow_duplicate_ip to false * in `tendermint testnet`, set allow_duplicate_ip to true Refs #2712 * fixes after Ismail's review --- CHANGELOG_PENDING.md | 2 ++ cmd/tendermint/commands/testnet.go | 1 + config/config.go | 2 +- docs/tendermint-core/configuration.md | 2 +- 4 files changed, 5 insertions(+), 2 deletions(-) diff --git a/CHANGELOG_PENDING.md b/CHANGELOG_PENDING.md index 022965bb..e2b87f74 100644 --- a/CHANGELOG_PENDING.md +++ b/CHANGELOG_PENDING.md @@ -9,6 +9,7 @@ Special thanks to external contributors on this release: * CLI/RPC/Config - [cli] Removed `node` `--proxy_app=dummy` option. Use `kvstore` (`persistent_kvstore`) instead. - [cli] Renamed `node` `--proxy_app=nilapp` to `--proxy_app=noop`. +- [config] \#2992 `allow_duplicate_ip` is now set to false * Apps @@ -17,6 +18,7 @@ Special thanks to external contributors on this release: * Blockchain Protocol * P2P Protocol +- multiple connections from the same IP are now disabled by default (see `allow_duplicate_ip` config option) ### FEATURES: diff --git a/cmd/tendermint/commands/testnet.go b/cmd/tendermint/commands/testnet.go index 7e5635ca..10c7d937 100644 --- a/cmd/tendermint/commands/testnet.go +++ b/cmd/tendermint/commands/testnet.go @@ -145,6 +145,7 @@ func testnetFiles(cmd *cobra.Command, args []string) error { nodeDir := filepath.Join(outputDir, fmt.Sprintf("%s%d", nodeDirPrefix, i)) config.SetRoot(nodeDir) config.P2P.AddrBookStrict = false + config.P2P.AllowDuplicateIP = true if populatePersistentPeers { config.P2P.PersistentPeers = persistentPeers } diff --git a/config/config.go b/config/config.go index 2b9c8758..fd262f1e 100644 --- a/config/config.go +++ b/config/config.go @@ -434,7 +434,7 @@ func DefaultP2PConfig() *P2PConfig { RecvRate: 5120000, // 5 mB/s PexReactor: true, SeedMode: false, - AllowDuplicateIP: true, // so non-breaking yet + AllowDuplicateIP: false, HandshakeTimeout: 20 * time.Second, DialTimeout: 3 * time.Second, TestDialFail: false, diff --git a/docs/tendermint-core/configuration.md b/docs/tendermint-core/configuration.md index e66dcf51..0d9a58c4 100644 --- a/docs/tendermint-core/configuration.md +++ b/docs/tendermint-core/configuration.md @@ -170,7 +170,7 @@ seed_mode = false private_peer_ids = "" # Toggle to disable guard against peers connecting from the same ip. -allow_duplicate_ip = true +allow_duplicate_ip = false # Peer connection configuration. handshake_timeout = "20s" From 30f346fe44b416d0597374ec63b3423689cc6f6f Mon Sep 17 00:00:00 2001 From: Zach Date: Mon, 17 Dec 2018 14:02:26 -0500 Subject: [PATCH 165/267] docs: add rpc link to docs navbar and re-org sidebar (#3041) * add rpc to docs navbar and close #3000 * Update config.js --- docs/.vuepress/config.js | 45 +++++++++++++++++++++------------------- 1 file changed, 24 insertions(+), 21 deletions(-) diff --git a/docs/.vuepress/config.js b/docs/.vuepress/config.js index 7c8aeee5..5ecc97cf 100644 --- a/docs/.vuepress/config.js +++ b/docs/.vuepress/config.js @@ -19,7 +19,10 @@ module.exports = { indexName: 'tendermint', debug: false }, - nav: [{ text: "Back to Tendermint", link: "https://tendermint.com" }], + nav: [ + { text: "Back to Tendermint", link: "https://tendermint.com" }, + { text: "RPC", link: "../rpc/" } + ], sidebar: [ { title: "Introduction", @@ -31,6 +34,20 @@ module.exports = { "/introduction/what-is-tendermint" ] }, + { + title: "Apps", + collapsable: false, + children: [ + "/app-dev/getting-started", + "/app-dev/abci-cli", + "/app-dev/app-architecture", + "/app-dev/app-development", + "/app-dev/subscribing-to-events-via-websocket", + "/app-dev/indexing-transactions", + "/app-dev/abci-spec", + "/app-dev/ecosystem" + ] + }, { title: "Tendermint Core", collapsable: false, @@ -49,15 +66,6 @@ module.exports = { "/tendermint-core/validators" ] }, - { - title: "Tools", - collapsable: false, - children: [ - "/tools/", - "/tools/benchmarking", - "/tools/monitoring" - ] - }, { title: "Networks", collapsable: false, @@ -68,18 +76,13 @@ module.exports = { ] }, { - title: "Apps", + title: "Tools", collapsable: false, - children: [ - "/app-dev/getting-started", - "/app-dev/abci-cli", - "/app-dev/app-architecture", - "/app-dev/app-development", - "/app-dev/subscribing-to-events-via-websocket", - "/app-dev/indexing-transactions", - "/app-dev/abci-spec", - "/app-dev/ecosystem" - ] + children: [ + "/tools/", + "/tools/benchmarking", + "/tools/monitoring" + ] }, { title: "Tendermint Spec", From daddebac29d9f1258f7e93282e84709bd16a0acd Mon Sep 17 00:00:00 2001 From: Zach Date: Wed, 19 Dec 2018 12:45:12 -0500 Subject: [PATCH 166/267] circleci: update go version (#3051) --- .circleci/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index dcc0e289..5669384c 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -3,7 +3,7 @@ version: 2 defaults: &defaults working_directory: /go/src/github.com/tendermint/tendermint docker: - - image: circleci/golang:1.11.3 + - image: circleci/golang:1.11.4 environment: GOBIN: /tmp/workspace/bin From c510f823e7858d288e64b2a7c4149e1e155fac13 Mon Sep 17 00:00:00 2001 From: Anton Kaliaev Date: Mon, 17 Dec 2018 20:35:05 +0400 Subject: [PATCH 167/267] mempool: move tx to back, not front (#3036) because we pop txs from the front if the cache is full Refs #3035 --- mempool/mempool.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mempool/mempool.go b/mempool/mempool.go index c5f966c4..3a1921bc 100644 --- a/mempool/mempool.go +++ b/mempool/mempool.go @@ -676,7 +676,7 @@ func (cache *mapTxCache) Push(tx types.Tx) bool { // Use the tx hash in the cache txHash := sha256.Sum256(tx) if moved, exists := cache.map_[txHash]; exists { - cache.list.MoveToFront(moved) + cache.list.MoveToBack(moved) return false } From c6604b5a9b74c8de242ac9b033edb4f833c9a2d1 Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Fri, 21 Dec 2018 16:31:28 -0500 Subject: [PATCH 168/267] changelog and version --- CHANGELOG.md | 10 ++++++++++ version/version.go | 2 +- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0397ebdb..46e9cb37 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,15 @@ # Changelog +## v0.27.4 + +*December 21st, 2018* + +### BUG FIXES: + +- [mempool] [\#3036](https://github.com/tendermint/tendermint/issues/3036) Fix + LRU cache by popping the least recently used item when the cache is full, + not the most recently used one! + ## v0.27.3 *December 16th, 2018* diff --git a/version/version.go b/version/version.go index ace1b41d..3cbdab02 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.27.3" + TMCoreSemVer = "0.27.4" // ABCISemVer is the semantic version of the ABCI library ABCISemVer = "0.15.0" From 41e2eeee9c7bb5be3f5cbe077f649edaa070b00c Mon Sep 17 00:00:00 2001 From: yutianwu Date: Sat, 22 Dec 2018 05:58:27 +0800 Subject: [PATCH 169/267] R4R: Split immutable and mutable parts of priv_validator.json (#2870) * split immutable and mutable parts of priv_validator.json * fix bugs * minor changes * retrig test * delete scripts/wire2amino.go * fix test * fixes from review * privval: remove mtx * rearrange priv_validator.go * upgrade path * write tests for the upgrade * fix for unsafe_reset_all * add test * add reset test --- CHANGELOG_PENDING.md | 1 + cmd/priv_val_server/main.go | 12 +- cmd/tendermint/commands/gen_validator.go | 2 +- cmd/tendermint/commands/init.go | 16 +- .../commands/reset_priv_validator.go | 28 +- cmd/tendermint/commands/show_validator.go | 2 +- cmd/tendermint/commands/testnet.go | 11 +- config/config.go | 70 ++-- config/toml.go | 24 +- config/toml_test.go | 2 +- consensus/common_test.go | 31 +- consensus/replay_test.go | 4 +- consensus/wal_generator.go | 5 +- node/node.go | 21 +- privval/old_priv_validator.go | 80 ++++ privval/old_priv_validator_test.go | 77 ++++ privval/priv_validator.go | 387 +++++++++++------- privval/priv_validator_test.go | 128 ++++-- rpc/test/helpers.go | 5 +- scripts/privValUpgrade.go | 41 ++ scripts/privValUpgrade_test.go | 121 ++++++ scripts/wire2amino.go | 182 -------- 22 files changed, 805 insertions(+), 445 deletions(-) create mode 100644 privval/old_priv_validator.go create mode 100644 privval/old_priv_validator_test.go create mode 100644 scripts/privValUpgrade.go create mode 100644 scripts/privValUpgrade_test.go delete mode 100644 scripts/wire2amino.go diff --git a/CHANGELOG_PENDING.md b/CHANGELOG_PENDING.md index e2b87f74..2fd0d795 100644 --- a/CHANGELOG_PENDING.md +++ b/CHANGELOG_PENDING.md @@ -21,6 +21,7 @@ Special thanks to external contributors on this release: - multiple connections from the same IP are now disabled by default (see `allow_duplicate_ip` config option) ### FEATURES: +- [privval] \#1181 Split immutable and mutable parts of priv_validator.json ### IMPROVEMENTS: diff --git a/cmd/priv_val_server/main.go b/cmd/priv_val_server/main.go index 03aa57f4..54602558 100644 --- a/cmd/priv_val_server/main.go +++ b/cmd/priv_val_server/main.go @@ -13,9 +13,10 @@ import ( func main() { var ( - addr = flag.String("addr", ":26659", "Address of client to connect to") - chainID = flag.String("chain-id", "mychain", "chain id") - privValPath = flag.String("priv", "", "priv val file path") + addr = flag.String("addr", ":26659", "Address of client to connect to") + chainID = flag.String("chain-id", "mychain", "chain id") + privValKeyPath = flag.String("priv-key", "", "priv val key file path") + privValStatePath = flag.String("priv-state", "", "priv val state file path") logger = log.NewTMLogger( log.NewSyncWriter(os.Stdout), @@ -27,10 +28,11 @@ func main() { "Starting private validator", "addr", *addr, "chainID", *chainID, - "privPath", *privValPath, + "privKeyPath", *privValKeyPath, + "privStatePath", *privValStatePath, ) - pv := privval.LoadFilePV(*privValPath) + pv := privval.LoadFilePV(*privValKeyPath, *privValStatePath) rs := privval.NewRemoteSigner( logger, diff --git a/cmd/tendermint/commands/gen_validator.go b/cmd/tendermint/commands/gen_validator.go index 20d43d4d..572bc974 100644 --- a/cmd/tendermint/commands/gen_validator.go +++ b/cmd/tendermint/commands/gen_validator.go @@ -17,7 +17,7 @@ var GenValidatorCmd = &cobra.Command{ } func genValidator(cmd *cobra.Command, args []string) { - pv := privval.GenFilePV("") + pv := privval.GenFilePV("", "") jsbz, err := cdc.MarshalJSON(pv) if err != nil { panic(err) diff --git a/cmd/tendermint/commands/init.go b/cmd/tendermint/commands/init.go index 85ee4491..896bee2e 100644 --- a/cmd/tendermint/commands/init.go +++ b/cmd/tendermint/commands/init.go @@ -4,7 +4,6 @@ import ( "fmt" "github.com/spf13/cobra" - cfg "github.com/tendermint/tendermint/config" cmn "github.com/tendermint/tendermint/libs/common" "github.com/tendermint/tendermint/p2p" @@ -26,15 +25,18 @@ func initFiles(cmd *cobra.Command, args []string) error { func initFilesWithConfig(config *cfg.Config) error { // private validator - privValFile := config.PrivValidatorFile() + privValKeyFile := config.PrivValidatorKeyFile() + privValStateFile := config.PrivValidatorStateFile() var pv *privval.FilePV - if cmn.FileExists(privValFile) { - pv = privval.LoadFilePV(privValFile) - logger.Info("Found private validator", "path", privValFile) + if cmn.FileExists(privValKeyFile) { + pv = privval.LoadFilePV(privValKeyFile, privValStateFile) + logger.Info("Found private validator", "keyFile", privValKeyFile, + "stateFile", privValStateFile) } else { - pv = privval.GenFilePV(privValFile) + pv = privval.GenFilePV(privValKeyFile, privValStateFile) pv.Save() - logger.Info("Generated private validator", "path", privValFile) + logger.Info("Generated private validator", "keyFile", privValKeyFile, + "stateFile", privValStateFile) } nodeKeyFile := config.NodeKeyFile() diff --git a/cmd/tendermint/commands/reset_priv_validator.go b/cmd/tendermint/commands/reset_priv_validator.go index 53d34712..122c2a72 100644 --- a/cmd/tendermint/commands/reset_priv_validator.go +++ b/cmd/tendermint/commands/reset_priv_validator.go @@ -5,6 +5,7 @@ import ( "github.com/spf13/cobra" + cmn "github.com/tendermint/tendermint/libs/common" "github.com/tendermint/tendermint/libs/log" "github.com/tendermint/tendermint/privval" ) @@ -27,36 +28,41 @@ var ResetPrivValidatorCmd = &cobra.Command{ // XXX: this is totally unsafe. // it's only suitable for testnets. func resetAll(cmd *cobra.Command, args []string) { - ResetAll(config.DBDir(), config.P2P.AddrBookFile(), config.PrivValidatorFile(), logger) + ResetAll(config.DBDir(), config.P2P.AddrBookFile(), config.PrivValidatorKeyFile(), + config.PrivValidatorStateFile(), logger) } // XXX: this is totally unsafe. // it's only suitable for testnets. func resetPrivValidator(cmd *cobra.Command, args []string) { - resetFilePV(config.PrivValidatorFile(), logger) + resetFilePV(config.PrivValidatorKeyFile(), config.PrivValidatorStateFile(), logger) } -// ResetAll removes the privValidator and address book files plus all data. +// ResetAll removes address book files plus all data, and resets the privValdiator data. // Exported so other CLI tools can use it. -func ResetAll(dbDir, addrBookFile, privValFile string, logger log.Logger) { - resetFilePV(privValFile, logger) +func ResetAll(dbDir, addrBookFile, privValKeyFile, privValStateFile string, logger log.Logger) { removeAddrBook(addrBookFile, logger) if err := os.RemoveAll(dbDir); err == nil { logger.Info("Removed all blockchain history", "dir", dbDir) } else { logger.Error("Error removing all blockchain history", "dir", dbDir, "err", err) } + // recreate the dbDir since the privVal state needs to live there + cmn.EnsureDir(dbDir, 0700) + resetFilePV(privValKeyFile, privValStateFile, logger) } -func resetFilePV(privValFile string, logger log.Logger) { - if _, err := os.Stat(privValFile); err == nil { - pv := privval.LoadFilePV(privValFile) +func resetFilePV(privValKeyFile, privValStateFile string, logger log.Logger) { + if _, err := os.Stat(privValKeyFile); err == nil { + pv := privval.LoadFilePVEmptyState(privValKeyFile, privValStateFile) pv.Reset() - logger.Info("Reset private validator file to genesis state", "file", privValFile) + logger.Info("Reset private validator file to genesis state", "keyFile", privValKeyFile, + "stateFile", privValStateFile) } else { - pv := privval.GenFilePV(privValFile) + pv := privval.GenFilePV(privValKeyFile, privValStateFile) pv.Save() - logger.Info("Generated private validator file", "file", privValFile) + logger.Info("Generated private validator file", "file", "keyFile", privValKeyFile, + "stateFile", privValStateFile) } } diff --git a/cmd/tendermint/commands/show_validator.go b/cmd/tendermint/commands/show_validator.go index 54765164..78bc0603 100644 --- a/cmd/tendermint/commands/show_validator.go +++ b/cmd/tendermint/commands/show_validator.go @@ -16,7 +16,7 @@ var ShowValidatorCmd = &cobra.Command{ } func showValidator(cmd *cobra.Command, args []string) { - privValidator := privval.LoadOrGenFilePV(config.PrivValidatorFile()) + privValidator := privval.LoadOrGenFilePV(config.PrivValidatorKeyFile(), config.PrivValidatorStateFile()) pubKeyJSONBytes, _ := cdc.MarshalJSON(privValidator.GetPubKey()) fmt.Println(string(pubKeyJSONBytes)) } diff --git a/cmd/tendermint/commands/testnet.go b/cmd/tendermint/commands/testnet.go index 10c7d937..c3ef8619 100644 --- a/cmd/tendermint/commands/testnet.go +++ b/cmd/tendermint/commands/testnet.go @@ -85,11 +85,18 @@ func testnetFiles(cmd *cobra.Command, args []string) error { _ = os.RemoveAll(outputDir) return err } + err = os.MkdirAll(filepath.Join(nodeDir, "data"), nodeDirPerm) + if err != nil { + _ = os.RemoveAll(outputDir) + return err + } initFilesWithConfig(config) - pvFile := filepath.Join(nodeDir, config.BaseConfig.PrivValidator) - pv := privval.LoadFilePV(pvFile) + pvKeyFile := filepath.Join(nodeDir, config.BaseConfig.PrivValidatorKey) + pvStateFile := filepath.Join(nodeDir, config.BaseConfig.PrivValidatorState) + + pv := privval.LoadFilePV(pvKeyFile, pvStateFile) genVals[i] = types.GenesisValidator{ Address: pv.GetPubKey().Address(), PubKey: pv.GetPubKey(), diff --git a/config/config.go b/config/config.go index fd262f1e..fc50d7c6 100644 --- a/config/config.go +++ b/config/config.go @@ -35,15 +35,24 @@ var ( defaultConfigFileName = "config.toml" defaultGenesisJSONName = "genesis.json" - defaultPrivValName = "priv_validator.json" + defaultPrivValKeyName = "priv_validator_key.json" + defaultPrivValStateName = "priv_validator_state.json" + defaultNodeKeyName = "node_key.json" defaultAddrBookName = "addrbook.json" - defaultConfigFilePath = filepath.Join(defaultConfigDir, defaultConfigFileName) - defaultGenesisJSONPath = filepath.Join(defaultConfigDir, defaultGenesisJSONName) - defaultPrivValPath = filepath.Join(defaultConfigDir, defaultPrivValName) - defaultNodeKeyPath = filepath.Join(defaultConfigDir, defaultNodeKeyName) - defaultAddrBookPath = filepath.Join(defaultConfigDir, defaultAddrBookName) + defaultConfigFilePath = filepath.Join(defaultConfigDir, defaultConfigFileName) + defaultGenesisJSONPath = filepath.Join(defaultConfigDir, defaultGenesisJSONName) + defaultPrivValKeyPath = filepath.Join(defaultConfigDir, defaultPrivValKeyName) + defaultPrivValStatePath = filepath.Join(defaultDataDir, defaultPrivValStateName) + + defaultNodeKeyPath = filepath.Join(defaultConfigDir, defaultNodeKeyName) + defaultAddrBookPath = filepath.Join(defaultConfigDir, defaultAddrBookName) +) + +var ( + oldPrivVal = "priv_validator.json" + oldPrivValPath = filepath.Join(defaultConfigDir, oldPrivVal) ) // Config defines the top level configuration for a Tendermint node @@ -160,7 +169,10 @@ type BaseConfig struct { Genesis string `mapstructure:"genesis_file"` // Path to the JSON file containing the private key to use as a validator in the consensus protocol - PrivValidator string `mapstructure:"priv_validator_file"` + PrivValidatorKey string `mapstructure:"priv_validator_key_file"` + + // Path to the JSON file containing the last sign state of a validator + PrivValidatorState string `mapstructure:"priv_validator_state_file"` // TCP or UNIX socket address for Tendermint to listen on for // connections from an external PrivValidator process @@ -183,19 +195,20 @@ type BaseConfig struct { // DefaultBaseConfig returns a default base configuration for a Tendermint node func DefaultBaseConfig() BaseConfig { return BaseConfig{ - Genesis: defaultGenesisJSONPath, - PrivValidator: defaultPrivValPath, - NodeKey: defaultNodeKeyPath, - Moniker: defaultMoniker, - ProxyApp: "tcp://127.0.0.1:26658", - ABCI: "socket", - LogLevel: DefaultPackageLogLevels(), - LogFormat: LogFormatPlain, - ProfListenAddress: "", - FastSync: true, - FilterPeers: false, - DBBackend: "leveldb", - DBPath: "data", + Genesis: defaultGenesisJSONPath, + PrivValidatorKey: defaultPrivValKeyPath, + PrivValidatorState: defaultPrivValStatePath, + NodeKey: defaultNodeKeyPath, + Moniker: defaultMoniker, + ProxyApp: "tcp://127.0.0.1:26658", + ABCI: "socket", + LogLevel: DefaultPackageLogLevels(), + LogFormat: LogFormatPlain, + ProfListenAddress: "", + FastSync: true, + FilterPeers: false, + DBBackend: "leveldb", + DBPath: "data", } } @@ -218,9 +231,20 @@ func (cfg BaseConfig) GenesisFile() string { return rootify(cfg.Genesis, cfg.RootDir) } -// PrivValidatorFile returns the full path to the priv_validator.json file -func (cfg BaseConfig) PrivValidatorFile() string { - return rootify(cfg.PrivValidator, cfg.RootDir) +// PrivValidatorKeyFile returns the full path to the priv_validator_key.json file +func (cfg BaseConfig) PrivValidatorKeyFile() string { + return rootify(cfg.PrivValidatorKey, cfg.RootDir) +} + +// PrivValidatorFile returns the full path to the priv_validator_state.json file +func (cfg BaseConfig) PrivValidatorStateFile() string { + return rootify(cfg.PrivValidatorState, cfg.RootDir) +} + +// OldPrivValidatorFile returns the full path of the priv_validator.json from pre v0.28.0. +// TODO: eventually remove. +func (cfg BaseConfig) OldPrivValidatorFile() string { + return rootify(oldPrivValPath, cfg.RootDir) } // NodeKeyFile returns the full path to the node_key.json file diff --git a/config/toml.go b/config/toml.go index 9aa30451..79ae99be 100644 --- a/config/toml.go +++ b/config/toml.go @@ -95,7 +95,10 @@ log_format = "{{ .BaseConfig.LogFormat }}" genesis_file = "{{ js .BaseConfig.Genesis }}" # Path to the JSON file containing the private key to use as a validator in the consensus protocol -priv_validator_file = "{{ js .BaseConfig.PrivValidator }}" +priv_validator_key_file = "{{ js .BaseConfig.PrivValidatorKey }}" + +# Path to the JSON file containing the last sign state of a validator +priv_validator_state_file = "{{ js .BaseConfig.PrivValidatorState }}" # TCP or UNIX socket address for Tendermint to listen on for # connections from an external PrivValidator process @@ -342,7 +345,8 @@ func ResetTestRoot(testName string) *Config { baseConfig := DefaultBaseConfig() configFilePath := filepath.Join(rootDir, defaultConfigFilePath) genesisFilePath := filepath.Join(rootDir, baseConfig.Genesis) - privFilePath := filepath.Join(rootDir, baseConfig.PrivValidator) + privKeyFilePath := filepath.Join(rootDir, baseConfig.PrivValidatorKey) + privStateFilePath := filepath.Join(rootDir, baseConfig.PrivValidatorState) // Write default config file if missing. if !cmn.FileExists(configFilePath) { @@ -352,7 +356,8 @@ func ResetTestRoot(testName string) *Config { cmn.MustWriteFile(genesisFilePath, []byte(testGenesis), 0644) } // we always overwrite the priv val - cmn.MustWriteFile(privFilePath, []byte(testPrivValidator), 0644) + cmn.MustWriteFile(privKeyFilePath, []byte(testPrivValidatorKey), 0644) + cmn.MustWriteFile(privStateFilePath, []byte(testPrivValidatorState), 0644) config := TestConfig().SetRoot(rootDir) return config @@ -374,7 +379,7 @@ var testGenesis = `{ "app_hash": "" }` -var testPrivValidator = `{ +var testPrivValidatorKey = `{ "address": "A3258DCBF45DCA0DF052981870F2D1441A36D145", "pub_key": { "type": "tendermint/PubKeyEd25519", @@ -383,8 +388,11 @@ var testPrivValidator = `{ "priv_key": { "type": "tendermint/PrivKeyEd25519", "value": "EVkqJO/jIXp3rkASXfh9YnyToYXRXhBr6g9cQVxPFnQBP/5povV4HTjvsy530kybxKHwEi85iU8YL0qQhSYVoQ==" - }, - "last_height": "0", - "last_round": "0", - "last_step": 0 + } +}` + +var testPrivValidatorState = `{ + "height": "0", + "round": "0", + "step": 0 }` diff --git a/config/toml_test.go b/config/toml_test.go index a1637f67..59528db1 100644 --- a/config/toml_test.go +++ b/config/toml_test.go @@ -60,7 +60,7 @@ func TestEnsureTestRoot(t *testing.T) { // TODO: make sure the cfg returned and testconfig are the same! baseConfig := DefaultBaseConfig() - ensureFiles(t, rootDir, defaultDataDir, baseConfig.Genesis, baseConfig.PrivValidator) + ensureFiles(t, rootDir, defaultDataDir, baseConfig.Genesis, baseConfig.PrivValidatorKey, baseConfig.PrivValidatorState) } func checkConfig(configFile string) bool { diff --git a/consensus/common_test.go b/consensus/common_test.go index 46be5cbd..1f6be437 100644 --- a/consensus/common_test.go +++ b/consensus/common_test.go @@ -6,14 +6,18 @@ import ( "fmt" "io/ioutil" "os" - "path" + "path/filepath" "reflect" "sort" "sync" "testing" "time" + "github.com/go-kit/kit/log/term" + abcicli "github.com/tendermint/tendermint/abci/client" + "github.com/tendermint/tendermint/abci/example/counter" + "github.com/tendermint/tendermint/abci/example/kvstore" abci "github.com/tendermint/tendermint/abci/types" bc "github.com/tendermint/tendermint/blockchain" cfg "github.com/tendermint/tendermint/config" @@ -27,11 +31,6 @@ import ( sm "github.com/tendermint/tendermint/state" "github.com/tendermint/tendermint/types" tmtime "github.com/tendermint/tendermint/types/time" - - "github.com/tendermint/tendermint/abci/example/counter" - "github.com/tendermint/tendermint/abci/example/kvstore" - - "github.com/go-kit/kit/log/term" ) const ( @@ -281,9 +280,10 @@ func newConsensusStateWithConfigAndBlockStore(thisConfig *cfg.Config, state sm.S } func loadPrivValidator(config *cfg.Config) *privval.FilePV { - privValidatorFile := config.PrivValidatorFile() - ensureDir(path.Dir(privValidatorFile), 0700) - privValidator := privval.LoadOrGenFilePV(privValidatorFile) + privValidatorKeyFile := config.PrivValidatorKeyFile() + ensureDir(filepath.Dir(privValidatorKeyFile), 0700) + privValidatorStateFile := config.PrivValidatorStateFile() + privValidator := privval.LoadOrGenFilePV(privValidatorKeyFile, privValidatorStateFile) privValidator.Reset() return privValidator } @@ -591,7 +591,7 @@ func randConsensusNet(nValidators int, testName string, tickerFunc func() Timeou for _, opt := range configOpts { opt(thisConfig) } - ensureDir(path.Dir(thisConfig.Consensus.WalFile()), 0700) // dir for wal + ensureDir(filepath.Dir(thisConfig.Consensus.WalFile()), 0700) // dir for wal app := appFunc() vals := types.TM2PB.ValidatorUpdates(state.Validators) app.InitChain(abci.RequestInitChain{Validators: vals}) @@ -612,16 +612,21 @@ func randConsensusNetWithPeers(nValidators, nPeers int, testName string, tickerF stateDB := dbm.NewMemDB() // each state needs its own db state, _ := sm.LoadStateFromDBOrGenesisDoc(stateDB, genDoc) thisConfig := ResetConfig(fmt.Sprintf("%s_%d", testName, i)) - ensureDir(path.Dir(thisConfig.Consensus.WalFile()), 0700) // dir for wal + ensureDir(filepath.Dir(thisConfig.Consensus.WalFile()), 0700) // dir for wal var privVal types.PrivValidator if i < nValidators { privVal = privVals[i] } else { - tempFile, err := ioutil.TempFile("", "priv_validator_") + tempKeyFile, err := ioutil.TempFile("", "priv_validator_key_") if err != nil { panic(err) } - privVal = privval.GenFilePV(tempFile.Name()) + tempStateFile, err := ioutil.TempFile("", "priv_validator_state_") + if err != nil { + panic(err) + } + + privVal = privval.GenFilePV(tempKeyFile.Name(), tempStateFile.Name()) } app := appFunc() diff --git a/consensus/replay_test.go b/consensus/replay_test.go index 7cd32c7a..71b93775 100644 --- a/consensus/replay_test.go +++ b/consensus/replay_test.go @@ -319,7 +319,7 @@ func testHandshakeReplay(t *testing.T, nBlocks int, mode uint) { walFile := tempWALWithData(walBody) config.Consensus.SetWalFile(walFile) - privVal := privval.LoadFilePV(config.PrivValidatorFile()) + privVal := privval.LoadFilePV(config.PrivValidatorKeyFile(), config.PrivValidatorStateFile()) wal, err := NewWAL(walFile) require.NoError(t, err) @@ -633,7 +633,7 @@ func TestInitChainUpdateValidators(t *testing.T) { clientCreator := proxy.NewLocalClientCreator(app) config := ResetConfig("proxy_test_") - privVal := privval.LoadFilePV(config.PrivValidatorFile()) + privVal := privval.LoadFilePV(config.PrivValidatorKeyFile(), config.PrivValidatorStateFile()) stateDB, state, store := stateAndStore(config, privVal.GetPubKey(), 0x0) oldValAddr := state.Validators.Validators[0].Address diff --git a/consensus/wal_generator.go b/consensus/wal_generator.go index 5ff597a5..83861d3e 100644 --- a/consensus/wal_generator.go +++ b/consensus/wal_generator.go @@ -40,8 +40,9 @@ func WALGenerateNBlocks(wr io.Writer, numBlocks int) (err error) { // COPY PASTE FROM node.go WITH A FEW MODIFICATIONS // NOTE: we can't import node package because of circular dependency. // NOTE: we don't do handshake so need to set state.Version.Consensus.App directly. - privValidatorFile := config.PrivValidatorFile() - privValidator := privval.LoadOrGenFilePV(privValidatorFile) + privValidatorKeyFile := config.PrivValidatorKeyFile() + privValidatorStateFile := config.PrivValidatorStateFile() + privValidator := privval.LoadOrGenFilePV(privValidatorKeyFile, privValidatorStateFile) genDoc, err := types.GenesisDocFromFile(config.GenesisFile()) if err != nil { return errors.Wrap(err, "failed to read genesis file") diff --git a/node/node.go b/node/node.go index 993f1cd1..00d9e8a7 100644 --- a/node/node.go +++ b/node/node.go @@ -7,6 +7,7 @@ import ( "net" "net/http" _ "net/http/pprof" + "os" "strings" "time" @@ -86,8 +87,26 @@ func DefaultNewNode(config *cfg.Config, logger log.Logger) (*Node, error) { if err != nil { return nil, err } + + // Convert old PrivValidator if it exists. + oldPrivVal := config.OldPrivValidatorFile() + newPrivValKey := config.PrivValidatorKeyFile() + newPrivValState := config.PrivValidatorStateFile() + if _, err := os.Stat(oldPrivVal); !os.IsNotExist(err) { + oldPV, err := privval.LoadOldFilePV(oldPrivVal) + if err != nil { + return nil, fmt.Errorf("Error reading OldPrivValidator from %v: %v\n", oldPrivVal, err) + } + logger.Info("Upgrading PrivValidator file", + "old", oldPrivVal, + "newKey", newPrivValKey, + "newState", newPrivValState, + ) + oldPV.Upgrade(newPrivValKey, newPrivValState) + } + return NewNode(config, - privval.LoadOrGenFilePV(config.PrivValidatorFile()), + privval.LoadOrGenFilePV(newPrivValKey, newPrivValState), nodeKey, proxy.DefaultClientCreator(config.ProxyApp, config.ABCI, config.DBDir()), DefaultGenesisDocProviderFunc(config), diff --git a/privval/old_priv_validator.go b/privval/old_priv_validator.go new file mode 100644 index 00000000..ec72c183 --- /dev/null +++ b/privval/old_priv_validator.go @@ -0,0 +1,80 @@ +package privval + +import ( + "io/ioutil" + "os" + + "github.com/tendermint/tendermint/crypto" + cmn "github.com/tendermint/tendermint/libs/common" + "github.com/tendermint/tendermint/types" +) + +// OldFilePV is the old version of the FilePV, pre v0.28.0. +type OldFilePV struct { + Address types.Address `json:"address"` + PubKey crypto.PubKey `json:"pub_key"` + LastHeight int64 `json:"last_height"` + LastRound int `json:"last_round"` + LastStep int8 `json:"last_step"` + LastSignature []byte `json:"last_signature,omitempty"` + LastSignBytes cmn.HexBytes `json:"last_signbytes,omitempty"` + PrivKey crypto.PrivKey `json:"priv_key"` + + filePath string +} + +// LoadOldFilePV loads an OldFilePV from the filePath. +func LoadOldFilePV(filePath string) (*OldFilePV, error) { + pvJSONBytes, err := ioutil.ReadFile(filePath) + if err != nil { + return nil, err + } + pv := &OldFilePV{} + err = cdc.UnmarshalJSON(pvJSONBytes, &pv) + if err != nil { + return nil, err + } + + // overwrite pubkey and address for convenience + pv.PubKey = pv.PrivKey.PubKey() + pv.Address = pv.PubKey.Address() + + pv.filePath = filePath + return pv, nil +} + +// Upgrade convets the OldFilePV to the new FilePV, separating the immutable and mutable components, +// and persisting them to the keyFilePath and stateFilePath, respectively. +// It renames the original file by adding ".bak". +func (oldFilePV *OldFilePV) Upgrade(keyFilePath, stateFilePath string) *FilePV { + privKey := oldFilePV.PrivKey + pvKey := FilePVKey{ + PrivKey: privKey, + PubKey: privKey.PubKey(), + Address: privKey.PubKey().Address(), + filePath: keyFilePath, + } + + pvState := FilePVLastSignState{ + Height: oldFilePV.LastHeight, + Round: oldFilePV.LastRound, + Step: oldFilePV.LastStep, + Signature: oldFilePV.LastSignature, + SignBytes: oldFilePV.LastSignBytes, + filePath: stateFilePath, + } + + // Save the new PV files + pv := &FilePV{ + Key: pvKey, + LastSignState: pvState, + } + pv.Save() + + // Rename the old PV file + err := os.Rename(oldFilePV.filePath, oldFilePV.filePath+".bak") + if err != nil { + panic(err) + } + return pv +} diff --git a/privval/old_priv_validator_test.go b/privval/old_priv_validator_test.go new file mode 100644 index 00000000..46391a3f --- /dev/null +++ b/privval/old_priv_validator_test.go @@ -0,0 +1,77 @@ +package privval_test + +import ( + "io/ioutil" + "os" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/tendermint/tendermint/privval" +) + +const oldPrivvalContent = `{ + "address": "1D8089FAFDFAE4A637F3D616E17B92905FA2D91D", + "pub_key": { + "type": "tendermint/PubKeyEd25519", + "value": "r3Yg2AhDZ745CNTpavsGU+mRZ8WpRXqoJuyqjN8mJq0=" + }, + "last_height": "5", + "last_round": "0", + "last_step": 3, + "last_signature": "CTr7b9ZQlrJJf+12rPl5t/YSCUc/KqV7jQogCfFJA24e7hof69X6OMT7eFLVQHyodPjD/QTA298XHV5ejxInDQ==", + "last_signbytes": "750802110500000000000000220B08B398F3E00510F48DA6402A480A20FC258973076512999C3E6839A22E9FBDB1B77CF993E8A9955412A41A59D4CAD312240A20C971B286ACB8AAA6FCA0365EB0A660B189EDC08B46B5AF2995DEFA51A28D215B10013211746573742D636861696E2D533245415533", + "priv_key": { + "type": "tendermint/PrivKeyEd25519", + "value": "7MwvTGEWWjsYwjn2IpRb+GYsWi9nnFsw8jPLLY1UtP6vdiDYCENnvjkI1Olq+wZT6ZFnxalFeqgm7KqM3yYmrQ==" + } +}` + +func TestLoadAndUpgrade(t *testing.T) { + + oldFilePath := initTmpOldFile(t) + defer os.Remove(oldFilePath) + newStateFile, err := ioutil.TempFile("", "priv_validator_state*.json") + defer os.Remove(newStateFile.Name()) + require.NoError(t, err) + newKeyFile, err := ioutil.TempFile("", "priv_validator_key*.json") + defer os.Remove(newKeyFile.Name()) + require.NoError(t, err) + + oldPV, err := privval.LoadOldFilePV(oldFilePath) + assert.NoError(t, err) + newPV := oldPV.Upgrade(newKeyFile.Name(), newStateFile.Name()) + + assertEqualPV(t, oldPV, newPV) + assert.NoError(t, err) + upgradedPV := privval.LoadFilePV(newKeyFile.Name(), newStateFile.Name()) + assertEqualPV(t, oldPV, upgradedPV) + oldPV, err = privval.LoadOldFilePV(oldFilePath + ".bak") + require.NoError(t, err) + assertEqualPV(t, oldPV, upgradedPV) +} + +func assertEqualPV(t *testing.T, oldPV *privval.OldFilePV, newPV *privval.FilePV) { + assert.Equal(t, oldPV.Address, newPV.Key.Address) + assert.Equal(t, oldPV.Address, newPV.GetAddress()) + assert.Equal(t, oldPV.PubKey, newPV.Key.PubKey) + assert.Equal(t, oldPV.PubKey, newPV.GetPubKey()) + assert.Equal(t, oldPV.PrivKey, newPV.Key.PrivKey) + + assert.Equal(t, oldPV.LastHeight, newPV.LastSignState.Height) + assert.Equal(t, oldPV.LastRound, newPV.LastSignState.Round) + assert.Equal(t, oldPV.LastSignature, newPV.LastSignState.Signature) + assert.Equal(t, oldPV.LastSignBytes, newPV.LastSignState.SignBytes) + assert.Equal(t, oldPV.LastStep, newPV.LastSignState.Step) +} + +func initTmpOldFile(t *testing.T) string { + tmpfile, err := ioutil.TempFile("", "priv_validator_*.json") + require.NoError(t, err) + t.Logf("created test file %s", tmpfile.Name()) + _, err = tmpfile.WriteString(oldPrivvalContent) + require.NoError(t, err) + + return tmpfile.Name() +} diff --git a/privval/priv_validator.go b/privval/priv_validator.go index ba777e1f..1ee5b4d8 100644 --- a/privval/priv_validator.go +++ b/privval/priv_validator.go @@ -5,7 +5,6 @@ import ( "errors" "fmt" "io/ioutil" - "sync" "time" "github.com/tendermint/tendermint/crypto" @@ -35,100 +34,90 @@ func voteToStep(vote *types.Vote) int8 { } } -// FilePV implements PrivValidator using data persisted to disk -// to prevent double signing. -// NOTE: the directory containing the pv.filePath must already exist. -// It includes the LastSignature and LastSignBytes so we don't lose the signature -// if the process crashes after signing but before the resulting consensus message is processed. -type FilePV struct { - Address types.Address `json:"address"` - PubKey crypto.PubKey `json:"pub_key"` - LastHeight int64 `json:"last_height"` - LastRound int `json:"last_round"` - LastStep int8 `json:"last_step"` - LastSignature []byte `json:"last_signature,omitempty"` - LastSignBytes cmn.HexBytes `json:"last_signbytes,omitempty"` - PrivKey crypto.PrivKey `json:"priv_key"` +//------------------------------------------------------------------------------- + +// FilePVKey stores the immutable part of PrivValidator. +type FilePVKey struct { + Address types.Address `json:"address"` + PubKey crypto.PubKey `json:"pub_key"` + PrivKey crypto.PrivKey `json:"priv_key"` - // For persistence. - // Overloaded for testing. filePath string - mtx sync.Mutex } -// GetAddress returns the address of the validator. -// Implements PrivValidator. -func (pv *FilePV) GetAddress() types.Address { - return pv.Address -} - -// GetPubKey returns the public key of the validator. -// Implements PrivValidator. -func (pv *FilePV) GetPubKey() crypto.PubKey { - return pv.PubKey -} - -// GenFilePV generates a new validator with randomly generated private key -// and sets the filePath, but does not call Save(). -func GenFilePV(filePath string) *FilePV { - privKey := ed25519.GenPrivKey() - return &FilePV{ - Address: privKey.PubKey().Address(), - PubKey: privKey.PubKey(), - PrivKey: privKey, - LastStep: stepNone, - filePath: filePath, - } -} - -// LoadFilePV loads a FilePV from the filePath. The FilePV handles double -// signing prevention by persisting data to the filePath. If the filePath does -// not exist, the FilePV must be created manually and saved. -func LoadFilePV(filePath string) *FilePV { - pvJSONBytes, err := ioutil.ReadFile(filePath) - if err != nil { - cmn.Exit(err.Error()) - } - pv := &FilePV{} - err = cdc.UnmarshalJSON(pvJSONBytes, &pv) - if err != nil { - cmn.Exit(fmt.Sprintf("Error reading PrivValidator from %v: %v\n", filePath, err)) - } - - // overwrite pubkey and address for convenience - pv.PubKey = pv.PrivKey.PubKey() - pv.Address = pv.PubKey.Address() - - pv.filePath = filePath - return pv -} - -// LoadOrGenFilePV loads a FilePV from the given filePath -// or else generates a new one and saves it to the filePath. -func LoadOrGenFilePV(filePath string) *FilePV { - var pv *FilePV - if cmn.FileExists(filePath) { - pv = LoadFilePV(filePath) - } else { - pv = GenFilePV(filePath) - pv.Save() - } - return pv -} - -// Save persists the FilePV to disk. -func (pv *FilePV) Save() { - pv.mtx.Lock() - defer pv.mtx.Unlock() - pv.save() -} - -func (pv *FilePV) save() { - outFile := pv.filePath +// Save persists the FilePVKey to its filePath. +func (pvKey FilePVKey) Save() { + outFile := pvKey.filePath if outFile == "" { - panic("Cannot save PrivValidator: filePath not set") + panic("Cannot save PrivValidator key: filePath not set") } - jsonBytes, err := cdc.MarshalJSONIndent(pv, "", " ") + + jsonBytes, err := cdc.MarshalJSONIndent(pvKey, "", " ") + if err != nil { + panic(err) + } + err = cmn.WriteFileAtomic(outFile, jsonBytes, 0600) + if err != nil { + panic(err) + } + +} + +//------------------------------------------------------------------------------- + +// FilePVLastSignState stores the mutable part of PrivValidator. +type FilePVLastSignState struct { + Height int64 `json:"height"` + Round int `json:"round"` + Step int8 `json:"step"` + Signature []byte `json:"signature,omitempty"` + SignBytes cmn.HexBytes `json:"signbytes,omitempty"` + + filePath string +} + +// CheckHRS checks the given height, round, step (HRS) against that of the +// FilePVLastSignState. It returns an error if the arguments constitute a regression, +// or if they match but the SignBytes are empty. +// The returned boolean indicates whether the last Signature should be reused - +// it returns true if the HRS matches the arguments and the SignBytes are not empty (indicating +// we have already signed for this HRS, and can reuse the existing signature). +// It panics if the HRS matches the arguments, there's a SignBytes, but no Signature. +func (lss *FilePVLastSignState) CheckHRS(height int64, round int, step int8) (bool, error) { + + if lss.Height > height { + return false, errors.New("Height regression") + } + + if lss.Height == height { + if lss.Round > round { + return false, errors.New("Round regression") + } + + if lss.Round == round { + if lss.Step > step { + return false, errors.New("Step regression") + } else if lss.Step == step { + if lss.SignBytes != nil { + if lss.Signature == nil { + panic("pv: Signature is nil but SignBytes is not!") + } + return true, nil + } + return false, errors.New("No SignBytes found") + } + } + } + return false, nil +} + +// Save persists the FilePvLastSignState to its filePath. +func (lss *FilePVLastSignState) Save() { + outFile := lss.filePath + if outFile == "" { + panic("Cannot save FilePVLastSignState: filePath not set") + } + jsonBytes, err := cdc.MarshalJSONIndent(lss, "", " ") if err != nil { panic(err) } @@ -138,23 +127,115 @@ func (pv *FilePV) save() { } } -// Reset resets all fields in the FilePV. -// NOTE: Unsafe! -func (pv *FilePV) Reset() { - var sig []byte - pv.LastHeight = 0 - pv.LastRound = 0 - pv.LastStep = 0 - pv.LastSignature = sig - pv.LastSignBytes = nil - pv.Save() +//------------------------------------------------------------------------------- + +// FilePV implements PrivValidator using data persisted to disk +// to prevent double signing. +// NOTE: the directories containing pv.Key.filePath and pv.LastSignState.filePath must already exist. +// It includes the LastSignature and LastSignBytes so we don't lose the signature +// if the process crashes after signing but before the resulting consensus message is processed. +type FilePV struct { + Key FilePVKey + LastSignState FilePVLastSignState +} + +// GenFilePV generates a new validator with randomly generated private key +// and sets the filePaths, but does not call Save(). +func GenFilePV(keyFilePath, stateFilePath string) *FilePV { + privKey := ed25519.GenPrivKey() + + return &FilePV{ + Key: FilePVKey{ + Address: privKey.PubKey().Address(), + PubKey: privKey.PubKey(), + PrivKey: privKey, + filePath: keyFilePath, + }, + LastSignState: FilePVLastSignState{ + Step: stepNone, + filePath: stateFilePath, + }, + } +} + +// LoadFilePV loads a FilePV from the filePaths. The FilePV handles double +// signing prevention by persisting data to the stateFilePath. If either file path +// does not exist, the program will exit. +func LoadFilePV(keyFilePath, stateFilePath string) *FilePV { + return loadFilePV(keyFilePath, stateFilePath, true) +} + +// LoadFilePVEmptyState loads a FilePV from the given keyFilePath, with an empty LastSignState. +// If the keyFilePath does not exist, the program will exit. +func LoadFilePVEmptyState(keyFilePath, stateFilePath string) *FilePV { + return loadFilePV(keyFilePath, stateFilePath, false) +} + +// If loadState is true, we load from the stateFilePath. Otherwise, we use an empty LastSignState. +func loadFilePV(keyFilePath, stateFilePath string, loadState bool) *FilePV { + keyJSONBytes, err := ioutil.ReadFile(keyFilePath) + if err != nil { + cmn.Exit(err.Error()) + } + pvKey := FilePVKey{} + err = cdc.UnmarshalJSON(keyJSONBytes, &pvKey) + if err != nil { + cmn.Exit(fmt.Sprintf("Error reading PrivValidator key from %v: %v\n", keyFilePath, err)) + } + + // overwrite pubkey and address for convenience + pvKey.PubKey = pvKey.PrivKey.PubKey() + pvKey.Address = pvKey.PubKey.Address() + pvKey.filePath = keyFilePath + + pvState := FilePVLastSignState{} + if loadState { + stateJSONBytes, err := ioutil.ReadFile(stateFilePath) + if err != nil { + cmn.Exit(err.Error()) + } + err = cdc.UnmarshalJSON(stateJSONBytes, &pvState) + if err != nil { + cmn.Exit(fmt.Sprintf("Error reading PrivValidator state from %v: %v\n", stateFilePath, err)) + } + } + + pvState.filePath = stateFilePath + + return &FilePV{ + Key: pvKey, + LastSignState: pvState, + } +} + +// LoadOrGenFilePV loads a FilePV from the given filePaths +// or else generates a new one and saves it to the filePaths. +func LoadOrGenFilePV(keyFilePath, stateFilePath string) *FilePV { + var pv *FilePV + if cmn.FileExists(keyFilePath) { + pv = LoadFilePV(keyFilePath, stateFilePath) + } else { + pv = GenFilePV(keyFilePath, stateFilePath) + pv.Save() + } + return pv +} + +// GetAddress returns the address of the validator. +// Implements PrivValidator. +func (pv *FilePV) GetAddress() types.Address { + return pv.Key.Address +} + +// GetPubKey returns the public key of the validator. +// Implements PrivValidator. +func (pv *FilePV) GetPubKey() crypto.PubKey { + return pv.Key.PubKey } // SignVote signs a canonical representation of the vote, along with the // chainID. Implements PrivValidator. func (pv *FilePV) SignVote(chainID string, vote *types.Vote) error { - pv.mtx.Lock() - defer pv.mtx.Unlock() if err := pv.signVote(chainID, vote); err != nil { return fmt.Errorf("Error signing vote: %v", err) } @@ -164,65 +245,63 @@ func (pv *FilePV) SignVote(chainID string, vote *types.Vote) error { // SignProposal signs a canonical representation of the proposal, along with // the chainID. Implements PrivValidator. func (pv *FilePV) SignProposal(chainID string, proposal *types.Proposal) error { - pv.mtx.Lock() - defer pv.mtx.Unlock() if err := pv.signProposal(chainID, proposal); err != nil { return fmt.Errorf("Error signing proposal: %v", err) } return nil } -// returns error if HRS regression or no LastSignBytes. returns true if HRS is unchanged -func (pv *FilePV) checkHRS(height int64, round int, step int8) (bool, error) { - if pv.LastHeight > height { - return false, errors.New("Height regression") - } - - if pv.LastHeight == height { - if pv.LastRound > round { - return false, errors.New("Round regression") - } - - if pv.LastRound == round { - if pv.LastStep > step { - return false, errors.New("Step regression") - } else if pv.LastStep == step { - if pv.LastSignBytes != nil { - if pv.LastSignature == nil { - panic("pv: LastSignature is nil but LastSignBytes is not!") - } - return true, nil - } - return false, errors.New("No LastSignature found") - } - } - } - return false, nil +// Save persists the FilePV to disk. +func (pv *FilePV) Save() { + pv.Key.Save() + pv.LastSignState.Save() } +// Reset resets all fields in the FilePV. +// NOTE: Unsafe! +func (pv *FilePV) Reset() { + var sig []byte + pv.LastSignState.Height = 0 + pv.LastSignState.Round = 0 + pv.LastSignState.Step = 0 + pv.LastSignState.Signature = sig + pv.LastSignState.SignBytes = nil + pv.Save() +} + +// String returns a string representation of the FilePV. +func (pv *FilePV) String() string { + return fmt.Sprintf("PrivValidator{%v LH:%v, LR:%v, LS:%v}", pv.GetAddress(), pv.LastSignState.Height, pv.LastSignState.Round, pv.LastSignState.Step) +} + +//------------------------------------------------------------------------------------ + // signVote checks if the vote is good to sign and sets the vote signature. // It may need to set the timestamp as well if the vote is otherwise the same as // a previously signed vote (ie. we crashed after signing but before the vote hit the WAL). func (pv *FilePV) signVote(chainID string, vote *types.Vote) error { height, round, step := vote.Height, vote.Round, voteToStep(vote) - signBytes := vote.SignBytes(chainID) - sameHRS, err := pv.checkHRS(height, round, step) + lss := pv.LastSignState + + sameHRS, err := lss.CheckHRS(height, round, step) if err != nil { return err } + signBytes := vote.SignBytes(chainID) + // We might crash before writing to the wal, // causing us to try to re-sign for the same HRS. // If signbytes are the same, use the last signature. // If they only differ by timestamp, use last timestamp and signature // Otherwise, return error if sameHRS { - if bytes.Equal(signBytes, pv.LastSignBytes) { - vote.Signature = pv.LastSignature - } else if timestamp, ok := checkVotesOnlyDifferByTimestamp(pv.LastSignBytes, signBytes); ok { + if bytes.Equal(signBytes, lss.SignBytes) { + vote.Signature = lss.Signature + } else if timestamp, ok := checkVotesOnlyDifferByTimestamp(lss.SignBytes, signBytes); ok { vote.Timestamp = timestamp - vote.Signature = pv.LastSignature + vote.Signature = lss.Signature } else { err = fmt.Errorf("Conflicting data") } @@ -230,7 +309,7 @@ func (pv *FilePV) signVote(chainID string, vote *types.Vote) error { } // It passed the checks. Sign the vote - sig, err := pv.PrivKey.Sign(signBytes) + sig, err := pv.Key.PrivKey.Sign(signBytes) if err != nil { return err } @@ -244,24 +323,27 @@ func (pv *FilePV) signVote(chainID string, vote *types.Vote) error { // a previously signed proposal ie. we crashed after signing but before the proposal hit the WAL). func (pv *FilePV) signProposal(chainID string, proposal *types.Proposal) error { height, round, step := proposal.Height, proposal.Round, stepPropose - signBytes := proposal.SignBytes(chainID) - sameHRS, err := pv.checkHRS(height, round, step) + lss := pv.LastSignState + + sameHRS, err := lss.CheckHRS(height, round, step) if err != nil { return err } + signBytes := proposal.SignBytes(chainID) + // We might crash before writing to the wal, // causing us to try to re-sign for the same HRS. // If signbytes are the same, use the last signature. // If they only differ by timestamp, use last timestamp and signature // Otherwise, return error if sameHRS { - if bytes.Equal(signBytes, pv.LastSignBytes) { - proposal.Signature = pv.LastSignature - } else if timestamp, ok := checkProposalsOnlyDifferByTimestamp(pv.LastSignBytes, signBytes); ok { + if bytes.Equal(signBytes, lss.SignBytes) { + proposal.Signature = lss.Signature + } else if timestamp, ok := checkProposalsOnlyDifferByTimestamp(lss.SignBytes, signBytes); ok { proposal.Timestamp = timestamp - proposal.Signature = pv.LastSignature + proposal.Signature = lss.Signature } else { err = fmt.Errorf("Conflicting data") } @@ -269,7 +351,7 @@ func (pv *FilePV) signProposal(chainID string, proposal *types.Proposal) error { } // It passed the checks. Sign the proposal - sig, err := pv.PrivKey.Sign(signBytes) + sig, err := pv.Key.PrivKey.Sign(signBytes) if err != nil { return err } @@ -282,20 +364,15 @@ func (pv *FilePV) signProposal(chainID string, proposal *types.Proposal) error { func (pv *FilePV) saveSigned(height int64, round int, step int8, signBytes []byte, sig []byte) { - pv.LastHeight = height - pv.LastRound = round - pv.LastStep = step - pv.LastSignature = sig - pv.LastSignBytes = signBytes - pv.save() + pv.LastSignState.Height = height + pv.LastSignState.Round = round + pv.LastSignState.Step = step + pv.LastSignState.Signature = sig + pv.LastSignState.SignBytes = signBytes + pv.LastSignState.Save() } -// String returns a string representation of the FilePV. -func (pv *FilePV) String() string { - return fmt.Sprintf("PrivValidator{%v LH:%v, LR:%v, LS:%v}", pv.GetAddress(), pv.LastHeight, pv.LastRound, pv.LastStep) -} - -//------------------------------------- +//----------------------------------------------------------------------------------------- // returns the timestamp from the lastSignBytes. // returns true if the only difference in the votes is their timestamp. diff --git a/privval/priv_validator_test.go b/privval/priv_validator_test.go index 4f4eed97..06d75a80 100644 --- a/privval/priv_validator_test.go +++ b/privval/priv_validator_test.go @@ -18,36 +18,100 @@ import ( func TestGenLoadValidator(t *testing.T) { assert := assert.New(t) - tempFile, err := ioutil.TempFile("", "priv_validator_") + tempKeyFile, err := ioutil.TempFile("", "priv_validator_key_") require.Nil(t, err) - privVal := GenFilePV(tempFile.Name()) + tempStateFile, err := ioutil.TempFile("", "priv_validator_state_") + require.Nil(t, err) + + privVal := GenFilePV(tempKeyFile.Name(), tempStateFile.Name()) height := int64(100) - privVal.LastHeight = height + privVal.LastSignState.Height = height privVal.Save() addr := privVal.GetAddress() - privVal = LoadFilePV(tempFile.Name()) + privVal = LoadFilePV(tempKeyFile.Name(), tempStateFile.Name()) assert.Equal(addr, privVal.GetAddress(), "expected privval addr to be the same") - assert.Equal(height, privVal.LastHeight, "expected privval.LastHeight to have been saved") + assert.Equal(height, privVal.LastSignState.Height, "expected privval.LastHeight to have been saved") +} + +func TestResetValidator(t *testing.T) { + tempKeyFile, err := ioutil.TempFile("", "priv_validator_key_") + require.Nil(t, err) + tempStateFile, err := ioutil.TempFile("", "priv_validator_state_") + require.Nil(t, err) + + privVal := GenFilePV(tempKeyFile.Name(), tempStateFile.Name()) + emptyState := FilePVLastSignState{filePath: tempStateFile.Name()} + + // new priv val has empty state + assert.Equal(t, privVal.LastSignState, emptyState) + + // test vote + height, round := int64(10), 1 + voteType := byte(types.PrevoteType) + blockID := types.BlockID{[]byte{1, 2, 3}, types.PartSetHeader{}} + vote := newVote(privVal.Key.Address, 0, height, round, voteType, blockID) + err = privVal.SignVote("mychainid", vote) + assert.NoError(t, err, "expected no error signing vote") + + // priv val after signing is not same as empty + assert.NotEqual(t, privVal.LastSignState, emptyState) + + // priv val after reset is same as empty + privVal.Reset() + assert.Equal(t, privVal.LastSignState, emptyState) } func TestLoadOrGenValidator(t *testing.T) { assert := assert.New(t) - tempFile, err := ioutil.TempFile("", "priv_validator_") + tempKeyFile, err := ioutil.TempFile("", "priv_validator_key_") require.Nil(t, err) - tempFilePath := tempFile.Name() - if err := os.Remove(tempFilePath); err != nil { + tempStateFile, err := ioutil.TempFile("", "priv_validator_state_") + require.Nil(t, err) + + tempKeyFilePath := tempKeyFile.Name() + if err := os.Remove(tempKeyFilePath); err != nil { t.Error(err) } - privVal := LoadOrGenFilePV(tempFilePath) + tempStateFilePath := tempStateFile.Name() + if err := os.Remove(tempStateFilePath); err != nil { + t.Error(err) + } + + privVal := LoadOrGenFilePV(tempKeyFilePath, tempStateFilePath) addr := privVal.GetAddress() - privVal = LoadOrGenFilePV(tempFilePath) + privVal = LoadOrGenFilePV(tempKeyFilePath, tempStateFilePath) assert.Equal(addr, privVal.GetAddress(), "expected privval addr to be the same") } -func TestUnmarshalValidator(t *testing.T) { +func TestUnmarshalValidatorState(t *testing.T) { + assert, require := assert.New(t), require.New(t) + + // create some fixed values + serialized := `{ + "height": "1", + "round": "1", + "step": 1 + }` + + val := FilePVLastSignState{} + err := cdc.UnmarshalJSON([]byte(serialized), &val) + require.Nil(err, "%+v", err) + + // make sure the values match + assert.EqualValues(val.Height, 1) + assert.EqualValues(val.Round, 1) + assert.EqualValues(val.Step, 1) + + // export it and make sure it is the same + out, err := cdc.MarshalJSON(val) + require.Nil(err, "%+v", err) + assert.JSONEq(serialized, string(out)) +} + +func TestUnmarshalValidatorKey(t *testing.T) { assert, require := assert.New(t), require.New(t) // create some fixed values @@ -67,22 +131,19 @@ func TestUnmarshalValidator(t *testing.T) { "type": "tendermint/PubKeyEd25519", "value": "%s" }, - "last_height": "0", - "last_round": "0", - "last_step": 0, "priv_key": { "type": "tendermint/PrivKeyEd25519", "value": "%s" } }`, addr, pubB64, privB64) - val := FilePV{} + val := FilePVKey{} err := cdc.UnmarshalJSON([]byte(serialized), &val) require.Nil(err, "%+v", err) // make sure the values match - assert.EqualValues(addr, val.GetAddress()) - assert.EqualValues(pubKey, val.GetPubKey()) + assert.EqualValues(addr, val.Address) + assert.EqualValues(pubKey, val.PubKey) assert.EqualValues(privKey, val.PrivKey) // export it and make sure it is the same @@ -94,9 +155,12 @@ func TestUnmarshalValidator(t *testing.T) { func TestSignVote(t *testing.T) { assert := assert.New(t) - tempFile, err := ioutil.TempFile("", "priv_validator_") + tempKeyFile, err := ioutil.TempFile("", "priv_validator_key_") require.Nil(t, err) - privVal := GenFilePV(tempFile.Name()) + tempStateFile, err := ioutil.TempFile("", "priv_validator_state_") + require.Nil(t, err) + + privVal := GenFilePV(tempKeyFile.Name(), tempStateFile.Name()) block1 := types.BlockID{[]byte{1, 2, 3}, types.PartSetHeader{}} block2 := types.BlockID{[]byte{3, 2, 1}, types.PartSetHeader{}} @@ -104,7 +168,7 @@ func TestSignVote(t *testing.T) { voteType := byte(types.PrevoteType) // sign a vote for first time - vote := newVote(privVal.Address, 0, height, round, voteType, block1) + vote := newVote(privVal.Key.Address, 0, height, round, voteType, block1) err = privVal.SignVote("mychainid", vote) assert.NoError(err, "expected no error signing vote") @@ -114,10 +178,10 @@ func TestSignVote(t *testing.T) { // now try some bad votes cases := []*types.Vote{ - newVote(privVal.Address, 0, height, round-1, voteType, block1), // round regression - newVote(privVal.Address, 0, height-1, round, voteType, block1), // height regression - newVote(privVal.Address, 0, height-2, round+4, voteType, block1), // height regression and different round - newVote(privVal.Address, 0, height, round, voteType, block2), // different block + newVote(privVal.Key.Address, 0, height, round-1, voteType, block1), // round regression + newVote(privVal.Key.Address, 0, height-1, round, voteType, block1), // height regression + newVote(privVal.Key.Address, 0, height-2, round+4, voteType, block1), // height regression and different round + newVote(privVal.Key.Address, 0, height, round, voteType, block2), // different block } for _, c := range cases { @@ -136,9 +200,12 @@ func TestSignVote(t *testing.T) { func TestSignProposal(t *testing.T) { assert := assert.New(t) - tempFile, err := ioutil.TempFile("", "priv_validator_") + tempKeyFile, err := ioutil.TempFile("", "priv_validator_key_") require.Nil(t, err) - privVal := GenFilePV(tempFile.Name()) + tempStateFile, err := ioutil.TempFile("", "priv_validator_state_") + require.Nil(t, err) + + privVal := GenFilePV(tempKeyFile.Name(), tempStateFile.Name()) block1 := types.BlockID{[]byte{1, 2, 3}, types.PartSetHeader{5, []byte{1, 2, 3}}} block2 := types.BlockID{[]byte{3, 2, 1}, types.PartSetHeader{10, []byte{3, 2, 1}}} @@ -175,9 +242,12 @@ func TestSignProposal(t *testing.T) { } func TestDifferByTimestamp(t *testing.T) { - tempFile, err := ioutil.TempFile("", "priv_validator_") + tempKeyFile, err := ioutil.TempFile("", "priv_validator_key_") require.Nil(t, err) - privVal := GenFilePV(tempFile.Name()) + tempStateFile, err := ioutil.TempFile("", "priv_validator_state_") + require.Nil(t, err) + + privVal := GenFilePV(tempKeyFile.Name(), tempStateFile.Name()) block1 := types.BlockID{[]byte{1, 2, 3}, types.PartSetHeader{5, []byte{1, 2, 3}}} height, round := int64(10), 1 @@ -208,7 +278,7 @@ func TestDifferByTimestamp(t *testing.T) { { voteType := byte(types.PrevoteType) blockID := types.BlockID{[]byte{1, 2, 3}, types.PartSetHeader{}} - vote := newVote(privVal.Address, 0, height, round, voteType, blockID) + vote := newVote(privVal.Key.Address, 0, height, round, voteType, blockID) err := privVal.SignVote("mychainid", vote) assert.NoError(t, err, "expected no error signing vote") diff --git a/rpc/test/helpers.go b/rpc/test/helpers.go index e68ec149..b89c0a17 100644 --- a/rpc/test/helpers.go +++ b/rpc/test/helpers.go @@ -119,8 +119,9 @@ func NewTendermint(app abci.Application) *nm.Node { config := GetConfig() logger := log.NewTMLogger(log.NewSyncWriter(os.Stdout)) logger = log.NewFilter(logger, log.AllowError()) - pvFile := config.PrivValidatorFile() - pv := privval.LoadOrGenFilePV(pvFile) + pvKeyFile := config.PrivValidatorKeyFile() + pvKeyStateFile := config.PrivValidatorStateFile() + pv := privval.LoadOrGenFilePV(pvKeyFile, pvKeyStateFile) papp := proxy.NewLocalClientCreator(app) nodeKey, err := p2p.LoadOrGenNodeKey(config.NodeKeyFile()) if err != nil { diff --git a/scripts/privValUpgrade.go b/scripts/privValUpgrade.go new file mode 100644 index 00000000..72ce505e --- /dev/null +++ b/scripts/privValUpgrade.go @@ -0,0 +1,41 @@ +package main + +import ( + "fmt" + "os" + + "github.com/tendermint/tendermint/libs/log" + "github.com/tendermint/tendermint/privval" +) + +var ( + logger = log.NewTMLogger(log.NewSyncWriter(os.Stdout)) +) + +func main() { + args := os.Args[1:] + if len(args) != 3 { + fmt.Println("Expected three args: ") + fmt.Println("Eg. ~/.tendermint/config/priv_validator.json ~/.tendermint/config/priv_validator_key.json ~/.tendermint/data/priv_validator_state.json") + os.Exit(1) + } + err := loadAndUpgrade(args[0], args[1], args[2]) + if err != nil { + fmt.Println(err) + os.Exit(1) + } +} + +func loadAndUpgrade(oldPVPath, newPVKeyPath, newPVStatePath string) error { + oldPV, err := privval.LoadOldFilePV(oldPVPath) + if err != nil { + return fmt.Errorf("Error reading OldPrivValidator from %v: %v\n", oldPVPath, err) + } + logger.Info("Upgrading PrivValidator file", + "old", oldPVPath, + "newKey", newPVKeyPath, + "newState", newPVStatePath, + ) + oldPV.Upgrade(newPVKeyPath, newPVStatePath) + return nil +} diff --git a/scripts/privValUpgrade_test.go b/scripts/privValUpgrade_test.go new file mode 100644 index 00000000..bac4d315 --- /dev/null +++ b/scripts/privValUpgrade_test.go @@ -0,0 +1,121 @@ +package main + +import ( + "fmt" + "io/ioutil" + "os" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/tendermint/tendermint/privval" +) + +const oldPrivvalContent = `{ + "address": "1D8089FAFDFAE4A637F3D616E17B92905FA2D91D", + "pub_key": { + "type": "tendermint/PubKeyEd25519", + "value": "r3Yg2AhDZ745CNTpavsGU+mRZ8WpRXqoJuyqjN8mJq0=" + }, + "last_height": "5", + "last_round": "0", + "last_step": 3, + "last_signature": "CTr7b9ZQlrJJf+12rPl5t/YSCUc/KqV7jQogCfFJA24e7hof69X6OMT7eFLVQHyodPjD/QTA298XHV5ejxInDQ==", + "last_signbytes": "750802110500000000000000220B08B398F3E00510F48DA6402A480A20FC258973076512999C3E6839A22E9FBDB1B77CF993E8A9955412A41A59D4CAD312240A20C971B286ACB8AAA6FCA0365EB0A660B189EDC08B46B5AF2995DEFA51A28D215B10013211746573742D636861696E2D533245415533", + "priv_key": { + "type": "tendermint/PrivKeyEd25519", + "value": "7MwvTGEWWjsYwjn2IpRb+GYsWi9nnFsw8jPLLY1UtP6vdiDYCENnvjkI1Olq+wZT6ZFnxalFeqgm7KqM3yYmrQ==" + } +}` + +func TestLoadAndUpgrade(t *testing.T) { + + oldFilePath := initTmpOldFile(t) + defer os.Remove(oldFilePath) + newStateFile, err := ioutil.TempFile("", "priv_validator_state*.json") + defer os.Remove(newStateFile.Name()) + require.NoError(t, err) + newKeyFile, err := ioutil.TempFile("", "priv_validator_key*.json") + defer os.Remove(newKeyFile.Name()) + require.NoError(t, err) + emptyOldFile, err := ioutil.TempFile("", "priv_validator_empty*.json") + require.NoError(t, err) + defer os.Remove(emptyOldFile.Name()) + + type args struct { + oldPVPath string + newPVKeyPath string + newPVStatePath string + } + tests := []struct { + name string + args args + wantErr bool + wantPanic bool + }{ + {"successful upgrade", + args{oldPVPath: oldFilePath, newPVKeyPath: newKeyFile.Name(), newPVStatePath: newStateFile.Name()}, + false, false, + }, + {"unsuccessful upgrade: empty old privval file", + args{oldPVPath: emptyOldFile.Name(), newPVKeyPath: newKeyFile.Name(), newPVStatePath: newStateFile.Name()}, + true, false, + }, + {"unsuccessful upgrade: invalid new paths (1/3)", + args{oldPVPath: oldFilePath, newPVKeyPath: "", newPVStatePath: newStateFile.Name()}, + false, true, + }, + {"unsuccessful upgrade: invalid new paths (2/3)", + args{oldPVPath: oldFilePath, newPVKeyPath: newKeyFile.Name(), newPVStatePath: ""}, + false, true, + }, + {"unsuccessful upgrade: invalid new paths (3/3)", + args{oldPVPath: oldFilePath, newPVKeyPath: "", newPVStatePath: ""}, + false, true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + // need to re-write the file everytime because upgrading renames it + err := ioutil.WriteFile(oldFilePath, []byte(oldPrivvalContent), 0600) + require.NoError(t, err) + if tt.wantPanic { + require.Panics(t, func() { loadAndUpgrade(tt.args.oldPVPath, tt.args.newPVKeyPath, tt.args.newPVStatePath) }) + } else { + err = loadAndUpgrade(tt.args.oldPVPath, tt.args.newPVKeyPath, tt.args.newPVStatePath) + if tt.wantErr { + assert.Error(t, err) + fmt.Println("ERR", err) + } else { + assert.NoError(t, err) + upgradedPV := privval.LoadFilePV(tt.args.newPVKeyPath, tt.args.newPVStatePath) + oldPV, err := privval.LoadOldFilePV(tt.args.oldPVPath + ".bak") + require.NoError(t, err) + + assert.Equal(t, oldPV.Address, upgradedPV.Key.Address) + assert.Equal(t, oldPV.Address, upgradedPV.GetAddress()) + assert.Equal(t, oldPV.PubKey, upgradedPV.Key.PubKey) + assert.Equal(t, oldPV.PubKey, upgradedPV.GetPubKey()) + assert.Equal(t, oldPV.PrivKey, upgradedPV.Key.PrivKey) + + assert.Equal(t, oldPV.LastHeight, upgradedPV.LastSignState.Height) + assert.Equal(t, oldPV.LastRound, upgradedPV.LastSignState.Round) + assert.Equal(t, oldPV.LastSignature, upgradedPV.LastSignState.Signature) + assert.Equal(t, oldPV.LastSignBytes, upgradedPV.LastSignState.SignBytes) + assert.Equal(t, oldPV.LastStep, upgradedPV.LastSignState.Step) + + } + } + }) + } +} + +func initTmpOldFile(t *testing.T) string { + tmpfile, err := ioutil.TempFile("", "priv_validator_*.json") + require.NoError(t, err) + t.Logf("created test file %s", tmpfile.Name()) + _, err = tmpfile.WriteString(oldPrivvalContent) + require.NoError(t, err) + + return tmpfile.Name() +} diff --git a/scripts/wire2amino.go b/scripts/wire2amino.go deleted file mode 100644 index 26069b50..00000000 --- a/scripts/wire2amino.go +++ /dev/null @@ -1,182 +0,0 @@ -package main - -import ( - "encoding/json" - "fmt" - "io/ioutil" - "os" - "path/filepath" - "time" - - "github.com/tendermint/go-amino" - "github.com/tendermint/tendermint/crypto/ed25519" - cryptoAmino "github.com/tendermint/tendermint/crypto/encoding/amino" - - cmn "github.com/tendermint/tendermint/libs/common" - - "github.com/tendermint/tendermint/p2p" - "github.com/tendermint/tendermint/privval" - "github.com/tendermint/tendermint/types" -) - -type GenesisValidator struct { - PubKey Data `json:"pub_key"` - Power int64 `json:"power"` - Name string `json:"name"` -} - -type Genesis struct { - GenesisTime time.Time `json:"genesis_time"` - ChainID string `json:"chain_id"` - ConsensusParams *types.ConsensusParams `json:"consensus_params,omitempty"` - Validators []GenesisValidator `json:"validators"` - AppHash cmn.HexBytes `json:"app_hash"` - AppState json.RawMessage `json:"app_state,omitempty"` - AppOptions json.RawMessage `json:"app_options,omitempty"` // DEPRECATED -} - -type NodeKey struct { - PrivKey Data `json:"priv_key"` -} - -type PrivVal struct { - Address cmn.HexBytes `json:"address"` - LastHeight int64 `json:"last_height"` - LastRound int `json:"last_round"` - LastStep int8 `json:"last_step"` - PubKey Data `json:"pub_key"` - PrivKey Data `json:"priv_key"` -} - -type Data struct { - Type string `json:"type"` - Data cmn.HexBytes `json:"data"` -} - -func convertNodeKey(cdc *amino.Codec, jsonBytes []byte) ([]byte, error) { - var nodeKey NodeKey - err := json.Unmarshal(jsonBytes, &nodeKey) - if err != nil { - return nil, err - } - - var privKey ed25519.PrivKeyEd25519 - copy(privKey[:], nodeKey.PrivKey.Data) - - nodeKeyNew := p2p.NodeKey{privKey} - - bz, err := cdc.MarshalJSON(nodeKeyNew) - if err != nil { - return nil, err - } - return bz, nil -} - -func convertPrivVal(cdc *amino.Codec, jsonBytes []byte) ([]byte, error) { - var privVal PrivVal - err := json.Unmarshal(jsonBytes, &privVal) - if err != nil { - return nil, err - } - - var privKey ed25519.PrivKeyEd25519 - copy(privKey[:], privVal.PrivKey.Data) - - var pubKey ed25519.PubKeyEd25519 - copy(pubKey[:], privVal.PubKey.Data) - - privValNew := privval.FilePV{ - Address: pubKey.Address(), - PubKey: pubKey, - LastHeight: privVal.LastHeight, - LastRound: privVal.LastRound, - LastStep: privVal.LastStep, - PrivKey: privKey, - } - - bz, err := cdc.MarshalJSON(privValNew) - if err != nil { - return nil, err - } - return bz, nil -} - -func convertGenesis(cdc *amino.Codec, jsonBytes []byte) ([]byte, error) { - var genesis Genesis - err := json.Unmarshal(jsonBytes, &genesis) - if err != nil { - return nil, err - } - - genesisNew := types.GenesisDoc{ - GenesisTime: genesis.GenesisTime, - ChainID: genesis.ChainID, - ConsensusParams: genesis.ConsensusParams, - // Validators - AppHash: genesis.AppHash, - AppState: genesis.AppState, - } - - if genesis.AppOptions != nil { - genesisNew.AppState = genesis.AppOptions - } - - for _, v := range genesis.Validators { - var pubKey ed25519.PubKeyEd25519 - copy(pubKey[:], v.PubKey.Data) - genesisNew.Validators = append( - genesisNew.Validators, - types.GenesisValidator{ - PubKey: pubKey, - Power: v.Power, - Name: v.Name, - }, - ) - - } - - bz, err := cdc.MarshalJSON(genesisNew) - if err != nil { - return nil, err - } - return bz, nil -} - -func main() { - cdc := amino.NewCodec() - cryptoAmino.RegisterAmino(cdc) - - args := os.Args[1:] - if len(args) != 1 { - fmt.Println("Please specify a file to convert") - os.Exit(1) - } - - filePath := args[0] - fileName := filepath.Base(filePath) - - fileBytes, err := ioutil.ReadFile(filePath) - if err != nil { - panic(err) - } - - var bz []byte - - switch fileName { - case "node_key.json": - bz, err = convertNodeKey(cdc, fileBytes) - case "priv_validator.json": - bz, err = convertPrivVal(cdc, fileBytes) - case "genesis.json": - bz, err = convertGenesis(cdc, fileBytes) - default: - fmt.Println("Expected file name to be in (node_key.json, priv_validator.json, genesis.json)") - os.Exit(1) - } - - if err != nil { - panic(err) - } - fmt.Println(string(bz)) - -} From 2348f38927a8766592e02f63f7cfafffbff7f4b2 Mon Sep 17 00:00:00 2001 From: needkane <604476380@qq.com> Date: Sat, 22 Dec 2018 06:37:28 +0800 Subject: [PATCH 170/267] Update node_info.go (#3059) --- p2p/node_info.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/p2p/node_info.go b/p2p/node_info.go index 3e02e9c1..699fd7f1 100644 --- a/p2p/node_info.go +++ b/p2p/node_info.go @@ -9,7 +9,7 @@ import ( ) const ( - maxNodeInfoSize = 10240 // 10Kb + maxNodeInfoSize = 10240 // 10KB maxNumChannels = 16 // plenty of room for upgrades, for now ) From 6a80412a01c131a83cfe0eb3dfee2b9c6c740053 Mon Sep 17 00:00:00 2001 From: Ismail Khoffi Date: Sat, 22 Dec 2018 06:36:45 +0100 Subject: [PATCH 171/267] Remove privval.GetAddress(), memoize pubkey (#2948) privval: remove GetAddress(), memoize pubkey --- CHANGELOG_PENDING.md | 9 ++- blockchain/reactor_test.go | 2 +- cmd/tendermint/commands/init.go | 5 +- consensus/common_test.go | 12 ++-- consensus/reactor_test.go | 9 ++- consensus/replay_test.go | 6 -- consensus/state.go | 23 ++++---- consensus/state_test.go | 42 +++++++++----- consensus/types/height_vote_set_test.go | 3 +- node/node.go | 14 +++-- privval/ipc.go | 5 +- privval/remote_signer.go | 71 ++++++++++++----------- privval/tcp.go | 6 +- privval/tcp_test.go | 28 +++------ types/evidence_test.go | 3 +- types/priv_validator.go | 11 +--- types/protobuf_test.go | 5 +- types/test_util.go | 4 +- types/validator.go | 3 +- types/vote_set_test.go | 76 ++++++++++++++++--------- 20 files changed, 194 insertions(+), 143 deletions(-) diff --git a/CHANGELOG_PENDING.md b/CHANGELOG_PENDING.md index 2fd0d795..aaf80a4f 100644 --- a/CHANGELOG_PENDING.md +++ b/CHANGELOG_PENDING.md @@ -11,9 +11,14 @@ Special thanks to external contributors on this release: - [cli] Renamed `node` `--proxy_app=nilapp` to `--proxy_app=noop`. - [config] \#2992 `allow_duplicate_ip` is now set to false +- [privval] \#2926 split up `PubKeyMsg` into `PubKeyRequest` and `PubKeyResponse` to be consistent with other message types + * Apps -* Go API +* Go API +- [types] \#2926 memoize consensus public key on initialization of remote signer and return the memoized key on +`PrivValidator.GetPubKey()` instead of requesting it again +- [types] \#2981 Remove `PrivValidator.GetAddress()` * Blockchain Protocol @@ -26,4 +31,4 @@ Special thanks to external contributors on this release: ### IMPROVEMENTS: ### BUG FIXES: - +- [types] \#2926 do not panic if retrieving the private validator's public key fails diff --git a/blockchain/reactor_test.go b/blockchain/reactor_test.go index ac499efa..f6c29d65 100644 --- a/blockchain/reactor_test.go +++ b/blockchain/reactor_test.go @@ -42,7 +42,7 @@ func randGenesisDoc(numValidators int, randPower bool, minPower int64) (*types.G } func makeVote(header *types.Header, blockID types.BlockID, valset *types.ValidatorSet, privVal types.PrivValidator) *types.Vote { - addr := privVal.GetAddress() + addr := privVal.GetPubKey().Address() idx, _ := valset.GetByAddress(addr) vote := &types.Vote{ ValidatorAddress: addr, diff --git a/cmd/tendermint/commands/init.go b/cmd/tendermint/commands/init.go index 896bee2e..1d6e24d7 100644 --- a/cmd/tendermint/commands/init.go +++ b/cmd/tendermint/commands/init.go @@ -59,9 +59,10 @@ func initFilesWithConfig(config *cfg.Config) error { GenesisTime: tmtime.Now(), ConsensusParams: types.DefaultConsensusParams(), } + key := pv.GetPubKey() genDoc.Validators = []types.GenesisValidator{{ - Address: pv.GetPubKey().Address(), - PubKey: pv.GetPubKey(), + Address: key.Address(), + PubKey: key, Power: 10, }} diff --git a/consensus/common_test.go b/consensus/common_test.go index 1f6be437..a975b2b6 100644 --- a/consensus/common_test.go +++ b/consensus/common_test.go @@ -71,9 +71,10 @@ func NewValidatorStub(privValidator types.PrivValidator, valIndex int) *validato } func (vs *validatorStub) signVote(voteType types.SignedMsgType, hash []byte, header types.PartSetHeader) (*types.Vote, error) { + addr := vs.PrivValidator.GetPubKey().Address() vote := &types.Vote{ ValidatorIndex: vs.Index, - ValidatorAddress: vs.PrivValidator.GetAddress(), + ValidatorAddress: addr, Height: vs.Height, Round: vs.Round, Timestamp: tmtime.Now(), @@ -150,8 +151,9 @@ func signAddVotes(to *ConsensusState, voteType types.SignedMsgType, hash []byte, func validatePrevote(t *testing.T, cs *ConsensusState, round int, privVal *validatorStub, blockHash []byte) { prevotes := cs.Votes.Prevotes(round) + address := privVal.GetPubKey().Address() var vote *types.Vote - if vote = prevotes.GetByAddress(privVal.GetAddress()); vote == nil { + if vote = prevotes.GetByAddress(address); vote == nil { panic("Failed to find prevote from validator") } if blockHash == nil { @@ -167,8 +169,9 @@ func validatePrevote(t *testing.T, cs *ConsensusState, round int, privVal *valid func validateLastPrecommit(t *testing.T, cs *ConsensusState, privVal *validatorStub, blockHash []byte) { votes := cs.LastCommit + address := privVal.GetPubKey().Address() var vote *types.Vote - if vote = votes.GetByAddress(privVal.GetAddress()); vote == nil { + if vote = votes.GetByAddress(address); vote == nil { panic("Failed to find precommit from validator") } if !bytes.Equal(vote.BlockID.Hash, blockHash) { @@ -178,8 +181,9 @@ func validateLastPrecommit(t *testing.T, cs *ConsensusState, privVal *validatorS func validatePrecommit(t *testing.T, cs *ConsensusState, thisRound, lockRound int, privVal *validatorStub, votedBlockHash, lockedBlockHash []byte) { precommits := cs.Votes.Precommits(thisRound) + address := privVal.GetPubKey().Address() var vote *types.Vote - if vote = precommits.GetByAddress(privVal.GetAddress()); vote == nil { + if vote = precommits.GetByAddress(address); vote == nil { panic("Failed to find precommit from validator") } diff --git a/consensus/reactor_test.go b/consensus/reactor_test.go index 1636785c..5334895f 100644 --- a/consensus/reactor_test.go +++ b/consensus/reactor_test.go @@ -143,7 +143,8 @@ func TestReactorWithEvidence(t *testing.T) { // mock the evidence pool // everyone includes evidence of another double signing vIdx := (i + 1) % nValidators - evpool := newMockEvidencePool(privVals[vIdx].GetAddress()) + addr := privVals[vIdx].GetPubKey().Address() + evpool := newMockEvidencePool(addr) // Make ConsensusState blockExec := sm.NewBlockExecutor(stateDB, log.TestingLogger(), proxyAppConnCon, mempool, evpool) @@ -268,7 +269,8 @@ func TestReactorVotingPowerChange(t *testing.T) { // map of active validators activeVals := make(map[string]struct{}) for i := 0; i < nVals; i++ { - activeVals[string(css[i].privValidator.GetAddress())] = struct{}{} + addr := css[i].privValidator.GetPubKey().Address() + activeVals[string(addr)] = struct{}{} } // wait till everyone makes block 1 @@ -331,7 +333,8 @@ func TestReactorValidatorSetChanges(t *testing.T) { // map of active validators activeVals := make(map[string]struct{}) for i := 0; i < nVals; i++ { - activeVals[string(css[i].privValidator.GetAddress())] = struct{}{} + addr := css[i].privValidator.GetPubKey().Address() + activeVals[string(addr)] = struct{}{} } // wait till everyone makes block 1 diff --git a/consensus/replay_test.go b/consensus/replay_test.go index 71b93775..7c00251e 100644 --- a/consensus/replay_test.go +++ b/consensus/replay_test.go @@ -659,12 +659,6 @@ func TestInitChainUpdateValidators(t *testing.T) { assert.Equal(t, newValAddr, expectValAddr) } -func newInitChainApp(vals []abci.ValidatorUpdate) *initChainApp { - return &initChainApp{ - vals: vals, - } -} - // returns the vals on InitChain type initChainApp struct { abci.BaseApplication diff --git a/consensus/state.go b/consensus/state.go index 81bdce7d..1693e36b 100644 --- a/consensus/state.go +++ b/consensus/state.go @@ -2,13 +2,14 @@ package consensus import ( "bytes" - "errors" "fmt" "reflect" "runtime/debug" "sync" "time" + "github.com/pkg/errors" + cmn "github.com/tendermint/tendermint/libs/common" "github.com/tendermint/tendermint/libs/fail" "github.com/tendermint/tendermint/libs/log" @@ -829,13 +830,14 @@ func (cs *ConsensusState) enterPropose(height int64, round int) { } // if not a validator, we're done - if !cs.Validators.HasAddress(cs.privValidator.GetAddress()) { - logger.Debug("This node is not a validator", "addr", cs.privValidator.GetAddress(), "vals", cs.Validators) + address := cs.privValidator.GetPubKey().Address() + if !cs.Validators.HasAddress(address) { + logger.Debug("This node is not a validator", "addr", address, "vals", cs.Validators) return } logger.Debug("This node is a validator") - if cs.isProposer() { + if cs.isProposer(address) { logger.Info("enterPropose: Our turn to propose", "proposer", cs.Validators.GetProposer().Address, "privValidator", cs.privValidator) cs.decideProposal(height, round) } else { @@ -843,8 +845,8 @@ func (cs *ConsensusState) enterPropose(height int64, round int) { } } -func (cs *ConsensusState) isProposer() bool { - return bytes.Equal(cs.Validators.GetProposer().Address, cs.privValidator.GetAddress()) +func (cs *ConsensusState) isProposer(address []byte) bool { + return bytes.Equal(cs.Validators.GetProposer().Address, address) } func (cs *ConsensusState) defaultDecideProposal(height int64, round int) { @@ -929,7 +931,7 @@ func (cs *ConsensusState) createProposalBlock() (block *types.Block, blockParts cs.state.Validators.Size(), len(evidence), ), maxGas) - proposerAddr := cs.privValidator.GetAddress() + proposerAddr := cs.privValidator.GetPubKey().Address() block, parts := cs.state.MakeBlock(cs.Height, txs, commit, evidence, proposerAddr) return block, parts @@ -1474,7 +1476,8 @@ func (cs *ConsensusState) tryAddVote(vote *types.Vote, peerID p2p.ID) (bool, err if err == ErrVoteHeightMismatch { return added, err } else if voteErr, ok := err.(*types.ErrVoteConflictingVotes); ok { - if bytes.Equal(vote.ValidatorAddress, cs.privValidator.GetAddress()) { + addr := cs.privValidator.GetPubKey().Address() + if bytes.Equal(vote.ValidatorAddress, addr) { cs.Logger.Error("Found conflicting vote from ourselves. Did you unsafe_reset a validator?", "height", vote.Height, "round", vote.Round, "type", vote.Type) return added, err } @@ -1639,7 +1642,7 @@ func (cs *ConsensusState) addVote(vote *types.Vote, peerID p2p.ID) (added bool, } func (cs *ConsensusState) signVote(type_ types.SignedMsgType, hash []byte, header types.PartSetHeader) (*types.Vote, error) { - addr := cs.privValidator.GetAddress() + addr := cs.privValidator.GetPubKey().Address() valIndex, _ := cs.Validators.GetByAddress(addr) vote := &types.Vote{ @@ -1675,7 +1678,7 @@ func (cs *ConsensusState) voteTime() time.Time { // sign the vote and publish on internalMsgQueue func (cs *ConsensusState) signAddVote(type_ types.SignedMsgType, hash []byte, header types.PartSetHeader) *types.Vote { // if we don't have a key or we're not in the validator set, do nothing - if cs.privValidator == nil || !cs.Validators.HasAddress(cs.privValidator.GetAddress()) { + if cs.privValidator == nil || !cs.Validators.HasAddress(cs.privValidator.GetPubKey().Address()) { return nil } vote, err := cs.signVote(type_, hash, header) diff --git a/consensus/state_test.go b/consensus/state_test.go index ddab6404..40103e47 100644 --- a/consensus/state_test.go +++ b/consensus/state_test.go @@ -73,7 +73,8 @@ func TestStateProposerSelection0(t *testing.T) { // Commit a block and ensure proposer for the next height is correct. prop := cs1.GetRoundState().Validators.GetProposer() - if !bytes.Equal(prop.Address, cs1.privValidator.GetAddress()) { + address := cs1.privValidator.GetPubKey().Address() + if !bytes.Equal(prop.Address, address) { t.Fatalf("expected proposer to be validator %d. Got %X", 0, prop.Address) } @@ -87,7 +88,8 @@ func TestStateProposerSelection0(t *testing.T) { ensureNewRound(newRoundCh, height+1, 0) prop = cs1.GetRoundState().Validators.GetProposer() - if !bytes.Equal(prop.Address, vss[1].GetAddress()) { + addr := vss[1].GetPubKey().Address() + if !bytes.Equal(prop.Address, addr) { panic(fmt.Sprintf("expected proposer to be validator %d. Got %X", 1, prop.Address)) } } @@ -110,7 +112,8 @@ func TestStateProposerSelection2(t *testing.T) { // everyone just votes nil. we get a new proposer each round for i := 0; i < len(vss); i++ { prop := cs1.GetRoundState().Validators.GetProposer() - correctProposer := vss[(i+round)%len(vss)].GetAddress() + addr := vss[(i+round)%len(vss)].GetPubKey().Address() + correctProposer := addr if !bytes.Equal(prop.Address, correctProposer) { panic(fmt.Sprintf("expected RoundState.Validators.GetProposer() to be validator %d. Got %X", (i+2)%len(vss), prop.Address)) } @@ -505,7 +508,8 @@ func TestStateLockPOLRelock(t *testing.T) { timeoutWaitCh := subscribe(cs1.eventBus, types.EventQueryTimeoutWait) proposalCh := subscribe(cs1.eventBus, types.EventQueryCompleteProposal) - voteCh := subscribeToVoter(cs1, cs1.privValidator.GetAddress()) + addr := cs1.privValidator.GetPubKey().Address() + voteCh := subscribeToVoter(cs1, addr) newRoundCh := subscribe(cs1.eventBus, types.EventQueryNewRound) newBlockCh := subscribe(cs1.eventBus, types.EventQueryNewBlockHeader) @@ -596,7 +600,8 @@ func TestStateLockPOLUnlock(t *testing.T) { timeoutWaitCh := subscribe(cs1.eventBus, types.EventQueryTimeoutWait) newRoundCh := subscribe(cs1.eventBus, types.EventQueryNewRound) unlockCh := subscribe(cs1.eventBus, types.EventQueryUnlock) - voteCh := subscribeToVoter(cs1, cs1.privValidator.GetAddress()) + addr := cs1.privValidator.GetPubKey().Address() + voteCh := subscribeToVoter(cs1, addr) // everything done from perspective of cs1 @@ -689,7 +694,8 @@ func TestStateLockPOLSafety1(t *testing.T) { timeoutProposeCh := subscribe(cs1.eventBus, types.EventQueryTimeoutPropose) timeoutWaitCh := subscribe(cs1.eventBus, types.EventQueryTimeoutWait) newRoundCh := subscribe(cs1.eventBus, types.EventQueryNewRound) - voteCh := subscribeToVoter(cs1, cs1.privValidator.GetAddress()) + addr := cs1.privValidator.GetPubKey().Address() + voteCh := subscribeToVoter(cs1, addr) // start round and wait for propose and prevote startTestRound(cs1, cs1.Height, round) @@ -805,7 +811,8 @@ func TestStateLockPOLSafety2(t *testing.T) { timeoutWaitCh := subscribe(cs1.eventBus, types.EventQueryTimeoutWait) newRoundCh := subscribe(cs1.eventBus, types.EventQueryNewRound) unlockCh := subscribe(cs1.eventBus, types.EventQueryUnlock) - voteCh := subscribeToVoter(cs1, cs1.privValidator.GetAddress()) + addr := cs1.privValidator.GetPubKey().Address() + voteCh := subscribeToVoter(cs1, addr) // the block for R0: gets polkad but we miss it // (even though we signed it, shhh) @@ -896,7 +903,8 @@ func TestProposeValidBlock(t *testing.T) { timeoutProposeCh := subscribe(cs1.eventBus, types.EventQueryTimeoutPropose) newRoundCh := subscribe(cs1.eventBus, types.EventQueryNewRound) unlockCh := subscribe(cs1.eventBus, types.EventQueryUnlock) - voteCh := subscribeToVoter(cs1, cs1.privValidator.GetAddress()) + addr := cs1.privValidator.GetPubKey().Address() + voteCh := subscribeToVoter(cs1, addr) // start round and wait for propose and prevote startTestRound(cs1, cs1.Height, round) @@ -982,7 +990,8 @@ func TestSetValidBlockOnDelayedPrevote(t *testing.T) { timeoutWaitCh := subscribe(cs1.eventBus, types.EventQueryTimeoutWait) newRoundCh := subscribe(cs1.eventBus, types.EventQueryNewRound) validBlockCh := subscribe(cs1.eventBus, types.EventQueryValidBlock) - voteCh := subscribeToVoter(cs1, cs1.privValidator.GetAddress()) + addr := cs1.privValidator.GetPubKey().Address() + voteCh := subscribeToVoter(cs1, addr) // start round and wait for propose and prevote startTestRound(cs1, cs1.Height, round) @@ -1041,7 +1050,8 @@ func TestSetValidBlockOnDelayedProposal(t *testing.T) { timeoutProposeCh := subscribe(cs1.eventBus, types.EventQueryTimeoutPropose) newRoundCh := subscribe(cs1.eventBus, types.EventQueryNewRound) validBlockCh := subscribe(cs1.eventBus, types.EventQueryValidBlock) - voteCh := subscribeToVoter(cs1, cs1.privValidator.GetAddress()) + addr := cs1.privValidator.GetPubKey().Address() + voteCh := subscribeToVoter(cs1, addr) proposalCh := subscribe(cs1.eventBus, types.EventQueryCompleteProposal) round = round + 1 // move to round in which P0 is not proposer @@ -1111,7 +1121,8 @@ func TestWaitingTimeoutProposeOnNewRound(t *testing.T) { timeoutWaitCh := subscribe(cs1.eventBus, types.EventQueryTimeoutPropose) newRoundCh := subscribe(cs1.eventBus, types.EventQueryNewRound) - voteCh := subscribeToVoter(cs1, cs1.privValidator.GetAddress()) + addr := cs1.privValidator.GetPubKey().Address() + voteCh := subscribeToVoter(cs1, addr) // start round startTestRound(cs1, height, round) @@ -1144,7 +1155,8 @@ func TestRoundSkipOnNilPolkaFromHigherRound(t *testing.T) { timeoutWaitCh := subscribe(cs1.eventBus, types.EventQueryTimeoutWait) newRoundCh := subscribe(cs1.eventBus, types.EventQueryNewRound) - voteCh := subscribeToVoter(cs1, cs1.privValidator.GetAddress()) + addr := cs1.privValidator.GetPubKey().Address() + voteCh := subscribeToVoter(cs1, addr) // start round startTestRound(cs1, height, round) @@ -1177,7 +1189,8 @@ func TestWaitTimeoutProposeOnNilPolkaForTheCurrentRound(t *testing.T) { timeoutProposeCh := subscribe(cs1.eventBus, types.EventQueryTimeoutPropose) newRoundCh := subscribe(cs1.eventBus, types.EventQueryNewRound) - voteCh := subscribeToVoter(cs1, cs1.privValidator.GetAddress()) + addr := cs1.privValidator.GetPubKey().Address() + voteCh := subscribeToVoter(cs1, addr) // start round in which PO is not proposer startTestRound(cs1, height, round) @@ -1361,7 +1374,8 @@ func TestStateHalt1(t *testing.T) { timeoutWaitCh := subscribe(cs1.eventBus, types.EventQueryTimeoutWait) newRoundCh := subscribe(cs1.eventBus, types.EventQueryNewRound) newBlockCh := subscribe(cs1.eventBus, types.EventQueryNewBlock) - voteCh := subscribeToVoter(cs1, cs1.privValidator.GetAddress()) + addr := cs1.privValidator.GetPubKey().Address() + voteCh := subscribeToVoter(cs1, addr) // start round and wait for propose and prevote startTestRound(cs1, height, round) diff --git a/consensus/types/height_vote_set_test.go b/consensus/types/height_vote_set_test.go index e2298cef..4460cd3e 100644 --- a/consensus/types/height_vote_set_test.go +++ b/consensus/types/height_vote_set_test.go @@ -50,8 +50,9 @@ func TestPeerCatchupRounds(t *testing.T) { func makeVoteHR(t *testing.T, height int64, round int, privVals []types.PrivValidator, valIndex int) *types.Vote { privVal := privVals[valIndex] + addr := privVal.GetPubKey().Address() vote := &types.Vote{ - ValidatorAddress: privVal.GetAddress(), + ValidatorAddress: addr, ValidatorIndex: valIndex, Height: height, Round: round, diff --git a/node/node.go b/node/node.go index 00d9e8a7..be4c7cc7 100644 --- a/node/node.go +++ b/node/node.go @@ -259,16 +259,19 @@ func NewNode(config *cfg.Config, fastSync := config.FastSync if state.Validators.Size() == 1 { addr, _ := state.Validators.GetByIndex(0) - if bytes.Equal(privValidator.GetAddress(), addr) { + privValAddr := privValidator.GetPubKey().Address() + if bytes.Equal(privValAddr, addr) { fastSync = false } } + pubKey := privValidator.GetPubKey() + addr := pubKey.Address() // Log whether this node is a validator or an observer - if state.Validators.HasAddress(privValidator.GetAddress()) { - consensusLogger.Info("This node is a validator", "addr", privValidator.GetAddress(), "pubKey", privValidator.GetPubKey()) + if state.Validators.HasAddress(addr) { + consensusLogger.Info("This node is a validator", "addr", addr, "pubKey", pubKey) } else { - consensusLogger.Info("This node is not a validator", "addr", privValidator.GetAddress(), "pubKey", privValidator.GetPubKey()) + consensusLogger.Info("This node is not a validator", "addr", addr, "pubKey", pubKey) } csMetrics, p2pMetrics, memplMetrics, smMetrics := metricsProvider() @@ -636,7 +639,8 @@ func (n *Node) ConfigureRPC() { rpccore.SetEvidencePool(n.evidencePool) rpccore.SetP2PPeers(n.sw) rpccore.SetP2PTransport(n) - rpccore.SetPubKey(n.privValidator.GetPubKey()) + pubKey := n.privValidator.GetPubKey() + rpccore.SetPubKey(pubKey) rpccore.SetGenesisDoc(n.genesisDoc) rpccore.SetAddrBook(n.addrBook) rpccore.SetProxyAppQuery(n.proxyApp.Query()) diff --git a/privval/ipc.go b/privval/ipc.go index eda23fe6..1c82db33 100644 --- a/privval/ipc.go +++ b/privval/ipc.go @@ -67,7 +67,10 @@ func (sc *IPCVal) OnStart() error { return err } - sc.RemoteSignerClient = NewRemoteSignerClient(sc.conn) + sc.RemoteSignerClient, err = NewRemoteSignerClient(sc.conn) + if err != nil { + return err + } // Start a routine to keep the connection alive sc.cancelPing = make(chan struct{}, 1) diff --git a/privval/remote_signer.go b/privval/remote_signer.go index 5d6339c3..b80884de 100644 --- a/privval/remote_signer.go +++ b/privval/remote_signer.go @@ -6,6 +6,8 @@ import ( "net" "sync" + "github.com/pkg/errors" + "github.com/tendermint/go-amino" "github.com/tendermint/tendermint/crypto" cmn "github.com/tendermint/tendermint/libs/common" @@ -15,8 +17,9 @@ import ( // RemoteSignerClient implements PrivValidator, it uses a socket to request signatures // from an external process. type RemoteSignerClient struct { - conn net.Conn - lock sync.Mutex + conn net.Conn + consensusPubKey crypto.PubKey + mtx sync.Mutex } // Check that RemoteSignerClient implements PrivValidator. @@ -25,38 +28,29 @@ var _ types.PrivValidator = (*RemoteSignerClient)(nil) // NewRemoteSignerClient returns an instance of RemoteSignerClient. func NewRemoteSignerClient( conn net.Conn, -) *RemoteSignerClient { +) (*RemoteSignerClient, error) { sc := &RemoteSignerClient{ conn: conn, } - return sc -} - -// GetAddress implements PrivValidator. -func (sc *RemoteSignerClient) GetAddress() types.Address { pubKey, err := sc.getPubKey() if err != nil { - panic(err) + return nil, cmn.ErrorWrap(err, "error while retrieving public key for remote signer") } - - return pubKey.Address() + // retrieve and memoize the consensus public key once: + sc.consensusPubKey = pubKey + return sc, nil } // GetPubKey implements PrivValidator. func (sc *RemoteSignerClient) GetPubKey() crypto.PubKey { - pubKey, err := sc.getPubKey() - if err != nil { - panic(err) - } - - return pubKey + return sc.consensusPubKey } func (sc *RemoteSignerClient) getPubKey() (crypto.PubKey, error) { - sc.lock.Lock() - defer sc.lock.Unlock() + sc.mtx.Lock() + defer sc.mtx.Unlock() - err := writeMsg(sc.conn, &PubKeyMsg{}) + err := writeMsg(sc.conn, &PubKeyRequest{}) if err != nil { return nil, err } @@ -65,14 +59,22 @@ func (sc *RemoteSignerClient) getPubKey() (crypto.PubKey, error) { if err != nil { return nil, err } + pubKeyResp, ok := res.(*PubKeyResponse) + if !ok { + return nil, errors.Wrap(ErrUnexpectedResponse, "response is not PubKeyResponse") + } - return res.(*PubKeyMsg).PubKey, nil + if pubKeyResp.Error != nil { + return nil, errors.Wrap(pubKeyResp.Error, "failed to get private validator's public key") + } + + return pubKeyResp.PubKey, nil } // SignVote implements PrivValidator. func (sc *RemoteSignerClient) SignVote(chainID string, vote *types.Vote) error { - sc.lock.Lock() - defer sc.lock.Unlock() + sc.mtx.Lock() + defer sc.mtx.Unlock() err := writeMsg(sc.conn, &SignVoteRequest{Vote: vote}) if err != nil { @@ -101,8 +103,8 @@ func (sc *RemoteSignerClient) SignProposal( chainID string, proposal *types.Proposal, ) error { - sc.lock.Lock() - defer sc.lock.Unlock() + sc.mtx.Lock() + defer sc.mtx.Unlock() err := writeMsg(sc.conn, &SignProposalRequest{Proposal: proposal}) if err != nil { @@ -127,8 +129,8 @@ func (sc *RemoteSignerClient) SignProposal( // Ping is used to check connection health. func (sc *RemoteSignerClient) Ping() error { - sc.lock.Lock() - defer sc.lock.Unlock() + sc.mtx.Lock() + defer sc.mtx.Unlock() err := writeMsg(sc.conn, &PingRequest{}) if err != nil { @@ -152,7 +154,8 @@ type RemoteSignerMsg interface{} func RegisterRemoteSignerMsg(cdc *amino.Codec) { cdc.RegisterInterface((*RemoteSignerMsg)(nil), nil) - cdc.RegisterConcrete(&PubKeyMsg{}, "tendermint/remotesigner/PubKeyMsg", nil) + cdc.RegisterConcrete(&PubKeyRequest{}, "tendermint/remotesigner/PubKeyRequest", nil) + cdc.RegisterConcrete(&PubKeyResponse{}, "tendermint/remotesigner/PubKeyResponse", nil) cdc.RegisterConcrete(&SignVoteRequest{}, "tendermint/remotesigner/SignVoteRequest", nil) cdc.RegisterConcrete(&SignedVoteResponse{}, "tendermint/remotesigner/SignedVoteResponse", nil) cdc.RegisterConcrete(&SignProposalRequest{}, "tendermint/remotesigner/SignProposalRequest", nil) @@ -161,9 +164,13 @@ func RegisterRemoteSignerMsg(cdc *amino.Codec) { cdc.RegisterConcrete(&PingResponse{}, "tendermint/remotesigner/PingResponse", nil) } -// PubKeyMsg is a PrivValidatorSocket message containing the public key. -type PubKeyMsg struct { +// PubKeyRequest requests the consensus public key from the remote signer. +type PubKeyRequest struct{} + +// PubKeyResponse is a PrivValidatorSocket message containing the public key. +type PubKeyResponse struct { PubKey crypto.PubKey + Error *RemoteSignerError } // SignVoteRequest is a PrivValidatorSocket message containing a vote. @@ -227,10 +234,10 @@ func handleRequest(req RemoteSignerMsg, chainID string, privVal types.PrivValida var err error switch r := req.(type) { - case *PubKeyMsg: + case *PubKeyRequest: var p crypto.PubKey p = privVal.GetPubKey() - res = &PubKeyMsg{p} + res = &PubKeyResponse{p, nil} case *SignVoteRequest: err = privVal.SignVote(chainID, r.Vote) if err != nil { diff --git a/privval/tcp.go b/privval/tcp.go index 11bd833c..1fb736e6 100644 --- a/privval/tcp.go +++ b/privval/tcp.go @@ -107,8 +107,10 @@ func (sc *TCPVal) OnStart() error { } sc.conn = conn - - sc.RemoteSignerClient = NewRemoteSignerClient(sc.conn) + sc.RemoteSignerClient, err = NewRemoteSignerClient(sc.conn) + if err != nil { + return err + } // Start a routine to keep the connection alive sc.cancelPing = make(chan struct{}, 1) diff --git a/privval/tcp_test.go b/privval/tcp_test.go index d2489ad1..e893ef40 100644 --- a/privval/tcp_test.go +++ b/privval/tcp_test.go @@ -25,15 +25,10 @@ func TestSocketPVAddress(t *testing.T) { defer sc.Stop() defer rs.Stop() - serverAddr := rs.privVal.GetAddress() - - clientAddr := sc.GetAddress() + serverAddr := rs.privVal.GetPubKey().Address() + clientAddr := sc.GetPubKey().Address() assert.Equal(t, serverAddr, clientAddr) - - // TODO(xla): Remove when PrivValidator2 replaced PrivValidator. - assert.Equal(t, serverAddr, sc.GetAddress()) - } func TestSocketPVPubKey(t *testing.T) { @@ -47,12 +42,9 @@ func TestSocketPVPubKey(t *testing.T) { clientKey, err := sc.getPubKey() require.NoError(t, err) - privKey := rs.privVal.GetPubKey() + privvalPubKey := rs.privVal.GetPubKey() - assert.Equal(t, privKey, clientKey) - - // TODO(xla): Remove when PrivValidator2 replaced PrivValidator. - assert.Equal(t, privKey, sc.GetPubKey()) + assert.Equal(t, privvalPubKey, clientKey) } func TestSocketPVProposal(t *testing.T) { @@ -153,9 +145,9 @@ func TestSocketPVDeadline(t *testing.T) { go func(sc *TCPVal) { defer close(listenc) - require.NoError(t, sc.Start()) + assert.Equal(t, sc.Start().(cmn.Error).Data(), ErrConnTimeout) - assert.True(t, sc.IsRunning()) + assert.False(t, sc.IsRunning()) }(sc) for { @@ -174,9 +166,6 @@ func TestSocketPVDeadline(t *testing.T) { } <-listenc - - _, err := sc.getPubKey() - assert.Equal(t, err.(cmn.Error).Data(), ErrConnTimeout) } func TestRemoteSignerRetry(t *testing.T) { @@ -310,14 +299,15 @@ func TestErrUnexpectedResponse(t *testing.T) { testStartSocketPV(t, readyc, sc) defer sc.Stop() RemoteSignerConnDeadline(time.Millisecond)(rs) - RemoteSignerConnRetries(1e6)(rs) - + RemoteSignerConnRetries(100)(rs) // we do not want to Start() the remote signer here and instead use the connection to // reply with intentionally wrong replies below: rsConn, err := rs.connect() defer rsConn.Close() require.NoError(t, err) require.NotNil(t, rsConn) + // send over public key to get the remote signer running: + go testReadWriteResponse(t, &PubKeyResponse{}, rsConn) <-readyc // Proposal: diff --git a/types/evidence_test.go b/types/evidence_test.go index a96b63a9..19427150 100644 --- a/types/evidence_test.go +++ b/types/evidence_test.go @@ -17,8 +17,9 @@ type voteData struct { } func makeVote(val PrivValidator, chainID string, valIndex int, height int64, round, step int, blockID BlockID) *Vote { + addr := val.GetPubKey().Address() v := &Vote{ - ValidatorAddress: val.GetAddress(), + ValidatorAddress: addr, ValidatorIndex: valIndex, Height: height, Round: round, diff --git a/types/priv_validator.go b/types/priv_validator.go index ebd64446..f0a19f40 100644 --- a/types/priv_validator.go +++ b/types/priv_validator.go @@ -12,7 +12,6 @@ import ( // PrivValidator defines the functionality of a local Tendermint validator // that signs votes and proposals, and never double signs. type PrivValidator interface { - GetAddress() Address // redundant since .PubKey().Address() GetPubKey() crypto.PubKey SignVote(chainID string, vote *Vote) error @@ -29,7 +28,7 @@ func (pvs PrivValidatorsByAddress) Len() int { } func (pvs PrivValidatorsByAddress) Less(i, j int) bool { - return bytes.Compare(pvs[i].GetAddress(), pvs[j].GetAddress()) == -1 + return bytes.Compare(pvs[i].GetPubKey().Address(), pvs[j].GetPubKey().Address()) == -1 } func (pvs PrivValidatorsByAddress) Swap(i, j int) { @@ -51,11 +50,6 @@ func NewMockPV() *MockPV { return &MockPV{ed25519.GenPrivKey()} } -// Implements PrivValidator. -func (pv *MockPV) GetAddress() Address { - return pv.privKey.PubKey().Address() -} - // Implements PrivValidator. func (pv *MockPV) GetPubKey() crypto.PubKey { return pv.privKey.PubKey() @@ -85,7 +79,8 @@ func (pv *MockPV) SignProposal(chainID string, proposal *Proposal) error { // String returns a string representation of the MockPV. func (pv *MockPV) String() string { - return fmt.Sprintf("MockPV{%v}", pv.GetAddress()) + addr := pv.GetPubKey().Address() + return fmt.Sprintf("MockPV{%v}", addr) } // XXX: Implement. diff --git a/types/protobuf_test.go b/types/protobuf_test.go index f5a2ce5d..18acf57a 100644 --- a/types/protobuf_test.go +++ b/types/protobuf_test.go @@ -142,14 +142,15 @@ func TestABCIEvidence(t *testing.T) { blockID := makeBlockID([]byte("blockhash"), 1000, []byte("partshash")) blockID2 := makeBlockID([]byte("blockhash2"), 1000, []byte("partshash")) const chainID = "mychain" + pubKey := val.GetPubKey() ev := &DuplicateVoteEvidence{ - PubKey: val.GetPubKey(), + PubKey: pubKey, VoteA: makeVote(val, chainID, 0, 10, 2, 1, blockID), VoteB: makeVote(val, chainID, 0, 10, 2, 1, blockID2), } abciEv := TM2PB.Evidence( ev, - NewValidatorSet([]*Validator{NewValidator(val.GetPubKey(), 10)}), + NewValidatorSet([]*Validator{NewValidator(pubKey, 10)}), time.Now(), ) diff --git a/types/test_util.go b/types/test_util.go index 80f0c787..18e47214 100644 --- a/types/test_util.go +++ b/types/test_util.go @@ -10,9 +10,9 @@ func MakeCommit(blockID BlockID, height int64, round int, // all sign for i := 0; i < len(validators); i++ { - + addr := validators[i].GetPubKey().Address() vote := &Vote{ - ValidatorAddress: validators[i].GetAddress(), + ValidatorAddress: addr, ValidatorIndex: i, Height: height, Round: round, diff --git a/types/validator.go b/types/validator.go index b7c6c679..1de326b0 100644 --- a/types/validator.go +++ b/types/validator.go @@ -101,6 +101,7 @@ func RandValidator(randPower bool, minPower int64) (*Validator, PrivValidator) { if randPower { votePower += int64(cmn.RandUint32()) } - val := NewValidator(privVal.GetPubKey(), votePower) + pubKey := privVal.GetPubKey() + val := NewValidator(pubKey, votePower) return val, privVal } diff --git a/types/vote_set_test.go b/types/vote_set_test.go index 64187292..59205efc 100644 --- a/types/vote_set_test.go +++ b/types/vote_set_test.go @@ -66,7 +66,8 @@ func TestAddVote(t *testing.T) { // t.Logf(">> %v", voteSet) - if voteSet.GetByAddress(val0.GetAddress()) != nil { + val0Addr := val0.GetPubKey().Address() + if voteSet.GetByAddress(val0Addr) != nil { t.Errorf("Expected GetByAddress(val0.Address) to be nil") } if voteSet.BitArray().GetIndex(0) { @@ -78,7 +79,7 @@ func TestAddVote(t *testing.T) { } vote := &Vote{ - ValidatorAddress: val0.GetAddress(), + ValidatorAddress: val0Addr, ValidatorIndex: 0, // since privValidators are in order Height: height, Round: round, @@ -91,7 +92,7 @@ func TestAddVote(t *testing.T) { t.Error(err) } - if voteSet.GetByAddress(val0.GetAddress()) == nil { + if voteSet.GetByAddress(val0Addr) == nil { t.Errorf("Expected GetByAddress(val0.Address) to be present") } if !voteSet.BitArray().GetIndex(0) { @@ -118,7 +119,8 @@ func Test2_3Majority(t *testing.T) { } // 6 out of 10 voted for nil. for i := 0; i < 6; i++ { - vote := withValidator(voteProto, privValidators[i].GetAddress(), i) + addr := privValidators[i].GetPubKey().Address() + vote := withValidator(voteProto, addr, i) _, err := signAddVote(privValidators[i], vote, voteSet) if err != nil { t.Error(err) @@ -131,7 +133,8 @@ func Test2_3Majority(t *testing.T) { // 7th validator voted for some blockhash { - vote := withValidator(voteProto, privValidators[6].GetAddress(), 6) + addr := privValidators[6].GetPubKey().Address() + vote := withValidator(voteProto, addr, 6) _, err := signAddVote(privValidators[6], withBlockHash(vote, cmn.RandBytes(32)), voteSet) if err != nil { t.Error(err) @@ -144,7 +147,8 @@ func Test2_3Majority(t *testing.T) { // 8th validator voted for nil. { - vote := withValidator(voteProto, privValidators[7].GetAddress(), 7) + addr := privValidators[7].GetPubKey().Address() + vote := withValidator(voteProto, addr, 7) _, err := signAddVote(privValidators[7], vote, voteSet) if err != nil { t.Error(err) @@ -176,7 +180,8 @@ func Test2_3MajorityRedux(t *testing.T) { // 66 out of 100 voted for nil. for i := 0; i < 66; i++ { - vote := withValidator(voteProto, privValidators[i].GetAddress(), i) + addr := privValidators[i].GetPubKey().Address() + vote := withValidator(voteProto, addr, i) _, err := signAddVote(privValidators[i], vote, voteSet) if err != nil { t.Error(err) @@ -189,7 +194,8 @@ func Test2_3MajorityRedux(t *testing.T) { // 67th validator voted for nil { - vote := withValidator(voteProto, privValidators[66].GetAddress(), 66) + adrr := privValidators[66].GetPubKey().Address() + vote := withValidator(voteProto, adrr, 66) _, err := signAddVote(privValidators[66], withBlockHash(vote, nil), voteSet) if err != nil { t.Error(err) @@ -202,7 +208,8 @@ func Test2_3MajorityRedux(t *testing.T) { // 68th validator voted for a different BlockParts PartSetHeader { - vote := withValidator(voteProto, privValidators[67].GetAddress(), 67) + addr := privValidators[67].GetPubKey().Address() + vote := withValidator(voteProto, addr, 67) blockPartsHeader := PartSetHeader{blockPartsTotal, crypto.CRandBytes(32)} _, err := signAddVote(privValidators[67], withBlockPartsHeader(vote, blockPartsHeader), voteSet) if err != nil { @@ -216,7 +223,8 @@ func Test2_3MajorityRedux(t *testing.T) { // 69th validator voted for different BlockParts Total { - vote := withValidator(voteProto, privValidators[68].GetAddress(), 68) + addr := privValidators[68].GetPubKey().Address() + vote := withValidator(voteProto, addr, 68) blockPartsHeader := PartSetHeader{blockPartsTotal + 1, blockPartsHeader.Hash} _, err := signAddVote(privValidators[68], withBlockPartsHeader(vote, blockPartsHeader), voteSet) if err != nil { @@ -230,7 +238,8 @@ func Test2_3MajorityRedux(t *testing.T) { // 70th validator voted for different BlockHash { - vote := withValidator(voteProto, privValidators[69].GetAddress(), 69) + addr := privValidators[69].GetPubKey().Address() + vote := withValidator(voteProto, addr, 69) _, err := signAddVote(privValidators[69], withBlockHash(vote, cmn.RandBytes(32)), voteSet) if err != nil { t.Error(err) @@ -243,7 +252,8 @@ func Test2_3MajorityRedux(t *testing.T) { // 71st validator voted for the right BlockHash & BlockPartsHeader { - vote := withValidator(voteProto, privValidators[70].GetAddress(), 70) + addr := privValidators[70].GetPubKey().Address() + vote := withValidator(voteProto, addr, 70) _, err := signAddVote(privValidators[70], vote, voteSet) if err != nil { t.Error(err) @@ -271,7 +281,8 @@ func TestBadVotes(t *testing.T) { // val0 votes for nil. { - vote := withValidator(voteProto, privValidators[0].GetAddress(), 0) + addr := privValidators[0].GetPubKey().Address() + vote := withValidator(voteProto, addr, 0) added, err := signAddVote(privValidators[0], vote, voteSet) if !added || err != nil { t.Errorf("Expected VoteSet.Add to succeed") @@ -280,7 +291,8 @@ func TestBadVotes(t *testing.T) { // val0 votes again for some block. { - vote := withValidator(voteProto, privValidators[0].GetAddress(), 0) + addr := privValidators[0].GetPubKey().Address() + vote := withValidator(voteProto, addr, 0) added, err := signAddVote(privValidators[0], withBlockHash(vote, cmn.RandBytes(32)), voteSet) if added || err == nil { t.Errorf("Expected VoteSet.Add to fail, conflicting vote.") @@ -289,7 +301,8 @@ func TestBadVotes(t *testing.T) { // val1 votes on another height { - vote := withValidator(voteProto, privValidators[1].GetAddress(), 1) + addr := privValidators[1].GetPubKey().Address() + vote := withValidator(voteProto, addr, 1) added, err := signAddVote(privValidators[1], withHeight(vote, height+1), voteSet) if added || err == nil { t.Errorf("Expected VoteSet.Add to fail, wrong height") @@ -298,7 +311,8 @@ func TestBadVotes(t *testing.T) { // val2 votes on another round { - vote := withValidator(voteProto, privValidators[2].GetAddress(), 2) + addr := privValidators[2].GetPubKey().Address() + vote := withValidator(voteProto, addr, 2) added, err := signAddVote(privValidators[2], withRound(vote, round+1), voteSet) if added || err == nil { t.Errorf("Expected VoteSet.Add to fail, wrong round") @@ -307,7 +321,8 @@ func TestBadVotes(t *testing.T) { // val3 votes of another type. { - vote := withValidator(voteProto, privValidators[3].GetAddress(), 3) + addr := privValidators[3].GetPubKey().Address() + vote := withValidator(voteProto, addr, 3) added, err := signAddVote(privValidators[3], withType(vote, byte(PrecommitType)), voteSet) if added || err == nil { t.Errorf("Expected VoteSet.Add to fail, wrong type") @@ -331,9 +346,10 @@ func TestConflicts(t *testing.T) { BlockID: BlockID{nil, PartSetHeader{}}, } + val0Addr := privValidators[0].GetPubKey().Address() // val0 votes for nil. { - vote := withValidator(voteProto, privValidators[0].GetAddress(), 0) + vote := withValidator(voteProto, val0Addr, 0) added, err := signAddVote(privValidators[0], vote, voteSet) if !added || err != nil { t.Errorf("Expected VoteSet.Add to succeed") @@ -342,7 +358,7 @@ func TestConflicts(t *testing.T) { // val0 votes again for blockHash1. { - vote := withValidator(voteProto, privValidators[0].GetAddress(), 0) + vote := withValidator(voteProto, val0Addr, 0) added, err := signAddVote(privValidators[0], withBlockHash(vote, blockHash1), voteSet) if added { t.Errorf("Expected VoteSet.Add to fail, conflicting vote.") @@ -357,7 +373,7 @@ func TestConflicts(t *testing.T) { // val0 votes again for blockHash1. { - vote := withValidator(voteProto, privValidators[0].GetAddress(), 0) + vote := withValidator(voteProto, val0Addr, 0) added, err := signAddVote(privValidators[0], withBlockHash(vote, blockHash1), voteSet) if !added { t.Errorf("Expected VoteSet.Add to succeed, called SetPeerMaj23().") @@ -372,7 +388,7 @@ func TestConflicts(t *testing.T) { // val0 votes again for blockHash1. { - vote := withValidator(voteProto, privValidators[0].GetAddress(), 0) + vote := withValidator(voteProto, val0Addr, 0) added, err := signAddVote(privValidators[0], withBlockHash(vote, blockHash2), voteSet) if added { t.Errorf("Expected VoteSet.Add to fail, duplicate SetPeerMaj23() from peerA") @@ -384,7 +400,8 @@ func TestConflicts(t *testing.T) { // val1 votes for blockHash1. { - vote := withValidator(voteProto, privValidators[1].GetAddress(), 1) + addr := privValidators[1].GetPubKey().Address() + vote := withValidator(voteProto, addr, 1) added, err := signAddVote(privValidators[1], withBlockHash(vote, blockHash1), voteSet) if !added || err != nil { t.Errorf("Expected VoteSet.Add to succeed") @@ -401,7 +418,8 @@ func TestConflicts(t *testing.T) { // val2 votes for blockHash2. { - vote := withValidator(voteProto, privValidators[2].GetAddress(), 2) + addr := privValidators[2].GetPubKey().Address() + vote := withValidator(voteProto, addr, 2) added, err := signAddVote(privValidators[2], withBlockHash(vote, blockHash2), voteSet) if !added || err != nil { t.Errorf("Expected VoteSet.Add to succeed") @@ -421,7 +439,8 @@ func TestConflicts(t *testing.T) { // val2 votes for blockHash1. { - vote := withValidator(voteProto, privValidators[2].GetAddress(), 2) + addr := privValidators[2].GetPubKey().Address() + vote := withValidator(voteProto, addr, 2) added, err := signAddVote(privValidators[2], withBlockHash(vote, blockHash1), voteSet) if !added { t.Errorf("Expected VoteSet.Add to succeed") @@ -462,7 +481,8 @@ func TestMakeCommit(t *testing.T) { // 6 out of 10 voted for some block. for i := 0; i < 6; i++ { - vote := withValidator(voteProto, privValidators[i].GetAddress(), i) + addr := privValidators[i].GetPubKey().Address() + vote := withValidator(voteProto, addr, i) _, err := signAddVote(privValidators[i], vote, voteSet) if err != nil { t.Error(err) @@ -474,7 +494,8 @@ func TestMakeCommit(t *testing.T) { // 7th voted for some other block. { - vote := withValidator(voteProto, privValidators[6].GetAddress(), 6) + addr := privValidators[6].GetPubKey().Address() + vote := withValidator(voteProto, addr, 6) vote = withBlockHash(vote, cmn.RandBytes(32)) vote = withBlockPartsHeader(vote, PartSetHeader{123, cmn.RandBytes(32)}) @@ -486,7 +507,8 @@ func TestMakeCommit(t *testing.T) { // The 8th voted like everyone else. { - vote := withValidator(voteProto, privValidators[7].GetAddress(), 7) + addr := privValidators[7].GetPubKey().Address() + vote := withValidator(voteProto, addr, 7) _, err := signAddVote(privValidators[7], vote, voteSet) if err != nil { t.Error(err) From 49017a57874649567e967e17aa9c31f1b7af00fd Mon Sep 17 00:00:00 2001 From: srmo Date: Tue, 1 Jan 2019 08:42:39 +0100 Subject: [PATCH 172/267] 3070 [docs] unindent text as it is supposed to behave the same as the parts before (#3075) --- docs/spec/abci/apps.md | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/docs/spec/abci/apps.md b/docs/spec/abci/apps.md index acf2c4e6..a378a2a8 100644 --- a/docs/spec/abci/apps.md +++ b/docs/spec/abci/apps.md @@ -407,21 +407,22 @@ If `storeBlockHeight == stateBlockHeight && appBlockHeight < storeBlockHeight`, replay all blocks in full from `appBlockHeight` to `storeBlockHeight`. This happens if we completed processing the block, but the app forgot its height. -If `storeBlockHeight == stateBlockHeight && appBlockHeight == storeBlockHeight`, we're done +If `storeBlockHeight == stateBlockHeight && appBlockHeight == storeBlockHeight`, we're done. This happens if we crashed at an opportune spot. If `storeBlockHeight == stateBlockHeight+1` This happens if we started processing the block but didn't finish. - If `appBlockHeight < stateBlockHeight` - replay all blocks in full from `appBlockHeight` to `storeBlockHeight-1`, - and replay the block at `storeBlockHeight` using the WAL. - This happens if the app forgot the last block it committed. +If `appBlockHeight < stateBlockHeight` + replay all blocks in full from `appBlockHeight` to `storeBlockHeight-1`, + and replay the block at `storeBlockHeight` using the WAL. +This happens if the app forgot the last block it committed. - If `appBlockHeight == stateBlockHeight`, - replay the last block (storeBlockHeight) in full. - This happens if we crashed before the app finished Commit +If `appBlockHeight == stateBlockHeight`, + replay the last block (storeBlockHeight) in full. +This happens if we crashed before the app finished Commit + +If `appBlockHeight == storeBlockHeight` + update the state using the saved ABCI responses but dont run the block against the real app. +This happens if we crashed after the app finished Commit but before Tendermint saved the state. - If appBlockHeight == storeBlockHeight { - update the state using the saved ABCI responses but dont run the block against the real app. - This happens if we crashed after the app finished Commit but before Tendermint saved the state. From 56a4fb4d72b986992f72e53a9f15cfbfcf1b0629 Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Wed, 2 Jan 2019 17:18:45 -0800 Subject: [PATCH 173/267] add signing spec (#3061) * add signing spec * fixes from review * more fixes * fixes from review --- docs/spec/blockchain/blockchain.md | 2 +- docs/spec/consensus/signing.md | 205 +++++++++++++++++++++++++++++ 2 files changed, 206 insertions(+), 1 deletion(-) create mode 100644 docs/spec/consensus/signing.md diff --git a/docs/spec/blockchain/blockchain.md b/docs/spec/blockchain/blockchain.md index d96a3c7b..cda32326 100644 --- a/docs/spec/blockchain/blockchain.md +++ b/docs/spec/blockchain/blockchain.md @@ -230,7 +230,7 @@ The block version must match the state version. len(block.ChainID) < 50 ``` -ChainID must be maximum 50 UTF-8 symbols. +ChainID must be less than 50 bytes. ### Height diff --git a/docs/spec/consensus/signing.md b/docs/spec/consensus/signing.md new file mode 100644 index 00000000..d1ee71a6 --- /dev/null +++ b/docs/spec/consensus/signing.md @@ -0,0 +1,205 @@ +# Validator Signing + +Here we specify the rules for validating a proposal and vote before signing. +First we include some general notes on validating data structures common to both types. +We then provide specific validation rules for each. Finally, we include validation rules to prevent double-sigining. + +## SignedMsgType + +The `SignedMsgType` is a single byte that refers to the type of the message +being signed. It is defined in Go as follows: + +``` +// SignedMsgType is a type of signed message in the consensus. +type SignedMsgType byte + +const ( + // Votes + PrevoteType SignedMsgType = 0x01 + PrecommitType SignedMsgType = 0x02 + + // Proposals + ProposalType SignedMsgType = 0x20 +) +``` + +All signed messages must correspond to one of these types. + +## Timestamp + +Timestamp validation is subtle and there are currently no bounds placed on the +timestamp included in a proposal or vote. It is expected that validators will honestly +report their local clock time. The median of all timestamps +included in a commit is used as the timestamp for the next block height. + +Timestamps are expected to be strictly monotonic for a given validator, though +this is not currently enforced. + +## ChainID + +ChainID is an unstructured string with a max length of 50-bytes. +In the future, the ChainID may become structured, and may take on longer lengths. +For now, it is recommended that signers be configured for a particular ChainID, +and to only sign votes and proposals corresponding to that ChainID. + +## BlockID + +BlockID is the structure used to represent the block: + +``` +type BlockID struct { + Hash []byte + PartsHeader PartSetHeader +} + +type PartSetHeader struct { + Hash []byte + Total int +} +``` + +To be included in a valid vote or proposal, BlockID must either represent a `nil` block, or a complete one. +We introduce two methods, `BlockID.IsNil()` and `BlockID.IsComplete()` for these cases, respectively. + +`BlockID.IsNil()` returns true for BlockID `b` if each of the following +are true: + +``` +b.Hash == nil +b.PartsHeader.Total == 0 +b.PartsHeader.Hash == nil +``` + +`BlockID.IsComplete()` returns true for BlockID `b` if each of the following +are true: + +``` +len(b.Hash) == 32 +b.PartsHeader.Total > 0 +len(b.PartsHeader.Hash) == 32 +``` + +## Proposals + +The structure of a propsal for signing looks like: + +``` +type CanonicalProposal struct { + Type SignedMsgType // type alias for byte + Height int64 `binary:"fixed64"` + Round int64 `binary:"fixed64"` + POLRound int64 `binary:"fixed64"` + BlockID BlockID + Timestamp time.Time + ChainID string +} +``` + +A proposal is valid if each of the following lines evaluates to true for proposal `p`: + +``` +p.Type == 0x20 +p.Height > 0 +p.Round >= 0 +p.POLRound >= -1 +p.BlockID.IsComplete() +``` + +In other words, a proposal is valid for signing if it contains the type of a Proposal +(0x20), has a positive, non-zero height, a +non-negative round, a POLRound not less than -1, and a complete BlockID. + +## Votes + +The structure of a vote for signing looks like: + +``` +type CanonicalVote struct { + Type SignedMsgType // type alias for byte + Height int64 `binary:"fixed64"` + Round int64 `binary:"fixed64"` + Timestamp time.Time + BlockID BlockID + ChainID string +} +``` + +A vote is valid if each of the following lines evaluates to true for vote `v`: + +``` +v.Type == 0x1 || v.Type == 0x2 +v.Height > 0 +v.Round >= 0 +v.BlockID.IsNil() || v.BlockID.IsValid() +``` + +In other words, a vote is valid for signing if it contains the type of a Prevote +or Precommit (0x1 or 0x2, respectively), has a positive, non-zero height, a +non-negative round, and an empty or valid BlockID. + +## Invalid Votes and Proposals + +Votes and proposals which do not satisfy the above rules are considered invalid. +Peers gossipping invalid votes and proposals may be disconnected from other peers on the network. +Note, however, that there is not currently any explicit mechanism to punish validators signing votes or proposals that fail +these basic validation rules. + +## Double Signing + +Signers must be careful not to sign conflicting messages, also known as "double signing" or "equivocating". +Tendermint has mechanisms to publish evidence of validators that signed conflicting votes, so they can be punished +by the application. Note Tendermint does not currently handle evidence of conflciting proposals, though it may in the future. + +### State + +To prevent such double signing, signers must track the height, round, and type of the last message signed. +Assume the signer keeps the following state, `s`: + +``` +type LastSigned struct { + Height int64 + Round int64 + Type SignedMsgType // byte +} +``` + +After signing a vote or proposal `m`, the signer sets: + +``` +s.Height = m.Height +s.Round = m.Round +s.Type = m.Type +``` + +### Proposals + +A signer should only sign a proposal `p` if any of the following lines are true: + +``` +p.Height > s.Height +p.Height == s.Height && p.Round > s.Round +``` + +In other words, a proposal should only be signed if it's at a higher height, or a higher round for the same height. +Once a proposal or vote has been signed for a given height and round, a proposal should never be signed for the same height and round. + +### Votes + +A signer should only sign a vote `v` if any of the following lines are true: + +``` +v.Height > s.Height +v.Height == s.Height && v.Round > s.Round +v.Height == s.Height && v.Round == s.Round && v.Step == 0x1 && s.Step == 0x20 +v.Height == s.Height && v.Round == s.Round && v.Step == 0x2 && s.Step != 0x2 +``` + +In other words, a vote should only be signed if it's: + +- at a higher height +- at a higher round for the same height +- a prevote for the same height and round where we haven't signed a prevote or precommit (but have signed a proposal) +- a precommit for the same height and round where we haven't signed a precommit (but have signed a proposal and/or a prevote) + +This means that once a validator signs a prevote for a given height and round, the only other message it can sign for that height and round is a precommit. +And once a validator signs a precommit for a given height and round, it must not sign any other message for that same height and round. From 04e97f599aeafa939b55a9c81a7714335bd0596b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Piotr=20Husiaty=C5=84ski?= Date: Sun, 6 Jan 2019 09:40:15 +0100 Subject: [PATCH 174/267] fix build scripts (#3085) * fix build scripts Search for the right variable when introspecting Go code. `Version` was renamed to `TMCoreSemVer`. This is regression introduced in b95ac688af14d130e6ad0b580ed9a8181f6c487c * fix all `Version` introspections. Use `TMCoreSemVer` instead of `Version` --- DOCKER/build.sh | 2 +- DOCKER/push.sh | 2 +- scripts/dist.sh | 2 +- scripts/publish.sh | 2 +- scripts/release.sh | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/DOCKER/build.sh b/DOCKER/build.sh index ee617cc6..2aa42a6c 100755 --- a/DOCKER/build.sh +++ b/DOCKER/build.sh @@ -3,7 +3,7 @@ set -e # Get the tag from the version, or try to figure it out. if [ -z "$TAG" ]; then - TAG=$(awk -F\" '/Version =/ { print $2; exit }' < ../version/version.go) + TAG=$(awk -F\" '/TMCoreSemVer =/ { print $2; exit }' < ../version/version.go) fi if [ -z "$TAG" ]; then echo "Please specify a tag." diff --git a/DOCKER/push.sh b/DOCKER/push.sh index 32741dce..f228406d 100755 --- a/DOCKER/push.sh +++ b/DOCKER/push.sh @@ -3,7 +3,7 @@ set -e # Get the tag from the version, or try to figure it out. if [ -z "$TAG" ]; then - TAG=$(awk -F\" '/Version =/ { print $2; exit }' < ../version/version.go) + TAG=$(awk -F\" '/TMCoreSemVer =/ { print $2; exit }' < ../version/version.go) fi if [ -z "$TAG" ]; then echo "Please specify a tag." diff --git a/scripts/dist.sh b/scripts/dist.sh index 40aa71e9..f999c537 100755 --- a/scripts/dist.sh +++ b/scripts/dist.sh @@ -6,7 +6,7 @@ set -e # Get the version from the environment, or try to figure it out. if [ -z $VERSION ]; then - VERSION=$(awk -F\" '/Version =/ { print $2; exit }' < version/version.go) + VERSION=$(awk -F\" 'TMCoreSemVer =/ { print $2; exit }' < version/version.go) fi if [ -z "$VERSION" ]; then echo "Please specify a version." diff --git a/scripts/publish.sh b/scripts/publish.sh index ba944087..7da299aa 100755 --- a/scripts/publish.sh +++ b/scripts/publish.sh @@ -6,7 +6,7 @@ DIST_DIR=./build/dist # Get the version from the environment, or try to figure it out. if [ -z $VERSION ]; then - VERSION=$(awk -F\" '/Version =/ { print $2; exit }' < version/version.go) + VERSION=$(awk -F\" 'TMCoreSemVer =/ { print $2; exit }' < version/version.go) fi if [ -z "$VERSION" ]; then echo "Please specify a version." diff --git a/scripts/release.sh b/scripts/release.sh index 9a4e508e..8c40d36b 100755 --- a/scripts/release.sh +++ b/scripts/release.sh @@ -3,7 +3,7 @@ set -e # Get the version from the environment, or try to figure it out. if [ -z $VERSION ]; then - VERSION=$(awk -F\" '/Version =/ { print $2; exit }' < version/version.go) + VERSION=$(awk -F\" 'TMCoreSemVer =/ { print $2; exit }' < version/version.go) fi if [ -z "$VERSION" ]; then echo "Please specify a version." From 616c3a4baeb1f52589f6afe09bc2be256b9626d2 Mon Sep 17 00:00:00 2001 From: srmo Date: Sun, 6 Jan 2019 10:00:12 +0100 Subject: [PATCH 175/267] cs: prettify logging of ignored votes (#3086) Refs #3038 --- consensus/state.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/consensus/state.go b/consensus/state.go index 1693e36b..c6f73d35 100644 --- a/consensus/state.go +++ b/consensus/state.go @@ -1529,7 +1529,7 @@ func (cs *ConsensusState) addVote(vote *types.Vote, peerID p2p.ID) (added bool, // Not necessarily a bad peer, but not favourable behaviour. if vote.Height != cs.Height { err = ErrVoteHeightMismatch - cs.Logger.Info("Vote ignored and not added", "voteHeight", vote.Height, "csHeight", cs.Height, "err", err) + cs.Logger.Info("Vote ignored and not added", "voteHeight", vote.Height, "csHeight", cs.Height, "peerID", peerID) return } From 764cfe33aa55acb46316b3e8fdfa72c7bd49a6e4 Mon Sep 17 00:00:00 2001 From: Alessio Treglia Date: Thu, 10 Jan 2019 22:47:20 +0000 Subject: [PATCH 176/267] Don't use pointer receivers for PubKeyMultisigThreshold (#3100) * Don't use pointer receivers for PubKeyMultisigThreshold * test that showcases panic when PubKeyMultisigThreshold are used in sdk: - deserialization will fail in `readInfo` which tries to read a `crypto.PubKey` into a `localInfo` (called by cosmos-sdk/client/keys.GetKeyInfo) * Update changelog * Rename routeTable to nameTable, multisig key is no longer a pointer * sed -i 's/PubKeyAminoRoute/PubKeyAminoName/g' `grep -lrw PubKeyAminoRoute .` upon Jae's request * AminoRoutes -> AminoNames * sed -e 's/PrivKeyAminoRoute/PrivKeyAminoName/g' * Update crypto/encoding/amino/amino.go Co-Authored-By: alessio --- CHANGELOG_PENDING.md | 1 + crypto/ed25519/ed25519.go | 8 +++---- crypto/encoding/amino/amino.go | 28 ++++++++++++------------ crypto/encoding/amino/encode_test.go | 10 ++++----- crypto/multisig/threshold_pubkey.go | 12 +++++----- crypto/multisig/threshold_pubkey_test.go | 16 ++++++++++++++ crypto/multisig/wire.go | 4 ++-- crypto/secp256k1/secp256k1.go | 8 +++---- types/params.go | 4 ++-- types/protobuf.go | 6 ++--- 10 files changed, 57 insertions(+), 40 deletions(-) diff --git a/CHANGELOG_PENDING.md b/CHANGELOG_PENDING.md index aaf80a4f..2cedb02f 100644 --- a/CHANGELOG_PENDING.md +++ b/CHANGELOG_PENDING.md @@ -32,3 +32,4 @@ Special thanks to external contributors on this release: ### BUG FIXES: - [types] \#2926 do not panic if retrieving the private validator's public key fails +- [crypto/encoding] \#3101 Fix `PubKeyMultisigThreshold` unmarshalling into `crypto.PubKey` interface diff --git a/crypto/ed25519/ed25519.go b/crypto/ed25519/ed25519.go index 0c659e73..bc60838d 100644 --- a/crypto/ed25519/ed25519.go +++ b/crypto/ed25519/ed25519.go @@ -18,8 +18,8 @@ import ( var _ crypto.PrivKey = PrivKeyEd25519{} const ( - PrivKeyAminoRoute = "tendermint/PrivKeyEd25519" - PubKeyAminoRoute = "tendermint/PubKeyEd25519" + PrivKeyAminoName = "tendermint/PrivKeyEd25519" + PubKeyAminoName = "tendermint/PubKeyEd25519" // Size of an Edwards25519 signature. Namely the size of a compressed // Edwards25519 point, and a field element. Both of which are 32 bytes. SignatureSize = 64 @@ -30,11 +30,11 @@ var cdc = amino.NewCodec() func init() { cdc.RegisterInterface((*crypto.PubKey)(nil), nil) cdc.RegisterConcrete(PubKeyEd25519{}, - PubKeyAminoRoute, nil) + PubKeyAminoName, nil) cdc.RegisterInterface((*crypto.PrivKey)(nil), nil) cdc.RegisterConcrete(PrivKeyEd25519{}, - PrivKeyAminoRoute, nil) + PrivKeyAminoName, nil) } // PrivKeyEd25519 implements crypto.PrivKey. diff --git a/crypto/encoding/amino/amino.go b/crypto/encoding/amino/amino.go index d66ecd9b..f7be3a20 100644 --- a/crypto/encoding/amino/amino.go +++ b/crypto/encoding/amino/amino.go @@ -12,11 +12,11 @@ import ( var cdc = amino.NewCodec() -// routeTable is used to map public key concrete types back -// to their amino routes. This should eventually be handled +// nameTable is used to map public key concrete types back +// to their registered amino names. This should eventually be handled // by amino. Example usage: -// routeTable[reflect.TypeOf(ed25519.PubKeyEd25519{})] = ed25519.PubKeyAminoRoute -var routeTable = make(map[reflect.Type]string, 3) +// nameTable[reflect.TypeOf(ed25519.PubKeyEd25519{})] = ed25519.PubKeyAminoName +var nameTable = make(map[reflect.Type]string, 3) func init() { // NOTE: It's important that there be no conflicts here, @@ -29,16 +29,16 @@ func init() { // TODO: Have amino provide a way to go from concrete struct to route directly. // Its currently a private API - routeTable[reflect.TypeOf(ed25519.PubKeyEd25519{})] = ed25519.PubKeyAminoRoute - routeTable[reflect.TypeOf(secp256k1.PubKeySecp256k1{})] = secp256k1.PubKeyAminoRoute - routeTable[reflect.TypeOf(&multisig.PubKeyMultisigThreshold{})] = multisig.PubKeyMultisigThresholdAminoRoute + nameTable[reflect.TypeOf(ed25519.PubKeyEd25519{})] = ed25519.PubKeyAminoName + nameTable[reflect.TypeOf(secp256k1.PubKeySecp256k1{})] = secp256k1.PubKeyAminoName + nameTable[reflect.TypeOf(multisig.PubKeyMultisigThreshold{})] = multisig.PubKeyMultisigThresholdAminoRoute } -// PubkeyAminoRoute returns the amino route of a pubkey +// PubkeyAminoName returns the amino route of a pubkey // cdc is currently passed in, as eventually this will not be using // a package level codec. -func PubkeyAminoRoute(cdc *amino.Codec, key crypto.PubKey) (string, bool) { - route, found := routeTable[reflect.TypeOf(key)] +func PubkeyAminoName(cdc *amino.Codec, key crypto.PubKey) (string, bool) { + route, found := nameTable[reflect.TypeOf(key)] return route, found } @@ -47,17 +47,17 @@ func RegisterAmino(cdc *amino.Codec) { // These are all written here instead of cdc.RegisterInterface((*crypto.PubKey)(nil), nil) cdc.RegisterConcrete(ed25519.PubKeyEd25519{}, - ed25519.PubKeyAminoRoute, nil) + ed25519.PubKeyAminoName, nil) cdc.RegisterConcrete(secp256k1.PubKeySecp256k1{}, - secp256k1.PubKeyAminoRoute, nil) + secp256k1.PubKeyAminoName, nil) cdc.RegisterConcrete(multisig.PubKeyMultisigThreshold{}, multisig.PubKeyMultisigThresholdAminoRoute, nil) cdc.RegisterInterface((*crypto.PrivKey)(nil), nil) cdc.RegisterConcrete(ed25519.PrivKeyEd25519{}, - ed25519.PrivKeyAminoRoute, nil) + ed25519.PrivKeyAminoName, nil) cdc.RegisterConcrete(secp256k1.PrivKeySecp256k1{}, - secp256k1.PrivKeyAminoRoute, nil) + secp256k1.PrivKeyAminoName, nil) } func PrivKeyFromBytes(privKeyBytes []byte) (privKey crypto.PrivKey, err error) { diff --git a/crypto/encoding/amino/encode_test.go b/crypto/encoding/amino/encode_test.go index 056dbec4..c8cb2413 100644 --- a/crypto/encoding/amino/encode_test.go +++ b/crypto/encoding/amino/encode_test.go @@ -128,18 +128,18 @@ func TestPubKeyInvalidDataProperReturnsEmpty(t *testing.T) { require.Nil(t, pk) } -func TestPubkeyAminoRoute(t *testing.T) { +func TestPubkeyAminoName(t *testing.T) { tests := []struct { key crypto.PubKey want string found bool }{ - {ed25519.PubKeyEd25519{}, ed25519.PubKeyAminoRoute, true}, - {secp256k1.PubKeySecp256k1{}, secp256k1.PubKeyAminoRoute, true}, - {&multisig.PubKeyMultisigThreshold{}, multisig.PubKeyMultisigThresholdAminoRoute, true}, + {ed25519.PubKeyEd25519{}, ed25519.PubKeyAminoName, true}, + {secp256k1.PubKeySecp256k1{}, secp256k1.PubKeyAminoName, true}, + {multisig.PubKeyMultisigThreshold{}, multisig.PubKeyMultisigThresholdAminoRoute, true}, } for i, tc := range tests { - got, found := PubkeyAminoRoute(cdc, tc.key) + got, found := PubkeyAminoName(cdc, tc.key) require.Equal(t, tc.found, found, "not equal on tc %d", i) if tc.found { require.Equal(t, tc.want, got, "not equal on tc %d", i) diff --git a/crypto/multisig/threshold_pubkey.go b/crypto/multisig/threshold_pubkey.go index ca8d4230..41abc1e5 100644 --- a/crypto/multisig/threshold_pubkey.go +++ b/crypto/multisig/threshold_pubkey.go @@ -11,7 +11,7 @@ type PubKeyMultisigThreshold struct { PubKeys []crypto.PubKey `json:"pubkeys"` } -var _ crypto.PubKey = &PubKeyMultisigThreshold{} +var _ crypto.PubKey = PubKeyMultisigThreshold{} // NewPubKeyMultisigThreshold returns a new PubKeyMultisigThreshold. // Panics if len(pubkeys) < k or 0 >= k. @@ -22,7 +22,7 @@ func NewPubKeyMultisigThreshold(k int, pubkeys []crypto.PubKey) crypto.PubKey { if len(pubkeys) < k { panic("threshold k of n multisignature: len(pubkeys) < k") } - return &PubKeyMultisigThreshold{uint(k), pubkeys} + return PubKeyMultisigThreshold{uint(k), pubkeys} } // VerifyBytes expects sig to be an amino encoded version of a MultiSignature. @@ -31,7 +31,7 @@ func NewPubKeyMultisigThreshold(k int, pubkeys []crypto.PubKey) crypto.PubKey { // and all signatures are valid. (Not just k of the signatures) // The multisig uses a bitarray, so multiple signatures for the same key is not // a concern. -func (pk *PubKeyMultisigThreshold) VerifyBytes(msg []byte, marshalledSig []byte) bool { +func (pk PubKeyMultisigThreshold) VerifyBytes(msg []byte, marshalledSig []byte) bool { var sig *Multisignature err := cdc.UnmarshalBinaryBare(marshalledSig, &sig) if err != nil { @@ -64,18 +64,18 @@ func (pk *PubKeyMultisigThreshold) VerifyBytes(msg []byte, marshalledSig []byte) } // Bytes returns the amino encoded version of the PubKeyMultisigThreshold -func (pk *PubKeyMultisigThreshold) Bytes() []byte { +func (pk PubKeyMultisigThreshold) Bytes() []byte { return cdc.MustMarshalBinaryBare(pk) } // Address returns tmhash(PubKeyMultisigThreshold.Bytes()) -func (pk *PubKeyMultisigThreshold) Address() crypto.Address { +func (pk PubKeyMultisigThreshold) Address() crypto.Address { return crypto.Address(tmhash.Sum(pk.Bytes())) } // Equals returns true iff pk and other both have the same number of keys, and // all constituent keys are the same, and in the same order. -func (pk *PubKeyMultisigThreshold) Equals(other crypto.PubKey) bool { +func (pk PubKeyMultisigThreshold) Equals(other crypto.PubKey) bool { otherKey, sameType := other.(*PubKeyMultisigThreshold) if !sameType { return false diff --git a/crypto/multisig/threshold_pubkey_test.go b/crypto/multisig/threshold_pubkey_test.go index bfc874eb..d442e649 100644 --- a/crypto/multisig/threshold_pubkey_test.go +++ b/crypto/multisig/threshold_pubkey_test.go @@ -95,6 +95,22 @@ func TestMultiSigPubKeyEquality(t *testing.T) { require.False(t, multisigKey.Equals(multisigKey2)) } +func TestPubKeyMultisigThresholdAminoToIface(t *testing.T) { + msg := []byte{1, 2, 3, 4} + pubkeys, _ := generatePubKeysAndSignatures(5, msg) + multisigKey := NewPubKeyMultisigThreshold(2, pubkeys) + + ab, err := cdc.MarshalBinaryLengthPrefixed(multisigKey) + require.NoError(t, err) + // like other crypto.Pubkey implementations (e.g. ed25519.PubKeyEd25519), + // PubKeyMultisigThreshold should be deserializable into a crypto.PubKey: + var pubKey crypto.PubKey + err = cdc.UnmarshalBinaryLengthPrefixed(ab, &pubKey) + require.NoError(t, err) + + require.Equal(t, multisigKey, pubKey) +} + func generatePubKeysAndSignatures(n int, msg []byte) (pubkeys []crypto.PubKey, signatures [][]byte) { pubkeys = make([]crypto.PubKey, n) signatures = make([][]byte, n) diff --git a/crypto/multisig/wire.go b/crypto/multisig/wire.go index 68b84fbf..71e0db14 100644 --- a/crypto/multisig/wire.go +++ b/crypto/multisig/wire.go @@ -20,7 +20,7 @@ func init() { cdc.RegisterConcrete(PubKeyMultisigThreshold{}, PubKeyMultisigThresholdAminoRoute, nil) cdc.RegisterConcrete(ed25519.PubKeyEd25519{}, - ed25519.PubKeyAminoRoute, nil) + ed25519.PubKeyAminoName, nil) cdc.RegisterConcrete(secp256k1.PubKeySecp256k1{}, - secp256k1.PubKeyAminoRoute, nil) + secp256k1.PubKeyAminoName, nil) } diff --git a/crypto/secp256k1/secp256k1.go b/crypto/secp256k1/secp256k1.go index 7fc46d63..d3528fdd 100644 --- a/crypto/secp256k1/secp256k1.go +++ b/crypto/secp256k1/secp256k1.go @@ -16,8 +16,8 @@ import ( //------------------------------------- const ( - PrivKeyAminoRoute = "tendermint/PrivKeySecp256k1" - PubKeyAminoRoute = "tendermint/PubKeySecp256k1" + PrivKeyAminoName = "tendermint/PrivKeySecp256k1" + PubKeyAminoName = "tendermint/PubKeySecp256k1" ) var cdc = amino.NewCodec() @@ -25,11 +25,11 @@ var cdc = amino.NewCodec() func init() { cdc.RegisterInterface((*crypto.PubKey)(nil), nil) cdc.RegisterConcrete(PubKeySecp256k1{}, - PubKeyAminoRoute, nil) + PubKeyAminoName, nil) cdc.RegisterInterface((*crypto.PrivKey)(nil), nil) cdc.RegisterConcrete(PrivKeySecp256k1{}, - PrivKeyAminoRoute, nil) + PrivKeyAminoName, nil) } //------------------------------------- diff --git a/types/params.go b/types/params.go index ec8a8f57..91079e76 100644 --- a/types/params.go +++ b/types/params.go @@ -34,7 +34,7 @@ type EvidenceParams struct { } // ValidatorParams restrict the public key types validators can use. -// NOTE: uses ABCI pubkey naming, not Amino routes. +// NOTE: uses ABCI pubkey naming, not Amino names. type ValidatorParams struct { PubKeyTypes []string `json:"pub_key_types"` } @@ -107,7 +107,7 @@ func (params *ConsensusParams) Validate() error { // Check if keyType is a known ABCIPubKeyType for i := 0; i < len(params.Validator.PubKeyTypes); i++ { keyType := params.Validator.PubKeyTypes[i] - if _, ok := ABCIPubKeyTypesToAminoRoutes[keyType]; !ok { + if _, ok := ABCIPubKeyTypesToAminoNames[keyType]; !ok { return cmn.NewError("params.Validator.PubKeyTypes[%d], %s, is an unknown pubkey type", i, keyType) } diff --git a/types/protobuf.go b/types/protobuf.go index 0f0d25de..eed73b56 100644 --- a/types/protobuf.go +++ b/types/protobuf.go @@ -25,9 +25,9 @@ const ( ) // TODO: Make non-global by allowing for registration of more pubkey types -var ABCIPubKeyTypesToAminoRoutes = map[string]string{ - ABCIPubKeyTypeEd25519: ed25519.PubKeyAminoRoute, - ABCIPubKeyTypeSecp256k1: secp256k1.PubKeyAminoRoute, +var ABCIPubKeyTypesToAminoNames = map[string]string{ + ABCIPubKeyTypeEd25519: ed25519.PubKeyAminoName, + ABCIPubKeyTypeSecp256k1: secp256k1.PubKeyAminoName, } //------------------------------------------------------- From 7644d273077a3e33fb8b46950bbf2367931aec06 Mon Sep 17 00:00:00 2001 From: Alessio Treglia Date: Thu, 10 Jan 2019 23:37:34 +0000 Subject: [PATCH 177/267] Ensure multisig keys have 20-byte address (#3103) * Ensure multisig keys have 20-byte address Use crypto.AddressHash() to avoid returning 32-byte long address. Closes: #3102 * fix pointer * fix test --- CHANGELOG_PENDING.md | 1 + crypto/multisig/threshold_pubkey.go | 7 +++---- crypto/multisig/threshold_pubkey_test.go | 9 ++++++++- 3 files changed, 12 insertions(+), 5 deletions(-) diff --git a/CHANGELOG_PENDING.md b/CHANGELOG_PENDING.md index 2cedb02f..6614f792 100644 --- a/CHANGELOG_PENDING.md +++ b/CHANGELOG_PENDING.md @@ -32,4 +32,5 @@ Special thanks to external contributors on this release: ### BUG FIXES: - [types] \#2926 do not panic if retrieving the private validator's public key fails +- [crypto/multisig] \#3102 fix multisig keys address length - [crypto/encoding] \#3101 Fix `PubKeyMultisigThreshold` unmarshalling into `crypto.PubKey` interface diff --git a/crypto/multisig/threshold_pubkey.go b/crypto/multisig/threshold_pubkey.go index 41abc1e5..234d420f 100644 --- a/crypto/multisig/threshold_pubkey.go +++ b/crypto/multisig/threshold_pubkey.go @@ -2,7 +2,6 @@ package multisig import ( "github.com/tendermint/tendermint/crypto" - "github.com/tendermint/tendermint/crypto/tmhash" ) // PubKeyMultisigThreshold implements a K of N threshold multisig. @@ -32,7 +31,7 @@ func NewPubKeyMultisigThreshold(k int, pubkeys []crypto.PubKey) crypto.PubKey { // The multisig uses a bitarray, so multiple signatures for the same key is not // a concern. func (pk PubKeyMultisigThreshold) VerifyBytes(msg []byte, marshalledSig []byte) bool { - var sig *Multisignature + var sig Multisignature err := cdc.UnmarshalBinaryBare(marshalledSig, &sig) if err != nil { return false @@ -70,13 +69,13 @@ func (pk PubKeyMultisigThreshold) Bytes() []byte { // Address returns tmhash(PubKeyMultisigThreshold.Bytes()) func (pk PubKeyMultisigThreshold) Address() crypto.Address { - return crypto.Address(tmhash.Sum(pk.Bytes())) + return crypto.AddressHash(pk.Bytes()) } // Equals returns true iff pk and other both have the same number of keys, and // all constituent keys are the same, and in the same order. func (pk PubKeyMultisigThreshold) Equals(other crypto.PubKey) bool { - otherKey, sameType := other.(*PubKeyMultisigThreshold) + otherKey, sameType := other.(PubKeyMultisigThreshold) if !sameType { return false } diff --git a/crypto/multisig/threshold_pubkey_test.go b/crypto/multisig/threshold_pubkey_test.go index d442e649..2d2632ab 100644 --- a/crypto/multisig/threshold_pubkey_test.go +++ b/crypto/multisig/threshold_pubkey_test.go @@ -82,7 +82,7 @@ func TestMultiSigPubKeyEquality(t *testing.T) { msg := []byte{1, 2, 3, 4} pubkeys, _ := generatePubKeysAndSignatures(5, msg) multisigKey := NewPubKeyMultisigThreshold(2, pubkeys) - var unmarshalledMultisig *PubKeyMultisigThreshold + var unmarshalledMultisig PubKeyMultisigThreshold cdc.MustUnmarshalBinaryBare(multisigKey.Bytes(), &unmarshalledMultisig) require.True(t, multisigKey.Equals(unmarshalledMultisig)) @@ -95,6 +95,13 @@ func TestMultiSigPubKeyEquality(t *testing.T) { require.False(t, multisigKey.Equals(multisigKey2)) } +func TestAddress(t *testing.T) { + msg := []byte{1, 2, 3, 4} + pubkeys, _ := generatePubKeysAndSignatures(5, msg) + multisigKey := NewPubKeyMultisigThreshold(2, pubkeys) + require.Len(t, multisigKey.Address().Bytes(), 20) +} + func TestPubKeyMultisigThresholdAminoToIface(t *testing.T) { msg := []byte{1, 2, 3, 4} pubkeys, _ := generatePubKeysAndSignatures(5, msg) From 51094f9417c83bd7a5f314cface6134250b2bcaa Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Fri, 11 Jan 2019 08:28:29 -0500 Subject: [PATCH 178/267] update README (#3097) * update README * fix from review --- README.md | 93 ++++++++++++++++++++++++++++++------------------------- 1 file changed, 51 insertions(+), 42 deletions(-) diff --git a/README.md b/README.md index 6e5c9e9a..601e3830 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,8 @@ # Tendermint [Byzantine-Fault Tolerant](https://en.wikipedia.org/wiki/Byzantine_fault_tolerance) -[State Machine Replication](https://en.wikipedia.org/wiki/State_machine_replication). -Or [Blockchain](https://en.wikipedia.org/wiki/Blockchain_(database)) for short. +[State Machines](https://en.wikipedia.org/wiki/State_machine_replication). +Or [Blockchain](https://en.wikipedia.org/wiki/Blockchain_(database)), for short. [![version](https://img.shields.io/github/tag/tendermint/tendermint.svg)](https://github.com/tendermint/tendermint/releases/latest) [![API Reference]( @@ -66,49 +66,26 @@ See the [install instructions](/docs/introduction/install.md) - [Remote cluster using terraform and ansible](/docs/networks/terraform-and-ansible.md) - [Join the Cosmos testnet](https://cosmos.network/testnet) -## Resources - -### Tendermint Core - -For details about the blockchain data structures and the p2p protocols, see the -the [Tendermint specification](/docs/spec). - -For details on using the software, see the [documentation](/docs/) which is also -hosted at: https://tendermint.com/docs/ - -### Tools - -Benchmarking and monitoring is provided by `tm-bench` and `tm-monitor`, respectively. -Their code is found [here](/tools) and these binaries need to be built seperately. -Additional documentation is found [here](/docs/tools). - -### Sub-projects - -* [Amino](http://github.com/tendermint/go-amino), a reflection-based improvement on proto3 -* [IAVL](http://github.com/tendermint/iavl), Merkleized IAVL+ Tree implementation - -### Applications - -* [Cosmos SDK](http://github.com/cosmos/cosmos-sdk); a cryptocurrency application framework -* [Ethermint](http://github.com/cosmos/ethermint); Ethereum on Tendermint -* [Many more](https://tendermint.com/ecosystem) - -### Research - -* [The latest gossip on BFT consensus](https://arxiv.org/abs/1807.04938) -* [Master's Thesis on Tendermint](https://atrium.lib.uoguelph.ca/xmlui/handle/10214/9769) -* [Original Whitepaper](https://tendermint.com/static/docs/tendermint.pdf) -* [Blog](https://blog.cosmos.network/tendermint/home) - ## Contributing -Yay open source! Please see our [contributing guidelines](CONTRIBUTING.md). +Please abide by the [Code of Conduct](CODE_OF_CONDUCT.md) in all interactions, +and the [contributing guidelines](CONTRIBUTING.md) when submitting code. + +Join the larger community on the [forum](https://forum.cosmos.network/) and the [chat](https://riot.im/app/#/room/#tendermint:matrix.org). + +To learn more about the structure of the software, watch the [Developer +Sessions](https://www.youtube.com/playlist?list=PLdQIb0qr3pnBbG5ZG-0gr3zM86_s8Rpqv) +and read some [Architectural +Decision Records](https://github.com/tendermint/tendermint/tree/master/docs/architecture). + +Learn more by reading the code and comparing it to the +[specification](https://github.com/tendermint/tendermint/tree/develop/docs/spec). ## Versioning -### SemVer +### Semantic Versioning -Tendermint uses [SemVer](http://semver.org/) to determine when and how the version changes. +Tendermint uses [Semantic Versioning](http://semver.org/) to determine when and how the version changes. According to SemVer, anything in the public API can change at any time before version 1.0.0 To provide some stability to Tendermint users in these 0.X.X days, the MINOR version is used @@ -145,8 +122,40 @@ data into the new chain. However, any bump in the PATCH version should be compatible with existing histories (if not please open an [issue](https://github.com/tendermint/tendermint/issues)). -For more information on upgrading, see [here](./UPGRADING.md) +For more information on upgrading, see [UPGRADING.md](./UPGRADING.md) -## Code of Conduct +## Resources + +### Tendermint Core + +For details about the blockchain data structures and the p2p protocols, see the +[Tendermint specification](/docs/spec). + +For details on using the software, see the [documentation](/docs/) which is also +hosted at: https://tendermint.com/docs/ + +### Tools + +Benchmarking and monitoring is provided by `tm-bench` and `tm-monitor`, respectively. +Their code is found [here](/tools) and these binaries need to be built seperately. +Additional documentation is found [here](/docs/tools). + +### Sub-projects + +* [Amino](http://github.com/tendermint/go-amino), reflection-based proto3, with + interfaces +* [IAVL](http://github.com/tendermint/iavl), Merkleized IAVL+ Tree implementation + +### Applications + +* [Cosmos SDK](http://github.com/cosmos/cosmos-sdk); a cryptocurrency application framework +* [Ethermint](http://github.com/cosmos/ethermint); Ethereum on Tendermint +* [Many more](https://tendermint.com/ecosystem) + +### Research + +* [The latest gossip on BFT consensus](https://arxiv.org/abs/1807.04938) +* [Master's Thesis on Tendermint](https://atrium.lib.uoguelph.ca/xmlui/handle/10214/9769) +* [Original Whitepaper](https://tendermint.com/static/docs/tendermint.pdf) +* [Blog](https://blog.cosmos.network/tendermint/home) -Please read, understand and adhere to our [code of conduct](CODE_OF_CONDUCT.md). From 81c51cd4fcc8e826ba4dd4e6d4da696ba5c3fe37 Mon Sep 17 00:00:00 2001 From: Anton Kaliaev Date: Fri, 11 Jan 2019 17:24:45 +0300 Subject: [PATCH 179/267] rpc: include peer's remote IP in `/net_info` (#3052) Refs #3047 --- CHANGELOG_PENDING.md | 1 + rpc/core/net.go | 1 + rpc/core/types/responses.go | 2 ++ 3 files changed, 4 insertions(+) diff --git a/CHANGELOG_PENDING.md b/CHANGELOG_PENDING.md index 6614f792..6bfdf845 100644 --- a/CHANGELOG_PENDING.md +++ b/CHANGELOG_PENDING.md @@ -29,6 +29,7 @@ Special thanks to external contributors on this release: - [privval] \#1181 Split immutable and mutable parts of priv_validator.json ### IMPROVEMENTS: +- [rpc] \#3047 Include peer's remote IP in `/net_info` ### BUG FIXES: - [types] \#2926 do not panic if retrieving the private validator's public key fails diff --git a/rpc/core/net.go b/rpc/core/net.go index b80902da..4d95c2ef 100644 --- a/rpc/core/net.go +++ b/rpc/core/net.go @@ -53,6 +53,7 @@ func NetInfo() (*ctypes.ResultNetInfo, error) { NodeInfo: nodeInfo, IsOutbound: peer.IsOutbound(), ConnectionStatus: peer.Status(), + RemoteIP: peer.RemoteIP(), }) } // TODO: Should we include PersistentPeers and Seeds in here? diff --git a/rpc/core/types/responses.go b/rpc/core/types/responses.go index af5c4947..62be1caf 100644 --- a/rpc/core/types/responses.go +++ b/rpc/core/types/responses.go @@ -2,6 +2,7 @@ package core_types import ( "encoding/json" + "net" "time" abci "github.com/tendermint/tendermint/abci/types" @@ -110,6 +111,7 @@ type Peer struct { NodeInfo p2p.DefaultNodeInfo `json:"node_info"` IsOutbound bool `json:"is_outbound"` ConnectionStatus p2p.ConnectionStatus `json:"connection_status"` + RemoteIP net.IP `json:"remote_ip"` } // Validators for a height From 7f607d0ce23d5c578325b2d2a6bfe2273191cf9b Mon Sep 17 00:00:00 2001 From: Mauricio Serna Date: Fri, 11 Jan 2019 17:41:02 -0500 Subject: [PATCH 180/267] docs: fix p2p readme links (#3109) --- p2p/README.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/p2p/README.md b/p2p/README.md index 819a5056..df4c2ae0 100644 --- a/p2p/README.md +++ b/p2p/README.md @@ -4,8 +4,8 @@ The p2p package provides an abstraction around peer-to-peer communication. Docs: -- [Connection](https://github.com/tendermint/tendermint/blob/master/docs/spec/docs/spec/p2p/connection.md) for details on how connections and multiplexing work -- [Peer](https://github.com/tendermint/tendermint/blob/master/docs/spec/docs/spec/p2p/peer.md) for details on peer ID, handshakes, and peer exchange -- [Node](https://github.com/tendermint/tendermint/blob/master/docs/spec/docs/spec/p2p/node.md) for details about different types of nodes and how they should work -- [Pex](https://github.com/tendermint/tendermint/blob/master/docs/spec/docs/spec/reactors/pex/pex.md) for details on peer discovery and exchange -- [Config](https://github.com/tendermint/tendermint/blob/master/docs/spec/docs/spec/p2p/config.md) for details on some config option +- [Connection](https://github.com/tendermint/tendermint/blob/master/docs/spec/p2p/connection.md) for details on how connections and multiplexing work +- [Peer](https://github.com/tendermint/tendermint/blob/master/docs/spec/p2p/peer.md) for details on peer ID, handshakes, and peer exchange +- [Node](https://github.com/tendermint/tendermint/blob/master/docs/spec/p2p/node.md) for details about different types of nodes and how they should work +- [Pex](https://github.com/tendermint/tendermint/blob/master/docs/spec/reactors/pex/pex.md) for details on peer discovery and exchange +- [Config](https://github.com/tendermint/tendermint/blob/master/docs/spec/p2p/config.md) for details on some config option From ef94a322b8726974f7483deae88cccb5f1c1fe0e Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Sun, 13 Jan 2019 13:46:25 -0500 Subject: [PATCH 181/267] Make SecretConnection thread safe (#3111) * p2p/conn: add failing tests * p2p/conn: make SecretConnection thread safe * changelog * fix from review --- CHANGELOG_PENDING.md | 9 +++-- p2p/conn/secret_connection.go | 65 +++++++++++++++++++++--------- p2p/conn/secret_connection_test.go | 65 ++++++++++++++++++++++++++++++ 3 files changed, 117 insertions(+), 22 deletions(-) diff --git a/CHANGELOG_PENDING.md b/CHANGELOG_PENDING.md index 6bfdf845..7fc9bead 100644 --- a/CHANGELOG_PENDING.md +++ b/CHANGELOG_PENDING.md @@ -1,4 +1,4 @@ -## v0.27.4 +## v0.28.0 *TBD* @@ -15,9 +15,9 @@ Special thanks to external contributors on this release: * Apps -* Go API -- [types] \#2926 memoize consensus public key on initialization of remote signer and return the memoized key on -`PrivValidator.GetPubKey()` instead of requesting it again +* Go API +- [types] \#2926 memoize consensus public key on initialization of remote signer and return the memoized key on +`PrivValidator.GetPubKey()` instead of requesting it again - [types] \#2981 Remove `PrivValidator.GetAddress()` * Blockchain Protocol @@ -29,6 +29,7 @@ Special thanks to external contributors on this release: - [privval] \#1181 Split immutable and mutable parts of priv_validator.json ### IMPROVEMENTS: +- [p2p/conn] \#3111 make SecretConnection thread safe - [rpc] \#3047 Include peer's remote IP in `/net_info` ### BUG FIXES: diff --git a/p2p/conn/secret_connection.go b/p2p/conn/secret_connection.go index d1b6bce6..aa019aa3 100644 --- a/p2p/conn/secret_connection.go +++ b/p2p/conn/secret_connection.go @@ -8,6 +8,7 @@ import ( "errors" "io" "net" + "sync" "time" "golang.org/x/crypto/chacha20poly1305" @@ -27,20 +28,36 @@ const aeadSizeOverhead = 16 // overhead of poly 1305 authentication tag const aeadKeySize = chacha20poly1305.KeySize const aeadNonceSize = chacha20poly1305.NonceSize -// SecretConnection implements net.conn. +// SecretConnection implements net.Conn. // It is an implementation of the STS protocol. -// Note we do not (yet) assume that a remote peer's pubkey -// is known ahead of time, and thus we are technically -// still vulnerable to MITM. (TODO!) -// See docs/sts-final.pdf for more info +// See https://github.com/tendermint/tendermint/blob/0.1/docs/sts-final.pdf for +// details on the protocol. +// +// Consumers of the SecretConnection are responsible for authenticating +// the remote peer's pubkey against known information, like a nodeID. +// Otherwise they are vulnerable to MITM. +// (TODO(ismail): see also https://github.com/tendermint/tendermint/issues/3010) type SecretConnection struct { - conn io.ReadWriteCloser - recvBuffer []byte - recvNonce *[aeadNonceSize]byte - sendNonce *[aeadNonceSize]byte + + // immutable recvSecret *[aeadKeySize]byte sendSecret *[aeadKeySize]byte remPubKey crypto.PubKey + conn io.ReadWriteCloser + + // net.Conn must be thread safe: + // https://golang.org/pkg/net/#Conn. + // Since we have internal mutable state, + // we need mtxs. But recv and send states + // are independent, so we can use two mtxs. + // All .Read are covered by recvMtx, + // all .Write are covered by sendMtx. + recvMtx sync.Mutex + recvBuffer []byte + recvNonce *[aeadNonceSize]byte + + sendMtx sync.Mutex + sendNonce *[aeadNonceSize]byte } // MakeSecretConnection performs handshake and returns a new authenticated @@ -109,9 +126,12 @@ func (sc *SecretConnection) RemotePubKey() crypto.PubKey { return sc.remPubKey } -// Writes encrypted frames of `sealedFrameSize` -// CONTRACT: data smaller than dataMaxSize is read atomically. +// Writes encrypted frames of `totalFrameSize + aeadSizeOverhead`. +// CONTRACT: data smaller than dataMaxSize is written atomically. func (sc *SecretConnection) Write(data []byte) (n int, err error) { + sc.sendMtx.Lock() + defer sc.sendMtx.Unlock() + for 0 < len(data) { var frame = make([]byte, totalFrameSize) var chunk []byte @@ -130,6 +150,7 @@ func (sc *SecretConnection) Write(data []byte) (n int, err error) { if err != nil { return n, errors.New("Invalid SecretConnection Key") } + // encrypt the frame var sealedFrame = make([]byte, aeadSizeOverhead+totalFrameSize) aead.Seal(sealedFrame[:0], sc.sendNonce[:], frame, nil) @@ -147,23 +168,30 @@ func (sc *SecretConnection) Write(data []byte) (n int, err error) { // CONTRACT: data smaller than dataMaxSize is read atomically. func (sc *SecretConnection) Read(data []byte) (n int, err error) { + sc.recvMtx.Lock() + defer sc.recvMtx.Unlock() + + // read off and update the recvBuffer, if non-empty if 0 < len(sc.recvBuffer) { n = copy(data, sc.recvBuffer) sc.recvBuffer = sc.recvBuffer[n:] return } + // read off the conn + sealedFrame := make([]byte, totalFrameSize+aeadSizeOverhead) + _, err = io.ReadFull(sc.conn, sealedFrame) + if err != nil { + return + } + aead, err := chacha20poly1305.New(sc.recvSecret[:]) if err != nil { return n, errors.New("Invalid SecretConnection Key") } - sealedFrame := make([]byte, totalFrameSize+aeadSizeOverhead) - _, err = io.ReadFull(sc.conn, sealedFrame) - if err != nil { - return - } - // decrypt the frame + // decrypt the frame. + // reads and updates the sc.recvNonce var frame = make([]byte, totalFrameSize) _, err = aead.Open(frame[:0], sc.recvNonce[:], sealedFrame, nil) if err != nil { @@ -172,12 +200,13 @@ func (sc *SecretConnection) Read(data []byte) (n int, err error) { incrNonce(sc.recvNonce) // end decryption + // copy checkLength worth into data, + // set recvBuffer to the rest. var chunkLength = binary.LittleEndian.Uint32(frame) // read the first four bytes if chunkLength > dataMaxSize { return 0, errors.New("chunkLength is greater than dataMaxSize") } var chunk = frame[dataLenSize : dataLenSize+chunkLength] - n = copy(data, chunk) sc.recvBuffer = chunk[n:] return diff --git a/p2p/conn/secret_connection_test.go b/p2p/conn/secret_connection_test.go index 75ed8fe0..131ab922 100644 --- a/p2p/conn/secret_connection_test.go +++ b/p2p/conn/secret_connection_test.go @@ -7,10 +7,12 @@ import ( "fmt" "io" "log" + "net" "os" "path/filepath" "strconv" "strings" + "sync" "testing" "github.com/stretchr/testify/assert" @@ -98,6 +100,69 @@ func TestSecretConnectionHandshake(t *testing.T) { } } +func TestConcurrentWrite(t *testing.T) { + fooSecConn, barSecConn := makeSecretConnPair(t) + fooWriteText := cmn.RandStr(dataMaxSize) + + // write from two routines. + // should be safe from race according to net.Conn: + // https://golang.org/pkg/net/#Conn + n := 100 + wg := new(sync.WaitGroup) + wg.Add(3) + go writeLots(t, wg, fooSecConn, fooWriteText, n) + go writeLots(t, wg, fooSecConn, fooWriteText, n) + + // Consume reads from bar's reader + readLots(t, wg, barSecConn, n*2) + wg.Wait() + + if err := fooSecConn.Close(); err != nil { + t.Error(err) + } +} + +func TestConcurrentRead(t *testing.T) { + fooSecConn, barSecConn := makeSecretConnPair(t) + fooWriteText := cmn.RandStr(dataMaxSize) + n := 100 + + // read from two routines. + // should be safe from race according to net.Conn: + // https://golang.org/pkg/net/#Conn + wg := new(sync.WaitGroup) + wg.Add(3) + go readLots(t, wg, fooSecConn, n/2) + go readLots(t, wg, fooSecConn, n/2) + + // write to bar + writeLots(t, wg, barSecConn, fooWriteText, n) + wg.Wait() + + if err := fooSecConn.Close(); err != nil { + t.Error(err) + } +} + +func writeLots(t *testing.T, wg *sync.WaitGroup, conn net.Conn, txt string, n int) { + defer wg.Done() + for i := 0; i < n; i++ { + _, err := conn.Write([]byte(txt)) + if err != nil { + t.Fatalf("Failed to write to fooSecConn: %v", err) + } + } +} + +func readLots(t *testing.T, wg *sync.WaitGroup, conn net.Conn, n int) { + readBuffer := make([]byte, dataMaxSize) + for i := 0; i < n; i++ { + _, err := conn.Read(readBuffer) + assert.NoError(t, err) + } + wg.Done() +} + func TestSecretConnectionReadWrite(t *testing.T) { fooConn, barConn := makeKVStoreConnPair() fooWrites, barWrites := []string{}, []string{} From a6011c007db764b722c5fe6c5ccb9d85edbb92fe Mon Sep 17 00:00:00 2001 From: Ismail Khoffi Date: Sun, 13 Jan 2019 20:31:31 +0100 Subject: [PATCH 182/267] Close and retry a RemoteSigner on err (#2923) * Close and recreate a RemoteSigner on err * Update changelog * Address Anton's comments / suggestions: - update changelog - restart TCPVal - shut down on `ErrUnexpectedResponse` * re-init remote signer client with fresh connection if Ping fails - add/update TODOs in secret connection - rename tcp.go -> tcp_client.go, same with ipc to clarify their purpose * account for `conn returned by waitConnection can be `nil` - also add TODO about RemoteSigner conn field * Tests for retrying: IPC / TCP - shorter info log on success - set conn and use it in tests to close conn * Tests for retrying: IPC / TCP - shorter info log on success - set conn and use it in tests to close conn - add rwmutex for conn field in IPC * comments and doc.go * fix ipc tests. fixes #2677 * use constants for tests * cleanup some error statements * fixes #2784, race in tests * remove print statement * minor fixes from review * update comment on sts spec * cosmetics * p2p/conn: add failing tests * p2p/conn: make SecretConnection thread safe * changelog * IPCVal signer refactor - use a .reset() method - don't use embedded RemoteSignerClient - guard RemoteSignerClient with mutex - drop the .conn - expose Close() on RemoteSignerClient * apply IPCVal refactor to TCPVal * remove mtx from RemoteSignerClient * consolidate IPCVal and TCPVal, fixes #3104 - done in tcp_client.go - now called SocketVal - takes a listener in the constructor - make tcpListener and unixListener contain all the differences * delete ipc files * introduce unix and tcp dialer for RemoteSigner * rename files - drop tcp_ prefix - rename priv_validator.go to file.go * bring back listener options * fix node * fix priv_val_server * fix node test * minor cleanup and comments --- CHANGELOG_PENDING.md | 6 +- cmd/priv_val_server/main.go | 22 +- node/node.go | 17 +- node/node_test.go | 26 +- privval/client.go | 263 ++++++++++++++++++ privval/{tcp_test.go => client_test.go} | 152 ++++++---- privval/doc.go | 21 ++ privval/{priv_validator.go => file.go} | 3 +- .../{priv_validator_test.go => file_test.go} | 0 privval/ipc.go | 123 -------- privval/ipc_server.go | 132 --------- privval/ipc_test.go | 147 ---------- .../{old_priv_validator.go => old_file.go} | 0 ...riv_validator_test.go => old_file_test.go} | 0 privval/remote_signer.go | 56 ++-- privval/{tcp_server.go => server.go} | 88 +++--- privval/socket.go | 184 ++++++++++++ .../{tcp_socket_test.go => socket_test.go} | 35 ++- privval/tcp.go | 216 -------------- privval/tcp_socket.go | 90 ------ 20 files changed, 710 insertions(+), 871 deletions(-) create mode 100644 privval/client.go rename privval/{tcp_test.go => client_test.go} (70%) create mode 100644 privval/doc.go rename privval/{priv_validator.go => file.go} (99%) rename privval/{priv_validator_test.go => file_test.go} (100%) delete mode 100644 privval/ipc.go delete mode 100644 privval/ipc_server.go delete mode 100644 privval/ipc_test.go rename privval/{old_priv_validator.go => old_file.go} (100%) rename privval/{old_priv_validator_test.go => old_file_test.go} (100%) rename privval/{tcp_server.go => server.go} (63%) create mode 100644 privval/socket.go rename privval/{tcp_socket_test.go => socket_test.go} (52%) delete mode 100644 privval/tcp.go delete mode 100644 privval/tcp_socket.go diff --git a/CHANGELOG_PENDING.md b/CHANGELOG_PENDING.md index 7fc9bead..fd95a944 100644 --- a/CHANGELOG_PENDING.md +++ b/CHANGELOG_PENDING.md @@ -10,8 +10,8 @@ Special thanks to external contributors on this release: - [cli] Removed `node` `--proxy_app=dummy` option. Use `kvstore` (`persistent_kvstore`) instead. - [cli] Renamed `node` `--proxy_app=nilapp` to `--proxy_app=noop`. - [config] \#2992 `allow_duplicate_ip` is now set to false - - [privval] \#2926 split up `PubKeyMsg` into `PubKeyRequest` and `PubKeyResponse` to be consistent with other message types +- [privval] \#2923 listen for unix socket connections instead of dialing them * Apps @@ -23,13 +23,13 @@ Special thanks to external contributors on this release: * Blockchain Protocol * P2P Protocol -- multiple connections from the same IP are now disabled by default (see `allow_duplicate_ip` config option) ### FEATURES: -- [privval] \#1181 Split immutable and mutable parts of priv_validator.json +- [privval] \#1181 Split immutable and mutable parts of `priv_validator.json` ### IMPROVEMENTS: - [p2p/conn] \#3111 make SecretConnection thread safe +- [privval] \#2923 retry RemoteSigner connections on error - [rpc] \#3047 Include peer's remote IP in `/net_info` ### BUG FIXES: diff --git a/cmd/priv_val_server/main.go b/cmd/priv_val_server/main.go index 54602558..6949e878 100644 --- a/cmd/priv_val_server/main.go +++ b/cmd/priv_val_server/main.go @@ -3,6 +3,7 @@ package main import ( "flag" "os" + "time" "github.com/tendermint/tendermint/crypto/ed25519" cmn "github.com/tendermint/tendermint/libs/common" @@ -34,13 +35,20 @@ func main() { pv := privval.LoadFilePV(*privValKeyPath, *privValStatePath) - rs := privval.NewRemoteSigner( - logger, - *chainID, - *addr, - pv, - ed25519.GenPrivKey(), - ) + var dialer privval.Dialer + protocol, address := cmn.ProtocolAndAddress(*addr) + switch protocol { + case "unix": + dialer = privval.DialUnixFn(address) + case "tcp": + connTimeout := 3 * time.Second // TODO + dialer = privval.DialTCPFn(address, connTimeout, ed25519.GenPrivKey()) + default: + logger.Error("Unknown protocol", "protocol", protocol) + return + } + + rs := privval.NewRemoteSigner(logger, *chainID, pv, dialer) err := rs.Start() if err != nil { panic(err) diff --git a/node/node.go b/node/node.go index be4c7cc7..b7998dac 100644 --- a/node/node.go +++ b/node/node.go @@ -878,16 +878,20 @@ func createAndStartPrivValidatorSocketClient( listenAddr string, logger log.Logger, ) (types.PrivValidator, error) { - var pvsc types.PrivValidator + var listener net.Listener protocol, address := cmn.ProtocolAndAddress(listenAddr) + ln, err := net.Listen(protocol, address) + if err != nil { + return nil, err + } switch protocol { case "unix": - pvsc = privval.NewIPCVal(logger.With("module", "privval"), address) + listener = privval.NewUnixListener(ln) case "tcp": // TODO: persist this key so external signer // can actually authenticate us - pvsc = privval.NewTCPVal(logger.With("module", "privval"), listenAddr, ed25519.GenPrivKey()) + listener = privval.NewTCPListener(ln, ed25519.GenPrivKey()) default: return nil, fmt.Errorf( "Wrong listen address: expected either 'tcp' or 'unix' protocols, got %s", @@ -895,10 +899,9 @@ func createAndStartPrivValidatorSocketClient( ) } - if pvsc, ok := pvsc.(cmn.Service); ok { - if err := pvsc.Start(); err != nil { - return nil, errors.Wrap(err, "failed to start") - } + pvsc := privval.NewSocketVal(logger.With("module", "privval"), listener) + if err := pvsc.Start(); err != nil { + return nil, errors.Wrap(err, "failed to start") } return pvsc, nil diff --git a/node/node_test.go b/node/node_test.go index e675eb9a..96d779d4 100644 --- a/node/node_test.go +++ b/node/node_test.go @@ -122,25 +122,25 @@ func TestNodeSetPrivValTCP(t *testing.T) { config := cfg.ResetTestRoot("node_priv_val_tcp_test") config.BaseConfig.PrivValidatorListenAddr = addr - rs := privval.NewRemoteSigner( + dialer := privval.DialTCPFn(addr, 100*time.Millisecond, ed25519.GenPrivKey()) + pvsc := privval.NewRemoteSigner( log.TestingLogger(), config.ChainID(), - addr, types.NewMockPV(), - ed25519.GenPrivKey(), + dialer, ) - privval.RemoteSignerConnDeadline(5 * time.Millisecond)(rs) + go func() { - err := rs.Start() + err := pvsc.Start() if err != nil { panic(err) } }() - defer rs.Stop() + defer pvsc.Stop() n, err := DefaultNewNode(config, log.TestingLogger()) require.NoError(t, err) - assert.IsType(t, &privval.TCPVal{}, n.PrivValidator()) + assert.IsType(t, &privval.SocketVal{}, n.PrivValidator()) } // address without a protocol must result in error @@ -161,25 +161,25 @@ func TestNodeSetPrivValIPC(t *testing.T) { config := cfg.ResetTestRoot("node_priv_val_tcp_test") config.BaseConfig.PrivValidatorListenAddr = "unix://" + tmpfile - rs := privval.NewIPCRemoteSigner( + dialer := privval.DialUnixFn(tmpfile) + pvsc := privval.NewRemoteSigner( log.TestingLogger(), config.ChainID(), - tmpfile, types.NewMockPV(), + dialer, ) - privval.IPCRemoteSignerConnDeadline(3 * time.Second)(rs) done := make(chan struct{}) go func() { defer close(done) n, err := DefaultNewNode(config, log.TestingLogger()) require.NoError(t, err) - assert.IsType(t, &privval.IPCVal{}, n.PrivValidator()) + assert.IsType(t, &privval.SocketVal{}, n.PrivValidator()) }() - err := rs.Start() + err := pvsc.Start() require.NoError(t, err) - defer rs.Stop() + defer pvsc.Stop() <-done } diff --git a/privval/client.go b/privval/client.go new file mode 100644 index 00000000..4d4395fd --- /dev/null +++ b/privval/client.go @@ -0,0 +1,263 @@ +package privval + +import ( + "errors" + "fmt" + "net" + "sync" + "time" + + "github.com/tendermint/tendermint/crypto" + cmn "github.com/tendermint/tendermint/libs/common" + "github.com/tendermint/tendermint/libs/log" + "github.com/tendermint/tendermint/types" +) + +const ( + defaultConnHeartBeatSeconds = 2 + defaultDialRetries = 10 +) + +// Socket errors. +var ( + ErrUnexpectedResponse = errors.New("received unexpected response") +) + +var ( + connHeartbeat = time.Second * defaultConnHeartBeatSeconds +) + +// SocketValOption sets an optional parameter on the SocketVal. +type SocketValOption func(*SocketVal) + +// SocketValHeartbeat sets the period on which to check the liveness of the +// connected Signer connections. +func SocketValHeartbeat(period time.Duration) SocketValOption { + return func(sc *SocketVal) { sc.connHeartbeat = period } +} + +// SocketVal implements PrivValidator. +// It listens for an external process to dial in and uses +// the socket to request signatures. +type SocketVal struct { + cmn.BaseService + + listener net.Listener + + // ping + cancelPing chan struct{} + pingTicker *time.Ticker + connHeartbeat time.Duration + + // signer is mutable since it can be + // reset if the connection fails. + // failures are detected by a background + // ping routine. + // Methods on the underlying net.Conn itself + // are already gorountine safe. + mtx sync.RWMutex + signer *RemoteSignerClient +} + +// Check that SocketVal implements PrivValidator. +var _ types.PrivValidator = (*SocketVal)(nil) + +// NewSocketVal returns an instance of SocketVal. +func NewSocketVal( + logger log.Logger, + listener net.Listener, +) *SocketVal { + sc := &SocketVal{ + listener: listener, + connHeartbeat: connHeartbeat, + } + + sc.BaseService = *cmn.NewBaseService(logger, "SocketVal", sc) + + return sc +} + +//-------------------------------------------------------- +// Implement PrivValidator + +// GetPubKey implements PrivValidator. +func (sc *SocketVal) GetPubKey() crypto.PubKey { + sc.mtx.RLock() + defer sc.mtx.RUnlock() + return sc.signer.GetPubKey() +} + +// SignVote implements PrivValidator. +func (sc *SocketVal) SignVote(chainID string, vote *types.Vote) error { + sc.mtx.RLock() + defer sc.mtx.RUnlock() + return sc.signer.SignVote(chainID, vote) +} + +// SignProposal implements PrivValidator. +func (sc *SocketVal) SignProposal(chainID string, proposal *types.Proposal) error { + sc.mtx.RLock() + defer sc.mtx.RUnlock() + return sc.signer.SignProposal(chainID, proposal) +} + +//-------------------------------------------------------- +// More thread safe methods proxied to the signer + +// Ping is used to check connection health. +func (sc *SocketVal) Ping() error { + sc.mtx.RLock() + defer sc.mtx.RUnlock() + return sc.signer.Ping() +} + +// Close closes the underlying net.Conn. +func (sc *SocketVal) Close() { + sc.mtx.RLock() + defer sc.mtx.RUnlock() + if sc.signer != nil { + if err := sc.signer.Close(); err != nil { + sc.Logger.Error("OnStop", "err", err) + } + } + + if sc.listener != nil { + if err := sc.listener.Close(); err != nil { + sc.Logger.Error("OnStop", "err", err) + } + } +} + +//-------------------------------------------------------- +// Service start and stop + +// OnStart implements cmn.Service. +func (sc *SocketVal) OnStart() error { + if closed, err := sc.reset(); err != nil { + sc.Logger.Error("OnStart", "err", err) + return err + } else if closed { + return fmt.Errorf("listener is closed") + } + + // Start a routine to keep the connection alive + sc.cancelPing = make(chan struct{}, 1) + sc.pingTicker = time.NewTicker(sc.connHeartbeat) + go func() { + for { + select { + case <-sc.pingTicker.C: + err := sc.Ping() + if err != nil { + sc.Logger.Error("Ping", "err", err) + if err == ErrUnexpectedResponse { + return + } + + closed, err := sc.reset() + if err != nil { + sc.Logger.Error("Reconnecting to remote signer failed", "err", err) + continue + } + if closed { + sc.Logger.Info("listener is closing") + return + } + + sc.Logger.Info("Re-created connection to remote signer", "impl", sc) + } + case <-sc.cancelPing: + sc.pingTicker.Stop() + return + } + } + }() + + return nil +} + +// OnStop implements cmn.Service. +func (sc *SocketVal) OnStop() { + if sc.cancelPing != nil { + close(sc.cancelPing) + } + sc.Close() +} + +//-------------------------------------------------------- +// Connection and signer management + +// waits to accept and sets a new connection. +// connection is closed in OnStop. +// returns true if the listener is closed +// (ie. it returns a nil conn). +func (sc *SocketVal) reset() (bool, error) { + sc.mtx.Lock() + defer sc.mtx.Unlock() + + // first check if the conn already exists and close it. + if sc.signer != nil { + if err := sc.signer.Close(); err != nil { + sc.Logger.Error("error closing connection", "err", err) + } + } + + // wait for a new conn + conn, err := sc.waitConnection() + if err != nil { + return false, err + } + + // listener is closed + if conn == nil { + return true, nil + } + + sc.signer, err = NewRemoteSignerClient(conn) + if err != nil { + // failed to fetch the pubkey. close out the connection. + if err := conn.Close(); err != nil { + sc.Logger.Error("error closing connection", "err", err) + } + return false, err + } + return false, nil +} + +func (sc *SocketVal) acceptConnection() (net.Conn, error) { + conn, err := sc.listener.Accept() + if err != nil { + if !sc.IsRunning() { + return nil, nil // Ignore error from listener closing. + } + return nil, err + + } + return conn, nil +} + +// waitConnection uses the configured wait timeout to error if no external +// process connects in the time period. +func (sc *SocketVal) waitConnection() (net.Conn, error) { + var ( + connc = make(chan net.Conn, 1) + errc = make(chan error, 1) + ) + + go func(connc chan<- net.Conn, errc chan<- error) { + conn, err := sc.acceptConnection() + if err != nil { + errc <- err + return + } + + connc <- conn + }(connc, errc) + + select { + case conn := <-connc: + return conn, nil + case err := <-errc: + return nil, err + } +} diff --git a/privval/tcp_test.go b/privval/client_test.go similarity index 70% rename from privval/tcp_test.go rename to privval/client_test.go index e893ef40..7fae6bf8 100644 --- a/privval/tcp_test.go +++ b/privval/client_test.go @@ -17,6 +17,16 @@ import ( "github.com/tendermint/tendermint/types" ) +var ( + testAcceptDeadline = defaultAcceptDeadlineSeconds * time.Second + + testConnDeadline = 100 * time.Millisecond + testConnDeadline2o3 = 66 * time.Millisecond // 2/3 of the other one + + testHeartbeatTimeout = 10 * time.Millisecond + testHeartbeatTimeout3o2 = 6 * time.Millisecond // 3/2 of the other one +) + func TestSocketPVAddress(t *testing.T) { var ( chainID = cmn.RandStr(12) @@ -39,8 +49,7 @@ func TestSocketPVPubKey(t *testing.T) { defer sc.Stop() defer rs.Stop() - clientKey, err := sc.getPubKey() - require.NoError(t, err) + clientKey := sc.GetPubKey() privvalPubKey := rs.privVal.GetPubKey() @@ -95,14 +104,14 @@ func TestSocketPVVoteResetDeadline(t *testing.T) { defer sc.Stop() defer rs.Stop() - time.Sleep(3 * time.Millisecond) + time.Sleep(testConnDeadline2o3) require.NoError(t, rs.privVal.SignVote(chainID, want)) require.NoError(t, sc.SignVote(chainID, have)) assert.Equal(t, want.Signature, have.Signature) // This would exceed the deadline if it was not extended by the previous message - time.Sleep(3 * time.Millisecond) + time.Sleep(testConnDeadline2o3) require.NoError(t, rs.privVal.SignVote(chainID, want)) require.NoError(t, sc.SignVote(chainID, have)) @@ -122,7 +131,7 @@ func TestSocketPVVoteKeepalive(t *testing.T) { defer sc.Stop() defer rs.Stop() - time.Sleep(10 * time.Millisecond) + time.Sleep(testConnDeadline * 2) require.NoError(t, rs.privVal.SignVote(chainID, want)) require.NoError(t, sc.SignVote(chainID, have)) @@ -131,18 +140,13 @@ func TestSocketPVVoteKeepalive(t *testing.T) { func TestSocketPVDeadline(t *testing.T) { var ( - addr = testFreeAddr(t) - listenc = make(chan struct{}) - sc = NewTCPVal( - log.TestingLogger(), - addr, - ed25519.GenPrivKey(), - ) + addr = testFreeAddr(t) + listenc = make(chan struct{}) + thisConnTimeout = 100 * time.Millisecond + sc = newSocketVal(log.TestingLogger(), addr, thisConnTimeout) ) - TCPValConnTimeout(100 * time.Millisecond)(sc) - - go func(sc *TCPVal) { + go func(sc *SocketVal) { defer close(listenc) assert.Equal(t, sc.Start().(cmn.Error).Data(), ErrConnTimeout) @@ -199,9 +203,8 @@ func TestRemoteSignerRetry(t *testing.T) { rs := NewRemoteSigner( log.TestingLogger(), cmn.RandStr(12), - ln.Addr().String(), types.NewMockPV(), - ed25519.GenPrivKey(), + DialTCPFn(ln.Addr().String(), testConnDeadline, ed25519.GenPrivKey()), ) defer rs.Stop() @@ -230,15 +233,8 @@ func TestRemoteSignVoteErrors(t *testing.T) { defer sc.Stop() defer rs.Stop() - err := writeMsg(sc.conn, &SignVoteRequest{Vote: vote}) - require.NoError(t, err) - - res, err := readMsg(sc.conn) - require.NoError(t, err) - - resp := *res.(*SignedVoteResponse) - require.NotNil(t, resp.Error) - require.Equal(t, resp.Error.Description, types.ErroringMockPVErr.Error()) + err := sc.SignVote("", vote) + require.Equal(t, err.(*RemoteSignerError).Description, types.ErroringMockPVErr.Error()) err = rs.privVal.SignVote(chainID, vote) require.Error(t, err) @@ -257,15 +253,8 @@ func TestRemoteSignProposalErrors(t *testing.T) { defer sc.Stop() defer rs.Stop() - err := writeMsg(sc.conn, &SignProposalRequest{Proposal: proposal}) - require.NoError(t, err) - - res, err := readMsg(sc.conn) - require.NoError(t, err) - - resp := *res.(*SignedProposalResponse) - require.NotNil(t, resp.Error) - require.Equal(t, resp.Error.Description, types.ErroringMockPVErr.Error()) + err := sc.SignProposal("", proposal) + require.Equal(t, err.(*RemoteSignerError).Description, types.ErroringMockPVErr.Error()) err = rs.privVal.SignProposal(chainID, proposal) require.Error(t, err) @@ -285,15 +274,10 @@ func TestErrUnexpectedResponse(t *testing.T) { rs = NewRemoteSigner( logger, chainID, - addr, types.NewMockPV(), - ed25519.GenPrivKey(), - ) - sc = NewTCPVal( - logger, - addr, - ed25519.GenPrivKey(), + DialTCPFn(addr, testConnDeadline, ed25519.GenPrivKey()), ) + sc = newSocketVal(logger, addr, testConnDeadline) ) testStartSocketPV(t, readyc, sc) @@ -331,11 +315,73 @@ func TestErrUnexpectedResponse(t *testing.T) { require.Equal(t, err, ErrUnexpectedResponse) } +func TestRetryTCPConnToRemoteSigner(t *testing.T) { + var ( + addr = testFreeAddr(t) + logger = log.TestingLogger() + chainID = cmn.RandStr(12) + readyc = make(chan struct{}) + + rs = NewRemoteSigner( + logger, + chainID, + types.NewMockPV(), + DialTCPFn(addr, testConnDeadline, ed25519.GenPrivKey()), + ) + thisConnTimeout = testConnDeadline + sc = newSocketVal(logger, addr, thisConnTimeout) + ) + // Ping every: + SocketValHeartbeat(testHeartbeatTimeout)(sc) + + RemoteSignerConnDeadline(testConnDeadline)(rs) + RemoteSignerConnRetries(10)(rs) + + testStartSocketPV(t, readyc, sc) + defer sc.Stop() + require.NoError(t, rs.Start()) + assert.True(t, rs.IsRunning()) + + <-readyc + time.Sleep(testHeartbeatTimeout * 2) + + rs.Stop() + rs2 := NewRemoteSigner( + logger, + chainID, + types.NewMockPV(), + DialTCPFn(addr, testConnDeadline, ed25519.GenPrivKey()), + ) + // let some pings pass + time.Sleep(testHeartbeatTimeout3o2) + require.NoError(t, rs2.Start()) + assert.True(t, rs2.IsRunning()) + defer rs2.Stop() + + // give the client some time to re-establish the conn to the remote signer + // should see sth like this in the logs: + // + // E[10016-01-10|17:12:46.128] Ping err="remote signer timed out" + // I[10016-01-10|17:16:42.447] Re-created connection to remote signer impl=SocketVal + time.Sleep(testConnDeadline * 2) +} + +func newSocketVal(logger log.Logger, addr string, connDeadline time.Duration) *SocketVal { + ln, err := net.Listen(cmn.ProtocolAndAddress(addr)) + if err != nil { + panic(err) + } + tcpLn := NewTCPListener(ln, ed25519.GenPrivKey()) + TCPListenerAcceptDeadline(testAcceptDeadline)(tcpLn) + TCPListenerConnDeadline(testConnDeadline)(tcpLn) + return NewSocketVal(logger, tcpLn) +} + func testSetupSocketPair( t *testing.T, chainID string, privValidator types.PrivValidator, -) (*TCPVal, *RemoteSigner) { +) (*SocketVal, *RemoteSigner) { var ( addr = testFreeAddr(t) logger = log.TestingLogger() @@ -344,20 +390,16 @@ func testSetupSocketPair( rs = NewRemoteSigner( logger, chainID, - addr, privVal, - ed25519.GenPrivKey(), - ) - sc = NewTCPVal( - logger, - addr, - ed25519.GenPrivKey(), + DialTCPFn(addr, testConnDeadline, ed25519.GenPrivKey()), ) + + thisConnTimeout = testConnDeadline + sc = newSocketVal(logger, addr, thisConnTimeout) ) - TCPValConnTimeout(5 * time.Millisecond)(sc) - TCPValHeartbeat(2 * time.Millisecond)(sc) - RemoteSignerConnDeadline(5 * time.Millisecond)(rs) + SocketValHeartbeat(testHeartbeatTimeout)(sc) + RemoteSignerConnDeadline(testConnDeadline)(rs) RemoteSignerConnRetries(1e6)(rs) testStartSocketPV(t, readyc, sc) @@ -378,8 +420,8 @@ func testReadWriteResponse(t *testing.T, resp RemoteSignerMsg, rsConn net.Conn) require.NoError(t, err) } -func testStartSocketPV(t *testing.T, readyc chan struct{}, sc *TCPVal) { - go func(sc *TCPVal) { +func testStartSocketPV(t *testing.T, readyc chan struct{}, sc *SocketVal) { + go func(sc *SocketVal) { require.NoError(t, sc.Start()) assert.True(t, sc.IsRunning()) diff --git a/privval/doc.go b/privval/doc.go new file mode 100644 index 00000000..ed378c19 --- /dev/null +++ b/privval/doc.go @@ -0,0 +1,21 @@ +/* + +Package privval provides different implementations of the types.PrivValidator. + +FilePV + +FilePV is the simplest implementation and developer default. It uses one file for the private key and another to store state. + +SocketVal + +SocketVal establishes a connection to an external process, like a Key Management Server (KMS), using a socket. +SocketVal listens for the external KMS process to dial in. +SocketVal takes a listener, which determines the type of connection +(ie. encrypted over tcp, or unencrypted over unix). + +RemoteSigner + +RemoteSigner is a simple wrapper around a net.Conn. It's used by both IPCVal and TCPVal. + +*/ +package privval diff --git a/privval/priv_validator.go b/privval/file.go similarity index 99% rename from privval/priv_validator.go rename to privval/file.go index 1ee5b4d8..8072cfa4 100644 --- a/privval/priv_validator.go +++ b/privval/file.go @@ -22,6 +22,7 @@ const ( stepPrecommit int8 = 3 ) +// A vote is either stepPrevote or stepPrecommit. func voteToStep(vote *types.Vote) int8 { switch vote.Type { case types.PrevoteType: @@ -29,7 +30,7 @@ func voteToStep(vote *types.Vote) int8 { case types.PrecommitType: return stepPrecommit default: - cmn.PanicSanity("Unknown vote type") + panic("Unknown vote type") return 0 } } diff --git a/privval/priv_validator_test.go b/privval/file_test.go similarity index 100% rename from privval/priv_validator_test.go rename to privval/file_test.go diff --git a/privval/ipc.go b/privval/ipc.go deleted file mode 100644 index 1c82db33..00000000 --- a/privval/ipc.go +++ /dev/null @@ -1,123 +0,0 @@ -package privval - -import ( - "net" - "time" - - cmn "github.com/tendermint/tendermint/libs/common" - "github.com/tendermint/tendermint/libs/log" - "github.com/tendermint/tendermint/types" -) - -// IPCValOption sets an optional parameter on the SocketPV. -type IPCValOption func(*IPCVal) - -// IPCValConnTimeout sets the read and write timeout for connections -// from external signing processes. -func IPCValConnTimeout(timeout time.Duration) IPCValOption { - return func(sc *IPCVal) { sc.connTimeout = timeout } -} - -// IPCValHeartbeat sets the period on which to check the liveness of the -// connected Signer connections. -func IPCValHeartbeat(period time.Duration) IPCValOption { - return func(sc *IPCVal) { sc.connHeartbeat = period } -} - -// IPCVal implements PrivValidator, it uses a unix socket to request signatures -// from an external process. -type IPCVal struct { - cmn.BaseService - *RemoteSignerClient - - addr string - - connTimeout time.Duration - connHeartbeat time.Duration - - conn net.Conn - cancelPing chan struct{} - pingTicker *time.Ticker -} - -// Check that IPCVal implements PrivValidator. -var _ types.PrivValidator = (*IPCVal)(nil) - -// NewIPCVal returns an instance of IPCVal. -func NewIPCVal( - logger log.Logger, - socketAddr string, -) *IPCVal { - sc := &IPCVal{ - addr: socketAddr, - connTimeout: connTimeout, - connHeartbeat: connHeartbeat, - } - - sc.BaseService = *cmn.NewBaseService(logger, "IPCVal", sc) - - return sc -} - -// OnStart implements cmn.Service. -func (sc *IPCVal) OnStart() error { - err := sc.connect() - if err != nil { - sc.Logger.Error("OnStart", "err", err) - return err - } - - sc.RemoteSignerClient, err = NewRemoteSignerClient(sc.conn) - if err != nil { - return err - } - - // Start a routine to keep the connection alive - sc.cancelPing = make(chan struct{}, 1) - sc.pingTicker = time.NewTicker(sc.connHeartbeat) - go func() { - for { - select { - case <-sc.pingTicker.C: - err := sc.Ping() - if err != nil { - sc.Logger.Error("Ping", "err", err) - } - case <-sc.cancelPing: - sc.pingTicker.Stop() - return - } - } - }() - - return nil -} - -// OnStop implements cmn.Service. -func (sc *IPCVal) OnStop() { - if sc.cancelPing != nil { - close(sc.cancelPing) - } - - if sc.conn != nil { - if err := sc.conn.Close(); err != nil { - sc.Logger.Error("OnStop", "err", err) - } - } -} - -func (sc *IPCVal) connect() error { - la, err := net.ResolveUnixAddr("unix", sc.addr) - if err != nil { - return err - } - - conn, err := net.DialUnix("unix", nil, la) - if err != nil { - return err - } - - sc.conn = newTimeoutConn(conn, sc.connTimeout) - - return nil -} diff --git a/privval/ipc_server.go b/privval/ipc_server.go deleted file mode 100644 index ba957477..00000000 --- a/privval/ipc_server.go +++ /dev/null @@ -1,132 +0,0 @@ -package privval - -import ( - "io" - "net" - "time" - - cmn "github.com/tendermint/tendermint/libs/common" - "github.com/tendermint/tendermint/libs/log" - "github.com/tendermint/tendermint/types" -) - -// IPCRemoteSignerOption sets an optional parameter on the IPCRemoteSigner. -type IPCRemoteSignerOption func(*IPCRemoteSigner) - -// IPCRemoteSignerConnDeadline sets the read and write deadline for connections -// from external signing processes. -func IPCRemoteSignerConnDeadline(deadline time.Duration) IPCRemoteSignerOption { - return func(ss *IPCRemoteSigner) { ss.connDeadline = deadline } -} - -// IPCRemoteSignerConnRetries sets the amount of attempted retries to connect. -func IPCRemoteSignerConnRetries(retries int) IPCRemoteSignerOption { - return func(ss *IPCRemoteSigner) { ss.connRetries = retries } -} - -// IPCRemoteSigner is a RPC implementation of PrivValidator that listens on a unix socket. -type IPCRemoteSigner struct { - cmn.BaseService - - addr string - chainID string - connDeadline time.Duration - connRetries int - privVal types.PrivValidator - - listener *net.UnixListener -} - -// NewIPCRemoteSigner returns an instance of IPCRemoteSigner. -func NewIPCRemoteSigner( - logger log.Logger, - chainID, socketAddr string, - privVal types.PrivValidator, -) *IPCRemoteSigner { - rs := &IPCRemoteSigner{ - addr: socketAddr, - chainID: chainID, - connDeadline: time.Second * defaultConnDeadlineSeconds, - connRetries: defaultDialRetries, - privVal: privVal, - } - - rs.BaseService = *cmn.NewBaseService(logger, "IPCRemoteSigner", rs) - - return rs -} - -// OnStart implements cmn.Service. -func (rs *IPCRemoteSigner) OnStart() error { - err := rs.listen() - if err != nil { - err = cmn.ErrorWrap(err, "listen") - rs.Logger.Error("OnStart", "err", err) - return err - } - - go func() { - for { - conn, err := rs.listener.AcceptUnix() - if err != nil { - rs.Logger.Error("AcceptUnix", "err", err) - return - } - go rs.handleConnection(conn) - } - }() - - return nil -} - -// OnStop implements cmn.Service. -func (rs *IPCRemoteSigner) OnStop() { - if rs.listener != nil { - if err := rs.listener.Close(); err != nil { - rs.Logger.Error("OnStop", "err", cmn.ErrorWrap(err, "closing listener failed")) - } - } -} - -func (rs *IPCRemoteSigner) listen() error { - la, err := net.ResolveUnixAddr("unix", rs.addr) - if err != nil { - return err - } - - rs.listener, err = net.ListenUnix("unix", la) - - return err -} - -func (rs *IPCRemoteSigner) handleConnection(conn net.Conn) { - for { - if !rs.IsRunning() { - return // Ignore error from listener closing. - } - - // Reset the connection deadline - conn.SetDeadline(time.Now().Add(rs.connDeadline)) - - req, err := readMsg(conn) - if err != nil { - if err != io.EOF { - rs.Logger.Error("handleConnection", "err", err) - } - return - } - - res, err := handleRequest(req, rs.chainID, rs.privVal) - - if err != nil { - // only log the error; we'll reply with an error in res - rs.Logger.Error("handleConnection", "err", err) - } - - err = writeMsg(conn, res) - if err != nil { - rs.Logger.Error("handleConnection", "err", err) - return - } - } -} diff --git a/privval/ipc_test.go b/privval/ipc_test.go deleted file mode 100644 index c8d6dfc7..00000000 --- a/privval/ipc_test.go +++ /dev/null @@ -1,147 +0,0 @@ -package privval - -import ( - "io/ioutil" - "os" - "testing" - "time" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - - cmn "github.com/tendermint/tendermint/libs/common" - "github.com/tendermint/tendermint/libs/log" - "github.com/tendermint/tendermint/types" -) - -func TestIPCPVVote(t *testing.T) { - var ( - chainID = cmn.RandStr(12) - sc, rs = testSetupIPCSocketPair(t, chainID, types.NewMockPV()) - - ts = time.Now() - vType = types.PrecommitType - want = &types.Vote{Timestamp: ts, Type: vType} - have = &types.Vote{Timestamp: ts, Type: vType} - ) - defer sc.Stop() - defer rs.Stop() - - require.NoError(t, rs.privVal.SignVote(chainID, want)) - require.NoError(t, sc.SignVote(chainID, have)) - assert.Equal(t, want.Signature, have.Signature) -} - -func TestIPCPVVoteResetDeadline(t *testing.T) { - var ( - chainID = cmn.RandStr(12) - sc, rs = testSetupIPCSocketPair(t, chainID, types.NewMockPV()) - - ts = time.Now() - vType = types.PrecommitType - want = &types.Vote{Timestamp: ts, Type: vType} - have = &types.Vote{Timestamp: ts, Type: vType} - ) - defer sc.Stop() - defer rs.Stop() - - time.Sleep(3 * time.Millisecond) - - require.NoError(t, rs.privVal.SignVote(chainID, want)) - require.NoError(t, sc.SignVote(chainID, have)) - assert.Equal(t, want.Signature, have.Signature) - - // This would exceed the deadline if it was not extended by the previous message - time.Sleep(3 * time.Millisecond) - - require.NoError(t, rs.privVal.SignVote(chainID, want)) - require.NoError(t, sc.SignVote(chainID, have)) - assert.Equal(t, want.Signature, have.Signature) -} - -func TestIPCPVVoteKeepalive(t *testing.T) { - var ( - chainID = cmn.RandStr(12) - sc, rs = testSetupIPCSocketPair(t, chainID, types.NewMockPV()) - - ts = time.Now() - vType = types.PrecommitType - want = &types.Vote{Timestamp: ts, Type: vType} - have = &types.Vote{Timestamp: ts, Type: vType} - ) - defer sc.Stop() - defer rs.Stop() - - time.Sleep(10 * time.Millisecond) - - require.NoError(t, rs.privVal.SignVote(chainID, want)) - require.NoError(t, sc.SignVote(chainID, have)) - assert.Equal(t, want.Signature, have.Signature) -} - -func testSetupIPCSocketPair( - t *testing.T, - chainID string, - privValidator types.PrivValidator, -) (*IPCVal, *IPCRemoteSigner) { - addr, err := testUnixAddr() - require.NoError(t, err) - - var ( - logger = log.TestingLogger() - privVal = privValidator - readyc = make(chan struct{}) - rs = NewIPCRemoteSigner( - logger, - chainID, - addr, - privVal, - ) - sc = NewIPCVal( - logger, - addr, - ) - ) - - IPCValConnTimeout(5 * time.Millisecond)(sc) - IPCValHeartbeat(time.Millisecond)(sc) - - IPCRemoteSignerConnDeadline(time.Millisecond * 5)(rs) - - testStartIPCRemoteSigner(t, readyc, rs) - - <-readyc - - require.NoError(t, sc.Start()) - assert.True(t, sc.IsRunning()) - - return sc, rs -} - -func testStartIPCRemoteSigner(t *testing.T, readyc chan struct{}, rs *IPCRemoteSigner) { - go func(rs *IPCRemoteSigner) { - require.NoError(t, rs.Start()) - assert.True(t, rs.IsRunning()) - - readyc <- struct{}{} - }(rs) -} - -func testUnixAddr() (string, error) { - f, err := ioutil.TempFile("/tmp", "nettest") - if err != nil { - return "", err - } - - addr := f.Name() - err = f.Close() - if err != nil { - return "", err - } - err = os.Remove(addr) - if err != nil { - return "", err - } - - return addr, nil -} diff --git a/privval/old_priv_validator.go b/privval/old_file.go similarity index 100% rename from privval/old_priv_validator.go rename to privval/old_file.go diff --git a/privval/old_priv_validator_test.go b/privval/old_file_test.go similarity index 100% rename from privval/old_priv_validator_test.go rename to privval/old_file_test.go diff --git a/privval/remote_signer.go b/privval/remote_signer.go index b80884de..d928b198 100644 --- a/privval/remote_signer.go +++ b/privval/remote_signer.go @@ -4,7 +4,6 @@ import ( "fmt" "io" "net" - "sync" "github.com/pkg/errors" @@ -14,31 +13,41 @@ import ( "github.com/tendermint/tendermint/types" ) -// RemoteSignerClient implements PrivValidator, it uses a socket to request signatures +// Socket errors. +var ( + ErrConnTimeout = errors.New("remote signer timed out") +) + +// RemoteSignerClient implements PrivValidator. +// It uses a net.Conn to request signatures // from an external process. type RemoteSignerClient struct { - conn net.Conn + conn net.Conn + + // memoized consensusPubKey crypto.PubKey - mtx sync.Mutex } // Check that RemoteSignerClient implements PrivValidator. var _ types.PrivValidator = (*RemoteSignerClient)(nil) // NewRemoteSignerClient returns an instance of RemoteSignerClient. -func NewRemoteSignerClient( - conn net.Conn, -) (*RemoteSignerClient, error) { - sc := &RemoteSignerClient{ - conn: conn, - } - pubKey, err := sc.getPubKey() +func NewRemoteSignerClient(conn net.Conn) (*RemoteSignerClient, error) { + + // retrieve and memoize the consensus public key once. + pubKey, err := getPubKey(conn) if err != nil { return nil, cmn.ErrorWrap(err, "error while retrieving public key for remote signer") } - // retrieve and memoize the consensus public key once: - sc.consensusPubKey = pubKey - return sc, nil + return &RemoteSignerClient{ + conn: conn, + consensusPubKey: pubKey, + }, nil +} + +// Close calls Close on the underlying net.Conn. +func (sc *RemoteSignerClient) Close() error { + return sc.conn.Close() } // GetPubKey implements PrivValidator. @@ -46,16 +55,14 @@ func (sc *RemoteSignerClient) GetPubKey() crypto.PubKey { return sc.consensusPubKey } -func (sc *RemoteSignerClient) getPubKey() (crypto.PubKey, error) { - sc.mtx.Lock() - defer sc.mtx.Unlock() - - err := writeMsg(sc.conn, &PubKeyRequest{}) +// not thread-safe (only called on startup). +func getPubKey(conn net.Conn) (crypto.PubKey, error) { + err := writeMsg(conn, &PubKeyRequest{}) if err != nil { return nil, err } - res, err := readMsg(sc.conn) + res, err := readMsg(conn) if err != nil { return nil, err } @@ -73,9 +80,6 @@ func (sc *RemoteSignerClient) getPubKey() (crypto.PubKey, error) { // SignVote implements PrivValidator. func (sc *RemoteSignerClient) SignVote(chainID string, vote *types.Vote) error { - sc.mtx.Lock() - defer sc.mtx.Unlock() - err := writeMsg(sc.conn, &SignVoteRequest{Vote: vote}) if err != nil { return err @@ -103,9 +107,6 @@ func (sc *RemoteSignerClient) SignProposal( chainID string, proposal *types.Proposal, ) error { - sc.mtx.Lock() - defer sc.mtx.Unlock() - err := writeMsg(sc.conn, &SignProposalRequest{Proposal: proposal}) if err != nil { return err @@ -129,9 +130,6 @@ func (sc *RemoteSignerClient) SignProposal( // Ping is used to check connection health. func (sc *RemoteSignerClient) Ping() error { - sc.mtx.Lock() - defer sc.mtx.Unlock() - err := writeMsg(sc.conn, &PingRequest{}) if err != nil { return err diff --git a/privval/tcp_server.go b/privval/server.go similarity index 63% rename from privval/tcp_server.go rename to privval/server.go index 694023d7..8b22c69e 100644 --- a/privval/tcp_server.go +++ b/privval/server.go @@ -5,6 +5,7 @@ import ( "net" "time" + "github.com/pkg/errors" "github.com/tendermint/tendermint/crypto/ed25519" cmn "github.com/tendermint/tendermint/libs/common" "github.com/tendermint/tendermint/libs/log" @@ -12,6 +13,11 @@ import ( "github.com/tendermint/tendermint/types" ) +// Socket errors. +var ( + ErrDialRetryMax = errors.New("dialed maximum retries") +) + // RemoteSignerOption sets an optional parameter on the RemoteSigner. type RemoteSignerOption func(*RemoteSigner) @@ -26,38 +32,64 @@ func RemoteSignerConnRetries(retries int) RemoteSignerOption { return func(ss *RemoteSigner) { ss.connRetries = retries } } -// RemoteSigner implements PrivValidator by dialing to a socket. +// RemoteSigner dials using its dialer and responds to any +// signature requests using its privVal. type RemoteSigner struct { cmn.BaseService - addr string chainID string connDeadline time.Duration connRetries int - privKey ed25519.PrivKeyEd25519 privVal types.PrivValidator - conn net.Conn + dialer Dialer + conn net.Conn } -// NewRemoteSigner returns an instance of RemoteSigner. +// Dialer dials a remote address and returns a net.Conn or an error. +type Dialer func() (net.Conn, error) + +// DialTCPFn dials the given tcp addr, using the given connTimeout and privKey for the +// authenticated encryption handshake. +func DialTCPFn(addr string, connTimeout time.Duration, privKey ed25519.PrivKeyEd25519) Dialer { + return func() (net.Conn, error) { + conn, err := cmn.Connect(addr) + if err == nil { + err = conn.SetDeadline(time.Now().Add(connTimeout)) + } + if err == nil { + conn, err = p2pconn.MakeSecretConnection(conn, privKey) + } + return conn, err + } +} + +// DialUnixFn dials the given unix socket. +func DialUnixFn(addr string) Dialer { + return func() (net.Conn, error) { + unixAddr := &net.UnixAddr{addr, "unix"} + return net.DialUnix("unix", nil, unixAddr) + } +} + +// NewRemoteSigner return a RemoteSigner that will dial using the given +// dialer and respond to any signature requests over the connection +// using the given privVal. func NewRemoteSigner( logger log.Logger, - chainID, socketAddr string, + chainID string, privVal types.PrivValidator, - privKey ed25519.PrivKeyEd25519, + dialer Dialer, ) *RemoteSigner { rs := &RemoteSigner{ - addr: socketAddr, chainID: chainID, connDeadline: time.Second * defaultConnDeadlineSeconds, connRetries: defaultDialRetries, - privKey: privKey, privVal: privVal, + dialer: dialer, } rs.BaseService = *cmn.NewBaseService(logger, "RemoteSigner", rs) - return rs } @@ -68,6 +100,7 @@ func (rs *RemoteSigner) OnStart() error { rs.Logger.Error("OnStart", "err", err) return err } + rs.conn = conn go rs.handleConnection(conn) @@ -91,36 +124,11 @@ func (rs *RemoteSigner) connect() (net.Conn, error) { if retries != rs.connRetries { time.Sleep(rs.connDeadline) } - - conn, err := cmn.Connect(rs.addr) + conn, err := rs.dialer() if err != nil { - rs.Logger.Error( - "connect", - "addr", rs.addr, - "err", err, - ) - + rs.Logger.Error("dialing", "err", err) continue } - - if err := conn.SetDeadline(time.Now().Add(connTimeout)); err != nil { - rs.Logger.Error( - "connect", - "err", err, - ) - continue - } - - conn, err = p2pconn.MakeSecretConnection(conn, rs.privKey) - if err != nil { - rs.Logger.Error( - "connect", - "err", err, - ) - - continue - } - return conn, nil } @@ -139,7 +147,7 @@ func (rs *RemoteSigner) handleConnection(conn net.Conn) { req, err := readMsg(conn) if err != nil { if err != io.EOF { - rs.Logger.Error("handleConnection", "err", err) + rs.Logger.Error("handleConnection readMsg", "err", err) } return } @@ -148,12 +156,12 @@ func (rs *RemoteSigner) handleConnection(conn net.Conn) { if err != nil { // only log the error; we'll reply with an error in res - rs.Logger.Error("handleConnection", "err", err) + rs.Logger.Error("handleConnection handleRequest", "err", err) } err = writeMsg(conn, res) if err != nil { - rs.Logger.Error("handleConnection", "err", err) + rs.Logger.Error("handleConnection writeMsg", "err", err) return } } diff --git a/privval/socket.go b/privval/socket.go new file mode 100644 index 00000000..96fa6c8e --- /dev/null +++ b/privval/socket.go @@ -0,0 +1,184 @@ +package privval + +import ( + "net" + "time" + + "github.com/tendermint/tendermint/crypto/ed25519" + p2pconn "github.com/tendermint/tendermint/p2p/conn" +) + +const ( + defaultAcceptDeadlineSeconds = 3 + defaultConnDeadlineSeconds = 3 +) + +// timeoutError can be used to check if an error returned from the netp package +// was due to a timeout. +type timeoutError interface { + Timeout() bool +} + +//------------------------------------------------------------------ +// TCP Listener + +// TCPListenerOption sets an optional parameter on the tcpListener. +type TCPListenerOption func(*tcpListener) + +// TCPListenerAcceptDeadline sets the deadline for the listener. +// A zero time value disables the deadline. +func TCPListenerAcceptDeadline(deadline time.Duration) TCPListenerOption { + return func(tl *tcpListener) { tl.acceptDeadline = deadline } +} + +// TCPListenerConnDeadline sets the read and write deadline for connections +// from external signing processes. +func TCPListenerConnDeadline(deadline time.Duration) TCPListenerOption { + return func(tl *tcpListener) { tl.connDeadline = deadline } +} + +// tcpListener implements net.Listener. +var _ net.Listener = (*tcpListener)(nil) + +// tcpListener wraps a *net.TCPListener to standardise protocol timeouts +// and potentially other tuning parameters. It also returns encrypted connections. +type tcpListener struct { + *net.TCPListener + + secretConnKey ed25519.PrivKeyEd25519 + + acceptDeadline time.Duration + connDeadline time.Duration +} + +// NewTCPListener returns a listener that accepts authenticated encrypted connections +// using the given secretConnKey and the default timeout values. +func NewTCPListener(ln net.Listener, secretConnKey ed25519.PrivKeyEd25519) *tcpListener { + return &tcpListener{ + TCPListener: ln.(*net.TCPListener), + secretConnKey: secretConnKey, + acceptDeadline: time.Second * defaultAcceptDeadlineSeconds, + connDeadline: time.Second * defaultConnDeadlineSeconds, + } +} + +// Accept implements net.Listener. +func (ln *tcpListener) Accept() (net.Conn, error) { + err := ln.SetDeadline(time.Now().Add(ln.acceptDeadline)) + if err != nil { + return nil, err + } + + tc, err := ln.AcceptTCP() + if err != nil { + return nil, err + } + + // Wrap the conn in our timeout and encryption wrappers + timeoutConn := newTimeoutConn(tc, ln.connDeadline) + secretConn, err := p2pconn.MakeSecretConnection(timeoutConn, ln.secretConnKey) + if err != nil { + return nil, err + } + + return secretConn, nil +} + +//------------------------------------------------------------------ +// Unix Listener + +// unixListener implements net.Listener. +var _ net.Listener = (*unixListener)(nil) + +type UnixListenerOption func(*unixListener) + +// UnixListenerAcceptDeadline sets the deadline for the listener. +// A zero time value disables the deadline. +func UnixListenerAcceptDeadline(deadline time.Duration) UnixListenerOption { + return func(ul *unixListener) { ul.acceptDeadline = deadline } +} + +// UnixListenerConnDeadline sets the read and write deadline for connections +// from external signing processes. +func UnixListenerConnDeadline(deadline time.Duration) UnixListenerOption { + return func(ul *unixListener) { ul.connDeadline = deadline } +} + +// unixListener wraps a *net.UnixListener to standardise protocol timeouts +// and potentially other tuning parameters. It returns unencrypted connections. +type unixListener struct { + *net.UnixListener + + acceptDeadline time.Duration + connDeadline time.Duration +} + +// NewUnixListener returns a listener that accepts unencrypted connections +// using the default timeout values. +func NewUnixListener(ln net.Listener) *unixListener { + return &unixListener{ + UnixListener: ln.(*net.UnixListener), + acceptDeadline: time.Second * defaultAcceptDeadlineSeconds, + connDeadline: time.Second * defaultConnDeadlineSeconds, + } +} + +// Accept implements net.Listener. +func (ln *unixListener) Accept() (net.Conn, error) { + err := ln.SetDeadline(time.Now().Add(ln.acceptDeadline)) + if err != nil { + return nil, err + } + + tc, err := ln.AcceptUnix() + if err != nil { + return nil, err + } + + // Wrap the conn in our timeout wrapper + conn := newTimeoutConn(tc, ln.connDeadline) + + // TODO: wrap in something that authenticates + // with a MAC - https://github.com/tendermint/tendermint/issues/3099 + + return conn, nil +} + +//------------------------------------------------------------------ +// Connection + +// timeoutConn implements net.Conn. +var _ net.Conn = (*timeoutConn)(nil) + +// timeoutConn wraps a net.Conn to standardise protocol timeouts / deadline resets. +type timeoutConn struct { + net.Conn + + connDeadline time.Duration +} + +// newTimeoutConn returns an instance of newTCPTimeoutConn. +func newTimeoutConn( + conn net.Conn, + connDeadline time.Duration) *timeoutConn { + return &timeoutConn{ + conn, + connDeadline, + } +} + +// Read implements net.Conn. +func (c timeoutConn) Read(b []byte) (n int, err error) { + // Reset deadline + c.Conn.SetReadDeadline(time.Now().Add(c.connDeadline)) + + return c.Conn.Read(b) +} + +// Write implements net.Conn. +func (c timeoutConn) Write(b []byte) (n int, err error) { + // Reset deadline + c.Conn.SetWriteDeadline(time.Now().Add(c.connDeadline)) + + return c.Conn.Write(b) +} diff --git a/privval/tcp_socket_test.go b/privval/socket_test.go similarity index 52% rename from privval/tcp_socket_test.go rename to privval/socket_test.go index 285e73ed..0c05fa3a 100644 --- a/privval/tcp_socket_test.go +++ b/privval/socket_test.go @@ -4,17 +4,31 @@ import ( "net" "testing" "time" + + "github.com/tendermint/tendermint/crypto/ed25519" ) -func TestTCPTimeoutListenerAcceptDeadline(t *testing.T) { +//------------------------------------------- +// helper funcs + +func newPrivKey() ed25519.PrivKeyEd25519 { + return ed25519.GenPrivKey() +} + +//------------------------------------------- +// tests + +func TestTCPListenerAcceptDeadline(t *testing.T) { ln, err := net.Listen("tcp", "127.0.0.1:0") if err != nil { t.Fatal(err) } - ln = newTCPTimeoutListener(ln, time.Millisecond, time.Second, time.Second) + tcpLn := NewTCPListener(ln, newPrivKey()) + TCPListenerAcceptDeadline(time.Millisecond)(tcpLn) + TCPListenerConnDeadline(time.Second)(tcpLn) - _, err = ln.Accept() + _, err = tcpLn.Accept() opErr, ok := err.(*net.OpError) if !ok { t.Fatalf("have %v, want *net.OpError", err) @@ -25,14 +39,17 @@ func TestTCPTimeoutListenerAcceptDeadline(t *testing.T) { } } -func TestTCPTimeoutListenerConnDeadline(t *testing.T) { +func TestTCPListenerConnDeadline(t *testing.T) { ln, err := net.Listen("tcp", "127.0.0.1:0") if err != nil { t.Fatal(err) } - ln = newTCPTimeoutListener(ln, time.Second, time.Millisecond, time.Second) + tcpLn := NewTCPListener(ln, newPrivKey()) + TCPListenerAcceptDeadline(time.Second)(tcpLn) + TCPListenerConnDeadline(time.Millisecond)(tcpLn) + readyc := make(chan struct{}) donec := make(chan struct{}) go func(ln net.Listener) { defer close(donec) @@ -41,6 +58,7 @@ func TestTCPTimeoutListenerConnDeadline(t *testing.T) { if err != nil { t.Fatal(err) } + <-readyc time.Sleep(2 * time.Millisecond) @@ -54,12 +72,13 @@ func TestTCPTimeoutListenerConnDeadline(t *testing.T) { if have, want := opErr.Op, "read"; have != want { t.Errorf("have %v, want %v", have, want) } - }(ln) + }(tcpLn) - _, err = net.Dial("tcp", ln.Addr().String()) + dialer := DialTCPFn(ln.Addr().String(), testConnDeadline, newPrivKey()) + _, err = dialer() if err != nil { t.Fatal(err) } - + close(readyc) <-donec } diff --git a/privval/tcp.go b/privval/tcp.go deleted file mode 100644 index 1fb736e6..00000000 --- a/privval/tcp.go +++ /dev/null @@ -1,216 +0,0 @@ -package privval - -import ( - "errors" - "net" - "time" - - "github.com/tendermint/tendermint/crypto/ed25519" - cmn "github.com/tendermint/tendermint/libs/common" - "github.com/tendermint/tendermint/libs/log" - p2pconn "github.com/tendermint/tendermint/p2p/conn" - "github.com/tendermint/tendermint/types" -) - -const ( - defaultAcceptDeadlineSeconds = 3 - defaultConnDeadlineSeconds = 3 - defaultConnHeartBeatSeconds = 2 - defaultDialRetries = 10 -) - -// Socket errors. -var ( - ErrDialRetryMax = errors.New("dialed maximum retries") - ErrConnTimeout = errors.New("remote signer timed out") - ErrUnexpectedResponse = errors.New("received unexpected response") -) - -var ( - acceptDeadline = time.Second * defaultAcceptDeadlineSeconds - connTimeout = time.Second * defaultConnDeadlineSeconds - connHeartbeat = time.Second * defaultConnHeartBeatSeconds -) - -// TCPValOption sets an optional parameter on the SocketPV. -type TCPValOption func(*TCPVal) - -// TCPValAcceptDeadline sets the deadline for the TCPVal listener. -// A zero time value disables the deadline. -func TCPValAcceptDeadline(deadline time.Duration) TCPValOption { - return func(sc *TCPVal) { sc.acceptDeadline = deadline } -} - -// TCPValConnTimeout sets the read and write timeout for connections -// from external signing processes. -func TCPValConnTimeout(timeout time.Duration) TCPValOption { - return func(sc *TCPVal) { sc.connTimeout = timeout } -} - -// TCPValHeartbeat sets the period on which to check the liveness of the -// connected Signer connections. -func TCPValHeartbeat(period time.Duration) TCPValOption { - return func(sc *TCPVal) { sc.connHeartbeat = period } -} - -// TCPVal implements PrivValidator, it uses a socket to request signatures -// from an external process. -type TCPVal struct { - cmn.BaseService - *RemoteSignerClient - - addr string - acceptDeadline time.Duration - connTimeout time.Duration - connHeartbeat time.Duration - privKey ed25519.PrivKeyEd25519 - - conn net.Conn - listener net.Listener - cancelPing chan struct{} - pingTicker *time.Ticker -} - -// Check that TCPVal implements PrivValidator. -var _ types.PrivValidator = (*TCPVal)(nil) - -// NewTCPVal returns an instance of TCPVal. -func NewTCPVal( - logger log.Logger, - socketAddr string, - privKey ed25519.PrivKeyEd25519, -) *TCPVal { - sc := &TCPVal{ - addr: socketAddr, - acceptDeadline: acceptDeadline, - connTimeout: connTimeout, - connHeartbeat: connHeartbeat, - privKey: privKey, - } - - sc.BaseService = *cmn.NewBaseService(logger, "TCPVal", sc) - - return sc -} - -// OnStart implements cmn.Service. -func (sc *TCPVal) OnStart() error { - if err := sc.listen(); err != nil { - sc.Logger.Error("OnStart", "err", err) - return err - } - - conn, err := sc.waitConnection() - if err != nil { - sc.Logger.Error("OnStart", "err", err) - return err - } - - sc.conn = conn - sc.RemoteSignerClient, err = NewRemoteSignerClient(sc.conn) - if err != nil { - return err - } - - // Start a routine to keep the connection alive - sc.cancelPing = make(chan struct{}, 1) - sc.pingTicker = time.NewTicker(sc.connHeartbeat) - go func() { - for { - select { - case <-sc.pingTicker.C: - err := sc.Ping() - if err != nil { - sc.Logger.Error( - "Ping", - "err", err, - ) - } - case <-sc.cancelPing: - sc.pingTicker.Stop() - return - } - } - }() - - return nil -} - -// OnStop implements cmn.Service. -func (sc *TCPVal) OnStop() { - if sc.cancelPing != nil { - close(sc.cancelPing) - } - - if sc.conn != nil { - if err := sc.conn.Close(); err != nil { - sc.Logger.Error("OnStop", "err", err) - } - } - - if sc.listener != nil { - if err := sc.listener.Close(); err != nil { - sc.Logger.Error("OnStop", "err", err) - } - } -} - -func (sc *TCPVal) acceptConnection() (net.Conn, error) { - conn, err := sc.listener.Accept() - if err != nil { - if !sc.IsRunning() { - return nil, nil // Ignore error from listener closing. - } - return nil, err - - } - - conn, err = p2pconn.MakeSecretConnection(conn, sc.privKey) - if err != nil { - return nil, err - } - - return conn, nil -} - -func (sc *TCPVal) listen() error { - ln, err := net.Listen(cmn.ProtocolAndAddress(sc.addr)) - if err != nil { - return err - } - - sc.listener = newTCPTimeoutListener( - ln, - sc.acceptDeadline, - sc.connTimeout, - sc.connHeartbeat, - ) - - return nil -} - -// waitConnection uses the configured wait timeout to error if no external -// process connects in the time period. -func (sc *TCPVal) waitConnection() (net.Conn, error) { - var ( - connc = make(chan net.Conn, 1) - errc = make(chan error, 1) - ) - - go func(connc chan<- net.Conn, errc chan<- error) { - conn, err := sc.acceptConnection() - if err != nil { - errc <- err - return - } - - connc <- conn - }(connc, errc) - - select { - case conn := <-connc: - return conn, nil - case err := <-errc: - return nil, err - } -} diff --git a/privval/tcp_socket.go b/privval/tcp_socket.go deleted file mode 100644 index 2b17bf26..00000000 --- a/privval/tcp_socket.go +++ /dev/null @@ -1,90 +0,0 @@ -package privval - -import ( - "net" - "time" -) - -// timeoutError can be used to check if an error returned from the netp package -// was due to a timeout. -type timeoutError interface { - Timeout() bool -} - -// tcpTimeoutListener implements net.Listener. -var _ net.Listener = (*tcpTimeoutListener)(nil) - -// tcpTimeoutListener wraps a *net.TCPListener to standardise protocol timeouts -// and potentially other tuning parameters. -type tcpTimeoutListener struct { - *net.TCPListener - - acceptDeadline time.Duration - connDeadline time.Duration - period time.Duration -} - -// timeoutConn wraps a net.Conn to standardise protocol timeouts / deadline resets. -type timeoutConn struct { - net.Conn - - connDeadline time.Duration -} - -// newTCPTimeoutListener returns an instance of tcpTimeoutListener. -func newTCPTimeoutListener( - ln net.Listener, - acceptDeadline, connDeadline time.Duration, - period time.Duration, -) tcpTimeoutListener { - return tcpTimeoutListener{ - TCPListener: ln.(*net.TCPListener), - acceptDeadline: acceptDeadline, - connDeadline: connDeadline, - period: period, - } -} - -// newTimeoutConn returns an instance of newTCPTimeoutConn. -func newTimeoutConn( - conn net.Conn, - connDeadline time.Duration) *timeoutConn { - return &timeoutConn{ - conn, - connDeadline, - } -} - -// Accept implements net.Listener. -func (ln tcpTimeoutListener) Accept() (net.Conn, error) { - err := ln.SetDeadline(time.Now().Add(ln.acceptDeadline)) - if err != nil { - return nil, err - } - - tc, err := ln.AcceptTCP() - if err != nil { - return nil, err - } - - // Wrap the conn in our timeout wrapper - conn := newTimeoutConn(tc, ln.connDeadline) - - return conn, nil -} - -// Read implements net.Listener. -func (c timeoutConn) Read(b []byte) (n int, err error) { - // Reset deadline - c.Conn.SetReadDeadline(time.Now().Add(c.connDeadline)) - - return c.Conn.Read(b) -} - -// Write implements net.Listener. -func (c timeoutConn) Write(b []byte) (n int, err error) { - // Reset deadline - c.Conn.SetWriteDeadline(time.Now().Add(c.connDeadline)) - - return c.Conn.Write(b) -} From be00cd1adde6e452cf90c5ee2c525c5e13ebdca9 Mon Sep 17 00:00:00 2001 From: Gian Felipe Date: Mon, 14 Jan 2019 06:34:29 +1100 Subject: [PATCH 183/267] Hotfix/validating query result length (#3053) * Validating that there are txs in the query results before loop throught the array * Created tests to validate the error has been fixed * Added comments * Fixing misspeling * check if the variable "skipCount" is bigger than zero. If it is not, we set it to 0. If it, we do not do anything. * using function that validates the skipCount variable * undo Gopkg.lock changes --- CHANGELOG_PENDING.md | 2 ++ rpc/client/rpc_test.go | 5 +++++ rpc/core/pipe.go | 9 +++++++++ rpc/core/tx.go | 3 ++- 4 files changed, 18 insertions(+), 1 deletion(-) diff --git a/CHANGELOG_PENDING.md b/CHANGELOG_PENDING.md index fd95a944..41e92255 100644 --- a/CHANGELOG_PENDING.md +++ b/CHANGELOG_PENDING.md @@ -33,6 +33,8 @@ Special thanks to external contributors on this release: - [rpc] \#3047 Include peer's remote IP in `/net_info` ### BUG FIXES: + - [types] \#2926 do not panic if retrieving the private validator's public key fails +- [rpc] \#3080 check if the variable "skipCount" is bigger than zero. If it is not, we set it to 0. If it, we do not do anything. - [crypto/multisig] \#3102 fix multisig keys address length - [crypto/encoding] \#3101 Fix `PubKeyMultisigThreshold` unmarshalling into `crypto.PubKey` interface diff --git a/rpc/client/rpc_test.go b/rpc/client/rpc_test.go index fa5080f9..dac7ec12 100644 --- a/rpc/client/rpc_test.go +++ b/rpc/client/rpc_test.go @@ -428,5 +428,10 @@ func TestTxSearch(t *testing.T) { if len(result.Txs) == 0 { t.Fatal("expected a lot of transactions") } + + // query a non existing tx with page 1 and txsPerPage 1 + result, err = c.TxSearch("app.creator='Cosmoshi Neetowoko'", true, 1, 1) + require.Nil(t, err, "%+v", err) + require.Len(t, result.Txs, 0) } } diff --git a/rpc/core/pipe.go b/rpc/core/pipe.go index 7f459654..3d745e6a 100644 --- a/rpc/core/pipe.go +++ b/rpc/core/pipe.go @@ -154,3 +154,12 @@ func validatePerPage(perPage int) int { } return perPage } + +func validateSkipCount(page, perPage int) int { + skipCount := (page - 1) * perPage + if skipCount < 0 { + return 0 + } + + return skipCount +} diff --git a/rpc/core/tx.go b/rpc/core/tx.go index 3bb0f28e..f1bfd56a 100644 --- a/rpc/core/tx.go +++ b/rpc/core/tx.go @@ -201,10 +201,11 @@ func TxSearch(query string, prove bool, page, perPage int) (*ctypes.ResultTxSear totalCount := len(results) perPage = validatePerPage(perPage) page = validatePage(page, perPage, totalCount) - skipCount := (page - 1) * perPage + skipCount := validateSkipCount(page, perPage) apiResults := make([]*ctypes.ResultTx, cmn.MinInt(perPage, totalCount-skipCount)) var proof types.TxProof + // if there's no tx in the results array, we don't need to loop through the apiResults array for i := 0; i < len(apiResults); i++ { r := results[skipCount+i] height := r.Height From 1895cde590f7abd041daa0d4751983bc9efd7ad0 Mon Sep 17 00:00:00 2001 From: Zarko Milosevic Date: Sun, 13 Jan 2019 20:47:00 +0100 Subject: [PATCH 184/267] [WIP] Fill in consensus core details in ADR 030 (#2696) * Initial work towards making ConsensusCore spec complete * Initial version of executor and complete consensus --- .../adr-030-consensus-refactor.md | 306 ++++++++++++++++++ types/evidence_test.go | 2 +- 2 files changed, 307 insertions(+), 1 deletion(-) diff --git a/docs/architecture/adr-030-consensus-refactor.md b/docs/architecture/adr-030-consensus-refactor.md index d48cfe10..5c8c3d75 100644 --- a/docs/architecture/adr-030-consensus-refactor.md +++ b/docs/architecture/adr-030-consensus-refactor.md @@ -126,6 +126,312 @@ func TestConsensusXXX(t *testing.T) { } ``` + +## Consensus Executor + +## Consensus Core + +```go +type Event interface{} + +type EventNewHeight struct { + Height int64 + ValidatorId int +} + +type EventNewRound HeightAndRound + +type EventProposal struct { + Height int64 + Round int + Timestamp Time + BlockID BlockID + POLRound int + Sender int +} + +type Majority23PrevotesBlock struct { + Height int64 + Round int + BlockID BlockID +} + +type Majority23PrecommitBlock struct { + Height int64 + Round int + BlockID BlockID +} + +type HeightAndRound struct { + Height int64 + Round int +} + +type Majority23PrevotesAny HeightAndRound +type Majority23PrecommitAny HeightAndRound +type TimeoutPropose HeightAndRound +type TimeoutPrevotes HeightAndRound +type TimeoutPrecommit HeightAndRound + + +type Message interface{} + +type MessageProposal struct { + Height int64 + Round int + BlockID BlockID + POLRound int +} + +type VoteType int + +const ( + VoteTypeUnknown VoteType = iota + Prevote + Precommit +) + + +type MessageVote struct { + Height int64 + Round int + BlockID BlockID + Type VoteType +} + + +type MessageDecision struct { + Height int64 + Round int + BlockID BlockID +} + +type TriggerTimeout struct { + Height int64 + Round int + Duration Duration +} + + +type RoundStep int + +const ( + RoundStepUnknown RoundStep = iota + RoundStepPropose + RoundStepPrevote + RoundStepPrecommit + RoundStepCommit +) + +type State struct { + Height int64 + Round int + Step RoundStep + LockedValue BlockID + LockedRound int + ValidValue BlockID + ValidRound int + ValidatorId int + ValidatorSetSize int +} + +func proposer(height int64, round int) int {} +func getValue() BlockID {} + +func Consensus(event Event, state State) (State, Message, TriggerTimeout) { + msg = nil + timeout = nil + switch event := event.(type) { + case EventNewHeight: + if event.Height > state.Height { + state.Height = event.Height + state.Round = -1 + state.Step = RoundStepPropose + state.LockedValue = nil + state.LockedRound = -1 + state.ValidValue = nil + state.ValidRound = -1 + state.ValidatorId = event.ValidatorId + } + return state, msg, timeout + + case EventNewRound: + if event.Height == state.Height and event.Round > state.Round { + state.Round = eventRound + state.Step = RoundStepPropose + if proposer(state.Height, state.Round) == state.ValidatorId { + proposal = state.ValidValue + if proposal == nil { + proposal = getValue() + } + msg = MessageProposal { state.Height, state.Round, proposal, state.ValidRound } + } + timeout = TriggerTimeout { state.Height, state.Round, timeoutPropose(state.Round) } + } + return state, msg, timeout + + case EventProposal: + if event.Height == state.Height and event.Round == state.Round and + event.Sender == proposal(state.Height, state.Round) and state.Step == RoundStepPropose { + if event.POLRound >= state.LockedRound or event.BlockID == state.BlockID or state.LockedRound == -1 { + msg = MessageVote { state.Height, state.Round, event.BlockID, Prevote } + } + state.Step = RoundStepPrevote + } + return state, msg, timeout + + case TimeoutPropose: + if event.Height == state.Height and event.Round == state.Round and state.Step == RoundStepPropose { + msg = MessageVote { state.Height, state.Round, nil, Prevote } + state.Step = RoundStepPrevote + } + return state, msg, timeout + + case Majority23PrevotesBlock: + if event.Height == state.Height and event.Round == state.Round and state.Step >= RoundStepPrevote and event.Round > state.ValidRound { + state.ValidRound = event.Round + state.ValidValue = event.BlockID + if state.Step == RoundStepPrevote { + state.LockedRound = event.Round + state.LockedValue = event.BlockID + msg = MessageVote { state.Height, state.Round, event.BlockID, Precommit } + state.Step = RoundStepPrecommit + } + } + return state, msg, timeout + + case Majority23PrevotesAny: + if event.Height == state.Height and event.Round == state.Round and state.Step == RoundStepPrevote { + timeout = TriggerTimeout { state.Height, state.Round, timeoutPrevote(state.Round) } + } + return state, msg, timeout + + case TimeoutPrevote: + if event.Height == state.Height and event.Round == state.Round and state.Step == RoundStepPrevote { + msg = MessageVote { state.Height, state.Round, nil, Precommit } + state.Step = RoundStepPrecommit + } + return state, msg, timeout + + case Majority23PrecommitBlock: + if event.Height == state.Height { + state.Step = RoundStepCommit + state.LockedValue = event.BlockID + } + return state, msg, timeout + + case Majority23PrecommitAny: + if event.Height == state.Height and event.Round == state.Round { + timeout = TriggerTimeout { state.Height, state.Round, timeoutPrecommit(state.Round) } + } + return state, msg, timeout + + case TimeoutPrecommit: + if event.Height == state.Height and event.Round == state.Round { + state.Round = state.Round + 1 + } + return state, msg, timeout + } +} + +func ConsensusExecutor() { + proposal = nil + votes = HeightVoteSet { Height: 1 } + state = State { + Height: 1 + Round: 0 + Step: RoundStepPropose + LockedValue: nil + LockedRound: -1 + ValidValue: nil + ValidRound: -1 + } + + event = EventNewHeight {1, id} + state, msg, timeout = Consensus(event, state) + + event = EventNewRound {state.Height, 0} + state, msg, timeout = Consensus(event, state) + + if msg != nil { + send msg + } + + if timeout != nil { + trigger timeout + } + + for { + select { + case message := <- msgCh: + switch msg := message.(type) { + case MessageProposal: + + case MessageVote: + if msg.Height == state.Height { + newVote = votes.AddVote(msg) + if newVote { + switch msg.Type { + case Prevote: + prevotes = votes.Prevotes(msg.Round) + if prevotes.WeakCertificate() and msg.Round > state.Round { + event = EventNewRound { msg.Height, msg.Round } + state, msg, timeout = Consensus(event, state) + state = handleStateChange(state, msg, timeout) + } + + if blockID, ok = prevotes.TwoThirdsMajority(); ok and blockID != nil { + if msg.Round == state.Round and hasBlock(blockID) { + event = Majority23PrevotesBlock { msg.Height, msg.Round, blockID } + state, msg, timeout = Consensus(event, state) + state = handleStateChange(state, msg, timeout) + } + if proposal != nil and proposal.POLRound == msg.Round and hasBlock(blockID) { + event = EventProposal { + Height: state.Height + Round: state.Round + BlockID: blockID + POLRound: proposal.POLRound + Sender: message.Sender + } + state, msg, timeout = Consensus(event, state) + state = handleStateChange(state, msg, timeout) + } + } + + if prevotes.HasTwoThirdsAny() and msg.Round == state.Round { + event = Majority23PrevotesAny { msg.Height, msg.Round, blockID } + state, msg, timeout = Consensus(event, state) + state = handleStateChange(state, msg, timeout) + } + + case Precommit: + + } + } + } + case timeout := <- timeoutCh: + + case block := <- blockCh: + + } + } +} + +func handleStateChange(state, msg, timeout) State { + if state.Step == Commit { + state = ExecuteBlock(state.LockedValue) + } + if msg != nil { + send msg + } + if timeout != nil { + trigger timeout + } +} + +``` + ### Implementation roadmap * implement proposed implementation diff --git a/types/evidence_test.go b/types/evidence_test.go index 19427150..1f1338ca 100644 --- a/types/evidence_test.go +++ b/types/evidence_test.go @@ -62,7 +62,7 @@ func TestEvidence(t *testing.T) { {vote1, makeVote(val, chainID, 0, 10, 3, 1, blockID2), false}, // wrong round {vote1, makeVote(val, chainID, 0, 10, 2, 2, blockID2), false}, // wrong step {vote1, makeVote(val2, chainID, 0, 10, 2, 1, blockID), false}, // wrong validator - {vote1, badVote, false}, // signed by wrong key + {vote1, badVote, false}, // signed by wrong key } pubKey := val.GetPubKey() From fc031d980b9801470f8eb7e7ed52a0d49e8f3724 Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Sun, 13 Jan 2019 17:15:34 -0500 Subject: [PATCH 185/267] Bucky/v0.28.0 (#3119) * changelog pending and upgrading * linkify and version bump * changelog shuffle --- CHANGELOG.md | 53 ++++++++++++++++++++++++++++++++++++++++++++ CHANGELOG_PENDING.md | 19 +--------------- UPGRADING.md | 50 +++++++++++++++++++++++++++++++++++++++++ version/version.go | 2 +- 4 files changed, 105 insertions(+), 19 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 46e9cb37..75ca299c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,58 @@ # Changelog +## v0.28.0 + +*January 14th, 2019* + +Special thanks to external contributors on this release: +@fmauricios, @gianfelipe93, @husio, @needkane, @srmo, @yutianwu + +This release is primarily about upgrades to the `privval` system - +separating the `priv_validator.json` into distinct config and data files, and +refactoring the socket validator to support reconnections. + +See [UPGRADING.md](UPGRADING.md) for more details. + +### BREAKING CHANGES: + +* CLI/RPC/Config +- [cli] Removed `node` `--proxy_app=dummy` option. Use `kvstore` (`persistent_kvstore`) instead. +- [cli] Renamed `node` `--proxy_app=nilapp` to `--proxy_app=noop`. +- [config] [\#2992](https://github.com/tendermint/tendermint/issues/2992) `allow_duplicate_ip` is now set to false +- [privval] [\#1181](https://github.com/tendermint/tendermint/issues/1181) Split immutable and mutable parts of `priv_validator.json` + (@yutianwu) +- [privval] [\#2926](https://github.com/tendermint/tendermint/issues/2926) Split up `PubKeyMsg` into `PubKeyRequest` and `PubKeyResponse` to be consistent with other message types +- [privval] [\#2923](https://github.com/tendermint/tendermint/issues/2923) Listen for unix socket connections instead of dialing them + +* Apps + +* Go API +- [types] [\#2981](https://github.com/tendermint/tendermint/issues/2981) Remove `PrivValidator.GetAddress()` + +* Blockchain Protocol + +* P2P Protocol + +### FEATURES: +- [rpc] [\#3052](https://github.com/tendermint/tendermint/issues/3052) Include peer's remote IP in `/net_info` + +### IMPROVEMENTS: +- [consensus] [\#3086](https://github.com/tendermint/tendermint/issues/3086) Log peerID on ignored votes (@srmo) +- [docs] [\#3061](https://github.com/tendermint/tendermint/issues/3061) Added spec on signing consensus msgs at + ./docs/spec/consensus/signing.md +- [privval] [\#2948](https://github.com/tendermint/tendermint/issues/2948) Memoize pubkey so it's only requested once on startup +- [privval] [\#2923](https://github.com/tendermint/tendermint/issues/2923) Retry RemoteSigner connections on error + +### BUG FIXES: + +- [types] [\#2926](https://github.com/tendermint/tendermint/issues/2926) Do not panic if retrieving the private validator's public key fails +- [rpc] [\#3053](https://github.com/tendermint/tendermint/issues/3053) Fix internal error in `/tx_search` when results are empty + (@gianfelipe93) +- [crypto/multisig] [\#3102](https://github.com/tendermint/tendermint/issues/3102) Fix multisig keys address length +- [crypto/encoding] [\#3101](https://github.com/tendermint/tendermint/issues/3101) Fix `PubKeyMultisigThreshold` unmarshalling into `crypto.PubKey` interface +- [build] [\#3085](https://github.com/tendermint/tendermint/issues/3085) Fix `Version` field in build scripts (@husio) +- [p2p/conn] [\#3111](https://github.com/tendermint/tendermint/issues/3111) Make SecretConnection thread safe + ## v0.27.4 *December 21st, 2018* diff --git a/CHANGELOG_PENDING.md b/CHANGELOG_PENDING.md index 41e92255..332cfbf7 100644 --- a/CHANGELOG_PENDING.md +++ b/CHANGELOG_PENDING.md @@ -1,4 +1,4 @@ -## v0.28.0 +## v0.29.0 *TBD* @@ -7,34 +7,17 @@ Special thanks to external contributors on this release: ### BREAKING CHANGES: * CLI/RPC/Config -- [cli] Removed `node` `--proxy_app=dummy` option. Use `kvstore` (`persistent_kvstore`) instead. -- [cli] Renamed `node` `--proxy_app=nilapp` to `--proxy_app=noop`. -- [config] \#2992 `allow_duplicate_ip` is now set to false -- [privval] \#2926 split up `PubKeyMsg` into `PubKeyRequest` and `PubKeyResponse` to be consistent with other message types -- [privval] \#2923 listen for unix socket connections instead of dialing them * Apps * Go API -- [types] \#2926 memoize consensus public key on initialization of remote signer and return the memoized key on -`PrivValidator.GetPubKey()` instead of requesting it again -- [types] \#2981 Remove `PrivValidator.GetAddress()` * Blockchain Protocol * P2P Protocol ### FEATURES: -- [privval] \#1181 Split immutable and mutable parts of `priv_validator.json` ### IMPROVEMENTS: -- [p2p/conn] \#3111 make SecretConnection thread safe -- [privval] \#2923 retry RemoteSigner connections on error -- [rpc] \#3047 Include peer's remote IP in `/net_info` ### BUG FIXES: - -- [types] \#2926 do not panic if retrieving the private validator's public key fails -- [rpc] \#3080 check if the variable "skipCount" is bigger than zero. If it is not, we set it to 0. If it, we do not do anything. -- [crypto/multisig] \#3102 fix multisig keys address length -- [crypto/encoding] \#3101 Fix `PubKeyMultisigThreshold` unmarshalling into `crypto.PubKey` interface diff --git a/UPGRADING.md b/UPGRADING.md index 63f000f5..3e2d1f69 100644 --- a/UPGRADING.md +++ b/UPGRADING.md @@ -3,6 +3,56 @@ This guide provides steps to be followed when you upgrade your applications to a newer version of Tendermint Core. +## v0.28.0 + +This release breaks the format for the `priv_validator.json` file +and the protocol used for the external validator process. +It is compatible with v0.27.0 blockchains (neither the BlockProtocol or the +P2PProtocol have changed). + +Please read carefully for details about upgrading. + +XXX: Backup your `config/priv_validator.json` +before proceeding. + +### `priv_validator.json` + +The `config/priv_validator.json` is now two files: +`config/priv_validator_key.json` and `data/priv_validator_state.json`. +The former contains the key material, the later contains the details on the last +thing signed. + +When running v0.28.0 for the first time, it will back up any pre-existing +`priv_validator.json` file and proceed to split it into the two new files. +Upgrading should happen automatically without problem. + +To upgrade manually, use the provided `privValUpgrade.go` script, with exact paths for the old +`priv_validator.json` and the locations for the two new files. It's recomended +to use the default paths, of `config/priv_validator_key.json` and +`data/priv_validator_state.json`, respectively: + +``` +go run scripts/privValUpgrade.go +``` + +### External validator signers + +The Unix and TCP implementations of the remote signing validator +have been consolidated into a single implementation. +Thus in both cases, the external process is expected to dial +Tendermint. This is different from how Unix sockets used to work, where +Tendermint dialed the external process. + +The `PubKeyMsg` was also split into two for consistency with other message +types. + +Note that the TCP sockets don't yet use a persistent key, +so while they're encrypted, they can't yet be properly authenticated. +See [#3105](https://github.com/tendermint/tendermint/issues/3105). +Note the Unix socket has neither encryption nor authentication, but will +add a shared-secret in [#3099](https://github.com/tendermint/tendermint/issues/3099). + + ## v0.27.0 This release contains some breaking changes to the block and p2p protocols, diff --git a/version/version.go b/version/version.go index 3cbdab02..658e0e89 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.27.4" + TMCoreSemVer = "0.28.0" // ABCISemVer is the semantic version of the ABCI library ABCISemVer = "0.15.0" From 1ccc0918f5f57f3e74ae471385258e504df01a27 Mon Sep 17 00:00:00 2001 From: Ismail Khoffi Date: Sun, 13 Jan 2019 23:40:50 +0100 Subject: [PATCH 186/267] More ProposerPriority tests (#2966) * more proposer priority tests - test that we don't reset to zero when updating / adding - test that same power validators alternate * add another test to track / simulate similar behaviour as in #2960 * address some of Chris' review comments * address some more of Chris' review comments --- state/state_test.go | 341 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 341 insertions(+) diff --git a/state/state_test.go b/state/state_test.go index 2ca5f8b2..0448008e 100644 --- a/state/state_test.go +++ b/state/state_test.go @@ -3,6 +3,7 @@ package state import ( "bytes" "fmt" + "math/big" "testing" "github.com/stretchr/testify/assert" @@ -263,6 +264,346 @@ func TestOneValidatorChangesSaveLoad(t *testing.T) { } } +func TestProposerPriorityDoesNotGetResetToZero(t *testing.T) { + // assert that we preserve accum when calling updateState: + // https://github.com/tendermint/tendermint/issues/2718 + tearDown, _, state := setupTestCase(t) + defer tearDown(t) + origVotingPower := int64(10) + val1PubKey := ed25519.GenPrivKey().PubKey() + val1 := &types.Validator{Address: val1PubKey.Address(), PubKey: val1PubKey, VotingPower: origVotingPower} + + state.Validators = types.NewValidatorSet([]*types.Validator{val1}) + state.NextValidators = state.Validators + + // NewValidatorSet calls IncrementProposerPriority but uses on a copy of val1 + assert.EqualValues(t, 0, val1.ProposerPriority) + + block := makeBlock(state, state.LastBlockHeight+1) + blockID := types.BlockID{block.Hash(), block.MakePartSet(testPartSize).Header()} + abciResponses := &ABCIResponses{ + EndBlock: &abci.ResponseEndBlock{ValidatorUpdates: nil}, + } + validatorUpdates, err := types.PB2TM.ValidatorUpdates(abciResponses.EndBlock.ValidatorUpdates) + require.NoError(t, err) + updatedState, err := updateState(state, blockID, &block.Header, abciResponses, validatorUpdates) + assert.NoError(t, err) + + assert.Equal(t, -origVotingPower, updatedState.NextValidators.Validators[0].ProposerPriority) + + // add a validator + val2PubKey := ed25519.GenPrivKey().PubKey() + val2VotingPower := int64(100) + updateAddVal := abci.ValidatorUpdate{PubKey: types.TM2PB.PubKey(val2PubKey), Power: val2VotingPower} + validatorUpdates, err = types.PB2TM.ValidatorUpdates([]abci.ValidatorUpdate{updateAddVal}) + assert.NoError(t, err) + updatedState2, err := updateState(updatedState, blockID, &block.Header, abciResponses, validatorUpdates) + assert.NoError(t, err) + + require.Equal(t, len(updatedState2.NextValidators.Validators), 2) + _, addedVal2 := updatedState2.NextValidators.GetByAddress(val2PubKey.Address()) + // adding a validator should not lead to a ProposerPriority equal to zero (unless the combination of averaging and + // incrementing would cause so; which is not the case here) + totalPowerBefore2 := origVotingPower // 10 + wantVal2ProposerPrio := -(totalPowerBefore2 + (totalPowerBefore2 >> 3)) + val2VotingPower // 89 + avg := (0 + wantVal2ProposerPrio) / 2 // 44 + wantVal2ProposerPrio -= avg // 45 + totalPowerAfter := origVotingPower + val2VotingPower // 110 + wantVal2ProposerPrio -= totalPowerAfter // -65 + assert.Equal(t, wantVal2ProposerPrio, addedVal2.ProposerPriority) // not zero == -65 + + // Updating a validator does not reset the ProposerPriority to zero: + updatedVotingPowVal2 := int64(1) + updateVal := abci.ValidatorUpdate{PubKey: types.TM2PB.PubKey(val2PubKey), Power: updatedVotingPowVal2} + validatorUpdates, err = types.PB2TM.ValidatorUpdates([]abci.ValidatorUpdate{updateVal}) + assert.NoError(t, err) + updatedState3, err := updateState(updatedState2, blockID, &block.Header, abciResponses, validatorUpdates) + assert.NoError(t, err) + + require.Equal(t, len(updatedState3.NextValidators.Validators), 2) + _, prevVal1 := updatedState3.Validators.GetByAddress(val1PubKey.Address()) + _, updatedVal2 := updatedState3.NextValidators.GetByAddress(val2PubKey.Address()) + + expectedVal1PrioBeforeAvg := prevVal1.ProposerPriority + prevVal1.VotingPower // -44 + 10 == -34 + wantVal2ProposerPrio = wantVal2ProposerPrio + updatedVotingPowVal2 // -64 + avg = (wantVal2ProposerPrio + expectedVal1PrioBeforeAvg) / 2 // (-64-34)/2 == -49 + wantVal2ProposerPrio = wantVal2ProposerPrio - avg // -15 + assert.Equal(t, wantVal2ProposerPrio, updatedVal2.ProposerPriority) // -15 +} + +func TestProposerPriorityProposerAlternates(t *testing.T) { + // Regression test that would fail if the inner workings of + // IncrementProposerPriority change. + // Additionally, make sure that same power validators alternate if both + // have the same voting power (and the 2nd was added later). + tearDown, _, state := setupTestCase(t) + defer tearDown(t) + origVotinPower := int64(10) + val1PubKey := ed25519.GenPrivKey().PubKey() + val1 := &types.Validator{Address: val1PubKey.Address(), PubKey: val1PubKey, VotingPower: origVotinPower} + + // reset state validators to above validator + state.Validators = types.NewValidatorSet([]*types.Validator{val1}) + state.NextValidators = state.Validators + // we only have one validator: + assert.Equal(t, val1PubKey.Address(), state.Validators.Proposer.Address) + + block := makeBlock(state, state.LastBlockHeight+1) + blockID := types.BlockID{block.Hash(), block.MakePartSet(testPartSize).Header()} + // no updates: + abciResponses := &ABCIResponses{ + EndBlock: &abci.ResponseEndBlock{ValidatorUpdates: nil}, + } + validatorUpdates, err := types.PB2TM.ValidatorUpdates(abciResponses.EndBlock.ValidatorUpdates) + require.NoError(t, err) + + updatedState, err := updateState(state, blockID, &block.Header, abciResponses, validatorUpdates) + assert.NoError(t, err) + + // 0 + 10 (initial prio) - 10 (avg) - 10 (mostest - total) = -10 + assert.Equal(t, -origVotinPower, updatedState.NextValidators.Validators[0].ProposerPriority) + assert.Equal(t, val1PubKey.Address(), updatedState.NextValidators.Proposer.Address) + + // add a validator with the same voting power as the first + val2PubKey := ed25519.GenPrivKey().PubKey() + updateAddVal := abci.ValidatorUpdate{PubKey: types.TM2PB.PubKey(val2PubKey), Power: origVotinPower} + validatorUpdates, err = types.PB2TM.ValidatorUpdates([]abci.ValidatorUpdate{updateAddVal}) + assert.NoError(t, err) + + updatedState2, err := updateState(updatedState, blockID, &block.Header, abciResponses, validatorUpdates) + assert.NoError(t, err) + + require.Equal(t, len(updatedState2.NextValidators.Validators), 2) + assert.Equal(t, updatedState2.Validators, updatedState.NextValidators) + + // val1 will still be proposer as val2 just got added: + assert.Equal(t, val1PubKey.Address(), updatedState.NextValidators.Proposer.Address) + assert.Equal(t, updatedState2.Validators.Proposer.Address, updatedState2.NextValidators.Proposer.Address) + assert.Equal(t, updatedState2.Validators.Proposer.Address, val1PubKey.Address()) + assert.Equal(t, updatedState2.NextValidators.Proposer.Address, val1PubKey.Address()) + + _, updatedVal1 := updatedState2.NextValidators.GetByAddress(val1PubKey.Address()) + _, oldVal1 := updatedState2.Validators.GetByAddress(val1PubKey.Address()) + _, updatedVal2 := updatedState2.NextValidators.GetByAddress(val2PubKey.Address()) + + totalPower := origVotinPower + v2PrioWhenAddedVal2 := -(totalPower + (totalPower >> 3)) + v2PrioWhenAddedVal2 = v2PrioWhenAddedVal2 + origVotinPower // -11 + 10 == -1 + v1PrioWhenAddedVal2 := oldVal1.ProposerPriority + origVotinPower // -10 + 10 == 0 + // have to express the AVG in big.Ints as -1/2 == -1 in big.Int while -1/2 == 0 in int64 + avgSum := big.NewInt(0).Add(big.NewInt(v2PrioWhenAddedVal2), big.NewInt(v1PrioWhenAddedVal2)) + avg := avgSum.Div(avgSum, big.NewInt(2)) + expectedVal2Prio := v2PrioWhenAddedVal2 - avg.Int64() + totalPower = 2 * origVotinPower // 10 + 10 + expectedVal1Prio := oldVal1.ProposerPriority + origVotinPower - avg.Int64() - totalPower + // val1's ProposerPriority story: -10 (see above) + 10 (voting pow) - (-1) (avg) - 20 (total) == -19 + assert.EqualValues(t, expectedVal1Prio, updatedVal1.ProposerPriority) + // val2 prio when added: -(totalVotingPower + (totalVotingPower >> 3)) == -11 + // -> -11 + 10 (voting power) - (-1) (avg) == 0 + assert.EqualValues(t, expectedVal2Prio, updatedVal2.ProposerPriority, "unexpected proposer priority for validator: %v", updatedVal2) + + validatorUpdates, err = types.PB2TM.ValidatorUpdates(abciResponses.EndBlock.ValidatorUpdates) + require.NoError(t, err) + + updatedState3, err := updateState(updatedState2, blockID, &block.Header, abciResponses, validatorUpdates) + assert.NoError(t, err) + + // proposer changes from now on (every iteration) as long as there are no changes in the validator set: + assert.NotEqual(t, updatedState3.Validators.Proposer.Address, updatedState3.NextValidators.Proposer.Address) + + assert.Equal(t, updatedState3.Validators, updatedState2.NextValidators) + _, updatedVal1 = updatedState3.NextValidators.GetByAddress(val1PubKey.Address()) + _, oldVal1 = updatedState3.Validators.GetByAddress(val1PubKey.Address()) + _, updatedVal2 = updatedState3.NextValidators.GetByAddress(val2PubKey.Address()) + _, oldVal2 := updatedState3.Validators.GetByAddress(val2PubKey.Address()) + + // val2 will be proposer: + assert.Equal(t, val2PubKey.Address(), updatedState3.NextValidators.Proposer.Address) + // check if expected proposer prio is matched: + + avgSum = big.NewInt(oldVal1.ProposerPriority + origVotinPower + oldVal2.ProposerPriority + origVotinPower) + avg = avgSum.Div(avgSum, big.NewInt(2)) + expectedVal1Prio2 := oldVal1.ProposerPriority + origVotinPower - avg.Int64() + expectedVal2Prio2 := oldVal2.ProposerPriority + origVotinPower - avg.Int64() - totalPower + + // -19 + 10 - 0 (avg) == -9 + assert.EqualValues(t, expectedVal1Prio2, updatedVal1.ProposerPriority, "unexpected proposer priority for validator: %v", updatedVal2) + // 0 + 10 - 0 (avg) - 20 (total) == -10 + assert.EqualValues(t, expectedVal2Prio2, updatedVal2.ProposerPriority, "unexpected proposer priority for validator: %v", updatedVal2) + + // no changes in voting power and both validators have same voting power + // -> proposers should alternate: + oldState := updatedState3 + for i := 0; i < 1000; i++ { + // no validator updates: + abciResponses := &ABCIResponses{ + EndBlock: &abci.ResponseEndBlock{ValidatorUpdates: nil}, + } + validatorUpdates, err = types.PB2TM.ValidatorUpdates(abciResponses.EndBlock.ValidatorUpdates) + require.NoError(t, err) + + updatedState, err := updateState(oldState, blockID, &block.Header, abciResponses, validatorUpdates) + assert.NoError(t, err) + // alternate (and cyclic priorities): + assert.NotEqual(t, updatedState.Validators.Proposer.Address, updatedState.NextValidators.Proposer.Address, "iter: %v", i) + assert.Equal(t, oldState.Validators.Proposer.Address, updatedState.NextValidators.Proposer.Address, "iter: %v", i) + + _, updatedVal1 = updatedState.NextValidators.GetByAddress(val1PubKey.Address()) + _, updatedVal2 = updatedState.NextValidators.GetByAddress(val2PubKey.Address()) + + if i%2 == 0 { + assert.Equal(t, updatedState.Validators.Proposer.Address, val2PubKey.Address()) + assert.Equal(t, expectedVal1Prio, updatedVal1.ProposerPriority) // -19 + assert.Equal(t, expectedVal2Prio, updatedVal2.ProposerPriority) // 0 + } else { + assert.Equal(t, updatedState.Validators.Proposer.Address, val1PubKey.Address()) + assert.Equal(t, expectedVal1Prio2, updatedVal1.ProposerPriority) // -9 + assert.Equal(t, expectedVal2Prio2, updatedVal2.ProposerPriority) // -10 + } + // update for next iteration: + oldState = updatedState + } +} + +func TestLargeGenesisValidator(t *testing.T) { + tearDown, _, state := setupTestCase(t) + defer tearDown(t) + // TODO: increase genesis voting power to sth. more close to MaxTotalVotingPower with changes that + // fix with tendermint/issues/2960; currently, the last iteration would take forever though + genesisVotingPower := int64(types.MaxTotalVotingPower / 100000000000000) + genesisPubKey := ed25519.GenPrivKey().PubKey() + // fmt.Println("genesis addr: ", genesisPubKey.Address()) + genesisVal := &types.Validator{Address: genesisPubKey.Address(), PubKey: genesisPubKey, VotingPower: genesisVotingPower} + // reset state validators to above validator + state.Validators = types.NewValidatorSet([]*types.Validator{genesisVal}) + state.NextValidators = state.Validators + require.True(t, len(state.Validators.Validators) == 1) + + // update state a few times with no validator updates + // asserts that the single validator's ProposerPrio stays the same + oldState := state + for i := 0; i < 10; i++ { + // no updates: + abciResponses := &ABCIResponses{ + EndBlock: &abci.ResponseEndBlock{ValidatorUpdates: nil}, + } + validatorUpdates, err := types.PB2TM.ValidatorUpdates(abciResponses.EndBlock.ValidatorUpdates) + require.NoError(t, err) + + block := makeBlock(oldState, oldState.LastBlockHeight+1) + blockID := types.BlockID{block.Hash(), block.MakePartSet(testPartSize).Header()} + + updatedState, err := updateState(oldState, blockID, &block.Header, abciResponses, validatorUpdates) + // no changes in voting power (ProposerPrio += VotingPower == 0 in 1st round; than shiftByAvg == no-op, + // than -Total == -Voting) + // -> no change in ProposerPrio (stays -Total == -VotingPower): + assert.EqualValues(t, oldState.NextValidators, updatedState.NextValidators) + assert.EqualValues(t, -genesisVotingPower, updatedState.NextValidators.Proposer.ProposerPriority) + + oldState = updatedState + } + // add another validator, do a few iterations (create blocks), + // add more validators with same voting power as the 2nd + // let the genesis validator "unbond", + // see how long it takes until the effect wears off and both begin to alternate + // see: https://github.com/tendermint/tendermint/issues/2960 + firstAddedValPubKey := ed25519.GenPrivKey().PubKey() + // fmt.Println("first added addr: ", firstAddedValPubKey.Address()) + firstAddedValVotingPower := int64(10) + firstAddedVal := abci.ValidatorUpdate{PubKey: types.TM2PB.PubKey(firstAddedValPubKey), Power: firstAddedValVotingPower} + validatorUpdates, err := types.PB2TM.ValidatorUpdates([]abci.ValidatorUpdate{firstAddedVal}) + assert.NoError(t, err) + abciResponses := &ABCIResponses{ + EndBlock: &abci.ResponseEndBlock{ValidatorUpdates: []abci.ValidatorUpdate{firstAddedVal}}, + } + block := makeBlock(oldState, oldState.LastBlockHeight+1) + blockID := types.BlockID{block.Hash(), block.MakePartSet(testPartSize).Header()} + updatedState, err := updateState(oldState, blockID, &block.Header, abciResponses, validatorUpdates) + + lastState := updatedState + for i := 0; i < 200; i++ { + // no updates: + abciResponses := &ABCIResponses{ + EndBlock: &abci.ResponseEndBlock{ValidatorUpdates: nil}, + } + validatorUpdates, err := types.PB2TM.ValidatorUpdates(abciResponses.EndBlock.ValidatorUpdates) + require.NoError(t, err) + + block := makeBlock(lastState, lastState.LastBlockHeight+1) + blockID := types.BlockID{block.Hash(), block.MakePartSet(testPartSize).Header()} + + updatedStateInner, err := updateState(lastState, blockID, &block.Header, abciResponses, validatorUpdates) + lastState = updatedStateInner + } + // set state to last state of above iteration + state = lastState + + // set oldState to state before above iteration + oldState = updatedState + _, oldGenesisVal := oldState.NextValidators.GetByAddress(genesisVal.Address) + _, newGenesisVal := state.NextValidators.GetByAddress(genesisVal.Address) + _, addedOldVal := oldState.NextValidators.GetByAddress(firstAddedValPubKey.Address()) + _, addedNewVal := state.NextValidators.GetByAddress(firstAddedValPubKey.Address()) + // expect large negative proposer priority for both (genesis validator decreased, 2nd validator increased): + assert.True(t, oldGenesisVal.ProposerPriority > newGenesisVal.ProposerPriority) + assert.True(t, addedOldVal.ProposerPriority < addedNewVal.ProposerPriority) + + // add 10 validators with the same voting power as the one added directly after genesis: + for i := 0; i < 10; i++ { + addedPubKey := ed25519.GenPrivKey().PubKey() + + addedVal := abci.ValidatorUpdate{PubKey: types.TM2PB.PubKey(addedPubKey), Power: firstAddedValVotingPower} + validatorUpdates, err := types.PB2TM.ValidatorUpdates([]abci.ValidatorUpdate{addedVal}) + assert.NoError(t, err) + + abciResponses := &ABCIResponses{ + EndBlock: &abci.ResponseEndBlock{ValidatorUpdates: []abci.ValidatorUpdate{addedVal}}, + } + block := makeBlock(oldState, oldState.LastBlockHeight+1) + blockID := types.BlockID{block.Hash(), block.MakePartSet(testPartSize).Header()} + state, err = updateState(state, blockID, &block.Header, abciResponses, validatorUpdates) + } + require.Equal(t, 10+2, len(state.NextValidators.Validators)) + + // remove genesis validator: + removeGenesisVal := abci.ValidatorUpdate{PubKey: types.TM2PB.PubKey(genesisPubKey), Power: 0} + abciResponses = &ABCIResponses{ + EndBlock: &abci.ResponseEndBlock{ValidatorUpdates: []abci.ValidatorUpdate{removeGenesisVal}}, + } + block = makeBlock(oldState, oldState.LastBlockHeight+1) + blockID = types.BlockID{block.Hash(), block.MakePartSet(testPartSize).Header()} + validatorUpdates, err = types.PB2TM.ValidatorUpdates(abciResponses.EndBlock.ValidatorUpdates) + require.NoError(t, err) + updatedState, err = updateState(state, blockID, &block.Header, abciResponses, validatorUpdates) + require.NoError(t, err) + // only the first added val (not the genesis val) should be left + assert.Equal(t, 11, len(updatedState.NextValidators.Validators)) + + // call update state until the effect for the 3rd added validator + // being proposer for a long time after the genesis validator left wears off: + curState := updatedState + count := 0 + isProposerUnchanged := true + for isProposerUnchanged { + abciResponses := &ABCIResponses{ + EndBlock: &abci.ResponseEndBlock{ValidatorUpdates: nil}, + } + validatorUpdates, err = types.PB2TM.ValidatorUpdates(abciResponses.EndBlock.ValidatorUpdates) + require.NoError(t, err) + block = makeBlock(curState, curState.LastBlockHeight+1) + blockID = types.BlockID{block.Hash(), block.MakePartSet(testPartSize).Header()} + curState, err = updateState(curState, blockID, &block.Header, abciResponses, validatorUpdates) + if !bytes.Equal(curState.Validators.Proposer.Address, curState.NextValidators.Proposer.Address) { + isProposerUnchanged = false + } + count++ + } + // first proposer change happens after this many iters; we probably want to lower this number: + // TODO: change with https://github.com/tendermint/tendermint/issues/2960 + firstProposerChangeExpectedAfter := 438 + assert.Equal(t, firstProposerChangeExpectedAfter, count) +} + func TestStoreLoadValidatorsIncrementsProposerPriority(t *testing.T) { const valSetSize = 2 tearDown, stateDB, state := setupTestCase(t) From 1f683188752dde08d7bac8d932efc0bfcf97ece8 Mon Sep 17 00:00:00 2001 From: Ismail Khoffi Date: Sun, 13 Jan 2019 23:56:36 +0100 Subject: [PATCH 187/267] fix order of BlockID and Timestamp in Vote and Proposal (#3078) * Consistent order fields of Timestamp/BlockID fields in CanonicalVote and CanonicalProposal * update spec too * Introduce and use IsZero & IsComplete: - update IsZero method according to spec and introduce IsComplete - use methods in validate basic to validate: proposals come with a "complete" blockId and votes are either complete or empty - update spec: BlockID.IsNil() -> BlockID.IsZero() and fix typo * BlockID comes first * fix tests --- CHANGELOG_PENDING.md | 1 + docs/spec/blockchain/blockchain.md | 4 ---- docs/spec/blockchain/encoding.md | 2 +- docs/spec/consensus/signing.md | 10 +++++----- types/block.go | 19 ++++++++++++++----- types/canonical.go | 4 ++-- types/part_set.go | 2 +- types/proposal.go | 4 ++++ types/vote.go | 7 ++++++- types/vote_test.go | 12 ++++++------ 10 files changed, 40 insertions(+), 25 deletions(-) diff --git a/CHANGELOG_PENDING.md b/CHANGELOG_PENDING.md index 332cfbf7..83ec4d8f 100644 --- a/CHANGELOG_PENDING.md +++ b/CHANGELOG_PENDING.md @@ -7,6 +7,7 @@ Special thanks to external contributors on this release: ### BREAKING CHANGES: * CLI/RPC/Config +- [types] consistent field order of `CanonicalVote` and `CanonicalProposal` * Apps diff --git a/docs/spec/blockchain/blockchain.md b/docs/spec/blockchain/blockchain.md index cda32326..f80c8c05 100644 --- a/docs/spec/blockchain/blockchain.md +++ b/docs/spec/blockchain/blockchain.md @@ -109,10 +109,6 @@ Tendermint uses the [Google.Protobuf.WellKnownTypes.Timestamp](https://developers.google.com/protocol-buffers/docs/reference/csharp/class/google/protobuf/well-known-types/timestamp) format, which uses two integers, one for Seconds and for Nanoseconds. -NOTE: there is currently a small divergence between Tendermint and the -Google.Protobuf.WellKnownTypes.Timestamp that should be resolved. See [this -issue](https://github.com/tendermint/go-amino/issues/223) for details. - ## Data Data is just a wrapper for a list of transactions, where transactions are diff --git a/docs/spec/blockchain/encoding.md b/docs/spec/blockchain/encoding.md index cb506739..689ebbd6 100644 --- a/docs/spec/blockchain/encoding.md +++ b/docs/spec/blockchain/encoding.md @@ -301,8 +301,8 @@ type CanonicalVote struct { Type byte Height int64 `binary:"fixed64"` Round int64 `binary:"fixed64"` - Timestamp time.Time BlockID CanonicalBlockID + Timestamp time.Time ChainID string } ``` diff --git a/docs/spec/consensus/signing.md b/docs/spec/consensus/signing.md index d1ee71a6..f97df0c6 100644 --- a/docs/spec/consensus/signing.md +++ b/docs/spec/consensus/signing.md @@ -59,9 +59,9 @@ type PartSetHeader struct { ``` To be included in a valid vote or proposal, BlockID must either represent a `nil` block, or a complete one. -We introduce two methods, `BlockID.IsNil()` and `BlockID.IsComplete()` for these cases, respectively. +We introduce two methods, `BlockID.IsZero()` and `BlockID.IsComplete()` for these cases, respectively. -`BlockID.IsNil()` returns true for BlockID `b` if each of the following +`BlockID.IsZero()` returns true for BlockID `b` if each of the following are true: ``` @@ -81,7 +81,7 @@ len(b.PartsHeader.Hash) == 32 ## Proposals -The structure of a propsal for signing looks like: +The structure of a proposal for signing looks like: ``` type CanonicalProposal struct { @@ -118,8 +118,8 @@ type CanonicalVote struct { Type SignedMsgType // type alias for byte Height int64 `binary:"fixed64"` Round int64 `binary:"fixed64"` - Timestamp time.Time BlockID BlockID + Timestamp time.Time ChainID string } ``` @@ -130,7 +130,7 @@ A vote is valid if each of the following lines evaluates to true for vote `v`: v.Type == 0x1 || v.Type == 0x2 v.Height > 0 v.Round >= 0 -v.BlockID.IsNil() || v.BlockID.IsValid() +v.BlockID.IsZero() || v.BlockID.IsComplete() ``` In other words, a vote is valid for signing if it contains the type of a Prevote diff --git a/types/block.go b/types/block.go index 15b88d81..93315ade 100644 --- a/types/block.go +++ b/types/block.go @@ -11,6 +11,7 @@ import ( "github.com/tendermint/tendermint/crypto" "github.com/tendermint/tendermint/crypto/merkle" + "github.com/tendermint/tendermint/crypto/tmhash" cmn "github.com/tendermint/tendermint/libs/common" "github.com/tendermint/tendermint/version" ) @@ -788,11 +789,6 @@ type BlockID struct { PartsHeader PartSetHeader `json:"parts"` } -// IsZero returns true if this is the BlockID for a nil-block -func (blockID BlockID) IsZero() bool { - return len(blockID.Hash) == 0 && blockID.PartsHeader.IsZero() -} - // Equals returns true if the BlockID matches the given BlockID func (blockID BlockID) Equals(other BlockID) bool { return bytes.Equal(blockID.Hash, other.Hash) && @@ -820,6 +816,19 @@ func (blockID BlockID) ValidateBasic() error { return nil } +// IsZero returns true if this is the BlockID of a nil block. +func (blockID BlockID) IsZero() bool { + return len(blockID.Hash) == 0 && + blockID.PartsHeader.IsZero() +} + +// IsComplete returns true if this is a valid BlockID of a non-nil block. +func (blockID BlockID) IsComplete() bool { + return len(blockID.Hash) == tmhash.Size && + blockID.PartsHeader.Total > 0 && + len(blockID.PartsHeader.Hash) == tmhash.Size +} + // String returns a human readable string representation of the BlockID func (blockID BlockID) String() string { return fmt.Sprintf(`%v:%v`, blockID.Hash, blockID.PartsHeader) diff --git a/types/canonical.go b/types/canonical.go index eabd7684..47a8c817 100644 --- a/types/canonical.go +++ b/types/canonical.go @@ -36,8 +36,8 @@ type CanonicalVote struct { Type SignedMsgType // type alias for byte Height int64 `binary:"fixed64"` Round int64 `binary:"fixed64"` - Timestamp time.Time BlockID CanonicalBlockID + Timestamp time.Time ChainID string } @@ -75,8 +75,8 @@ func CanonicalizeVote(chainID string, vote *Vote) CanonicalVote { Type: vote.Type, Height: vote.Height, Round: int64(vote.Round), // cast int->int64 to make amino encode it fixed64 (does not work for int) - Timestamp: vote.Timestamp, BlockID: CanonicalizeBlockID(vote.BlockID), + Timestamp: vote.Timestamp, ChainID: chainID, } } diff --git a/types/part_set.go b/types/part_set.go index a040258d..7e1aa3c3 100644 --- a/types/part_set.go +++ b/types/part_set.go @@ -75,7 +75,7 @@ func (psh PartSetHeader) String() string { } func (psh PartSetHeader) IsZero() bool { - return psh.Total == 0 + return psh.Total == 0 && len(psh.Hash) == 0 } func (psh PartSetHeader) Equals(other PartSetHeader) bool { diff --git a/types/proposal.go b/types/proposal.go index f3b62aae..97c06596 100644 --- a/types/proposal.go +++ b/types/proposal.go @@ -60,6 +60,10 @@ func (p *Proposal) ValidateBasic() error { if err := p.BlockID.ValidateBasic(); err != nil { return fmt.Errorf("Wrong BlockID: %v", err) } + // ValidateBasic above would pass even if the BlockID was empty: + if !p.BlockID.IsComplete() { + return fmt.Errorf("Expected a complete, non-empty BlockID, got: %v", p.BlockID) + } // NOTE: Timestamp validation is subtle and handled elsewhere. diff --git a/types/vote.go b/types/vote.go index bf14d403..8ff51d3c 100644 --- a/types/vote.go +++ b/types/vote.go @@ -52,8 +52,8 @@ type Vote struct { Type SignedMsgType `json:"type"` Height int64 `json:"height"` Round int `json:"round"` - Timestamp time.Time `json:"timestamp"` BlockID BlockID `json:"block_id"` // zero if vote is nil. + Timestamp time.Time `json:"timestamp"` ValidatorAddress Address `json:"validator_address"` ValidatorIndex int `json:"validator_index"` Signature []byte `json:"signature"` @@ -127,6 +127,11 @@ func (vote *Vote) ValidateBasic() error { if err := vote.BlockID.ValidateBasic(); err != nil { return fmt.Errorf("Wrong BlockID: %v", err) } + // BlockID.ValidateBasic would not err if we for instance have an empty hash but a + // non-empty PartsSetHeader: + if !vote.BlockID.IsZero() && !vote.BlockID.IsComplete() { + return fmt.Errorf("BlockID must be either empty or complete, got: %v", vote.BlockID) + } if len(vote.ValidatorAddress) != crypto.AddressSize { return fmt.Errorf("Expected ValidatorAddress size to be %d bytes, got %d bytes", crypto.AddressSize, diff --git a/types/vote_test.go b/types/vote_test.go index 942f2d6b..aefa4fcf 100644 --- a/types/vote_test.go +++ b/types/vote_test.go @@ -63,8 +63,8 @@ func TestVoteSignableTestVectors(t *testing.T) { { CanonicalizeVote("", &Vote{}), // NOTE: Height and Round are skipped here. This case needs to be considered while parsing. - // []byte{0x22, 0x9, 0x9, 0x0, 0x9, 0x6e, 0x88, 0xf1, 0xff, 0xff, 0xff}, - []byte{0x22, 0xb, 0x8, 0x80, 0x92, 0xb8, 0xc3, 0x98, 0xfe, 0xff, 0xff, 0xff, 0x1}, + // []byte{0x2a, 0x9, 0x9, 0x0, 0x9, 0x6e, 0x88, 0xf1, 0xff, 0xff, 0xff}, + []byte{0x2a, 0xb, 0x8, 0x80, 0x92, 0xb8, 0xc3, 0x98, 0xfe, 0xff, 0xff, 0xff, 0x1}, }, // with proper (fixed size) height and round (PreCommit): { @@ -76,7 +76,7 @@ func TestVoteSignableTestVectors(t *testing.T) { 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, // height 0x19, // (field_number << 3) | wire_type 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, // round - 0x22, // (field_number << 3) | wire_type + 0x2a, // (field_number << 3) | wire_type // remaining fields (timestamp): 0xb, 0x8, 0x80, 0x92, 0xb8, 0xc3, 0x98, 0xfe, 0xff, 0xff, 0xff, 0x1}, }, @@ -90,7 +90,7 @@ func TestVoteSignableTestVectors(t *testing.T) { 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, // height 0x19, // (field_number << 3) | wire_type 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, // round - 0x22, // (field_number << 3) | wire_type + 0x2a, // (field_number << 3) | wire_type // remaining fields (timestamp): 0xb, 0x8, 0x80, 0x92, 0xb8, 0xc3, 0x98, 0xfe, 0xff, 0xff, 0xff, 0x1}, }, @@ -102,7 +102,7 @@ func TestVoteSignableTestVectors(t *testing.T) { 0x19, // (field_number << 3) | wire_type 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, // round // remaining fields (timestamp): - 0x22, + 0x2a, 0xb, 0x8, 0x80, 0x92, 0xb8, 0xc3, 0x98, 0xfe, 0xff, 0xff, 0xff, 0x1}, }, // containing non-empty chain_id: @@ -114,7 +114,7 @@ func TestVoteSignableTestVectors(t *testing.T) { 0x19, // (field_number << 3) | wire_type 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, // round // remaining fields: - 0x22, // (field_number << 3) | wire_type + 0x2a, // (field_number << 3) | wire_type 0xb, 0x8, 0x80, 0x92, 0xb8, 0xc3, 0x98, 0xfe, 0xff, 0xff, 0xff, 0x1, // timestamp 0x32, // (field_number << 3) | wire_type 0xd, 0x74, 0x65, 0x73, 0x74, 0x5f, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x5f, 0x69, 0x64}, // chainID From ec53ce359bb8f011e4dbb715da098bea08c32ded Mon Sep 17 00:00:00 2001 From: Dev Ojha Date: Sun, 13 Jan 2019 17:02:38 -0600 Subject: [PATCH 188/267] Simple merkle rfc compatibility (#2713) * Begin simple merkle compatibility PR * Fix query_test * Use trillian test vectors * Change the split point per RFC 6962 * update spec * refactor innerhash to match spec * Update changelog * Address @liamsi's comments * Write the comment requested by @liamsi --- CHANGELOG_PENDING.md | 1 + blockchain/store_test.go | 2 +- crypto/merkle/hash.go | 21 +++++++ crypto/merkle/proof_simple_value.go | 8 +-- crypto/merkle/rfc6962_test.go | 97 +++++++++++++++++++++++++++++ crypto/merkle/simple_map_test.go | 12 ++-- crypto/merkle/simple_proof.go | 19 +++--- crypto/merkle/simple_tree.go | 38 +++++------ crypto/merkle/simple_tree_test.go | 36 ++++++++--- docs/spec/blockchain/encoding.md | 70 +++++++++------------ lite/proxy/query_test.go | 6 +- types/part_set.go | 13 +--- types/results_test.go | 2 +- types/tx.go | 13 ++-- types/tx_test.go | 7 +-- 15 files changed, 233 insertions(+), 112 deletions(-) create mode 100644 crypto/merkle/hash.go create mode 100644 crypto/merkle/rfc6962_test.go diff --git a/CHANGELOG_PENDING.md b/CHANGELOG_PENDING.md index 83ec4d8f..8012832f 100644 --- a/CHANGELOG_PENDING.md +++ b/CHANGELOG_PENDING.md @@ -14,6 +14,7 @@ Special thanks to external contributors on this release: * Go API * Blockchain Protocol + * [merkle] \#2713 Merkle trees now match the RFC 6962 specification * P2P Protocol diff --git a/blockchain/store_test.go b/blockchain/store_test.go index a52039fa..8059072e 100644 --- a/blockchain/store_test.go +++ b/blockchain/store_test.go @@ -311,7 +311,7 @@ func TestLoadBlockPart(t *testing.T) { gotPart, _, panicErr := doFn(loadPart) require.Nil(t, panicErr, "an existent and proper block should not panic") require.Nil(t, res, "a properly saved block should return a proper block") - require.Equal(t, gotPart.(*types.Part).Hash(), part1.Hash(), + require.Equal(t, gotPart.(*types.Part), part1, "expecting successful retrieval of previously saved block") } diff --git a/crypto/merkle/hash.go b/crypto/merkle/hash.go new file mode 100644 index 00000000..4e24046a --- /dev/null +++ b/crypto/merkle/hash.go @@ -0,0 +1,21 @@ +package merkle + +import ( + "github.com/tendermint/tendermint/crypto/tmhash" +) + +// TODO: make these have a large predefined capacity +var ( + leafPrefix = []byte{0} + innerPrefix = []byte{1} +) + +// returns tmhash(0x00 || leaf) +func leafHash(leaf []byte) []byte { + return tmhash.Sum(append(leafPrefix, leaf...)) +} + +// returns tmhash(0x01 || left || right) +func innerHash(left []byte, right []byte) []byte { + return tmhash.Sum(append(innerPrefix, append(left, right...)...)) +} diff --git a/crypto/merkle/proof_simple_value.go b/crypto/merkle/proof_simple_value.go index 904b6e5e..247921ad 100644 --- a/crypto/merkle/proof_simple_value.go +++ b/crypto/merkle/proof_simple_value.go @@ -71,11 +71,11 @@ func (op SimpleValueOp) Run(args [][]byte) ([][]byte, error) { hasher.Write(value) // does not error vhash := hasher.Sum(nil) + bz := new(bytes.Buffer) // Wrap to hash the KVPair. - hasher = tmhash.New() - encodeByteSlice(hasher, []byte(op.key)) // does not error - encodeByteSlice(hasher, []byte(vhash)) // does not error - kvhash := hasher.Sum(nil) + encodeByteSlice(bz, []byte(op.key)) // does not error + encodeByteSlice(bz, []byte(vhash)) // does not error + kvhash := leafHash(bz.Bytes()) if !bytes.Equal(kvhash, op.Proof.LeafHash) { return nil, cmn.NewError("leaf hash mismatch: want %X got %X", op.Proof.LeafHash, kvhash) diff --git a/crypto/merkle/rfc6962_test.go b/crypto/merkle/rfc6962_test.go new file mode 100644 index 00000000..b6413b47 --- /dev/null +++ b/crypto/merkle/rfc6962_test.go @@ -0,0 +1,97 @@ +package merkle + +// Copyright 2016 Google Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// These tests were taken from https://github.com/google/trillian/blob/master/merkle/rfc6962/rfc6962_test.go, +// and consequently fall under the above license. +import ( + "bytes" + "encoding/hex" + "testing" + + "github.com/tendermint/tendermint/crypto/tmhash" +) + +func TestRFC6962Hasher(t *testing.T) { + _, leafHashTrail := trailsFromByteSlices([][]byte{[]byte("L123456")}) + leafHash := leafHashTrail.Hash + _, leafHashTrail = trailsFromByteSlices([][]byte{[]byte{}}) + emptyLeafHash := leafHashTrail.Hash + for _, tc := range []struct { + desc string + got []byte + want string + }{ + // Since creating a merkle tree of no leaves is unsupported here, we skip + // the corresponding trillian test vector. + + // Check that the empty hash is not the same as the hash of an empty leaf. + // echo -n 00 | xxd -r -p | sha256sum + { + desc: "RFC6962 Empty Leaf", + want: "6e340b9cffb37a989ca544e6bb780a2c78901d3fb33738768511a30617afa01d"[:tmhash.Size*2], + got: emptyLeafHash, + }, + // echo -n 004C313233343536 | xxd -r -p | sha256sum + { + desc: "RFC6962 Leaf", + want: "395aa064aa4c29f7010acfe3f25db9485bbd4b91897b6ad7ad547639252b4d56"[:tmhash.Size*2], + got: leafHash, + }, + // echo -n 014E3132334E343536 | xxd -r -p | sha256sum + { + desc: "RFC6962 Node", + want: "aa217fe888e47007fa15edab33c2b492a722cb106c64667fc2b044444de66bbb"[:tmhash.Size*2], + got: innerHash([]byte("N123"), []byte("N456")), + }, + } { + t.Run(tc.desc, func(t *testing.T) { + wantBytes, err := hex.DecodeString(tc.want) + if err != nil { + t.Fatalf("hex.DecodeString(%x): %v", tc.want, err) + } + if got, want := tc.got, wantBytes; !bytes.Equal(got, want) { + t.Errorf("got %x, want %x", got, want) + } + }) + } +} + +func TestRFC6962HasherCollisions(t *testing.T) { + // Check that different leaves have different hashes. + leaf1, leaf2 := []byte("Hello"), []byte("World") + _, leafHashTrail := trailsFromByteSlices([][]byte{leaf1}) + hash1 := leafHashTrail.Hash + _, leafHashTrail = trailsFromByteSlices([][]byte{leaf2}) + hash2 := leafHashTrail.Hash + if bytes.Equal(hash1, hash2) { + t.Errorf("Leaf hashes should differ, but both are %x", hash1) + } + // Compute an intermediate subtree hash. + _, subHash1Trail := trailsFromByteSlices([][]byte{hash1, hash2}) + subHash1 := subHash1Trail.Hash + // Check that this is not the same as a leaf hash of their concatenation. + preimage := append(hash1, hash2...) + _, forgedHashTrail := trailsFromByteSlices([][]byte{preimage}) + forgedHash := forgedHashTrail.Hash + if bytes.Equal(subHash1, forgedHash) { + t.Errorf("Hasher is not second-preimage resistant") + } + // Swap the order of nodes and check that the hash is different. + _, subHash2Trail := trailsFromByteSlices([][]byte{hash2, hash1}) + subHash2 := subHash2Trail.Hash + if bytes.Equal(subHash1, subHash2) { + t.Errorf("Subtree hash does not depend on the order of leaves") + } +} diff --git a/crypto/merkle/simple_map_test.go b/crypto/merkle/simple_map_test.go index 7abde119..366d9f39 100644 --- a/crypto/merkle/simple_map_test.go +++ b/crypto/merkle/simple_map_test.go @@ -13,14 +13,14 @@ func TestSimpleMap(t *testing.T) { values []string // each string gets converted to []byte in test want string }{ - {[]string{"key1"}, []string{"value1"}, "321d150de16dceb51c72981b432b115045383259b1a550adf8dc80f927508967"}, - {[]string{"key1"}, []string{"value2"}, "2a9e4baf321eac99f6eecc3406603c14bc5e85bb7b80483cbfc75b3382d24a2f"}, + {[]string{"key1"}, []string{"value1"}, "a44d3cc7daba1a4600b00a2434b30f8b970652169810d6dfa9fb1793a2189324"}, + {[]string{"key1"}, []string{"value2"}, "0638e99b3445caec9d95c05e1a3fc1487b4ddec6a952ff337080360b0dcc078c"}, // swap order with 2 keys - {[]string{"key1", "key2"}, []string{"value1", "value2"}, "c4d8913ab543ba26aa970646d4c99a150fd641298e3367cf68ca45fb45a49881"}, - {[]string{"key2", "key1"}, []string{"value2", "value1"}, "c4d8913ab543ba26aa970646d4c99a150fd641298e3367cf68ca45fb45a49881"}, + {[]string{"key1", "key2"}, []string{"value1", "value2"}, "8fd19b19e7bb3f2b3ee0574027d8a5a4cec370464ea2db2fbfa5c7d35bb0cff3"}, + {[]string{"key2", "key1"}, []string{"value2", "value1"}, "8fd19b19e7bb3f2b3ee0574027d8a5a4cec370464ea2db2fbfa5c7d35bb0cff3"}, // swap order with 3 keys - {[]string{"key1", "key2", "key3"}, []string{"value1", "value2", "value3"}, "b23cef00eda5af4548a213a43793f2752d8d9013b3f2b64bc0523a4791196268"}, - {[]string{"key1", "key3", "key2"}, []string{"value1", "value3", "value2"}, "b23cef00eda5af4548a213a43793f2752d8d9013b3f2b64bc0523a4791196268"}, + {[]string{"key1", "key2", "key3"}, []string{"value1", "value2", "value3"}, "1dd674ec6782a0d586a903c9c63326a41cbe56b3bba33ed6ff5b527af6efb3dc"}, + {[]string{"key1", "key3", "key2"}, []string{"value1", "value3", "value2"}, "1dd674ec6782a0d586a903c9c63326a41cbe56b3bba33ed6ff5b527af6efb3dc"}, } for i, tc := range tests { db := newSimpleMap() diff --git a/crypto/merkle/simple_proof.go b/crypto/merkle/simple_proof.go index fd6d07b8..f01dcdca 100644 --- a/crypto/merkle/simple_proof.go +++ b/crypto/merkle/simple_proof.go @@ -5,7 +5,6 @@ import ( "errors" "fmt" - "github.com/tendermint/tendermint/crypto/tmhash" cmn "github.com/tendermint/tendermint/libs/common" ) @@ -67,7 +66,8 @@ func SimpleProofsFromMap(m map[string][]byte) (rootHash []byte, proofs map[strin // Verify that the SimpleProof proves the root hash. // Check sp.Index/sp.Total manually if needed -func (sp *SimpleProof) Verify(rootHash []byte, leafHash []byte) error { +func (sp *SimpleProof) Verify(rootHash []byte, leaf []byte) error { + leafHash := leafHash(leaf) if sp.Total < 0 { return errors.New("Proof total must be positive") } @@ -128,19 +128,19 @@ func computeHashFromAunts(index int, total int, leafHash []byte, innerHashes [][ if len(innerHashes) == 0 { return nil } - numLeft := (total + 1) / 2 + numLeft := getSplitPoint(total) if index < numLeft { leftHash := computeHashFromAunts(index, numLeft, leafHash, innerHashes[:len(innerHashes)-1]) if leftHash == nil { return nil } - return simpleHashFromTwoHashes(leftHash, innerHashes[len(innerHashes)-1]) + return innerHash(leftHash, innerHashes[len(innerHashes)-1]) } rightHash := computeHashFromAunts(index-numLeft, total-numLeft, leafHash, innerHashes[:len(innerHashes)-1]) if rightHash == nil { return nil } - return simpleHashFromTwoHashes(innerHashes[len(innerHashes)-1], rightHash) + return innerHash(innerHashes[len(innerHashes)-1], rightHash) } } @@ -182,12 +182,13 @@ func trailsFromByteSlices(items [][]byte) (trails []*SimpleProofNode, root *Simp case 0: return nil, nil case 1: - trail := &SimpleProofNode{tmhash.Sum(items[0]), nil, nil, nil} + trail := &SimpleProofNode{leafHash(items[0]), nil, nil, nil} return []*SimpleProofNode{trail}, trail default: - lefts, leftRoot := trailsFromByteSlices(items[:(len(items)+1)/2]) - rights, rightRoot := trailsFromByteSlices(items[(len(items)+1)/2:]) - rootHash := simpleHashFromTwoHashes(leftRoot.Hash, rightRoot.Hash) + k := getSplitPoint(len(items)) + lefts, leftRoot := trailsFromByteSlices(items[:k]) + rights, rightRoot := trailsFromByteSlices(items[k:]) + rootHash := innerHash(leftRoot.Hash, rightRoot.Hash) root := &SimpleProofNode{rootHash, nil, nil, nil} leftRoot.Parent = root leftRoot.Right = rightRoot diff --git a/crypto/merkle/simple_tree.go b/crypto/merkle/simple_tree.go index 7aacb088..e150c0d3 100644 --- a/crypto/merkle/simple_tree.go +++ b/crypto/merkle/simple_tree.go @@ -1,23 +1,9 @@ package merkle import ( - "github.com/tendermint/tendermint/crypto/tmhash" + "math/bits" ) -// simpleHashFromTwoHashes is the basic operation of the Merkle tree: Hash(left | right). -func simpleHashFromTwoHashes(left, right []byte) []byte { - var hasher = tmhash.New() - err := encodeByteSlice(hasher, left) - if err != nil { - panic(err) - } - err = encodeByteSlice(hasher, right) - if err != nil { - panic(err) - } - return hasher.Sum(nil) -} - // SimpleHashFromByteSlices computes a Merkle tree where the leaves are the byte slice, // in the provided order. func SimpleHashFromByteSlices(items [][]byte) []byte { @@ -25,11 +11,12 @@ func SimpleHashFromByteSlices(items [][]byte) []byte { case 0: return nil case 1: - return tmhash.Sum(items[0]) + return leafHash(items[0]) default: - left := SimpleHashFromByteSlices(items[:(len(items)+1)/2]) - right := SimpleHashFromByteSlices(items[(len(items)+1)/2:]) - return simpleHashFromTwoHashes(left, right) + k := getSplitPoint(len(items)) + left := SimpleHashFromByteSlices(items[:k]) + right := SimpleHashFromByteSlices(items[k:]) + return innerHash(left, right) } } @@ -44,3 +31,16 @@ func SimpleHashFromMap(m map[string][]byte) []byte { } return sm.Hash() } + +func getSplitPoint(length int) int { + if length < 1 { + panic("Trying to split a tree with size < 1") + } + uLength := uint(length) + bitlen := bits.Len(uLength) + k := 1 << uint(bitlen-1) + if k == length { + k >>= 1 + } + return k +} diff --git a/crypto/merkle/simple_tree_test.go b/crypto/merkle/simple_tree_test.go index 32edc652..9abe321c 100644 --- a/crypto/merkle/simple_tree_test.go +++ b/crypto/merkle/simple_tree_test.go @@ -34,7 +34,6 @@ func TestSimpleProof(t *testing.T) { // For each item, check the trail. for i, item := range items { - itemHash := tmhash.Sum(item) proof := proofs[i] // Check total/index @@ -43,30 +42,53 @@ func TestSimpleProof(t *testing.T) { require.Equal(t, proof.Total, total, "Unmatched totals: %d vs %d", proof.Total, total) // Verify success - err := proof.Verify(rootHash, itemHash) - require.NoError(t, err, "Verificatior failed: %v.", err) + err := proof.Verify(rootHash, item) + require.NoError(t, err, "Verification failed: %v.", err) // Trail too long should make it fail origAunts := proof.Aunts proof.Aunts = append(proof.Aunts, cmn.RandBytes(32)) - err = proof.Verify(rootHash, itemHash) + err = proof.Verify(rootHash, item) require.Error(t, err, "Expected verification to fail for wrong trail length") proof.Aunts = origAunts // Trail too short should make it fail proof.Aunts = proof.Aunts[0 : len(proof.Aunts)-1] - err = proof.Verify(rootHash, itemHash) + err = proof.Verify(rootHash, item) require.Error(t, err, "Expected verification to fail for wrong trail length") proof.Aunts = origAunts // Mutating the itemHash should make it fail. - err = proof.Verify(rootHash, MutateByteSlice(itemHash)) + err = proof.Verify(rootHash, MutateByteSlice(item)) require.Error(t, err, "Expected verification to fail for mutated leaf hash") // Mutating the rootHash should make it fail. - err = proof.Verify(MutateByteSlice(rootHash), itemHash) + err = proof.Verify(MutateByteSlice(rootHash), item) require.Error(t, err, "Expected verification to fail for mutated root hash") } } + +func Test_getSplitPoint(t *testing.T) { + tests := []struct { + length int + want int + }{ + {1, 0}, + {2, 1}, + {3, 2}, + {4, 2}, + {5, 4}, + {10, 8}, + {20, 16}, + {100, 64}, + {255, 128}, + {256, 128}, + {257, 256}, + } + for _, tt := range tests { + got := getSplitPoint(tt.length) + require.Equal(t, tt.want, got, "getSplitPoint(%d) = %v, want %v", tt.length, got, tt.want) + } +} diff --git a/docs/spec/blockchain/encoding.md b/docs/spec/blockchain/encoding.md index 689ebbd6..aefe1e7f 100644 --- a/docs/spec/blockchain/encoding.md +++ b/docs/spec/blockchain/encoding.md @@ -144,12 +144,17 @@ func MakeParts(obj interface{}, partSize int) []Part For an overview of Merkle trees, see [wikipedia](https://en.wikipedia.org/wiki/Merkle_tree) -A Simple Tree is a simple compact binary tree for a static list of items. Simple Merkle trees are used in numerous places in Tendermint to compute a cryptographic digest of a data structure. In a Simple Tree, the transactions and validation signatures of a block are hashed using this simple merkle tree logic. +We use the RFC 6962 specification of a merkle tree, instantiated with sha256 as the hash function. +Merkle trees are used throughout Tendermint to compute a cryptographic digest of a data structure. +The differences between RFC 6962 and the simplest form a merkle tree are that: -If the number of items is not a power of two, the tree will not be full -and some leaf nodes will be at different levels. Simple Tree tries to -keep both sides of the tree the same size, but the left side may be one -greater, for example: +1) leaf nodes and inner nodes have different hashes. + This is to prevent a proof to an inner node, claiming that it is the hash of the leaf. + The leaf nodes are `SHA256(0x00 || leaf_data)`, and inner nodes are `SHA256(0x01 || left_hash || right_hash)`. + +2) When the number of items isn't a power of two, the left half of the tree is as big as it could be. + (The smallest power of two less than the number of items) This allows new leaves to be added with less + recomputation. For example: ``` Simple Tree with 6 items Simple Tree with 7 items @@ -163,48 +168,31 @@ greater, for example: / \ / \ / \ / \ / \ / \ / \ / \ / \ / \ / \ / \ - * h2 * h5 * * * h6 - / \ / \ / \ / \ / \ -h0 h1 h3 h4 h0 h1 h2 h3 h4 h5 -``` - -Tendermint always uses the `TMHASH` hash function, which is equivalent to -SHA256: - -``` -func TMHASH(bz []byte) []byte { - return SHA256(bz) -} + * * h4 h5 * * * h6 + / \ / \ / \ / \ / \ +h0 h1 h2 h3 h0 h1 h2 h3 h4 h5 ``` ### Simple Merkle Root -The function `SimpleMerkleRoot` is a simple recursive function defined as follows: +The function `MerkleRoot` is a simple recursive function defined as follows: ```go -func SimpleMerkleRoot(hashes [][]byte) []byte{ - switch len(hashes) { - case 0: - return nil - case 1: - return hashes[0] - default: - left := SimpleMerkleRoot(hashes[:(len(hashes)+1)/2]) - right := SimpleMerkleRoot(hashes[(len(hashes)+1)/2:]) - return SimpleConcatHash(left, right) - } -} - -func SimpleConcatHash(left, right []byte) []byte{ - left = encodeByteSlice(left) - right = encodeByteSlice(right) - return TMHASH(append(left, right)) +func MerkleRootFromLeafs(leafs [][]byte) []byte{ + switch len(items) { + case 0: + return nil + case 1: + return leafHash(leafs[0]) // SHA256(0x00 || leafs[0]) + default: + k := getSplitPoint(len(items)) // largest power of two smaller than items + left := MerkleRootFromLeafs(items[:k]) + right := MerkleRootFromLeafs(items[k:]) + return innerHash(left, right) // SHA256(0x01 || left || right) + } } ``` -Note that the leaves are Amino encoded as byte-arrays (ie. simple Uvarint length -prefix) before being concatenated together and hashed. - Note: we will abuse notion and invoke `SimpleMerkleRoot` with arguments of type `struct` or type `[]struct`. For `struct` arguments, we compute a `[][]byte` containing the hash of each field in the struct, in the same order the fields appear in the struct. @@ -214,7 +202,7 @@ For `[]struct` arguments, we compute a `[][]byte` by hashing the individual `str Proof that a leaf is in a Merkle tree consists of a simple structure: -``` +```golang type SimpleProof struct { Aunts [][]byte } @@ -222,7 +210,7 @@ type SimpleProof struct { Which is verified using the following: -``` +```golang func (proof SimpleProof) Verify(index, total int, leafHash, rootHash []byte) bool { computedHash := computeHashFromAunts(index, total, leafHash, proof.Aunts) return computedHash == rootHash @@ -238,7 +226,7 @@ func computeHashFromAunts(index, total int, leafHash []byte, innerHashes [][]byt assert(len(innerHashes) > 0) - numLeft := (total + 1) / 2 + numLeft := getSplitPoint(total) // largest power of 2 less than total if index < numLeft { leftHash := computeHashFromAunts(index, numLeft, leafHash, innerHashes[:len(innerHashes)-1]) assert(leftHash != nil) diff --git a/lite/proxy/query_test.go b/lite/proxy/query_test.go index 0e30d755..d8d45df3 100644 --- a/lite/proxy/query_test.go +++ b/lite/proxy/query_test.go @@ -10,6 +10,7 @@ import ( "github.com/stretchr/testify/require" "github.com/tendermint/tendermint/abci/example/kvstore" + "github.com/tendermint/tendermint/crypto/merkle" "github.com/tendermint/tendermint/lite" certclient "github.com/tendermint/tendermint/lite/client" nm "github.com/tendermint/tendermint/node" @@ -143,12 +144,13 @@ func TestTxProofs(t *testing.T) { require.NotNil(err) require.Contains(err.Error(), "not found") - // Now let's check with the real tx hash. + // Now let's check with the real tx root hash. key = types.Tx(tx).Hash() res, err = cl.Tx(key, true) require.NoError(err, "%#v", err) require.NotNil(res) - err = res.Proof.Validate(key) + keyHash := merkle.SimpleHashFromByteSlices([][]byte{key}) + err = res.Proof.Validate(keyHash) assert.NoError(err, "%#v", err) commit, err := GetCertifiedCommit(br.Height, cl, cert) diff --git a/types/part_set.go b/types/part_set.go index 7e1aa3c3..3c1c8b29 100644 --- a/types/part_set.go +++ b/types/part_set.go @@ -9,7 +9,6 @@ import ( "github.com/pkg/errors" "github.com/tendermint/tendermint/crypto/merkle" - "github.com/tendermint/tendermint/crypto/tmhash" cmn "github.com/tendermint/tendermint/libs/common" ) @@ -27,16 +26,6 @@ type Part struct { hash []byte } -func (part *Part) Hash() []byte { - if part.hash != nil { - return part.hash - } - hasher := tmhash.New() - hasher.Write(part.Bytes) // nolint: errcheck, gas - part.hash = hasher.Sum(nil) - return part.hash -} - // ValidateBasic performs basic validation. func (part *Part) ValidateBasic() error { if part.Index < 0 { @@ -217,7 +206,7 @@ func (ps *PartSet) AddPart(part *Part) (bool, error) { } // Check hash proof - if part.Proof.Verify(ps.Hash(), part.Hash()) != nil { + if part.Proof.Verify(ps.Hash(), part.Bytes) != nil { return false, ErrPartSetInvalidProof } diff --git a/types/results_test.go b/types/results_test.go index 4e57e580..def042d5 100644 --- a/types/results_test.go +++ b/types/results_test.go @@ -38,7 +38,7 @@ func TestABCIResults(t *testing.T) { for i, res := range results { proof := results.ProveResult(i) - valid := proof.Verify(root, res.Hash()) + valid := proof.Verify(root, res.Bytes()) assert.NoError(t, valid, "%d", i) } } diff --git a/types/tx.go b/types/tx.go index 41be7794..87d387a0 100644 --- a/types/tx.go +++ b/types/tx.go @@ -31,13 +31,14 @@ func (tx Tx) String() string { // Txs is a slice of Tx. type Txs []Tx -// Hash returns the simple Merkle root hash of the transactions. +// Hash returns the Merkle root hash of the transaction hashes. +// i.e. the leaves of the tree are the hashes of the txs. func (txs Txs) Hash() []byte { // These allocations will be removed once Txs is switched to [][]byte, // ref #2603. This is because golang does not allow type casting slices without unsafe txBzs := make([][]byte, len(txs)) for i := 0; i < len(txs); i++ { - txBzs[i] = txs[i] + txBzs[i] = txs[i].Hash() } return merkle.SimpleHashFromByteSlices(txBzs) } @@ -69,7 +70,7 @@ func (txs Txs) Proof(i int) TxProof { l := len(txs) bzs := make([][]byte, l) for i := 0; i < l; i++ { - bzs[i] = txs[i] + bzs[i] = txs[i].Hash() } root, proofs := merkle.SimpleProofsFromByteSlices(bzs) @@ -87,8 +88,8 @@ type TxProof struct { Proof merkle.SimpleProof } -// LeadHash returns the hash of the this proof refers to. -func (tp TxProof) LeafHash() []byte { +// Leaf returns the hash(tx), which is the leaf in the merkle tree which this proof refers to. +func (tp TxProof) Leaf() []byte { return tp.Data.Hash() } @@ -104,7 +105,7 @@ func (tp TxProof) Validate(dataHash []byte) error { if tp.Proof.Total <= 0 { return errors.New("Proof total must be positive") } - valid := tp.Proof.Verify(tp.RootHash, tp.LeafHash()) + valid := tp.Proof.Verify(tp.RootHash, tp.Leaf()) if valid != nil { return errors.New("Proof is not internally consistent") } diff --git a/types/tx_test.go b/types/tx_test.go index 5cdadce5..511f4c3a 100644 --- a/types/tx_test.go +++ b/types/tx_test.go @@ -66,14 +66,13 @@ func TestValidTxProof(t *testing.T) { root := txs.Hash() // make sure valid proof for every tx for i := range txs { - leaf := txs[i] - leafHash := leaf.Hash() + tx := []byte(txs[i]) proof := txs.Proof(i) assert.Equal(t, i, proof.Proof.Index, "%d: %d", h, i) assert.Equal(t, len(txs), proof.Proof.Total, "%d: %d", h, i) assert.EqualValues(t, root, proof.RootHash, "%d: %d", h, i) - assert.EqualValues(t, leaf, proof.Data, "%d: %d", h, i) - assert.EqualValues(t, leafHash, proof.LeafHash(), "%d: %d", h, i) + assert.EqualValues(t, tx, proof.Data, "%d: %d", h, i) + assert.EqualValues(t, txs[i].Hash(), proof.Leaf(), "%d: %d", h, i) assert.Nil(t, proof.Validate(root), "%d: %d", h, i) assert.NotNil(t, proof.Validate([]byte("foobar")), "%d: %d", h, i) From 5f93220c614c349d0018bb6728da31aa37cb24e3 Mon Sep 17 00:00:00 2001 From: Thane Thomson Date: Mon, 14 Jan 2019 11:41:09 +0200 Subject: [PATCH 189/267] Adds tests for Unix sockets As per #3115, adds simple Unix socket connect/accept deadline tests in pretty much the same way as the TCP connect/accept deadline tests work. --- privval/socket_test.go | 88 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 88 insertions(+) diff --git a/privval/socket_test.go b/privval/socket_test.go index 0c05fa3a..808a57ae 100644 --- a/privval/socket_test.go +++ b/privval/socket_test.go @@ -1,7 +1,9 @@ package privval import ( + "io/ioutil" "net" + "os" "testing" "time" @@ -82,3 +84,89 @@ func TestTCPListenerConnDeadline(t *testing.T) { close(readyc) <-donec } + +// testUnixAddr will attempt to obtain a platform-independent temporary file +// name for a Unix socket +func testUnixAddr() (string, error) { + f, err := ioutil.TempFile("", "tendermint-privval-test") + if err != nil { + return "", err + } + addr := f.Name() + f.Close() + os.Remove(addr) + return addr, nil +} + +func TestUnixListenerAcceptDeadline(t *testing.T) { + addr, err := testUnixAddr() + if err != nil { + t.Fatal(err) + } + ln, err := net.Listen("unix", addr) + if err != nil { + t.Fatal(err) + } + + unixLn := NewUnixListener(ln) + UnixListenerAcceptDeadline(time.Millisecond)(unixLn) + UnixListenerConnDeadline(time.Second)(unixLn) + + _, err = unixLn.Accept() + opErr, ok := err.(*net.OpError) + if !ok { + t.Fatalf("have %v, want *net.OpError", err) + } + + if have, want := opErr.Op, "accept"; have != want { + t.Errorf("have %v, want %v", have, want) + } +} + +func TestUnixListenerConnDeadline(t *testing.T) { + addr, err := testUnixAddr() + if err != nil { + t.Fatal(err) + } + ln, err := net.Listen("unix", addr) + if err != nil { + t.Fatal(err) + } + + unixLn := NewUnixListener(ln) + UnixListenerAcceptDeadline(time.Second)(unixLn) + UnixListenerConnDeadline(time.Millisecond)(unixLn) + + readyc := make(chan struct{}) + donec := make(chan struct{}) + go func(ln net.Listener) { + defer close(donec) + + c, err := ln.Accept() + if err != nil { + t.Fatal(err) + } + <-readyc + + time.Sleep(2 * time.Millisecond) + + msg := make([]byte, 200) + _, err = c.Read(msg) + opErr, ok := err.(*net.OpError) + if !ok { + t.Fatalf("have %v, want *net.OpError", err) + } + + if have, want := opErr.Op, "read"; have != want { + t.Errorf("have %v, want %v", have, want) + } + }(unixLn) + + dialer := DialUnixFn(addr) + _, err = dialer() + if err != nil { + t.Fatal(err) + } + close(readyc) + <-donec +} From 7b2c4bb4938f237820a117499fe58b76b1e4bfe0 Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Mon, 14 Jan 2019 11:53:43 -0500 Subject: [PATCH 190/267] update ADR-020 (#3116) --- docs/architecture/adr-020-block-size.md | 29 ++++++++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/docs/architecture/adr-020-block-size.md b/docs/architecture/adr-020-block-size.md index aebf3069..39385789 100644 --- a/docs/architecture/adr-020-block-size.md +++ b/docs/architecture/adr-020-block-size.md @@ -7,6 +7,7 @@ 28-08-2018: Third version after Ethan's comments 30-08-2018: AminoOverheadForBlock => MaxAminoOverheadForBlock 31-08-2018: Bounding evidence and chain ID +13-01-2019: Add section on MaxBytes vs MaxDataBytes ## Context @@ -20,6 +21,32 @@ We should just remove MaxTxs all together and stick with MaxBytes, and have a But we can't just reap BlockSize.MaxBytes, since MaxBytes is for the entire block, not for the txs inside the block. There's extra amino overhead + the actual headers on top of the actual transactions + evidence + last commit. +We could also consider using a MaxDataBytes instead of or in addition to MaxBytes. + +## MaxBytes vs MaxDataBytes + +The [PR #3045](https://github.com/tendermint/tendermint/pull/3045) suggested +additional clarity/justification was necessary here, wither respect to the use +of MaxDataBytes in addition to, or instead of, MaxBytes. + +MaxBytes provides a clear limit on the total size of a block that requires no +additional calculation if you want to use it to bound resource usage, and there +has been considerable discussions about optimizing tendermint around 1MB blocks. +Regardless, we need some maximum on the size of a block so we can avoid +unmarshalling blocks that are too big during the consensus, and it seems more +straightforward to provide a single fixed number for this rather than a +computation of "MaxDataBytes + everything else you need to make room for +(signatures, evidence, header)". MaxBytes provides a simple bound so we can +always say "blocks are less than X MB". + +Having both MaxBytes and MaxDataBytes feels like unnecessary complexity. It's +not particularly surprising for MaxBytes to imply the maximum size of the +entire block (not just txs), one just has to know that a block includes header, +txs, evidence, votes. For more fine grained control over the txs included in the +block, there is the MaxGas. In practice, the MaxGas may be expected to do most of +the tx throttling, and the MaxBytes to just serve as an upper bound on the total +size. Applications can use MaxGas as a MaxDataBytes by just taking the gas for +every tx to be its size in bytes. ## Proposed solution @@ -61,7 +88,7 @@ MaxXXX stayed the same. ## Status -Proposed. +Accepted. ## Consequences From 5f4d8e031e2ae6f3abb70e754aacf02c57ac84f0 Mon Sep 17 00:00:00 2001 From: Anton Kaliaev Date: Mon, 14 Jan 2019 23:10:13 +0400 Subject: [PATCH 191/267] [log] fix year format (#3125) Refs #3060 --- CHANGELOG_PENDING.md | 3 ++- libs/log/tmfmt_logger.go | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/CHANGELOG_PENDING.md b/CHANGELOG_PENDING.md index 8012832f..0d3c4d6d 100644 --- a/CHANGELOG_PENDING.md +++ b/CHANGELOG_PENDING.md @@ -7,7 +7,7 @@ Special thanks to external contributors on this release: ### BREAKING CHANGES: * CLI/RPC/Config -- [types] consistent field order of `CanonicalVote` and `CanonicalProposal` +- [types] consistent field order of `CanonicalVote` and `CanonicalProposal` * Apps @@ -23,3 +23,4 @@ Special thanks to external contributors on this release: ### IMPROVEMENTS: ### BUG FIXES: +- [log] \#3060 fix year format diff --git a/libs/log/tmfmt_logger.go b/libs/log/tmfmt_logger.go index 247ce8fc..d841263e 100644 --- a/libs/log/tmfmt_logger.go +++ b/libs/log/tmfmt_logger.go @@ -90,7 +90,7 @@ func (l tmfmtLogger) Log(keyvals ...interface{}) error { // D - first character of the level, uppercase (ASCII only) // [2016-05-02|11:06:44.322] - our time format (see https://golang.org/src/time/format.go) // Stopping ... - message - enc.buf.WriteString(fmt.Sprintf("%c[%s] %-44s ", lvl[0]-32, time.Now().Format("2016-01-02|15:04:05.000"), msg)) + enc.buf.WriteString(fmt.Sprintf("%c[%s] %-44s ", lvl[0]-32, time.Now().Format("2006-01-02|15:04:05.000"), msg)) if module != unknown { enc.buf.WriteString("module=" + module + " ") From bc00a032c17ba5c0f4a138d0da98b50e36acaa5e Mon Sep 17 00:00:00 2001 From: Anton Kaliaev Date: Tue, 15 Jan 2019 02:33:33 +0400 Subject: [PATCH 192/267] makefile: fix build-docker-localnode target (#3122) cd does not work because it's executed in a subprocess so it has to be either chained by && or ; See https://stackoverflow.com/q/1789594/820520 for more details. Fixes #3058 --- Makefile | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/Makefile b/Makefile index d0f8c439..1250fccb 100644 --- a/Makefile +++ b/Makefile @@ -292,9 +292,7 @@ build-linux: GOOS=linux GOARCH=amd64 $(MAKE) build build-docker-localnode: - cd networks/local - make - cd - + @cd networks/local && make # Run a 4-node testnet locally localnet-start: localnet-stop From 4daca1a634c0d288d5fdab2f0ed8a7103e59c4ad Mon Sep 17 00:00:00 2001 From: Anton Kaliaev Date: Tue, 15 Jan 2019 02:35:31 +0400 Subject: [PATCH 193/267] return maxPerPage (not defaultPerPage) if per_page is greater than max (#3124) it's more user-friendly. Refs #3065 --- CHANGELOG_PENDING.md | 1 + rpc/core/pipe.go | 4 +++- rpc/core/pipe_test.go | 3 +-- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/CHANGELOG_PENDING.md b/CHANGELOG_PENDING.md index 0d3c4d6d..0c52b5f3 100644 --- a/CHANGELOG_PENDING.md +++ b/CHANGELOG_PENDING.md @@ -21,6 +21,7 @@ Special thanks to external contributors on this release: ### FEATURES: ### IMPROVEMENTS: +- [rpc] \#3065 return maxPerPage (100), not defaultPerPage (30) if `per_page` is greater than the max 100. ### BUG FIXES: - [log] \#3060 fix year format diff --git a/rpc/core/pipe.go b/rpc/core/pipe.go index 3d745e6a..23649544 100644 --- a/rpc/core/pipe.go +++ b/rpc/core/pipe.go @@ -149,8 +149,10 @@ func validatePage(page, perPage, totalCount int) int { } func validatePerPage(perPage int) int { - if perPage < 1 || perPage > maxPerPage { + if perPage < 1 { return defaultPerPage + } else if perPage > maxPerPage { + return maxPerPage } return perPage } diff --git a/rpc/core/pipe_test.go b/rpc/core/pipe_test.go index 225e3649..19ed11fc 100644 --- a/rpc/core/pipe_test.go +++ b/rpc/core/pipe_test.go @@ -47,7 +47,6 @@ func TestPaginationPage(t *testing.T) { } func TestPaginationPerPage(t *testing.T) { - cases := []struct { totalCount int perPage int @@ -59,7 +58,7 @@ func TestPaginationPerPage(t *testing.T) { {5, defaultPerPage, defaultPerPage}, {5, maxPerPage - 1, maxPerPage - 1}, {5, maxPerPage, maxPerPage}, - {5, maxPerPage + 1, defaultPerPage}, + {5, maxPerPage + 1, maxPerPage}, } for _, c := range cases { From ca00cd6a78be56884873257ed4bd5f61f3210d5b Mon Sep 17 00:00:00 2001 From: Thane Thomson Date: Tue, 15 Jan 2019 10:14:41 +0200 Subject: [PATCH 194/267] Make privval listener testing generic This cuts out two tests by constructing test cases and iterating through them, rather than having separate sets of tests for TCP and Unix listeners. This is as per the feedback from #3121. --- privval/socket_test.go | 189 ++++++++++++++++------------------------- 1 file changed, 75 insertions(+), 114 deletions(-) diff --git a/privval/socket_test.go b/privval/socket_test.go index 808a57ae..88d9ef8e 100644 --- a/privval/socket_test.go +++ b/privval/socket_test.go @@ -20,74 +20,15 @@ func newPrivKey() ed25519.PrivKeyEd25519 { //------------------------------------------- // tests -func TestTCPListenerAcceptDeadline(t *testing.T) { - ln, err := net.Listen("tcp", "127.0.0.1:0") - if err != nil { - t.Fatal(err) - } - - tcpLn := NewTCPListener(ln, newPrivKey()) - TCPListenerAcceptDeadline(time.Millisecond)(tcpLn) - TCPListenerConnDeadline(time.Second)(tcpLn) - - _, err = tcpLn.Accept() - opErr, ok := err.(*net.OpError) - if !ok { - t.Fatalf("have %v, want *net.OpError", err) - } - - if have, want := opErr.Op, "accept"; have != want { - t.Errorf("have %v, want %v", have, want) - } +type listenerTestCase struct { + description string // For test reporting purposes. + listener net.Listener + dialer Dialer } -func TestTCPListenerConnDeadline(t *testing.T) { - ln, err := net.Listen("tcp", "127.0.0.1:0") - if err != nil { - t.Fatal(err) - } - - tcpLn := NewTCPListener(ln, newPrivKey()) - TCPListenerAcceptDeadline(time.Second)(tcpLn) - TCPListenerConnDeadline(time.Millisecond)(tcpLn) - - readyc := make(chan struct{}) - donec := make(chan struct{}) - go func(ln net.Listener) { - defer close(donec) - - c, err := ln.Accept() - if err != nil { - t.Fatal(err) - } - <-readyc - - time.Sleep(2 * time.Millisecond) - - msg := make([]byte, 200) - _, err = c.Read(msg) - opErr, ok := err.(*net.OpError) - if !ok { - t.Fatalf("have %v, want *net.OpError", err) - } - - if have, want := opErr.Op, "read"; have != want { - t.Errorf("have %v, want %v", have, want) - } - }(tcpLn) - - dialer := DialTCPFn(ln.Addr().String(), testConnDeadline, newPrivKey()) - _, err = dialer() - if err != nil { - t.Fatal(err) - } - close(readyc) - <-donec -} - -// testUnixAddr will attempt to obtain a platform-independent temporary file +// getTestUnixAddr will attempt to obtain a platform-independent temporary file // name for a Unix socket -func testUnixAddr() (string, error) { +func getTestUnixAddr() (string, error) { f, err := ioutil.TempFile("", "tendermint-privval-test") if err != nil { return "", err @@ -98,33 +39,24 @@ func testUnixAddr() (string, error) { return addr, nil } -func TestUnixListenerAcceptDeadline(t *testing.T) { - addr, err := testUnixAddr() - if err != nil { - t.Fatal(err) - } - ln, err := net.Listen("unix", addr) +func constructTCPListenerTestCase(t *testing.T, acceptDeadline, connectDeadline time.Duration) listenerTestCase { + ln, err := net.Listen("tcp", "127.0.0.1:0") if err != nil { t.Fatal(err) } - unixLn := NewUnixListener(ln) - UnixListenerAcceptDeadline(time.Millisecond)(unixLn) - UnixListenerConnDeadline(time.Second)(unixLn) - - _, err = unixLn.Accept() - opErr, ok := err.(*net.OpError) - if !ok { - t.Fatalf("have %v, want *net.OpError", err) - } - - if have, want := opErr.Op, "accept"; have != want { - t.Errorf("have %v, want %v", have, want) + tcpLn := NewTCPListener(ln, newPrivKey()) + TCPListenerAcceptDeadline(acceptDeadline)(tcpLn) + TCPListenerConnDeadline(connectDeadline)(tcpLn) + return listenerTestCase{ + description: "TCP", + listener: tcpLn, + dialer: DialTCPFn(ln.Addr().String(), testConnDeadline, newPrivKey()), } } -func TestUnixListenerConnDeadline(t *testing.T) { - addr, err := testUnixAddr() +func constructUnixListenerTestCase(t *testing.T, acceptDeadline, connectDeadline time.Duration) listenerTestCase { + addr, err := getTestUnixAddr() if err != nil { t.Fatal(err) } @@ -134,39 +66,68 @@ func TestUnixListenerConnDeadline(t *testing.T) { } unixLn := NewUnixListener(ln) - UnixListenerAcceptDeadline(time.Second)(unixLn) - UnixListenerConnDeadline(time.Millisecond)(unixLn) + UnixListenerAcceptDeadline(acceptDeadline)(unixLn) + UnixListenerConnDeadline(connectDeadline)(unixLn) + return listenerTestCase{ + description: "Unix", + listener: unixLn, + dialer: DialUnixFn(addr), + } +} - readyc := make(chan struct{}) - donec := make(chan struct{}) - go func(ln net.Listener) { - defer close(donec) +func constructListenerTestCases(t *testing.T, acceptDeadline, connectDeadline time.Duration) []listenerTestCase { + return []listenerTestCase{ + constructTCPListenerTestCase(t, acceptDeadline, connectDeadline), + constructUnixListenerTestCase(t, acceptDeadline, connectDeadline), + } +} - c, err := ln.Accept() +func TestListenerAcceptDeadlines(t *testing.T) { + for _, tc := range constructListenerTestCases(t, time.Millisecond, time.Second) { + _, err := tc.listener.Accept() + opErr, ok := err.(*net.OpError) + if !ok { + t.Fatalf("for %s listener, have %v, want *net.OpError", tc.description, err) + } + + if have, want := opErr.Op, "accept"; have != want { + t.Errorf("for %s listener, have %v, want %v", tc.description, have, want) + } + } +} + +func TestListenerConnectDeadlines(t *testing.T) { + for _, tc := range constructListenerTestCases(t, time.Second, time.Millisecond) { + readyc := make(chan struct{}) + donec := make(chan struct{}) + go func(ln net.Listener) { + defer close(donec) + + c, err := ln.Accept() + if err != nil { + t.Fatal(err) + } + <-readyc + + time.Sleep(2 * time.Millisecond) + + msg := make([]byte, 200) + _, err = c.Read(msg) + opErr, ok := err.(*net.OpError) + if !ok { + t.Fatalf("for %s listener, have %v, want *net.OpError", tc.description, err) + } + + if have, want := opErr.Op, "read"; have != want { + t.Errorf("for %s listener, have %v, want %v", tc.description, have, want) + } + }(tc.listener) + + _, err := tc.dialer() if err != nil { t.Fatal(err) } - <-readyc - - time.Sleep(2 * time.Millisecond) - - msg := make([]byte, 200) - _, err = c.Read(msg) - opErr, ok := err.(*net.OpError) - if !ok { - t.Fatalf("have %v, want *net.OpError", err) - } - - if have, want := opErr.Op, "read"; have != want { - t.Errorf("have %v, want %v", have, want) - } - }(unixLn) - - dialer := DialUnixFn(addr) - _, err = dialer() - if err != nil { - t.Fatal(err) + close(readyc) + <-donec } - close(readyc) - <-donec } From d1afa0ed6cf3d13be9ddbaf1dc60e2d46a2acf53 Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Tue, 15 Jan 2019 07:55:57 -0500 Subject: [PATCH 195/267] privval: fixes from review (#3126) https://github.com/tendermint/tendermint/pull/2923#pullrequestreview-192065694 --- cmd/priv_val_server/main.go | 2 +- node/node.go | 2 +- privval/client.go | 35 +++++------------------------------ 3 files changed, 7 insertions(+), 32 deletions(-) diff --git a/cmd/priv_val_server/main.go b/cmd/priv_val_server/main.go index 6949e878..768b9cf6 100644 --- a/cmd/priv_val_server/main.go +++ b/cmd/priv_val_server/main.go @@ -45,7 +45,7 @@ func main() { dialer = privval.DialTCPFn(address, connTimeout, ed25519.GenPrivKey()) default: logger.Error("Unknown protocol", "protocol", protocol) - return + os.Exit(1) } rs := privval.NewRemoteSigner(logger, *chainID, pv, dialer) diff --git a/node/node.go b/node/node.go index b7998dac..128714cb 100644 --- a/node/node.go +++ b/node/node.go @@ -901,7 +901,7 @@ func createAndStartPrivValidatorSocketClient( pvsc := privval.NewSocketVal(logger.With("module", "privval"), listener) if err := pvsc.Start(); err != nil { - return nil, errors.Wrap(err, "failed to start") + return nil, errors.Wrap(err, "failed to start private validator") } return pvsc, nil diff --git a/privval/client.go b/privval/client.go index 4d4395fd..1ad104d8 100644 --- a/privval/client.go +++ b/privval/client.go @@ -191,19 +191,19 @@ func (sc *SocketVal) OnStop() { // connection is closed in OnStop. // returns true if the listener is closed // (ie. it returns a nil conn). -func (sc *SocketVal) reset() (bool, error) { +func (sc *SocketVal) reset() (closed bool, err error) { sc.mtx.Lock() defer sc.mtx.Unlock() // first check if the conn already exists and close it. if sc.signer != nil { if err := sc.signer.Close(); err != nil { - sc.Logger.Error("error closing connection", "err", err) + sc.Logger.Error("error closing socket val connection during reset", "err", err) } } // wait for a new conn - conn, err := sc.waitConnection() + conn, err := sc.acceptConnection() if err != nil { return false, err } @@ -224,6 +224,8 @@ func (sc *SocketVal) reset() (bool, error) { return false, nil } +// Attempt to accept a connection. +// Times out after the listener's acceptDeadline func (sc *SocketVal) acceptConnection() (net.Conn, error) { conn, err := sc.listener.Accept() if err != nil { @@ -231,33 +233,6 @@ func (sc *SocketVal) acceptConnection() (net.Conn, error) { return nil, nil // Ignore error from listener closing. } return nil, err - } return conn, nil } - -// waitConnection uses the configured wait timeout to error if no external -// process connects in the time period. -func (sc *SocketVal) waitConnection() (net.Conn, error) { - var ( - connc = make(chan net.Conn, 1) - errc = make(chan error, 1) - ) - - go func(connc chan<- net.Conn, errc chan<- error) { - conn, err := sc.acceptConnection() - if err != nil { - errc <- err - return - } - - connc <- conn - }(connc, errc) - - select { - case conn := <-connc: - return conn, nil - case err := <-errc: - return nil, err - } -} From 73ea5effe5e8ad6dfaf4a3923830d89a35274663 Mon Sep 17 00:00:00 2001 From: Kunal Dhariwal Date: Tue, 15 Jan 2019 18:42:35 +0530 Subject: [PATCH 196/267] docs: update link for rpc docs (#3129) --- docs/app-dev/app-architecture.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/app-dev/app-architecture.md b/docs/app-dev/app-architecture.md index 943a3cd0..b9c8d2e9 100644 --- a/docs/app-dev/app-architecture.md +++ b/docs/app-dev/app-architecture.md @@ -45,6 +45,6 @@ Tendermint. See the following for more extensive documentation: - [Interchain Standard for the Light-Client REST API](https://github.com/cosmos/cosmos-sdk/pull/1028) -- [Tendermint RPC Docs](https://tendermint.github.io/slate/) +- [Tendermint RPC Docs](https://tendermint.com/rpc/) - [Tendermint in Production](../tendermint-core/running-in-production.md) - [ABCI spec](./abci-spec.md) From 3191ee8badde5a815da252dbcd77b641dd417521 Mon Sep 17 00:00:00 2001 From: Thane Thomson Date: Tue, 15 Jan 2019 18:06:57 +0200 Subject: [PATCH 197/267] Dropping "construct" prefix as per #3121 --- privval/socket_test.go | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/privval/socket_test.go b/privval/socket_test.go index 88d9ef8e..dfce45b5 100644 --- a/privval/socket_test.go +++ b/privval/socket_test.go @@ -26,9 +26,9 @@ type listenerTestCase struct { dialer Dialer } -// getTestUnixAddr will attempt to obtain a platform-independent temporary file +// testUnixAddr will attempt to obtain a platform-independent temporary file // name for a Unix socket -func getTestUnixAddr() (string, error) { +func testUnixAddr() (string, error) { f, err := ioutil.TempFile("", "tendermint-privval-test") if err != nil { return "", err @@ -39,7 +39,7 @@ func getTestUnixAddr() (string, error) { return addr, nil } -func constructTCPListenerTestCase(t *testing.T, acceptDeadline, connectDeadline time.Duration) listenerTestCase { +func tcpListenerTestCase(t *testing.T, acceptDeadline, connectDeadline time.Duration) listenerTestCase { ln, err := net.Listen("tcp", "127.0.0.1:0") if err != nil { t.Fatal(err) @@ -55,8 +55,8 @@ func constructTCPListenerTestCase(t *testing.T, acceptDeadline, connectDeadline } } -func constructUnixListenerTestCase(t *testing.T, acceptDeadline, connectDeadline time.Duration) listenerTestCase { - addr, err := getTestUnixAddr() +func unixListenerTestCase(t *testing.T, acceptDeadline, connectDeadline time.Duration) listenerTestCase { + addr, err := testUnixAddr() if err != nil { t.Fatal(err) } @@ -75,15 +75,15 @@ func constructUnixListenerTestCase(t *testing.T, acceptDeadline, connectDeadline } } -func constructListenerTestCases(t *testing.T, acceptDeadline, connectDeadline time.Duration) []listenerTestCase { +func listenerTestCases(t *testing.T, acceptDeadline, connectDeadline time.Duration) []listenerTestCase { return []listenerTestCase{ - constructTCPListenerTestCase(t, acceptDeadline, connectDeadline), - constructUnixListenerTestCase(t, acceptDeadline, connectDeadline), + tcpListenerTestCase(t, acceptDeadline, connectDeadline), + unixListenerTestCase(t, acceptDeadline, connectDeadline), } } func TestListenerAcceptDeadlines(t *testing.T) { - for _, tc := range constructListenerTestCases(t, time.Millisecond, time.Second) { + for _, tc := range listenerTestCases(t, time.Millisecond, time.Second) { _, err := tc.listener.Accept() opErr, ok := err.(*net.OpError) if !ok { @@ -97,7 +97,7 @@ func TestListenerAcceptDeadlines(t *testing.T) { } func TestListenerConnectDeadlines(t *testing.T) { - for _, tc := range constructListenerTestCases(t, time.Second, time.Millisecond) { + for _, tc := range listenerTestCases(t, time.Second, time.Millisecond) { readyc := make(chan struct{}) donec := make(chan struct{}) go func(ln net.Listener) { From dcb8f885256e40a088e0901ee127fe095647cea0 Mon Sep 17 00:00:00 2001 From: Anton Kaliaev Date: Tue, 15 Jan 2019 21:16:33 +0400 Subject: [PATCH 198/267] add "chain_id" label for all metrics (#3123) * add "chain_id" label for all metrics Refs #3082 * fix labels extraction --- CHANGELOG_PENDING.md | 2 ++ consensus/metrics.go | 44 +++++++++++++++++++++++++++----------------- mempool/metrics.go | 30 ++++++++++++++++++++---------- node/node.go | 12 +++++++----- p2p/metrics.go | 24 +++++++++++++++++------- state/metrics.go | 19 ++++++++++++++++--- 6 files changed, 89 insertions(+), 42 deletions(-) diff --git a/CHANGELOG_PENDING.md b/CHANGELOG_PENDING.md index 0c52b5f3..06eb9e37 100644 --- a/CHANGELOG_PENDING.md +++ b/CHANGELOG_PENDING.md @@ -12,6 +12,7 @@ Special thanks to external contributors on this release: * Apps * Go API +- [node] \#3082 MetricsProvider now requires you to pass a chain ID * Blockchain Protocol * [merkle] \#2713 Merkle trees now match the RFC 6962 specification @@ -22,6 +23,7 @@ Special thanks to external contributors on this release: ### IMPROVEMENTS: - [rpc] \#3065 return maxPerPage (100), not defaultPerPage (30) if `per_page` is greater than the max 100. +- [instrumentation] \#3082 add 'chain_id' label for all metrics ### BUG FIXES: - [log] \#3060 fix year format diff --git a/consensus/metrics.go b/consensus/metrics.go index 7b4a3fbc..b5207742 100644 --- a/consensus/metrics.go +++ b/consensus/metrics.go @@ -8,7 +8,11 @@ import ( stdprometheus "github.com/prometheus/client_golang/prometheus" ) -const MetricsSubsystem = "consensus" +const ( + // MetricsSubsystem is a subsystem shared by all metrics exposed by this + // package. + MetricsSubsystem = "consensus" +) // Metrics contains metrics exposed by this package. type Metrics struct { @@ -50,101 +54,107 @@ type Metrics struct { } // PrometheusMetrics returns Metrics build using Prometheus client library. -func PrometheusMetrics(namespace string) *Metrics { +// Optionally, labels can be provided along with their values ("foo", +// "fooValue"). +func PrometheusMetrics(namespace string, labelsAndValues ...string) *Metrics { + labels := []string{} + for i := 0; i < len(labelsAndValues); i += 2 { + labels = append(labels, labelsAndValues[i]) + } return &Metrics{ Height: prometheus.NewGaugeFrom(stdprometheus.GaugeOpts{ Namespace: namespace, Subsystem: MetricsSubsystem, Name: "height", Help: "Height of the chain.", - }, []string{}), + }, labels).With(labelsAndValues...), Rounds: prometheus.NewGaugeFrom(stdprometheus.GaugeOpts{ Namespace: namespace, Subsystem: MetricsSubsystem, Name: "rounds", Help: "Number of rounds.", - }, []string{}), + }, labels).With(labelsAndValues...), Validators: prometheus.NewGaugeFrom(stdprometheus.GaugeOpts{ Namespace: namespace, Subsystem: MetricsSubsystem, Name: "validators", Help: "Number of validators.", - }, []string{}), + }, labels).With(labelsAndValues...), ValidatorsPower: prometheus.NewGaugeFrom(stdprometheus.GaugeOpts{ Namespace: namespace, Subsystem: MetricsSubsystem, Name: "validators_power", Help: "Total power of all validators.", - }, []string{}), + }, labels).With(labelsAndValues...), MissingValidators: prometheus.NewGaugeFrom(stdprometheus.GaugeOpts{ Namespace: namespace, Subsystem: MetricsSubsystem, Name: "missing_validators", Help: "Number of validators who did not sign.", - }, []string{}), + }, labels).With(labelsAndValues...), MissingValidatorsPower: prometheus.NewGaugeFrom(stdprometheus.GaugeOpts{ Namespace: namespace, Subsystem: MetricsSubsystem, Name: "missing_validators_power", Help: "Total power of the missing validators.", - }, []string{}), + }, labels).With(labelsAndValues...), ByzantineValidators: prometheus.NewGaugeFrom(stdprometheus.GaugeOpts{ Namespace: namespace, Subsystem: MetricsSubsystem, Name: "byzantine_validators", Help: "Number of validators who tried to double sign.", - }, []string{}), + }, labels).With(labelsAndValues...), ByzantineValidatorsPower: prometheus.NewGaugeFrom(stdprometheus.GaugeOpts{ Namespace: namespace, Subsystem: MetricsSubsystem, Name: "byzantine_validators_power", Help: "Total power of the byzantine validators.", - }, []string{}), + }, labels).With(labelsAndValues...), BlockIntervalSeconds: prometheus.NewGaugeFrom(stdprometheus.GaugeOpts{ Namespace: namespace, Subsystem: MetricsSubsystem, Name: "block_interval_seconds", Help: "Time between this and the last block.", - }, []string{}), + }, labels).With(labelsAndValues...), NumTxs: prometheus.NewGaugeFrom(stdprometheus.GaugeOpts{ Namespace: namespace, Subsystem: MetricsSubsystem, Name: "num_txs", Help: "Number of transactions.", - }, []string{}), + }, labels).With(labelsAndValues...), BlockSizeBytes: prometheus.NewGaugeFrom(stdprometheus.GaugeOpts{ Namespace: namespace, Subsystem: MetricsSubsystem, Name: "block_size_bytes", Help: "Size of the block.", - }, []string{}), + }, labels).With(labelsAndValues...), TotalTxs: prometheus.NewGaugeFrom(stdprometheus.GaugeOpts{ Namespace: namespace, Subsystem: MetricsSubsystem, Name: "total_txs", Help: "Total number of transactions.", - }, []string{}), + }, labels).With(labelsAndValues...), CommittedHeight: prometheus.NewGaugeFrom(stdprometheus.GaugeOpts{ Namespace: namespace, Subsystem: MetricsSubsystem, Name: "latest_block_height", Help: "The latest block height.", - }, []string{}), + }, labels).With(labelsAndValues...), FastSyncing: prometheus.NewGaugeFrom(stdprometheus.GaugeOpts{ Namespace: namespace, Subsystem: MetricsSubsystem, Name: "fast_syncing", Help: "Whether or not a node is fast syncing. 1 if yes, 0 if no.", - }, []string{}), + }, labels).With(labelsAndValues...), BlockParts: prometheus.NewCounterFrom(stdprometheus.CounterOpts{ Namespace: namespace, Subsystem: MetricsSubsystem, Name: "block_parts", Help: "Number of blockparts transmitted by peer.", - }, []string{"peer_id"}), + }, append(labels, "peer_id")).With(labelsAndValues...), } } diff --git a/mempool/metrics.go b/mempool/metrics.go index 3418f1ef..5e4eaf5e 100644 --- a/mempool/metrics.go +++ b/mempool/metrics.go @@ -7,7 +7,11 @@ import ( stdprometheus "github.com/prometheus/client_golang/prometheus" ) -const MetricsSubsytem = "mempool" +const ( + // MetricsSubsystem is a subsystem shared by all metrics exposed by this + // package. + MetricsSubsystem = "mempool" +) // Metrics contains metrics exposed by this package. // see MetricsProvider for descriptions. @@ -23,33 +27,39 @@ type Metrics struct { } // PrometheusMetrics returns Metrics build using Prometheus client library. -func PrometheusMetrics(namespace string) *Metrics { +// Optionally, labels can be provided along with their values ("foo", +// "fooValue"). +func PrometheusMetrics(namespace string, labelsAndValues ...string) *Metrics { + labels := []string{} + for i := 0; i < len(labelsAndValues); i += 2 { + labels = append(labels, labelsAndValues[i]) + } return &Metrics{ Size: prometheus.NewGaugeFrom(stdprometheus.GaugeOpts{ Namespace: namespace, - Subsystem: MetricsSubsytem, + Subsystem: MetricsSubsystem, Name: "size", Help: "Size of the mempool (number of uncommitted transactions).", - }, []string{}), + }, labels).With(labelsAndValues...), TxSizeBytes: prometheus.NewHistogramFrom(stdprometheus.HistogramOpts{ Namespace: namespace, - Subsystem: MetricsSubsytem, + Subsystem: MetricsSubsystem, Name: "tx_size_bytes", Help: "Transaction sizes in bytes.", Buckets: stdprometheus.ExponentialBuckets(1, 3, 17), - }, []string{}), + }, labels).With(labelsAndValues...), FailedTxs: prometheus.NewCounterFrom(stdprometheus.CounterOpts{ Namespace: namespace, - Subsystem: MetricsSubsytem, + Subsystem: MetricsSubsystem, Name: "failed_txs", Help: "Number of failed transactions.", - }, []string{}), + }, labels).With(labelsAndValues...), RecheckTimes: prometheus.NewCounterFrom(stdprometheus.CounterOpts{ Namespace: namespace, - Subsystem: MetricsSubsytem, + Subsystem: MetricsSubsystem, Name: "recheck_times", Help: "Number of times transactions are rechecked in the mempool.", - }, []string{}), + }, labels).With(labelsAndValues...), } } diff --git a/node/node.go b/node/node.go index b7998dac..46cec300 100644 --- a/node/node.go +++ b/node/node.go @@ -117,15 +117,17 @@ func DefaultNewNode(config *cfg.Config, logger log.Logger) (*Node, error) { } // MetricsProvider returns a consensus, p2p and mempool Metrics. -type MetricsProvider func() (*cs.Metrics, *p2p.Metrics, *mempl.Metrics, *sm.Metrics) +type MetricsProvider func(chainID string) (*cs.Metrics, *p2p.Metrics, *mempl.Metrics, *sm.Metrics) // DefaultMetricsProvider returns Metrics build using Prometheus client library // if Prometheus is enabled. Otherwise, it returns no-op Metrics. func DefaultMetricsProvider(config *cfg.InstrumentationConfig) MetricsProvider { - return func() (*cs.Metrics, *p2p.Metrics, *mempl.Metrics, *sm.Metrics) { + return func(chainID string) (*cs.Metrics, *p2p.Metrics, *mempl.Metrics, *sm.Metrics) { if config.Prometheus { - return cs.PrometheusMetrics(config.Namespace), p2p.PrometheusMetrics(config.Namespace), - mempl.PrometheusMetrics(config.Namespace), sm.PrometheusMetrics(config.Namespace) + return cs.PrometheusMetrics(config.Namespace, "chain_id", chainID), + p2p.PrometheusMetrics(config.Namespace, "chain_id", chainID), + mempl.PrometheusMetrics(config.Namespace, "chain_id", chainID), + sm.PrometheusMetrics(config.Namespace, "chain_id", chainID) } return cs.NopMetrics(), p2p.NopMetrics(), mempl.NopMetrics(), sm.NopMetrics() } @@ -274,7 +276,7 @@ func NewNode(config *cfg.Config, consensusLogger.Info("This node is not a validator", "addr", addr, "pubKey", pubKey) } - csMetrics, p2pMetrics, memplMetrics, smMetrics := metricsProvider() + csMetrics, p2pMetrics, memplMetrics, smMetrics := metricsProvider(genDoc.ChainID) // Make MempoolReactor mempool := mempl.NewMempool( diff --git a/p2p/metrics.go b/p2p/metrics.go index b066fb31..1b90172c 100644 --- a/p2p/metrics.go +++ b/p2p/metrics.go @@ -7,7 +7,11 @@ import ( stdprometheus "github.com/prometheus/client_golang/prometheus" ) -const MetricsSubsystem = "p2p" +const ( + // MetricsSubsystem is a subsystem shared by all metrics exposed by this + // package. + MetricsSubsystem = "p2p" +) // Metrics contains metrics exposed by this package. type Metrics struct { @@ -24,38 +28,44 @@ type Metrics struct { } // PrometheusMetrics returns Metrics build using Prometheus client library. -func PrometheusMetrics(namespace string) *Metrics { +// Optionally, labels can be provided along with their values ("foo", +// "fooValue"). +func PrometheusMetrics(namespace string, labelsAndValues ...string) *Metrics { + labels := []string{} + for i := 0; i < len(labelsAndValues); i += 2 { + labels = append(labels, labelsAndValues[i]) + } return &Metrics{ Peers: prometheus.NewGaugeFrom(stdprometheus.GaugeOpts{ Namespace: namespace, Subsystem: MetricsSubsystem, Name: "peers", Help: "Number of peers.", - }, []string{}), + }, labels).With(labelsAndValues...), PeerReceiveBytesTotal: prometheus.NewCounterFrom(stdprometheus.CounterOpts{ Namespace: namespace, Subsystem: MetricsSubsystem, Name: "peer_receive_bytes_total", Help: "Number of bytes received from a given peer.", - }, []string{"peer_id"}), + }, append(labels, "peer_id")).With(labelsAndValues...), PeerSendBytesTotal: prometheus.NewCounterFrom(stdprometheus.CounterOpts{ Namespace: namespace, Subsystem: MetricsSubsystem, Name: "peer_send_bytes_total", Help: "Number of bytes sent to a given peer.", - }, []string{"peer_id"}), + }, append(labels, "peer_id")).With(labelsAndValues...), PeerPendingSendBytes: prometheus.NewGaugeFrom(stdprometheus.GaugeOpts{ Namespace: namespace, Subsystem: MetricsSubsystem, Name: "peer_pending_send_bytes", Help: "Number of pending bytes to be sent to a given peer.", - }, []string{"peer_id"}), + }, append(labels, "peer_id")).With(labelsAndValues...), NumTxs: prometheus.NewGaugeFrom(stdprometheus.GaugeOpts{ Namespace: namespace, Subsystem: MetricsSubsystem, Name: "num_txs", Help: "Number of transactions submitted by each peer.", - }, []string{"peer_id"}), + }, append(labels, "peer_id")).With(labelsAndValues...), } } diff --git a/state/metrics.go b/state/metrics.go index 4e99753f..bcd713f5 100644 --- a/state/metrics.go +++ b/state/metrics.go @@ -7,14 +7,26 @@ import ( stdprometheus "github.com/prometheus/client_golang/prometheus" ) -const MetricsSubsystem = "state" +const ( + // MetricsSubsystem is a subsystem shared by all metrics exposed by this + // package. + MetricsSubsystem = "state" +) +// Metrics contains metrics exposed by this package. type Metrics struct { // Time between BeginBlock and EndBlock. BlockProcessingTime metrics.Histogram } -func PrometheusMetrics(namespace string) *Metrics { +// PrometheusMetrics returns Metrics build using Prometheus client library. +// Optionally, labels can be provided along with their values ("foo", +// "fooValue"). +func PrometheusMetrics(namespace string, labelsAndValues ...string) *Metrics { + labels := []string{} + for i := 0; i < len(labelsAndValues); i += 2 { + labels = append(labels, labelsAndValues[i]) + } return &Metrics{ BlockProcessingTime: prometheus.NewHistogramFrom(stdprometheus.HistogramOpts{ Namespace: namespace, @@ -22,10 +34,11 @@ func PrometheusMetrics(namespace string) *Metrics { Name: "block_processing_time", Help: "Time between BeginBlock and EndBlock in ms.", Buckets: stdprometheus.LinearBuckets(1, 10, 10), - }, []string{}), + }, labels).With(labelsAndValues...), } } +// NopMetrics returns no-op Metrics. func NopMetrics() *Metrics { return &Metrics{ BlockProcessingTime: discard.NewHistogram(), From d4e67205411c15af756e35eed2240944aa3958b7 Mon Sep 17 00:00:00 2001 From: Thane Thomson Date: Wed, 16 Jan 2019 17:05:34 +0200 Subject: [PATCH 199/267] Expanding tests to cover Unix sockets version of client (#3132) * Adds a random suffix to temporary Unix sockets during testing * Adds Unix domain socket tests for client (FAILING) This adds Unix domain socket tests for the privval client. Right now, one of the tests (TestRemoteSignerRetry) fails, probably because the Unix domain socket state is known instantaneously on both sides by the OS. Committing this to collaborate on the error. * Removes extraneous logging * Completes testing of Unix sockets client version This completes the testing of the client connecting via Unix sockets. There are two specific tests (TestSocketPVDeadline and TestRemoteSignerRetryTCPOnly) that are only relevant to TCP connections. * Renames test to show TCP-specificity * Adds testing into closures for consistency (forgot previously) * Moves test specific to RemoteSigner into own file As per discussion on #3132, `TestRemoteSignerRetryTCPOnly` doesn't really belong with the client tests. This moves it into its own file related to the `RemoteSigner` class. --- privval/client_test.go | 533 ++++++++++++++++++---------------- privval/remote_signer_test.go | 68 +++++ privval/socket.go | 2 +- privval/socket_test.go | 2 +- 4 files changed, 350 insertions(+), 255 deletions(-) create mode 100644 privval/remote_signer_test.go diff --git a/privval/client_test.go b/privval/client_test.go index 7fae6bf8..3c327064 100644 --- a/privval/client_test.go +++ b/privval/client_test.go @@ -27,120 +27,170 @@ var ( testHeartbeatTimeout3o2 = 6 * time.Millisecond // 3/2 of the other one ) +type socketTestCase struct { + addr string + dialer Dialer +} + +func socketTestCases(t *testing.T) []socketTestCase { + tcpAddr := fmt.Sprintf("tcp://%s", testFreeTCPAddr(t)) + unixFilePath, err := testUnixAddr() + require.NoError(t, err) + unixAddr := fmt.Sprintf("unix://%s", unixFilePath) + return []socketTestCase{ + socketTestCase{ + addr: tcpAddr, + dialer: DialTCPFn(tcpAddr, testConnDeadline, ed25519.GenPrivKey()), + }, + socketTestCase{ + addr: unixAddr, + dialer: DialUnixFn(unixFilePath), + }, + } +} + func TestSocketPVAddress(t *testing.T) { - var ( - chainID = cmn.RandStr(12) - sc, rs = testSetupSocketPair(t, chainID, types.NewMockPV()) - ) - defer sc.Stop() - defer rs.Stop() + for _, tc := range socketTestCases(t) { + // Execute the test within a closure to ensure the deferred statements + // are called between each for loop iteration, for isolated test cases. + func() { + var ( + chainID = cmn.RandStr(12) + sc, rs = testSetupSocketPair(t, chainID, types.NewMockPV(), tc.addr, tc.dialer) + ) + defer sc.Stop() + defer rs.Stop() - serverAddr := rs.privVal.GetPubKey().Address() - clientAddr := sc.GetPubKey().Address() + serverAddr := rs.privVal.GetPubKey().Address() + clientAddr := sc.GetPubKey().Address() - assert.Equal(t, serverAddr, clientAddr) + assert.Equal(t, serverAddr, clientAddr) + }() + } } func TestSocketPVPubKey(t *testing.T) { - var ( - chainID = cmn.RandStr(12) - sc, rs = testSetupSocketPair(t, chainID, types.NewMockPV()) - ) - defer sc.Stop() - defer rs.Stop() + for _, tc := range socketTestCases(t) { + func() { + var ( + chainID = cmn.RandStr(12) + sc, rs = testSetupSocketPair(t, chainID, types.NewMockPV(), tc.addr, tc.dialer) + ) + defer sc.Stop() + defer rs.Stop() - clientKey := sc.GetPubKey() + clientKey := sc.GetPubKey() - privvalPubKey := rs.privVal.GetPubKey() + privvalPubKey := rs.privVal.GetPubKey() - assert.Equal(t, privvalPubKey, clientKey) + assert.Equal(t, privvalPubKey, clientKey) + }() + } } func TestSocketPVProposal(t *testing.T) { - var ( - chainID = cmn.RandStr(12) - sc, rs = testSetupSocketPair(t, chainID, types.NewMockPV()) + for _, tc := range socketTestCases(t) { + func() { + var ( + chainID = cmn.RandStr(12) + sc, rs = testSetupSocketPair(t, chainID, types.NewMockPV(), tc.addr, tc.dialer) - ts = time.Now() - privProposal = &types.Proposal{Timestamp: ts} - clientProposal = &types.Proposal{Timestamp: ts} - ) - defer sc.Stop() - defer rs.Stop() + ts = time.Now() + privProposal = &types.Proposal{Timestamp: ts} + clientProposal = &types.Proposal{Timestamp: ts} + ) + defer sc.Stop() + defer rs.Stop() - require.NoError(t, rs.privVal.SignProposal(chainID, privProposal)) - require.NoError(t, sc.SignProposal(chainID, clientProposal)) - assert.Equal(t, privProposal.Signature, clientProposal.Signature) + require.NoError(t, rs.privVal.SignProposal(chainID, privProposal)) + require.NoError(t, sc.SignProposal(chainID, clientProposal)) + assert.Equal(t, privProposal.Signature, clientProposal.Signature) + }() + } } func TestSocketPVVote(t *testing.T) { - var ( - chainID = cmn.RandStr(12) - sc, rs = testSetupSocketPair(t, chainID, types.NewMockPV()) + for _, tc := range socketTestCases(t) { + func() { + var ( + chainID = cmn.RandStr(12) + sc, rs = testSetupSocketPair(t, chainID, types.NewMockPV(), tc.addr, tc.dialer) - ts = time.Now() - vType = types.PrecommitType - want = &types.Vote{Timestamp: ts, Type: vType} - have = &types.Vote{Timestamp: ts, Type: vType} - ) - defer sc.Stop() - defer rs.Stop() + ts = time.Now() + vType = types.PrecommitType + want = &types.Vote{Timestamp: ts, Type: vType} + have = &types.Vote{Timestamp: ts, Type: vType} + ) + defer sc.Stop() + defer rs.Stop() - require.NoError(t, rs.privVal.SignVote(chainID, want)) - require.NoError(t, sc.SignVote(chainID, have)) - assert.Equal(t, want.Signature, have.Signature) + require.NoError(t, rs.privVal.SignVote(chainID, want)) + require.NoError(t, sc.SignVote(chainID, have)) + assert.Equal(t, want.Signature, have.Signature) + }() + } } func TestSocketPVVoteResetDeadline(t *testing.T) { - var ( - chainID = cmn.RandStr(12) - sc, rs = testSetupSocketPair(t, chainID, types.NewMockPV()) + for _, tc := range socketTestCases(t) { + func() { + var ( + chainID = cmn.RandStr(12) + sc, rs = testSetupSocketPair(t, chainID, types.NewMockPV(), tc.addr, tc.dialer) - ts = time.Now() - vType = types.PrecommitType - want = &types.Vote{Timestamp: ts, Type: vType} - have = &types.Vote{Timestamp: ts, Type: vType} - ) - defer sc.Stop() - defer rs.Stop() + ts = time.Now() + vType = types.PrecommitType + want = &types.Vote{Timestamp: ts, Type: vType} + have = &types.Vote{Timestamp: ts, Type: vType} + ) + defer sc.Stop() + defer rs.Stop() - time.Sleep(testConnDeadline2o3) + time.Sleep(testConnDeadline2o3) - require.NoError(t, rs.privVal.SignVote(chainID, want)) - require.NoError(t, sc.SignVote(chainID, have)) - assert.Equal(t, want.Signature, have.Signature) + require.NoError(t, rs.privVal.SignVote(chainID, want)) + require.NoError(t, sc.SignVote(chainID, have)) + assert.Equal(t, want.Signature, have.Signature) - // This would exceed the deadline if it was not extended by the previous message - time.Sleep(testConnDeadline2o3) + // This would exceed the deadline if it was not extended by the previous message + time.Sleep(testConnDeadline2o3) - require.NoError(t, rs.privVal.SignVote(chainID, want)) - require.NoError(t, sc.SignVote(chainID, have)) - assert.Equal(t, want.Signature, have.Signature) + require.NoError(t, rs.privVal.SignVote(chainID, want)) + require.NoError(t, sc.SignVote(chainID, have)) + assert.Equal(t, want.Signature, have.Signature) + }() + } } func TestSocketPVVoteKeepalive(t *testing.T) { - var ( - chainID = cmn.RandStr(12) - sc, rs = testSetupSocketPair(t, chainID, types.NewMockPV()) + for _, tc := range socketTestCases(t) { + func() { + var ( + chainID = cmn.RandStr(12) + sc, rs = testSetupSocketPair(t, chainID, types.NewMockPV(), tc.addr, tc.dialer) - ts = time.Now() - vType = types.PrecommitType - want = &types.Vote{Timestamp: ts, Type: vType} - have = &types.Vote{Timestamp: ts, Type: vType} - ) - defer sc.Stop() - defer rs.Stop() + ts = time.Now() + vType = types.PrecommitType + want = &types.Vote{Timestamp: ts, Type: vType} + have = &types.Vote{Timestamp: ts, Type: vType} + ) + defer sc.Stop() + defer rs.Stop() - time.Sleep(testConnDeadline * 2) + time.Sleep(testConnDeadline * 2) - require.NoError(t, rs.privVal.SignVote(chainID, want)) - require.NoError(t, sc.SignVote(chainID, have)) - assert.Equal(t, want.Signature, have.Signature) + require.NoError(t, rs.privVal.SignVote(chainID, want)) + require.NoError(t, sc.SignVote(chainID, have)) + assert.Equal(t, want.Signature, have.Signature) + }() + } } -func TestSocketPVDeadline(t *testing.T) { +// TestSocketPVDeadlineTCPOnly is not relevant to Unix domain sockets, since the +// OS knows instantaneously the state of both sides of the connection. +func TestSocketPVDeadlineTCPOnly(t *testing.T) { var ( - addr = testFreeAddr(t) + addr = testFreeTCPAddr(t) listenc = make(chan struct{}) thisConnTimeout = 100 * time.Millisecond sc = newSocketVal(log.TestingLogger(), addr, thisConnTimeout) @@ -172,218 +222,195 @@ func TestSocketPVDeadline(t *testing.T) { <-listenc } -func TestRemoteSignerRetry(t *testing.T) { - var ( - attemptc = make(chan int) - retries = 2 - ) - - ln, err := net.Listen("tcp", "127.0.0.1:0") - require.NoError(t, err) - - go func(ln net.Listener, attemptc chan<- int) { - attempts := 0 - - for { - conn, err := ln.Accept() - require.NoError(t, err) - - err = conn.Close() - require.NoError(t, err) - - attempts++ - - if attempts == retries { - attemptc <- attempts - break - } - } - }(ln, attemptc) - - rs := NewRemoteSigner( - log.TestingLogger(), - cmn.RandStr(12), - types.NewMockPV(), - DialTCPFn(ln.Addr().String(), testConnDeadline, ed25519.GenPrivKey()), - ) - defer rs.Stop() - - RemoteSignerConnDeadline(time.Millisecond)(rs) - RemoteSignerConnRetries(retries)(rs) - - assert.Equal(t, rs.Start(), ErrDialRetryMax) - - select { - case attempts := <-attemptc: - assert.Equal(t, retries, attempts) - case <-time.After(100 * time.Millisecond): - t.Error("expected remote to observe connection attempts") - } -} - func TestRemoteSignVoteErrors(t *testing.T) { - var ( - chainID = cmn.RandStr(12) - sc, rs = testSetupSocketPair(t, chainID, types.NewErroringMockPV()) + for _, tc := range socketTestCases(t) { + func() { + var ( + chainID = cmn.RandStr(12) + sc, rs = testSetupSocketPair(t, chainID, types.NewErroringMockPV(), tc.addr, tc.dialer) - ts = time.Now() - vType = types.PrecommitType - vote = &types.Vote{Timestamp: ts, Type: vType} - ) - defer sc.Stop() - defer rs.Stop() + ts = time.Now() + vType = types.PrecommitType + vote = &types.Vote{Timestamp: ts, Type: vType} + ) + defer sc.Stop() + defer rs.Stop() - err := sc.SignVote("", vote) - require.Equal(t, err.(*RemoteSignerError).Description, types.ErroringMockPVErr.Error()) + err := sc.SignVote("", vote) + require.Equal(t, err.(*RemoteSignerError).Description, types.ErroringMockPVErr.Error()) - err = rs.privVal.SignVote(chainID, vote) - require.Error(t, err) - err = sc.SignVote(chainID, vote) - require.Error(t, err) + err = rs.privVal.SignVote(chainID, vote) + require.Error(t, err) + err = sc.SignVote(chainID, vote) + require.Error(t, err) + }() + } } func TestRemoteSignProposalErrors(t *testing.T) { - var ( - chainID = cmn.RandStr(12) - sc, rs = testSetupSocketPair(t, chainID, types.NewErroringMockPV()) + for _, tc := range socketTestCases(t) { + func() { + var ( + chainID = cmn.RandStr(12) + sc, rs = testSetupSocketPair(t, chainID, types.NewErroringMockPV(), tc.addr, tc.dialer) - ts = time.Now() - proposal = &types.Proposal{Timestamp: ts} - ) - defer sc.Stop() - defer rs.Stop() + ts = time.Now() + proposal = &types.Proposal{Timestamp: ts} + ) + defer sc.Stop() + defer rs.Stop() - err := sc.SignProposal("", proposal) - require.Equal(t, err.(*RemoteSignerError).Description, types.ErroringMockPVErr.Error()) + err := sc.SignProposal("", proposal) + require.Equal(t, err.(*RemoteSignerError).Description, types.ErroringMockPVErr.Error()) - err = rs.privVal.SignProposal(chainID, proposal) - require.Error(t, err) + err = rs.privVal.SignProposal(chainID, proposal) + require.Error(t, err) - err = sc.SignProposal(chainID, proposal) - require.Error(t, err) + err = sc.SignProposal(chainID, proposal) + require.Error(t, err) + }() + } } func TestErrUnexpectedResponse(t *testing.T) { - var ( - addr = testFreeAddr(t) - logger = log.TestingLogger() - chainID = cmn.RandStr(12) - readyc = make(chan struct{}) - errc = make(chan error, 1) + for _, tc := range socketTestCases(t) { + func() { + var ( + logger = log.TestingLogger() + chainID = cmn.RandStr(12) + readyc = make(chan struct{}) + errc = make(chan error, 1) - rs = NewRemoteSigner( - logger, - chainID, - types.NewMockPV(), - DialTCPFn(addr, testConnDeadline, ed25519.GenPrivKey()), - ) - sc = newSocketVal(logger, addr, testConnDeadline) - ) + rs = NewRemoteSigner( + logger, + chainID, + types.NewMockPV(), + tc.dialer, + ) + sc = newSocketVal(logger, tc.addr, testConnDeadline) + ) - testStartSocketPV(t, readyc, sc) - defer sc.Stop() - RemoteSignerConnDeadline(time.Millisecond)(rs) - RemoteSignerConnRetries(100)(rs) - // we do not want to Start() the remote signer here and instead use the connection to - // reply with intentionally wrong replies below: - rsConn, err := rs.connect() - defer rsConn.Close() - require.NoError(t, err) - require.NotNil(t, rsConn) - // send over public key to get the remote signer running: - go testReadWriteResponse(t, &PubKeyResponse{}, rsConn) - <-readyc + testStartSocketPV(t, readyc, sc) + defer sc.Stop() + RemoteSignerConnDeadline(time.Millisecond)(rs) + RemoteSignerConnRetries(100)(rs) + // we do not want to Start() the remote signer here and instead use the connection to + // reply with intentionally wrong replies below: + rsConn, err := rs.connect() + defer rsConn.Close() + require.NoError(t, err) + require.NotNil(t, rsConn) + // send over public key to get the remote signer running: + go testReadWriteResponse(t, &PubKeyResponse{}, rsConn) + <-readyc - // Proposal: - go func(errc chan error) { - errc <- sc.SignProposal(chainID, &types.Proposal{}) - }(errc) - // read request and write wrong response: - go testReadWriteResponse(t, &SignedVoteResponse{}, rsConn) - err = <-errc - require.Error(t, err) - require.Equal(t, err, ErrUnexpectedResponse) + // Proposal: + go func(errc chan error) { + errc <- sc.SignProposal(chainID, &types.Proposal{}) + }(errc) + // read request and write wrong response: + go testReadWriteResponse(t, &SignedVoteResponse{}, rsConn) + err = <-errc + require.Error(t, err) + require.Equal(t, err, ErrUnexpectedResponse) - // Vote: - go func(errc chan error) { - errc <- sc.SignVote(chainID, &types.Vote{}) - }(errc) - // read request and write wrong response: - go testReadWriteResponse(t, &SignedProposalResponse{}, rsConn) - err = <-errc - require.Error(t, err) - require.Equal(t, err, ErrUnexpectedResponse) + // Vote: + go func(errc chan error) { + errc <- sc.SignVote(chainID, &types.Vote{}) + }(errc) + // read request and write wrong response: + go testReadWriteResponse(t, &SignedProposalResponse{}, rsConn) + err = <-errc + require.Error(t, err) + require.Equal(t, err, ErrUnexpectedResponse) + }() + } } -func TestRetryTCPConnToRemoteSigner(t *testing.T) { - var ( - addr = testFreeAddr(t) - logger = log.TestingLogger() - chainID = cmn.RandStr(12) - readyc = make(chan struct{}) +func TestRetryConnToRemoteSigner(t *testing.T) { + for _, tc := range socketTestCases(t) { + func() { + var ( + logger = log.TestingLogger() + chainID = cmn.RandStr(12) + readyc = make(chan struct{}) - rs = NewRemoteSigner( - logger, - chainID, - types.NewMockPV(), - DialTCPFn(addr, testConnDeadline, ed25519.GenPrivKey()), - ) - thisConnTimeout = testConnDeadline - sc = newSocketVal(logger, addr, thisConnTimeout) - ) - // Ping every: - SocketValHeartbeat(testHeartbeatTimeout)(sc) + rs = NewRemoteSigner( + logger, + chainID, + types.NewMockPV(), + tc.dialer, + ) + thisConnTimeout = testConnDeadline + sc = newSocketVal(logger, tc.addr, thisConnTimeout) + ) + // Ping every: + SocketValHeartbeat(testHeartbeatTimeout)(sc) - RemoteSignerConnDeadline(testConnDeadline)(rs) - RemoteSignerConnRetries(10)(rs) + RemoteSignerConnDeadline(testConnDeadline)(rs) + RemoteSignerConnRetries(10)(rs) - testStartSocketPV(t, readyc, sc) - defer sc.Stop() - require.NoError(t, rs.Start()) - assert.True(t, rs.IsRunning()) + testStartSocketPV(t, readyc, sc) + defer sc.Stop() + require.NoError(t, rs.Start()) + assert.True(t, rs.IsRunning()) - <-readyc - time.Sleep(testHeartbeatTimeout * 2) + <-readyc + time.Sleep(testHeartbeatTimeout * 2) - rs.Stop() - rs2 := NewRemoteSigner( - logger, - chainID, - types.NewMockPV(), - DialTCPFn(addr, testConnDeadline, ed25519.GenPrivKey()), - ) - // let some pings pass - time.Sleep(testHeartbeatTimeout3o2) - require.NoError(t, rs2.Start()) - assert.True(t, rs2.IsRunning()) - defer rs2.Stop() + rs.Stop() + rs2 := NewRemoteSigner( + logger, + chainID, + types.NewMockPV(), + tc.dialer, + ) + // let some pings pass + time.Sleep(testHeartbeatTimeout3o2) + require.NoError(t, rs2.Start()) + assert.True(t, rs2.IsRunning()) + defer rs2.Stop() - // give the client some time to re-establish the conn to the remote signer - // should see sth like this in the logs: - // - // E[10016-01-10|17:12:46.128] Ping err="remote signer timed out" - // I[10016-01-10|17:16:42.447] Re-created connection to remote signer impl=SocketVal - time.Sleep(testConnDeadline * 2) + // give the client some time to re-establish the conn to the remote signer + // should see sth like this in the logs: + // + // E[10016-01-10|17:12:46.128] Ping err="remote signer timed out" + // I[10016-01-10|17:16:42.447] Re-created connection to remote signer impl=SocketVal + time.Sleep(testConnDeadline * 2) + }() + } } func newSocketVal(logger log.Logger, addr string, connDeadline time.Duration) *SocketVal { - ln, err := net.Listen(cmn.ProtocolAndAddress(addr)) + proto, address := cmn.ProtocolAndAddress(addr) + ln, err := net.Listen(proto, address) + logger.Info("Listening at", "proto", proto, "address", address) if err != nil { panic(err) } - tcpLn := NewTCPListener(ln, ed25519.GenPrivKey()) - TCPListenerAcceptDeadline(testAcceptDeadline)(tcpLn) - TCPListenerConnDeadline(testConnDeadline)(tcpLn) - return NewSocketVal(logger, tcpLn) + var svln net.Listener + if proto == "unix" { + unixLn := NewUnixListener(ln) + UnixListenerAcceptDeadline(testAcceptDeadline)(unixLn) + UnixListenerConnDeadline(connDeadline)(unixLn) + svln = unixLn + } else { + tcpLn := NewTCPListener(ln, ed25519.GenPrivKey()) + TCPListenerAcceptDeadline(testAcceptDeadline)(tcpLn) + TCPListenerConnDeadline(connDeadline)(tcpLn) + svln = tcpLn + } + return NewSocketVal(logger, svln) } func testSetupSocketPair( t *testing.T, chainID string, privValidator types.PrivValidator, + addr string, + dialer Dialer, ) (*SocketVal, *RemoteSigner) { var ( - addr = testFreeAddr(t) logger = log.TestingLogger() privVal = privValidator readyc = make(chan struct{}) @@ -391,7 +418,7 @@ func testSetupSocketPair( logger, chainID, privVal, - DialTCPFn(addr, testConnDeadline, ed25519.GenPrivKey()), + dialer, ) thisConnTimeout = testConnDeadline @@ -429,8 +456,8 @@ func testStartSocketPV(t *testing.T, readyc chan struct{}, sc *SocketVal) { }(sc) } -// testFreeAddr claims a free port so we don't block on listener being ready. -func testFreeAddr(t *testing.T) string { +// testFreeTCPAddr claims a free port so we don't block on listener being ready. +func testFreeTCPAddr(t *testing.T) string { ln, err := net.Listen("tcp", "127.0.0.1:0") require.NoError(t, err) defer ln.Close() diff --git a/privval/remote_signer_test.go b/privval/remote_signer_test.go new file mode 100644 index 00000000..8927e224 --- /dev/null +++ b/privval/remote_signer_test.go @@ -0,0 +1,68 @@ +package privval + +import ( + "net" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/tendermint/tendermint/crypto/ed25519" + cmn "github.com/tendermint/tendermint/libs/common" + "github.com/tendermint/tendermint/libs/log" + "github.com/tendermint/tendermint/types" +) + +// TestRemoteSignerRetryTCPOnly will test connection retry attempts over TCP. We +// don't need this for Unix sockets because the OS instantly knows the state of +// both ends of the socket connection. This basically causes the +// RemoteSigner.dialer() call inside RemoteSigner.connect() to return +// successfully immediately, putting an instant stop to any retry attempts. +func TestRemoteSignerRetryTCPOnly(t *testing.T) { + var ( + attemptc = make(chan int) + retries = 2 + ) + + ln, err := net.Listen("tcp", "127.0.0.1:0") + require.NoError(t, err) + + go func(ln net.Listener, attemptc chan<- int) { + attempts := 0 + + for { + conn, err := ln.Accept() + require.NoError(t, err) + + err = conn.Close() + require.NoError(t, err) + + attempts++ + + if attempts == retries { + attemptc <- attempts + break + } + } + }(ln, attemptc) + + rs := NewRemoteSigner( + log.TestingLogger(), + cmn.RandStr(12), + types.NewMockPV(), + DialTCPFn(ln.Addr().String(), testConnDeadline, ed25519.GenPrivKey()), + ) + defer rs.Stop() + + RemoteSignerConnDeadline(time.Millisecond)(rs) + RemoteSignerConnRetries(retries)(rs) + + assert.Equal(t, rs.Start(), ErrDialRetryMax) + + select { + case attempts := <-attemptc: + assert.Equal(t, retries, attempts) + case <-time.After(100 * time.Millisecond): + t.Error("expected remote to observe connection attempts") + } +} diff --git a/privval/socket.go b/privval/socket.go index 96fa6c8e..bd9cd920 100644 --- a/privval/socket.go +++ b/privval/socket.go @@ -157,7 +157,7 @@ type timeoutConn struct { connDeadline time.Duration } -// newTimeoutConn returns an instance of newTCPTimeoutConn. +// newTimeoutConn returns an instance of timeoutConn. func newTimeoutConn( conn net.Conn, connDeadline time.Duration) *timeoutConn { diff --git a/privval/socket_test.go b/privval/socket_test.go index dfce45b5..b411b7f3 100644 --- a/privval/socket_test.go +++ b/privval/socket_test.go @@ -29,7 +29,7 @@ type listenerTestCase struct { // testUnixAddr will attempt to obtain a platform-independent temporary file // name for a Unix socket func testUnixAddr() (string, error) { - f, err := ioutil.TempFile("", "tendermint-privval-test") + f, err := ioutil.TempFile("", "tendermint-privval-test-*") if err != nil { return "", err } From 0cba0e11b5569f7e0d8b61db3c2ce5416ce2753e Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Wed, 16 Jan 2019 10:16:23 -0500 Subject: [PATCH 200/267] update changelog and upgrading (#3133) --- CHANGELOG.md | 22 ++++++++++++---------- UPGRADING.md | 9 ++++----- 2 files changed, 16 insertions(+), 15 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 75ca299c..4fa0ff5d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,7 @@ ## v0.28.0 -*January 14th, 2019* +*January 16th, 2019* Special thanks to external contributors on this release: @fmauricios, @gianfelipe93, @husio, @needkane, @srmo, @yutianwu @@ -11,16 +11,18 @@ This release is primarily about upgrades to the `privval` system - separating the `priv_validator.json` into distinct config and data files, and refactoring the socket validator to support reconnections. +XXX: Please backup your existing `priv_validator.json` before using this +version. + See [UPGRADING.md](UPGRADING.md) for more details. ### BREAKING CHANGES: * CLI/RPC/Config -- [cli] Removed `node` `--proxy_app=dummy` option. Use `kvstore` (`persistent_kvstore`) instead. -- [cli] Renamed `node` `--proxy_app=nilapp` to `--proxy_app=noop`. +- [cli] Removed `--proxy_app=dummy` option. Use `kvstore` (`persistent_kvstore`) instead. +- [cli] Renamed `--proxy_app=nilapp` to `--proxy_app=noop`. - [config] [\#2992](https://github.com/tendermint/tendermint/issues/2992) `allow_duplicate_ip` is now set to false -- [privval] [\#1181](https://github.com/tendermint/tendermint/issues/1181) Split immutable and mutable parts of `priv_validator.json` - (@yutianwu) +- [privval] [\#1181](https://github.com/tendermint/tendermint/issues/1181) Split `priv_validator.json` into immutable (`config/priv_validator_key.json`) and mutable (`data/priv_validator_state.json`) parts (@yutianwu) - [privval] [\#2926](https://github.com/tendermint/tendermint/issues/2926) Split up `PubKeyMsg` into `PubKeyRequest` and `PubKeyResponse` to be consistent with other message types - [privval] [\#2923](https://github.com/tendermint/tendermint/issues/2923) Listen for unix socket connections instead of dialing them @@ -38,20 +40,20 @@ See [UPGRADING.md](UPGRADING.md) for more details. ### IMPROVEMENTS: - [consensus] [\#3086](https://github.com/tendermint/tendermint/issues/3086) Log peerID on ignored votes (@srmo) -- [docs] [\#3061](https://github.com/tendermint/tendermint/issues/3061) Added spec on signing consensus msgs at +- [docs] [\#3061](https://github.com/tendermint/tendermint/issues/3061) Added specification for signing consensus msgs at ./docs/spec/consensus/signing.md - [privval] [\#2948](https://github.com/tendermint/tendermint/issues/2948) Memoize pubkey so it's only requested once on startup - [privval] [\#2923](https://github.com/tendermint/tendermint/issues/2923) Retry RemoteSigner connections on error ### BUG FIXES: -- [types] [\#2926](https://github.com/tendermint/tendermint/issues/2926) Do not panic if retrieving the private validator's public key fails -- [rpc] [\#3053](https://github.com/tendermint/tendermint/issues/3053) Fix internal error in `/tx_search` when results are empty - (@gianfelipe93) +- [build] [\#3085](https://github.com/tendermint/tendermint/issues/3085) Fix `Version` field in build scripts (@husio) - [crypto/multisig] [\#3102](https://github.com/tendermint/tendermint/issues/3102) Fix multisig keys address length - [crypto/encoding] [\#3101](https://github.com/tendermint/tendermint/issues/3101) Fix `PubKeyMultisigThreshold` unmarshalling into `crypto.PubKey` interface -- [build] [\#3085](https://github.com/tendermint/tendermint/issues/3085) Fix `Version` field in build scripts (@husio) - [p2p/conn] [\#3111](https://github.com/tendermint/tendermint/issues/3111) Make SecretConnection thread safe +- [rpc] [\#3053](https://github.com/tendermint/tendermint/issues/3053) Fix internal error in `/tx_search` when results are empty + (@gianfelipe93) +- [types] [\#2926](https://github.com/tendermint/tendermint/issues/2926) Do not panic if retrieving the private validator's public key fails ## v0.27.4 diff --git a/UPGRADING.md b/UPGRADING.md index 3e2d1f69..df950043 100644 --- a/UPGRADING.md +++ b/UPGRADING.md @@ -7,7 +7,7 @@ a newer version of Tendermint Core. This release breaks the format for the `priv_validator.json` file and the protocol used for the external validator process. -It is compatible with v0.27.0 blockchains (neither the BlockProtocol or the +It is compatible with v0.27.0 blockchains (neither the BlockProtocol nor the P2PProtocol have changed). Please read carefully for details about upgrading. @@ -20,7 +20,7 @@ before proceeding. The `config/priv_validator.json` is now two files: `config/priv_validator_key.json` and `data/priv_validator_state.json`. The former contains the key material, the later contains the details on the last -thing signed. +message signed. When running v0.28.0 for the first time, it will back up any pre-existing `priv_validator.json` file and proceed to split it into the two new files. @@ -43,8 +43,8 @@ Thus in both cases, the external process is expected to dial Tendermint. This is different from how Unix sockets used to work, where Tendermint dialed the external process. -The `PubKeyMsg` was also split into two for consistency with other message -types. +The `PubKeyMsg` was also split into separate `Request` and `Response` types +for consistency with other messages. Note that the TCP sockets don't yet use a persistent key, so while they're encrypted, they can't yet be properly authenticated. @@ -52,7 +52,6 @@ See [#3105](https://github.com/tendermint/tendermint/issues/3105). Note the Unix socket has neither encryption nor authentication, but will add a shared-secret in [#3099](https://github.com/tendermint/tendermint/issues/3099). - ## v0.27.0 This release contains some breaking changes to the block and p2p protocols, From 239ebe20769b79f46a2ddca88de78e8044543e70 Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Wed, 16 Jan 2019 10:21:15 -0500 Subject: [PATCH 201/267] fix changelog fmt (#3134) --- CHANGELOG.md | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4fa0ff5d..ed590005 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,17 +19,17 @@ See [UPGRADING.md](UPGRADING.md) for more details. ### BREAKING CHANGES: * CLI/RPC/Config -- [cli] Removed `--proxy_app=dummy` option. Use `kvstore` (`persistent_kvstore`) instead. -- [cli] Renamed `--proxy_app=nilapp` to `--proxy_app=noop`. -- [config] [\#2992](https://github.com/tendermint/tendermint/issues/2992) `allow_duplicate_ip` is now set to false -- [privval] [\#1181](https://github.com/tendermint/tendermint/issues/1181) Split `priv_validator.json` into immutable (`config/priv_validator_key.json`) and mutable (`data/priv_validator_state.json`) parts (@yutianwu) -- [privval] [\#2926](https://github.com/tendermint/tendermint/issues/2926) Split up `PubKeyMsg` into `PubKeyRequest` and `PubKeyResponse` to be consistent with other message types -- [privval] [\#2923](https://github.com/tendermint/tendermint/issues/2923) Listen for unix socket connections instead of dialing them + - [cli] Removed `--proxy_app=dummy` option. Use `kvstore` (`persistent_kvstore`) instead. + - [cli] Renamed `--proxy_app=nilapp` to `--proxy_app=noop`. + - [config] [\#2992](https://github.com/tendermint/tendermint/issues/2992) `allow_duplicate_ip` is now set to false + - [privval] [\#1181](https://github.com/tendermint/tendermint/issues/1181) Split `priv_validator.json` into immutable (`config/priv_validator_key.json`) and mutable (`data/priv_validator_state.json`) parts (@yutianwu) + - [privval] [\#2926](https://github.com/tendermint/tendermint/issues/2926) Split up `PubKeyMsg` into `PubKeyRequest` and `PubKeyResponse` to be consistent with other message types + - [privval] [\#2923](https://github.com/tendermint/tendermint/issues/2923) Listen for unix socket connections instead of dialing them * Apps * Go API -- [types] [\#2981](https://github.com/tendermint/tendermint/issues/2981) Remove `PrivValidator.GetAddress()` + - [types] [\#2981](https://github.com/tendermint/tendermint/issues/2981) Remove `PrivValidator.GetAddress()` * Blockchain Protocol @@ -72,9 +72,8 @@ See [UPGRADING.md](UPGRADING.md) for more details. ### BREAKING CHANGES: * Go API - -- [dep] [\#3027](https://github.com/tendermint/tendermint/issues/3027) Revert to mainline Go crypto library, eliminating the modified - `bcrypt.GenerateFromPassword` + - [dep] [\#3027](https://github.com/tendermint/tendermint/issues/3027) Revert to mainline Go crypto library, eliminating the modified + `bcrypt.GenerateFromPassword` ## v0.27.2 From 6d6d103f15dcea246b09aa0f5f40348262a21eb0 Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Wed, 16 Jan 2019 13:41:37 -0500 Subject: [PATCH 202/267] fixes from review (#3137) --- CHANGELOG.md | 4 ++-- UPGRADING.md | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ed590005..236b7072 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,7 +11,7 @@ This release is primarily about upgrades to the `privval` system - separating the `priv_validator.json` into distinct config and data files, and refactoring the socket validator to support reconnections. -XXX: Please backup your existing `priv_validator.json` before using this +**Note:** Please backup your existing `priv_validator.json` before using this version. See [UPGRADING.md](UPGRADING.md) for more details. @@ -53,7 +53,7 @@ See [UPGRADING.md](UPGRADING.md) for more details. - [p2p/conn] [\#3111](https://github.com/tendermint/tendermint/issues/3111) Make SecretConnection thread safe - [rpc] [\#3053](https://github.com/tendermint/tendermint/issues/3053) Fix internal error in `/tx_search` when results are empty (@gianfelipe93) -- [types] [\#2926](https://github.com/tendermint/tendermint/issues/2926) Do not panic if retrieving the private validator's public key fails +- [types] [\#2926](https://github.com/tendermint/tendermint/issues/2926) Do not panic if retrieving the privval's public key fails ## v0.27.4 diff --git a/UPGRADING.md b/UPGRADING.md index df950043..edd50d9e 100644 --- a/UPGRADING.md +++ b/UPGRADING.md @@ -12,7 +12,7 @@ P2PProtocol have changed). Please read carefully for details about upgrading. -XXX: Backup your `config/priv_validator.json` +**Note:** Backup your `config/priv_validator.json` before proceeding. ### `priv_validator.json` From 55d723870882512d1a0ba9709129ce3227446e2e Mon Sep 17 00:00:00 2001 From: Dev Ojha Date: Wed, 16 Jan 2019 15:03:19 -0600 Subject: [PATCH 203/267] Add comment to simple_merkle get_split_point (#3136) * Add comment to simple_merkle get_split_point * fix grammar error --- crypto/merkle/simple_tree.go | 1 + 1 file changed, 1 insertion(+) diff --git a/crypto/merkle/simple_tree.go b/crypto/merkle/simple_tree.go index e150c0d3..5de514b5 100644 --- a/crypto/merkle/simple_tree.go +++ b/crypto/merkle/simple_tree.go @@ -32,6 +32,7 @@ func SimpleHashFromMap(m map[string][]byte) []byte { return sm.Hash() } +// getSplitPoint returns the largest power of 2 less than length func getSplitPoint(length int) int { if length < 1 { panic("Trying to split a tree with size < 1") From bc8874020f15fc557bad7682ecd3100ea0785155 Mon Sep 17 00:00:00 2001 From: Gautham Santhosh Date: Thu, 17 Jan 2019 11:30:51 +0000 Subject: [PATCH 204/267] docs: fix broken link (#3142) --- docs/networks/docker-compose.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/networks/docker-compose.md b/docs/networks/docker-compose.md index 52616b3d..b7818c3b 100644 --- a/docs/networks/docker-compose.md +++ b/docs/networks/docker-compose.md @@ -4,7 +4,7 @@ With Docker Compose, you can spin up local testnets with a single command. ## Requirements -1. [Install tendermint](/docs/install.md) +1. [Install tendermint](/docs/introduction/install.md) 2. [Install docker](https://docs.docker.com/engine/installation/) 3. [Install docker-compose](https://docs.docker.com/compose/install/) From c69dbb25cec3afc2982ffda2e27a0f91fba7b9e2 Mon Sep 17 00:00:00 2001 From: Thane Thomson Date: Thu, 17 Jan 2019 15:10:56 +0200 Subject: [PATCH 205/267] Consolidates deadline tests for privval Unix/TCP (#3143) * Consolidates deadline tests for privval Unix/TCP Following on from #3115 and #3132, this converts fundamental timeout errors from the client's `acceptConnection()` method so that these can be detected by the test for the TCP connection. Timeout deadlines are now tested for both TCP and Unix domain socket connections. There is also no need for the additional secret connection code: the connection will time out at the `acceptConnection()` phase for TCP connections, and will time out when attempting to obtain the `RemoteSigner`'s public key for Unix domain socket connections. * Removes extraneous logging * Adds IsConnTimeout helper function This commit adds a helper function to detect whether an error is either a fundamental networking timeout error, or an `ErrConnTimeout` error specific to the `RemoteSigner` class. * Adds a test for the IsConnTimeout() helper function * Separates tests logically for IsConnTimeout --- privval/client_test.go | 53 ++++++++++++++++------------------- privval/remote_signer.go | 15 ++++++++++ privval/remote_signer_test.go | 22 +++++++++++++++ 3 files changed, 61 insertions(+), 29 deletions(-) diff --git a/privval/client_test.go b/privval/client_test.go index 3c327064..72682a8d 100644 --- a/privval/client_test.go +++ b/privval/client_test.go @@ -13,7 +13,6 @@ import ( cmn "github.com/tendermint/tendermint/libs/common" "github.com/tendermint/tendermint/libs/log" - p2pconn "github.com/tendermint/tendermint/p2p/conn" "github.com/tendermint/tendermint/types" ) @@ -186,40 +185,36 @@ func TestSocketPVVoteKeepalive(t *testing.T) { } } -// TestSocketPVDeadlineTCPOnly is not relevant to Unix domain sockets, since the -// OS knows instantaneously the state of both sides of the connection. -func TestSocketPVDeadlineTCPOnly(t *testing.T) { - var ( - addr = testFreeTCPAddr(t) - listenc = make(chan struct{}) - thisConnTimeout = 100 * time.Millisecond - sc = newSocketVal(log.TestingLogger(), addr, thisConnTimeout) - ) +func TestSocketPVDeadline(t *testing.T) { + for _, tc := range socketTestCases(t) { + func() { + var ( + listenc = make(chan struct{}) + thisConnTimeout = 100 * time.Millisecond + sc = newSocketVal(log.TestingLogger(), tc.addr, thisConnTimeout) + ) - go func(sc *SocketVal) { - defer close(listenc) + go func(sc *SocketVal) { + defer close(listenc) - assert.Equal(t, sc.Start().(cmn.Error).Data(), ErrConnTimeout) + // Note: the TCP connection times out at the accept() phase, + // whereas the Unix domain sockets connection times out while + // attempting to fetch the remote signer's public key. + assert.True(t, IsConnTimeout(sc.Start())) - assert.False(t, sc.IsRunning()) - }(sc) + assert.False(t, sc.IsRunning()) + }(sc) - for { - conn, err := cmn.Connect(addr) - if err != nil { - continue - } + for { + _, err := cmn.Connect(tc.addr) + if err == nil { + break + } + } - _, err = p2pconn.MakeSecretConnection( - conn, - ed25519.GenPrivKey(), - ) - if err == nil { - break - } + <-listenc + }() } - - <-listenc } func TestRemoteSignVoteErrors(t *testing.T) { diff --git a/privval/remote_signer.go b/privval/remote_signer.go index d928b198..1371e333 100644 --- a/privval/remote_signer.go +++ b/privval/remote_signer.go @@ -258,3 +258,18 @@ func handleRequest(req RemoteSignerMsg, chainID string, privVal types.PrivValida return res, err } + +// IsConnTimeout returns a boolean indicating whether the error is known to +// report that a connection timeout occurred. This detects both fundamental +// network timeouts, as well as ErrConnTimeout errors. +func IsConnTimeout(err error) bool { + if cmnErr, ok := err.(cmn.Error); ok { + if cmnErr.Data() == ErrConnTimeout { + return true + } + } + if _, ok := err.(timeoutError); ok { + return true + } + return false +} diff --git a/privval/remote_signer_test.go b/privval/remote_signer_test.go index 8927e224..cb2a600d 100644 --- a/privval/remote_signer_test.go +++ b/privval/remote_signer_test.go @@ -5,6 +5,7 @@ import ( "testing" "time" + "github.com/pkg/errors" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/tendermint/tendermint/crypto/ed25519" @@ -66,3 +67,24 @@ func TestRemoteSignerRetryTCPOnly(t *testing.T) { t.Error("expected remote to observe connection attempts") } } + +func TestIsConnTimeoutForFundamentalTimeouts(t *testing.T) { + // Generate a networking timeout + dialer := DialTCPFn(testFreeTCPAddr(t), time.Millisecond, ed25519.GenPrivKey()) + _, err := dialer() + assert.Error(t, err) + assert.True(t, IsConnTimeout(err)) +} + +func TestIsConnTimeoutForWrappedConnTimeouts(t *testing.T) { + dialer := DialTCPFn(testFreeTCPAddr(t), time.Millisecond, ed25519.GenPrivKey()) + _, err := dialer() + assert.Error(t, err) + err = cmn.ErrorWrap(ErrConnTimeout, err.Error()) + assert.True(t, IsConnTimeout(err)) +} + +func TestIsConnTimeoutForNonTimeoutErrors(t *testing.T) { + assert.False(t, IsConnTimeout(cmn.ErrorWrap(ErrDialRetryMax, "max retries exceeded"))) + assert.False(t, IsConnTimeout(errors.New("completely irrelevant error"))) +} From 87991059aab94ee86fbcd1b647cc0e5d58d505db Mon Sep 17 00:00:00 2001 From: Anton Kaliaev Date: Thu, 17 Jan 2019 17:42:57 +0400 Subject: [PATCH 206/267] docs: fix RPC links (#3141) --- docs/app-dev/indexing-transactions.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/app-dev/indexing-transactions.md b/docs/app-dev/indexing-transactions.md index 61c959ca..de8336a4 100644 --- a/docs/app-dev/indexing-transactions.md +++ b/docs/app-dev/indexing-transactions.md @@ -78,7 +78,7 @@ endpoint: curl "localhost:26657/tx_search?query=\"account.name='igor'\"&prove=true" ``` -Check out [API docs](https://tendermint.github.io/slate/?shell#txsearch) +Check out [API docs](https://tendermint.com/rpc/#txsearch) for more information on query syntax and other options. ## Subscribing to transactions @@ -97,5 +97,5 @@ by providing a query to `/subscribe` RPC endpoint. } ``` -Check out [API docs](https://tendermint.github.io/slate/#subscribe) for +Check out [API docs](https://tendermint.com/rpc/#subscribe) for more information on query syntax and other options. From 8fd8f800d0c1853a59ce9519276cce3cfced22cf Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Thu, 17 Jan 2019 21:46:40 -0500 Subject: [PATCH 207/267] Bucky/fix evidence halt (#34) * consensus: createProposalBlock function * blockExecutor.CreateProposalBlock - factored out of consensus pkg into a method on blockExec - new private interfaces for mempool ("txNotifier") and evpool with one function each - consensus tests still require more mempool methods * failing test for CreateProposalBlock * Fix bug in include evidece into block * evidence: change maxBytes to maxSize * MaxEvidencePerBlock - changed to return both the max number and the max bytes - preparation for #2590 * changelog * fix linter * Fix from review Co-Authored-By: ebuchman --- CHANGELOG_PENDING.md | 3 +- consensus/mempool_test.go | 18 ++++--- consensus/reactor_test.go | 4 +- consensus/replay_file.go | 2 +- consensus/replay_test.go | 2 +- consensus/state.go | 49 +++++++++-------- evidence/pool.go | 8 +-- evidence/store.go | 20 +++---- evidence/wire.go | 5 ++ node/node_test.go | 111 ++++++++++++++++++++++++++++++++++++++ state/execution.go | 28 +++++++++- state/validation.go | 9 ++-- state/validation_test.go | 7 ++- types/block.go | 5 +- types/evidence.go | 43 ++++++++++++--- types/wire.go | 5 ++ 16 files changed, 254 insertions(+), 65 deletions(-) diff --git a/CHANGELOG_PENDING.md b/CHANGELOG_PENDING.md index 332cfbf7..61b58142 100644 --- a/CHANGELOG_PENDING.md +++ b/CHANGELOG_PENDING.md @@ -1,4 +1,4 @@ -## v0.29.0 +## v0.28.1 *TBD* @@ -21,3 +21,4 @@ Special thanks to external contributors on this release: ### IMPROVEMENTS: ### BUG FIXES: +- [consensus] \#? Fix consensus halt from proposing blocks with too much evidence diff --git a/consensus/mempool_test.go b/consensus/mempool_test.go index 49ba74fe..bb4bf6eb 100644 --- a/consensus/mempool_test.go +++ b/consensus/mempool_test.go @@ -10,6 +10,7 @@ import ( "github.com/tendermint/tendermint/abci/example/code" abci "github.com/tendermint/tendermint/abci/types" + sm "github.com/tendermint/tendermint/state" "github.com/tendermint/tendermint/types" ) @@ -17,12 +18,17 @@ func init() { config = ResetConfig("consensus_mempool_test") } +// for testing +func assertMempool(txn txNotifier) sm.Mempool { + return txn.(sm.Mempool) +} + func TestMempoolNoProgressUntilTxsAvailable(t *testing.T) { config := ResetConfig("consensus_mempool_txs_available_test") config.Consensus.CreateEmptyBlocks = false state, privVals := randGenesisState(1, false, 10) cs := newConsensusStateWithConfig(config, state, privVals[0], NewCounterApplication()) - cs.mempool.EnableTxsAvailable() + assertMempool(cs.txNotifier).EnableTxsAvailable() height, round := cs.Height, cs.Round newBlockCh := subscribe(cs.eventBus, types.EventQueryNewBlock) startTestRound(cs, height, round) @@ -40,7 +46,7 @@ func TestMempoolProgressAfterCreateEmptyBlocksInterval(t *testing.T) { config.Consensus.CreateEmptyBlocksInterval = ensureTimeout state, privVals := randGenesisState(1, false, 10) cs := newConsensusStateWithConfig(config, state, privVals[0], NewCounterApplication()) - cs.mempool.EnableTxsAvailable() + assertMempool(cs.txNotifier).EnableTxsAvailable() height, round := cs.Height, cs.Round newBlockCh := subscribe(cs.eventBus, types.EventQueryNewBlock) startTestRound(cs, height, round) @@ -55,7 +61,7 @@ func TestMempoolProgressInHigherRound(t *testing.T) { config.Consensus.CreateEmptyBlocks = false state, privVals := randGenesisState(1, false, 10) cs := newConsensusStateWithConfig(config, state, privVals[0], NewCounterApplication()) - cs.mempool.EnableTxsAvailable() + assertMempool(cs.txNotifier).EnableTxsAvailable() height, round := cs.Height, cs.Round newBlockCh := subscribe(cs.eventBus, types.EventQueryNewBlock) newRoundCh := subscribe(cs.eventBus, types.EventQueryNewRound) @@ -91,7 +97,7 @@ func deliverTxsRange(cs *ConsensusState, start, end int) { for i := start; i < end; i++ { txBytes := make([]byte, 8) binary.BigEndian.PutUint64(txBytes, uint64(i)) - err := cs.mempool.CheckTx(txBytes, nil) + err := assertMempool(cs.txNotifier).CheckTx(txBytes, nil) if err != nil { panic(fmt.Sprintf("Error after CheckTx: %v", err)) } @@ -141,7 +147,7 @@ func TestMempoolRmBadTx(t *testing.T) { // Try to send the tx through the mempool. // CheckTx should not err, but the app should return a bad abci code // and the tx should get removed from the pool - err := cs.mempool.CheckTx(txBytes, func(r *abci.Response) { + err := assertMempool(cs.txNotifier).CheckTx(txBytes, func(r *abci.Response) { if r.GetCheckTx().Code != code.CodeTypeBadNonce { t.Fatalf("expected checktx to return bad nonce, got %v", r) } @@ -153,7 +159,7 @@ func TestMempoolRmBadTx(t *testing.T) { // check for the tx for { - txs := cs.mempool.ReapMaxBytesMaxGas(int64(len(txBytes)), -1) + txs := assertMempool(cs.txNotifier).ReapMaxBytesMaxGas(int64(len(txBytes)), -1) if len(txs) == 0 { emptyMempoolCh <- struct{}{} return diff --git a/consensus/reactor_test.go b/consensus/reactor_test.go index 5334895f..4772108b 100644 --- a/consensus/reactor_test.go +++ b/consensus/reactor_test.go @@ -225,7 +225,7 @@ func TestReactorCreatesBlockWhenEmptyBlocksFalse(t *testing.T) { defer stopConsensusNet(log.TestingLogger(), reactors, eventBuses) // send a tx - if err := css[3].mempool.CheckTx([]byte{1, 2, 3}, nil); err != nil { + if err := assertMempool(css[3].txNotifier).CheckTx([]byte{1, 2, 3}, nil); err != nil { //t.Fatal(err) } @@ -448,7 +448,7 @@ func waitForAndValidateBlock(t *testing.T, n int, activeVals map[string]struct{} err := validateBlock(newBlock, activeVals) assert.Nil(t, err) for _, tx := range txs { - err := css[j].mempool.CheckTx(tx, nil) + err := assertMempool(css[j].txNotifier).CheckTx(tx, nil) assert.Nil(t, err) } }, css) diff --git a/consensus/replay_file.go b/consensus/replay_file.go index a326e70e..2d087914 100644 --- a/consensus/replay_file.go +++ b/consensus/replay_file.go @@ -137,7 +137,7 @@ func (pb *playback) replayReset(count int, newStepCh chan interface{}) error { pb.cs.Wait() newCS := NewConsensusState(pb.cs.config, pb.genesisState.Copy(), pb.cs.blockExec, - pb.cs.blockStore, pb.cs.mempool, pb.cs.evpool) + pb.cs.blockStore, pb.cs.txNotifier, pb.cs.evpool) newCS.SetEventBus(pb.cs.eventBus) newCS.startForReplay() diff --git a/consensus/replay_test.go b/consensus/replay_test.go index 7c00251e..d3aaebf1 100644 --- a/consensus/replay_test.go +++ b/consensus/replay_test.go @@ -87,7 +87,7 @@ func sendTxs(cs *ConsensusState, ctx context.Context) { return default: tx := []byte{byte(i)} - cs.mempool.CheckTx(tx, nil) + assertMempool(cs.txNotifier).CheckTx(tx, nil) i++ } } diff --git a/consensus/state.go b/consensus/state.go index c6f73d35..26b07417 100644 --- a/consensus/state.go +++ b/consensus/state.go @@ -57,6 +57,16 @@ func (ti *timeoutInfo) String() string { return fmt.Sprintf("%v ; %d/%d %v", ti.Duration, ti.Height, ti.Round, ti.Step) } +// interface to the mempool +type txNotifier interface { + TxsAvailable() <-chan struct{} +} + +// interface to the evidence pool +type evidencePool interface { + AddEvidence(types.Evidence) error +} + // ConsensusState handles execution of the consensus algorithm. // It processes votes and proposals, and upon reaching agreement, // commits blocks to the chain and executes them against the application. @@ -68,11 +78,18 @@ type ConsensusState struct { config *cfg.ConsensusConfig privValidator types.PrivValidator // for signing votes - // services for creating and executing blocks - blockExec *sm.BlockExecutor + // store blocks and commits blockStore sm.BlockStore - mempool sm.Mempool - evpool sm.EvidencePool + + // create and execute blocks + blockExec *sm.BlockExecutor + + // notify us if txs are available + txNotifier txNotifier + + // add evidence to the pool + // when it's detected + evpool evidencePool // internal state mtx sync.RWMutex @@ -128,15 +145,15 @@ func NewConsensusState( state sm.State, blockExec *sm.BlockExecutor, blockStore sm.BlockStore, - mempool sm.Mempool, - evpool sm.EvidencePool, + txNotifier txNotifier, + evpool evidencePool, options ...StateOption, ) *ConsensusState { cs := &ConsensusState{ config: config, blockExec: blockExec, blockStore: blockStore, - mempool: mempool, + txNotifier: txNotifier, peerMsgQueue: make(chan msgInfo, msgQueueSize), internalMsgQueue: make(chan msgInfo, msgQueueSize), timeoutTicker: NewTimeoutTicker(), @@ -484,7 +501,7 @@ func (cs *ConsensusState) updateToState(state sm.State) { // If state isn't further out than cs.state, just ignore. // This happens when SwitchToConsensus() is called in the reactor. // We don't want to reset e.g. the Votes, but we still want to - // signal the new round step, because other services (eg. mempool) + // signal the new round step, because other services (eg. txNotifier) // depend on having an up-to-date peer state! if !cs.state.IsEmpty() && (state.LastBlockHeight <= cs.state.LastBlockHeight) { cs.Logger.Info("Ignoring updateToState()", "newHeight", state.LastBlockHeight+1, "oldHeight", cs.state.LastBlockHeight+1) @@ -599,7 +616,7 @@ func (cs *ConsensusState) receiveRoutine(maxSteps int) { var mi msgInfo select { - case <-cs.mempool.TxsAvailable(): + case <-cs.txNotifier.TxsAvailable(): cs.handleTxsAvailable() case mi = <-cs.peerMsgQueue: cs.wal.Write(mi) @@ -921,20 +938,8 @@ func (cs *ConsensusState) createProposalBlock() (block *types.Block, blockParts return } - maxBytes := cs.state.ConsensusParams.BlockSize.MaxBytes - maxGas := cs.state.ConsensusParams.BlockSize.MaxGas - // bound evidence to 1/10th of the block - evidence := cs.evpool.PendingEvidence(types.MaxEvidenceBytesPerBlock(maxBytes)) - // Mempool validated transactions - txs := cs.mempool.ReapMaxBytesMaxGas(types.MaxDataBytes( - maxBytes, - cs.state.Validators.Size(), - len(evidence), - ), maxGas) proposerAddr := cs.privValidator.GetPubKey().Address() - block, parts := cs.state.MakeBlock(cs.Height, txs, commit, evidence, proposerAddr) - - return block, parts + return cs.blockExec.CreateProposalBlock(cs.Height, cs.state, commit, proposerAddr) } // Enter: `timeoutPropose` after entering Propose. diff --git a/evidence/pool.go b/evidence/pool.go index da00a348..b5fdbdf1 100644 --- a/evidence/pool.go +++ b/evidence/pool.go @@ -57,10 +57,10 @@ func (evpool *EvidencePool) PriorityEvidence() []types.Evidence { return evpool.evidenceStore.PriorityEvidence() } -// PendingEvidence returns uncommitted evidence up to maxBytes. -// If maxBytes is -1, all evidence is returned. -func (evpool *EvidencePool) PendingEvidence(maxBytes int64) []types.Evidence { - return evpool.evidenceStore.PendingEvidence(maxBytes) +// PendingEvidence returns up to maxNum uncommitted evidence. +// If maxNum is -1, all evidence is returned. +func (evpool *EvidencePool) PendingEvidence(maxNum int64) []types.Evidence { + return evpool.evidenceStore.PendingEvidence(maxNum) } // State returns the current state of the evpool. diff --git a/evidence/store.go b/evidence/store.go index ccfd2d48..17b37aab 100644 --- a/evidence/store.go +++ b/evidence/store.go @@ -86,26 +86,26 @@ func (store *EvidenceStore) PriorityEvidence() (evidence []types.Evidence) { return l } -// PendingEvidence returns known uncommitted evidence up to maxBytes. -// If maxBytes is -1, all evidence is returned. -func (store *EvidenceStore) PendingEvidence(maxBytes int64) (evidence []types.Evidence) { - return store.listEvidence(baseKeyPending, maxBytes) +// PendingEvidence returns up to maxNum known, uncommitted evidence. +// If maxNum is -1, all evidence is returned. +func (store *EvidenceStore) PendingEvidence(maxNum int64) (evidence []types.Evidence) { + return store.listEvidence(baseKeyPending, maxNum) } -// listEvidence lists the evidence for the given prefix key up to maxBytes. +// listEvidence lists up to maxNum pieces of evidence for the given prefix key. // It is wrapped by PriorityEvidence and PendingEvidence for convenience. -// If maxBytes is -1, there's no cap on the size of returned evidence. -func (store *EvidenceStore) listEvidence(prefixKey string, maxBytes int64) (evidence []types.Evidence) { - var bytes int64 +// If maxNum is -1, there's no cap on the size of returned evidence. +func (store *EvidenceStore) listEvidence(prefixKey string, maxNum int64) (evidence []types.Evidence) { + var count int64 iter := dbm.IteratePrefix(store.db, []byte(prefixKey)) defer iter.Close() for ; iter.Valid(); iter.Next() { val := iter.Value() - if maxBytes > 0 && bytes+int64(len(val)) > maxBytes { + if count == maxNum { return evidence } - bytes += int64(len(val)) + count++ var ei EvidenceInfo err := cdc.UnmarshalBinaryBare(val, &ei) diff --git a/evidence/wire.go b/evidence/wire.go index 73ff33b2..86655953 100644 --- a/evidence/wire.go +++ b/evidence/wire.go @@ -13,3 +13,8 @@ func init() { cryptoAmino.RegisterAmino(cdc) types.RegisterEvidences(cdc) } + +// For testing purposes only +func RegisterMockEvidences() { + types.RegisterMockEvidences(cdc) +} diff --git a/node/node_test.go b/node/node_test.go index 96d779d4..06561e07 100644 --- a/node/node_test.go +++ b/node/node_test.go @@ -15,10 +15,14 @@ import ( "github.com/tendermint/tendermint/abci/example/kvstore" cfg "github.com/tendermint/tendermint/config" "github.com/tendermint/tendermint/crypto/ed25519" + "github.com/tendermint/tendermint/evidence" cmn "github.com/tendermint/tendermint/libs/common" + dbm "github.com/tendermint/tendermint/libs/db" "github.com/tendermint/tendermint/libs/log" + mempl "github.com/tendermint/tendermint/mempool" "github.com/tendermint/tendermint/p2p" "github.com/tendermint/tendermint/privval" + "github.com/tendermint/tendermint/proxy" sm "github.com/tendermint/tendermint/state" "github.com/tendermint/tendermint/types" tmtime "github.com/tendermint/tendermint/types/time" @@ -192,3 +196,110 @@ func testFreeAddr(t *testing.T) string { return fmt.Sprintf("127.0.0.1:%d", ln.Addr().(*net.TCPAddr).Port) } + +// create a proposal block using real and full +// mempool and evidence pool and validate it. +func TestCreateProposalBlock(t *testing.T) { + config := cfg.ResetTestRoot("node_create_proposal") + cc := proxy.NewLocalClientCreator(kvstore.NewKVStoreApplication()) + proxyApp := proxy.NewAppConns(cc) + err := proxyApp.Start() + require.Nil(t, err) + defer proxyApp.Stop() + + logger := log.TestingLogger() + + var height int64 = 1 + state, stateDB := state(1, height) + maxBytes := 16384 + state.ConsensusParams.BlockSize.MaxBytes = int64(maxBytes) + proposerAddr, _ := state.Validators.GetByIndex(0) + + // Make Mempool + memplMetrics := mempl.PrometheusMetrics("node_test") + mempool := mempl.NewMempool( + config.Mempool, + proxyApp.Mempool(), + state.LastBlockHeight, + mempl.WithMetrics(memplMetrics), + mempl.WithPreCheck(sm.TxPreCheck(state)), + mempl.WithPostCheck(sm.TxPostCheck(state)), + ) + mempool.SetLogger(logger) + + // Make EvidencePool + types.RegisterMockEvidencesGlobal() + evidence.RegisterMockEvidences() + evidenceDB := dbm.NewMemDB() + evidenceStore := evidence.NewEvidenceStore(evidenceDB) + evidencePool := evidence.NewEvidencePool(stateDB, evidenceStore) + evidencePool.SetLogger(logger) + + // fill the evidence pool with more evidence + // than can fit in a block + minEvSize := 12 + numEv := (maxBytes / types.MaxEvidenceBytesDenominator) / minEvSize + for i := 0; i < numEv; i++ { + ev := types.NewMockRandomGoodEvidence(1, proposerAddr, cmn.RandBytes(minEvSize)) + err := evidencePool.AddEvidence(ev) + assert.NoError(t, err) + } + + // fill the mempool with more txs + // than can fit in a block + txLength := 1000 + for i := 0; i < maxBytes/txLength; i++ { + tx := cmn.RandBytes(txLength) + err := mempool.CheckTx(tx, nil) + assert.NoError(t, err) + } + + blockExec := sm.NewBlockExecutor( + stateDB, + logger, + proxyApp.Consensus(), + mempool, + evidencePool, + ) + + commit := &types.Commit{} + block, _ := blockExec.CreateProposalBlock( + height, + state, commit, + proposerAddr, + ) + + err = blockExec.ValidateBlock(state, block) + assert.NoError(t, err) + +} + +func state(nVals int, height int64) (sm.State, dbm.DB) { + vals := make([]types.GenesisValidator, nVals) + for i := 0; i < nVals; i++ { + secret := []byte(fmt.Sprintf("test%d", i)) + pk := ed25519.GenPrivKeyFromSecret(secret) + vals[i] = types.GenesisValidator{ + pk.PubKey().Address(), + pk.PubKey(), + 1000, + fmt.Sprintf("test%d", i), + } + } + s, _ := sm.MakeGenesisState(&types.GenesisDoc{ + ChainID: "test-chain", + Validators: vals, + AppHash: nil, + }) + + // save validators to db for 2 heights + stateDB := dbm.NewMemDB() + sm.SaveState(stateDB, s) + + for i := 1; i < int(height); i++ { + s.LastBlockHeight++ + s.LastValidators = s.Validators.Copy() + sm.SaveState(stateDB, s) + } + return s, stateDB +} diff --git a/state/execution.go b/state/execution.go index decadddf..85bbd382 100644 --- a/state/execution.go +++ b/state/execution.go @@ -29,7 +29,8 @@ type BlockExecutor struct { // events eventBus types.BlockEventPublisher - // update these with block results after commit + // manage the mempool lock during commit + // and update both with block results after commit. mempool Mempool evpool EvidencePool @@ -73,6 +74,31 @@ func (blockExec *BlockExecutor) SetEventBus(eventBus types.BlockEventPublisher) blockExec.eventBus = eventBus } +// CreateProposalBlock calls state.MakeBlock with evidence from the evpool +// and txs from the mempool. The max bytes must be big enough to fit the commit. +// Up to 1/10th of the block space is allcoated for maximum sized evidence. +// The rest is given to txs, up to the max gas. +func (blockExec *BlockExecutor) CreateProposalBlock( + height int64, + state State, commit *types.Commit, + proposerAddr []byte, +) (*types.Block, *types.PartSet) { + + maxBytes := state.ConsensusParams.BlockSize.MaxBytes + maxGas := state.ConsensusParams.BlockSize.MaxGas + + // Fetch a limited amount of valid evidence + maxNumEvidence, _ := types.MaxEvidencePerBlock(maxBytes) + evidence := blockExec.evpool.PendingEvidence(maxNumEvidence) + + // Fetch a limited amount of valid txs + maxDataBytes := types.MaxDataBytes(maxBytes, state.Validators.Size(), len(evidence)) + txs := blockExec.mempool.ReapMaxBytesMaxGas(maxDataBytes, maxGas) + + return state.MakeBlock(height, txs, commit, evidence, proposerAddr) + +} + // ValidateBlock validates the given block against the given state. // If the block is invalid, it returns an error. // Validation does not mutate state, but does require historical information from the stateDB, diff --git a/state/validation.go b/state/validation.go index e28d40e8..cd571e34 100644 --- a/state/validation.go +++ b/state/validation.go @@ -133,10 +133,11 @@ func validateBlock(stateDB dbm.DB, state State, block *types.Block) error { } // Limit the amount of evidence - maxEvidenceBytes := types.MaxEvidenceBytesPerBlock(state.ConsensusParams.BlockSize.MaxBytes) - evidenceBytes := int64(len(block.Evidence.Evidence)) * types.MaxEvidenceBytes - if evidenceBytes > maxEvidenceBytes { - return types.NewErrEvidenceOverflow(maxEvidenceBytes, evidenceBytes) + maxNumEvidence, _ := types.MaxEvidencePerBlock(state.ConsensusParams.BlockSize.MaxBytes) + numEvidence := int64(len(block.Evidence.Evidence)) + if numEvidence > maxNumEvidence { + return types.NewErrEvidenceOverflow(maxNumEvidence, numEvidence) + } // Validate all evidence. diff --git a/state/validation_test.go b/state/validation_test.go index f89fbdea..12aaf636 100644 --- a/state/validation_test.go +++ b/state/validation_test.go @@ -109,10 +109,9 @@ func TestValidateBlockEvidence(t *testing.T) { // A block with too much evidence fails. maxBlockSize := state.ConsensusParams.BlockSize.MaxBytes - maxEvidenceBytes := types.MaxEvidenceBytesPerBlock(maxBlockSize) - maxEvidence := maxEvidenceBytes / types.MaxEvidenceBytes - require.True(t, maxEvidence > 2) - for i := int64(0); i < maxEvidence; i++ { + maxNumEvidence, _ := types.MaxEvidencePerBlock(maxBlockSize) + require.True(t, maxNumEvidence > 2) + for i := int64(0); i < maxNumEvidence; i++ { block.Evidence.Evidence = append(block.Evidence.Evidence, goodEvidence) } block.EvidenceHash = block.Evidence.Hash() diff --git a/types/block.go b/types/block.go index 15b88d81..a540ea59 100644 --- a/types/block.go +++ b/types/block.go @@ -322,16 +322,17 @@ func MaxDataBytes(maxBytes int64, valsCount, evidenceCount int) int64 { } // MaxDataBytesUnknownEvidence returns the maximum size of block's data when -// evidence count is unknown. MaxEvidenceBytesPerBlock will be used as the size +// evidence count is unknown. MaxEvidencePerBlock will be used for the size // of evidence. // // XXX: Panics on negative result. func MaxDataBytesUnknownEvidence(maxBytes int64, valsCount int) int64 { + _, maxEvidenceBytes := MaxEvidencePerBlock(maxBytes) maxDataBytes := maxBytes - MaxAminoOverheadForBlock - MaxHeaderBytes - int64(valsCount)*MaxVoteBytes - - MaxEvidenceBytesPerBlock(maxBytes) + maxEvidenceBytes if maxDataBytes < 0 { panic(fmt.Sprintf( diff --git a/types/evidence.go b/types/evidence.go index fb242345..f04b7e43 100644 --- a/types/evidence.go +++ b/types/evidence.go @@ -36,8 +36,8 @@ func (err *ErrEvidenceInvalid) Error() string { // ErrEvidenceOverflow is for when there is too much evidence in a block. type ErrEvidenceOverflow struct { - MaxBytes int64 - GotBytes int64 + MaxNum int64 + GotNum int64 } // NewErrEvidenceOverflow returns a new ErrEvidenceOverflow where got > max. @@ -47,7 +47,7 @@ func NewErrEvidenceOverflow(max, got int64) *ErrEvidenceOverflow { // Error returns a string representation of the error. func (err *ErrEvidenceOverflow) Error() string { - return fmt.Sprintf("Too much evidence: Max %d bytes, got %d bytes", err.MaxBytes, err.GotBytes) + return fmt.Sprintf("Too much evidence: Max %d, got %d", err.MaxNum, err.GotNum) } //------------------------------------------- @@ -72,13 +72,23 @@ func RegisterEvidences(cdc *amino.Codec) { func RegisterMockEvidences(cdc *amino.Codec) { cdc.RegisterConcrete(MockGoodEvidence{}, "tendermint/MockGoodEvidence", nil) + cdc.RegisterConcrete(MockRandomGoodEvidence{}, "tendermint/MockRandomGoodEvidence", nil) cdc.RegisterConcrete(MockBadEvidence{}, "tendermint/MockBadEvidence", nil) } -// MaxEvidenceBytesPerBlock returns the maximum evidence size per block - -// 1/10th of the maximum block size. -func MaxEvidenceBytesPerBlock(blockMaxBytes int64) int64 { - return blockMaxBytes / 10 +const ( + MaxEvidenceBytesDenominator = 10 +) + +// MaxEvidencePerBlock returns the maximum number of evidences +// allowed in the block and their maximum total size (limitted to 1/10th +// of the maximum block size). +// TODO: change to a constant, or to a fraction of the validator set size. +// See https://github.com/tendermint/tendermint/issues/2590 +func MaxEvidencePerBlock(blockMaxBytes int64) (int64, int64) { + maxBytes := blockMaxBytes / MaxEvidenceBytesDenominator + maxNum := maxBytes / MaxEvidenceBytes + return maxNum, maxBytes } //------------------------------------------- @@ -193,6 +203,25 @@ func (dve *DuplicateVoteEvidence) ValidateBasic() error { //----------------------------------------------------------------- +// UNSTABLE +type MockRandomGoodEvidence struct { + MockGoodEvidence + randBytes []byte +} + +var _ Evidence = &MockRandomGoodEvidence{} + +// UNSTABLE +func NewMockRandomGoodEvidence(height int64, address []byte, randBytes []byte) MockRandomGoodEvidence { + return MockRandomGoodEvidence{ + MockGoodEvidence{height, address}, randBytes, + } +} + +func (e MockRandomGoodEvidence) Hash() []byte { + return []byte(fmt.Sprintf("%d-%x", e.Height_, e.randBytes)) +} + // UNSTABLE type MockGoodEvidence struct { Height_ int64 diff --git a/types/wire.go b/types/wire.go index f3c314fa..92304801 100644 --- a/types/wire.go +++ b/types/wire.go @@ -20,3 +20,8 @@ func RegisterBlockAmino(cdc *amino.Codec) { func GetCodec() *amino.Codec { return cdc } + +// For testing purposes only +func RegisterMockEvidencesGlobal() { + RegisterMockEvidences(cdc) +} From 4d36647eea5d9994f4e036d81beb8f49d25f6493 Mon Sep 17 00:00:00 2001 From: Henry Harder Date: Thu, 17 Jan 2019 23:46:34 -0800 Subject: [PATCH 208/267] Add ParadigmCore to ecosystem.json (#3145) Adding the OrderStream network client (alpha), ParadigmCore, to the `ecosystem.json` file. --- docs/app-dev/ecosystem.json | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/docs/app-dev/ecosystem.json b/docs/app-dev/ecosystem.json index 57059701..9e264af2 100644 --- a/docs/app-dev/ecosystem.json +++ b/docs/app-dev/ecosystem.json @@ -63,6 +63,13 @@ "author": "Zach Balder", "description": "Public service reporting and tracking" }, + { + "name": "ParadigmCore", + "url": "https://github.com/ParadigmFoundation/ParadigmCore", + "language": "TypeScript", + "author": "Paradigm Labs", + "description": "Reference implementation of the Paradigm Protocol, and OrderStream network client." + }, { "name": "Passchain", "url": "https://github.com/trusch/passchain", From f5f1416a149e525d9f48c8762c7999809d6e9395 Mon Sep 17 00:00:00 2001 From: bradyjoestar Date: Fri, 18 Jan 2019 16:09:12 +0800 Subject: [PATCH 209/267] json2wal: increase reader's buffer size (#3147) ``` panic: failed to unmarshal json: unexpected end of JSON input goroutine 1 [running]: main.main() /root/gelgo/src/github.com/tendermint/tendermint/scripts/json2wal/main.go:66 +0x73f ``` Closes #3146 --- scripts/json2wal/main.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/scripts/json2wal/main.go b/scripts/json2wal/main.go index be3487e5..9611b9b5 100644 --- a/scripts/json2wal/main.go +++ b/scripts/json2wal/main.go @@ -45,7 +45,10 @@ func main() { } defer walFile.Close() - br := bufio.NewReader(f) + // the length of tendermint/wal/MsgInfo in the wal.json may exceed the defaultBufSize(4096) of bufio + // because of the byte array in BlockPart + // leading to unmarshal error: unexpected end of JSON input + br := bufio.NewReaderSize(f, 2*types.BlockPartSizeBytes) dec := cs.NewWALEncoder(walFile) for { From 5a2e69df819e094ec3b065d02912179937b51ea4 Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Fri, 18 Jan 2019 12:11:02 -0500 Subject: [PATCH 210/267] changelog and version --- CHANGELOG.md | 13 +++++++++++++ CHANGELOG_PENDING.md | 3 +-- version/version.go | 2 +- 3 files changed, 15 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 236b7072..707d0d2d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,18 @@ # Changelog +## v0.28.1 + +*January 18th, 2019* + +Special thanks to external contributors on this release: +@HaoyangLiu + +Friendly reminder, we have a [bug bounty +program](https://hackerone.com/tendermint). + +### BUG FIXES: +- [consensus] Fix consensus halt from proposing blocks with too much evidence + ## v0.28.0 *January 16th, 2019* diff --git a/CHANGELOG_PENDING.md b/CHANGELOG_PENDING.md index 61b58142..332cfbf7 100644 --- a/CHANGELOG_PENDING.md +++ b/CHANGELOG_PENDING.md @@ -1,4 +1,4 @@ -## v0.28.1 +## v0.29.0 *TBD* @@ -21,4 +21,3 @@ Special thanks to external contributors on this release: ### IMPROVEMENTS: ### BUG FIXES: -- [consensus] \#? Fix consensus halt from proposing blocks with too much evidence diff --git a/version/version.go b/version/version.go index 658e0e89..707dbf16 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.28.0" + TMCoreSemVer = "0.28.1" // ABCISemVer is the semantic version of the ABCI library ABCISemVer = "0.15.0" From d3e8889411fade8c592ecd05fe2ac8839cf31732 Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Sat, 19 Jan 2019 14:08:41 -0500 Subject: [PATCH 211/267] update btcd fork for v0.1.1 (#3164) * update btcd fork for v0.1.1 * changelog --- CHANGELOG_PENDING.md | 3 ++- Gopkg.lock | 5 +++-- Gopkg.toml | 7 ++++--- 3 files changed, 9 insertions(+), 6 deletions(-) diff --git a/CHANGELOG_PENDING.md b/CHANGELOG_PENDING.md index 06eb9e37..358ea833 100644 --- a/CHANGELOG_PENDING.md +++ b/CHANGELOG_PENDING.md @@ -26,4 +26,5 @@ Special thanks to external contributors on this release: - [instrumentation] \#3082 add 'chain_id' label for all metrics ### BUG FIXES: -- [log] \#3060 fix year format +- [log] \#3060 Fix year format +- [crypto] \#3164 Update `btcd` fork for rare signRFC6979 bug diff --git a/Gopkg.lock b/Gopkg.lock index 76d6fcb9..9880f3f3 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -361,11 +361,12 @@ revision = "6b91fda63f2e36186f1c9d0e48578defb69c5d43" [[projects]] - digest = "1:605b6546f3f43745695298ec2d342d3e952b6d91cdf9f349bea9315f677d759f" + digest = "1:83f5e189eea2baad419a6a410984514266ff690075759c87e9ede596809bd0b8" name = "github.com/tendermint/btcd" packages = ["btcec"] pruneopts = "UT" - revision = "e5840949ff4fff0c56f9b6a541e22b63581ea9df" + revision = "80daadac05d1cd29571fccf27002d79667a88b58" + version = "v0.1.1" [[projects]] digest = "1:ad9c4c1a4e7875330b1f62906f2830f043a23edb5db997e3a5ac5d3e6eadf80a" diff --git a/Gopkg.toml b/Gopkg.toml index 16c1b463..72ec6659 100644 --- a/Gopkg.toml +++ b/Gopkg.toml @@ -75,6 +75,10 @@ name = "github.com/prometheus/client_golang" version = "^0.9.1" +[[constraint]] + name = "github.com/tendermint/btcd" + version = "v0.1.1" + ################################### ## Some repos dont have releases. ## Pin to revision @@ -92,9 +96,6 @@ name = "github.com/btcsuite/btcutil" revision = "d4cc87b860166d00d6b5b9e0d3b3d71d6088d4d4" -[[constraint]] - name = "github.com/tendermint/btcd" - revision = "e5840949ff4fff0c56f9b6a541e22b63581ea9df" [[constraint]] name = "github.com/rcrowley/go-metrics" From 40c887baf7bdf2add66d798ae7baa14e15fe7d92 Mon Sep 17 00:00:00 2001 From: Ismail Khoffi Date: Sat, 19 Jan 2019 21:55:08 +0100 Subject: [PATCH 212/267] Normalize priorities to not exceed total voting power (#3049) * more proposer priority tests - test that we don't reset to zero when updating / adding - test that same power validators alternate * add another test to track / simulate similar behaviour as in #2960 * address some of Chris' review comments * address some more of Chris' review comments * temporarily pushing branch with the following changes: The total power might change if: - a validator is added - a validator is removed - a validator is updated Decrement the accums (of all validators) directly after any of these events (by the inverse of the change) * Fix 2960 by re-normalizing / scaling priorities to be in bounds of total power, additionally: - remove heap where it doesn't make sense - avg. only at the end of IncrementProposerPriority instead of each iteration - update (and slightly improve) TestAveragingInIncrementProposerPriorityWithVotingPower to reflect above changes * Fix 2960 by re-normalizing / scaling priorities to be in bounds of total power, additionally: - remove heap where it doesn't make sense - avg. only at the end of IncrementProposerPriority instead of each iteration - update (and slightly improve) TestAveragingInIncrementProposerPriorityWithVotingPower to reflect above changes * fix tests * add comment * update changelog pending & some minor changes * comment about division will floor the result & fix typo * Update TestLargeGenesisValidator: - remove TODO and increase large genesis validator's voting power accordingly * move changelog entry to P2P Protocol * Ceil instead of flooring when dividing & update test * quickly fix failing TestProposerPriorityDoesNotGetResetToZero: - divide by Ceil((maxPriority - minPriority) / 2*totalVotingPower) * fix typo: rename getValWitMostPriority -> getValWithMostPriority * test proposer frequencies * return absolute value for diff. keep testing * use for loop for div * cleanup, more tests * spellcheck * get rid of using floats: manually ceil where necessary * Remove float, simplify, fix tests to match chris's proof (#3157) --- CHANGELOG_PENDING.md | 2 + p2p/metrics.go | 2 +- state/state_test.go | 246 ++++++++++++++++++++++++++++++------ types/validator_set.go | 108 ++++++++++------ types/validator_set_test.go | 84 ++++++------ 5 files changed, 318 insertions(+), 124 deletions(-) diff --git a/CHANGELOG_PENDING.md b/CHANGELOG_PENDING.md index 358ea833..19e10623 100644 --- a/CHANGELOG_PENDING.md +++ b/CHANGELOG_PENDING.md @@ -18,6 +18,8 @@ Special thanks to external contributors on this release: * [merkle] \#2713 Merkle trees now match the RFC 6962 specification * P2P Protocol + - [consensus] \#2960 normalize priorities to not exceed `2*TotalVotingPower` to mitigate unfair proposer selection + heavily preferring earlier joined validators in the case of an early bonded large validator unbonding ### FEATURES: diff --git a/p2p/metrics.go b/p2p/metrics.go index 1b90172c..3a6b9568 100644 --- a/p2p/metrics.go +++ b/p2p/metrics.go @@ -72,7 +72,7 @@ func PrometheusMetrics(namespace string, labelsAndValues ...string) *Metrics { // NopMetrics returns no-op Metrics. func NopMetrics() *Metrics { return &Metrics{ - Peers: discard.NewGauge(), + Peers: discard.NewGauge(), PeerReceiveBytesTotal: discard.NewCounter(), PeerSendBytesTotal: discard.NewCounter(), PeerPendingSendBytes: discard.NewGauge(), diff --git a/state/state_test.go b/state/state_test.go index 0448008e..9ab0de13 100644 --- a/state/state_test.go +++ b/state/state_test.go @@ -3,6 +3,7 @@ package state import ( "bytes" "fmt" + "math" "math/big" "testing" @@ -264,14 +265,133 @@ func TestOneValidatorChangesSaveLoad(t *testing.T) { } } +func TestProposerFrequency(t *testing.T) { + + // some explicit test cases + testCases := []struct { + powers []int64 + }{ + // 2 vals + {[]int64{1, 1}}, + {[]int64{1, 2}}, + {[]int64{1, 100}}, + {[]int64{5, 5}}, + {[]int64{5, 100}}, + {[]int64{50, 50}}, + {[]int64{50, 100}}, + {[]int64{1, 1000}}, + + // 3 vals + {[]int64{1, 1, 1}}, + {[]int64{1, 2, 3}}, + {[]int64{1, 2, 3}}, + {[]int64{1, 1, 10}}, + {[]int64{1, 1, 100}}, + {[]int64{1, 10, 100}}, + {[]int64{1, 1, 1000}}, + {[]int64{1, 10, 1000}}, + {[]int64{1, 100, 1000}}, + + // 4 vals + {[]int64{1, 1, 1, 1}}, + {[]int64{1, 2, 3, 4}}, + {[]int64{1, 1, 1, 10}}, + {[]int64{1, 1, 1, 100}}, + {[]int64{1, 1, 1, 1000}}, + {[]int64{1, 1, 10, 100}}, + {[]int64{1, 1, 10, 1000}}, + {[]int64{1, 1, 100, 1000}}, + {[]int64{1, 10, 100, 1000}}, + } + + for caseNum, testCase := range testCases { + // run each case 5 times to sample different + // initial priorities + for i := 0; i < 5; i++ { + valSet := genValSetWithPowers(testCase.powers) + testProposerFreq(t, caseNum, valSet) + } + } + + // some random test cases with up to 300 validators + maxVals := 100 + maxPower := 1000 + nTestCases := 5 + for i := 0; i < nTestCases; i++ { + N := cmn.RandInt() % maxVals + vals := make([]*types.Validator, N) + totalVotePower := int64(0) + for j := 0; j < N; j++ { + votePower := int64(cmn.RandInt() % maxPower) + totalVotePower += votePower + privVal := types.NewMockPV() + pubKey := privVal.GetPubKey() + val := types.NewValidator(pubKey, votePower) + val.ProposerPriority = cmn.RandInt64() + vals[j] = val + } + valSet := types.NewValidatorSet(vals) + valSet.RescalePriorities(totalVotePower) + testProposerFreq(t, i, valSet) + } +} + +// new val set with given powers and random initial priorities +func genValSetWithPowers(powers []int64) *types.ValidatorSet { + size := len(powers) + vals := make([]*types.Validator, size) + totalVotePower := int64(0) + for i := 0; i < size; i++ { + totalVotePower += powers[i] + val := types.NewValidator(ed25519.GenPrivKey().PubKey(), powers[i]) + val.ProposerPriority = cmn.RandInt64() + vals[i] = val + } + valSet := types.NewValidatorSet(vals) + valSet.RescalePriorities(totalVotePower) + return valSet +} + +// test a proposer appears as frequently as expected +func testProposerFreq(t *testing.T, caseNum int, valSet *types.ValidatorSet) { + N := valSet.Size() + totalPower := valSet.TotalVotingPower() + + // run the proposer selection and track frequencies + runMult := 1 + runs := int(totalPower) * runMult + freqs := make([]int, N) + for i := 0; i < runs; i++ { + prop := valSet.GetProposer() + idx, _ := valSet.GetByAddress(prop.Address) + freqs[idx] += 1 + valSet.IncrementProposerPriority(1) + } + + // assert frequencies match expected (max off by 1) + for i, freq := range freqs { + _, val := valSet.GetByIndex(i) + expectFreq := int(val.VotingPower) * runMult + gotFreq := freq + abs := int(math.Abs(float64(expectFreq - gotFreq))) + + // max bound on expected vs seen freq was proven + // to be 1 for the 2 validator case in + // https://github.com/cwgoes/tm-proposer-idris + // and inferred to generalize to N-1 + bound := N - 1 + require.True(t, abs <= bound, fmt.Sprintf("Case %d val %d (%d): got %d, expected %d", caseNum, i, N, gotFreq, expectFreq)) + } +} + +// TestProposerPriorityDoesNotGetResetToZero assert that we preserve accum when calling updateState +// see https://github.com/tendermint/tendermint/issues/2718 func TestProposerPriorityDoesNotGetResetToZero(t *testing.T) { - // assert that we preserve accum when calling updateState: - // https://github.com/tendermint/tendermint/issues/2718 tearDown, _, state := setupTestCase(t) defer tearDown(t) - origVotingPower := int64(10) + val1VotingPower := int64(10) val1PubKey := ed25519.GenPrivKey().PubKey() - val1 := &types.Validator{Address: val1PubKey.Address(), PubKey: val1PubKey, VotingPower: origVotingPower} + val1 := &types.Validator{Address: val1PubKey.Address(), PubKey: val1PubKey, VotingPower: val1VotingPower} state.Validators = types.NewValidatorSet([]*types.Validator{val1}) state.NextValidators = state.Validators @@ -288,8 +408,9 @@ func TestProposerPriorityDoesNotGetResetToZero(t *testing.T) { require.NoError(t, err) updatedState, err := updateState(state, blockID, &block.Header, abciResponses, validatorUpdates) assert.NoError(t, err) - - assert.Equal(t, -origVotingPower, updatedState.NextValidators.Validators[0].ProposerPriority) + curTotal := val1VotingPower + // one increment step and one validator: 0 + power - total_power == 0 + assert.Equal(t, 0+val1VotingPower-curTotal, updatedState.NextValidators.Validators[0].ProposerPriority) // add a validator val2PubKey := ed25519.GenPrivKey().PubKey() @@ -301,22 +422,33 @@ func TestProposerPriorityDoesNotGetResetToZero(t *testing.T) { assert.NoError(t, err) require.Equal(t, len(updatedState2.NextValidators.Validators), 2) + _, updatedVal1 := updatedState2.NextValidators.GetByAddress(val1PubKey.Address()) _, addedVal2 := updatedState2.NextValidators.GetByAddress(val2PubKey.Address()) // adding a validator should not lead to a ProposerPriority equal to zero (unless the combination of averaging and // incrementing would cause so; which is not the case here) - totalPowerBefore2 := origVotingPower // 10 - wantVal2ProposerPrio := -(totalPowerBefore2 + (totalPowerBefore2 >> 3)) + val2VotingPower // 89 - avg := (0 + wantVal2ProposerPrio) / 2 // 44 - wantVal2ProposerPrio -= avg // 45 - totalPowerAfter := origVotingPower + val2VotingPower // 110 - wantVal2ProposerPrio -= totalPowerAfter // -65 - assert.Equal(t, wantVal2ProposerPrio, addedVal2.ProposerPriority) // not zero == -65 + totalPowerBefore2 := curTotal + // while adding we compute prio == -1.125 * total: + wantVal2ProposerPrio := -(totalPowerBefore2 + (totalPowerBefore2 >> 3)) + wantVal2ProposerPrio = wantVal2ProposerPrio + val2VotingPower + // then increment: + totalPowerAfter := val1VotingPower + val2VotingPower + // mostest: + wantVal2ProposerPrio = wantVal2ProposerPrio - totalPowerAfter + avg := big.NewInt(0).Add(big.NewInt(val1VotingPower), big.NewInt(wantVal2ProposerPrio)) + avg.Div(avg, big.NewInt(2)) + wantVal2ProposerPrio = wantVal2ProposerPrio - avg.Int64() + wantVal1Prio := 0 + val1VotingPower - avg.Int64() + assert.Equal(t, wantVal1Prio, updatedVal1.ProposerPriority) + assert.Equal(t, wantVal2ProposerPrio, addedVal2.ProposerPriority) // Updating a validator does not reset the ProposerPriority to zero: updatedVotingPowVal2 := int64(1) updateVal := abci.ValidatorUpdate{PubKey: types.TM2PB.PubKey(val2PubKey), Power: updatedVotingPowVal2} validatorUpdates, err = types.PB2TM.ValidatorUpdates([]abci.ValidatorUpdate{updateVal}) assert.NoError(t, err) + + // this will cause the diff of priorities (31) + // to be larger than threshold == 2*totalVotingPower (22): updatedState3, err := updateState(updatedState2, blockID, &block.Header, abciResponses, validatorUpdates) assert.NoError(t, err) @@ -324,11 +456,18 @@ func TestProposerPriorityDoesNotGetResetToZero(t *testing.T) { _, prevVal1 := updatedState3.Validators.GetByAddress(val1PubKey.Address()) _, updatedVal2 := updatedState3.NextValidators.GetByAddress(val2PubKey.Address()) - expectedVal1PrioBeforeAvg := prevVal1.ProposerPriority + prevVal1.VotingPower // -44 + 10 == -34 - wantVal2ProposerPrio = wantVal2ProposerPrio + updatedVotingPowVal2 // -64 - avg = (wantVal2ProposerPrio + expectedVal1PrioBeforeAvg) / 2 // (-64-34)/2 == -49 - wantVal2ProposerPrio = wantVal2ProposerPrio - avg // -15 - assert.Equal(t, wantVal2ProposerPrio, updatedVal2.ProposerPriority) // -15 + // divide previous priorities by 2 == CEIL(31/22) as diff > threshold: + expectedVal1PrioBeforeAvg := prevVal1.ProposerPriority/2 + prevVal1.VotingPower + wantVal2ProposerPrio = wantVal2ProposerPrio/2 + updatedVotingPowVal2 + // val1 will be proposer: + total := val1VotingPower + updatedVotingPowVal2 + expectedVal1PrioBeforeAvg = expectedVal1PrioBeforeAvg - total + avgI64 := (wantVal2ProposerPrio + expectedVal1PrioBeforeAvg) / 2 + wantVal2ProposerPrio = wantVal2ProposerPrio - avgI64 + wantVal1Prio = expectedVal1PrioBeforeAvg - avgI64 + assert.Equal(t, wantVal2ProposerPrio, updatedVal2.ProposerPriority) + _, updatedVal1 = updatedState3.NextValidators.GetByAddress(val1PubKey.Address()) + assert.Equal(t, wantVal1Prio, updatedVal1.ProposerPriority) } func TestProposerPriorityProposerAlternates(t *testing.T) { @@ -338,9 +477,9 @@ func TestProposerPriorityProposerAlternates(t *testing.T) { // have the same voting power (and the 2nd was added later). tearDown, _, state := setupTestCase(t) defer tearDown(t) - origVotinPower := int64(10) + val1VotingPower := int64(10) val1PubKey := ed25519.GenPrivKey().PubKey() - val1 := &types.Validator{Address: val1PubKey.Address(), PubKey: val1PubKey, VotingPower: origVotinPower} + val1 := &types.Validator{Address: val1PubKey.Address(), PubKey: val1PubKey, VotingPower: val1VotingPower} // reset state validators to above validator state.Validators = types.NewValidatorSet([]*types.Validator{val1}) @@ -361,12 +500,14 @@ func TestProposerPriorityProposerAlternates(t *testing.T) { assert.NoError(t, err) // 0 + 10 (initial prio) - 10 (avg) - 10 (mostest - total) = -10 - assert.Equal(t, -origVotinPower, updatedState.NextValidators.Validators[0].ProposerPriority) + totalPower := val1VotingPower + wantVal1Prio := 0 + val1VotingPower - totalPower + assert.Equal(t, wantVal1Prio, updatedState.NextValidators.Validators[0].ProposerPriority) assert.Equal(t, val1PubKey.Address(), updatedState.NextValidators.Proposer.Address) // add a validator with the same voting power as the first val2PubKey := ed25519.GenPrivKey().PubKey() - updateAddVal := abci.ValidatorUpdate{PubKey: types.TM2PB.PubKey(val2PubKey), Power: origVotinPower} + updateAddVal := abci.ValidatorUpdate{PubKey: types.TM2PB.PubKey(val2PubKey), Power: val1VotingPower} validatorUpdates, err = types.PB2TM.ValidatorUpdates([]abci.ValidatorUpdate{updateAddVal}) assert.NoError(t, err) @@ -386,16 +527,18 @@ func TestProposerPriorityProposerAlternates(t *testing.T) { _, oldVal1 := updatedState2.Validators.GetByAddress(val1PubKey.Address()) _, updatedVal2 := updatedState2.NextValidators.GetByAddress(val2PubKey.Address()) - totalPower := origVotinPower + totalPower = val1VotingPower // no update v2PrioWhenAddedVal2 := -(totalPower + (totalPower >> 3)) - v2PrioWhenAddedVal2 = v2PrioWhenAddedVal2 + origVotinPower // -11 + 10 == -1 - v1PrioWhenAddedVal2 := oldVal1.ProposerPriority + origVotinPower // -10 + 10 == 0 + v2PrioWhenAddedVal2 = v2PrioWhenAddedVal2 + val1VotingPower // -11 + 10 == -1 + v1PrioWhenAddedVal2 := oldVal1.ProposerPriority + val1VotingPower // -10 + 10 == 0 + totalPower = 2 * val1VotingPower // now we have to validators with that power + v1PrioWhenAddedVal2 = v1PrioWhenAddedVal2 - totalPower // mostest // have to express the AVG in big.Ints as -1/2 == -1 in big.Int while -1/2 == 0 in int64 avgSum := big.NewInt(0).Add(big.NewInt(v2PrioWhenAddedVal2), big.NewInt(v1PrioWhenAddedVal2)) avg := avgSum.Div(avgSum, big.NewInt(2)) expectedVal2Prio := v2PrioWhenAddedVal2 - avg.Int64() - totalPower = 2 * origVotinPower // 10 + 10 - expectedVal1Prio := oldVal1.ProposerPriority + origVotinPower - avg.Int64() - totalPower + totalPower = 2 * val1VotingPower // 10 + 10 + expectedVal1Prio := oldVal1.ProposerPriority + val1VotingPower - avg.Int64() - totalPower // val1's ProposerPriority story: -10 (see above) + 10 (voting pow) - (-1) (avg) - 20 (total) == -19 assert.EqualValues(t, expectedVal1Prio, updatedVal1.ProposerPriority) // val2 prio when added: -(totalVotingPower + (totalVotingPower >> 3)) == -11 @@ -421,10 +564,12 @@ func TestProposerPriorityProposerAlternates(t *testing.T) { assert.Equal(t, val2PubKey.Address(), updatedState3.NextValidators.Proposer.Address) // check if expected proposer prio is matched: - avgSum = big.NewInt(oldVal1.ProposerPriority + origVotinPower + oldVal2.ProposerPriority + origVotinPower) + expectedVal1Prio2 := oldVal1.ProposerPriority + val1VotingPower + expectedVal2Prio2 := oldVal2.ProposerPriority + val1VotingPower - totalPower + avgSum = big.NewInt(expectedVal1Prio + expectedVal2Prio) avg = avgSum.Div(avgSum, big.NewInt(2)) - expectedVal1Prio2 := oldVal1.ProposerPriority + origVotinPower - avg.Int64() - expectedVal2Prio2 := oldVal2.ProposerPriority + origVotinPower - avg.Int64() - totalPower + expectedVal1Prio -= avg.Int64() + expectedVal2Prio -= avg.Int64() // -19 + 10 - 0 (avg) == -9 assert.EqualValues(t, expectedVal1Prio2, updatedVal1.ProposerPriority, "unexpected proposer priority for validator: %v", updatedVal2) @@ -468,9 +613,8 @@ func TestProposerPriorityProposerAlternates(t *testing.T) { func TestLargeGenesisValidator(t *testing.T) { tearDown, _, state := setupTestCase(t) defer tearDown(t) - // TODO: increase genesis voting power to sth. more close to MaxTotalVotingPower with changes that - // fix with tendermint/issues/2960; currently, the last iteration would take forever though - genesisVotingPower := int64(types.MaxTotalVotingPower / 100000000000000) + + genesisVotingPower := int64(types.MaxTotalVotingPower / 1000) genesisPubKey := ed25519.GenPrivKey().PubKey() // fmt.Println("genesis addr: ", genesisPubKey.Address()) genesisVal := &types.Validator{Address: genesisPubKey.Address(), PubKey: genesisPubKey, VotingPower: genesisVotingPower} @@ -494,11 +638,11 @@ func TestLargeGenesisValidator(t *testing.T) { blockID := types.BlockID{block.Hash(), block.MakePartSet(testPartSize).Header()} updatedState, err := updateState(oldState, blockID, &block.Header, abciResponses, validatorUpdates) - // no changes in voting power (ProposerPrio += VotingPower == 0 in 1st round; than shiftByAvg == no-op, + // no changes in voting power (ProposerPrio += VotingPower == Voting in 1st round; than shiftByAvg == 0, // than -Total == -Voting) - // -> no change in ProposerPrio (stays -Total == -VotingPower): + // -> no change in ProposerPrio (stays zero): assert.EqualValues(t, oldState.NextValidators, updatedState.NextValidators) - assert.EqualValues(t, -genesisVotingPower, updatedState.NextValidators.Proposer.ProposerPriority) + assert.EqualValues(t, 0, updatedState.NextValidators.Proposer.ProposerPriority) oldState = updatedState } @@ -508,7 +652,6 @@ func TestLargeGenesisValidator(t *testing.T) { // see how long it takes until the effect wears off and both begin to alternate // see: https://github.com/tendermint/tendermint/issues/2960 firstAddedValPubKey := ed25519.GenPrivKey().PubKey() - // fmt.Println("first added addr: ", firstAddedValPubKey.Address()) firstAddedValVotingPower := int64(10) firstAddedVal := abci.ValidatorUpdate{PubKey: types.TM2PB.PubKey(firstAddedValPubKey), Power: firstAddedValVotingPower} validatorUpdates, err := types.PB2TM.ValidatorUpdates([]abci.ValidatorUpdate{firstAddedVal}) @@ -598,10 +741,33 @@ func TestLargeGenesisValidator(t *testing.T) { } count++ } - // first proposer change happens after this many iters; we probably want to lower this number: - // TODO: change with https://github.com/tendermint/tendermint/issues/2960 - firstProposerChangeExpectedAfter := 438 + updatedState = curState + // the proposer changes after this number of blocks + firstProposerChangeExpectedAfter := 1 assert.Equal(t, firstProposerChangeExpectedAfter, count) + // store proposers here to see if we see them again in the same order: + numVals := len(updatedState.Validators.Validators) + proposers := make([]*types.Validator, numVals) + for i := 0; i < 100; i++ { + // no updates: + abciResponses := &ABCIResponses{ + EndBlock: &abci.ResponseEndBlock{ValidatorUpdates: nil}, + } + validatorUpdates, err := types.PB2TM.ValidatorUpdates(abciResponses.EndBlock.ValidatorUpdates) + require.NoError(t, err) + + block := makeBlock(updatedState, updatedState.LastBlockHeight+1) + blockID := types.BlockID{block.Hash(), block.MakePartSet(testPartSize).Header()} + + updatedState, err = updateState(updatedState, blockID, &block.Header, abciResponses, validatorUpdates) + if i > numVals { // expect proposers to cycle through after the first iteration (of numVals blocks): + if proposers[i%numVals] == nil { + proposers[i%numVals] = updatedState.NextValidators.Proposer + } else { + assert.Equal(t, proposers[i%numVals], updatedState.NextValidators.Proposer) + } + } + } } func TestStoreLoadValidatorsIncrementsProposerPriority(t *testing.T) { diff --git a/types/validator_set.go b/types/validator_set.go index 8b2d71b8..4040810f 100644 --- a/types/validator_set.go +++ b/types/validator_set.go @@ -13,13 +13,13 @@ import ( ) // The maximum allowed total voting power. -// We set the ProposerPriority of freshly added validators to -1.125*totalVotingPower. -// To compute 1.125*totalVotingPower efficiently, we do: -// totalVotingPower + (totalVotingPower >> 3) because -// 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 = int64(8198552921648689607) +// It needs to be sufficiently small to, in all cases:: +// 1. prevent clipping in incrementProposerPriority() +// 2. let (diff+diffMax-1) not overflow in IncrementPropposerPriotity() +// (Proof of 1 is tricky, left to the reader). +// It could be higher, but this is sufficiently large for our purposes, +// and leaves room for defensive purposes. +const MaxTotalVotingPower = int64(math.MaxInt64) / 8 // ValidatorSet represent a set of *Validator at a given height. // The validators can be fetched by address or index. @@ -78,44 +78,57 @@ func (vals *ValidatorSet) IncrementProposerPriority(times int) { panic("Cannot call IncrementProposerPriority with non-positive times") } - const shiftEveryNthIter = 10 + // Cap the difference between priorities to be proportional to 2*totalPower by + // re-normalizing priorities, i.e., rescale all priorities by multiplying with: + // 2*totalVotingPower/(maxPriority - minPriority) + diffMax := 2 * vals.TotalVotingPower() + vals.RescalePriorities(diffMax) + var proposer *Validator // call IncrementProposerPriority(1) times times: for i := 0; i < times; i++ { - shiftByAvgProposerPriority := i%shiftEveryNthIter == 0 - proposer = vals.incrementProposerPriority(shiftByAvgProposerPriority) - } - isShiftedAvgOnLastIter := (times-1)%shiftEveryNthIter == 0 - if !isShiftedAvgOnLastIter { - validatorsHeap := cmn.NewHeap() - vals.shiftByAvgProposerPriority(validatorsHeap) + proposer = vals.incrementProposerPriority() } + vals.shiftByAvgProposerPriority() + vals.Proposer = proposer } -func (vals *ValidatorSet) incrementProposerPriority(subAvg bool) *Validator { - for _, val := range vals.Validators { - // Check for overflow for sum. - val.ProposerPriority = safeAddClip(val.ProposerPriority, val.VotingPower) +func (vals *ValidatorSet) RescalePriorities(diffMax int64) { + // NOTE: This check is merely a sanity check which could be + // removed if all tests would init. voting power appropriately; + // i.e. diffMax should always be > 0 + if diffMax == 0 { + return } - validatorsHeap := cmn.NewHeap() - if subAvg { // shift by avg ProposerPriority - vals.shiftByAvgProposerPriority(validatorsHeap) - } else { // just update the heap + // Caculating ceil(diff/diffMax): + // Re-normalization is performed by dividing by an integer for simplicity. + // NOTE: This may make debugging priority issues easier as well. + diff := computeMaxMinPriorityDiff(vals) + ratio := (diff + diffMax - 1) / diffMax + if ratio > 1 { for _, val := range vals.Validators { - validatorsHeap.PushComparable(val, proposerPriorityComparable{val}) + val.ProposerPriority /= ratio } } +} +func (vals *ValidatorSet) incrementProposerPriority() *Validator { + for _, val := range vals.Validators { + // Check for overflow for sum. + newPrio := safeAddClip(val.ProposerPriority, val.VotingPower) + val.ProposerPriority = newPrio + } // Decrement the validator with most ProposerPriority: - mostest := validatorsHeap.Peek().(*Validator) + mostest := vals.getValWithMostPriority() // mind underflow mostest.ProposerPriority = safeSubClip(mostest.ProposerPriority, vals.TotalVotingPower()) return mostest } +// should not be called on an empty validator set func (vals *ValidatorSet) computeAvgProposerPriority() int64 { n := int64(len(vals.Validators)) sum := big.NewInt(0) @@ -131,11 +144,38 @@ func (vals *ValidatorSet) computeAvgProposerPriority() int64 { panic(fmt.Sprintf("Cannot represent avg ProposerPriority as an int64 %v", avg)) } -func (vals *ValidatorSet) shiftByAvgProposerPriority(validatorsHeap *cmn.Heap) { +// compute the difference between the max and min ProposerPriority of that set +func computeMaxMinPriorityDiff(vals *ValidatorSet) int64 { + max := int64(math.MinInt64) + min := int64(math.MaxInt64) + for _, v := range vals.Validators { + if v.ProposerPriority < min { + min = v.ProposerPriority + } + if v.ProposerPriority > max { + max = v.ProposerPriority + } + } + diff := max - min + if diff < 0 { + return -1 * diff + } else { + return diff + } +} + +func (vals *ValidatorSet) getValWithMostPriority() *Validator { + var res *Validator + for _, val := range vals.Validators { + res = res.CompareProposerPriority(val) + } + return res +} + +func (vals *ValidatorSet) shiftByAvgProposerPriority() { avgProposerPriority := vals.computeAvgProposerPriority() for _, val := range vals.Validators { val.ProposerPriority = safeSubClip(val.ProposerPriority, avgProposerPriority) - validatorsHeap.PushComparable(val, proposerPriorityComparable{val}) } } @@ -508,20 +548,6 @@ func (valz ValidatorsByAddress) Swap(i, j int) { valz[j] = it } -//------------------------------------- -// Use with Heap for sorting validators by ProposerPriority - -type proposerPriorityComparable struct { - *Validator -} - -// We want to find the validator with the greatest ProposerPriority. -func (ac proposerPriorityComparable) Less(o interface{}) bool { - other := o.(proposerPriorityComparable).Validator - larger := ac.CompareProposerPriority(other) - return bytes.Equal(larger.Address, ac.Address) -} - //---------------------------------------- // For testing diff --git a/types/validator_set_test.go b/types/validator_set_test.go index 26793cc1..dd49ee16 100644 --- a/types/validator_set_test.go +++ b/types/validator_set_test.go @@ -392,10 +392,16 @@ func TestAveragingInIncrementProposerPriority(t *testing.T) { 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. + // average is zero in each round: + vp0 := int64(10) + vp1 := int64(1) + vp2 := int64(1) + total := vp0 + vp1 + vp2 + avg := (vp0 + vp1 + vp2 - total) / 3 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}}} + {Address: []byte{0}, ProposerPriority: 0, VotingPower: vp0}, + {Address: []byte{1}, ProposerPriority: 0, VotingPower: vp1}, + {Address: []byte{2}, ProposerPriority: 0, VotingPower: vp2}}} tcs := []struct { vals *ValidatorSet wantProposerPrioritys []int64 @@ -407,95 +413,89 @@ func TestAveragingInIncrementProposerPriorityWithVotingPower(t *testing.T) { 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}, + 0 + vp0 - total - avg, // mostest will be subtracted by total voting power (12) + 0 + vp1, + 0 + vp2}, 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}, + (0 + vp0 - total) + vp0 - total - avg, // this will be mostest on 2nd iter, too + (0 + vp1) + vp1, + (0 + vp2) + vp2}, 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}, + 0 + 3*(vp0-total) - avg, // still mostest + 0 + 3*vp1, + 0 + 3*vp2}, 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}, + 0 + 4*(vp0-total), // still mostest + 0 + 4*vp1, + 0 + 4*vp2}, 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}, + 0 + 4*(vp0-total) + vp0, // 4 iters was mostest + 0 + 5*vp1 - total, // now this val is mostest for the 1st time (hence -12==totalVotingPower) + 0 + 5*vp2}, 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}, + 0 + 6*vp0 - 5*total, // mostest again + 0 + 6*vp1 - total, // mostest once up to here + 0 + 6*vp2}, 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}, + 0 + 7*vp0 - 6*total, // in 7 iters this val is mostest 6 times + 0 + 7*vp1 - total, // in 7 iters this val is mostest 1 time + 0 + 7*vp2}, 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}, + 0 + 8*vp0 - 7*total, // mostest again + 0 + 8*vp1 - total, + 0 + 8*vp2}, 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 + 0 + 9*vp0 - 7*total, + 0 + 9*vp1 - total, + 0 + 9*vp2 - total}, // 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 + 0 + 10*vp0 - 8*total, // after 10 iters this is mostest again + 0 + 10*vp1 - total, // after 6 iters this val is "mostest" once and not in between + 0 + 10*vp2 - total}, // 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 + 0 + 11*vp0 - 9*total, + 0 + 11*vp1 - total, // after 6 iters this val is "mostest" once and not in between + 0 + 11*vp2 - total}, // after 10 iters this val is "mostest" once 11, vals.Validators[0]}, } From 4f8769175ed938a031c329de7da80821edbf7880 Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Sat, 19 Jan 2019 16:08:57 -0500 Subject: [PATCH 213/267] [types] hash of ConsensusParams includes only a subset of fields (#3165) * types: dont hash entire ConsensusParams * update encoding spec * update blockchain spec * spec: consensus params hash * changelog --- CHANGELOG_PENDING.md | 10 ++- docs/spec/blockchain/blockchain.md | 75 +++++++++---------- docs/spec/blockchain/encoding.md | 112 +++++++++++++++++++---------- docs/spec/blockchain/state.md | 14 ++++ types/params.go | 21 ++++-- 5 files changed, 148 insertions(+), 84 deletions(-) diff --git a/CHANGELOG_PENDING.md b/CHANGELOG_PENDING.md index 19e10623..392b61da 100644 --- a/CHANGELOG_PENDING.md +++ b/CHANGELOG_PENDING.md @@ -7,7 +7,6 @@ Special thanks to external contributors on this release: ### BREAKING CHANGES: * CLI/RPC/Config -- [types] consistent field order of `CanonicalVote` and `CanonicalProposal` * Apps @@ -16,6 +15,11 @@ Special thanks to external contributors on this release: * Blockchain Protocol * [merkle] \#2713 Merkle trees now match the RFC 6962 specification + * [types] \#3078 Re-order Timestamp and BlockID in CanonicalVote so it's + consistent with CanonicalProposal (BlockID comes + first) + * [types] \#3165 Hash of ConsensusParams only includes BlockSize.MaxBytes and + BlockSize.MaxGas * P2P Protocol - [consensus] \#2960 normalize priorities to not exceed `2*TotalVotingPower` to mitigate unfair proposer selection @@ -24,8 +28,8 @@ Special thanks to external contributors on this release: ### FEATURES: ### IMPROVEMENTS: -- [rpc] \#3065 return maxPerPage (100), not defaultPerPage (30) if `per_page` is greater than the max 100. -- [instrumentation] \#3082 add 'chain_id' label for all metrics +- [rpc] \#3065 Return maxPerPage (100), not defaultPerPage (30) if `per_page` is greater than the max 100. +- [instrumentation] \#3082 Add `chain_id` label for all metrics ### BUG FIXES: - [log] \#3060 Fix year format diff --git a/docs/spec/blockchain/blockchain.md b/docs/spec/blockchain/blockchain.md index f80c8c05..92b55e35 100644 --- a/docs/spec/blockchain/blockchain.md +++ b/docs/spec/blockchain/blockchain.md @@ -51,7 +51,7 @@ type Header struct { // hashes of block data LastCommitHash []byte // commit from validators from the last block - DataHash []byte // Merkle root of transactions + DataHash []byte // MerkleRoot of transactions // hashes from the app output from the prev block ValidatorsHash []byte // validators for the current block @@ -83,25 +83,27 @@ type Version struct { ## BlockID The `BlockID` contains two distinct Merkle roots of the block. -The first, used as the block's main hash, is the Merkle root -of all the fields in the header. The second, used for secure gossipping of -the block during consensus, is the Merkle root of the complete serialized block -cut into parts. The `BlockID` includes these two hashes, as well as the number of -parts. +The first, used as the block's main hash, is the MerkleRoot +of all the fields in the header (ie. `MerkleRoot(header)`. +The second, used for secure gossipping of the block during consensus, +is the MerkleRoot of the complete serialized block +cut into parts (ie. `MerkleRoot(MakeParts(block))`). +The `BlockID` includes these two hashes, as well as the number of +parts (ie. `len(MakeParts(block))`) ```go type BlockID struct { Hash []byte - Parts PartsHeader + PartsHeader PartSetHeader } -type PartsHeader struct { - Hash []byte +type PartSetHeader struct { Total int32 + Hash []byte } ``` -TODO: link to details of merkle sums. +See [MerkleRoot](/docs/spec/blockchain/encoding.md#MerkleRoot) for details. ## Time @@ -142,12 +144,12 @@ The vote includes information about the validator signing it. ```go type Vote struct { - Type SignedMsgType // byte + Type byte Height int64 Round int - Timestamp time.Time BlockID BlockID - ValidatorAddress Address + Timestamp Time + ValidatorAddress []byte ValidatorIndex int Signature []byte } @@ -160,8 +162,8 @@ a _precommit_ has `vote.Type == 2`. ## Signature Signatures in Tendermint are raw bytes representing the underlying signature. -The only signature scheme currently supported for Tendermint validators is -ED25519. The signature is the raw 64-byte ED25519 signature. + +See the [signature spec](/docs/spec/blockchain/encoding.md#key-types) for more. ## EvidenceData @@ -188,6 +190,8 @@ type DuplicateVoteEvidence struct { } ``` +See the [pubkey spec](/docs/spec/blockchain/encoding.md#key-types) for more. + ## Validation Here we describe the validation rules for every element in a block. @@ -205,7 +209,7 @@ the current version of the `state` corresponds to the state after executing transactions from the `prevBlock`. Elements of an object are accessed as expected, ie. `block.Header`. -See [here](https://github.com/tendermint/tendermint/blob/master/docs/spec/blockchain/state.md) for the definition of `state`. +See the [definition of `State`](/docs/spec/blockchain/state.md). ### Header @@ -284,28 +288,25 @@ The first block has `block.Header.TotalTxs = block.Header.NumberTxs`. LastBlockID is the previous block's BlockID: ```go -prevBlockParts := MakeParts(prevBlock, state.LastConsensusParams.BlockGossip.BlockPartSize) +prevBlockParts := MakeParts(prevBlock) block.Header.LastBlockID == BlockID { - Hash: SimpleMerkleRoot(prevBlock.Header), + Hash: MerkleRoot(prevBlock.Header), PartsHeader{ - Hash: SimpleMerkleRoot(prevBlockParts), + Hash: MerkleRoot(prevBlockParts), Total: len(prevBlockParts), }, } ``` -Note: it depends on the ConsensusParams, -which are held in the `state` and may be updated by the application. - The first block has `block.Header.LastBlockID == BlockID{}`. ### LastCommitHash ```go -block.Header.LastCommitHash == SimpleMerkleRoot(block.LastCommit) +block.Header.LastCommitHash == MerkleRoot(block.LastCommit) ``` -Simple Merkle root of the votes included in the block. +MerkleRoot of the votes included in the block. These are the votes that committed the previous block. The first block has `block.Header.LastCommitHash == []byte{}` @@ -313,37 +314,37 @@ The first block has `block.Header.LastCommitHash == []byte{}` ### DataHash ```go -block.Header.DataHash == SimpleMerkleRoot(block.Txs.Txs) +block.Header.DataHash == MerkleRoot(block.Txs.Txs) ``` -Simple Merkle root of the transactions included in the block. +MerkleRoot of the transactions included in the block. ### ValidatorsHash ```go -block.ValidatorsHash == SimpleMerkleRoot(state.Validators) +block.ValidatorsHash == MerkleRoot(state.Validators) ``` -Simple Merkle root of the current validator set that is committing the block. +MerkleRoot of the current validator set that is committing the block. This can be used to validate the `LastCommit` included in the next block. ### NextValidatorsHash ```go -block.NextValidatorsHash == SimpleMerkleRoot(state.NextValidators) +block.NextValidatorsHash == MerkleRoot(state.NextValidators) ``` -Simple Merkle root of the next validator set that will be the validator set that commits the next block. +MerkleRoot of the next validator set that will be the validator set that commits the next block. This is included so that the current validator set gets a chance to sign the next validator sets Merkle root. -### ConsensusParamsHash +### ConsensusHash ```go -block.ConsensusParamsHash == TMHASH(amino(state.ConsensusParams)) +block.ConsensusHash == state.ConsensusParams.Hash() ``` -Hash of the amino-encoded consensus parameters. +Hash of the amino-encoding of a subset of the consensus parameters. ### AppHash @@ -358,20 +359,20 @@ The first block has `block.Header.AppHash == []byte{}`. ### LastResultsHash ```go -block.ResultsHash == SimpleMerkleRoot(state.LastResults) +block.ResultsHash == MerkleRoot(state.LastResults) ``` -Simple Merkle root of the results of the transactions in the previous block. +MerkleRoot of the results of the transactions in the previous block. The first block has `block.Header.ResultsHash == []byte{}`. ## EvidenceHash ```go -block.EvidenceHash == SimpleMerkleRoot(block.Evidence) +block.EvidenceHash == MerkleRoot(block.Evidence) ``` -Simple Merkle root of the evidence of Byzantine behaviour included in this block. +MerkleRoot of the evidence of Byzantine behaviour included in this block. ### ProposerAddress diff --git a/docs/spec/blockchain/encoding.md b/docs/spec/blockchain/encoding.md index aefe1e7f..9552ab07 100644 --- a/docs/spec/blockchain/encoding.md +++ b/docs/spec/blockchain/encoding.md @@ -30,6 +30,12 @@ For example, the byte-array `[0xA, 0xB]` would be encoded as `0x020A0B`, while a byte-array containing 300 entires beginning with `[0xA, 0xB, ...]` would be encoded as `0xAC020A0B...` where `0xAC02` is the UVarint encoding of 300. +## Hashing + +Tendermint uses `SHA256` as its hash function. +Objects are always Amino encoded before being hashed. +So `SHA256(obj)` is short for `SHA256(AminoEncode(obj))`. + ## Public Key Cryptography Tendermint uses Amino to distinguish between different types of private keys, @@ -68,23 +74,27 @@ For example, the 33-byte (or 0x21-byte in hex) Secp256k1 pubkey would be encoded as `EB5AE98721020BD40F225A57ED383B440CF073BC5539D0341F5767D2BF2D78406D00475A2EE9` -### Addresses +### Key Types -Addresses for each public key types are computed as follows: +Each type specifies it's own pubkey, address, and signature format. #### Ed25519 -First 20-bytes of the SHA256 hash of the raw 32-byte public key: +TODO: pubkey + +The address is the first 20-bytes of the SHA256 hash of the raw 32-byte public key: ``` address = SHA256(pubkey)[:20] ``` -NOTE: before v0.22.0, this was the RIPEMD160 of the Amino encoded public key. +The signature is the raw 64-byte ED25519 signature. #### Secp256k1 -RIPEMD160 hash of the SHA256 hash of the OpenSSL compressed public key: +TODO: pubkey + +The address is the RIPEMD160 hash of the SHA256 hash of the OpenSSL compressed public key: ``` address = RIPEMD160(SHA256(pubkey)) @@ -92,12 +102,21 @@ address = RIPEMD160(SHA256(pubkey)) This is the same as Bitcoin. +The signature is the 64-byte concatenation of ECDSA `r` and `s` (ie. `r || s`), +where `s` is lexicographically less than its inverse, to prevent malleability. +This is like Ethereum, but without the extra byte for pubkey recovery, since +Tendermint assumes the pubkey is always provided anyway. + +#### Multisig + +TODO + ## Other Common Types ### BitArray -The BitArray is used in block headers and some consensus messages to signal -whether or not something was done by each validator. BitArray is represented +The BitArray is used in some consensus messages to represent votes received from +validators, or parts received in a block. It is represented with a struct containing the number of bits (`Bits`) and the bit-array itself encoded in base64 (`Elems`). @@ -119,24 +138,27 @@ representing `1` and `0`. Ie. the BitArray `10110` would be JSON encoded as Part is used to break up blocks into pieces that can be gossiped in parallel and securely verified using a Merkle tree of the parts. -Part contains the index of the part in the larger set (`Index`), the actual -underlying data of the part (`Bytes`), and a simple Merkle proof that the part is contained in -the larger set (`Proof`). +Part contains the index of the part (`Index`), the actual +underlying data of the part (`Bytes`), and a Merkle proof that the part is contained in +the set (`Proof`). ```go type Part struct { Index int - Bytes byte[] - Proof byte[] + Bytes []byte + Proof SimpleProof } ``` +See details of SimpleProof, below. + ### MakeParts Encode an object using Amino and slice it into parts. +Tendermint uses a part size of 65536 bytes. ```go -func MakeParts(obj interface{}, partSize int) []Part +func MakeParts(block Block) []Part ``` ## Merkle Trees @@ -144,12 +166,12 @@ func MakeParts(obj interface{}, partSize int) []Part For an overview of Merkle trees, see [wikipedia](https://en.wikipedia.org/wiki/Merkle_tree) -We use the RFC 6962 specification of a merkle tree, instantiated with sha256 as the hash function. +We use the RFC 6962 specification of a merkle tree, with sha256 as the hash function. Merkle trees are used throughout Tendermint to compute a cryptographic digest of a data structure. The differences between RFC 6962 and the simplest form a merkle tree are that: -1) leaf nodes and inner nodes have different hashes. - This is to prevent a proof to an inner node, claiming that it is the hash of the leaf. +1) leaf nodes and inner nodes have different hashes. + This is for "second pre-image resistance", to prevent the proof to an inner node being valid as the proof of a leaf. The leaf nodes are `SHA256(0x00 || leaf_data)`, and inner nodes are `SHA256(0x01 || left_hash || right_hash)`. 2) When the number of items isn't a power of two, the left half of the tree is as big as it could be. @@ -173,46 +195,64 @@ The differences between RFC 6962 and the simplest form a merkle tree are that: h0 h1 h2 h3 h0 h1 h2 h3 h4 h5 ``` -### Simple Merkle Root +### MerkleRoot The function `MerkleRoot` is a simple recursive function defined as follows: ```go -func MerkleRootFromLeafs(leafs [][]byte) []byte{ +// SHA256(0x00 || leaf) +func leafHash(leaf []byte) []byte { + return tmhash.Sum(append(0x00, leaf...)) +} + +// SHA256(0x01 || left || right) +func innerHash(left []byte, right []byte) []byte { + return tmhash.Sum(append(0x01, append(left, right...)...)) +} + +// largest power of 2 less than k +func getSplitPoint(k int) { ... } + +func MerkleRoot(leafs [][]byte) []byte{ switch len(items) { case 0: return nil case 1: - return leafHash(leafs[0]) // SHA256(0x00 || leafs[0]) + return leafHash(leafs[0]) default: - k := getSplitPoint(len(items)) // largest power of two smaller than items - left := MerkleRootFromLeafs(items[:k]) - right := MerkleRootFromLeafs(items[k:]) - return innerHash(left, right) // SHA256(0x01 || left || right) + k := getSplitPoint(len(items)) + left := MerkleRoot(items[:k]) + right := MerkleRoot(items[k:]) + return innerHash(left, right) } } ``` -Note: we will abuse notion and invoke `SimpleMerkleRoot` with arguments of type `struct` or type `[]struct`. +Note: we will abuse notion and invoke `MerkleRoot` with arguments of type `struct` or type `[]struct`. For `struct` arguments, we compute a `[][]byte` containing the hash of each field in the struct, in the same order the fields appear in the struct. For `[]struct` arguments, we compute a `[][]byte` by hashing the individual `struct` elements. ### Simple Merkle Proof -Proof that a leaf is in a Merkle tree consists of a simple structure: +Proof that a leaf is in a Merkle tree is composed as follows: ```golang type SimpleProof struct { + Total int + Index int + LeafHash []byte Aunts [][]byte } ``` -Which is verified using the following: +Which is verified as follows: ```golang -func (proof SimpleProof) Verify(index, total int, leafHash, rootHash []byte) bool { - computedHash := computeHashFromAunts(index, total, leafHash, proof.Aunts) +func (proof SimpleProof) Verify(rootHash []byte, leaf []byte) bool { + assert(proof.LeafHash, leafHash(leaf) + + computedHash := computeHashFromAunts(proof.Index, proof.Total, proof.LeafHash, proof.Aunts) return computedHash == rootHash } @@ -230,22 +270,14 @@ func computeHashFromAunts(index, total int, leafHash []byte, innerHashes [][]byt if index < numLeft { leftHash := computeHashFromAunts(index, numLeft, leafHash, innerHashes[:len(innerHashes)-1]) assert(leftHash != nil) - return SimpleHashFromTwoHashes(leftHash, innerHashes[len(innerHashes)-1]) + return innerHash(leftHash, innerHashes[len(innerHashes)-1]) } rightHash := computeHashFromAunts(index-numLeft, total-numLeft, leafHash, innerHashes[:len(innerHashes)-1]) assert(rightHash != nil) - return SimpleHashFromTwoHashes(innerHashes[len(innerHashes)-1], rightHash) + return innerHash(innerHashes[len(innerHashes)-1], rightHash) } ``` -### Simple Tree with Dictionaries - -The Simple Tree is used to merkelize a list of items, so to merkelize a -(short) dictionary of key-value pairs, encode the dictionary as an -ordered list of `KVPair` structs. The block hash is such a hash -derived from all the fields of the block `Header`. The state hash is -similarly derived. - ### IAVL+ Tree Because Tendermint only uses a Simple Merkle Tree, application developers are expect to use their own Merkle tree in their applications. For example, the IAVL+ Tree - an immutable self-balancing binary tree for persisting application state is used by the [Cosmos SDK](https://github.com/cosmos/cosmos-sdk/blob/develop/docs/sdk/core/multistore.md) @@ -297,4 +329,6 @@ type CanonicalVote struct { The field ordering and the fixed sized encoding for the first three fields is optimized to ease parsing of SignBytes in HSMs. It creates fixed offsets for relevant fields that need to be read in this context. -See [#1622](https://github.com/tendermint/tendermint/issues/1622) for more details. +For more details, see the [signing spec](/docs/spec/consensus/signing.md). +Also, see the motivating discussion in +[#1622](https://github.com/tendermint/tendermint/issues/1622). diff --git a/docs/spec/blockchain/state.md b/docs/spec/blockchain/state.md index 0b46e503..ff6fcf2e 100644 --- a/docs/spec/blockchain/state.md +++ b/docs/spec/blockchain/state.md @@ -78,6 +78,8 @@ func TotalVotingPower(vals []Validators) int64{ ConsensusParams define various limits for blockchain data structures. Like validator sets, they are set during genesis and can be updated by the application through ABCI. +When hashed, only a subset of the params are included, to allow the params to +evolve without breaking the header. ```go type ConsensusParams struct { @@ -86,6 +88,18 @@ type ConsensusParams struct { Validator } +type hashedParams struct { + BlockMaxBytes int64 + BlockMaxGas int64 +} + +func (params ConsensusParams) Hash() []byte { + SHA256(hashedParams{ + BlockMaxBytes: params.BlockSize.MaxBytes, + BlockMaxGas: params.BlockSize.MaxGas, + }) +} + type BlockSize struct { MaxBytes int64 MaxGas int64 diff --git a/types/params.go b/types/params.go index 91079e76..03e43c19 100644 --- a/types/params.go +++ b/types/params.go @@ -22,6 +22,14 @@ type ConsensusParams struct { Validator ValidatorParams `json:"validator"` } +// HashedParams is a subset of ConsensusParams. +// It is amino encoded and hashed into +// the Header.ConsensusHash. +type HashedParams struct { + BlockMaxBytes int64 + BlockMaxGas int64 +} + // BlockSizeParams define limits on the block size. type BlockSizeParams struct { MaxBytes int64 `json:"max_bytes"` @@ -116,13 +124,16 @@ func (params *ConsensusParams) Validate() error { return nil } -// Hash returns a hash of the parameters to store in the block header -// No Merkle tree here, only three values are hashed here -// thus benefit from saving space < drawbacks from proofs' overhead -// Revisit this function if new fields are added to ConsensusParams +// Hash returns a hash of a subset of the parameters to store in the block header. +// Only the Block.MaxBytes and Block.MaxGas are included in the hash. +// This allows the ConsensusParams to evolve more without breaking the block +// protocol. No need for a Merkle tree here, just a small struct to hash. func (params *ConsensusParams) Hash() []byte { hasher := tmhash.New() - bz := cdcEncode(params) + bz := cdcEncode(HashedParams{ + params.BlockSize.MaxBytes, + params.BlockSize.MaxGas, + }) if bz == nil { panic("cannot fail to encode ConsensusParams") } From da95f4aa6da2b966fe9243e481e6cfb3bf3b2c5a Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Sun, 20 Jan 2019 17:27:49 -0500 Subject: [PATCH 214/267] mempool: enforce maxMsgSize limit in CheckTx (#3168) - fixes #3008 - reactor requires encoded messages are less than maxMsgSize - requires size of tx + amino-overhead to not exceed maxMsgSize --- mempool/mempool.go | 10 ++++++++ mempool/mempool_test.go | 56 +++++++++++++++++++++++++++++++++++++++++ mempool/reactor.go | 12 +++------ 3 files changed, 70 insertions(+), 8 deletions(-) diff --git a/mempool/mempool.go b/mempool/mempool.go index 3a1921bc..9069dab6 100644 --- a/mempool/mempool.go +++ b/mempool/mempool.go @@ -65,6 +65,9 @@ var ( // ErrMempoolIsFull means Tendermint & an application can't handle that much load ErrMempoolIsFull = errors.New("Mempool is full") + + // ErrTxTooLarge means the tx is too big to be sent in a message to other peers + ErrTxTooLarge = fmt.Errorf("Tx too large. Max size is %d", maxTxSize) ) // ErrPreCheck is returned when tx is too big @@ -309,6 +312,13 @@ func (mem *Mempool) CheckTx(tx types.Tx, cb func(*abci.Response)) (err error) { return ErrMempoolIsFull } + // The size of the corresponding amino-encoded TxMessage + // can't be larger than the maxMsgSize, otherwise we can't + // relay it to peers. + if len(tx) > maxTxSize { + return ErrTxTooLarge + } + if mem.preCheck != nil { if err := mem.preCheck(tx); err != nil { return ErrPreCheck{err} diff --git a/mempool/mempool_test.go b/mempool/mempool_test.go index 15bfaa25..9d21e734 100644 --- a/mempool/mempool_test.go +++ b/mempool/mempool_test.go @@ -14,10 +14,12 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + amino "github.com/tendermint/go-amino" "github.com/tendermint/tendermint/abci/example/counter" "github.com/tendermint/tendermint/abci/example/kvstore" abci "github.com/tendermint/tendermint/abci/types" cfg "github.com/tendermint/tendermint/config" + cmn "github.com/tendermint/tendermint/libs/common" "github.com/tendermint/tendermint/libs/log" "github.com/tendermint/tendermint/proxy" "github.com/tendermint/tendermint/types" @@ -394,6 +396,60 @@ func TestMempoolCloseWAL(t *testing.T) { require.Equal(t, 1, len(m3), "expecting the wal match in") } +// Size of the amino encoded TxMessage is the length of the +// encoded byte array, plus 1 for the struct field, plus 4 +// for the amino prefix. +func txMessageSize(tx types.Tx) int { + return amino.ByteSliceSize(tx) + 1 + 4 +} + +func TestMempoolMaxMsgSize(t *testing.T) { + app := kvstore.NewKVStoreApplication() + cc := proxy.NewLocalClientCreator(app) + mempl := newMempoolWithApp(cc) + + testCases := []struct { + len int + err bool + }{ + // check small txs. no error + {10, false}, + {1000, false}, + {1000000, false}, + + // check around maxTxSize + // changes from no error to error + {maxTxSize - 2, false}, + {maxTxSize - 1, false}, + {maxTxSize, false}, + {maxTxSize + 1, true}, + {maxTxSize + 2, true}, + + // check around maxMsgSize. all error + {maxMsgSize - 1, true}, + {maxMsgSize, true}, + {maxMsgSize + 1, true}, + } + + for i, testCase := range testCases { + caseString := fmt.Sprintf("case %d, len %d", i, testCase.len) + + tx := cmn.RandBytes(testCase.len) + err := mempl.CheckTx(tx, nil) + msg := &TxMessage{tx} + encoded := cdc.MustMarshalBinaryBare(msg) + require.Equal(t, len(encoded), txMessageSize(tx), caseString) + if !testCase.err { + require.True(t, len(encoded) <= maxMsgSize, caseString) + require.NoError(t, err, caseString) + } else { + require.True(t, len(encoded) > maxMsgSize, caseString) + require.Equal(t, err, ErrTxTooLarge, caseString) + } + } + +} + func checksumIt(data []byte) string { h := md5.New() h.Write(data) diff --git a/mempool/reactor.go b/mempool/reactor.go index 072f9667..ff87f050 100644 --- a/mempool/reactor.go +++ b/mempool/reactor.go @@ -6,7 +6,6 @@ import ( "time" amino "github.com/tendermint/go-amino" - abci "github.com/tendermint/tendermint/abci/types" "github.com/tendermint/tendermint/libs/clist" "github.com/tendermint/tendermint/libs/log" @@ -18,8 +17,10 @@ import ( const ( MempoolChannel = byte(0x30) - maxMsgSize = 1048576 // 1MB TODO make it configurable - peerCatchupSleepIntervalMS = 100 // If peer is behind, sleep this amount + maxMsgSize = 1048576 // 1MB TODO make it configurable + maxTxSize = maxMsgSize - 8 // account for amino overhead of TxMessage + + peerCatchupSleepIntervalMS = 100 // If peer is behind, sleep this amount ) // MempoolReactor handles mempool tx broadcasting amongst peers. @@ -98,11 +99,6 @@ func (memR *MempoolReactor) Receive(chID byte, src p2p.Peer, msgBytes []byte) { } } -// BroadcastTx is an alias for Mempool.CheckTx. Broadcasting itself happens in peer routines. -func (memR *MempoolReactor) BroadcastTx(tx types.Tx, cb func(*abci.Response)) error { - return memR.Mempool.CheckTx(tx, cb) -} - // PeerState describes the state of a peer. type PeerState interface { GetHeight() int64 From de5a6010f0d635c603c473b6a4870e0e70f129b8 Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Mon, 21 Jan 2019 09:21:04 -0500 Subject: [PATCH 215/267] fix DynamicVerifier for large validator set changes (#3171) * base verifier: bc->bv and check chainid * improve some comments * comments in dynamic verifier * fix comment in doc about BaseVerifier It requires the validator set to perfectly match. * failing test for #2862 * move errTooMuchChange to types. fixes #2862 * changelog, comments * ic -> dv * update comment, link to issue --- CHANGELOG_PENDING.md | 6 +- lite/base_verifier.go | 28 +++++--- lite/commit.go | 2 +- lite/dbprovider.go | 3 + lite/doc.go | 4 -- lite/dynamic_verifier.go | 127 +++++++++++++++++++--------------- lite/dynamic_verifier_test.go | 65 +++++++++++++++++ lite/errors/errors.go | 22 ------ lite/multiprovider.go | 2 + lite/provider.go | 2 +- types/block.go | 1 + types/validator_set.go | 32 +++++++-- 12 files changed, 194 insertions(+), 100 deletions(-) diff --git a/CHANGELOG_PENDING.md b/CHANGELOG_PENDING.md index 392b61da..af1c5566 100644 --- a/CHANGELOG_PENDING.md +++ b/CHANGELOG_PENDING.md @@ -22,7 +22,7 @@ Special thanks to external contributors on this release: BlockSize.MaxGas * P2P Protocol - - [consensus] \#2960 normalize priorities to not exceed `2*TotalVotingPower` to mitigate unfair proposer selection + - [consensus] \#2960 normalize priorities to not exceed `2*TotalVotingPower` to mitigate unfair proposer selection heavily preferring earlier joined validators in the case of an early bonded large validator unbonding ### FEATURES: @@ -32,5 +32,7 @@ Special thanks to external contributors on this release: - [instrumentation] \#3082 Add `chain_id` label for all metrics ### BUG FIXES: -- [log] \#3060 Fix year format - [crypto] \#3164 Update `btcd` fork for rare signRFC6979 bug +- [lite] \#3171 Fix verifying large validator set changes +- [log] \#3060 Fix year format +- [mempool] \#3168 Limit tx size to fit in the max reactor msg size diff --git a/lite/base_verifier.go b/lite/base_verifier.go index fcde01c0..9eb880bb 100644 --- a/lite/base_verifier.go +++ b/lite/base_verifier.go @@ -35,34 +35,40 @@ func NewBaseVerifier(chainID string, height int64, valset *types.ValidatorSet) * } // Implements Verifier. -func (bc *BaseVerifier) ChainID() string { - return bc.chainID +func (bv *BaseVerifier) ChainID() string { + return bv.chainID } // Implements Verifier. -func (bc *BaseVerifier) Verify(signedHeader types.SignedHeader) error { +func (bv *BaseVerifier) Verify(signedHeader types.SignedHeader) error { - // We can't verify commits older than bc.height. - if signedHeader.Height < bc.height { + // We can't verify commits for a different chain. + if signedHeader.ChainID != bv.chainID { + return cmn.NewError("BaseVerifier chainID is %v, cannot verify chainID %v", + bv.chainID, signedHeader.ChainID) + } + + // We can't verify commits older than bv.height. + if signedHeader.Height < bv.height { return cmn.NewError("BaseVerifier height is %v, cannot verify height %v", - bc.height, signedHeader.Height) + bv.height, signedHeader.Height) } // We can't verify with the wrong validator set. if !bytes.Equal(signedHeader.ValidatorsHash, - bc.valset.Hash()) { - return lerr.ErrUnexpectedValidators(signedHeader.ValidatorsHash, bc.valset.Hash()) + bv.valset.Hash()) { + return lerr.ErrUnexpectedValidators(signedHeader.ValidatorsHash, bv.valset.Hash()) } // Do basic sanity checks. - err := signedHeader.ValidateBasic(bc.chainID) + err := signedHeader.ValidateBasic(bv.chainID) if err != nil { return cmn.ErrorWrap(err, "in verify") } // Check commit signatures. - err = bc.valset.VerifyCommit( - bc.chainID, signedHeader.Commit.BlockID, + err = bv.valset.VerifyCommit( + bv.chainID, signedHeader.Commit.BlockID, signedHeader.Height, signedHeader.Commit) if err != nil { return cmn.ErrorWrap(err, "in verify") diff --git a/lite/commit.go b/lite/commit.go index 25efb8dc..6cd35417 100644 --- a/lite/commit.go +++ b/lite/commit.go @@ -8,7 +8,7 @@ import ( "github.com/tendermint/tendermint/types" ) -// FullCommit is a signed header (the block header and a commit that signs it), +// FullCommit contains a SignedHeader (the block header and a commit that signs it), // the validator set which signed the commit, and the next validator set. The // next validator set (which is proven from the block header) allows us to // revert to block-by-block updating of lite Verifier's latest validator set, diff --git a/lite/dbprovider.go b/lite/dbprovider.go index 9f4b264f..ef1b2a59 100644 --- a/lite/dbprovider.go +++ b/lite/dbprovider.go @@ -13,6 +13,9 @@ import ( "github.com/tendermint/tendermint/types" ) +var _ PersistentProvider = (*DBProvider)(nil) + +// DBProvider stores commits and validator sets in a DB. type DBProvider struct { logger log.Logger label string diff --git a/lite/doc.go b/lite/doc.go index f68798dc..429b096e 100644 --- a/lite/doc.go +++ b/lite/doc.go @@ -53,10 +53,6 @@ SignedHeader, and that the SignedHeader was to be signed by the exact given validator set, and that the height of the commit is at least height (or greater). -SignedHeader.Commit may be signed by a different validator set, it can get -verified with a BaseVerifier as long as sufficient signatures from the -previous validator set are present in the commit. - DynamicVerifier - this Verifier implements an auto-update and persistence strategy to verify any SignedHeader of the blockchain. diff --git a/lite/dynamic_verifier.go b/lite/dynamic_verifier.go index 6a772091..8b69d2d7 100644 --- a/lite/dynamic_verifier.go +++ b/lite/dynamic_verifier.go @@ -18,12 +18,17 @@ var _ Verifier = (*DynamicVerifier)(nil) // "source" provider to obtain the needed FullCommits to securely sync with // validator set changes. It stores properly validated data on the // "trusted" local system. +// TODO: make this single threaded and create a new +// ConcurrentDynamicVerifier that wraps it with concurrency. +// see https://github.com/tendermint/tendermint/issues/3170 type DynamicVerifier struct { - logger log.Logger chainID string - // These are only properly validated data, from local system. + logger log.Logger + + // Already validated, stored locally trusted PersistentProvider - // This is a source of new info, like a node rpc, or other import method. + + // New info, like a node rpc, or other import method. source Provider // pending map to synchronize concurrent verification requests @@ -35,8 +40,8 @@ type DynamicVerifier struct { // trusted provider to store validated data and the source provider to // obtain missing data (e.g. FullCommits). // -// The trusted provider should a CacheProvider, MemProvider or -// files.Provider. The source provider should be a client.HTTPProvider. +// The trusted provider should be a DBProvider. +// The source provider should be a client.HTTPProvider. func NewDynamicVerifier(chainID string, trusted PersistentProvider, source Provider) *DynamicVerifier { return &DynamicVerifier{ logger: log.NewNopLogger(), @@ -47,68 +52,71 @@ func NewDynamicVerifier(chainID string, trusted PersistentProvider, source Provi } } -func (ic *DynamicVerifier) SetLogger(logger log.Logger) { +func (dv *DynamicVerifier) SetLogger(logger log.Logger) { logger = logger.With("module", "lite") - ic.logger = logger - ic.trusted.SetLogger(logger) - ic.source.SetLogger(logger) + dv.logger = logger + dv.trusted.SetLogger(logger) + dv.source.SetLogger(logger) } // Implements Verifier. -func (ic *DynamicVerifier) ChainID() string { - return ic.chainID +func (dv *DynamicVerifier) ChainID() string { + return dv.chainID } // Implements Verifier. // // If the validators have changed since the last known time, it looks to -// ic.trusted and ic.source to prove the new validators. On success, it will -// try to store the SignedHeader in ic.trusted if the next +// dv.trusted and dv.source to prove the new validators. On success, it will +// try to store the SignedHeader in dv.trusted if the next // validator can be sourced. -func (ic *DynamicVerifier) Verify(shdr types.SignedHeader) error { +func (dv *DynamicVerifier) Verify(shdr types.SignedHeader) error { // Performs synchronization for multi-threads verification at the same height. - ic.mtx.Lock() - if pending := ic.pendingVerifications[shdr.Height]; pending != nil { - ic.mtx.Unlock() + dv.mtx.Lock() + if pending := dv.pendingVerifications[shdr.Height]; pending != nil { + dv.mtx.Unlock() <-pending // pending is chan struct{} } else { pending := make(chan struct{}) - ic.pendingVerifications[shdr.Height] = pending + dv.pendingVerifications[shdr.Height] = pending defer func() { close(pending) - ic.mtx.Lock() - delete(ic.pendingVerifications, shdr.Height) - ic.mtx.Unlock() + dv.mtx.Lock() + delete(dv.pendingVerifications, shdr.Height) + dv.mtx.Unlock() }() - ic.mtx.Unlock() + dv.mtx.Unlock() } + //Get the exact trusted commit for h, and if it is - // equal to shdr, then don't even verify it, - // and just return nil. - trustedFCSameHeight, err := ic.trusted.LatestFullCommit(ic.chainID, shdr.Height, shdr.Height) + // equal to shdr, then it's already trusted, so + // just return nil. + trustedFCSameHeight, err := dv.trusted.LatestFullCommit(dv.chainID, shdr.Height, shdr.Height) if err == nil { // If loading trust commit successfully, and trust commit equal to shdr, then don't verify it, // just return nil. if bytes.Equal(trustedFCSameHeight.SignedHeader.Hash(), shdr.Hash()) { - ic.logger.Info(fmt.Sprintf("Load full commit at height %d from cache, there is not need to verify.", shdr.Height)) + dv.logger.Info(fmt.Sprintf("Load full commit at height %d from cache, there is not need to verify.", shdr.Height)) return nil } } else if !lerr.IsErrCommitNotFound(err) { // Return error if it is not CommitNotFound error - ic.logger.Info(fmt.Sprintf("Encountered unknown error in loading full commit at height %d.", shdr.Height)) + dv.logger.Info(fmt.Sprintf("Encountered unknown error in loading full commit at height %d.", shdr.Height)) return err } // Get the latest known full commit <= h-1 from our trusted providers. // The full commit at h-1 contains the valset to sign for h. - h := shdr.Height - 1 - trustedFC, err := ic.trusted.LatestFullCommit(ic.chainID, 1, h) + prevHeight := shdr.Height - 1 + trustedFC, err := dv.trusted.LatestFullCommit(dv.chainID, 1, prevHeight) if err != nil { return err } - if trustedFC.Height() == h { + // sync up to the prevHeight and assert our latest NextValidatorSet + // is the ValidatorSet for the SignedHeader + if trustedFC.Height() == prevHeight { // Return error if valset doesn't match. if !bytes.Equal( trustedFC.NextValidators.Hash(), @@ -118,11 +126,12 @@ func (ic *DynamicVerifier) Verify(shdr types.SignedHeader) error { shdr.Header.ValidatorsHash) } } else { - // If valset doesn't match... - if !bytes.Equal(trustedFC.NextValidators.Hash(), + // If valset doesn't match, try to update + if !bytes.Equal( + trustedFC.NextValidators.Hash(), shdr.Header.ValidatorsHash) { // ... update. - trustedFC, err = ic.updateToHeight(h) + trustedFC, err = dv.updateToHeight(prevHeight) if err != nil { return err } @@ -137,14 +146,21 @@ func (ic *DynamicVerifier) Verify(shdr types.SignedHeader) error { } // Verify the signed header using the matching valset. - cert := NewBaseVerifier(ic.chainID, trustedFC.Height()+1, trustedFC.NextValidators) + cert := NewBaseVerifier(dv.chainID, trustedFC.Height()+1, trustedFC.NextValidators) err = cert.Verify(shdr) if err != nil { return err } + // By now, the SignedHeader is fully validated and we're synced up to + // SignedHeader.Height - 1. To sync to SignedHeader.Height, we need + // the validator set at SignedHeader.Height + 1 so we can verify the + // SignedHeader.NextValidatorSet. + // TODO: is the ValidateFull below mostly redundant with the BaseVerifier.Verify above? + // See https://github.com/tendermint/tendermint/issues/3174. + // Get the next validator set. - nextValset, err := ic.source.ValidatorSet(ic.chainID, shdr.Height+1) + nextValset, err := dv.source.ValidatorSet(dv.chainID, shdr.Height+1) if lerr.IsErrUnknownValidators(err) { // Ignore this error. return nil @@ -160,31 +176,31 @@ func (ic *DynamicVerifier) Verify(shdr types.SignedHeader) error { } // Validate the full commit. This checks the cryptographic // signatures of Commit against Validators. - if err := nfc.ValidateFull(ic.chainID); err != nil { + if err := nfc.ValidateFull(dv.chainID); err != nil { return err } // Trust it. - return ic.trusted.SaveFullCommit(nfc) + return dv.trusted.SaveFullCommit(nfc) } // verifyAndSave will verify if this is a valid source full commit given the -// best match trusted full commit, and if good, persist to ic.trusted. +// best match trusted full commit, and if good, persist to dv.trusted. // Returns ErrTooMuchChange when >2/3 of trustedFC did not sign sourceFC. // Panics if trustedFC.Height() >= sourceFC.Height(). -func (ic *DynamicVerifier) verifyAndSave(trustedFC, sourceFC FullCommit) error { +func (dv *DynamicVerifier) verifyAndSave(trustedFC, sourceFC FullCommit) error { if trustedFC.Height() >= sourceFC.Height() { panic("should not happen") } err := trustedFC.NextValidators.VerifyFutureCommit( sourceFC.Validators, - ic.chainID, sourceFC.SignedHeader.Commit.BlockID, + dv.chainID, sourceFC.SignedHeader.Commit.BlockID, sourceFC.SignedHeader.Height, sourceFC.SignedHeader.Commit, ) if err != nil { return err } - return ic.trusted.SaveFullCommit(sourceFC) + return dv.trusted.SaveFullCommit(sourceFC) } // updateToHeight will use divide-and-conquer to find a path to h. @@ -192,29 +208,30 @@ func (ic *DynamicVerifier) verifyAndSave(trustedFC, sourceFC FullCommit) error { // for height h, using repeated applications of bisection if necessary. // // Returns ErrCommitNotFound if source provider doesn't have the commit for h. -func (ic *DynamicVerifier) updateToHeight(h int64) (FullCommit, error) { +func (dv *DynamicVerifier) updateToHeight(h int64) (FullCommit, error) { // Fetch latest full commit from source. - sourceFC, err := ic.source.LatestFullCommit(ic.chainID, h, h) + sourceFC, err := dv.source.LatestFullCommit(dv.chainID, h, h) if err != nil { return FullCommit{}, err } - // Validate the full commit. This checks the cryptographic - // signatures of Commit against Validators. - if err := sourceFC.ValidateFull(ic.chainID); err != nil { - return FullCommit{}, err - } - // If sourceFC.Height() != h, we can't do it. if sourceFC.Height() != h { return FullCommit{}, lerr.ErrCommitNotFound() } + // Validate the full commit. This checks the cryptographic + // signatures of Commit against Validators. + if err := sourceFC.ValidateFull(dv.chainID); err != nil { + return FullCommit{}, err + } + + // Verify latest FullCommit against trusted FullCommits FOR_LOOP: for { // Fetch latest full commit from trusted. - trustedFC, err := ic.trusted.LatestFullCommit(ic.chainID, 1, h) + trustedFC, err := dv.trusted.LatestFullCommit(dv.chainID, 1, h) if err != nil { return FullCommit{}, err } @@ -224,21 +241,21 @@ FOR_LOOP: } // Try to update to full commit with checks. - err = ic.verifyAndSave(trustedFC, sourceFC) + err = dv.verifyAndSave(trustedFC, sourceFC) if err == nil { // All good! return sourceFC, nil } // Handle special case when err is ErrTooMuchChange. - if lerr.IsErrTooMuchChange(err) { + if types.IsErrTooMuchChange(err) { // Divide and conquer. start, end := trustedFC.Height(), sourceFC.Height() if !(start < end) { panic("should not happen") } mid := (start + end) / 2 - _, err = ic.updateToHeight(mid) + _, err = dv.updateToHeight(mid) if err != nil { return FullCommit{}, err } @@ -249,8 +266,8 @@ FOR_LOOP: } } -func (ic *DynamicVerifier) LastTrustedHeight() int64 { - fc, err := ic.trusted.LatestFullCommit(ic.chainID, 1, 1<<63-1) +func (dv *DynamicVerifier) LastTrustedHeight() int64 { + fc, err := dv.trusted.LatestFullCommit(dv.chainID, 1, 1<<63-1) if err != nil { panic("should not happen") } diff --git a/lite/dynamic_verifier_test.go b/lite/dynamic_verifier_test.go index 9ff8ed81..386de513 100644 --- a/lite/dynamic_verifier_test.go +++ b/lite/dynamic_verifier_test.go @@ -10,6 +10,7 @@ import ( dbm "github.com/tendermint/tendermint/libs/db" log "github.com/tendermint/tendermint/libs/log" + "github.com/tendermint/tendermint/types" ) func TestInquirerValidPath(t *testing.T) { @@ -70,6 +71,70 @@ func TestInquirerValidPath(t *testing.T) { assert.Nil(err, "%+v", err) } +func TestDynamicVerify(t *testing.T) { + trust := NewDBProvider("trust", dbm.NewMemDB()) + source := NewDBProvider("source", dbm.NewMemDB()) + + // 10 commits with one valset, 1 to change, + // 10 commits with the next one + n1, n2 := 10, 10 + nCommits := n1 + n2 + 1 + maxHeight := int64(nCommits) + fcz := make([]FullCommit, nCommits) + + // gen the 2 val sets + chainID := "dynamic-verifier" + power := int64(10) + keys1 := genPrivKeys(5) + vals1 := keys1.ToValidators(power, 0) + keys2 := genPrivKeys(5) + vals2 := keys2.ToValidators(power, 0) + + // make some commits with the first + for i := 0; i < n1; i++ { + fcz[i] = makeFullCommit(int64(i), keys1, vals1, vals1, chainID) + } + + // update the val set + fcz[n1] = makeFullCommit(int64(n1), keys1, vals1, vals2, chainID) + + // make some commits with the new one + for i := n1 + 1; i < nCommits; i++ { + fcz[i] = makeFullCommit(int64(i), keys2, vals2, vals2, chainID) + } + + // Save everything in the source + for _, fc := range fcz { + source.SaveFullCommit(fc) + } + + // Initialize a Verifier with the initial state. + err := trust.SaveFullCommit(fcz[0]) + require.Nil(t, err) + ver := NewDynamicVerifier(chainID, trust, source) + ver.SetLogger(log.TestingLogger()) + + // fetch the latest from the source + latestFC, err := source.LatestFullCommit(chainID, 1, maxHeight) + require.NoError(t, err) + + // try to update to the latest + err = ver.Verify(latestFC.SignedHeader) + require.NoError(t, err) + +} + +func makeFullCommit(height int64, keys privKeys, vals, nextVals *types.ValidatorSet, chainID string) FullCommit { + height += 1 + consHash := []byte("special-params") + appHash := []byte(fmt.Sprintf("h=%d", height)) + resHash := []byte(fmt.Sprintf("res=%d", height)) + return keys.GenFullCommit( + chainID, height, nil, + vals, nextVals, + appHash, consHash, resHash, 0, len(keys)) +} + func TestInquirerVerifyHistorical(t *testing.T) { assert, require := assert.New(t), require.New(t) trust := NewDBProvider("trust", dbm.NewMemDB()) diff --git a/lite/errors/errors.go b/lite/errors/errors.go index 59b6380d..75442c72 100644 --- a/lite/errors/errors.go +++ b/lite/errors/errors.go @@ -25,12 +25,6 @@ func (e errUnexpectedValidators) Error() string { e.got, e.want) } -type errTooMuchChange struct{} - -func (e errTooMuchChange) Error() string { - return "Insufficient signatures to validate due to valset changes" -} - type errUnknownValidators struct { chainID string height int64 @@ -85,22 +79,6 @@ func IsErrUnexpectedValidators(err error) bool { return false } -//----------------- -// ErrTooMuchChange - -// ErrTooMuchChange indicates that the underlying validator set was changed by >1/3. -func ErrTooMuchChange() error { - return cmn.ErrorWrap(errTooMuchChange{}, "") -} - -func IsErrTooMuchChange(err error) bool { - if err_, ok := err.(cmn.Error); ok { - _, ok := err_.Data().(errTooMuchChange) - return ok - } - return false -} - //----------------- // ErrUnknownValidators diff --git a/lite/multiprovider.go b/lite/multiprovider.go index 734d042c..a05e19b1 100644 --- a/lite/multiprovider.go +++ b/lite/multiprovider.go @@ -6,6 +6,8 @@ import ( "github.com/tendermint/tendermint/types" ) +var _ PersistentProvider = (*multiProvider)(nil) + // multiProvider allows you to place one or more caches in front of a source // Provider. It runs through them in order until a match is found. type multiProvider struct { diff --git a/lite/provider.go b/lite/provider.go index 97e06a06..ebab1626 100644 --- a/lite/provider.go +++ b/lite/provider.go @@ -1,7 +1,7 @@ package lite import ( - log "github.com/tendermint/tendermint/libs/log" + "github.com/tendermint/tendermint/libs/log" "github.com/tendermint/tendermint/types" ) diff --git a/types/block.go b/types/block.go index 5872a680..99ee3f8e 100644 --- a/types/block.go +++ b/types/block.go @@ -638,6 +638,7 @@ func (commit *Commit) StringIndented(indent string) string { //----------------------------------------------------------------------------- // SignedHeader is a header along with the commits that prove it. +// It is the basis of the lite client. type SignedHeader struct { *Header `json:"header"` Commit *Commit `json:"commit"` diff --git a/types/validator_set.go b/types/validator_set.go index 4040810f..38b9260a 100644 --- a/types/validator_set.go +++ b/types/validator_set.go @@ -413,8 +413,7 @@ func (vals *ValidatorSet) VerifyCommit(chainID string, blockID BlockID, height i if talliedVotingPower > vals.TotalVotingPower()*2/3 { return nil } - return fmt.Errorf("Invalid commit -- insufficient voting power: got %v, needed %v", - talliedVotingPower, vals.TotalVotingPower()*2/3+1) + return errTooMuchChange{talliedVotingPower, vals.TotalVotingPower()*2/3 + 1} } // VerifyFutureCommit will check to see if the set would be valid with a different @@ -496,12 +495,37 @@ func (vals *ValidatorSet) VerifyFutureCommit(newSet *ValidatorSet, chainID strin } if oldVotingPower <= oldVals.TotalVotingPower()*2/3 { - return cmn.NewError("Invalid commit -- insufficient old voting power: got %v, needed %v", - oldVotingPower, oldVals.TotalVotingPower()*2/3+1) + return errTooMuchChange{oldVotingPower, oldVals.TotalVotingPower()*2/3 + 1} } return nil } +//----------------- +// ErrTooMuchChange + +func IsErrTooMuchChange(err error) bool { + switch err_ := err.(type) { + case cmn.Error: + _, ok := err_.Data().(errTooMuchChange) + return ok + case errTooMuchChange: + return true + default: + return false + } +} + +type errTooMuchChange struct { + got int64 + needed int64 +} + +func (e errTooMuchChange) Error() string { + return fmt.Sprintf("Invalid commit -- insufficient old voting power: got %v, needed %v", e.got, e.needed) +} + +//---------------- + func (vals *ValidatorSet) String() string { return vals.StringIndented("") } From 7a8aeff4b0f5fa7d9113315e2c2ccf64883a1f90 Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Mon, 21 Jan 2019 10:02:57 -0500 Subject: [PATCH 216/267] update spec for Merkle RFC 6962 (#3175) * spec: specify when MerkleRoot is on hashes * remove unnecessary hash methods * update changelog * fix test --- CHANGELOG_PENDING.md | 5 ++++- docs/spec/blockchain/blockchain.md | 13 +++++++++---- docs/spec/blockchain/encoding.md | 16 +++++++++++++--- docs/spec/blockchain/state.md | 2 +- types/results.go | 11 +++-------- types/results_test.go | 19 ++++++++++--------- types/validator.go | 8 -------- 7 files changed, 40 insertions(+), 34 deletions(-) diff --git a/CHANGELOG_PENDING.md b/CHANGELOG_PENDING.md index af1c5566..5a425f8b 100644 --- a/CHANGELOG_PENDING.md +++ b/CHANGELOG_PENDING.md @@ -12,9 +12,12 @@ Special thanks to external contributors on this release: * Go API - [node] \#3082 MetricsProvider now requires you to pass a chain ID +- [types] \#2713 Rename `TxProof.LeafHash` to `TxProof.Leaf` +- [crypto/merkle] \#2713 `SimpleProof.Verify` takes a `leaf` instead of a + `leafHash` and performs the hashing itself * Blockchain Protocol - * [merkle] \#2713 Merkle trees now match the RFC 6962 specification + * [crypto/merkle] \#2713 Merkle trees now match the RFC 6962 specification * [types] \#3078 Re-order Timestamp and BlockID in CanonicalVote so it's consistent with CanonicalProposal (BlockID comes first) diff --git a/docs/spec/blockchain/blockchain.md b/docs/spec/blockchain/blockchain.md index 92b55e35..00cccfc2 100644 --- a/docs/spec/blockchain/blockchain.md +++ b/docs/spec/blockchain/blockchain.md @@ -51,7 +51,7 @@ type Header struct { // hashes of block data LastCommitHash []byte // commit from validators from the last block - DataHash []byte // MerkleRoot of transactions + DataHash []byte // MerkleRoot of transaction hashes // hashes from the app output from the prev block ValidatorsHash []byte // validators for the current block @@ -303,7 +303,7 @@ The first block has `block.Header.LastBlockID == BlockID{}`. ### LastCommitHash ```go -block.Header.LastCommitHash == MerkleRoot(block.LastCommit) +block.Header.LastCommitHash == MerkleRoot(block.LastCommit.Precommits) ``` MerkleRoot of the votes included in the block. @@ -314,10 +314,15 @@ The first block has `block.Header.LastCommitHash == []byte{}` ### DataHash ```go -block.Header.DataHash == MerkleRoot(block.Txs.Txs) +block.Header.DataHash == MerkleRoot(Hashes(block.Txs.Txs)) ``` -MerkleRoot of the transactions included in the block. +MerkleRoot of the hashes of transactions included in the block. + +Note the transactions are hashed before being included in the Merkle tree, +so the leaves of the Merkle tree are the hashes, not the transactions +themselves. This is because transaction hashes are regularly used as identifiers for +transactions. ### ValidatorsHash diff --git a/docs/spec/blockchain/encoding.md b/docs/spec/blockchain/encoding.md index 9552ab07..1b999335 100644 --- a/docs/spec/blockchain/encoding.md +++ b/docs/spec/blockchain/encoding.md @@ -213,7 +213,7 @@ func innerHash(left []byte, right []byte) []byte { // largest power of 2 less than k func getSplitPoint(k int) { ... } -func MerkleRoot(leafs [][]byte) []byte{ +func MerkleRoot(items [][]byte) []byte{ switch len(items) { case 0: return nil @@ -228,10 +228,20 @@ func MerkleRoot(leafs [][]byte) []byte{ } ``` +Note: `MerkleRoot` operates on items which are arbitrary byte arrays, not +necessarily hashes. For items which need to be hashed first, we introduce the +`Hashes` function: + +``` +func Hashes(items [][]byte) [][]byte { + return SHA256 of each item +} +``` + Note: we will abuse notion and invoke `MerkleRoot` with arguments of type `struct` or type `[]struct`. -For `struct` arguments, we compute a `[][]byte` containing the hash of each +For `struct` arguments, we compute a `[][]byte` containing the amino encoding of each field in the struct, in the same order the fields appear in the struct. -For `[]struct` arguments, we compute a `[][]byte` by hashing the individual `struct` elements. +For `[]struct` arguments, we compute a `[][]byte` by amino encoding the individual `struct` elements. ### Simple Merkle Proof diff --git a/docs/spec/blockchain/state.md b/docs/spec/blockchain/state.md index ff6fcf2e..7df096bc 100644 --- a/docs/spec/blockchain/state.md +++ b/docs/spec/blockchain/state.md @@ -60,7 +60,7 @@ When hashing the Validator struct, the address is not included, because it is redundant with the pubkey. The `state.Validators`, `state.LastValidators`, and `state.NextValidators`, must always by sorted by validator address, -so that there is a canonical order for computing the SimpleMerkleRoot. +so that there is a canonical order for computing the MerkleRoot. We also define a `TotalVotingPower` function, to return the total voting power: diff --git a/types/results.go b/types/results.go index db781168..d7d82d89 100644 --- a/types/results.go +++ b/types/results.go @@ -3,25 +3,20 @@ package types import ( abci "github.com/tendermint/tendermint/abci/types" "github.com/tendermint/tendermint/crypto/merkle" - "github.com/tendermint/tendermint/crypto/tmhash" cmn "github.com/tendermint/tendermint/libs/common" ) //----------------------------------------------------------------------------- // ABCIResult is the deterministic component of a ResponseDeliverTx. -// TODO: add Tags +// TODO: add tags and other fields +// https://github.com/tendermint/tendermint/issues/1007 type ABCIResult struct { Code uint32 `json:"code"` Data cmn.HexBytes `json:"data"` } -// Hash returns the canonical hash of the ABCIResult -func (a ABCIResult) Hash() []byte { - bz := tmhash.Sum(cdcEncode(a)) - return bz -} - +// Bytes returns the amino encoded ABCIResult func (a ABCIResult) Bytes() []byte { return cdcEncode(a) } diff --git a/types/results_test.go b/types/results_test.go index def042d5..a37de9ec 100644 --- a/types/results_test.go +++ b/types/results_test.go @@ -16,20 +16,21 @@ func TestABCIResults(t *testing.T) { e := ABCIResult{Code: 14, Data: []byte("foo")} f := ABCIResult{Code: 14, Data: []byte("bar")} - // Nil and []byte{} should produce the same hash. - require.Equal(t, a.Hash(), a.Hash()) - require.Equal(t, b.Hash(), b.Hash()) - require.Equal(t, a.Hash(), b.Hash()) + // Nil and []byte{} should produce the same bytes + require.Equal(t, a.Bytes(), a.Bytes()) + require.Equal(t, b.Bytes(), b.Bytes()) + require.Equal(t, a.Bytes(), b.Bytes()) // a and b should be the same, don't go in results. results := ABCIResults{a, c, d, e, f} - // Make sure each result hashes properly. + // Make sure each result serializes differently var last []byte - for i, res := range results { - h := res.Hash() - assert.NotEqual(t, last, h, "%d", i) - last = h + assert.Equal(t, last, a.Bytes()) // first one is empty + for i, res := range results[1:] { + bz := res.Bytes() + assert.NotEqual(t, last, bz, "%d", i) + last = bz } // Make sure that we can get a root hash from results and verify proofs. diff --git a/types/validator.go b/types/validator.go index 1de326b0..0b8967b2 100644 --- a/types/validator.go +++ b/types/validator.go @@ -4,8 +4,6 @@ import ( "bytes" "fmt" - "github.com/tendermint/tendermint/crypto/tmhash" - "github.com/tendermint/tendermint/crypto" cmn "github.com/tendermint/tendermint/libs/common" ) @@ -70,12 +68,6 @@ func (v *Validator) String() string { v.ProposerPriority) } -// Hash computes the unique ID of a validator with a given voting power. -// It excludes the ProposerPriority value, which changes with every round. -func (v *Validator) Hash() []byte { - return tmhash.Sum(v.Bytes()) -} - // Bytes computes the unique encoding of a validator with a given voting power. // These are the bytes that gets hashed in consensus. It excludes address // as its redundant with the pubkey. This also excludes ProposerPriority From d9d4f3e6292c100e2c0a06c16d0a9cd4b6508255 Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Mon, 21 Jan 2019 19:32:10 -0500 Subject: [PATCH 217/267] Prepare v0.29.0 (#3184) * update changelog and upgrading * add note about max voting power in abci spec * update version * changelog --- CHANGELOG.md | 72 ++++++++++++++++++++++++++++++++++++++++++ CHANGELOG_PENDING.md | 20 +----------- UPGRADING.md | 22 +++++++++++++ docs/spec/abci/apps.md | 6 ++++ version/version.go | 6 ++-- 5 files changed, 104 insertions(+), 22 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 707d0d2d..227c8484 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,77 @@ # Changelog +## v0.29.0 + +*January 21, 2019* + +Special thanks to external contributors on this release: +@bradyjoestar, @kunaldhariwal, @gauthamzz, @hrharder + +This release is primarily about making some breaking changes to +the Block protocol version before Cosmos launch, and to fixing more issues +in the proposer selection algorithm discovered on Cosmos testnets. + +The Block protocol changes include using a standard Merkle tree format (RFC 6962), +fixing some inconsistencies between field orders in Vote and Proposal structs, +and constraining the hash of the ConsensusParams to include only a few fields. + +The proposer selection algorithm saw significant progress, +including a [formal proof by @cwgoes for the base-case in Idris](https://github.com/cwgoes/tm-proposer-idris) +and a [much more detailed specification (still in progress) by +@ancazamfir](https://github.com/tendermint/tendermint/pull/3140). + +Fixes to the proposer selection algorithm include normalizing the proposer +priorities to mitigate the effects of large changes to the validator set. +That said, we just discovered [another bug](https://github.com/tendermint/tendermint/issues/3181), +which will be fixed in the next breaking release. + +While we are trying to stabilize the Block protocol to preserve compatibility +with old chains, there may be some final changes yet to come before Cosmos +launch as we continue to audit and test the software. + +Friendly reminder, we have a [bug bounty +program](https://hackerone.com/tendermint). + +### BREAKING CHANGES: + +* CLI/RPC/Config + +* Apps +- [state] [\#3049](https://github.com/tendermint/tendermint/issues/3049) Total voting power of the validator set is upper bounded by + `MaxInt64 / 8`. Apps must ensure they do not return changes to the validator + set that cause this maximum to be exceeded. + +* Go API +- [node] [\#3082](https://github.com/tendermint/tendermint/issues/3082) MetricsProvider now requires you to pass a chain ID +- [types] [\#2713](https://github.com/tendermint/tendermint/issues/2713) Rename `TxProof.LeafHash` to `TxProof.Leaf` +- [crypto/merkle] [\#2713](https://github.com/tendermint/tendermint/issues/2713) `SimpleProof.Verify` takes a `leaf` instead of a + `leafHash` and performs the hashing itself + +* Blockchain Protocol + * [crypto/merkle] [\#2713](https://github.com/tendermint/tendermint/issues/2713) Merkle trees now match the RFC 6962 specification + * [types] [\#3078](https://github.com/tendermint/tendermint/issues/3078) Re-order Timestamp and BlockID in CanonicalVote so it's + consistent with CanonicalProposal (BlockID comes + first) + * [types] [\#3165](https://github.com/tendermint/tendermint/issues/3165) Hash of ConsensusParams only includes BlockSize.MaxBytes and + BlockSize.MaxGas + +* P2P Protocol + - [consensus] [\#3049](https://github.com/tendermint/tendermint/issues/3049) Normalize priorities to not exceed `2*TotalVotingPower` to mitigate unfair proposer selection + heavily preferring earlier joined validators in the case of an early bonded large validator unbonding + +### FEATURES: + +### IMPROVEMENTS: +- [rpc] [\#3065](https://github.com/tendermint/tendermint/issues/3065) Return maxPerPage (100), not defaultPerPage (30) if `per_page` is greater than the max 100. +- [instrumentation] [\#3082](https://github.com/tendermint/tendermint/issues/3082) Add `chain_id` label for all metrics + +### BUG FIXES: +- [crypto] [\#3164](https://github.com/tendermint/tendermint/issues/3164) Update `btcd` fork for rare signRFC6979 bug +- [lite] [\#3171](https://github.com/tendermint/tendermint/issues/3171) Fix verifying large validator set changes +- [log] [\#3125](https://github.com/tendermint/tendermint/issues/3125) Fix year format +- [mempool] [\#3168](https://github.com/tendermint/tendermint/issues/3168) Limit tx size to fit in the max reactor msg size +- [scripts] [\#3147](https://github.com/tendermint/tendermint/issues/3147) Fix json2wal for large block parts (@bradyjoestar) + ## v0.28.1 *January 18th, 2019* diff --git a/CHANGELOG_PENDING.md b/CHANGELOG_PENDING.md index 5a425f8b..06b2ec52 100644 --- a/CHANGELOG_PENDING.md +++ b/CHANGELOG_PENDING.md @@ -1,4 +1,4 @@ -## v0.29.0 +## v0.30.0 *TBD* @@ -11,31 +11,13 @@ Special thanks to external contributors on this release: * Apps * Go API -- [node] \#3082 MetricsProvider now requires you to pass a chain ID -- [types] \#2713 Rename `TxProof.LeafHash` to `TxProof.Leaf` -- [crypto/merkle] \#2713 `SimpleProof.Verify` takes a `leaf` instead of a - `leafHash` and performs the hashing itself * Blockchain Protocol - * [crypto/merkle] \#2713 Merkle trees now match the RFC 6962 specification - * [types] \#3078 Re-order Timestamp and BlockID in CanonicalVote so it's - consistent with CanonicalProposal (BlockID comes - first) - * [types] \#3165 Hash of ConsensusParams only includes BlockSize.MaxBytes and - BlockSize.MaxGas * P2P Protocol - - [consensus] \#2960 normalize priorities to not exceed `2*TotalVotingPower` to mitigate unfair proposer selection - heavily preferring earlier joined validators in the case of an early bonded large validator unbonding ### FEATURES: ### IMPROVEMENTS: -- [rpc] \#3065 Return maxPerPage (100), not defaultPerPage (30) if `per_page` is greater than the max 100. -- [instrumentation] \#3082 Add `chain_id` label for all metrics ### BUG FIXES: -- [crypto] \#3164 Update `btcd` fork for rare signRFC6979 bug -- [lite] \#3171 Fix verifying large validator set changes -- [log] \#3060 Fix year format -- [mempool] \#3168 Limit tx size to fit in the max reactor msg size diff --git a/UPGRADING.md b/UPGRADING.md index edd50d9e..dd35ff26 100644 --- a/UPGRADING.md +++ b/UPGRADING.md @@ -3,6 +3,28 @@ This guide provides steps to be followed when you upgrade your applications to a newer version of Tendermint Core. +## v0.29.0 + +This release contains some breaking changes to the block and p2p protocols, +and will not be compatible with any previous versions of the software, primarily +due to changes in how various data structures are hashed. + +Any implementations of Tendermint blockchain verification, including lite clients, +will need to be updated. For specific details: +- [Merkle tree](./docs/spec/blockchain/encoding.md#merkle-trees) +- [ConsensusParams](./docs/spec/blockchain/state.md#consensusparams) + +There was also a small change to field ordering in the vote struct. Any +implementations of an out-of-process validator (like a Key-Management Server) +will need to be updated. For specific details: +- [Vote](https://github.com/tendermint/tendermint/blob/develop/docs/spec/consensus/signing.md#votes) + +Finally, the proposer selection algorithm continues to evolve. See the +[work-in-progress +specification](https://github.com/tendermint/tendermint/pull/3140). + +For everything else, please see the [CHANGELOG](./CHANGELOG.md#v0.29.0). + ## v0.28.0 This release breaks the format for the `priv_validator.json` file diff --git a/docs/spec/abci/apps.md b/docs/spec/abci/apps.md index a378a2a8..a3b34240 100644 --- a/docs/spec/abci/apps.md +++ b/docs/spec/abci/apps.md @@ -166,6 +166,11 @@ the tags will be hashed into the next block header. The application may set the validator set during InitChain, and update it during EndBlock. +Note that the maximum total power of the validator set is bounded by +`MaxTotalVotingPower = MaxInt64 / 8`. Applications are responsible for ensuring +they do not make changes to the validator set that cause it to exceed this +limit. + ### InitChain ResponseInitChain can return a list of validators. @@ -206,6 +211,7 @@ following rules: - if the validator does not already exist, it will be added to the validator set with the given power - if the validator does already exist, its power will be adjusted to the given power +- the total power of the new validator set must not exceed MaxTotalVotingPower Note the updates returned in block `H` will only take effect at block `H+2`. diff --git a/version/version.go b/version/version.go index 707dbf16..87d81c6f 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.28.1" + TMCoreSemVer = "0.29.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 = 5 + P2PProtocol Protocol = 6 // BlockProtocol versions all block data structures and processing. - BlockProtocol Protocol = 8 + BlockProtocol Protocol = 9 ) //------------------------------------------------------------------------ From a97d6995c990a4e22035d43dba849e80119804ee Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Tue, 22 Jan 2019 12:24:26 -0500 Subject: [PATCH 218/267] fix changelog indent (#3190) --- CHANGELOG.md | 10 +++++----- types/validator_set.go | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 227c8484..d79fb867 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -37,15 +37,15 @@ program](https://hackerone.com/tendermint). * CLI/RPC/Config * Apps -- [state] [\#3049](https://github.com/tendermint/tendermint/issues/3049) Total voting power of the validator set is upper bounded by + - [state] [\#3049](https://github.com/tendermint/tendermint/issues/3049) Total voting power of the validator set is upper bounded by `MaxInt64 / 8`. Apps must ensure they do not return changes to the validator set that cause this maximum to be exceeded. * Go API -- [node] [\#3082](https://github.com/tendermint/tendermint/issues/3082) MetricsProvider now requires you to pass a chain ID -- [types] [\#2713](https://github.com/tendermint/tendermint/issues/2713) Rename `TxProof.LeafHash` to `TxProof.Leaf` -- [crypto/merkle] [\#2713](https://github.com/tendermint/tendermint/issues/2713) `SimpleProof.Verify` takes a `leaf` instead of a - `leafHash` and performs the hashing itself + - [node] [\#3082](https://github.com/tendermint/tendermint/issues/3082) MetricsProvider now requires you to pass a chain ID + - [types] [\#2713](https://github.com/tendermint/tendermint/issues/2713) Rename `TxProof.LeafHash` to `TxProof.Leaf` + - [crypto/merkle] [\#2713](https://github.com/tendermint/tendermint/issues/2713) `SimpleProof.Verify` takes a `leaf` instead of a + `leafHash` and performs the hashing itself * Blockchain Protocol * [crypto/merkle] [\#2713](https://github.com/tendermint/tendermint/issues/2713) Merkle trees now match the RFC 6962 specification diff --git a/types/validator_set.go b/types/validator_set.go index 38b9260a..a36e1920 100644 --- a/types/validator_set.go +++ b/types/validator_set.go @@ -98,7 +98,7 @@ func (vals *ValidatorSet) RescalePriorities(diffMax int64) { // NOTE: This check is merely a sanity check which could be // removed if all tests would init. voting power appropriately; // i.e. diffMax should always be > 0 - if diffMax == 0 { + if diffMax <= 0 { return } From 2449bf7300aa1656fc246a06e1e03864aa41766f Mon Sep 17 00:00:00 2001 From: Anton Kaliaev Date: Tue, 22 Jan 2019 22:23:18 +0400 Subject: [PATCH 219/267] p2p: file descriptor leaks (#3150) * close peer's connection to avoid fd leak Fixes #2967 * rename peer#Addr to RemoteAddr * fix test * fixes after Ethan's review * bring back the check * changelog entry * write a test for switch#acceptRoutine * increase timeouts? :( * remove extra assertNPeersWithTimeout * simplify test * assert number of peers (just to be safe) * Cleanup in OnStop * run tests with verbose flag on CircleCI * spawn a reading routine to prevent connection from closing * get port from the listener random port is faster, but often results in ``` panic: listen tcp 127.0.0.1:44068: bind: address already in use [recovered] panic: listen tcp 127.0.0.1:44068: bind: address already in use goroutine 79 [running]: testing.tRunner.func1(0xc0001bd600) /usr/local/go/src/testing/testing.go:792 +0x387 panic(0x974d20, 0xc0001b0500) /usr/local/go/src/runtime/panic.go:513 +0x1b9 github.com/tendermint/tendermint/p2p.MakeSwitch(0xc0000f42a0, 0x0, 0x9fb9cc, 0x9, 0x9fc346, 0xb, 0xb42128, 0x0, 0x0, 0x0, ...) /home/vagrant/go/src/github.com/tendermint/tendermint/p2p/test_util.go:182 +0xa28 github.com/tendermint/tendermint/p2p.MakeConnectedSwitches(0xc0000f42a0, 0x2, 0xb42128, 0xb41eb8, 0x4f1205, 0xc0001bed80, 0x4f16ed) /home/vagrant/go/src/github.com/tendermint/tendermint/p2p/test_util.go:75 +0xf9 github.com/tendermint/tendermint/p2p.MakeSwitchPair(0xbb8d20, 0xc0001bd600, 0xb42128, 0x2f7, 0x4f16c0) /home/vagrant/go/src/github.com/tendermint/tendermint/p2p/switch_test.go:94 +0x4c github.com/tendermint/tendermint/p2p.TestSwitches(0xc0001bd600) /home/vagrant/go/src/github.com/tendermint/tendermint/p2p/switch_test.go:117 +0x58 testing.tRunner(0xc0001bd600, 0xb42038) /usr/local/go/src/testing/testing.go:827 +0xbf created by testing.(*T).Run /usr/local/go/src/testing/testing.go:878 +0x353 exit status 2 FAIL github.com/tendermint/tendermint/p2p 0.350s ``` --- .circleci/config.yml | 2 +- CHANGELOG_PENDING.md | 1 + p2p/conn_set.go | 8 ++++++ p2p/dummy/peer.go | 10 +++++++ p2p/peer.go | 18 ++++++++---- p2p/peer_set_test.go | 2 ++ p2p/peer_test.go | 53 ++++++++++++++++++++--------------- p2p/pex/pex_reactor_test.go | 2 ++ p2p/switch.go | 23 ++++++++++++---- p2p/switch_test.go | 55 ++++++++++++++++++++++++++++++++++++- p2p/test_util.go | 22 +++++++++++++-- p2p/transport.go | 16 +++++++---- 12 files changed, 170 insertions(+), 42 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 5669384c..ecc7c0ac 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -240,7 +240,7 @@ jobs: for pkg in $(go list github.com/tendermint/tendermint/... | circleci tests split --split-by=timings); do id=$(basename "$pkg") - GOCACHE=off go test -timeout 5m -race -coverprofile=/tmp/workspace/profiles/$id.out -covermode=atomic "$pkg" | tee "/tmp/logs/$id-$RANDOM.log" + GOCACHE=off go test -v -timeout 5m -race -coverprofile=/tmp/workspace/profiles/$id.out -covermode=atomic "$pkg" | tee "/tmp/logs/$id-$RANDOM.log" done - persist_to_workspace: root: /tmp/workspace diff --git a/CHANGELOG_PENDING.md b/CHANGELOG_PENDING.md index 06b2ec52..183b9d60 100644 --- a/CHANGELOG_PENDING.md +++ b/CHANGELOG_PENDING.md @@ -21,3 +21,4 @@ Special thanks to external contributors on this release: ### IMPROVEMENTS: ### BUG FIXES: +- [p2p] \#2967 Fix file descriptor leaks diff --git a/p2p/conn_set.go b/p2p/conn_set.go index f960c0e8..d6462278 100644 --- a/p2p/conn_set.go +++ b/p2p/conn_set.go @@ -11,6 +11,7 @@ type ConnSet interface { HasIP(net.IP) bool Set(net.Conn, []net.IP) Remove(net.Conn) + RemoveAddr(net.Addr) } type connSetItem struct { @@ -62,6 +63,13 @@ func (cs *connSet) Remove(c net.Conn) { delete(cs.conns, c.RemoteAddr().String()) } +func (cs *connSet) RemoveAddr(addr net.Addr) { + cs.Lock() + defer cs.Unlock() + + delete(cs.conns, addr.String()) +} + func (cs *connSet) Set(c net.Conn, ips []net.IP) { cs.Lock() defer cs.Unlock() diff --git a/p2p/dummy/peer.go b/p2p/dummy/peer.go index 71def27e..57edafc6 100644 --- a/p2p/dummy/peer.go +++ b/p2p/dummy/peer.go @@ -55,6 +55,16 @@ func (p *peer) RemoteIP() net.IP { return net.ParseIP("127.0.0.1") } +// Addr always returns tcp://localhost:8800. +func (p *peer) RemoteAddr() net.Addr { + return &net.TCPAddr{IP: net.ParseIP("127.0.0.1"), Port: 8800} +} + +// CloseConn always returns nil. +func (p *peer) CloseConn() error { + return nil +} + // Status always returns empry connection status. func (p *peer) Status() tmconn.ConnectionStatus { return tmconn.ConnectionStatus{} diff --git a/p2p/peer.go b/p2p/peer.go index da301d49..73332a2a 100644 --- a/p2p/peer.go +++ b/p2p/peer.go @@ -18,15 +18,18 @@ type Peer interface { cmn.Service FlushStop() - ID() ID // peer's cryptographic ID - RemoteIP() net.IP // remote IP of the connection + ID() ID // peer's cryptographic ID + RemoteIP() net.IP // remote IP of the connection + RemoteAddr() net.Addr // remote address of the connection IsOutbound() bool // did we dial the peer IsPersistent() bool // do we redial this peer when we disconnect + CloseConn() error // close original connection + NodeInfo() NodeInfo // peer's info Status() tmconn.ConnectionStatus - OriginalAddr() *NetAddress + OriginalAddr() *NetAddress // original address for outbound peers Send(byte, []byte) bool TrySend(byte, []byte) bool @@ -296,6 +299,11 @@ func (p *peer) hasChannel(chID byte) bool { return false } +// CloseConn closes original connection. Used for cleaning up in cases where the peer had not been started at all. +func (p *peer) CloseConn() error { + return p.peerConn.conn.Close() +} + //--------------------------------------------------- // methods only used for testing // TODO: can we remove these? @@ -305,8 +313,8 @@ func (pc *peerConn) CloseConn() { pc.conn.Close() // nolint: errcheck } -// Addr returns peer's remote network address. -func (p *peer) Addr() net.Addr { +// RemoteAddr returns peer's remote network address. +func (p *peer) RemoteAddr() net.Addr { return p.peerConn.conn.RemoteAddr() } diff --git a/p2p/peer_set_test.go b/p2p/peer_set_test.go index 3eb5357d..1d2372fb 100644 --- a/p2p/peer_set_test.go +++ b/p2p/peer_set_test.go @@ -30,6 +30,8 @@ func (mp *mockPeer) Get(s string) interface{} { return s } func (mp *mockPeer) Set(string, interface{}) {} func (mp *mockPeer) RemoteIP() net.IP { return mp.ip } func (mp *mockPeer) OriginalAddr() *NetAddress { return nil } +func (mp *mockPeer) RemoteAddr() net.Addr { return &net.TCPAddr{IP: mp.ip, Port: 8800} } +func (mp *mockPeer) CloseConn() error { return nil } // Returns a mock peer func newMockPeer(ip net.IP) *mockPeer { diff --git a/p2p/peer_test.go b/p2p/peer_test.go index e53d6013..90be3113 100644 --- a/p2p/peer_test.go +++ b/p2p/peer_test.go @@ -39,7 +39,7 @@ func TestPeerBasic(t *testing.T) { assert.False(p.IsPersistent()) p.persistent = true assert.True(p.IsPersistent()) - assert.Equal(rp.Addr().DialString(), p.Addr().String()) + assert.Equal(rp.Addr().DialString(), p.RemoteAddr().String()) assert.Equal(rp.ID(), p.ID()) } @@ -137,9 +137,9 @@ type remotePeer struct { PrivKey crypto.PrivKey Config *config.P2PConfig addr *NetAddress - quit chan struct{} channels cmn.HexBytes listenAddr string + listener net.Listener } func (rp *remotePeer) Addr() *NetAddress { @@ -159,25 +159,45 @@ func (rp *remotePeer) Start() { if e != nil { golog.Fatalf("net.Listen tcp :0: %+v", e) } + rp.listener = l rp.addr = NewNetAddress(PubKeyToID(rp.PrivKey.PubKey()), l.Addr()) - rp.quit = make(chan struct{}) if rp.channels == nil { rp.channels = []byte{testCh} } - go rp.accept(l) + go rp.accept() } func (rp *remotePeer) Stop() { - close(rp.quit) + rp.listener.Close() } -func (rp *remotePeer) accept(l net.Listener) { +func (rp *remotePeer) Dial(addr *NetAddress) (net.Conn, error) { + conn, err := addr.DialTimeout(1 * time.Second) + if err != nil { + return nil, err + } + pc, err := testInboundPeerConn(conn, rp.Config, rp.PrivKey) + if err != nil { + return nil, err + } + _, err = handshake(pc.conn, time.Second, rp.nodeInfo()) + if err != nil { + return nil, err + } + return conn, err +} + +func (rp *remotePeer) accept() { conns := []net.Conn{} for { - conn, err := l.Accept() + conn, err := rp.listener.Accept() if err != nil { - golog.Fatalf("Failed to accept conn: %+v", err) + golog.Printf("Failed to accept conn: %+v", err) + for _, conn := range conns { + _ = conn.Close() + } + return } pc, err := testInboundPeerConn(conn, rp.Config, rp.PrivKey) @@ -185,31 +205,20 @@ func (rp *remotePeer) accept(l net.Listener) { golog.Fatalf("Failed to create a peer: %+v", err) } - _, err = handshake(pc.conn, time.Second, rp.nodeInfo(l)) + _, err = handshake(pc.conn, time.Second, rp.nodeInfo()) if err != nil { golog.Fatalf("Failed to perform handshake: %+v", err) } conns = append(conns, conn) - - select { - case <-rp.quit: - for _, conn := range conns { - if err := conn.Close(); err != nil { - golog.Fatal(err) - } - } - return - default: - } } } -func (rp *remotePeer) nodeInfo(l net.Listener) NodeInfo { +func (rp *remotePeer) nodeInfo() NodeInfo { return DefaultNodeInfo{ ProtocolVersion: defaultProtocolVersion, ID_: rp.Addr().ID, - ListenAddr: l.Addr().String(), + ListenAddr: rp.listener.Addr().String(), Network: "testing", Version: "1.2.3-rc0-deadbeef", Channels: rp.channels, diff --git a/p2p/pex/pex_reactor_test.go b/p2p/pex/pex_reactor_test.go index 2e2f3f24..f5125c60 100644 --- a/p2p/pex/pex_reactor_test.go +++ b/p2p/pex/pex_reactor_test.go @@ -404,6 +404,8 @@ func (mockPeer) TrySend(byte, []byte) bool { return false } func (mockPeer) Set(string, interface{}) {} func (mockPeer) Get(string) interface{} { return nil } func (mockPeer) OriginalAddr() *p2p.NetAddress { return nil } +func (mockPeer) RemoteAddr() net.Addr { return &net.TCPAddr{IP: net.ParseIP("127.0.0.1"), Port: 8800} } +func (mockPeer) CloseConn() error { return nil } func assertPeersWithTimeout( t *testing.T, diff --git a/p2p/switch.go b/p2p/switch.go index 0490eebb..dbd9c2a6 100644 --- a/p2p/switch.go +++ b/p2p/switch.go @@ -210,6 +210,7 @@ func (sw *Switch) OnStart() error { func (sw *Switch) OnStop() { // Stop peers for _, p := range sw.peers.List() { + sw.transport.Cleanup(p) p.Stop() if sw.peers.Remove(p) { sw.metrics.Peers.Add(float64(-1)) @@ -304,6 +305,7 @@ func (sw *Switch) stopAndRemovePeer(peer Peer, reason interface{}) { if sw.peers.Remove(peer) { sw.metrics.Peers.Add(float64(-1)) } + sw.transport.Cleanup(peer) peer.Stop() for _, reactor := range sw.reactors { reactor.RemovePeer(peer, reason) @@ -529,13 +531,16 @@ func (sw *Switch) acceptRoutine() { "max", sw.config.MaxNumInboundPeers, ) - _ = p.Stop() + sw.transport.Cleanup(p) continue } if err := sw.addPeer(p); err != nil { - _ = p.Stop() + sw.transport.Cleanup(p) + if p.IsRunning() { + _ = p.Stop() + } sw.Logger.Info( "Ignoring inbound connection: error while adding peer", "err", err, @@ -593,7 +598,10 @@ func (sw *Switch) addOutboundPeerWithConfig( } if err := sw.addPeer(p); err != nil { - _ = p.Stop() + sw.transport.Cleanup(p) + if p.IsRunning() { + _ = p.Stop() + } return err } @@ -628,7 +636,8 @@ func (sw *Switch) filterPeer(p Peer) error { return nil } -// addPeer starts up the Peer and adds it to the Switch. +// addPeer starts up the Peer and adds it to the Switch. Error is returned if +// the peer is filtered out or failed to start or can't be added. func (sw *Switch) addPeer(p Peer) error { if err := sw.filterPeer(p); err != nil { return err @@ -636,11 +645,15 @@ func (sw *Switch) addPeer(p Peer) error { p.SetLogger(sw.Logger.With("peer", p.NodeInfo().NetAddress())) - // All good. Start peer + // Handle the shut down case where the switch has stopped but we're + // concurrently trying to add a peer. if sw.IsRunning() { + // All good. Start peer if err := sw.startInitPeer(p); err != nil { return err } + } else { + sw.Logger.Error("Won't start a peer - switch is not running", "peer", p) } // Add the peer to .peers. diff --git a/p2p/switch_test.go b/p2p/switch_test.go index 6c515be0..35866161 100644 --- a/p2p/switch_test.go +++ b/p2p/switch_test.go @@ -3,7 +3,9 @@ package p2p import ( "bytes" "fmt" + "io" "io/ioutil" + "net" "net/http" "net/http/httptest" "regexp" @@ -13,7 +15,6 @@ import ( "time" stdprometheus "github.com/prometheus/client_golang/prometheus" - "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -477,6 +478,58 @@ func TestSwitchFullConnectivity(t *testing.T) { } } +func TestSwitchAcceptRoutine(t *testing.T) { + cfg.MaxNumInboundPeers = 5 + + // make switch + sw := MakeSwitch(cfg, 1, "testing", "123.123.123", initSwitchFunc) + err := sw.Start() + require.NoError(t, err) + defer sw.Stop() + + remotePeers := make([]*remotePeer, 0) + assert.Equal(t, 0, sw.Peers().Size()) + + // 1. check we connect up to MaxNumInboundPeers + for i := 0; i < cfg.MaxNumInboundPeers; i++ { + rp := &remotePeer{PrivKey: ed25519.GenPrivKey(), Config: cfg} + remotePeers = append(remotePeers, rp) + rp.Start() + c, err := rp.Dial(sw.NodeInfo().NetAddress()) + require.NoError(t, err) + // spawn a reading routine to prevent connection from closing + go func(c net.Conn) { + for { + one := make([]byte, 1) + _, err := c.Read(one) + if err != nil { + return + } + } + }(c) + } + time.Sleep(10 * time.Millisecond) + assert.Equal(t, cfg.MaxNumInboundPeers, sw.Peers().Size()) + + // 2. check we close new connections if we already have MaxNumInboundPeers peers + rp := &remotePeer{PrivKey: ed25519.GenPrivKey(), Config: cfg} + rp.Start() + conn, err := rp.Dial(sw.NodeInfo().NetAddress()) + require.NoError(t, err) + // check conn is closed + one := make([]byte, 1) + conn.SetReadDeadline(time.Now().Add(10 * time.Millisecond)) + _, err = conn.Read(one) + assert.Equal(t, io.EOF, err) + assert.Equal(t, cfg.MaxNumInboundPeers, sw.Peers().Size()) + rp.Stop() + + // stop remote peers + for _, rp := range remotePeers { + rp.Stop() + } +} + func BenchmarkSwitchBroadcast(b *testing.B) { s1, s2 := MakeSwitchPair(b, func(i int, sw *Switch) *Switch { // Make bar reactors of bar channels each diff --git a/p2p/test_util.go b/p2p/test_util.go index ea788b79..04629fca 100644 --- a/p2p/test_util.go +++ b/p2p/test_util.go @@ -247,17 +247,35 @@ func testNodeInfo(id ID, name string) NodeInfo { } func testNodeInfoWithNetwork(id ID, name, network string) NodeInfo { + port, err := getFreePort() + if err != nil { + panic(err) + } return DefaultNodeInfo{ ProtocolVersion: defaultProtocolVersion, ID_: id, - ListenAddr: fmt.Sprintf("127.0.0.1:%d", cmn.RandIntn(64512)+1023), + ListenAddr: fmt.Sprintf("127.0.0.1:%d", port), Network: network, Version: "1.2.3-rc0-deadbeef", Channels: []byte{testCh}, Moniker: name, Other: DefaultNodeInfoOther{ TxIndex: "on", - RPCAddress: fmt.Sprintf("127.0.0.1:%d", cmn.RandIntn(64512)+1023), + RPCAddress: fmt.Sprintf("127.0.0.1:%d", port), }, } } + +func getFreePort() (int, error) { + addr, err := net.ResolveTCPAddr("tcp", "localhost:0") + if err != nil { + return 0, err + } + + l, err := net.ListenTCP("tcp", addr) + if err != nil { + return 0, err + } + defer l.Close() + return l.Addr().(*net.TCPAddr).Port, nil +} diff --git a/p2p/transport.go b/p2p/transport.go index 69fab312..2d4420a1 100644 --- a/p2p/transport.go +++ b/p2p/transport.go @@ -52,6 +52,9 @@ type Transport interface { // Dial connects to the Peer for the address. Dial(NetAddress, peerConfig) (Peer, error) + + // Cleanup any resources associated with Peer. + Cleanup(Peer) } // transportLifecycle bundles the methods for callers to control start and stop @@ -274,6 +277,13 @@ func (mt *MultiplexTransport) acceptPeers() { } } +// Cleanup removes the given address from the connections set and +// closes the connection. +func (mt *MultiplexTransport) Cleanup(peer Peer) { + mt.conns.RemoveAddr(peer.RemoteAddr()) + _ = peer.CloseConn() +} + func (mt *MultiplexTransport) cleanup(c net.Conn) error { mt.conns.Remove(c) @@ -418,12 +428,6 @@ func (mt *MultiplexTransport) wrapPeer( PeerMetrics(cfg.metrics), ) - // Wait for Peer to Stop so we can cleanup. - go func(c net.Conn) { - <-p.Quit() - _ = mt.cleanup(c) - }(c) - return p } From 98b42e9eb2cb102bcbf46712284bf460e06b5542 Mon Sep 17 00:00:00 2001 From: Gautham Santhosh Date: Thu, 24 Jan 2019 07:45:43 +0100 Subject: [PATCH 220/267] docs: explain how someone can run his/her own ABCI app on localnet (#3195) Closes #3192. --- docs/networks/docker-compose.md | 72 +++++++++++++++++++++++++++++++++ 1 file changed, 72 insertions(+) diff --git a/docs/networks/docker-compose.md b/docs/networks/docker-compose.md index b7818c3b..7e4adde8 100644 --- a/docs/networks/docker-compose.md +++ b/docs/networks/docker-compose.md @@ -78,6 +78,78 @@ cd $GOPATH/src/github.com/tendermint/tendermint rm -rf ./build/node* ``` +## Configuring abci containers + +To use your own abci applications with 4-node setup edit the [docker-compose.yaml](https://github.com/tendermint/tendermint/blob/develop/docker-compose.yml) file and add image to your abci application. + +``` + abci0: + container_name: abci0 + image: "abci-image" + build: + context: . + dockerfile: abci.Dockerfile + command: + networks: + localnet: + ipv4_address: 192.167.10.6 + + abci1: + container_name: abci1 + image: "abci-image" + build: + context: . + dockerfile: abci.Dockerfile + command: + networks: + localnet: + ipv4_address: 192.167.10.7 + + abci2: + container_name: abci2 + image: "abci-image" + build: + context: . + dockerfile: abci.Dockerfile + command: + networks: + localnet: + ipv4_address: 192.167.10.8 + + abci3: + container_name: abci3 + image: "abci-image" + build: + context: . + dockerfile: abci.Dockerfile + command: + networks: + localnet: + ipv4_address: 192.167.10.9 + +``` + +Override the [command](https://github.com/tendermint/tendermint/blob/master/networks/local/localnode/Dockerfile#L12) in each node to connect to it's abci. + +``` + node0: + container_name: node0 + image: "tendermint/localnode" + ports: + - "26656-26657:26656-26657" + environment: + - ID=0 + - LOG=$${LOG:-tendermint.log} + volumes: + - ./build:/tendermint:Z + command: node --proxy_app=tcp://abci0:26658 + networks: + localnet: + ipv4_address: 192.167.10.2 +``` + +Similarly do for node1, node2 and node3 then [run testnet](https://github.com/tendermint/tendermint/blob/master/docs/networks/docker-compose.md#run-a-testnet) + ## Logging Log is saved under the attached volume, in the `tendermint.log` file. If the From 1efacaa8d3e491d94095a96ccf5d62698fa776dd Mon Sep 17 00:00:00 2001 From: Anton Kaliaev Date: Thu, 24 Jan 2019 11:33:58 +0400 Subject: [PATCH 221/267] docs: update pubsub ADR (#3131) * docs: update pubsub ADR * third version --- docs/architecture/adr-033-pubsub.md | 103 +++++++++++++++++++++++----- 1 file changed, 87 insertions(+), 16 deletions(-) diff --git a/docs/architecture/adr-033-pubsub.md b/docs/architecture/adr-033-pubsub.md index 0ef0cae6..c52bf44a 100644 --- a/docs/architecture/adr-033-pubsub.md +++ b/docs/architecture/adr-033-pubsub.md @@ -5,6 +5,8 @@ Author: Anton Kaliaev (@melekes) ## Changelog 02-10-2018: Initial draft +16-01-2019: Second version based on our conversation with Jae +17-01-2019: Third version explaining how new design solves current issues ## Context @@ -40,7 +42,14 @@ goroutines can be used to avoid uncontrolled memory growth. In certain cases, this is what you want. But in our case, because we need strict ordering of events (if event A was published before B, the guaranteed -delivery order will be A -> B), we can't use goroutines. +delivery order will be A -> B), we can't publish msg in a new goroutine every time. + +We can also have a goroutine per subscriber, although we'd need to be careful +with the number of subscribers. It's more difficult to implement as well + +unclear if we'll benefit from it (cause we'd be forced to create N additional +channels to distribute msg to these goroutines). + +### Non-blocking send There is also a question whenever we should have a non-blocking send: @@ -56,15 +65,14 @@ for each subscriber { ``` This fixes the "slow client problem", but there is no way for a slow client to -know if it had missed a message. On the other hand, if we're going to stick -with blocking send, **devs must always ensure subscriber's handling code does not -block**. As you can see, there is an implicit choice between ordering guarantees -and using goroutines. +know if it had missed a message. We could return a second channel and close it +to indicate subscription termination. On the other hand, if we're going to +stick with blocking send, **devs must always ensure subscriber's handling code +does not block**, which is a hard task to put on their shoulders. The interim option is to run goroutines pool for a single message, wait for all goroutines to finish. This will solve "slow client problem", but we'd still have to wait `max(goroutine_X_time)` before we can publish the next message. -My opinion: not worth doing. ### Channels vs Callbacks @@ -76,8 +84,6 @@ memory leaks and/or memory usage increase. Go channels are de-facto standard for carrying data between goroutines. -**Question: Is it worth switching to callback functions?** - ### Why `Subscribe()` accepts an `out` channel? Because in our tests, we create buffered channels (cap: 1). Alternatively, we @@ -85,27 +91,89 @@ can make capacity an argument. ## Decision -Change Subscribe() function to return out channel: +Change Subscribe() function to return a `Subscription` struct: ```go -// outCap can be used to set capacity of out channel (unbuffered by default). -Subscribe(ctx context.Context, clientID string, query Query, outCap... int) (out <-chan interface{}, err error) { +type Subscription struct { + // private fields +} + +func (s *Subscription) Out() <-chan MsgAndTags +func (s *Subscription) Cancelled() <-chan struct{} +func (s *Subscription) Err() error ``` -It's more idiomatic since we're closing it during Unsubscribe/UnsubscribeAll calls. +Out returns a channel onto which messages and tags are published. +Unsubscribe/UnsubscribeAll does not close the channel to avoid clients from +receiving a nil message. -Also, we should make tags available to subscribers: +Cancelled returns a channel that's closed when the subscription is terminated +and supposed to be used in a select statement. + +If Cancelled is not closed yet, Err() returns nil. +If Cancelled is closed, Err returns a non-nil error explaining why: +Unsubscribed if the subscriber choose to unsubscribe, +OutOfCapacity if the subscriber is not pulling messages fast enough and the Out channel become full. +After Err returns a non-nil error, successive calls to Err() return the same error. + +```go +subscription, err := pubsub.Subscribe(...) +if err != nil { + // ... +} +for { +select { + case msgAndTags <- subscription.Out(): + // ... + case <-subscription.Cancelled(): + return subscription.Err() +} +``` + +Make Out() channel buffered (cap: 1) by default. In most cases, we want to +terminate the slow subscriber. Only in rare cases, we want to block the pubsub +(e.g. when debugging consensus). This should lower the chances of the pubsub +being frozen. + +```go +// outCap can be used to set capacity of Out channel (1 by default). Set to 0 +for unbuffered channel (WARNING: it may block the pubsub). +Subscribe(ctx context.Context, clientID string, query Query, outCap... int) (Subscription, error) { +``` + +Also, Out() channel should return tags along with a message: ```go type MsgAndTags struct { Msg interface{} Tags TagMap } - -// outCap can be used to set capacity of out channel (unbuffered by default). -Subscribe(ctx context.Context, clientID string, query Query, outCap... int) (out <-chan MsgAndTags, err error) { ``` +to inform clients of which Tags were used with Msg. + +### How this new design solves the current issues? + +https://github.com/tendermint/tendermint/issues/951 (https://github.com/tendermint/tendermint/issues/1880) + +Because of non-blocking send, situation where we'll deadlock is not possible +anymore. If the client stops reading messages, it will be removed. + +https://github.com/tendermint/tendermint/issues/1879 + +MsgAndTags is used now instead of a plain message. + +### Future problems and their possible solutions + +https://github.com/tendermint/tendermint/issues/2826 + +One question I am still pondering about: how to prevent pubsub from slowing +down consensus. We can increase the pubsub queue size (which is 0 now). Also, +it's probably a good idea to limit the total number of subscribers. + +This can be made automatically. Say we set queue size to 1000 and, when it's >= +80% full, refuse new subscriptions. + ## Status In review @@ -116,7 +184,10 @@ In review - more idiomatic interface - subscribers know what tags msg was published with +- subscribers aware of the reason their subscription was cancelled ### Negative +- (since v1) no concurrency when it comes to publishing messages + ### Neutral From fbd1e79465bbb258ac85178e0073b42fec67b512 Mon Sep 17 00:00:00 2001 From: Infinytum <43315617+infinytum@users.noreply.github.com> Date: Thu, 24 Jan 2019 09:10:34 +0100 Subject: [PATCH 222/267] docs: fix lite client formatting (#3198) Closes #3180 --- lite/doc.go | 39 +++++++++++++++++---------------------- 1 file changed, 17 insertions(+), 22 deletions(-) diff --git a/lite/doc.go b/lite/doc.go index 429b096e..c02b5021 100644 --- a/lite/doc.go +++ b/lite/doc.go @@ -15,9 +15,7 @@ for you, so you can just build nice applications. We design for clients who have no strong trust relationship with any Tendermint node, just the blockchain and validator set as a whole. -# Data structures - -## SignedHeader +SignedHeader SignedHeader is a block header along with a commit -- enough validator precommit-vote signatures to prove its validity (> 2/3 of the voting power) @@ -42,7 +40,7 @@ The FullCommit is also declared in this package as a convenience structure, which includes the SignedHeader along with the full current and next ValidatorSets. -## Verifier +Verifier A Verifier validates a new SignedHeader given the currently known state. There are two different types of Verifiers provided. @@ -56,35 +54,32 @@ greater). DynamicVerifier - this Verifier implements an auto-update and persistence strategy to verify any SignedHeader of the blockchain. -## Provider and PersistentProvider +Provider and PersistentProvider A Provider allows us to store and retrieve the FullCommits. -```go -type Provider interface { - // LatestFullCommit returns the latest commit with - // minHeight <= height <= maxHeight. - // If maxHeight is zero, returns the latest where - // minHeight <= height. - LatestFullCommit(chainID string, minHeight, maxHeight int64) (FullCommit, error) -} -``` + type Provider interface { + // LatestFullCommit returns the latest commit with + // minHeight <= height <= maxHeight. + // If maxHeight is zero, returns the latest where + // minHeight <= height. + LatestFullCommit(chainID string, minHeight, maxHeight int64) (FullCommit, error) + } * client.NewHTTPProvider - query Tendermint rpc. A PersistentProvider is a Provider that also allows for saving state. This is used by the DynamicVerifier for persistence. -```go -type PersistentProvider interface { - Provider + type PersistentProvider interface { + Provider - // SaveFullCommit saves a FullCommit (without verification). - SaveFullCommit(fc FullCommit) error -} -``` + // SaveFullCommit saves a FullCommit (without verification). + SaveFullCommit(fc FullCommit) error + } * DBProvider - persistence provider for use with any libs/DB. + * MultiProvider - combine multiple providers. The suggested use for local light clients is client.NewHTTPProvider(...) for @@ -93,7 +88,7 @@ dbm.NewMemDB()), NewDBProvider("label", db.NewFileDB(...))) to store confirmed full commits (Trusted) -# How We Track Validators +How We Track Validators Unless you want to blindly trust the node you talk with, you need to trace every response back to a hash in a block header and validate the commit From c4157549abb9be5f0239cf6e3bf6b2d49427a30a Mon Sep 17 00:00:00 2001 From: Anton Kaliaev Date: Thu, 24 Jan 2019 13:53:02 +0400 Subject: [PATCH 223/267] only log "Reached max attempts to dial" once (#3144) Closes #3037 --- CHANGELOG_PENDING.md | 1 + p2p/pex/pex_reactor.go | 6 +++++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/CHANGELOG_PENDING.md b/CHANGELOG_PENDING.md index 183b9d60..98acd282 100644 --- a/CHANGELOG_PENDING.md +++ b/CHANGELOG_PENDING.md @@ -19,6 +19,7 @@ Special thanks to external contributors on this release: ### FEATURES: ### IMPROVEMENTS: +- [pex] \#3037 only log "Reached max attempts to dial" once ### BUG FIXES: - [p2p] \#2967 Fix file descriptor leaks diff --git a/p2p/pex/pex_reactor.go b/p2p/pex/pex_reactor.go index 057aadaa..0b043ca8 100644 --- a/p2p/pex/pex_reactor.go +++ b/p2p/pex/pex_reactor.go @@ -471,7 +471,11 @@ func (r *PEXReactor) dialPeer(addr *p2p.NetAddress) { attempts, lastDialed := r.dialAttemptsInfo(addr) if attempts > maxAttemptsToDial { - r.Logger.Error("Reached max attempts to dial", "addr", addr, "attempts", attempts) + // Do not log the message if the addr gets readded. + if attempts+1 == maxAttemptsToDial { + r.Logger.Info("Reached max attempts to dial", "addr", addr, "attempts", attempts) + r.attemptsToDial.Store(addr.DialString(), _attemptsToDial{attempts + 1, time.Now()}) + } r.book.MarkBad(addr) return } From c20fbed2f75cd8f3f3e4d3f2618f27839b7cd822 Mon Sep 17 00:00:00 2001 From: Zarko Milosevic Date: Thu, 24 Jan 2019 15:33:47 +0100 Subject: [PATCH 224/267] [WIP] fix halting issue (#3197) fix halting issue --- consensus/state.go | 12 +++---- consensus/state_test.go | 65 ++++++++++++++++++++++++++++++++++ consensus/types/round_state.go | 39 ++++++++++---------- 3 files changed, 91 insertions(+), 25 deletions(-) diff --git a/consensus/state.go b/consensus/state.go index 26b07417..c69dfb87 100644 --- a/consensus/state.go +++ b/consensus/state.go @@ -94,7 +94,6 @@ type ConsensusState struct { // internal state mtx sync.RWMutex cstypes.RoundState - triggeredTimeoutPrecommit bool state sm.State // State until height-1. // state changes may be triggered by: msgs from peers, @@ -732,6 +731,7 @@ func (cs *ConsensusState) handleTxsAvailable() { cs.mtx.Lock() defer cs.mtx.Unlock() // we only need to do this for round 0 + cs.enterNewRound(cs.Height, 0) cs.enterPropose(cs.Height, 0) } @@ -782,7 +782,7 @@ func (cs *ConsensusState) enterNewRound(height int64, round int) { cs.ProposalBlockParts = nil } cs.Votes.SetRound(round + 1) // also track next round (round+1) to allow round-skipping - cs.triggeredTimeoutPrecommit = false + cs.TriggeredTimeoutPrecommit = false cs.eventBus.PublishEventNewRound(cs.NewRoundEvent()) cs.metrics.Rounds.Set(float64(round)) @@ -1128,12 +1128,12 @@ func (cs *ConsensusState) enterPrecommit(height int64, round int) { func (cs *ConsensusState) enterPrecommitWait(height int64, round int) { logger := cs.Logger.With("height", height, "round", round) - if cs.Height != height || round < cs.Round || (cs.Round == round && cs.triggeredTimeoutPrecommit) { + if cs.Height != height || round < cs.Round || (cs.Round == round && cs.TriggeredTimeoutPrecommit) { logger.Debug( fmt.Sprintf( "enterPrecommitWait(%v/%v): Invalid args. "+ - "Current state is Height/Round: %v/%v/, triggeredTimeoutPrecommit:%v", - height, round, cs.Height, cs.Round, cs.triggeredTimeoutPrecommit)) + "Current state is Height/Round: %v/%v/, TriggeredTimeoutPrecommit:%v", + height, round, cs.Height, cs.Round, cs.TriggeredTimeoutPrecommit)) return } if !cs.Votes.Precommits(round).HasTwoThirdsAny() { @@ -1143,7 +1143,7 @@ func (cs *ConsensusState) enterPrecommitWait(height int64, round int) { defer func() { // Done enterPrecommitWait: - cs.triggeredTimeoutPrecommit = true + cs.TriggeredTimeoutPrecommit = true cs.newStep() }() diff --git a/consensus/state_test.go b/consensus/state_test.go index 40103e47..10c04fbc 100644 --- a/consensus/state_test.go +++ b/consensus/state_test.go @@ -1279,6 +1279,71 @@ func TestCommitFromPreviousRound(t *testing.T) { ensureNewRound(newRoundCh, height+1, 0) } +type fakeTxNotifier struct { + ch chan struct{} +} + +func (n *fakeTxNotifier) TxsAvailable() <-chan struct{} { + return n.ch +} + +func (n *fakeTxNotifier) Notify() { + n.ch <- struct{}{} +} + +func TestStartNextHeightCorrectly(t *testing.T) { + cs1, vss := randConsensusState(4) + cs1.config.SkipTimeoutCommit = false + cs1.txNotifier = &fakeTxNotifier{ch: make(chan struct{})} + + vs2, vs3, vs4 := vss[1], vss[2], vss[3] + height, round := cs1.Height, cs1.Round + + proposalCh := subscribe(cs1.eventBus, types.EventQueryCompleteProposal) + timeoutProposeCh := subscribe(cs1.eventBus, types.EventQueryTimeoutPropose) + + newRoundCh := subscribe(cs1.eventBus, types.EventQueryNewRound) + newBlockHeader := subscribe(cs1.eventBus, types.EventQueryNewBlockHeader) + addr := cs1.privValidator.GetPubKey().Address() + voteCh := subscribeToVoter(cs1, addr) + + // start round and wait for propose and prevote + startTestRound(cs1, height, round) + ensureNewRound(newRoundCh, height, round) + + ensureNewProposal(proposalCh, height, round) + rs := cs1.GetRoundState() + theBlockHash := rs.ProposalBlock.Hash() + theBlockParts := rs.ProposalBlockParts.Header() + + ensurePrevote(voteCh, height, round) + validatePrevote(t, cs1, round, vss[0], theBlockHash) + + signAddVotes(cs1, types.PrevoteType, theBlockHash, theBlockParts, vs2, vs3, vs4) + + ensurePrecommit(voteCh, height, round) + // the proposed block should now be locked and our precommit added + validatePrecommit(t, cs1, round, round, vss[0], theBlockHash, theBlockHash) + + rs = cs1.GetRoundState() + + // add precommits + signAddVotes(cs1, types.PrecommitType, nil, types.PartSetHeader{}, vs2) + signAddVotes(cs1, types.PrecommitType, theBlockHash, theBlockParts, vs3) + signAddVotes(cs1, types.PrecommitType, theBlockHash, theBlockParts, vs4) + + ensureNewBlockHeader(newBlockHeader, height, theBlockHash) + + rs = cs1.GetRoundState() + assert.True(t, rs.TriggeredTimeoutPrecommit) + + cs1.txNotifier.(*fakeTxNotifier).Notify() + + ensureNewTimeout(timeoutProposeCh, height+1, round, cs1.config.TimeoutPropose.Nanoseconds()) + rs = cs1.GetRoundState() + assert.False(t, rs.TriggeredTimeoutPrecommit, "triggeredTimeoutPrecommit should be false at the beginning of each round") +} + //------------------------------------------------------------------------------------------ // SlashingSuite // TODO: Slashing diff --git a/consensus/types/round_state.go b/consensus/types/round_state.go index 418f73a8..eab13b6c 100644 --- a/consensus/types/round_state.go +++ b/consensus/types/round_state.go @@ -65,25 +65,26 @@ func (rs RoundStepType) String() string { // NOTE: Not thread safe. Should only be manipulated by functions downstream // of the cs.receiveRoutine type RoundState struct { - Height int64 `json:"height"` // Height we are working on - Round int `json:"round"` - Step RoundStepType `json:"step"` - StartTime time.Time `json:"start_time"` - CommitTime time.Time `json:"commit_time"` // Subjective time when +2/3 precommits for Block at Round were found - Validators *types.ValidatorSet `json:"validators"` - Proposal *types.Proposal `json:"proposal"` - ProposalBlock *types.Block `json:"proposal_block"` - ProposalBlockParts *types.PartSet `json:"proposal_block_parts"` - LockedRound int `json:"locked_round"` - LockedBlock *types.Block `json:"locked_block"` - LockedBlockParts *types.PartSet `json:"locked_block_parts"` - ValidRound int `json:"valid_round"` // Last known round with POL for non-nil valid block. - ValidBlock *types.Block `json:"valid_block"` // Last known block of POL mentioned above. - ValidBlockParts *types.PartSet `json:"valid_block_parts"` // Last known block parts of POL metnioned above. - Votes *HeightVoteSet `json:"votes"` - CommitRound int `json:"commit_round"` // - LastCommit *types.VoteSet `json:"last_commit"` // Last precommits at Height-1 - LastValidators *types.ValidatorSet `json:"last_validators"` + Height int64 `json:"height"` // Height we are working on + Round int `json:"round"` + Step RoundStepType `json:"step"` + StartTime time.Time `json:"start_time"` + CommitTime time.Time `json:"commit_time"` // Subjective time when +2/3 precommits for Block at Round were found + Validators *types.ValidatorSet `json:"validators"` + Proposal *types.Proposal `json:"proposal"` + ProposalBlock *types.Block `json:"proposal_block"` + ProposalBlockParts *types.PartSet `json:"proposal_block_parts"` + LockedRound int `json:"locked_round"` + LockedBlock *types.Block `json:"locked_block"` + LockedBlockParts *types.PartSet `json:"locked_block_parts"` + ValidRound int `json:"valid_round"` // Last known round with POL for non-nil valid block. + ValidBlock *types.Block `json:"valid_block"` // Last known block of POL mentioned above. + ValidBlockParts *types.PartSet `json:"valid_block_parts"` // Last known block parts of POL metnioned above. + Votes *HeightVoteSet `json:"votes"` + CommitRound int `json:"commit_round"` // + LastCommit *types.VoteSet `json:"last_commit"` // Last precommits at Height-1 + LastValidators *types.ValidatorSet `json:"last_validators"` + TriggeredTimeoutPrecommit bool `json:"triggered_timeout_precommit"` } // Compressed version of the RoundState for use in RPC From 899259619241e118be99b88aebcb7ffc20c587c3 Mon Sep 17 00:00:00 2001 From: Anton Kaliaev Date: Thu, 24 Jan 2019 19:17:53 +0400 Subject: [PATCH 225/267] update changelog --- CHANGELOG.md | 35 +++++++++++++++++++++++++++++++++++ CHANGELOG_PENDING.md | 2 -- 2 files changed, 35 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d79fb867..567b8f44 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,40 @@ # Changelog +## v0.29.1 + +*January 24, 2019* + +Special thanks to external contributors on this release: +@infinytum, @gauthamzz + +This release contains two important fixes: one for p2p layer where we sometimes +were not closing connections and one for consensus layer where consensus with +no empty blocks (`create_empty_blocks = false`) could halt. + +Friendly reminder, we have a [bug bounty +program](https://hackerone.com/tendermint). + +### BREAKING CHANGES: + +* CLI/RPC/Config + +* Apps + +* Go API + +* Blockchain Protocol + +* P2P Protocol + +### FEATURES: + +### IMPROVEMENTS: +- [pex] [\#3037](https://github.com/tendermint/tendermint/issues/3037) Only log "Reached max attempts to dial" once + +### BUG FIXES: +- [consensus] [\#3199](https://github.com/tendermint/tendermint/issues/3199) Fix consensus halt with no empty blocks from not resetting triggeredTimeoutCommit +- [p2p] [\#2967](https://github.com/tendermint/tendermint/issues/2967) Fix file descriptor leak + ## v0.29.0 *January 21, 2019* diff --git a/CHANGELOG_PENDING.md b/CHANGELOG_PENDING.md index 98acd282..06b2ec52 100644 --- a/CHANGELOG_PENDING.md +++ b/CHANGELOG_PENDING.md @@ -19,7 +19,5 @@ Special thanks to external contributors on this release: ### FEATURES: ### IMPROVEMENTS: -- [pex] \#3037 only log "Reached max attempts to dial" once ### BUG FIXES: -- [p2p] \#2967 Fix file descriptor leaks From bb0a9b3d6d7059f40aa8201311e644b67514bab5 Mon Sep 17 00:00:00 2001 From: Anton Kaliaev Date: Thu, 24 Jan 2019 19:18:32 +0400 Subject: [PATCH 226/267] bump version --- version/version.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version/version.go b/version/version.go index 87d81c6f..9d05afe6 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.29.0" + TMCoreSemVer = "0.29.1" // ABCISemVer is the semantic version of the ABCI library ABCISemVer = "0.15.0" From 90970d0ddc6c0a66e521a5be55f6d60870e8ecfa Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Thu, 24 Jan 2019 11:19:52 -0500 Subject: [PATCH 227/267] fix changelog --- CHANGELOG.md | 16 ++-------------- 1 file changed, 2 insertions(+), 14 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 567b8f44..437b7970 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,22 +14,10 @@ no empty blocks (`create_empty_blocks = false`) could halt. Friendly reminder, we have a [bug bounty program](https://hackerone.com/tendermint). -### BREAKING CHANGES: - -* CLI/RPC/Config - -* Apps - -* Go API - -* Blockchain Protocol - -* P2P Protocol - -### FEATURES: - ### IMPROVEMENTS: - [pex] [\#3037](https://github.com/tendermint/tendermint/issues/3037) Only log "Reached max attempts to dial" once +- [rpc] [\#3159](https://github.com/tendermint/tendermint/issues/3159) Expose + `triggered_timeout_commit` in the `/dump_consensus_state` ### BUG FIXES: - [consensus] [\#3199](https://github.com/tendermint/tendermint/issues/3199) Fix consensus halt with no empty blocks from not resetting triggeredTimeoutCommit From 75cbe4a1c1bfb853b393371e6f525aa2b49d4c70 Mon Sep 17 00:00:00 2001 From: Joon Date: Fri, 25 Jan 2019 22:10:36 +0900 Subject: [PATCH 228/267] R4R: Config TestRoot modification for LCD test (#3177) * add ResetTestRootWithChainID * modify chainid --- config/toml.go | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/config/toml.go b/config/toml.go index 79ae99be..e842e9e3 100644 --- a/config/toml.go +++ b/config/toml.go @@ -2,6 +2,7 @@ package config import ( "bytes" + "fmt" "os" "path/filepath" "text/template" @@ -317,6 +318,10 @@ namespace = "{{ .Instrumentation.Namespace }}" /****** these are for test settings ***********/ func ResetTestRoot(testName string) *Config { + return ResetTestRootWithChainID(testName, "") +} + +func ResetTestRootWithChainID(testName string, chainID string) *Config { rootDir := os.ExpandEnv("$HOME/.tendermint_test") rootDir = filepath.Join(rootDir, testName) // Remove ~/.tendermint_test_bak @@ -353,6 +358,10 @@ func ResetTestRoot(testName string) *Config { writeDefaultConfigFile(configFilePath) } if !cmn.FileExists(genesisFilePath) { + if chainID == "" { + chainID = "tendermint_test" + } + testGenesis := fmt.Sprintf(testGenesisFmt, chainID) cmn.MustWriteFile(genesisFilePath, []byte(testGenesis), 0644) } // we always overwrite the priv val @@ -363,9 +372,9 @@ func ResetTestRoot(testName string) *Config { return config } -var testGenesis = `{ +var testGenesisFmt = `{ "genesis_time": "2018-10-10T08:20:13.695936996Z", - "chain_id": "tendermint_test", + "chain_id": "%s", "validators": [ { "pub_key": { From d6dd43cdaa6e80ff4184b5e5d8d7a0a101114e75 Mon Sep 17 00:00:00 2001 From: Ismail Khoffi Date: Fri, 25 Jan 2019 14:38:26 +0100 Subject: [PATCH 229/267] adr: style fixes (#3206) - links to issues - fix a few markdown glitches - inline code - etc --- docs/architecture/adr-033-pubsub.md | 37 +++++++++++++++++------------ 1 file changed, 22 insertions(+), 15 deletions(-) diff --git a/docs/architecture/adr-033-pubsub.md b/docs/architecture/adr-033-pubsub.md index c52bf44a..55bf320c 100644 --- a/docs/architecture/adr-033-pubsub.md +++ b/docs/architecture/adr-033-pubsub.md @@ -5,13 +5,15 @@ Author: Anton Kaliaev (@melekes) ## Changelog 02-10-2018: Initial draft + 16-01-2019: Second version based on our conversation with Jae + 17-01-2019: Third version explaining how new design solves current issues ## Context Since the initial version of the pubsub, there's been a number of issues -raised: #951, #1879, #1880. Some of them are high-level issues questioning the +raised: [#951], [#1879], [#1880]. Some of them are high-level issues questioning the core design choices made. Others are minor and mostly about the interface of `Subscribe()` / `Publish()` functions. @@ -91,7 +93,7 @@ can make capacity an argument. ## Decision -Change Subscribe() function to return a `Subscription` struct: +Change `Subscribe()` function to return a `Subscription` struct: ```go type Subscription struct { @@ -103,18 +105,18 @@ func (s *Subscription) Cancelled() <-chan struct{} func (s *Subscription) Err() error ``` -Out returns a channel onto which messages and tags are published. -Unsubscribe/UnsubscribeAll does not close the channel to avoid clients from +`Out()` returns a channel onto which messages and tags are published. +`Unsubscribe`/`UnsubscribeAll` does not close the channel to avoid clients from receiving a nil message. -Cancelled returns a channel that's closed when the subscription is terminated +`Cancelled()` returns a channel that's closed when the subscription is terminated and supposed to be used in a select statement. -If Cancelled is not closed yet, Err() returns nil. -If Cancelled is closed, Err returns a non-nil error explaining why: -Unsubscribed if the subscriber choose to unsubscribe, -OutOfCapacity if the subscriber is not pulling messages fast enough and the Out channel become full. -After Err returns a non-nil error, successive calls to Err() return the same error. +If the channel returned by `Cancelled()` is not closed yet, `Err()` returns nil. +If the channel is closed, `Err()` returns a non-nil error explaining why: +`ErrUnsubscribed` if the subscriber choose to unsubscribe, +`ErrOutOfCapacity` if the subscriber is not pulling messages fast enough and the channel returned by `Out()` became full. +After `Err()` returns a non-nil error, successive calls to `Err() return the same error. ```go subscription, err := pubsub.Subscribe(...) @@ -130,18 +132,18 @@ select { } ``` -Make Out() channel buffered (cap: 1) by default. In most cases, we want to +Make the `Out()` channel buffered (with capacity 1) by default. In most cases, we want to terminate the slow subscriber. Only in rare cases, we want to block the pubsub (e.g. when debugging consensus). This should lower the chances of the pubsub being frozen. ```go // outCap can be used to set capacity of Out channel (1 by default). Set to 0 -for unbuffered channel (WARNING: it may block the pubsub). +// for unbuffered channel (WARNING: it may block the pubsub). Subscribe(ctx context.Context, clientID string, query Query, outCap... int) (Subscription, error) { ``` -Also, Out() channel should return tags along with a message: +Also, the `Out()` channel should return tags along with a message: ```go type MsgAndTags struct { @@ -154,12 +156,12 @@ to inform clients of which Tags were used with Msg. ### How this new design solves the current issues? -https://github.com/tendermint/tendermint/issues/951 (https://github.com/tendermint/tendermint/issues/1880) +[#951] ([#1880]): Because of non-blocking send, situation where we'll deadlock is not possible anymore. If the client stops reading messages, it will be removed. -https://github.com/tendermint/tendermint/issues/1879 +[#1879]: MsgAndTags is used now instead of a plain message. @@ -191,3 +193,8 @@ In review - (since v1) no concurrency when it comes to publishing messages ### Neutral + + +[#951]: https://github.com/tendermint/tendermint/issues/951 +[#1879]: https://github.com/tendermint/tendermint/issues/1879 +[#1880]: https://github.com/tendermint/tendermint/issues/1880 From ddbdffb4e52710a3bbbb54f2fc9eb80719d7f065 Mon Sep 17 00:00:00 2001 From: Jae Kwon Date: Fri, 25 Jan 2019 11:00:55 -0800 Subject: [PATCH 230/267] add design philosophy doc (#3034) --- PHILOSOPHY.md | 158 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 158 insertions(+) create mode 100644 PHILOSOPHY.md diff --git a/PHILOSOPHY.md b/PHILOSOPHY.md new file mode 100644 index 00000000..cf79710f --- /dev/null +++ b/PHILOSOPHY.md @@ -0,0 +1,158 @@ +## Design goals + +The design goals for Tendermint (and the SDK and related libraries) are: + + * Simplicity and Legibility + * Parallel performance, namely ability to utilize multicore architecture + * Ability to evolve the codebase bug-free + * Debuggability + * Complete correctness that considers all edge cases, esp in concurrency + * Future-proof modular architecture, message protocol, APIs, and encapsulation + + +### Justification + +Legibility is key to maintaining bug-free software as it evolves toward more +optimizations, more ease of debugging, and additional features. + +It is too easy to introduce bugs over time by replacing lines of code with +those that may panic, which means ideally locks are unlocked by defer +statements. + +For example, + +```go +func (obj *MyObj) something() { + mtx.Lock() + obj.something = other + mtx.Unlock() +} +``` + +It is too easy to refactor the codebase in the future to replace `other` with +`other.String()` for example, and this may introduce a bug that causes a +deadlock. So as much as reasonably possible, we need to be using defer +statements, even though it introduces additional overhead. + +If it is necessary to optimize the unlocking of mutex locks, the solution is +more modularity via smaller functions, so that defer'd unlocks are scoped +within a smaller function. + +Similarly, idiomatic for-loops should always be preferred over those that use +custom counters, because it is too easy to evolve the body of a for-loop to +become more complicated over time, and it becomes more and more difficult to +assess the correctness of such a for-loop by visual inspection. + + +### On performance + +It doesn't matter whether there are alternative implementations that are 2x or +3x more performant, when the software doesn't work, deadlocks, or if bugs +cannot be debugged. By taking advantage of multicore concurrency, the +Tendermint implementation will at least be an order of magnitude within the +range of what is theoretically possible. The design philosophy of Tendermint, +and the choice of Go as implementation language, is designed to make Tendermint +implementation the standard specification for concurrent BFT software. + +By focusing on the message protocols (e.g. ABCI, p2p messages), and +encapsulation e.g. IAVL module, (relatively) independent reactors, we are both +implementing a standard implementation to be used as the specification for +future implementations in more optimizable languages like Rust, Java, and C++; +as well as creating sufficiently performant software. Tendermint Core will +never be as fast as future implementations of the Tendermint Spec, because Go +isn't designed to be as fast as possible. The advantage of using Go is that we +can develop the whole stack of modular components **faster** than in other +languages. + +Furthermore, the real bottleneck is in the application layer, and it isn't +necessary to support more than a sufficiently decentralized set of validators +(e.g. 100 ~ 300 validators is sufficient, with delegated bonded PoS). + +Instead of optimizing Tendermint performance down to the metal, lets focus on +optimizing on other matters, namely ability to push feature complete software +that works well enough, can be debugged and maintained, and can serve as a spec +for future implementations. + + +### On encapsulation + +In order to create maintainable, forward-optimizable software, it is critical +to develop well-encapsulated objects that have well understood properties, and +to re-use these easy-to-use-correctly components as building blocks for further +encapsulated meta-objects. + +For example, mutexes are cheap enough for Tendermint's design goals when there +isn't goroutine contention, so it is encouraged to create concurrency safe +structures with struct-level mutexes. If they are used in the context of +non-concurrent logic, then the performance is good enough. If they are used in +the context of concurrent logic, then it will still perform correctly. + +Examples of this design principle can be seen in the types.ValidatorSet struct, +and the cmn.Rand struct. It's one single struct declaration that can be used +in both concurrent and non-concurrent logic, and due to its well encapsulation, +it's easy to get the usage of the mutex right. + +#### example: cmn.Rand: + +`The default Source is safe for concurrent use by multiple goroutines, but +Sources created by NewSource are not`. The reason why the default +package-level source is safe for concurrent use is because it is protected (see +`lockedSource` in https://golang.org/src/math/rand/rand.go). + +But we shouldn't rely on the global source, we should be creating our own +Rand/Source instances and using them, especially for determinism in testing. +So it is reasonable to have cmn.Rand be protected by a mutex. Whether we want +our own implementation of Rand is another question, but the answer there is +also in the affirmative. Sometimes you want to know where Rand is being used +in your code, so it becomes a simple matter of dropping in a log statement to +inject inspectability into Rand usage. Also, it is nice to be able to extend +the functionality of Rand with custom methods. For these reasons, and for the +reasons which is outlined in this design philosophy document, we should +continue to use the cmn.Rand object, with mutex protection. + +Another key aspect of good encapsulation is the choice of exposed vs unexposed +methods. It should be clear to the reader of the code, which methods are +intended to be used in what context, and what safe usage is. Part of this is +solved by hiding methods via unexported methods. Another part of this is +naming conventions on the methods (e.g. underscores) with good documentation, +and code organization. If there are too many exposed methods and it isn't +clear what methods have what side effects, then there is something wrong about +the design of abstractions that should be revisited. + + +### On concurrency + +In order for Tendermint to remain relevant in the years to come, it is vital +for Tendermint to take advantage of multicore architectures. Due to the nature +of the problem, namely consensus across a concurrent p2p gossip network, and to +handle RPC requests for a large number of consuming subscribers, it is +unavoidable for Tendermint development to require expertise in concurrency +design, especially when it comes to the reactor design, and also for RPC +request handling. + + +## Guidelines + +Here are some guidelines for designing for (sufficient) performance and concurrency: + + * Mutex locks are cheap enough when there isn't contention. + * Do not optimize code without analytical or observed proof that it is in a hot path. + * Don't over-use channels when mutex locks w/ encapsulation are sufficient. + * The need to drain channels are often a hint of unconsidered edge cases. + * The creation of O(N) one-off goroutines is generally technical debt that + needs to get addressed sooner than later. Avoid creating too many +goroutines as a patch around incomplete concurrency design, or at least be +aware of the debt and do not invest in the debt. On the other hand, Tendermint +is designed to have a limited number of peers (e.g. 10 or 20), so the creation +of O(C) goroutines per O(P) peers is still O(C\*P=constant). + * Use defer statements to unlock as much as possible. If you want to unlock sooner, + try to create more modular functions that do make use of defer statements. + +## Matras + +* Premature optimization kills +* Readability is paramount +* Beautiful is better than fast. +* In the face of ambiguity, refuse the temptation to guess. +* In the face of bugs, refuse the temptation to cover the bug. +* There should be one-- and preferably only one --obvious way to do it. From a58d5897e42ff58ea0919e46444f4508ed38df90 Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Fri, 25 Jan 2019 15:01:22 -0500 Subject: [PATCH 231/267] note about TmCoreSemVer --- version/version.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/version/version.go b/version/version.go index 9d05afe6..86c38c03 100644 --- a/version/version.go +++ b/version/version.go @@ -18,6 +18,8 @@ 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. + // XXX: Don't change the name of this variable or you will break + // automation :) TMCoreSemVer = "0.29.1" // ABCISemVer is the semantic version of the ABCI library From 57af99d901fccd7078a4da910b665842321ac4cd Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Fri, 25 Jan 2019 15:01:39 -0500 Subject: [PATCH 232/267] types: comments on user vs internal events Distinguish between user events and internal consensus events --- types/events.go | 31 ++++++++++++++++++++----------- 1 file changed, 20 insertions(+), 11 deletions(-) diff --git a/types/events.go b/types/events.go index 9b3b158d..b70bc9dc 100644 --- a/types/events.go +++ b/types/events.go @@ -11,21 +11,30 @@ import ( // Reserved event types (alphabetically sorted). const ( - EventCompleteProposal = "CompleteProposal" - EventLock = "Lock" + // Block level events for mass consumption by users. + // These events are triggered from the state package, + // after a block has been committed. + // These are also used by the tx indexer for async indexing. + // All of this data can be fetched through the rpc. EventNewBlock = "NewBlock" EventNewBlockHeader = "NewBlockHeader" - EventNewRound = "NewRound" - EventNewRoundStep = "NewRoundStep" - EventPolka = "Polka" - EventRelock = "Relock" - EventTimeoutPropose = "TimeoutPropose" - EventTimeoutWait = "TimeoutWait" EventTx = "Tx" - EventUnlock = "Unlock" - EventValidBlock = "ValidBlock" EventValidatorSetUpdates = "ValidatorSetUpdates" - EventVote = "Vote" + + // Internal consensus events. + // These are used for testing the consensus state machine. + // They can also be used to build real-time consensus visualizers. + EventCompleteProposal = "CompleteProposal" + EventLock = "Lock" + EventNewRound = "NewRound" + EventNewRoundStep = "NewRoundStep" + EventPolka = "Polka" + EventRelock = "Relock" + EventTimeoutPropose = "TimeoutPropose" + EventTimeoutWait = "TimeoutWait" + EventUnlock = "Unlock" + EventValidBlock = "ValidBlock" + EventVote = "Vote" ) /////////////////////////////////////////////////////////////////////////////// From 9b6b792ce70e52516a643abba077ee82668db48b Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Fri, 25 Jan 2019 18:11:31 -0500 Subject: [PATCH 233/267] pubsub: comments --- libs/pubsub/pubsub.go | 32 +++++++++++++++++++++----------- 1 file changed, 21 insertions(+), 11 deletions(-) diff --git a/libs/pubsub/pubsub.go b/libs/pubsub/pubsub.go index b4e392bb..cb41f8b3 100644 --- a/libs/pubsub/pubsub.go +++ b/libs/pubsub/pubsub.go @@ -223,9 +223,11 @@ func (s *Server) Unsubscribe(ctx context.Context, clientID string, query Query) } // original query is used here because we're using pointers as map keys + // ? select { case s.cmds <- cmd{op: unsub, clientID: clientID, query: origQuery}: s.mtx.Lock() + // if its the only query left, should we also delete the client? delete(clientSubscriptions, query.String()) s.mtx.Unlock() return nil @@ -353,20 +355,24 @@ func (state *state) remove(clientID string, q Query) { } ch, ok := clientToChannelMap[clientID] - if ok { - close(ch) + if !ok { + return + } - delete(state.clients[clientID], q) + close(ch) - // if it not subscribed to anything else, remove the client - if len(state.clients[clientID]) == 0 { - delete(state.clients, clientID) - } + // remove the query from client map. + // if client is not subscribed to anything else, remove it. + delete(state.clients[clientID], q) + if len(state.clients[clientID]) == 0 { + delete(state.clients, clientID) + } - delete(state.queries[q], clientID) - if len(state.queries[q]) == 0 { - delete(state.queries, q) - } + // remove the client from query map. + // if query has no other clients subscribed, remove it. + delete(state.queries[q], clientID) + if len(state.queries[q]) == 0 { + delete(state.queries, q) } } @@ -380,11 +386,15 @@ func (state *state) removeAll(clientID string) { ch := state.queries[q][clientID] close(ch) + // remove the client from query map. + // if query has no other clients subscribed, remove it. delete(state.queries[q], clientID) if len(state.queries[q]) == 0 { delete(state.queries, q) } } + + // remove the client. delete(state.clients, clientID) } From d91ea9b59d7f77b20b92ae8caf1f37d6584b994c Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Fri, 25 Jan 2019 18:28:06 -0500 Subject: [PATCH 234/267] adr-033 update --- docs/architecture/adr-033-pubsub.md | 69 ++++++++++++++++++++++++----- 1 file changed, 58 insertions(+), 11 deletions(-) diff --git a/docs/architecture/adr-033-pubsub.md b/docs/architecture/adr-033-pubsub.md index 55bf320c..88922646 100644 --- a/docs/architecture/adr-033-pubsub.md +++ b/docs/architecture/adr-033-pubsub.md @@ -10,6 +10,8 @@ Author: Anton Kaliaev (@melekes) 17-01-2019: Third version explaining how new design solves current issues +25-01-2019: Fourth version to treat buffered and unbuffered channels differently + ## Context Since the initial version of the pubsub, there's been a number of issues @@ -53,7 +55,10 @@ channels to distribute msg to these goroutines). ### Non-blocking send -There is also a question whenever we should have a non-blocking send: +There is also a question whenever we should have a non-blocking send. +Currently, sends are blocking, so publishing to one client can block on +publishing to another. This means a slow or unresponsive client can halt the +system. Instead, we can use a non-blocking send: ```go for each subscriber { @@ -89,10 +94,25 @@ Go channels are de-facto standard for carrying data between goroutines. ### Why `Subscribe()` accepts an `out` channel? Because in our tests, we create buffered channels (cap: 1). Alternatively, we -can make capacity an argument. +can make capacity an argument and return a channel. ## Decision +### MsgAndTags + +Use a `MsgAndTags` struct on the subscription channel to indicate what tags the +msg matched. + +```go +type MsgAndTags struct { + Msg interface{} + Tags TagMap +} +``` + +### Subscription Struct + + Change `Subscribe()` function to return a `Subscription` struct: ```go @@ -132,27 +152,53 @@ select { } ``` +### Capacity and Subscriptions + Make the `Out()` channel buffered (with capacity 1) by default. In most cases, we want to terminate the slow subscriber. Only in rare cases, we want to block the pubsub (e.g. when debugging consensus). This should lower the chances of the pubsub being frozen. ```go -// outCap can be used to set capacity of Out channel (1 by default). Set to 0 -// for unbuffered channel (WARNING: it may block the pubsub). +// outCap can be used to set capacity of Out channel +// (1 by default, must be greater than 0). Subscribe(ctx context.Context, clientID string, query Query, outCap... int) (Subscription, error) { ``` -Also, the `Out()` channel should return tags along with a message: +Use a different function for an unbuffered channel: ```go -type MsgAndTags struct { - Msg interface{} - Tags TagMap -} +// Subscription uses an unbuffered channel. Publishing will block. +SubscribeUnbuffered(ctx context.Context, clientID string, query Query) (Subscription, error) { ``` -to inform clients of which Tags were used with Msg. +SubscribeUnbuffered should not be exposed to users. + +### Blocking/Nonblocking + +The publisher should treat these kinds of channels separately. +It should block on unbuffered channels (for use with internal consensus events +in the consensus tests) and not block on the buffered ones. If a client is too +slow to keep up with it's messages, it's subscription is terminated: + +for each subscription { + out := subscription.outChan + if cap(out) == 0 { + // block on unbuffered channel + out <- msg + } else { + // don't block on buffered channels + select { + case out <- msg: + default: + // set the error, notify on the cancel chan + subscription.err = fmt.Errorf("client is too slow for msg) + close(subscription.cancelChan) + + // ... unsubscribe and close out + } + } +} ### How this new design solves the current issues? @@ -167,7 +213,7 @@ MsgAndTags is used now instead of a plain message. ### Future problems and their possible solutions -https://github.com/tendermint/tendermint/issues/2826 +[#2826] One question I am still pondering about: how to prevent pubsub from slowing down consensus. We can increase the pubsub queue size (which is 0 now). Also, @@ -198,3 +244,4 @@ In review [#951]: https://github.com/tendermint/tendermint/issues/951 [#1879]: https://github.com/tendermint/tendermint/issues/1879 [#1880]: https://github.com/tendermint/tendermint/issues/1880 +[#2826]: https://github.com/tendermint/tendermint/issues/2826 From 71e5939441b3ecdbad99345d9733ebe7e271645d Mon Sep 17 00:00:00 2001 From: cong Date: Mon, 28 Jan 2019 15:36:35 +0800 Subject: [PATCH 235/267] start eventBus & indexerService before replay and use them while replaying blocks (#3194) so if we did not index the last block (because of panic or smth else), we index it during replay Closes #3186 --- CHANGELOG_PENDING.md | 1 + consensus/replay.go | 9 +++++ consensus/replay_file.go | 13 +++---- node/node.go | 73 ++++++++++++++++++++++------------------ state/execution.go | 3 +- state/execution_test.go | 4 +-- 6 files changed, 61 insertions(+), 42 deletions(-) diff --git a/CHANGELOG_PENDING.md b/CHANGELOG_PENDING.md index 06b2ec52..6b7677b5 100644 --- a/CHANGELOG_PENDING.md +++ b/CHANGELOG_PENDING.md @@ -21,3 +21,4 @@ Special thanks to external contributors on this release: ### IMPROVEMENTS: ### BUG FIXES: +- [node] \#3186 EventBus and indexerService should be started before first block (for replay last block on handshake) execution diff --git a/consensus/replay.go b/consensus/replay.go index 16b66e86..96e23b7b 100644 --- a/consensus/replay.go +++ b/consensus/replay.go @@ -196,6 +196,7 @@ type Handshaker struct { stateDB dbm.DB initialState sm.State store sm.BlockStore + eventBus types.BlockEventPublisher genDoc *types.GenesisDoc logger log.Logger @@ -209,6 +210,7 @@ func NewHandshaker(stateDB dbm.DB, state sm.State, stateDB: stateDB, initialState: state, store: store, + eventBus: types.NopEventBus{}, genDoc: genDoc, logger: log.NewNopLogger(), nBlocks: 0, @@ -219,6 +221,12 @@ func (h *Handshaker) SetLogger(l log.Logger) { h.logger = l } +// SetEventBus - sets the event bus for publishing block related events. +// If not called, it defaults to types.NopEventBus. +func (h *Handshaker) SetEventBus(eventBus types.BlockEventPublisher) { + h.eventBus = eventBus +} + func (h *Handshaker) NBlocks() int { return h.nBlocks } @@ -432,6 +440,7 @@ func (h *Handshaker) replayBlock(state sm.State, height int64, proxyApp proxy.Ap meta := h.store.LoadBlockMeta(height) blockExec := sm.NewBlockExecutor(h.stateDB, h.logger, proxyApp, sm.MockMempool{}, sm.MockEvidencePool{}) + blockExec.SetEventBus(h.eventBus) var err error state, err = blockExec.ApplyBlock(state, meta.BlockID, block) diff --git a/consensus/replay_file.go b/consensus/replay_file.go index 2d087914..3e92bad6 100644 --- a/consensus/replay_file.go +++ b/consensus/replay_file.go @@ -326,17 +326,18 @@ func newConsensusStateForReplay(config cfg.BaseConfig, csConfig *cfg.ConsensusCo cmn.Exit(fmt.Sprintf("Error starting proxy app conns: %v", err)) } - handshaker := NewHandshaker(stateDB, state, blockStore, gdoc) - err = handshaker.Handshake(proxyApp) - if err != nil { - cmn.Exit(fmt.Sprintf("Error on handshake: %v", err)) - } - eventBus := types.NewEventBus() if err := eventBus.Start(); err != nil { cmn.Exit(fmt.Sprintf("Failed to start event bus: %v", err)) } + handshaker := NewHandshaker(stateDB, state, blockStore, gdoc) + handshaker.SetEventBus(eventBus) + err = handshaker.Handshake(proxyApp) + if err != nil { + cmn.Exit(fmt.Sprintf("Error on handshake: %v", err)) + } + mempool, evpool := sm.MockMempool{}, sm.MockEvidencePool{} blockExec := sm.NewBlockExecutor(stateDB, log.TestingLogger(), proxyApp.Consensus(), mempool, evpool) diff --git a/node/node.go b/node/node.go index 0c38fc11..b7ca6517 100644 --- a/node/node.go +++ b/node/node.go @@ -217,11 +217,51 @@ func NewNode(config *cfg.Config, return nil, fmt.Errorf("Error starting proxy app connections: %v", err) } + // EventBus and IndexerService must be started before the handshake because + // we might need to index the txs of the replayed block as this might not have happened + // when the node stopped last time (i.e. the node stopped after it saved the block + // but before it indexed the txs, or, endblocker panicked) + eventBus := types.NewEventBus() + eventBus.SetLogger(logger.With("module", "events")) + + err = eventBus.Start() + if err != nil { + return nil, err + } + + // Transaction indexing + var txIndexer txindex.TxIndexer + switch config.TxIndex.Indexer { + case "kv": + store, err := dbProvider(&DBContext{"tx_index", config}) + if err != nil { + return nil, err + } + if config.TxIndex.IndexTags != "" { + txIndexer = kv.NewTxIndex(store, kv.IndexTags(splitAndTrimEmpty(config.TxIndex.IndexTags, ",", " "))) + } else if config.TxIndex.IndexAllTags { + txIndexer = kv.NewTxIndex(store, kv.IndexAllTags()) + } else { + txIndexer = kv.NewTxIndex(store) + } + default: + txIndexer = &null.TxIndex{} + } + + indexerService := txindex.NewIndexerService(txIndexer, eventBus) + indexerService.SetLogger(logger.With("module", "txindex")) + + err = indexerService.Start() + if err != nil { + return nil, err + } + // Create the handshaker, which calls RequestInfo, sets the AppVersion on the state, // and replays any blocks as necessary to sync tendermint with the app. consensusLogger := logger.With("module", "consensus") handshaker := cs.NewHandshaker(stateDB, state, blockStore, genDoc) handshaker.SetLogger(consensusLogger) + handshaker.SetEventBus(eventBus) if err := handshaker.Handshake(proxyApp); err != nil { return nil, fmt.Errorf("Error during handshake: %v", err) } @@ -343,35 +383,10 @@ func NewNode(config *cfg.Config, consensusReactor := cs.NewConsensusReactor(consensusState, fastSync, cs.ReactorMetrics(csMetrics)) consensusReactor.SetLogger(consensusLogger) - eventBus := types.NewEventBus() - eventBus.SetLogger(logger.With("module", "events")) - // services which will be publishing and/or subscribing for messages (events) // consensusReactor will set it on consensusState and blockExecutor consensusReactor.SetEventBus(eventBus) - // Transaction indexing - var txIndexer txindex.TxIndexer - switch config.TxIndex.Indexer { - case "kv": - store, err := dbProvider(&DBContext{"tx_index", config}) - if err != nil { - return nil, err - } - if config.TxIndex.IndexTags != "" { - txIndexer = kv.NewTxIndex(store, kv.IndexTags(splitAndTrimEmpty(config.TxIndex.IndexTags, ",", " "))) - } else if config.TxIndex.IndexAllTags { - txIndexer = kv.NewTxIndex(store, kv.IndexAllTags()) - } else { - txIndexer = kv.NewTxIndex(store) - } - default: - txIndexer = &null.TxIndex{} - } - - indexerService := txindex.NewIndexerService(txIndexer, eventBus) - indexerService.SetLogger(logger.With("module", "txindex")) - p2pLogger := logger.With("module", "p2p") nodeInfo, err := makeNodeInfo( config, @@ -534,11 +549,6 @@ func (n *Node) OnStart() error { time.Sleep(genTime.Sub(now)) } - err := n.eventBus.Start() - if err != nil { - return err - } - // Add private IDs to addrbook to block those peers being added n.addrBook.AddPrivateIDs(splitAndTrimEmpty(n.config.P2P.PrivatePeerIDs, ",", " ")) @@ -582,8 +592,7 @@ func (n *Node) OnStart() error { } } - // start tx indexer - return n.indexerService.Start() + return nil } // OnStop stops the Node. It implements cmn.Service. diff --git a/state/execution.go b/state/execution.go index 85bbd382..d59c8af0 100644 --- a/state/execution.go +++ b/state/execution.go @@ -49,8 +49,7 @@ func BlockExecutorWithMetrics(metrics *Metrics) BlockExecutorOption { // NewBlockExecutor returns a new BlockExecutor with a NopEventBus. // Call SetEventBus to provide one. -func NewBlockExecutor(db dbm.DB, logger log.Logger, proxyApp proxy.AppConnConsensus, - mempool Mempool, evpool EvidencePool, options ...BlockExecutorOption) *BlockExecutor { +func NewBlockExecutor(db dbm.DB, logger log.Logger, proxyApp proxy.AppConnConsensus, mempool Mempool, evpool EvidencePool, options ...BlockExecutorOption) *BlockExecutor { res := &BlockExecutor{ db: db, proxyApp: proxyApp, diff --git a/state/execution_test.go b/state/execution_test.go index 21df1ee5..b14ee649 100644 --- a/state/execution_test.go +++ b/state/execution_test.go @@ -309,8 +309,8 @@ func TestEndBlockValidatorUpdates(t *testing.T) { state, stateDB := state(1, 1) - blockExec := NewBlockExecutor(stateDB, log.TestingLogger(), proxyApp.Consensus(), - MockMempool{}, MockEvidencePool{}) + blockExec := NewBlockExecutor(stateDB, log.TestingLogger(), proxyApp.Consensus(), MockMempool{}, MockEvidencePool{}) + eventBus := types.NewEventBus() err = eventBus.Start() require.NoError(t, err) From 8d2dd7e554250cd1e912d4cd9b9ff829c2110ab3 Mon Sep 17 00:00:00 2001 From: Anton Kaliaev Date: Mon, 28 Jan 2019 12:38:11 +0400 Subject: [PATCH 236/267] refactor TestListenerConnectDeadlines to avoid data races (#3201) Fixes #3179 --- privval/socket_test.go | 43 ++++++++++++++++++------------------------ 1 file changed, 18 insertions(+), 25 deletions(-) diff --git a/privval/socket_test.go b/privval/socket_test.go index b411b7f3..7f7bbd89 100644 --- a/privval/socket_test.go +++ b/privval/socket_test.go @@ -98,36 +98,29 @@ func TestListenerAcceptDeadlines(t *testing.T) { func TestListenerConnectDeadlines(t *testing.T) { for _, tc := range listenerTestCases(t, time.Second, time.Millisecond) { - readyc := make(chan struct{}) - donec := make(chan struct{}) - go func(ln net.Listener) { - defer close(donec) - - c, err := ln.Accept() + go func(dialer Dialer) { + _, err := dialer() if err != nil { - t.Fatal(err) + panic(err) } - <-readyc + }(tc.dialer) - time.Sleep(2 * time.Millisecond) - - msg := make([]byte, 200) - _, err = c.Read(msg) - opErr, ok := err.(*net.OpError) - if !ok { - t.Fatalf("for %s listener, have %v, want *net.OpError", tc.description, err) - } - - if have, want := opErr.Op, "read"; have != want { - t.Errorf("for %s listener, have %v, want %v", tc.description, have, want) - } - }(tc.listener) - - _, err := tc.dialer() + c, err := tc.listener.Accept() if err != nil { t.Fatal(err) } - close(readyc) - <-donec + + time.Sleep(2 * time.Millisecond) + + msg := make([]byte, 200) + _, err = c.Read(msg) + opErr, ok := err.(*net.OpError) + if !ok { + t.Fatalf("for %s listener, have %v, want *net.OpError", tc.description, err) + } + + if have, want := opErr.Op, "read"; have != want { + t.Errorf("for %s listener, have %v, want %v", tc.description, have, want) + } } } From ff3c4bfc765199c31af6487661e1b23aa8be7037 Mon Sep 17 00:00:00 2001 From: Anton Kaliaev Date: Mon, 28 Jan 2019 14:57:47 +0400 Subject: [PATCH 237/267] add go-deadlock tool to help detect deadlocks (#3218) * add go-deadlock tool to help detect deadlocks Run it with `make test_with_deadlock`. After it's done, use Git to cleanup `git checkout .` Link: https://github.com/sasha-s/go-deadlock/ Replaces https://github.com/tendermint/tendermint/pull/3148 * add a target to cleanup changes --- CHANGELOG_PENDING.md | 1 + Makefile | 15 ++++++++++++++- scripts/get_tools.sh | 5 +++++ 3 files changed, 20 insertions(+), 1 deletion(-) diff --git a/CHANGELOG_PENDING.md b/CHANGELOG_PENDING.md index 6b7677b5..ac4b1628 100644 --- a/CHANGELOG_PENDING.md +++ b/CHANGELOG_PENDING.md @@ -19,6 +19,7 @@ Special thanks to external contributors on this release: ### FEATURES: ### IMPROVEMENTS: +- [tools] add go-deadlock tool to help detect deadlocks ### BUG FIXES: - [node] \#3186 EventBus and indexerService should be started before first block (for replay last block on handshake) execution diff --git a/Makefile b/Makefile index 1250fccb..853a01c9 100644 --- a/Makefile +++ b/Makefile @@ -226,6 +226,19 @@ test_race: @echo "--> Running go test --race" @GOCACHE=off go test -p 1 -v -race $(PACKAGES) +# uses https://github.com/sasha-s/go-deadlock/ to detect potential deadlocks +test_with_deadlock: + find . -name "*.go" | grep -v "vendor/" | xargs -n 1 sed -i.bak 's/sync.RWMutex/deadlock.RWMutex/' + find . -name "*.go" | grep -v "vendor/" | xargs -n 1 sed -i.bak 's/sync.Mutex/deadlock.Mutex/' + find . -name "*.go" | grep -v "vendor/" | xargs -n 1 goimports -w + make test + make cleanup_after_test_with_deadlock + +# cleanes up after you ran test_with_deadlock +cleanup_after_test_with_deadlock: + find . -name "*.go" | grep -v "vendor/" | xargs -n 1 sed -i.bak 's/deadlock.RWMutex/sync.RWMutex/' + find . -name "*.go" | grep -v "vendor/" | xargs -n 1 sed -i.bak 's/deadlock.Mutex/sync.Mutex/' + find . -name "*.go" | grep -v "vendor/" | xargs -n 1 goimports -w ######################################## ### Formatting, linting, and vetting @@ -330,4 +343,4 @@ build-slate: # To avoid unintended conflicts with file names, always add to .PHONY # unless there is a reason not to. # https://www.gnu.org/software/make/manual/html_node/Phony-Targets.html -.PHONY: check build build_race build_abci dist install install_abci check_dep check_tools get_tools get_dev_tools update_tools get_vendor_deps draw_deps get_protoc protoc_abci protoc_libs gen_certs clean_certs grpc_dbserver test_cover test_apps test_persistence test_p2p test test_race test_integrations test_release test100 vagrant_test fmt rpc-docs build-linux localnet-start localnet-stop build-docker build-docker-localnode sentry-start sentry-config sentry-stop build-slate protoc_grpc protoc_all build_c install_c +.PHONY: check build build_race build_abci dist install install_abci check_dep check_tools get_tools get_dev_tools update_tools get_vendor_deps draw_deps get_protoc protoc_abci protoc_libs gen_certs clean_certs grpc_dbserver test_cover test_apps test_persistence test_p2p test test_race test_integrations test_release test100 vagrant_test fmt rpc-docs build-linux localnet-start localnet-stop build-docker build-docker-localnode sentry-start sentry-config sentry-stop build-slate protoc_grpc protoc_all build_c install_c test_with_deadlock cleanup_after_test_with_deadlock diff --git a/scripts/get_tools.sh b/scripts/get_tools.sh index 955ec943..47077c10 100755 --- a/scripts/get_tools.sh +++ b/scripts/get_tools.sh @@ -51,3 +51,8 @@ installFromGithub golang/dep 22125cfaa6ddc71e145b1535d4b7ee9744fefff2 cmd/dep installFromGithub alecthomas/gometalinter 17a7ffa42374937bfecabfb8d2efbd4db0c26741 installFromGithub gogo/protobuf 61dbc136cf5d2f08d68a011382652244990a53a9 protoc-gen-gogo installFromGithub square/certstrap e27060a3643e814151e65b9807b6b06d169580a7 + +## make test_with_deadlock +installFromGithub petermattis/goid b0b1615b78e5ee59739545bb38426383b2cda4c9 +installFromGithub sasha-s/go-deadlock d68e2bc52ae3291765881b9056f2c1527f245f1e +go get golang.org/x/tools/cmd/goimports From a335caaedb5e0e700b7397864d0423c9158b7359 Mon Sep 17 00:00:00 2001 From: Thane Thomson Date: Mon, 28 Jan 2019 14:13:17 +0200 Subject: [PATCH 238/267] alias amino imports (#3219) As per conversation here: https://github.com/tendermint/tendermint/pull/3218#discussion_r251364041 This is the result of running the following code on the repo: ```bash find . -name '*.go' | grep -v 'vendor/' | xargs -n 1 goimports -w ``` --- benchmarks/codec_test.go | 2 +- blockchain/wire.go | 2 +- cmd/tendermint/commands/wire.go | 2 +- consensus/reactor.go | 8 ++++---- consensus/reactor_test.go | 2 +- consensus/replay.go | 1 + consensus/state.go | 2 +- consensus/types/round_state_test.go | 2 +- consensus/wal_test.go | 1 + consensus/wire.go | 2 +- crypto/merkle/proof_test.go | 2 +- crypto/merkle/wire.go | 2 +- evidence/wire.go | 2 +- mempool/wire.go | 2 +- node/node.go | 2 +- p2p/conn/wire.go | 2 +- p2p/pex/wire.go | 2 +- p2p/wire.go | 2 +- privval/remote_signer.go | 2 +- privval/wire.go | 2 +- rpc/client/rpc_test.go | 2 +- rpc/core/types/wire.go | 2 +- rpc/grpc/grpc_test.go | 4 ++-- rpc/lib/client/args_test.go | 3 +-- rpc/lib/client/http_client.go | 2 +- rpc/lib/client/ws_client.go | 2 +- rpc/lib/types/types_test.go | 2 +- scripts/json2wal/main.go | 2 +- scripts/wal2json/main.go | 2 +- state/txindex/kv/wire.go | 2 +- state/wire.go | 2 +- tools/tm-monitor/mock/eventmeter.go | 2 +- tools/tm-monitor/monitor/monitor.go | 2 +- tools/tm-monitor/monitor/monitor_test.go | 2 +- tools/tm-monitor/monitor/node.go | 12 ++++++------ types/protobuf_test.go | 3 +-- types/tx.go | 2 +- types/wire.go | 2 +- 38 files changed, 47 insertions(+), 47 deletions(-) diff --git a/benchmarks/codec_test.go b/benchmarks/codec_test.go index 3e027028..eff5c734 100644 --- a/benchmarks/codec_test.go +++ b/benchmarks/codec_test.go @@ -4,7 +4,7 @@ import ( "testing" "time" - "github.com/tendermint/go-amino" + amino "github.com/tendermint/go-amino" proto "github.com/tendermint/tendermint/benchmarks/proto" "github.com/tendermint/tendermint/crypto/ed25519" diff --git a/blockchain/wire.go b/blockchain/wire.go index 91156fa8..487fbe2b 100644 --- a/blockchain/wire.go +++ b/blockchain/wire.go @@ -1,7 +1,7 @@ package blockchain import ( - "github.com/tendermint/go-amino" + amino "github.com/tendermint/go-amino" "github.com/tendermint/tendermint/types" ) diff --git a/cmd/tendermint/commands/wire.go b/cmd/tendermint/commands/wire.go index 0f0b536d..322f92b3 100644 --- a/cmd/tendermint/commands/wire.go +++ b/cmd/tendermint/commands/wire.go @@ -1,7 +1,7 @@ package commands import ( - "github.com/tendermint/go-amino" + amino "github.com/tendermint/go-amino" cryptoAmino "github.com/tendermint/tendermint/crypto/encoding/amino" ) diff --git a/consensus/reactor.go b/consensus/reactor.go index 1f508319..b92ae1f7 100644 --- a/consensus/reactor.go +++ b/consensus/reactor.go @@ -8,7 +8,7 @@ import ( "github.com/pkg/errors" - "github.com/tendermint/go-amino" + amino "github.com/tendermint/go-amino" cstypes "github.com/tendermint/tendermint/consensus/types" cmn "github.com/tendermint/tendermint/libs/common" tmevents "github.com/tendermint/tendermint/libs/events" @@ -438,9 +438,9 @@ func (conR *ConsensusReactor) broadcastHasVoteMessage(vote *types.Vote) { func makeRoundStepMessage(rs *cstypes.RoundState) (nrsMsg *NewRoundStepMessage) { nrsMsg = &NewRoundStepMessage{ - Height: rs.Height, - Round: rs.Round, - Step: rs.Step, + Height: rs.Height, + Round: rs.Round, + Step: rs.Step, SecondsSinceStartTime: int(time.Since(rs.StartTime).Seconds()), LastCommitRound: rs.LastCommit.Round(), } diff --git a/consensus/reactor_test.go b/consensus/reactor_test.go index 4772108b..28e245ae 100644 --- a/consensus/reactor_test.go +++ b/consensus/reactor_test.go @@ -14,7 +14,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/tendermint/tendermint/abci/client" + abcicli "github.com/tendermint/tendermint/abci/client" "github.com/tendermint/tendermint/abci/example/kvstore" abci "github.com/tendermint/tendermint/abci/types" bc "github.com/tendermint/tendermint/blockchain" diff --git a/consensus/replay.go b/consensus/replay.go index 96e23b7b..3ac63657 100644 --- a/consensus/replay.go +++ b/consensus/replay.go @@ -6,6 +6,7 @@ import ( "hash/crc32" "io" "reflect" + //"strconv" //"strings" "time" diff --git a/consensus/state.go b/consensus/state.go index c69dfb87..158e1605 100644 --- a/consensus/state.go +++ b/consensus/state.go @@ -94,7 +94,7 @@ type ConsensusState struct { // internal state mtx sync.RWMutex cstypes.RoundState - state sm.State // State until height-1. + state sm.State // State until height-1. // state changes may be triggered by: msgs from peers, // msgs from ourself, or by timeouts diff --git a/consensus/types/round_state_test.go b/consensus/types/round_state_test.go index 6a1c4533..c2bc9f7c 100644 --- a/consensus/types/round_state_test.go +++ b/consensus/types/round_state_test.go @@ -3,7 +3,7 @@ package types import ( "testing" - "github.com/tendermint/go-amino" + amino "github.com/tendermint/go-amino" "github.com/tendermint/tendermint/crypto/ed25519" cmn "github.com/tendermint/tendermint/libs/common" "github.com/tendermint/tendermint/types" diff --git a/consensus/wal_test.go b/consensus/wal_test.go index c056f201..b2711fb4 100644 --- a/consensus/wal_test.go +++ b/consensus/wal_test.go @@ -7,6 +7,7 @@ import ( "io/ioutil" "os" "path/filepath" + // "sync" "testing" "time" diff --git a/consensus/wire.go b/consensus/wire.go index 567e6095..ecd092a1 100644 --- a/consensus/wire.go +++ b/consensus/wire.go @@ -1,7 +1,7 @@ package consensus import ( - "github.com/tendermint/go-amino" + amino "github.com/tendermint/go-amino" "github.com/tendermint/tendermint/types" ) diff --git a/crypto/merkle/proof_test.go b/crypto/merkle/proof_test.go index 320b9188..2a0bdccf 100644 --- a/crypto/merkle/proof_test.go +++ b/crypto/merkle/proof_test.go @@ -4,7 +4,7 @@ import ( "testing" "github.com/stretchr/testify/assert" - "github.com/tendermint/go-amino" + amino "github.com/tendermint/go-amino" cmn "github.com/tendermint/tendermint/libs/common" ) diff --git a/crypto/merkle/wire.go b/crypto/merkle/wire.go index c20ec9aa..2b6ee350 100644 --- a/crypto/merkle/wire.go +++ b/crypto/merkle/wire.go @@ -1,7 +1,7 @@ package merkle import ( - "github.com/tendermint/go-amino" + amino "github.com/tendermint/go-amino" ) var cdc *amino.Codec diff --git a/evidence/wire.go b/evidence/wire.go index 86655953..6752bc3e 100644 --- a/evidence/wire.go +++ b/evidence/wire.go @@ -1,7 +1,7 @@ package evidence import ( - "github.com/tendermint/go-amino" + amino "github.com/tendermint/go-amino" cryptoAmino "github.com/tendermint/tendermint/crypto/encoding/amino" "github.com/tendermint/tendermint/types" ) diff --git a/mempool/wire.go b/mempool/wire.go index ed089726..9224af41 100644 --- a/mempool/wire.go +++ b/mempool/wire.go @@ -1,7 +1,7 @@ package mempool import ( - "github.com/tendermint/go-amino" + amino "github.com/tendermint/go-amino" ) var cdc = amino.NewCodec() diff --git a/node/node.go b/node/node.go index b7ca6517..1b731981 100644 --- a/node/node.go +++ b/node/node.go @@ -34,7 +34,7 @@ import ( rpccore "github.com/tendermint/tendermint/rpc/core" ctypes "github.com/tendermint/tendermint/rpc/core/types" grpccore "github.com/tendermint/tendermint/rpc/grpc" - "github.com/tendermint/tendermint/rpc/lib/server" + rpcserver "github.com/tendermint/tendermint/rpc/lib/server" sm "github.com/tendermint/tendermint/state" "github.com/tendermint/tendermint/state/txindex" "github.com/tendermint/tendermint/state/txindex/kv" diff --git a/p2p/conn/wire.go b/p2p/conn/wire.go index 4bd778c7..5231a6ca 100644 --- a/p2p/conn/wire.go +++ b/p2p/conn/wire.go @@ -1,7 +1,7 @@ package conn import ( - "github.com/tendermint/go-amino" + amino "github.com/tendermint/go-amino" cryptoAmino "github.com/tendermint/tendermint/crypto/encoding/amino" ) diff --git a/p2p/pex/wire.go b/p2p/pex/wire.go index 57fc9385..c88b1941 100644 --- a/p2p/pex/wire.go +++ b/p2p/pex/wire.go @@ -1,7 +1,7 @@ package pex import ( - "github.com/tendermint/go-amino" + amino "github.com/tendermint/go-amino" ) var cdc *amino.Codec = amino.NewCodec() diff --git a/p2p/wire.go b/p2p/wire.go index 40176e3a..191e3c52 100644 --- a/p2p/wire.go +++ b/p2p/wire.go @@ -1,7 +1,7 @@ package p2p import ( - "github.com/tendermint/go-amino" + amino "github.com/tendermint/go-amino" cryptoAmino "github.com/tendermint/tendermint/crypto/encoding/amino" ) diff --git a/privval/remote_signer.go b/privval/remote_signer.go index 1371e333..a5b8cac6 100644 --- a/privval/remote_signer.go +++ b/privval/remote_signer.go @@ -7,7 +7,7 @@ import ( "github.com/pkg/errors" - "github.com/tendermint/go-amino" + amino "github.com/tendermint/go-amino" "github.com/tendermint/tendermint/crypto" cmn "github.com/tendermint/tendermint/libs/common" "github.com/tendermint/tendermint/types" diff --git a/privval/wire.go b/privval/wire.go index 2e11677e..637039b7 100644 --- a/privval/wire.go +++ b/privval/wire.go @@ -1,7 +1,7 @@ package privval import ( - "github.com/tendermint/go-amino" + amino "github.com/tendermint/go-amino" cryptoAmino "github.com/tendermint/tendermint/crypto/encoding/amino" ) diff --git a/rpc/client/rpc_test.go b/rpc/client/rpc_test.go index dac7ec12..8ae88f43 100644 --- a/rpc/client/rpc_test.go +++ b/rpc/client/rpc_test.go @@ -12,7 +12,7 @@ import ( abci "github.com/tendermint/tendermint/abci/types" "github.com/tendermint/tendermint/rpc/client" - "github.com/tendermint/tendermint/rpc/test" + rpctest "github.com/tendermint/tendermint/rpc/test" "github.com/tendermint/tendermint/types" ) diff --git a/rpc/core/types/wire.go b/rpc/core/types/wire.go index ef1fa800..2180b5a5 100644 --- a/rpc/core/types/wire.go +++ b/rpc/core/types/wire.go @@ -1,7 +1,7 @@ package core_types import ( - "github.com/tendermint/go-amino" + amino "github.com/tendermint/go-amino" "github.com/tendermint/tendermint/types" ) diff --git a/rpc/grpc/grpc_test.go b/rpc/grpc/grpc_test.go index eda3896f..ff05c835 100644 --- a/rpc/grpc/grpc_test.go +++ b/rpc/grpc/grpc_test.go @@ -8,8 +8,8 @@ import ( "github.com/stretchr/testify/require" "github.com/tendermint/tendermint/abci/example/kvstore" - "github.com/tendermint/tendermint/rpc/grpc" - "github.com/tendermint/tendermint/rpc/test" + core_grpc "github.com/tendermint/tendermint/rpc/grpc" + rpctest "github.com/tendermint/tendermint/rpc/test" ) func TestMain(m *testing.M) { diff --git a/rpc/lib/client/args_test.go b/rpc/lib/client/args_test.go index cb7a56bd..e3dd09e8 100644 --- a/rpc/lib/client/args_test.go +++ b/rpc/lib/client/args_test.go @@ -5,8 +5,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - - "github.com/tendermint/go-amino" + amino "github.com/tendermint/go-amino" ) type Tx []byte diff --git a/rpc/lib/client/http_client.go b/rpc/lib/client/http_client.go index 21be5fe0..97b8dfe7 100644 --- a/rpc/lib/client/http_client.go +++ b/rpc/lib/client/http_client.go @@ -12,7 +12,7 @@ import ( "strings" "github.com/pkg/errors" - "github.com/tendermint/go-amino" + amino "github.com/tendermint/go-amino" types "github.com/tendermint/tendermint/rpc/lib/types" ) diff --git a/rpc/lib/client/ws_client.go b/rpc/lib/client/ws_client.go index b183118d..e3b55956 100644 --- a/rpc/lib/client/ws_client.go +++ b/rpc/lib/client/ws_client.go @@ -13,7 +13,7 @@ import ( "github.com/pkg/errors" metrics "github.com/rcrowley/go-metrics" - "github.com/tendermint/go-amino" + amino "github.com/tendermint/go-amino" cmn "github.com/tendermint/tendermint/libs/common" types "github.com/tendermint/tendermint/rpc/lib/types" ) diff --git a/rpc/lib/types/types_test.go b/rpc/lib/types/types_test.go index 3e885132..a5b2da9c 100644 --- a/rpc/lib/types/types_test.go +++ b/rpc/lib/types/types_test.go @@ -8,7 +8,7 @@ import ( "github.com/pkg/errors" "github.com/stretchr/testify/assert" - "github.com/tendermint/go-amino" + amino "github.com/tendermint/go-amino" ) type SampleResult struct { diff --git a/scripts/json2wal/main.go b/scripts/json2wal/main.go index 9611b9b5..dd3e29d0 100644 --- a/scripts/json2wal/main.go +++ b/scripts/json2wal/main.go @@ -14,7 +14,7 @@ import ( "os" "strings" - "github.com/tendermint/go-amino" + amino "github.com/tendermint/go-amino" cs "github.com/tendermint/tendermint/consensus" "github.com/tendermint/tendermint/types" ) diff --git a/scripts/wal2json/main.go b/scripts/wal2json/main.go index cf8ae86c..ee90ecaa 100644 --- a/scripts/wal2json/main.go +++ b/scripts/wal2json/main.go @@ -12,7 +12,7 @@ import ( "io" "os" - "github.com/tendermint/go-amino" + amino "github.com/tendermint/go-amino" cs "github.com/tendermint/tendermint/consensus" "github.com/tendermint/tendermint/types" ) diff --git a/state/txindex/kv/wire.go b/state/txindex/kv/wire.go index ccca7525..de168b22 100644 --- a/state/txindex/kv/wire.go +++ b/state/txindex/kv/wire.go @@ -1,7 +1,7 @@ package kv import ( - "github.com/tendermint/go-amino" + amino "github.com/tendermint/go-amino" ) var cdc = amino.NewCodec() diff --git a/state/wire.go b/state/wire.go index eeb156d6..f7a61129 100644 --- a/state/wire.go +++ b/state/wire.go @@ -1,7 +1,7 @@ package state import ( - "github.com/tendermint/go-amino" + amino "github.com/tendermint/go-amino" cryptoAmino "github.com/tendermint/tendermint/crypto/encoding/amino" ) diff --git a/tools/tm-monitor/mock/eventmeter.go b/tools/tm-monitor/mock/eventmeter.go index 27129758..7bbedc7f 100644 --- a/tools/tm-monitor/mock/eventmeter.go +++ b/tools/tm-monitor/mock/eventmeter.go @@ -4,7 +4,7 @@ import ( stdlog "log" "reflect" - "github.com/tendermint/go-amino" + amino "github.com/tendermint/go-amino" "github.com/tendermint/tendermint/libs/log" em "github.com/tendermint/tendermint/tools/tm-monitor/eventmeter" ) diff --git a/tools/tm-monitor/monitor/monitor.go b/tools/tm-monitor/monitor/monitor.go index 764f281f..86022c31 100644 --- a/tools/tm-monitor/monitor/monitor.go +++ b/tools/tm-monitor/monitor/monitor.go @@ -46,7 +46,7 @@ func NewMonitor(options ...func(*Monitor)) *Monitor { nodeQuit: make(map[string]chan struct{}), recalculateNetworkUptimeEvery: 10 * time.Second, numValidatorsUpdateInterval: 5 * time.Second, - logger: log.NewNopLogger(), + logger: log.NewNopLogger(), } for _, option := range options { diff --git a/tools/tm-monitor/monitor/monitor_test.go b/tools/tm-monitor/monitor/monitor_test.go index 9694e577..324f43f7 100644 --- a/tools/tm-monitor/monitor/monitor_test.go +++ b/tools/tm-monitor/monitor/monitor_test.go @@ -7,7 +7,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/tendermint/go-amino" + amino "github.com/tendermint/go-amino" "github.com/tendermint/tendermint/crypto/ed25519" ctypes "github.com/tendermint/tendermint/rpc/core/types" mock "github.com/tendermint/tendermint/tools/tm-monitor/mock" diff --git a/tools/tm-monitor/monitor/node.go b/tools/tm-monitor/monitor/node.go index 1dc113a6..6f705145 100644 --- a/tools/tm-monitor/monitor/node.go +++ b/tools/tm-monitor/monitor/node.go @@ -55,13 +55,13 @@ func NewNode(rpcAddr string, options ...func(*Node)) *Node { func NewNodeWithEventMeterAndRpcClient(rpcAddr string, em eventMeter, rpcClient rpc_client.HTTPClient, options ...func(*Node)) *Node { n := &Node{ - rpcAddr: rpcAddr, - em: em, - rpcClient: rpcClient, - Name: rpcAddr, - quit: make(chan struct{}), + rpcAddr: rpcAddr, + em: em, + rpcClient: rpcClient, + Name: rpcAddr, + quit: make(chan struct{}), checkIsValidatorInterval: 5 * time.Second, - logger: log.NewNopLogger(), + logger: log.NewNopLogger(), } for _, option := range options { diff --git a/types/protobuf_test.go b/types/protobuf_test.go index 18acf57a..40859d9e 100644 --- a/types/protobuf_test.go +++ b/types/protobuf_test.go @@ -7,8 +7,7 @@ import ( "github.com/golang/protobuf/proto" "github.com/stretchr/testify/assert" - "github.com/tendermint/go-amino" - + amino "github.com/tendermint/go-amino" abci "github.com/tendermint/tendermint/abci/types" "github.com/tendermint/tendermint/crypto" "github.com/tendermint/tendermint/crypto/ed25519" diff --git a/types/tx.go b/types/tx.go index 87d387a0..0c6845a7 100644 --- a/types/tx.go +++ b/types/tx.go @@ -5,7 +5,7 @@ import ( "errors" "fmt" - "github.com/tendermint/go-amino" + amino "github.com/tendermint/go-amino" abci "github.com/tendermint/tendermint/abci/types" "github.com/tendermint/tendermint/crypto/merkle" diff --git a/types/wire.go b/types/wire.go index 92304801..81a7bf76 100644 --- a/types/wire.go +++ b/types/wire.go @@ -2,7 +2,7 @@ package types import ( amino "github.com/tendermint/go-amino" - "github.com/tendermint/tendermint/crypto/encoding/amino" + cryptoAmino "github.com/tendermint/tendermint/crypto/encoding/amino" ) var cdc = amino.NewCodec() From 9a0bfafef6eb2f10a07006fde7780ed191ebdf19 Mon Sep 17 00:00:00 2001 From: Thane Thomson Date: Mon, 28 Jan 2019 15:41:39 +0200 Subject: [PATCH 239/267] docs: fix links (#3220) Because there's nothing worse than having to copy/paste a link from a web page to navigate to it :grin: --- docs/tendermint-core/rpc.md | 2 +- docs/tools/benchmarking.md | 2 +- docs/tools/monitoring.md | 2 +- tools/tm-bench/README.md | 2 +- tools/tm-monitor/README.md | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/tendermint-core/rpc.md b/docs/tendermint-core/rpc.md index 7ae59f0d..4ea5ab0d 100644 --- a/docs/tendermint-core/rpc.md +++ b/docs/tendermint-core/rpc.md @@ -2,6 +2,6 @@ The RPC documentation is hosted here: -- https://tendermint.com/rpc/ +- [https://tendermint.com/rpc/](https://tendermint.com/rpc/) To update the documentation, edit the relevant `godoc` comments in the [rpc/core directory](https://github.com/tendermint/tendermint/tree/develop/rpc/core). diff --git a/docs/tools/benchmarking.md b/docs/tools/benchmarking.md index e17c2856..67a472e4 100644 --- a/docs/tools/benchmarking.md +++ b/docs/tools/benchmarking.md @@ -2,7 +2,7 @@ Tendermint blockchain benchmarking tool: -- https://github.com/tendermint/tools/tree/master/tm-bench +- [https://github.com/tendermint/tendermint/tree/master/tools/tm-bench](https://github.com/tendermint/tendermint/tree/master/tools/tm-bench) For example, the following: diff --git a/docs/tools/monitoring.md b/docs/tools/monitoring.md index c0fa94c0..fa3901dd 100644 --- a/docs/tools/monitoring.md +++ b/docs/tools/monitoring.md @@ -3,7 +3,7 @@ Tendermint blockchain monitoring tool; watches over one or more nodes, collecting and providing various statistics to the user: -- https://github.com/tendermint/tendermint/tree/master/tools/tm-monitor +- [https://github.com/tendermint/tendermint/tree/master/tools/tm-monitor](https://github.com/tendermint/tendermint/tree/master/tools/tm-monitor) ## Quick Start diff --git a/tools/tm-bench/README.md b/tools/tm-bench/README.md index 9159a754..b4e8cec5 100644 --- a/tools/tm-bench/README.md +++ b/tools/tm-bench/README.md @@ -2,7 +2,7 @@ Tendermint blockchain benchmarking tool: -- https://github.com/tendermint/tools/tree/master/tm-bench +- [https://github.com/tendermint/tendermint/tree/master/tools/tm-bench](https://github.com/tendermint/tendermint/tree/master/tools/tm-bench) For example, the following: `tm-bench -T 30 -r 10000 localhost:26657` diff --git a/tools/tm-monitor/README.md b/tools/tm-monitor/README.md index cf421684..374a56b0 100644 --- a/tools/tm-monitor/README.md +++ b/tools/tm-monitor/README.md @@ -3,7 +3,7 @@ Tendermint blockchain monitoring tool; watches over one or more nodes, collecting and providing various statistics to the user: -- https://github.com/tendermint/tools/tree/master/tm-monitor +- [https://github.com/tendermint/tendermint/tree/master/tools/tm-monitor](https://github.com/tendermint/tendermint/tree/master/tools/tm-monitor) ## Quick Start From e1edd2aa6a860d505313b73cba4cb74fa4de10fc Mon Sep 17 00:00:00 2001 From: Zach Date: Mon, 28 Jan 2019 14:41:37 -0500 Subject: [PATCH 240/267] hardcode rpc link (#3223) --- docs/.vuepress/config.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/.vuepress/config.js b/docs/.vuepress/config.js index 5ecc97cf..3e55a240 100644 --- a/docs/.vuepress/config.js +++ b/docs/.vuepress/config.js @@ -21,7 +21,7 @@ module.exports = { }, nav: [ { text: "Back to Tendermint", link: "https://tendermint.com" }, - { text: "RPC", link: "../rpc/" } + { text: "RPC", link: "https://tendermint.com/rpc/" } ], sidebar: [ { From 0b3a87a3232d59ef51ee810ca8f3645504ab6d2f Mon Sep 17 00:00:00 2001 From: rickyyangz <38900912+rickyyangz@users.noreply.github.com> Date: Tue, 29 Jan 2019 14:12:07 +0800 Subject: [PATCH 241/267] mempool: correct args order in the log msg (#3221) Before: Unexpected tx response from proxy during recheck\n Expected: {r.CheckTx.Data}, got: {memTx.tx} After: Unexpected tx response from proxy during recheck\n Expected: {memTx.tx}, got: {tx} Closes #3214 --- mempool/mempool.go | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/mempool/mempool.go b/mempool/mempool.go index 9069dab6..8550f2f8 100644 --- a/mempool/mempool.go +++ b/mempool/mempool.go @@ -408,14 +408,11 @@ func (mem *Mempool) resCbRecheck(req *abci.Request, res *abci.Response) { case *abci.Response_CheckTx: tx := req.GetCheckTx().Tx memTx := mem.recheckCursor.Value.(*mempoolTx) - if !bytes.Equal(req.GetCheckTx().Tx, memTx.tx) { - cmn.PanicSanity( - fmt.Sprintf( - "Unexpected tx response from proxy during recheck\nExpected %X, got %X", - r.CheckTx.Data, - memTx.tx, - ), - ) + if !bytes.Equal(tx, memTx.tx) { + panic(fmt.Sprintf( + "Unexpected tx response from proxy during recheck\nExpected %X, got %X", + memTx.tx, + tx)) } var postCheckErr error if mem.postCheck != nil { From 6dd817cbbcd9b0b2e2672d8871bce1c5ff385856 Mon Sep 17 00:00:00 2001 From: Ismail Khoffi Date: Tue, 29 Jan 2019 09:44:59 +0100 Subject: [PATCH 242/267] secret connection: check for low order points (#3040) > Implement a check for the blacklisted low order points, ala the X25519 has_small_order() function in libsodium (#3010 (comment)) resolves first half of #3010 --- p2p/conn/secret_connection.go | 52 ++++++++++++++++++++++++++++++ p2p/conn/secret_connection_test.go | 26 +++++++++++++++ 2 files changed, 78 insertions(+) diff --git a/p2p/conn/secret_connection.go b/p2p/conn/secret_connection.go index aa019aa3..d2ba6fb5 100644 --- a/p2p/conn/secret_connection.go +++ b/p2p/conn/secret_connection.go @@ -4,6 +4,7 @@ import ( "bytes" crand "crypto/rand" "crypto/sha256" + "crypto/subtle" "encoding/binary" "errors" "io" @@ -28,6 +29,8 @@ const aeadSizeOverhead = 16 // overhead of poly 1305 authentication tag const aeadKeySize = chacha20poly1305.KeySize const aeadNonceSize = chacha20poly1305.NonceSize +var ErrSmallOrderRemotePubKey = errors.New("detected low order point from remote peer") + // SecretConnection implements net.Conn. // It is an implementation of the STS protocol. // See https://github.com/tendermint/tendermint/blob/0.1/docs/sts-final.pdf for @@ -251,6 +254,9 @@ func shareEphPubKey(conn io.ReadWriteCloser, locEphPub *[32]byte) (remEphPub *[3 if err2 != nil { return nil, err2, true // abort } + if hasSmallOrder(_remEphPub) { + return nil, ErrSmallOrderRemotePubKey, true + } return _remEphPub, nil, false }, ) @@ -266,6 +272,52 @@ func shareEphPubKey(conn io.ReadWriteCloser, locEphPub *[32]byte) (remEphPub *[3 return &_remEphPub, nil } +// use the samne blacklist as lib sodium (see https://eprint.iacr.org/2017/806.pdf for reference): +// https://github.com/jedisct1/libsodium/blob/536ed00d2c5e0c65ac01e29141d69a30455f2038/src/libsodium/crypto_scalarmult/curve25519/ref10/x25519_ref10.c#L11-L17 +var blacklist = [][32]byte{ + // 0 (order 4) + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + // 1 (order 1) + {0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + // 325606250916557431795983626356110631294008115727848805560023387167927233504 + // (order 8) + {0xe0, 0xeb, 0x7a, 0x7c, 0x3b, 0x41, 0xb8, 0xae, 0x16, 0x56, 0xe3, + 0xfa, 0xf1, 0x9f, 0xc4, 0x6a, 0xda, 0x09, 0x8d, 0xeb, 0x9c, 0x32, + 0xb1, 0xfd, 0x86, 0x62, 0x05, 0x16, 0x5f, 0x49, 0xb8, 0x00}, + // 39382357235489614581723060781553021112529911719440698176882885853963445705823 + // (order 8) + {0x5f, 0x9c, 0x95, 0xbc, 0xa3, 0x50, 0x8c, 0x24, 0xb1, 0xd0, 0xb1, + 0x55, 0x9c, 0x83, 0xef, 0x5b, 0x04, 0x44, 0x5c, 0xc4, 0x58, 0x1c, + 0x8e, 0x86, 0xd8, 0x22, 0x4e, 0xdd, 0xd0, 0x9f, 0x11, 0x57}, + // p-1 (order 2) + {0xec, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x7f}, + // p (=0, order 4) + {0xed, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x7f}, + // p+1 (=1, order 1) + {0xee, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x7f}, +} + +func hasSmallOrder(pubKey [32]byte) bool { + isSmallOrderPoint := false + for _, bl := range blacklist { + if subtle.ConstantTimeCompare(pubKey[:], bl[:]) == 1 { + isSmallOrderPoint = true + break + } + } + return isSmallOrderPoint +} + func deriveSecretAndChallenge(dhSecret *[32]byte, locIsLeast bool) (recvSecret, sendSecret *[aeadKeySize]byte, challenge *[32]byte) { hash := sha256.New hkdf := hkdf.New(hash, dhSecret[:], nil, []byte("TENDERMINT_SECRET_CONNECTION_KEY_AND_CHALLENGE_GEN")) diff --git a/p2p/conn/secret_connection_test.go b/p2p/conn/secret_connection_test.go index 131ab922..69d9c09f 100644 --- a/p2p/conn/secret_connection_test.go +++ b/p2p/conn/secret_connection_test.go @@ -100,6 +100,32 @@ func TestSecretConnectionHandshake(t *testing.T) { } } +func TestShareLowOrderPubkey(t *testing.T) { + var fooConn, barConn = makeKVStoreConnPair() + locEphPub, _ := genEphKeys() + + // all blacklisted low order points: + for _, remLowOrderPubKey := range blacklist { + _, _ = cmn.Parallel( + func(_ int) (val interface{}, err error, abort bool) { + _, err = shareEphPubKey(fooConn, locEphPub) + + require.Error(t, err) + require.Equal(t, err, ErrSmallOrderRemotePubKey) + + return nil, nil, false + }, + func(_ int) (val interface{}, err error, abort bool) { + readRemKey, err := shareEphPubKey(barConn, &remLowOrderPubKey) + + require.NoError(t, err) + require.Equal(t, locEphPub, readRemKey) + + return nil, nil, false + }) + } +} + func TestConcurrentWrite(t *testing.T) { fooSecConn, barSecConn := makeSecretConnPair(t) fooWriteText := cmn.RandStr(dataMaxSize) From 8985a1fa63fd9d64840d187243e74b7218a7167e Mon Sep 17 00:00:00 2001 From: Anton Kaliaev Date: Tue, 29 Jan 2019 13:16:43 +0400 Subject: [PATCH 243/267] pubsub: fixes after Ethan's review (#3212) in https://github.com/tendermint/tendermint/pull/3209 --- libs/pubsub/pubsub.go | 112 ++++++++++++++++++++++++------------- libs/pubsub/pubsub_test.go | 19 +++++++ 2 files changed, 91 insertions(+), 40 deletions(-) diff --git a/libs/pubsub/pubsub.go b/libs/pubsub/pubsub.go index cb41f8b3..a39c8c73 100644 --- a/libs/pubsub/pubsub.go +++ b/libs/pubsub/pubsub.go @@ -101,7 +101,7 @@ type Server struct { cmdsCap int mtx sync.RWMutex - subscriptions map[string]map[string]Query // subscriber -> query (string) -> Query + subscriptions map[string]map[string]struct{} // subscriber -> query (string) -> empty struct } // Option sets a parameter for the server. @@ -143,7 +143,7 @@ func (ts tagMap) Len() int { // provided, the resulting server's queue is unbuffered. func NewServer(options ...Option) *Server { s := &Server{ - subscriptions: make(map[string]map[string]Query), + subscriptions: make(map[string]map[string]struct{}), } s.BaseService = *cmn.NewBaseService(nil, "PubSub", s) @@ -193,11 +193,9 @@ func (s *Server) Subscribe(ctx context.Context, clientID string, query Query, ou case s.cmds <- cmd{op: sub, clientID: clientID, query: query, ch: out}: s.mtx.Lock() if _, ok = s.subscriptions[clientID]; !ok { - s.subscriptions[clientID] = make(map[string]Query) + s.subscriptions[clientID] = make(map[string]struct{}) } - // preserve original query - // see Unsubscribe - s.subscriptions[clientID][query.String()] = query + s.subscriptions[clientID][query.String()] = struct{}{} s.mtx.Unlock() return nil case <-ctx.Done(): @@ -211,24 +209,23 @@ func (s *Server) Subscribe(ctx context.Context, clientID string, query Query, ou // returned to the caller if the context is canceled or if subscription does // not exist. func (s *Server) Unsubscribe(ctx context.Context, clientID string, query Query) error { - var origQuery Query s.mtx.RLock() clientSubscriptions, ok := s.subscriptions[clientID] if ok { - origQuery, ok = clientSubscriptions[query.String()] + _, ok = clientSubscriptions[query.String()] } s.mtx.RUnlock() if !ok { return ErrSubscriptionNotFound } - // original query is used here because we're using pointers as map keys - // ? select { - case s.cmds <- cmd{op: unsub, clientID: clientID, query: origQuery}: + case s.cmds <- cmd{op: unsub, clientID: clientID, query: query}: s.mtx.Lock() - // if its the only query left, should we also delete the client? delete(clientSubscriptions, query.String()) + if len(clientSubscriptions) == 0 { + delete(s.subscriptions, clientID) + } s.mtx.Unlock() return nil case <-ctx.Done(): @@ -288,17 +285,27 @@ func (s *Server) OnStop() { // NOTE: not goroutine safe type state struct { - // query -> client -> ch - queries map[Query]map[string]chan<- interface{} - // client -> query -> struct{} - clients map[string]map[Query]struct{} + // query string -> client -> ch + queryToChanMap map[string]map[string]chan<- interface{} + // client -> query string -> struct{} + clientToQueryMap map[string]map[string]struct{} + // query string -> queryPlusRefCount + queries map[string]*queryPlusRefCount +} + +// queryPlusRefCount holds a pointer to a query and reference counter. When +// refCount is zero, query will be removed. +type queryPlusRefCount struct { + q Query + refCount int } // OnStart implements Service.OnStart by starting the server. func (s *Server) OnStart() error { go s.loop(state{ - queries: make(map[Query]map[string]chan<- interface{}), - clients: make(map[string]map[Query]struct{}), + queryToChanMap: make(map[string]map[string]chan<- interface{}), + clientToQueryMap: make(map[string]map[string]struct{}), + queries: make(map[string]*queryPlusRefCount), }) return nil } @@ -319,7 +326,7 @@ loop: state.removeAll(cmd.clientID) } case shutdown: - for clientID := range state.clients { + for clientID := range state.clientToQueryMap { state.removeAll(clientID) } break loop @@ -332,24 +339,34 @@ loop: } func (state *state) add(clientID string, q Query, ch chan<- interface{}) { + qStr := q.String() // initialize clientToChannelMap per query if needed - if _, ok := state.queries[q]; !ok { - state.queries[q] = make(map[string]chan<- interface{}) + if _, ok := state.queryToChanMap[qStr]; !ok { + state.queryToChanMap[qStr] = make(map[string]chan<- interface{}) } // create subscription - state.queries[q][clientID] = ch + state.queryToChanMap[qStr][clientID] = ch + + // initialize queries if needed + if _, ok := state.queries[qStr]; !ok { + state.queries[qStr] = &queryPlusRefCount{q: q, refCount: 0} + } + // increment reference counter + state.queries[qStr].refCount++ // add client if needed - if _, ok := state.clients[clientID]; !ok { - state.clients[clientID] = make(map[Query]struct{}) + if _, ok := state.clientToQueryMap[clientID]; !ok { + state.clientToQueryMap[clientID] = make(map[string]struct{}) } - state.clients[clientID][q] = struct{}{} + state.clientToQueryMap[clientID][qStr] = struct{}{} } func (state *state) remove(clientID string, q Query) { - clientToChannelMap, ok := state.queries[q] + qStr := q.String() + + clientToChannelMap, ok := state.queryToChanMap[qStr] if !ok { return } @@ -363,43 +380,58 @@ func (state *state) remove(clientID string, q Query) { // remove the query from client map. // if client is not subscribed to anything else, remove it. - delete(state.clients[clientID], q) - if len(state.clients[clientID]) == 0 { - delete(state.clients, clientID) + delete(state.clientToQueryMap[clientID], qStr) + if len(state.clientToQueryMap[clientID]) == 0 { + delete(state.clientToQueryMap, clientID) } // remove the client from query map. // if query has no other clients subscribed, remove it. - delete(state.queries[q], clientID) - if len(state.queries[q]) == 0 { - delete(state.queries, q) + delete(state.queryToChanMap[qStr], clientID) + if len(state.queryToChanMap[qStr]) == 0 { + delete(state.queryToChanMap, qStr) + } + + // decrease ref counter in queries + state.queries[qStr].refCount-- + // remove the query if nobody else is using it + if state.queries[qStr].refCount == 0 { + delete(state.queries, qStr) } } func (state *state) removeAll(clientID string) { - queryMap, ok := state.clients[clientID] + queryMap, ok := state.clientToQueryMap[clientID] if !ok { return } - for q := range queryMap { - ch := state.queries[q][clientID] + for qStr := range queryMap { + ch := state.queryToChanMap[qStr][clientID] close(ch) // remove the client from query map. // if query has no other clients subscribed, remove it. - delete(state.queries[q], clientID) - if len(state.queries[q]) == 0 { - delete(state.queries, q) + delete(state.queryToChanMap[qStr], clientID) + if len(state.queryToChanMap[qStr]) == 0 { + delete(state.queryToChanMap, qStr) + } + + // decrease ref counter in queries + state.queries[qStr].refCount-- + // remove the query if nobody else is using it + if state.queries[qStr].refCount == 0 { + delete(state.queries, qStr) } } // remove the client. - delete(state.clients, clientID) + delete(state.clientToQueryMap, clientID) } func (state *state) send(msg interface{}, tags TagMap) { - for q, clientToChannelMap := range state.queries { + for qStr, clientToChannelMap := range state.queryToChanMap { + q := state.queries[qStr].q if q.Matches(tags) { for _, ch := range clientToChannelMap { ch <- msg diff --git a/libs/pubsub/pubsub_test.go b/libs/pubsub/pubsub_test.go index 5e9931e4..bb660d9e 100644 --- a/libs/pubsub/pubsub_test.go +++ b/libs/pubsub/pubsub_test.go @@ -115,6 +115,25 @@ func TestUnsubscribe(t *testing.T) { assert.False(t, ok) } +func TestClientUnsubscribesTwice(t *testing.T) { + s := pubsub.NewServer() + s.SetLogger(log.TestingLogger()) + s.Start() + defer s.Stop() + + ctx := context.Background() + ch := make(chan interface{}) + err := s.Subscribe(ctx, clientID, query.MustParse("tm.events.type='NewBlock'"), ch) + require.NoError(t, err) + err = s.Unsubscribe(ctx, clientID, query.MustParse("tm.events.type='NewBlock'")) + require.NoError(t, err) + + err = s.Unsubscribe(ctx, clientID, query.MustParse("tm.events.type='NewBlock'")) + assert.Equal(t, pubsub.ErrSubscriptionNotFound, err) + err = s.UnsubscribeAll(ctx, clientID) + assert.Equal(t, pubsub.ErrSubscriptionNotFound, err) +} + func TestResubscribe(t *testing.T) { s := pubsub.NewServer() s.SetLogger(log.TestingLogger()) From d47094550315c094512a242445e0dde24b5a03f5 Mon Sep 17 00:00:00 2001 From: Anton Kaliaev Date: Wed, 30 Jan 2019 12:24:26 +0400 Subject: [PATCH 244/267] update gometalinter to 3.0.0 (#3233) in the attempt to fix https://circleci.com/gh/tendermint/tendermint/43165 also code is simplified by running gofmt -s . remove unused vars enable linters we're currently passing remove deprecated linters --- Makefile | 42 +++++++++++------------ abci/types/messages_test.go | 2 +- cmd/tendermint/commands/root_test.go | 4 --- consensus/wal_test.go | 4 +-- crypto/merkle/rfc6962_test.go | 2 +- evidence/pool_test.go | 2 -- evidence/reactor.go | 2 +- libs/flowrate/io_test.go | 16 ++++----- libs/pubsub/query/query_test.go | 6 ++-- p2p/conn/connection_test.go | 2 +- p2p/transport_test.go | 8 ++--- privval/client_test.go | 4 +-- scripts/get_tools.sh | 4 +-- state/txindex/kv/kv_test.go | 4 +-- tools/tm-monitor/eventmeter/eventmeter.go | 2 +- types/genesis_test.go | 6 ++-- types/part_set.go | 3 -- 17 files changed, 52 insertions(+), 61 deletions(-) diff --git a/Makefile b/Makefile index 853a01c9..8c0928d0 100644 --- a/Makefile +++ b/Makefile @@ -249,31 +249,31 @@ fmt: metalinter: @echo "--> Running linter" @gometalinter $(LINT_FLAGS) --disable-all \ + --enable=vet \ + --enable=vetshadow \ --enable=deadcode \ - --enable=gosimple \ + --enable=varcheck \ + --enable=structcheck \ --enable=misspell \ --enable=safesql \ + --enable=gosec \ + --enable=goimports \ + --enable=gofmt \ ./... - #--enable=gas \ - #--enable=maligned \ - #--enable=dupl \ - #--enable=errcheck \ - #--enable=goconst \ - #--enable=gocyclo \ - #--enable=goimports \ - #--enable=golint \ <== comments on anything exported #--enable=gotype \ - #--enable=ineffassign \ - #--enable=interfacer \ - #--enable=megacheck \ - #--enable=staticcheck \ - #--enable=structcheck \ - #--enable=unconvert \ - #--enable=unparam \ - #--enable=unused \ - #--enable=varcheck \ - #--enable=vet \ - #--enable=vetshadow \ + #--enable=gotypex \ + #--enable=gocyclo \ + #--enable=golint \ + #--enable=maligned \ + #--enable=errcheck \ + #--enable=staticcheck \ + #--enable=dupl \ + #--enable=ineffassign \ + #--enable=interfacer \ + #--enable=unconvert \ + #--enable=goconst \ + #--enable=unparam \ + #--enable=nakedret \ metalinter_all: @echo "--> Running linter (all)" @@ -343,4 +343,4 @@ build-slate: # To avoid unintended conflicts with file names, always add to .PHONY # unless there is a reason not to. # https://www.gnu.org/software/make/manual/html_node/Phony-Targets.html -.PHONY: check build build_race build_abci dist install install_abci check_dep check_tools get_tools get_dev_tools update_tools get_vendor_deps draw_deps get_protoc protoc_abci protoc_libs gen_certs clean_certs grpc_dbserver test_cover test_apps test_persistence test_p2p test test_race test_integrations test_release test100 vagrant_test fmt rpc-docs build-linux localnet-start localnet-stop build-docker build-docker-localnode sentry-start sentry-config sentry-stop build-slate protoc_grpc protoc_all build_c install_c test_with_deadlock cleanup_after_test_with_deadlock +.PHONY: check build build_race build_abci dist install install_abci check_dep check_tools get_tools get_dev_tools update_tools get_vendor_deps draw_deps get_protoc protoc_abci protoc_libs gen_certs clean_certs grpc_dbserver test_cover test_apps test_persistence test_p2p test test_race test_integrations test_release test100 vagrant_test fmt rpc-docs build-linux localnet-start localnet-stop build-docker build-docker-localnode sentry-start sentry-config sentry-stop build-slate protoc_grpc protoc_all build_c install_c test_with_deadlock cleanup_after_test_with_deadlock metalinter metalinter_all diff --git a/abci/types/messages_test.go b/abci/types/messages_test.go index 14bc5718..762111b6 100644 --- a/abci/types/messages_test.go +++ b/abci/types/messages_test.go @@ -83,7 +83,7 @@ func TestWriteReadMessage2(t *testing.T) { Log: phrase, GasWanted: 10, Tags: []cmn.KVPair{ - cmn.KVPair{Key: []byte("abc"), Value: []byte("def")}, + {Key: []byte("abc"), Value: []byte("def")}, }, }, // TODO: add the rest diff --git a/cmd/tendermint/commands/root_test.go b/cmd/tendermint/commands/root_test.go index e8095b38..892a49b7 100644 --- a/cmd/tendermint/commands/root_test.go +++ b/cmd/tendermint/commands/root_test.go @@ -22,10 +22,6 @@ var ( defaultRoot = os.ExpandEnv("$HOME/.some/test/dir") ) -const ( - rootName = "root" -) - // clearConfig clears env vars, the given root dir, and resets viper. func clearConfig(dir string) { if err := os.Unsetenv("TMHOME"); err != nil { diff --git a/consensus/wal_test.go b/consensus/wal_test.go index b2711fb4..c2a7d8bb 100644 --- a/consensus/wal_test.go +++ b/consensus/wal_test.go @@ -68,8 +68,8 @@ func TestWALTruncate(t *testing.T) { func TestWALEncoderDecoder(t *testing.T) { now := tmtime.Now() msgs := []TimedWALMessage{ - TimedWALMessage{Time: now, Msg: EndHeightMessage{0}}, - TimedWALMessage{Time: now, Msg: timeoutInfo{Duration: time.Second, Height: 1, Round: 1, Step: types.RoundStepPropose}}, + {Time: now, Msg: EndHeightMessage{0}}, + {Time: now, Msg: timeoutInfo{Duration: time.Second, Height: 1, Round: 1, Step: types.RoundStepPropose}}, } b := new(bytes.Buffer) diff --git a/crypto/merkle/rfc6962_test.go b/crypto/merkle/rfc6962_test.go index b6413b47..52eab422 100644 --- a/crypto/merkle/rfc6962_test.go +++ b/crypto/merkle/rfc6962_test.go @@ -26,7 +26,7 @@ import ( func TestRFC6962Hasher(t *testing.T) { _, leafHashTrail := trailsFromByteSlices([][]byte{[]byte("L123456")}) leafHash := leafHashTrail.Hash - _, leafHashTrail = trailsFromByteSlices([][]byte{[]byte{}}) + _, leafHashTrail = trailsFromByteSlices([][]byte{{}}) emptyLeafHash := leafHashTrail.Hash for _, tc := range []struct { desc string diff --git a/evidence/pool_test.go b/evidence/pool_test.go index 0640c1da..1f4f1a06 100644 --- a/evidence/pool_test.go +++ b/evidence/pool_test.go @@ -13,8 +13,6 @@ import ( tmtime "github.com/tendermint/tendermint/types/time" ) -var mockState = sm.State{} - func TestMain(m *testing.M) { types.RegisterMockEvidences(cdc) diff --git a/evidence/reactor.go b/evidence/reactor.go index 6bb45e68..bbbab3e9 100644 --- a/evidence/reactor.go +++ b/evidence/reactor.go @@ -48,7 +48,7 @@ func (evR *EvidenceReactor) SetLogger(l log.Logger) { // It returns the list of channels for this reactor. func (evR *EvidenceReactor) GetChannels() []*p2p.ChannelDescriptor { return []*p2p.ChannelDescriptor{ - &p2p.ChannelDescriptor{ + { ID: EvidenceChannel, Priority: 5, }, diff --git a/libs/flowrate/io_test.go b/libs/flowrate/io_test.go index c84029d5..ab2c7121 100644 --- a/libs/flowrate/io_test.go +++ b/libs/flowrate/io_test.go @@ -81,12 +81,12 @@ func TestReader(t *testing.T) { // Active, Start, Duration, Idle, Bytes, Samples, InstRate, CurRate, AvgRate, PeakRate, BytesRem, TimeRem, Progress want := []Status{ - Status{true, start, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, - Status{true, start, _100ms, 0, 10, 1, 100, 100, 100, 100, 0, 0, 0}, - Status{true, start, _200ms, _100ms, 20, 2, 100, 100, 100, 100, 0, 0, 0}, - Status{true, start, _300ms, _200ms, 20, 3, 0, 90, 67, 100, 0, 0, 0}, - Status{false, start, _300ms, 0, 20, 3, 0, 0, 67, 100, 0, 0, 0}, - Status{false, start, _300ms, 0, 20, 3, 0, 0, 67, 100, 0, 0, 0}, + {true, start, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, + {true, start, _100ms, 0, 10, 1, 100, 100, 100, 100, 0, 0, 0}, + {true, start, _200ms, _100ms, 20, 2, 100, 100, 100, 100, 0, 0, 0}, + {true, start, _300ms, _200ms, 20, 3, 0, 90, 67, 100, 0, 0, 0}, + {false, start, _300ms, 0, 20, 3, 0, 0, 67, 100, 0, 0, 0}, + {false, start, _300ms, 0, 20, 3, 0, 0, 67, 100, 0, 0, 0}, } for i, s := range status { if !statusesAreEqual(&s, &want[i]) { @@ -139,8 +139,8 @@ func TestWriter(t *testing.T) { // Active, Start, Duration, Idle, Bytes, Samples, InstRate, CurRate, AvgRate, PeakRate, BytesRem, TimeRem, Progress want := []Status{ - Status{true, start, _400ms, 0, 80, 4, 200, 200, 200, 200, 20, _100ms, 80000}, - Status{true, start, _500ms, _100ms, 100, 5, 200, 200, 200, 200, 0, 0, 100000}, + {true, start, _400ms, 0, 80, 4, 200, 200, 200, 200, 20, _100ms, 80000}, + {true, start, _500ms, _100ms, 100, 5, 200, 200, 200, 200, 0, 0, 100000}, } for i, s := range status { if !statusesAreEqual(&s, &want[i]) { diff --git a/libs/pubsub/query/query_test.go b/libs/pubsub/query/query_test.go index f0d94099..d1810f46 100644 --- a/libs/pubsub/query/query_test.go +++ b/libs/pubsub/query/query_test.go @@ -73,9 +73,9 @@ func TestConditions(t *testing.T) { s string conditions []query.Condition }{ - {s: "tm.events.type='NewBlock'", conditions: []query.Condition{query.Condition{Tag: "tm.events.type", Op: query.OpEqual, Operand: "NewBlock"}}}, - {s: "tx.gas > 7 AND tx.gas < 9", conditions: []query.Condition{query.Condition{Tag: "tx.gas", Op: query.OpGreater, Operand: int64(7)}, query.Condition{Tag: "tx.gas", Op: query.OpLess, Operand: int64(9)}}}, - {s: "tx.time >= TIME 2013-05-03T14:45:00Z", conditions: []query.Condition{query.Condition{Tag: "tx.time", Op: query.OpGreaterEqual, Operand: txTime}}}, + {s: "tm.events.type='NewBlock'", conditions: []query.Condition{{Tag: "tm.events.type", Op: query.OpEqual, Operand: "NewBlock"}}}, + {s: "tx.gas > 7 AND tx.gas < 9", conditions: []query.Condition{{Tag: "tx.gas", Op: query.OpGreater, Operand: int64(7)}, {Tag: "tx.gas", Op: query.OpLess, Operand: int64(9)}}}, + {s: "tx.time >= TIME 2013-05-03T14:45:00Z", conditions: []query.Condition{{Tag: "tx.time", Op: query.OpGreaterEqual, Operand: txTime}}}, } for _, tc := range testCases { diff --git a/p2p/conn/connection_test.go b/p2p/conn/connection_test.go index a757f07a..afad69d1 100644 --- a/p2p/conn/connection_test.go +++ b/p2p/conn/connection_test.go @@ -30,7 +30,7 @@ func createMConnectionWithCallbacks(conn net.Conn, onReceive func(chID byte, msg cfg := DefaultMConnConfig() cfg.PingInterval = 90 * time.Millisecond cfg.PongTimeout = 45 * time.Millisecond - chDescs := []*ChannelDescriptor{&ChannelDescriptor{ID: 0x01, Priority: 1, SendQueueCapacity: 1}} + chDescs := []*ChannelDescriptor{{ID: 0x01, Priority: 1, SendQueueCapacity: 1}} c := NewMConnectionWithConfig(conn, chDescs, onReceive, onError, cfg) c.SetLogger(log.TestingLogger()) return c diff --git a/p2p/transport_test.go b/p2p/transport_test.go index 182b2889..7d9c17fb 100644 --- a/p2p/transport_test.go +++ b/p2p/transport_test.go @@ -498,13 +498,13 @@ func TestTransportConnDuplicateIPFilter(t *testing.T) { ) cs.Set(c, []net.IP{ - net.IP{10, 0, 10, 1}, - net.IP{10, 0, 10, 2}, - net.IP{10, 0, 10, 3}, + {10, 0, 10, 1}, + {10, 0, 10, 2}, + {10, 0, 10, 3}, }) if err := filter(cs, c, []net.IP{ - net.IP{10, 0, 10, 2}, + {10, 0, 10, 2}, }); err == nil { t.Errorf("expected Peer to be rejected as duplicate") } diff --git a/privval/client_test.go b/privval/client_test.go index 72682a8d..1aea58cf 100644 --- a/privval/client_test.go +++ b/privval/client_test.go @@ -37,11 +37,11 @@ func socketTestCases(t *testing.T) []socketTestCase { require.NoError(t, err) unixAddr := fmt.Sprintf("unix://%s", unixFilePath) return []socketTestCase{ - socketTestCase{ + { addr: tcpAddr, dialer: DialTCPFn(tcpAddr, testConnDeadline, ed25519.GenPrivKey()), }, - socketTestCase{ + { addr: unixAddr, dialer: DialUnixFn(unixFilePath), }, diff --git a/scripts/get_tools.sh b/scripts/get_tools.sh index 47077c10..87e30a3d 100755 --- a/scripts/get_tools.sh +++ b/scripts/get_tools.sh @@ -47,8 +47,8 @@ installFromGithub() { installFromGithub mitchellh/gox 51ed453898ca5579fea9ad1f08dff6b121d9f2e8 installFromGithub golang/dep 22125cfaa6ddc71e145b1535d4b7ee9744fefff2 cmd/dep -## gometalinter v2.0.11 -installFromGithub alecthomas/gometalinter 17a7ffa42374937bfecabfb8d2efbd4db0c26741 +## gometalinter v3.0.0 +installFromGithub alecthomas/gometalinter df395bfa67c5d0630d936c0044cf07ff05086655 installFromGithub gogo/protobuf 61dbc136cf5d2f08d68a011382652244990a53a9 protoc-gen-gogo installFromGithub square/certstrap e27060a3643e814151e65b9807b6b06d169580a7 diff --git a/state/txindex/kv/kv_test.go b/state/txindex/kv/kv_test.go index 0f206514..b726a423 100644 --- a/state/txindex/kv/kv_test.go +++ b/state/txindex/kv/kv_test.go @@ -184,8 +184,8 @@ func TestIndexAllTags(t *testing.T) { indexer := NewTxIndex(db.NewMemDB(), IndexAllTags()) txResult := txResultWithTags([]cmn.KVPair{ - cmn.KVPair{Key: []byte("account.owner"), Value: []byte("Ivan")}, - cmn.KVPair{Key: []byte("account.number"), Value: []byte("1")}, + {Key: []byte("account.owner"), Value: []byte("Ivan")}, + {Key: []byte("account.number"), Value: []byte("1")}, }) err := indexer.Index(txResult) diff --git a/tools/tm-monitor/eventmeter/eventmeter.go b/tools/tm-monitor/eventmeter/eventmeter.go index 185f3774..63d58b96 100644 --- a/tools/tm-monitor/eventmeter/eventmeter.go +++ b/tools/tm-monitor/eventmeter/eventmeter.go @@ -196,7 +196,7 @@ func (em *EventMeter) RegisterDisconnectCallback(f DisconnectCallbackFunc) { // Private func (em *EventMeter) subscribe() error { - for query, _ := range em.queryToMetricMap { + for query := range em.queryToMetricMap { if err := em.wsc.Subscribe(context.TODO(), query); err != nil { return err } diff --git a/types/genesis_test.go b/types/genesis_test.go index e7f041a8..0e81187e 100644 --- a/types/genesis_test.go +++ b/types/genesis_test.go @@ -15,9 +15,9 @@ import ( func TestGenesisBad(t *testing.T) { // test some bad ones from raw json testCases := [][]byte{ - []byte{}, // empty - []byte{1, 1, 1, 1, 1}, // junk - []byte(`{}`), // empty + {}, // empty + {1, 1, 1, 1, 1}, // junk + []byte(`{}`), // empty []byte(`{"chain_id":"mychain","validators":[{}]}`), // invalid validator // missing pub_key type []byte(`{"validators":[{"pub_key":{"value":"AT/+aaL1eB0477Mud9JMm8Sh8BIvOYlPGC9KkIUmFaE="},"power":"10","name":""}]}`), diff --git a/types/part_set.go b/types/part_set.go index 3c1c8b29..4533fb75 100644 --- a/types/part_set.go +++ b/types/part_set.go @@ -21,9 +21,6 @@ type Part struct { Index int `json:"index"` Bytes cmn.HexBytes `json:"bytes"` Proof merkle.SimpleProof `json:"proof"` - - // Cache - hash []byte } // ValidateBasic performs basic validation. From 6485e68bebcbf4f91b1bb8fcc52e01e2c97ae6a4 Mon Sep 17 00:00:00 2001 From: Ismail Khoffi Date: Mon, 4 Feb 2019 09:24:54 +0100 Subject: [PATCH 245/267] Use ethereum's secp256k1 lib (#3234) * switch from fork (tendermint/btcd) to orig package (btcsuite/btcd); also - remove obsolete check in test `size != -1` is always true - WIP as the serialization still needs to be wrapped * WIP: wrap signature & privkey, pubkey needs to be wrapped as well * wrap pubkey too * use "github.com/ethereum/go-ethereum/crypto/secp256k1" if cgo is available, else use "github.com/btcsuite/btcd/btcec" and take care of lower-S when verifying Annoyingly, had to disable pruning when importing github.com/ethereum/go-ethereum/ :-/ * update comment * update comment * emulate signature_nocgo.go for additional benchmarks: https://github.com/ethereum/go-ethereum/blob/592bf6a59cac9697f0491b24e5093cb759d7e44c/crypto/signature_nocgo.go#L60-L76 * use our format (r || s) in lower-s form when in the non-cgo case * remove comment about using the C library directly * vendor github.com/btcsuite/btcd too * Add test for the !cgo case * update changelog pending Closes #3162 #3163 Refs #1958, #2091, tendermint/btcd#1 --- CHANGELOG_PENDING.md | 2 + Gopkg.lock | 24 ++++---- Gopkg.toml | 16 +++++- crypto/encoding/amino/encode_test.go | 5 +- crypto/secp256k1/secp256k1.go | 30 ++-------- crypto/secp256k1/secp256k1_cgo.go | 24 ++++++++ crypto/secp256k1/secp256k1_nocgo.go | 71 ++++++++++++++++++++++++ crypto/secp256k1/secp256k1_nocgo_test.go | 39 +++++++++++++ crypto/secp256k1/secpk256k1_test.go | 2 +- 9 files changed, 169 insertions(+), 44 deletions(-) create mode 100644 crypto/secp256k1/secp256k1_cgo.go create mode 100644 crypto/secp256k1/secp256k1_nocgo.go create mode 100644 crypto/secp256k1/secp256k1_nocgo_test.go diff --git a/CHANGELOG_PENDING.md b/CHANGELOG_PENDING.md index ac4b1628..b66cd4e8 100644 --- a/CHANGELOG_PENDING.md +++ b/CHANGELOG_PENDING.md @@ -20,6 +20,8 @@ Special thanks to external contributors on this release: ### IMPROVEMENTS: - [tools] add go-deadlock tool to help detect deadlocks +- [crypto] \#3163 use ethereum's libsecp256k1 go-wrapper for signatures when cgo is available +- [crypto] \#3162 wrap btcd instead of forking it to keep up with fixes (used if cgo is not available) ### BUG FIXES: - [node] \#3186 EventBus and indexerService should be started before first block (for replay last block on handshake) execution diff --git a/Gopkg.lock b/Gopkg.lock index 9880f3f3..9e3d5d8a 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -10,12 +10,11 @@ revision = "3a771d992973f24aa725d07868b467d1ddfceafb" [[projects]] - branch = "master" - digest = "1:c0decf632843204d2b8781de7b26e7038584e2dcccc7e2f401e88ae85b1df2b7" + digest = "1:093bf93a65962e8191e3e8cd8fc6c363f83d43caca9739c906531ba7210a9904" name = "github.com/btcsuite/btcd" packages = ["btcec"] pruneopts = "UT" - revision = "67e573d211ace594f1366b4ce9d39726c4b19bd0" + revision = "ed77733ec07dfc8a513741138419b8d9d3de9d2d" [[projects]] digest = "1:1d8e1cb71c33a9470bbbae09bfec09db43c6bf358dfcae13cd8807c4e2a9a2bf" @@ -35,6 +34,14 @@ revision = "8991bc29aa16c548c550c7ff78260e27b9ab7c73" version = "v1.1.1" +[[projects]] + digest = "1:b42be5a3601f833e0b9f2d6625d887ec1309764bfcac3d518f3db425dcd4ec5c" + name = "github.com/ethereum/go-ethereum" + packages = ["crypto/secp256k1"] + pruneopts = "T" + revision = "9dc5d1a915ac0e0bd8429d6ac41df50eec91de5f" + version = "v1.8.21" + [[projects]] digest = "1:544229a3ca0fb2dd5ebc2896d3d2ff7ce096d9751635301e44e37e761349ee70" name = "github.com/fortytw2/leaktest" @@ -360,14 +367,6 @@ pruneopts = "UT" revision = "6b91fda63f2e36186f1c9d0e48578defb69c5d43" -[[projects]] - digest = "1:83f5e189eea2baad419a6a410984514266ff690075759c87e9ede596809bd0b8" - name = "github.com/tendermint/btcd" - packages = ["btcec"] - pruneopts = "UT" - revision = "80daadac05d1cd29571fccf27002d79667a88b58" - version = "v0.1.1" - [[projects]] digest = "1:ad9c4c1a4e7875330b1f62906f2830f043a23edb5db997e3a5ac5d3e6eadf80a" name = "github.com/tendermint/go-amino" @@ -504,8 +503,10 @@ analyzer-name = "dep" analyzer-version = 1 input-imports = [ + "github.com/btcsuite/btcd/btcec", "github.com/btcsuite/btcutil/base58", "github.com/btcsuite/btcutil/bech32", + "github.com/ethereum/go-ethereum/crypto/secp256k1", "github.com/fortytw2/leaktest", "github.com/go-kit/kit/log", "github.com/go-kit/kit/log/level", @@ -535,7 +536,6 @@ "github.com/syndtr/goleveldb/leveldb/errors", "github.com/syndtr/goleveldb/leveldb/iterator", "github.com/syndtr/goleveldb/leveldb/opt", - "github.com/tendermint/btcd/btcec", "github.com/tendermint/go-amino", "golang.org/x/crypto/bcrypt", "golang.org/x/crypto/chacha20poly1305", diff --git a/Gopkg.toml b/Gopkg.toml index 72ec6659..e5d6b8da 100644 --- a/Gopkg.toml +++ b/Gopkg.toml @@ -75,14 +75,26 @@ name = "github.com/prometheus/client_golang" version = "^0.9.1" +# we use the secp256k1 implementation: [[constraint]] - name = "github.com/tendermint/btcd" - version = "v0.1.1" + name = "github.com/ethereum/go-ethereum" + version = "^v1.8.21" + + # Prevent dep from pruning build scripts and codegen templates + # note: this leaves the whole go-ethereum package in vendor + # can be removed when https://github.com/golang/dep/issues/1847 is resolved + [[prune.project]] + name = "github.com/ethereum/go-ethereum" + unused-packages = false ################################### ## Some repos dont have releases. ## Pin to revision +[[constraint]] + name = "github.com/btcsuite/btcd" + revision = "ed77733ec07dfc8a513741138419b8d9d3de9d2d" + [[constraint]] name = "golang.org/x/crypto" revision = "505ab145d0a99da450461ae2c1a9f6cd10d1f447" diff --git a/crypto/encoding/amino/encode_test.go b/crypto/encoding/amino/encode_test.go index c8cb2413..95510306 100644 --- a/crypto/encoding/amino/encode_test.go +++ b/crypto/encoding/amino/encode_test.go @@ -25,9 +25,8 @@ func checkAminoBinary(t *testing.T, src, dst interface{}, size int) { assert.Equal(t, byterSrc.Bytes(), bz, "Amino binary vs Bytes() mismatch") } // Make sure we have the expected length. - if size != -1 { - assert.Equal(t, size, len(bz), "Amino binary size mismatch") - } + assert.Equal(t, size, len(bz), "Amino binary size mismatch") + // Unmarshal. err = cdc.UnmarshalBinaryBare(bz, dst) require.Nil(t, err, "%+v", err) diff --git a/crypto/secp256k1/secp256k1.go b/crypto/secp256k1/secp256k1.go index d3528fdd..78857c45 100644 --- a/crypto/secp256k1/secp256k1.go +++ b/crypto/secp256k1/secp256k1.go @@ -7,10 +7,12 @@ import ( "fmt" "io" - secp256k1 "github.com/tendermint/btcd/btcec" - amino "github.com/tendermint/go-amino" "golang.org/x/crypto/ripemd160" + secp256k1 "github.com/btcsuite/btcd/btcec" + + amino "github.com/tendermint/go-amino" + "github.com/tendermint/tendermint/crypto" ) @@ -44,16 +46,6 @@ func (privKey PrivKeySecp256k1) Bytes() []byte { return cdc.MustMarshalBinaryBare(privKey) } -// Sign creates an ECDSA signature on curve Secp256k1, using SHA256 on the msg. -func (privKey PrivKeySecp256k1) Sign(msg []byte) ([]byte, error) { - priv, _ := secp256k1.PrivKeyFromBytes(secp256k1.S256(), privKey[:]) - sig, err := priv.Sign(crypto.Sha256(msg)) - if err != nil { - return nil, err - } - return sig.Serialize(), nil -} - // PubKey performs the point-scalar multiplication from the privKey on the // generator point to get the pubkey. func (privKey PrivKeySecp256k1) PubKey() crypto.PubKey { @@ -137,20 +129,6 @@ func (pubKey PubKeySecp256k1) Bytes() []byte { return bz } -func (pubKey PubKeySecp256k1) VerifyBytes(msg []byte, sig []byte) bool { - pub, err := secp256k1.ParsePubKey(pubKey[:], secp256k1.S256()) - if err != nil { - return false - } - parsedSig, err := secp256k1.ParseSignature(sig[:], secp256k1.S256()) - if err != nil { - return false - } - // Underlying library ensures that this signature is in canonical form, to - // prevent Secp256k1 malleability from altering the sign of the s term. - return parsedSig.Verify(crypto.Sha256(msg), pub) -} - func (pubKey PubKeySecp256k1) String() string { return fmt.Sprintf("PubKeySecp256k1{%X}", pubKey[:]) } diff --git a/crypto/secp256k1/secp256k1_cgo.go b/crypto/secp256k1/secp256k1_cgo.go new file mode 100644 index 00000000..30414d2b --- /dev/null +++ b/crypto/secp256k1/secp256k1_cgo.go @@ -0,0 +1,24 @@ +// +build cgo + +package secp256k1 + +import ( + "github.com/ethereum/go-ethereum/crypto/secp256k1" + + "github.com/tendermint/tendermint/crypto" +) + +// Sign creates an ECDSA signature on curve Secp256k1, using SHA256 on the msg. +func (privKey PrivKeySecp256k1) Sign(msg []byte) ([]byte, error) { + rsv, err := secp256k1.Sign(crypto.Sha256(msg), privKey[:]) + if err != nil { + return nil, err + } + // we do not need v in r||s||v: + rs := rsv[:len(rsv)-1] + return rs, nil +} + +func (pubKey PubKeySecp256k1) VerifyBytes(msg []byte, sig []byte) bool { + return secp256k1.VerifySignature(pubKey[:], crypto.Sha256(msg), sig) +} diff --git a/crypto/secp256k1/secp256k1_nocgo.go b/crypto/secp256k1/secp256k1_nocgo.go new file mode 100644 index 00000000..34b006fa --- /dev/null +++ b/crypto/secp256k1/secp256k1_nocgo.go @@ -0,0 +1,71 @@ +// +build !cgo + +package secp256k1 + +import ( + "math/big" + + secp256k1 "github.com/btcsuite/btcd/btcec" + + "github.com/tendermint/tendermint/crypto" +) + +// used to reject malleable signatures +// see: +// - https://github.com/ethereum/go-ethereum/blob/f9401ae011ddf7f8d2d95020b7446c17f8d98dc1/crypto/signature_nocgo.go#L90-L93 +// - https://github.com/ethereum/go-ethereum/blob/f9401ae011ddf7f8d2d95020b7446c17f8d98dc1/crypto/crypto.go#L39 +var secp256k1N, _ = new(big.Int).SetString("fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141", 16) +var secp256k1halfN = new(big.Int).Div(secp256k1N, big.NewInt(2)) + +// Sign creates an ECDSA signature on curve Secp256k1, using SHA256 on the msg. +// The returned signature will be of the form R || S (in lower-S form). +func (privKey PrivKeySecp256k1) Sign(msg []byte) ([]byte, error) { + priv, _ := secp256k1.PrivKeyFromBytes(secp256k1.S256(), privKey[:]) + sig, err := priv.Sign(crypto.Sha256(msg)) + if err != nil { + return nil, err + } + sigBytes := serializeSig(sig) + return sigBytes, nil +} + +// VerifyBytes verifies a signature of the form R || S. +// It rejects signatures which are not in lower-S form. +func (pubKey PubKeySecp256k1) VerifyBytes(msg []byte, sigStr []byte) bool { + if len(sigStr) != 64 { + return false + } + pub, err := secp256k1.ParsePubKey(pubKey[:], secp256k1.S256()) + if err != nil { + return false + } + // parse the signature: + signature := signatureFromBytes(sigStr) + // Reject malleable signatures. libsecp256k1 does this check but btcec doesn't. + // see: https://github.com/ethereum/go-ethereum/blob/f9401ae011ddf7f8d2d95020b7446c17f8d98dc1/crypto/signature_nocgo.go#L90-L93 + if signature.S.Cmp(secp256k1halfN) > 0 { + return false + } + return signature.Verify(crypto.Sha256(msg), pub) +} + +// Read Signature struct from R || S. Caller needs to ensure +// that len(sigStr) == 64. +func signatureFromBytes(sigStr []byte) *secp256k1.Signature { + return &secp256k1.Signature{ + new(big.Int).SetBytes(sigStr[:32]), + new(big.Int).SetBytes(sigStr[32:64]), + } +} + +// Serialize signature to R || S. +// R, S are padded to 32 bytes respectively. +func serializeSig(sig *secp256k1.Signature) []byte { + rBytes := sig.R.Bytes() + sBytes := sig.S.Bytes() + sigBytes := make([]byte, 64) + // 0 pad the byte arrays from the left if they aren't big enough. + copy(sigBytes[32-len(rBytes):32], rBytes) + copy(sigBytes[64-len(sBytes):64], sBytes) + return sigBytes +} diff --git a/crypto/secp256k1/secp256k1_nocgo_test.go b/crypto/secp256k1/secp256k1_nocgo_test.go new file mode 100644 index 00000000..95966478 --- /dev/null +++ b/crypto/secp256k1/secp256k1_nocgo_test.go @@ -0,0 +1,39 @@ +// +build !cgo + +package secp256k1 + +import ( + "testing" + + secp256k1 "github.com/btcsuite/btcd/btcec" + + "github.com/stretchr/testify/require" +) + +// Ensure that signature verification works, and that +// non-canonical signatures fail. +// Note: run with CGO_ENABLED=0 or go test -tags !cgo. +func TestSignatureVerificationAndRejectUpperS(t *testing.T) { + msg := []byte("We have lingered long enough on the shores of the cosmic ocean.") + for i := 0; i < 500; i++ { + priv := GenPrivKey() + sigStr, err := priv.Sign(msg) + require.NoError(t, err) + sig := signatureFromBytes(sigStr) + require.False(t, sig.S.Cmp(secp256k1halfN) > 0) + + pub := priv.PubKey() + require.True(t, pub.VerifyBytes(msg, sigStr)) + + // malleate: + sig.S.Sub(secp256k1.S256().CurveParams.N, sig.S) + require.True(t, sig.S.Cmp(secp256k1halfN) > 0) + malSigStr := serializeSig(sig) + + require.False(t, pub.VerifyBytes(msg, malSigStr), + "VerifyBytes incorrect with malleated & invalid S. sig=%v, key=%v", + sig, + priv, + ) + } +} diff --git a/crypto/secp256k1/secpk256k1_test.go b/crypto/secp256k1/secpk256k1_test.go index 2fa48301..0f0b5adc 100644 --- a/crypto/secp256k1/secpk256k1_test.go +++ b/crypto/secp256k1/secpk256k1_test.go @@ -11,7 +11,7 @@ import ( "github.com/tendermint/tendermint/crypto" "github.com/tendermint/tendermint/crypto/secp256k1" - underlyingSecp256k1 "github.com/tendermint/btcd/btcec" + underlyingSecp256k1 "github.com/btcsuite/btcd/btcec" ) type keyData struct { From eb4e23b91eca39e3568ad1dc3a1f793773810374 Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Mon, 4 Feb 2019 07:30:24 -0800 Subject: [PATCH 246/267] fix FlushStop (#3247) * p2p/pex: failing test * p2p/conn: add stopMtx for FlushStop and OnStop * changelog --- CHANGELOG_PENDING.md | 2 + p2p/conn/connection.go | 19 +++++++++- p2p/pex/pex_reactor_test.go | 75 +++++++++++++++++++++++++++++++++++++ 3 files changed, 95 insertions(+), 1 deletion(-) diff --git a/CHANGELOG_PENDING.md b/CHANGELOG_PENDING.md index b66cd4e8..434232e4 100644 --- a/CHANGELOG_PENDING.md +++ b/CHANGELOG_PENDING.md @@ -25,3 +25,5 @@ Special thanks to external contributors on this release: ### BUG FIXES: - [node] \#3186 EventBus and indexerService should be started before first block (for replay last block on handshake) execution +- [p2p] \#3247 Fix panic in SeedMode when calling FlushStop and OnStop + concurrently diff --git a/p2p/conn/connection.go b/p2p/conn/connection.go index fb20c477..92073491 100644 --- a/p2p/conn/connection.go +++ b/p2p/conn/connection.go @@ -8,6 +8,7 @@ import ( "math" "net" "reflect" + "sync" "sync/atomic" "time" @@ -89,6 +90,10 @@ type MConnection struct { quitSendRoutine chan struct{} doneSendRoutine chan struct{} + // used to ensure FlushStop and OnStop + // are safe to call concurrently. + stopMtx sync.Mutex + flushTimer *cmn.ThrottleTimer // flush writes as necessary but throttled. pingTimer *cmn.RepeatTimer // send pings periodically @@ -210,8 +215,17 @@ func (c *MConnection) OnStart() error { // It additionally ensures that all successful // .Send() calls will get flushed before closing // the connection. -// NOTE: it is not safe to call this method more than once. func (c *MConnection) FlushStop() { + c.stopMtx.Lock() + defer c.stopMtx.Unlock() + + select { + case <-c.quitSendRoutine: + // already quit via OnStop + return + default: + } + c.BaseService.OnStop() c.flushTimer.Stop() c.pingTimer.Stop() @@ -247,6 +261,9 @@ func (c *MConnection) FlushStop() { // OnStop implements BaseService func (c *MConnection) OnStop() { + c.stopMtx.Lock() + defer c.stopMtx.Unlock() + select { case <-c.quitSendRoutine: // already quit via FlushStop diff --git a/p2p/pex/pex_reactor_test.go b/p2p/pex/pex_reactor_test.go index f5125c60..4f4ccb03 100644 --- a/p2p/pex/pex_reactor_test.go +++ b/p2p/pex/pex_reactor_test.go @@ -316,6 +316,81 @@ func TestPEXReactorCrawlStatus(t *testing.T) { // TODO: test } +// connect a peer to a seed, wait a bit, then stop it. +// this should give it time to request addrs and for the seed +// to call FlushStop, and allows us to test calling Stop concurrently +// with FlushStop. Before a fix, this non-deterministically reproduced +// https://github.com/tendermint/tendermint/issues/3231. +func TestPEXReactorSeedModeFlushStop(t *testing.T) { + N := 2 + switches := make([]*p2p.Switch, N) + + // directory to store address books + dir, err := ioutil.TempDir("", "pex_reactor") + require.Nil(t, err) + defer os.RemoveAll(dir) // nolint: errcheck + + books := make([]*addrBook, N) + logger := log.TestingLogger() + + // create switches + for i := 0; i < N; i++ { + switches[i] = p2p.MakeSwitch(cfg, i, "testing", "123.123.123", func(i int, sw *p2p.Switch) *p2p.Switch { + books[i] = NewAddrBook(filepath.Join(dir, fmt.Sprintf("addrbook%d.json", i)), false) + books[i].SetLogger(logger.With("pex", i)) + sw.SetAddrBook(books[i]) + + sw.SetLogger(logger.With("pex", i)) + + config := &PEXReactorConfig{} + if i == 0 { + // first one is a seed node + config = &PEXReactorConfig{SeedMode: true} + } + r := NewPEXReactor(books[i], config) + r.SetLogger(logger.With("pex", i)) + r.SetEnsurePeersPeriod(250 * time.Millisecond) + sw.AddReactor("pex", r) + + return sw + }) + } + + for _, sw := range switches { + err := sw.Start() // start switch and reactors + require.Nil(t, err) + } + + reactor := switches[0].Reactors()["pex"].(*PEXReactor) + peerID := switches[1].NodeInfo().ID() + + err = switches[1].DialPeerWithAddress(switches[0].NodeInfo().NetAddress(), false) + assert.NoError(t, err) + + // sleep up to a second while waiting for the peer to send us a message. + // this isn't perfect since it's possible the peer sends us a msg and we FlushStop + // before this loop catches it. but non-deterministically it works pretty well. + for i := 0; i < 1000; i++ { + v := reactor.lastReceivedRequests.Get(string(peerID)) + if v != nil { + break + } + time.Sleep(time.Millisecond) + } + + // by now the FlushStop should have happened. Try stopping the peer. + // it should be safe to do this. + peers := switches[0].Peers().List() + for _, peer := range peers { + peer.Stop() + } + + // stop the switches + for _, s := range switches { + s.Stop() + } +} + func TestPEXReactorDoesNotAddPrivatePeersToAddrBook(t *testing.T) { peer := p2p.CreateRandomPeer(false) From 39eba4e1543960e7783581ec75d2e531e8d44afa Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Mon, 4 Feb 2019 13:00:06 -0500 Subject: [PATCH 247/267] WAL: better errors and new fail point (#3246) * privval: more info in errors * wal: change Debug logs to Info * wal: log and return error on corrupted wal instead of panicing * fail: Exit right away instead of sending interupt * consensus: FAIL before handling our own vote allows to replicate #3089: - run using `FAIL_TEST_INDEX=0` - delete some bytes from the end of the WAL - start normally Results in logs like: ``` I[2019-02-03|18:12:58.225] Searching for height module=consensus wal=/Users/ethanbuchman/.tendermint/data/cs.wal/wal height=1 min=0 max=0 E[2019-02-03|18:12:58.225] Error on catchup replay. Proceeding to start ConsensusState anyway module=consensus err="failed to read data: EOF" I[2019-02-03|18:12:58.225] Started node module=main nodeInfo="{ProtocolVersion:{P2P:6 Block:9 App:1} ID_:35e87e93f2e31f305b65a5517fd2102331b56002 ListenAddr:tcp://0.0.0.0:26656 Network:test-chain-J8JvJH Version:0.29.1 Channels:4020212223303800 Moniker:Ethans-MacBook-Pro.local Other:{TxIndex:on RPCAddress:tcp://0.0.0.0:26657}}" E[2019-02-03|18:12:58.226] Couldn't connect to any seeds module=p2p I[2019-02-03|18:12:59.229] Timed out module=consensus dur=998.568ms height=1 round=0 step=RoundStepNewHeight I[2019-02-03|18:12:59.230] enterNewRound(1/0). Current: 1/0/RoundStepNewHeight module=consensus height=1 round=0 I[2019-02-03|18:12:59.230] enterPropose(1/0). Current: 1/0/RoundStepNewRound module=consensus height=1 round=0 I[2019-02-03|18:12:59.230] enterPropose: Our turn to propose module=consensus height=1 round=0 proposer=AD278B7767B05D7FBEB76207024C650988FA77D5 privValidator="PrivValidator{AD278B7767B05D7FBEB76207024C650988FA77D5 LH:1, LR:0, LS:2}" E[2019-02-03|18:12:59.230] enterPropose: Error signing proposal module=consensus height=1 round=0 err="Error signing proposal: Step regression at height 1 round 0. Got 1, last step 2" I[2019-02-03|18:13:02.233] Timed out module=consensus dur=3s height=1 round=0 step=RoundStepPropose I[2019-02-03|18:13:02.233] enterPrevote(1/0). Current: 1/0/RoundStepPropose module=consensus I[2019-02-03|18:13:02.233] enterPrevote: ProposalBlock is nil module=consensus height=1 round=0 E[2019-02-03|18:13:02.234] Error signing vote module=consensus height=1 round=0 vote="Vote{0:AD278B7767B0 1/00/1(Prevote) 000000000000 000000000000 @ 2019-02-04T02:13:02.233897Z}" err="Error signing vote: Conflicting data" ``` Notice the EOF, the step regression, and the conflicting data. * wal: change errors to be DataCorruptionError * exit on corrupt WAL * fix log * fix new line --- consensus/replay.go | 4 ++-- consensus/state.go | 26 ++++++++++++++++++++++++++ consensus/wal.go | 14 +++++++------- libs/fail/fail.go | 5 +++-- privval/file.go | 6 +++--- 5 files changed, 41 insertions(+), 14 deletions(-) diff --git a/consensus/replay.go b/consensus/replay.go index 3ac63657..21fef6b2 100644 --- a/consensus/replay.go +++ b/consensus/replay.go @@ -144,8 +144,8 @@ func (cs *ConsensusState) catchupReplay(csHeight int64) error { if err == io.EOF { break } else if IsDataCorruptionError(err) { - cs.Logger.Debug("data has been corrupted in last height of consensus WAL", "err", err, "height", csHeight) - panic(fmt.Sprintf("data has been corrupted (%v) in last height %d of consensus WAL", err, csHeight)) + cs.Logger.Error("data has been corrupted in last height of consensus WAL", "err", err, "height", csHeight) + return err } else if err != nil { return err } diff --git a/consensus/state.go b/consensus/state.go index 158e1605..cec7e5f5 100644 --- a/consensus/state.go +++ b/consensus/state.go @@ -307,6 +307,23 @@ func (cs *ConsensusState) OnStart() error { // reload from consensus log to catchup if cs.doWALCatchup { if err := cs.catchupReplay(cs.Height); err != nil { + // don't try to recover from data corruption error + if IsDataCorruptionError(err) { + cs.Logger.Error("Encountered corrupt WAL file", "err", err.Error()) + cs.Logger.Error("Please repair the WAL file before restarting") + fmt.Println(`You can attempt to repair the WAL as follows: + +---- +WALFILE=~/.tendermint/data/cs.wal/wal +cp $WALFILE ${WALFILE}.bak # backup the file +go run scripts/wal2json/main.go $WALFILE > wal.json # this will panic, but can be ignored +rm $WALFILE # remove the corrupt file +go run scripts/json2wal/main.go wal.json $WALFILE # rebuild the file without corruption +----`) + + return err + } + cs.Logger.Error("Error on catchup replay. Proceeding to start ConsensusState anyway", "err", err.Error()) // NOTE: if we ever do return an error here, // make sure to stop the timeoutTicker @@ -624,6 +641,15 @@ func (cs *ConsensusState) receiveRoutine(maxSteps int) { cs.handleMsg(mi) case mi = <-cs.internalMsgQueue: cs.wal.WriteSync(mi) // NOTE: fsync + + if _, ok := mi.Msg.(*VoteMessage); ok { + // we actually want to simulate failing during + // the previous WriteSync, but this isn't easy to do. + // Equivalent would be to fail here and manually remove + // some bytes from the end of the wal. + fail.Fail() // XXX + } + // handles proposals, block parts, votes cs.handleMsg(mi) case ti := <-cs.timeoutTicker.Chan(): // tockChan: diff --git a/consensus/wal.go b/consensus/wal.go index bbc9908f..ba89cd1a 100644 --- a/consensus/wal.go +++ b/consensus/wal.go @@ -163,7 +163,7 @@ func (wal *baseWAL) SearchForEndHeight(height int64, options *WALSearchOptions) // NOTE: starting from the last file in the group because we're usually // searching for the last height. See replay.go min, max := wal.group.MinIndex(), wal.group.MaxIndex() - wal.Logger.Debug("Searching for height", "height", height, "min", min, "max", max) + wal.Logger.Info("Searching for height", "height", height, "min", min, "max", max) for index := max; index >= min; index-- { gr, err = wal.group.NewReader(index) if err != nil { @@ -183,7 +183,7 @@ func (wal *baseWAL) SearchForEndHeight(height int64, options *WALSearchOptions) break } if options.IgnoreDataCorruptionErrors && IsDataCorruptionError(err) { - wal.Logger.Debug("Corrupted entry. Skipping...", "err", err) + wal.Logger.Error("Corrupted entry. Skipping...", "err", err) // do nothing continue } else if err != nil { @@ -194,7 +194,7 @@ func (wal *baseWAL) SearchForEndHeight(height int64, options *WALSearchOptions) if m, ok := msg.Msg.(EndHeightMessage); ok { lastHeightFound = m.Height if m.Height == height { // found - wal.Logger.Debug("Found", "height", height, "index", index) + wal.Logger.Info("Found", "height", height, "index", index) return gr, true, nil } } @@ -281,25 +281,25 @@ func (dec *WALDecoder) Decode() (*TimedWALMessage, error) { return nil, err } if err != nil { - return nil, fmt.Errorf("failed to read checksum: %v", err) + return nil, DataCorruptionError{fmt.Errorf("failed to read checksum: %v", err)} } crc := binary.BigEndian.Uint32(b) b = make([]byte, 4) _, err = dec.rd.Read(b) if err != nil { - return nil, fmt.Errorf("failed to read length: %v", err) + return nil, DataCorruptionError{fmt.Errorf("failed to read length: %v", err)} } length := binary.BigEndian.Uint32(b) if length > maxMsgSizeBytes { - return nil, fmt.Errorf("length %d exceeded maximum possible value of %d bytes", length, maxMsgSizeBytes) + return nil, DataCorruptionError{fmt.Errorf("length %d exceeded maximum possible value of %d bytes", length, maxMsgSizeBytes)} } data := make([]byte, length) _, err = dec.rd.Read(data) if err != nil { - return nil, fmt.Errorf("failed to read data: %v", err) + return nil, DataCorruptionError{fmt.Errorf("failed to read data: %v", err)} } // check checksum before decoding data diff --git a/libs/fail/fail.go b/libs/fail/fail.go index edfca13e..d7912af5 100644 --- a/libs/fail/fail.go +++ b/libs/fail/fail.go @@ -72,7 +72,8 @@ func FailRand(n int) { func Exit() { fmt.Printf("*** fail-test %d ***\n", callIndex) - proc, _ := os.FindProcess(os.Getpid()) - proc.Signal(os.Interrupt) + os.Exit(1) + // proc, _ := os.FindProcess(os.Getpid()) + // proc.Signal(os.Interrupt) // panic(fmt.Sprintf("*** fail-test %d ***", callIndex)) } diff --git a/privval/file.go b/privval/file.go index 8072cfa4..d27d7a78 100644 --- a/privval/file.go +++ b/privval/file.go @@ -87,17 +87,17 @@ type FilePVLastSignState struct { func (lss *FilePVLastSignState) CheckHRS(height int64, round int, step int8) (bool, error) { if lss.Height > height { - return false, errors.New("Height regression") + return false, fmt.Errorf("Height regression. Got %v, last height %v", height, lss.Height) } if lss.Height == height { if lss.Round > round { - return false, errors.New("Round regression") + return false, fmt.Errorf("Round regression at height %v. Got %v, last round %v", height, round, lss.Round) } if lss.Round == round { if lss.Step > step { - return false, errors.New("Step regression") + return false, fmt.Errorf("Step regression at height %v round %v. Got %v, last step %v", height, round, step, lss.Step) } else if lss.Step == step { if lss.SignBytes != nil { if lss.Signature == nil { From 1809efa3500e215e531dfd78dd6fe180ef3ef4b1 Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Mon, 4 Feb 2019 13:01:59 -0500 Subject: [PATCH 248/267] Introduce CommitSig alias for Vote in Commit (#3245) * types: memoize height/round in commit instead of first vote * types: commit.ValidateBasic in VerifyCommit * types: new CommitSig alias for Vote In preparation for reducing the redundancy in Commits, we introduce the CommitSig as an alias for Vote. This is non-breaking on the protocol, and minor breaking on the Go API, as Commit now contains a list of CommitSig instead of Vote. * remove dependence on ToVote * update some comments * fix tests * fix tests * fixes from review --- CHANGELOG_PENDING.md | 1 + blockchain/reactor_test.go | 4 +- blockchain/store_test.go | 23 ++++---- consensus/replay_test.go | 2 +- consensus/state.go | 4 +- consensus/types/round_state_test.go | 6 +-- lite/helpers.go | 6 +-- state/execution.go | 2 +- state/execution_test.go | 20 +++---- types/block.go | 83 +++++++++++++++++++++-------- types/block_test.go | 1 - types/validator_set.go | 18 +++---- types/validator_set_test.go | 4 +- types/vote.go | 10 ++++ types/vote_set.go | 8 +-- types/vote_test.go | 14 +++++ 16 files changed, 136 insertions(+), 70 deletions(-) diff --git a/CHANGELOG_PENDING.md b/CHANGELOG_PENDING.md index 434232e4..daa42654 100644 --- a/CHANGELOG_PENDING.md +++ b/CHANGELOG_PENDING.md @@ -11,6 +11,7 @@ Special thanks to external contributors on this release: * Apps * Go API + - [types] \#3245 Commit uses `type CommitSig Vote` instead of `Vote` directly. * Blockchain Protocol diff --git a/blockchain/reactor_test.go b/blockchain/reactor_test.go index f6c29d65..138e1622 100644 --- a/blockchain/reactor_test.go +++ b/blockchain/reactor_test.go @@ -100,8 +100,8 @@ func newBlockchainReactor(logger log.Logger, genDoc *types.GenesisDoc, privVals lastBlockMeta := blockStore.LoadBlockMeta(blockHeight - 1) lastBlock := blockStore.LoadBlock(blockHeight - 1) - vote := makeVote(&lastBlock.Header, lastBlockMeta.BlockID, state.Validators, privVals[0]) - lastCommit = &types.Commit{Precommits: []*types.Vote{vote}, BlockID: lastBlockMeta.BlockID} + vote := makeVote(&lastBlock.Header, lastBlockMeta.BlockID, state.Validators, privVals[0]).CommitSig() + lastCommit = &types.Commit{Precommits: []*types.CommitSig{vote}, BlockID: lastBlockMeta.BlockID} } thisBlock := makeBlock(blockHeight, state, lastCommit) diff --git a/blockchain/store_test.go b/blockchain/store_test.go index 8059072e..9abc210b 100644 --- a/blockchain/store_test.go +++ b/blockchain/store_test.go @@ -6,6 +6,7 @@ import ( "runtime/debug" "strings" "testing" + "time" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -20,6 +21,15 @@ import ( tmtime "github.com/tendermint/tendermint/types/time" ) +// make a Commit with a single vote containing just the height and a timestamp +func makeTestCommit(height int64, timestamp time.Time) *types.Commit { + return &types.Commit{ + Precommits: []*types.CommitSig{ + {Height: height, Timestamp: timestamp}, + }, + } +} + func makeStateAndBlockStore(logger log.Logger) (sm.State, *BlockStore) { config := cfg.ResetTestRoot("blockchain_reactor_test") // blockDB := dbm.NewDebugDB("blockDB", dbm.NewMemDB()) @@ -86,8 +96,7 @@ var ( partSet = block.MakePartSet(2) part1 = partSet.GetPart(0) part2 = partSet.GetPart(1) - seenCommit1 = &types.Commit{Precommits: []*types.Vote{{Height: 10, - Timestamp: tmtime.Now()}}} + seenCommit1 = makeTestCommit(10, tmtime.Now()) ) // TODO: This test should be simplified ... @@ -107,8 +116,7 @@ func TestBlockStoreSaveLoadBlock(t *testing.T) { // save a block block := makeBlock(bs.Height()+1, state, new(types.Commit)) validPartSet := block.MakePartSet(2) - seenCommit := &types.Commit{Precommits: []*types.Vote{{Height: 10, - Timestamp: tmtime.Now()}}} + seenCommit := makeTestCommit(10, tmtime.Now()) bs.SaveBlock(block, partSet, seenCommit) require.Equal(t, bs.Height(), block.Header.Height, "expecting the new height to be changed") @@ -127,8 +135,7 @@ func TestBlockStoreSaveLoadBlock(t *testing.T) { // End of setup, test data - commitAtH10 := &types.Commit{Precommits: []*types.Vote{{Height: 10, - Timestamp: tmtime.Now()}}} + commitAtH10 := makeTestCommit(10, tmtime.Now()) tuples := []struct { block *types.Block parts *types.PartSet @@ -351,9 +358,7 @@ func TestBlockFetchAtHeight(t *testing.T) { block := makeBlock(bs.Height()+1, state, new(types.Commit)) partSet := block.MakePartSet(2) - seenCommit := &types.Commit{Precommits: []*types.Vote{{Height: 10, - Timestamp: tmtime.Now()}}} - + seenCommit := makeTestCommit(10, tmtime.Now()) bs.SaveBlock(block, partSet, seenCommit) require.Equal(t, bs.Height(), block.Header.Height, "expecting the new height to be changed") diff --git a/consensus/replay_test.go b/consensus/replay_test.go index d3aaebf1..e7269254 100644 --- a/consensus/replay_test.go +++ b/consensus/replay_test.go @@ -539,7 +539,7 @@ func makeBlockchainFromWAL(wal WAL) ([]*types.Block, []*types.Commit, error) { if p.Type == types.PrecommitType { thisBlockCommit = &types.Commit{ BlockID: p.BlockID, - Precommits: []*types.Vote{p}, + Precommits: []*types.CommitSig{p.CommitSig()}, } } } diff --git a/consensus/state.go b/consensus/state.go index cec7e5f5..c8185d46 100644 --- a/consensus/state.go +++ b/consensus/state.go @@ -489,7 +489,7 @@ func (cs *ConsensusState) reconstructLastCommit(state sm.State) { if precommit == nil { continue } - added, err := lastPrecommits.AddVote(precommit) + added, err := lastPrecommits.AddVote(seenCommit.ToVote(precommit)) if !added || err != nil { cmn.PanicCrisis(fmt.Sprintf("Failed to reconstruct LastCommit: %v", err)) } @@ -1356,7 +1356,7 @@ func (cs *ConsensusState) recordMetrics(height int64, block *types.Block) { missingValidators := 0 missingValidatorsPower := int64(0) for i, val := range cs.Validators.Validators { - var vote *types.Vote + var vote *types.CommitSig if i < len(block.LastCommit.Precommits) { vote = block.LastCommit.Precommits[i] } diff --git a/consensus/types/round_state_test.go b/consensus/types/round_state_test.go index c2bc9f7c..cb16f939 100644 --- a/consensus/types/round_state_test.go +++ b/consensus/types/round_state_test.go @@ -16,7 +16,7 @@ func BenchmarkRoundStateDeepCopy(b *testing.B) { // Random validators nval, ntxs := 100, 100 vset, _ := types.RandValidatorSet(nval, 1) - precommits := make([]*types.Vote, nval) + precommits := make([]*types.CommitSig, nval) blockID := types.BlockID{ Hash: cmn.RandBytes(20), PartsHeader: types.PartSetHeader{ @@ -25,12 +25,12 @@ func BenchmarkRoundStateDeepCopy(b *testing.B) { } sig := make([]byte, ed25519.SignatureSize) for i := 0; i < nval; i++ { - precommits[i] = &types.Vote{ + precommits[i] = (&types.Vote{ ValidatorAddress: types.Address(cmn.RandBytes(20)), Timestamp: tmtime.Now(), BlockID: blockID, Signature: sig, - } + }).CommitSig() } txs := make([]types.Tx, ntxs) for i := 0; i < ntxs; i++ { diff --git a/lite/helpers.go b/lite/helpers.go index 5177ee50..6b18b351 100644 --- a/lite/helpers.go +++ b/lite/helpers.go @@ -70,7 +70,7 @@ func (pkz privKeys) ToValidators(init, inc int64) *types.ValidatorSet { // signHeader properly signs the header with all keys from first to last exclusive. func (pkz privKeys) signHeader(header *types.Header, first, last int) *types.Commit { - votes := make([]*types.Vote, len(pkz)) + commitSigs := make([]*types.CommitSig, len(pkz)) // We need this list to keep the ordering. vset := pkz.ToValidators(1, 0) @@ -78,12 +78,12 @@ func (pkz privKeys) signHeader(header *types.Header, first, last int) *types.Com // Fill in the votes we want. for i := first; i < last && i < len(pkz); i++ { vote := makeVote(header, vset, pkz[i]) - votes[vote.ValidatorIndex] = vote + commitSigs[vote.ValidatorIndex] = vote.CommitSig() } res := &types.Commit{ BlockID: types.BlockID{Hash: header.Hash()}, - Precommits: votes, + Precommits: commitSigs, } return res } diff --git a/state/execution.go b/state/execution.go index d59c8af0..85477eeb 100644 --- a/state/execution.go +++ b/state/execution.go @@ -315,7 +315,7 @@ func getBeginBlockValidatorInfo(block *types.Block, lastValSet *types.ValidatorS // Collect the vote info (list of validators and whether or not they signed). voteInfos := make([]abci.VoteInfo, len(lastValSet.Validators)) for i, val := range lastValSet.Validators { - var vote *types.Vote + var vote *types.CommitSig if i < len(block.LastCommit.Precommits) { vote = block.LastCommit.Precommits[i] } diff --git a/state/execution_test.go b/state/execution_test.go index b14ee649..041fb558 100644 --- a/state/execution_test.go +++ b/state/execution_test.go @@ -65,17 +65,17 @@ func TestBeginBlockValidators(t *testing.T) { prevBlockID := types.BlockID{prevHash, prevParts} now := tmtime.Now() - vote0 := &types.Vote{ValidatorIndex: 0, Timestamp: now, Type: types.PrecommitType} - vote1 := &types.Vote{ValidatorIndex: 1, Timestamp: now} + commitSig0 := (&types.Vote{ValidatorIndex: 0, Timestamp: now, Type: types.PrecommitType}).CommitSig() + commitSig1 := (&types.Vote{ValidatorIndex: 1, Timestamp: now}).CommitSig() testCases := []struct { desc string - lastCommitPrecommits []*types.Vote + lastCommitPrecommits []*types.CommitSig expectedAbsentValidators []int }{ - {"none absent", []*types.Vote{vote0, vote1}, []int{}}, - {"one absent", []*types.Vote{vote0, nil}, []int{1}}, - {"multiple absent", []*types.Vote{nil, nil}, []int{0, 1}}, + {"none absent", []*types.CommitSig{commitSig0, commitSig1}, []int{}}, + {"one absent", []*types.CommitSig{commitSig0, nil}, []int{1}}, + {"multiple absent", []*types.CommitSig{nil, nil}, []int{0, 1}}, } for _, tc := range testCases { @@ -136,10 +136,10 @@ func TestBeginBlockByzantineValidators(t *testing.T) { types.TM2PB.Evidence(ev2, valSet, now)}}, } - vote0 := &types.Vote{ValidatorIndex: 0, Timestamp: now, Type: types.PrecommitType} - vote1 := &types.Vote{ValidatorIndex: 1, Timestamp: now} - votes := []*types.Vote{vote0, vote1} - lastCommit := &types.Commit{BlockID: prevBlockID, Precommits: votes} + commitSig0 := (&types.Vote{ValidatorIndex: 0, Timestamp: now, Type: types.PrecommitType}).CommitSig() + commitSig1 := (&types.Vote{ValidatorIndex: 1, Timestamp: now}).CommitSig() + commitSigs := []*types.CommitSig{commitSig0, commitSig1} + lastCommit := &types.Commit{BlockID: prevBlockID, Precommits: commitSigs} for _, tc := range testCases { block, _ := state.MakeBlock(10, makeTxs(2), lastCommit, nil, state.Validators.GetProposer().Address) diff --git a/types/block.go b/types/block.go index 99ee3f8e..ec09fd44 100644 --- a/types/block.go +++ b/types/block.go @@ -477,39 +477,77 @@ func (h *Header) StringIndented(indent string) string { //------------------------------------- +// CommitSig is a vote included in a Commit. +// For now, it is identical to a vote, +// but in the future it will contain fewer fields +// to eliminate the redundancy in commits. +// See https://github.com/tendermint/tendermint/issues/1648. +type CommitSig Vote + +// String returns the underlying Vote.String() +func (cs *CommitSig) String() string { + return cs.toVote().String() +} + +// toVote converts the CommitSig to a vote. +// Once CommitSig has fewer fields than vote, +// converting to a Vote will require more information. +func (cs *CommitSig) toVote() *Vote { + if cs == nil { + return nil + } + v := Vote(*cs) + return &v +} + // Commit contains the evidence that a block was committed by a set of validators. // NOTE: Commit is empty for height 1, but never nil. type Commit struct { // NOTE: The Precommits are in order of address to preserve the bonded ValidatorSet order. // Any peer with a block can gossip precommits by index with a peer without recalculating the // active ValidatorSet. - BlockID BlockID `json:"block_id"` - Precommits []*Vote `json:"precommits"` + BlockID BlockID `json:"block_id"` + Precommits []*CommitSig `json:"precommits"` // Volatile - firstPrecommit *Vote - hash cmn.HexBytes - bitArray *cmn.BitArray + height int64 + round int + hash cmn.HexBytes + bitArray *cmn.BitArray } -// FirstPrecommit returns the first non-nil precommit in the commit. -// If all precommits are nil, it returns an empty precommit with height 0. -func (commit *Commit) FirstPrecommit() *Vote { +// VoteSignBytes constructs the SignBytes for the given CommitSig. +// The only unique part of the SignBytes is the Timestamp - all other fields +// signed over are otherwise the same for all validators. +func (commit *Commit) VoteSignBytes(chainID string, cs *CommitSig) []byte { + return cs.toVote().SignBytes(chainID) +} + +// memoizeHeightRound memoizes the height and round of the commit using +// the first non-nil vote. +func (commit *Commit) memoizeHeightRound() { if len(commit.Precommits) == 0 { - return nil + return } - if commit.firstPrecommit != nil { - return commit.firstPrecommit + if commit.height > 0 { + return } for _, precommit := range commit.Precommits { if precommit != nil { - commit.firstPrecommit = precommit - return precommit + commit.height = precommit.Height + commit.round = precommit.Round + return } } - return &Vote{ - Type: PrecommitType, - } +} + +// ToVote converts a CommitSig to a Vote. +// If the CommitSig is nil, the Vote will be nil. +// When CommitSig is reduced to contain fewer fields, +// this will need access to the ValidatorSet to properly +// reconstruct the vote. +func (commit *Commit) ToVote(cs *CommitSig) *Vote { + return cs.toVote() } // Height returns the height of the commit @@ -517,7 +555,8 @@ func (commit *Commit) Height() int64 { if len(commit.Precommits) == 0 { return 0 } - return commit.FirstPrecommit().Height + commit.memoizeHeightRound() + return commit.height } // Round returns the round of the commit @@ -525,7 +564,8 @@ func (commit *Commit) Round() int { if len(commit.Precommits) == 0 { return 0 } - return commit.FirstPrecommit().Round + commit.memoizeHeightRound() + return commit.round } // Type returns the vote type of the commit, which is always VoteTypePrecommit @@ -554,12 +594,13 @@ func (commit *Commit) BitArray() *cmn.BitArray { return commit.bitArray } -// GetByIndex returns the vote corresponding to a given validator index +// GetByIndex returns the vote corresponding to a given validator index. +// Implements VoteSetReader. func (commit *Commit) GetByIndex(index int) *Vote { - return commit.Precommits[index] + return commit.Precommits[index].toVote() } -// IsCommit returns true if there is at least one vote +// IsCommit returns true if there is at least one vote. func (commit *Commit) IsCommit() bool { return len(commit.Precommits) != 0 } diff --git a/types/block_test.go b/types/block_test.go index bedd8c8d..31e7983f 100644 --- a/types/block_test.go +++ b/types/block_test.go @@ -198,7 +198,6 @@ func TestCommit(t *testing.T) { commit, err := MakeCommit(lastID, h-1, 1, voteSet, vals) require.NoError(t, err) - assert.NotNil(t, commit.FirstPrecommit()) assert.Equal(t, h-1, commit.Height()) assert.Equal(t, 1, commit.Round()) assert.Equal(t, PrecommitType, SignedMsgType(commit.Type())) diff --git a/types/validator_set.go b/types/validator_set.go index a36e1920..2edec595 100644 --- a/types/validator_set.go +++ b/types/validator_set.go @@ -368,6 +368,10 @@ func (vals *ValidatorSet) Iterate(fn func(index int, val *Validator) bool) { // Verify that +2/3 of the set had signed the given signBytes. func (vals *ValidatorSet) VerifyCommit(chainID string, blockID BlockID, height int64, commit *Commit) error { + + if err := commit.ValidateBasic(); err != nil { + return err + } if vals.Size() != len(commit.Precommits) { return fmt.Errorf("Invalid commit -- wrong set size: %v vs %v", vals.Size(), len(commit.Precommits)) } @@ -380,24 +384,14 @@ func (vals *ValidatorSet) VerifyCommit(chainID string, blockID BlockID, height i } talliedVotingPower := int64(0) - round := commit.Round() for idx, precommit := range commit.Precommits { if precommit == nil { continue // OK, some precommits can be missing. } - if precommit.Height != height { - return fmt.Errorf("Invalid commit -- wrong height: want %v got %v", height, precommit.Height) - } - if precommit.Round != round { - return fmt.Errorf("Invalid commit -- wrong round: want %v got %v", round, precommit.Round) - } - if precommit.Type != PrecommitType { - return fmt.Errorf("Invalid commit -- not precommit @ index %v", idx) - } _, val := vals.GetByIndex(idx) // Validate signature. - precommitSignBytes := precommit.SignBytes(chainID) + precommitSignBytes := commit.VoteSignBytes(chainID, precommit) if !val.PubKey.VerifyBytes(precommitSignBytes, precommit.Signature) { return fmt.Errorf("Invalid commit -- invalid signature: %v", precommit) } @@ -481,7 +475,7 @@ func (vals *ValidatorSet) VerifyFutureCommit(newSet *ValidatorSet, chainID strin seen[idx] = true // Validate signature. - precommitSignBytes := precommit.SignBytes(chainID) + precommitSignBytes := commit.VoteSignBytes(chainID, precommit) if !val.PubKey.VerifyBytes(precommitSignBytes, precommit.Signature) { return cmn.NewError("Invalid commit -- invalid signature: %v", precommit) } diff --git a/types/validator_set_test.go b/types/validator_set_test.go index dd49ee16..72b2f661 100644 --- a/types/validator_set_test.go +++ b/types/validator_set_test.go @@ -565,7 +565,7 @@ func TestValidatorSetVerifyCommit(t *testing.T) { vote.Signature = sig commit := &Commit{ BlockID: blockID, - Precommits: []*Vote{vote}, + Precommits: []*CommitSig{vote.CommitSig()}, } badChainID := "notmychainID" @@ -573,7 +573,7 @@ func TestValidatorSetVerifyCommit(t *testing.T) { badHeight := height + 1 badCommit := &Commit{ BlockID: blockID, - Precommits: []*Vote{nil}, + Precommits: []*CommitSig{nil}, } // test some error cases diff --git a/types/vote.go b/types/vote.go index 8ff51d3c..ad05d688 100644 --- a/types/vote.go +++ b/types/vote.go @@ -59,6 +59,16 @@ type Vote struct { Signature []byte `json:"signature"` } +// CommitSig converts the Vote to a CommitSig. +// If the Vote is nil, the CommitSig will be nil. +func (vote *Vote) CommitSig() *CommitSig { + if vote == nil { + return nil + } + cs := CommitSig(*vote) + return &cs +} + func (vote *Vote) SignBytes(chainID string) []byte { bz, err := cdc.MarshalBinaryLengthPrefixed(CanonicalizeVote(chainID, vote)) if err != nil { diff --git a/types/vote_set.go b/types/vote_set.go index 0cf6cbb7..14930da4 100644 --- a/types/vote_set.go +++ b/types/vote_set.go @@ -541,11 +541,13 @@ func (voteSet *VoteSet) MakeCommit() *Commit { } // For every validator, get the precommit - votesCopy := make([]*Vote, len(voteSet.votes)) - copy(votesCopy, voteSet.votes) + commitSigs := make([]*CommitSig, len(voteSet.votes)) + for i, v := range voteSet.votes { + commitSigs[i] = v.CommitSig() + } return &Commit{ BlockID: *voteSet.maj23, - Precommits: votesCopy, + Precommits: commitSigs, } } diff --git a/types/vote_test.go b/types/vote_test.go index aefa4fcf..e4bf658b 100644 --- a/types/vote_test.go +++ b/types/vote_test.go @@ -7,6 +7,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + amino "github.com/tendermint/go-amino" "github.com/tendermint/tendermint/crypto" "github.com/tendermint/tendermint/crypto/ed25519" "github.com/tendermint/tendermint/crypto/tmhash" @@ -43,6 +44,19 @@ func exampleVote(t byte) *Vote { } } +// Ensure that Vote and CommitSig have the same encoding. +// This ensures using CommitSig isn't a breaking change. +// This test will fail and can be removed once CommitSig contains only sigs and +// timestamps. +func TestVoteEncoding(t *testing.T) { + vote := examplePrecommit() + commitSig := vote.CommitSig() + cdc := amino.NewCodec() + bz1 := cdc.MustMarshalBinaryBare(vote) + bz2 := cdc.MustMarshalBinaryBare(commitSig) + assert.Equal(t, bz1, bz2) +} + func TestVoteSignable(t *testing.T) { vote := examplePrecommit() signBytes := vote.SignBytes("test_chain_id") From d8f0bc3e60eaddef11fe7e83328a22a149de5a01 Mon Sep 17 00:00:00 2001 From: Anton Kaliaev Date: Wed, 6 Feb 2019 14:11:35 +0400 Subject: [PATCH 249/267] Revert "quick fix for CircleCI (#2279)" This reverts commit 1cf6712a36e8ecc843a68aa373748e89e0afecba. --- .circleci/config.yml | 156 ++++++------------------------------------- 1 file changed, 22 insertions(+), 134 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index ecc7c0ac..82679337 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -48,10 +48,10 @@ jobs: key: v3-pkg-cache paths: - /go/pkg - # - save_cache: - # key: v3-tree-{{ .Environment.CIRCLE_SHA1 }} - # paths: - # - /go/src/github.com/tendermint/tendermint + - save_cache: + key: v3-tree-{{ .Environment.CIRCLE_SHA1 }} + paths: + - /go/src/github.com/tendermint/tendermint build_slate: <<: *defaults @@ -60,23 +60,8 @@ jobs: at: /tmp/workspace - restore_cache: key: v3-pkg-cache - # https://discuss.circleci.com/t/saving-cache-stopped-working-warning-skipping-this-step-disabled-in-configuration/24423/2 - # - restore_cache: - # key: v3-tree-{{ .Environment.CIRCLE_SHA1 }} - - checkout - - run: - name: tools - command: | - export PATH="$GOBIN:$PATH" - make get_tools - - run: - name: dependencies - command: | - export PATH="$GOBIN:$PATH" - make get_vendor_deps - - run: mkdir -p $GOPATH/src/github.com/tendermint - - run: ln -sf /home/circleci/project $GOPATH/src/github.com/tendermint/tendermint - + - restore_cache: + key: v3-tree-{{ .Environment.CIRCLE_SHA1 }} - run: name: slate docs command: | @@ -91,23 +76,8 @@ jobs: at: /tmp/workspace - restore_cache: key: v3-pkg-cache - # - restore_cache: - # key: v3-tree-{{ .Environment.CIRCLE_SHA1 }} - - checkout - - run: - name: tools - command: | - export PATH="$GOBIN:$PATH" - make get_tools - make get_dev_tools - - run: - name: dependencies - command: | - export PATH="$GOBIN:$PATH" - make get_vendor_deps - - run: mkdir -p $GOPATH/src/github.com/tendermint - - run: ln -sf /home/circleci/project $GOPATH/src/github.com/tendermint/tendermint - + - restore_cache: + key: v3-tree-{{ .Environment.CIRCLE_SHA1 }} - run: name: metalinter command: | @@ -128,22 +98,8 @@ jobs: at: /tmp/workspace - restore_cache: key: v3-pkg-cache - # - restore_cache: - # key: v3-tree-{{ .Environment.CIRCLE_SHA1 }} - - checkout - - run: - name: tools - command: | - export PATH="$GOBIN:$PATH" - make get_tools - - run: - name: dependencies - command: | - export PATH="$GOBIN:$PATH" - make get_vendor_deps - - run: mkdir -p $GOPATH/src/github.com/tendermint - - run: ln -sf /home/circleci/project $GOPATH/src/github.com/tendermint/tendermint - + - restore_cache: + key: v3-tree-{{ .Environment.CIRCLE_SHA1 }} - run: name: Run abci apps tests command: | @@ -159,22 +115,8 @@ jobs: at: /tmp/workspace - restore_cache: key: v3-pkg-cache - # - restore_cache: - # key: v3-tree-{{ .Environment.CIRCLE_SHA1 }} - - checkout - - run: - name: tools - command: | - export PATH="$GOBIN:$PATH" - make get_tools - - run: - name: dependencies - command: | - export PATH="$GOBIN:$PATH" - make get_vendor_deps - - run: mkdir -p $GOPATH/src/github.com/tendermint - - run: ln -sf /home/circleci/project $GOPATH/src/github.com/tendermint/tendermint - + - restore_cache: + key: v3-tree-{{ .Environment.CIRCLE_SHA1 }} - run: name: Run abci-cli tests command: | @@ -188,22 +130,8 @@ jobs: at: /tmp/workspace - restore_cache: key: v3-pkg-cache - # - restore_cache: - # key: v3-tree-{{ .Environment.CIRCLE_SHA1 }} - - checkout - - run: - name: tools - command: | - export PATH="$GOBIN:$PATH" - make get_tools - - run: - name: dependencies - command: | - export PATH="$GOBIN:$PATH" - make get_vendor_deps - - run: mkdir -p $GOPATH/src/github.com/tendermint - - run: ln -sf /home/circleci/project $GOPATH/src/github.com/tendermint/tendermint - + - restore_cache: + key: v3-tree-{{ .Environment.CIRCLE_SHA1 }} - run: sudo apt-get update && sudo apt-get install -y --no-install-recommends bsdmainutils - run: name: Run tests @@ -217,22 +145,8 @@ jobs: at: /tmp/workspace - restore_cache: key: v3-pkg-cache - # - restore_cache: - # key: v3-tree-{{ .Environment.CIRCLE_SHA1 }} - - checkout - - run: - name: tools - command: | - export PATH="$GOBIN:$PATH" - make get_tools - - run: - name: dependencies - command: | - export PATH="$GOBIN:$PATH" - make get_vendor_deps - - run: mkdir -p $GOPATH/src/github.com/tendermint - - run: ln -sf /home/circleci/project $GOPATH/src/github.com/tendermint/tendermint - + - restore_cache: + key: v3-tree-{{ .Environment.CIRCLE_SHA1 }} - run: mkdir -p /tmp/logs - run: name: Run tests @@ -256,22 +170,8 @@ jobs: at: /tmp/workspace - restore_cache: key: v3-pkg-cache - # - restore_cache: - # key: v3-tree-{{ .Environment.CIRCLE_SHA1 }} - - checkout - - run: - name: tools - command: | - export PATH="$GOBIN:$PATH" - make get_tools - - run: - name: dependencies - command: | - export PATH="$GOBIN:$PATH" - make get_vendor_deps - - run: mkdir -p $GOPATH/src/github.com/tendermint - - run: ln -sf /home/circleci/project $GOPATH/src/github.com/tendermint/tendermint - + - restore_cache: + key: v3-tree-{{ .Environment.CIRCLE_SHA1 }} - run: name: Run tests command: bash test/persist/test_failure_indices.sh @@ -317,22 +217,10 @@ jobs: steps: - attach_workspace: at: /tmp/workspace - # - restore_cache: - # key: v3-tree-{{ .Environment.CIRCLE_SHA1 }} - - checkout - - run: - name: tools - command: | - export PATH="$GOBIN:$PATH" - make get_tools - - run: - name: dependencies - command: | - export PATH="$GOBIN:$PATH" - make get_vendor_deps - - run: mkdir -p $GOPATH/src/github.com/tendermint - - run: ln -sf /home/circleci/project $GOPATH/src/github.com/tendermint/tendermint - + - restore_cache: + key: v3-pkg-cache + - restore_cache: + key: v3-tree-{{ .Environment.CIRCLE_SHA1 }} - run: name: gather command: | From da33dd04cc90f60f339afa8182f59294e67d151b Mon Sep 17 00:00:00 2001 From: Anton Kaliaev Date: Wed, 6 Feb 2019 15:00:55 +0400 Subject: [PATCH 250/267] switch to golangci-lint from gometalinter :speedboat: --- .circleci/config.yml | 2 +- .golangci.yml | 65 ++++++++++++++++++++++++++++++++++++++++++++ Makefile | 43 +++-------------------------- Vagrantfile | 2 +- libs/test.sh | 2 +- scripts/get_tools.sh | 21 ++++++++++---- 6 files changed, 88 insertions(+), 47 deletions(-) create mode 100644 .golangci.yml diff --git a/.circleci/config.yml b/.circleci/config.yml index 82679337..d2968094 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -83,7 +83,7 @@ jobs: command: | set -ex export PATH="$GOBIN:$PATH" - make metalinter + make lint - run: name: check_dep command: | diff --git a/.golangci.yml b/.golangci.yml new file mode 100644 index 00000000..ed2f6ab3 --- /dev/null +++ b/.golangci.yml @@ -0,0 +1,65 @@ +run: + deadline: 1m + +linters: + enable-all: true + disable: + - gocyclo + - golint + - maligned + - errcheck + - staticcheck + - dupl + - ineffassign + - interfacer + - unconvert + - goconst + - unparam + - nakedret + - lll + - gochecknoglobals + - govet + - gocritic + - gosec + - gochecknoinits + - scopelint + - stylecheck + - deadcode + - prealloc + - unused + - gosimple + +# linters-settings: +# govet: +# check-shadowing: true +# golint: +# min-confidence: 0 +# gocyclo: +# min-complexity: 10 +# maligned: +# suggest-new: true +# dupl: +# threshold: 100 +# goconst: +# min-len: 2 +# min-occurrences: 2 +# depguard: +# list-type: blacklist +# packages: +# # logging is allowed only by logutils.Log, logrus +# # is allowed to use only in logutils package +# - github.com/sirupsen/logrus +# misspell: +# locale: US +# lll: +# line-length: 140 +# goimports: +# local-prefixes: github.com/golangci/golangci-lint +# gocritic: +# enabled-tags: +# - performance +# - style +# - experimental +# disabled-checks: +# - wrapperFunc +# - commentFormatting # https://github.com/go-critic/go-critic/issues/755 diff --git a/Makefile b/Makefile index 8c0928d0..ac9700d6 100644 --- a/Makefile +++ b/Makefile @@ -1,7 +1,7 @@ GOTOOLS = \ github.com/mitchellh/gox \ github.com/golang/dep/cmd/dep \ - github.com/alecthomas/gometalinter \ + github.com/golangci/golangci-lint/cmd/golangci-lint \ github.com/gogo/protobuf/protoc-gen-gogo \ github.com/square/certstrap GOBIN?=${GOPATH}/bin @@ -11,8 +11,6 @@ INCLUDE = -I=. -I=${GOPATH}/src -I=${GOPATH}/src/github.com/gogo/protobuf/protob BUILD_TAGS?='tendermint' BUILD_FLAGS = -ldflags "-X github.com/tendermint/tendermint/version.GitCommit=`git rev-parse --short=8 HEAD`" -LINT_FLAGS = --exclude '.*\.pb\.go' --exclude 'vendor/*' --vendor --deadline=600s - all: check build test install check: check_tools get_vendor_deps @@ -82,10 +80,6 @@ get_tools: @echo "--> Installing tools" ./scripts/get_tools.sh -get_dev_tools: - @echo "--> Downloading linters (this may take awhile)" - $(GOPATH)/src/github.com/alecthomas/gometalinter/scripts/install.sh -b $(GOBIN) - update_tools: @echo "--> Updating tools" ./scripts/get_tools.sh @@ -246,38 +240,9 @@ cleanup_after_test_with_deadlock: fmt: @go fmt ./... -metalinter: +lint: @echo "--> Running linter" - @gometalinter $(LINT_FLAGS) --disable-all \ - --enable=vet \ - --enable=vetshadow \ - --enable=deadcode \ - --enable=varcheck \ - --enable=structcheck \ - --enable=misspell \ - --enable=safesql \ - --enable=gosec \ - --enable=goimports \ - --enable=gofmt \ - ./... - #--enable=gotype \ - #--enable=gotypex \ - #--enable=gocyclo \ - #--enable=golint \ - #--enable=maligned \ - #--enable=errcheck \ - #--enable=staticcheck \ - #--enable=dupl \ - #--enable=ineffassign \ - #--enable=interfacer \ - #--enable=unconvert \ - #--enable=goconst \ - #--enable=unparam \ - #--enable=nakedret \ - -metalinter_all: - @echo "--> Running linter (all)" - gometalinter $(LINT_FLAGS) --enable-all --disable=lll ./... + @golangci-lint run DESTINATION = ./index.html.md @@ -343,4 +308,4 @@ build-slate: # To avoid unintended conflicts with file names, always add to .PHONY # unless there is a reason not to. # https://www.gnu.org/software/make/manual/html_node/Phony-Targets.html -.PHONY: check build build_race build_abci dist install install_abci check_dep check_tools get_tools get_dev_tools update_tools get_vendor_deps draw_deps get_protoc protoc_abci protoc_libs gen_certs clean_certs grpc_dbserver test_cover test_apps test_persistence test_p2p test test_race test_integrations test_release test100 vagrant_test fmt rpc-docs build-linux localnet-start localnet-stop build-docker build-docker-localnode sentry-start sentry-config sentry-stop build-slate protoc_grpc protoc_all build_c install_c test_with_deadlock cleanup_after_test_with_deadlock metalinter metalinter_all +.PHONY: check build build_race build_abci dist install install_abci check_dep check_tools get_tools update_tools get_vendor_deps draw_deps get_protoc protoc_abci protoc_libs gen_certs clean_certs grpc_dbserver test_cover test_apps test_persistence test_p2p test test_race test_integrations test_release test100 vagrant_test fmt rpc-docs build-linux localnet-start localnet-stop build-docker build-docker-localnode sentry-start sentry-config sentry-stop build-slate protoc_grpc protoc_all build_c install_c test_with_deadlock cleanup_after_test_with_deadlock lint diff --git a/Vagrantfile b/Vagrantfile index f058d78e..320f3b1c 100644 --- a/Vagrantfile +++ b/Vagrantfile @@ -53,6 +53,6 @@ Vagrant.configure("2") do |config| # get all deps and tools, ready to install/test su - vagrant -c 'source /home/vagrant/.bash_profile' - su - vagrant -c 'cd /home/vagrant/go/src/github.com/tendermint/tendermint && make get_tools && make get_dev_tools && make get_vendor_deps' + su - vagrant -c 'cd /home/vagrant/go/src/github.com/tendermint/tendermint && make get_tools && make get_vendor_deps' SHELL end diff --git a/libs/test.sh b/libs/test.sh index ecf17fc4..64898b0d 100755 --- a/libs/test.sh +++ b/libs/test.sh @@ -2,7 +2,7 @@ set -e # run the linter -# make metalinter_test +# make lint # setup certs make gen_certs diff --git a/scripts/get_tools.sh b/scripts/get_tools.sh index 87e30a3d..dd956691 100755 --- a/scripts/get_tools.sh +++ b/scripts/get_tools.sh @@ -5,11 +5,14 @@ set -e # specific git hash. # # repos it installs: -# github.com/mitchellh/gox # github.com/golang/dep/cmd/dep -# gopkg.in/alecthomas/gometalinter.v2 # github.com/gogo/protobuf/protoc-gen-gogo # github.com/square/certstrap +# github.com/mitchellh/gox +# github.com/golangci/golangci-lint +# github.com/petermattis/goid +# github.com/sasha-s/go-deadlock +# goimports ## check if GOPATH is set if [ -z ${GOPATH+x} ]; then @@ -45,14 +48,22 @@ installFromGithub() { echo "" } -installFromGithub mitchellh/gox 51ed453898ca5579fea9ad1f08dff6b121d9f2e8 +######################## COMMON TOOLS ######################################## installFromGithub golang/dep 22125cfaa6ddc71e145b1535d4b7ee9744fefff2 cmd/dep -## gometalinter v3.0.0 -installFromGithub alecthomas/gometalinter df395bfa67c5d0630d936c0044cf07ff05086655 + +######################## DEVELOPER TOOLS ##################################### installFromGithub gogo/protobuf 61dbc136cf5d2f08d68a011382652244990a53a9 protoc-gen-gogo + installFromGithub square/certstrap e27060a3643e814151e65b9807b6b06d169580a7 +# used to build tm-monitor & tm-bench binaries +installFromGithub mitchellh/gox 51ed453898ca5579fea9ad1f08dff6b121d9f2e8 + +## golangci-lint v1.13.2 +installFromGithub golangci/golangci-lint 7b2421d55194c9dc385eff7720a037aa9244ca3c cmd/golangci-lint + ## make test_with_deadlock +## XXX: https://github.com/tendermint/tendermint/issues/3242 installFromGithub petermattis/goid b0b1615b78e5ee59739545bb38426383b2cda4c9 installFromGithub sasha-s/go-deadlock d68e2bc52ae3291765881b9056f2c1527f245f1e go get golang.org/x/tools/cmd/goimports From ffd3bf8448d5ca5fd36e1056a97586d7bbbf8fa4 Mon Sep 17 00:00:00 2001 From: Anton Kaliaev Date: Wed, 6 Feb 2019 15:16:38 +0400 Subject: [PATCH 251/267] remove or comment out unused code --- .golangci.yml | 2 - blockchain/pool.go | 32 +++---- consensus/common_test.go | 56 ++++++------ consensus/state_test.go | 4 - crypto/merkle/proof_test.go | 22 ++--- lite/proxy/query_test.go | 139 ++++++++++++++--------------- p2p/conn/secret_connection_test.go | 9 -- p2p/switch.go | 8 +- p2p/trust/metric_test.go | 72 +++++++-------- state/state_test.go | 4 - 10 files changed, 163 insertions(+), 185 deletions(-) diff --git a/.golangci.yml b/.golangci.yml index ed2f6ab3..6175fc90 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -26,8 +26,6 @@ linters: - stylecheck - deadcode - prealloc - - unused - - gosimple # linters-settings: # govet: diff --git a/blockchain/pool.go b/blockchain/pool.go index e6be3601..236bf7e0 100644 --- a/blockchain/pool.go +++ b/blockchain/pool.go @@ -363,23 +363,23 @@ func (pool *BlockPool) sendError(err error, peerID p2p.ID) { pool.errorsCh <- peerError{err, peerID} } -// unused by tendermint; left for debugging purposes -func (pool *BlockPool) debug() string { - pool.mtx.Lock() - defer pool.mtx.Unlock() +// for debugging purposes +// func (pool *BlockPool) debug() string { +// pool.mtx.Lock() +// defer pool.mtx.Unlock() - str := "" - nextHeight := pool.height + pool.requestersLen() - for h := pool.height; h < nextHeight; h++ { - if pool.requesters[h] == nil { - str += fmt.Sprintf("H(%v):X ", h) - } else { - str += fmt.Sprintf("H(%v):", h) - str += fmt.Sprintf("B?(%v) ", pool.requesters[h].block != nil) - } - } - return str -} +// str := "" +// nextHeight := pool.height + pool.requestersLen() +// for h := pool.height; h < nextHeight; h++ { +// if pool.requesters[h] == nil { +// str += fmt.Sprintf("H(%v):X ", h) +// } else { +// str += fmt.Sprintf("H(%v):", h) +// str += fmt.Sprintf("B?(%v) ", pool.requesters[h].block != nil) +// } +// } +// return str +// } //------------------------------------- diff --git a/consensus/common_test.go b/consensus/common_test.go index a975b2b6..4e3d60e1 100644 --- a/consensus/common_test.go +++ b/consensus/common_test.go @@ -378,35 +378,35 @@ func ensureNewEvent( } } -func ensureNewRoundStep(stepCh <-chan interface{}, height int64, round int) { - ensureNewEvent( - stepCh, - height, - round, - ensureTimeout, - "Timeout expired while waiting for NewStep event") -} +// func ensureNewRoundStep(stepCh <-chan interface{}, height int64, round int) { +// ensureNewEvent( +// stepCh, +// height, +// round, +// ensureTimeout, +// "Timeout expired while waiting for NewStep event") +// } -func ensureNewVote(voteCh <-chan interface{}, height int64, round int) { - select { - case <-time.After(ensureTimeout): - break - case v := <-voteCh: - edv, ok := v.(types.EventDataVote) - if !ok { - panic(fmt.Sprintf("expected a *types.Vote, "+ - "got %v. wrong subscription channel?", - reflect.TypeOf(v))) - } - vote := edv.Vote - if vote.Height != height { - panic(fmt.Sprintf("expected height %v, got %v", height, vote.Height)) - } - if vote.Round != round { - panic(fmt.Sprintf("expected round %v, got %v", round, vote.Round)) - } - } -} +// func ensureNewVote(voteCh <-chan interface{}, height int64, round int) { +// select { +// case <-time.After(ensureTimeout): +// break +// case v := <-voteCh: +// edv, ok := v.(types.EventDataVote) +// if !ok { +// panic(fmt.Sprintf("expected a *types.Vote, "+ +// "got %v. wrong subscription channel?", +// reflect.TypeOf(v))) +// } +// vote := edv.Vote +// if vote.Height != height { +// panic(fmt.Sprintf("expected height %v, got %v", height, vote.Height)) +// } +// if vote.Round != round { +// panic(fmt.Sprintf("expected round %v, got %v", round, vote.Round)) +// } +// } +// } func ensureNewRound(roundCh <-chan interface{}, height int64, round int) { select { diff --git a/consensus/state_test.go b/consensus/state_test.go index 10c04fbc..153f51e1 100644 --- a/consensus/state_test.go +++ b/consensus/state_test.go @@ -22,10 +22,6 @@ func init() { config = ResetConfig("consensus_state_test") } -func ensureProposeTimeout(timeoutPropose time.Duration) time.Duration { - return time.Duration(timeoutPropose.Nanoseconds()*2) * time.Nanosecond -} - /* ProposeSuite diff --git a/crypto/merkle/proof_test.go b/crypto/merkle/proof_test.go index 2a0bdccf..50415624 100644 --- a/crypto/merkle/proof_test.go +++ b/crypto/merkle/proof_test.go @@ -26,17 +26,17 @@ func NewDominoOp(key, input, output string) DominoOp { } } -func DominoOpDecoder(pop ProofOp) (ProofOperator, error) { - if pop.Type != ProofOpDomino { - panic("unexpected proof op type") - } - var op DominoOp // a bit strange as we'll discard this, but it works. - err := amino.UnmarshalBinaryLengthPrefixed(pop.Data, &op) - if err != nil { - return nil, cmn.ErrorWrap(err, "decoding ProofOp.Data into SimpleValueOp") - } - return NewDominoOp(string(pop.Key), op.Input, op.Output), nil -} +// func DominoOpDecoder(pop ProofOp) (ProofOperator, error) { +// if pop.Type != ProofOpDomino { +// panic("unexpected proof op type") +// } +// var op DominoOp // a bit strange as we'll discard this, but it works. +// err := amino.UnmarshalBinaryLengthPrefixed(pop.Data, &op) +// if err != nil { +// return nil, cmn.ErrorWrap(err, "decoding ProofOp.Data into SimpleValueOp") +// } +// return NewDominoOp(string(pop.Key), op.Input, op.Output), nil +// } func (dop DominoOp) ProofOp() ProofOp { bz := amino.MustMarshalBinaryLengthPrefixed(dop) diff --git a/lite/proxy/query_test.go b/lite/proxy/query_test.go index d8d45df3..707430b6 100644 --- a/lite/proxy/query_test.go +++ b/lite/proxy/query_test.go @@ -4,7 +4,6 @@ import ( "fmt" "os" "testing" - "time" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -21,7 +20,7 @@ import ( var node *nm.Node var chainID = "tendermint_test" // TODO use from config. -var waitForEventTimeout = 5 * time.Second +// var waitForEventTimeout = 5 * time.Second // TODO fix tests!! @@ -42,83 +41,83 @@ func kvstoreTx(k, v []byte) []byte { // TODO: enable it after general proof format has been adapted // in abci/examples/kvstore.go -func _TestAppProofs(t *testing.T) { - assert, require := assert.New(t), require.New(t) +// func TestAppProofs(t *testing.T) { +// assert, require := assert.New(t), require.New(t) - prt := defaultProofRuntime() - cl := client.NewLocal(node) - client.WaitForHeight(cl, 1, nil) +// prt := defaultProofRuntime() +// cl := client.NewLocal(node) +// client.WaitForHeight(cl, 1, nil) - // This sets up our trust on the node based on some past point. - source := certclient.NewProvider(chainID, cl) - seed, err := source.LatestFullCommit(chainID, 1, 1) - require.NoError(err, "%#v", err) - cert := lite.NewBaseVerifier(chainID, seed.Height(), seed.Validators) +// // This sets up our trust on the node based on some past point. +// source := certclient.NewProvider(chainID, cl) +// seed, err := source.LatestFullCommit(chainID, 1, 1) +// require.NoError(err, "%#v", err) +// cert := lite.NewBaseVerifier(chainID, seed.Height(), seed.Validators) - // Wait for tx confirmation. - done := make(chan int64) - go func() { - evtTyp := types.EventTx - _, err = client.WaitForOneEvent(cl, evtTyp, waitForEventTimeout) - require.Nil(err, "%#v", err) - close(done) - }() +// // Wait for tx confirmation. +// done := make(chan int64) +// go func() { +// evtTyp := types.EventTx +// _, err = client.WaitForOneEvent(cl, evtTyp, waitForEventTimeout) +// require.Nil(err, "%#v", err) +// close(done) +// }() - // Submit a transaction. - k := []byte("my-key") - v := []byte("my-value") - tx := kvstoreTx(k, v) - br, err := cl.BroadcastTxCommit(tx) - require.NoError(err, "%#v", err) - require.EqualValues(0, br.CheckTx.Code, "%#v", br.CheckTx) - require.EqualValues(0, br.DeliverTx.Code) - brh := br.Height +// // Submit a transaction. +// k := []byte("my-key") +// v := []byte("my-value") +// tx := kvstoreTx(k, v) +// br, err := cl.BroadcastTxCommit(tx) +// require.NoError(err, "%#v", err) +// require.EqualValues(0, br.CheckTx.Code, "%#v", br.CheckTx) +// require.EqualValues(0, br.DeliverTx.Code) +// brh := br.Height - // Fetch latest after tx commit. - <-done - latest, err := source.LatestFullCommit(chainID, 1, 1<<63-1) - require.NoError(err, "%#v", err) - rootHash := latest.SignedHeader.AppHash - if rootHash == nil { - // Fetch one block later, AppHash hasn't been committed yet. - // TODO find a way to avoid doing this. - client.WaitForHeight(cl, latest.SignedHeader.Height+1, nil) - latest, err = source.LatestFullCommit(chainID, latest.SignedHeader.Height+1, 1<<63-1) - require.NoError(err, "%#v", err) - rootHash = latest.SignedHeader.AppHash - } - require.NotNil(rootHash) +// // Fetch latest after tx commit. +// <-done +// latest, err := source.LatestFullCommit(chainID, 1, 1<<63-1) +// require.NoError(err, "%#v", err) +// rootHash := latest.SignedHeader.AppHash +// if rootHash == nil { +// // Fetch one block later, AppHash hasn't been committed yet. +// // TODO find a way to avoid doing this. +// client.WaitForHeight(cl, latest.SignedHeader.Height+1, nil) +// latest, err = source.LatestFullCommit(chainID, latest.SignedHeader.Height+1, 1<<63-1) +// require.NoError(err, "%#v", err) +// rootHash = latest.SignedHeader.AppHash +// } +// require.NotNil(rootHash) - // verify a query before the tx block has no data (and valid non-exist proof) - bs, height, proof, err := GetWithProof(prt, k, brh-1, cl, cert) - require.NoError(err, "%#v", err) - // require.NotNil(proof) - // TODO: Ensure that *some* keys will be there, ensuring that proof is nil, - // (currently there's a race condition) - // and ensure that proof proves absence of k. - require.Nil(bs) +// // verify a query before the tx block has no data (and valid non-exist proof) +// bs, height, proof, err := GetWithProof(prt, k, brh-1, cl, cert) +// require.NoError(err, "%#v", err) +// // require.NotNil(proof) +// // TODO: Ensure that *some* keys will be there, ensuring that proof is nil, +// // (currently there's a race condition) +// // and ensure that proof proves absence of k. +// require.Nil(bs) - // but given that block it is good - bs, height, proof, err = GetWithProof(prt, k, brh, cl, cert) - require.NoError(err, "%#v", err) - require.NotNil(proof) - require.Equal(height, brh) +// // but given that block it is good +// bs, height, proof, err = GetWithProof(prt, k, brh, cl, cert) +// require.NoError(err, "%#v", err) +// require.NotNil(proof) +// require.Equal(height, brh) - assert.EqualValues(v, bs) - err = prt.VerifyValue(proof, rootHash, string(k), bs) // XXX key encoding - assert.NoError(err, "%#v", err) +// assert.EqualValues(v, bs) +// err = prt.VerifyValue(proof, rootHash, string(k), bs) // XXX key encoding +// assert.NoError(err, "%#v", err) - // Test non-existing key. - missing := []byte("my-missing-key") - bs, _, proof, err = GetWithProof(prt, missing, 0, cl, cert) - require.NoError(err) - require.Nil(bs) - require.NotNil(proof) - err = prt.VerifyAbsence(proof, rootHash, string(missing)) // XXX VerifyAbsence(), keyencoding - assert.NoError(err, "%#v", err) - err = prt.VerifyAbsence(proof, rootHash, string(k)) // XXX VerifyAbsence(), keyencoding - assert.Error(err, "%#v", err) -} +// // Test non-existing key. +// missing := []byte("my-missing-key") +// bs, _, proof, err = GetWithProof(prt, missing, 0, cl, cert) +// require.NoError(err) +// require.Nil(bs) +// require.NotNil(proof) +// err = prt.VerifyAbsence(proof, rootHash, string(missing)) // XXX VerifyAbsence(), keyencoding +// assert.NoError(err, "%#v", err) +// err = prt.VerifyAbsence(proof, rootHash, string(k)) // XXX VerifyAbsence(), keyencoding +// assert.Error(err, "%#v", err) +// } func TestTxProofs(t *testing.T) { assert, require := assert.New(t), require.New(t) diff --git a/p2p/conn/secret_connection_test.go b/p2p/conn/secret_connection_test.go index 69d9c09f..6b285476 100644 --- a/p2p/conn/secret_connection_test.go +++ b/p2p/conn/secret_connection_test.go @@ -398,12 +398,3 @@ func BenchmarkSecretConnection(b *testing.B) { } //barSecConn.Close() race condition } - -func fingerprint(bz []byte) []byte { - const fbsize = 40 - if len(bz) < fbsize { - return bz - } else { - return bz[:fbsize] - } -} diff --git a/p2p/switch.go b/p2p/switch.go index dbd9c2a6..7d2e6c3f 100644 --- a/p2p/switch.go +++ b/p2p/switch.go @@ -480,14 +480,12 @@ func (sw *Switch) acceptRoutine() { metrics: sw.metrics, }) if err != nil { - switch err.(type) { + switch err := err.(type) { case ErrRejected: - rErr := err.(ErrRejected) - - if rErr.IsSelf() { + if err.IsSelf() { // Remove the given address from the address book and add to our addresses // to avoid dialing in the future. - addr := rErr.Addr() + addr := err.Addr() sw.addrBook.RemoveAddress(&addr) sw.addrBook.AddOurAddress(&addr) } diff --git a/p2p/trust/metric_test.go b/p2p/trust/metric_test.go index f690ce55..89327c0e 100644 --- a/p2p/trust/metric_test.go +++ b/p2p/trust/metric_test.go @@ -65,44 +65,44 @@ func TestTrustMetricCopyNilPointer(t *testing.T) { } // XXX: This test fails non-deterministically -func _TestTrustMetricStopPause(t *testing.T) { - // The TestTicker will provide manual control over - // the passing of time within the metric - tt := NewTestTicker() - tm := NewMetric() - tm.SetTicker(tt) - tm.Start() - // Allow some time intervals to pass and pause - tt.NextTick() - tt.NextTick() - tm.Pause() +// func _TestTrustMetricStopPause(t *testing.T) { +// // The TestTicker will provide manual control over +// // the passing of time within the metric +// tt := NewTestTicker() +// tm := NewMetric() +// tm.SetTicker(tt) +// tm.Start() +// // Allow some time intervals to pass and pause +// tt.NextTick() +// tt.NextTick() +// tm.Pause() - // could be 1 or 2 because Pause and NextTick race - first := tm.Copy().numIntervals +// // could be 1 or 2 because Pause and NextTick race +// first := tm.Copy().numIntervals - // Allow more time to pass and check the intervals are unchanged - tt.NextTick() - tt.NextTick() - assert.Equal(t, first, tm.Copy().numIntervals) +// // Allow more time to pass and check the intervals are unchanged +// tt.NextTick() +// tt.NextTick() +// assert.Equal(t, first, tm.Copy().numIntervals) - // Get the trust metric activated again - tm.GoodEvents(5) - // Allow some time intervals to pass and stop - tt.NextTick() - tt.NextTick() - tm.Stop() - tm.Wait() +// // Get the trust metric activated again +// tm.GoodEvents(5) +// // Allow some time intervals to pass and stop +// tt.NextTick() +// tt.NextTick() +// tm.Stop() +// tm.Wait() - second := tm.Copy().numIntervals - // Allow more intervals to pass while the metric is stopped - // and check that the number of intervals match - tm.NextTimeInterval() - tm.NextTimeInterval() - // XXX: fails non-deterministically: - // expected 5, got 6 - assert.Equal(t, second+2, tm.Copy().numIntervals) +// second := tm.Copy().numIntervals +// // Allow more intervals to pass while the metric is stopped +// // and check that the number of intervals match +// tm.NextTimeInterval() +// tm.NextTimeInterval() +// // XXX: fails non-deterministically: +// // expected 5, got 6 +// assert.Equal(t, second+2, tm.Copy().numIntervals) - if first > second { - t.Fatalf("numIntervals should always increase or stay the same over time") - } -} +// if first > second { +// t.Fatalf("numIntervals should always increase or stay the same over time") +// } +// } diff --git a/state/state_test.go b/state/state_test.go index 9ab0de13..904d7a10 100644 --- a/state/state_test.go +++ b/state/state_test.go @@ -938,10 +938,6 @@ func makeParams(blockBytes, blockGas, evidenceAge int64) types.ConsensusParams { } } -func pk() []byte { - return ed25519.GenPrivKey().PubKey().Bytes() -} - func TestApplyUpdates(t *testing.T) { initParams := makeParams(1, 2, 3) From 3c8156a55a7a13188c7c8fd6e54433b465212920 Mon Sep 17 00:00:00 2001 From: Anton Kaliaev Date: Wed, 6 Feb 2019 15:24:54 +0400 Subject: [PATCH 252/267] preallocating memory when we can --- .golangci.yml | 1 - crypto/merkle/proof.go | 2 +- libs/common/colors.go | 2 +- libs/events/events.go | 2 +- p2p/pex/pex_reactor.go | 3 +-- 5 files changed, 4 insertions(+), 6 deletions(-) diff --git a/.golangci.yml b/.golangci.yml index 6175fc90..ca86e6d5 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -25,7 +25,6 @@ linters: - scopelint - stylecheck - deadcode - - prealloc # linters-settings: # govet: diff --git a/crypto/merkle/proof.go b/crypto/merkle/proof.go index 8f8b460c..5e2a3ab1 100644 --- a/crypto/merkle/proof.go +++ b/crypto/merkle/proof.go @@ -98,7 +98,7 @@ func (prt *ProofRuntime) Decode(pop ProofOp) (ProofOperator, error) { } func (prt *ProofRuntime) DecodeProof(proof *Proof) (ProofOperators, error) { - var poz ProofOperators + poz := make(ProofOperators, 0, len(proof.Ops)) for _, pop := range proof.Ops { operator, err := prt.Decode(pop) if err != nil { diff --git a/libs/common/colors.go b/libs/common/colors.go index 4837f97b..89dda2c9 100644 --- a/libs/common/colors.go +++ b/libs/common/colors.go @@ -43,7 +43,7 @@ func treat(s string, color string) string { } func treatAll(color string, args ...interface{}) string { - var parts []string + parts := make([]string, 0, len(args)) for _, arg := range args { parts = append(parts, treat(fmt.Sprintf("%v", arg), color)) } diff --git a/libs/events/events.go b/libs/events/events.go index fb90bbea..34333a06 100644 --- a/libs/events/events.go +++ b/libs/events/events.go @@ -188,7 +188,7 @@ func (cell *eventCell) RemoveListener(listenerID string) int { func (cell *eventCell) FireEvent(data EventData) { cell.mtx.RLock() - var eventCallbacks []EventCallback + eventCallbacks := make([]EventCallback, 0, len(cell.listeners)) for _, cb := range cell.listeners { eventCallbacks = append(eventCallbacks, cb) } diff --git a/p2p/pex/pex_reactor.go b/p2p/pex/pex_reactor.go index 0b043ca8..01d1d8db 100644 --- a/p2p/pex/pex_reactor.go +++ b/p2p/pex/pex_reactor.go @@ -616,10 +616,9 @@ func (of oldestFirst) Less(i, j int) bool { return of[i].LastAttempt.Before(of[j // getPeersToCrawl returns addresses of potential peers that we wish to validate. // NOTE: The status information is ordered as described above. func (r *PEXReactor) getPeersToCrawl() []crawlPeerInfo { - var of oldestFirst - // TODO: be more selective addrs := r.book.ListOfKnownAddresses() + of := make(oldestFirst, 0, len(addrs)) for _, addr := range addrs { if len(addr.ID()) == 0 { continue // dont use peers without id From 23314daee4c1d3c2cb85c67f1debbdf2d5e9bd86 Mon Sep 17 00:00:00 2001 From: Anton Kaliaev Date: Wed, 6 Feb 2019 15:38:02 +0400 Subject: [PATCH 253/267] comment out clist tests w/ depend on runtime.SetFinalizer --- .golangci.yml | 1 - libs/clist/clist_test.go | 162 +++++++++++++++++++-------------------- 2 files changed, 80 insertions(+), 83 deletions(-) diff --git a/.golangci.yml b/.golangci.yml index ca86e6d5..4cae2f76 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -24,7 +24,6 @@ linters: - gochecknoinits - scopelint - stylecheck - - deadcode # linters-settings: # govet: diff --git a/libs/clist/clist_test.go b/libs/clist/clist_test.go index 4ded6177..edf01375 100644 --- a/libs/clist/clist_test.go +++ b/libs/clist/clist_test.go @@ -2,8 +2,6 @@ package clist import ( "fmt" - "runtime" - "sync/atomic" "testing" "time" @@ -65,100 +63,100 @@ func TestSmall(t *testing.T) { } -/* -This test is quite hacky because it relies on SetFinalizer -which isn't guaranteed to run at all. -*/ -// nolint: megacheck -func _TestGCFifo(t *testing.T) { +// This test is quite hacky because it relies on SetFinalizer +// which isn't guaranteed to run at all. +//func TestGCFifo(t *testing.T) { +// if runtime.GOARCH != "amd64" { +// t.Skipf("Skipping on non-amd64 machine") +// } - const numElements = 1000000 - l := New() - gcCount := new(uint64) +// const numElements = 1000000 +// l := New() +// gcCount := new(uint64) - // SetFinalizer doesn't work well with circular structures, - // so we construct a trivial non-circular structure to - // track. - type value struct { - Int int - } - done := make(chan struct{}) +// // SetFinalizer doesn't work well with circular structures, +// // so we construct a trivial non-circular structure to +// // track. +// type value struct { +// Int int +// } +// done := make(chan struct{}) - for i := 0; i < numElements; i++ { - v := new(value) - v.Int = i - l.PushBack(v) - runtime.SetFinalizer(v, func(v *value) { - atomic.AddUint64(gcCount, 1) - }) - } +// for i := 0; i < numElements; i++ { +// v := new(value) +// v.Int = i +// l.PushBack(v) +// runtime.SetFinalizer(v, func(v *value) { +// atomic.AddUint64(gcCount, 1) +// }) +// } - for el := l.Front(); el != nil; { - l.Remove(el) - //oldEl := el - el = el.Next() - //oldEl.DetachPrev() - //oldEl.DetachNext() - } +// for el := l.Front(); el != nil; { +// l.Remove(el) +// //oldEl := el +// el = el.Next() +// //oldEl.DetachPrev() +// //oldEl.DetachNext() +// } - runtime.GC() - time.Sleep(time.Second * 3) - runtime.GC() - time.Sleep(time.Second * 3) - _ = done +// runtime.GC() +// time.Sleep(time.Second * 3) +// runtime.GC() +// time.Sleep(time.Second * 3) +// _ = done - if *gcCount != numElements { - t.Errorf("Expected gcCount to be %v, got %v", numElements, - *gcCount) - } -} +// if *gcCount != numElements { +// t.Errorf("Expected gcCount to be %v, got %v", numElements, +// *gcCount) +// } +//} -/* -This test is quite hacky because it relies on SetFinalizer -which isn't guaranteed to run at all. -*/ -// nolint: megacheck -func _TestGCRandom(t *testing.T) { +// This test is quite hacky because it relies on SetFinalizer +// which isn't guaranteed to run at all. +// func TestGCRandom(t *testing.T) { +// if runtime.GOARCH != "amd64" { +// t.Skipf("Skipping on non-amd64 machine") +// } - const numElements = 1000000 - l := New() - gcCount := 0 +// const numElements = 1000000 +// l := New() +// gcCount := 0 - // SetFinalizer doesn't work well with circular structures, - // so we construct a trivial non-circular structure to - // track. - type value struct { - Int int - } +// // SetFinalizer doesn't work well with circular structures, +// // so we construct a trivial non-circular structure to +// // track. +// type value struct { +// Int int +// } - for i := 0; i < numElements; i++ { - v := new(value) - v.Int = i - l.PushBack(v) - runtime.SetFinalizer(v, func(v *value) { - gcCount++ - }) - } +// for i := 0; i < numElements; i++ { +// v := new(value) +// v.Int = i +// l.PushBack(v) +// runtime.SetFinalizer(v, func(v *value) { +// gcCount++ +// }) +// } - els := make([]*CElement, 0, numElements) - for el := l.Front(); el != nil; el = el.Next() { - els = append(els, el) - } +// els := make([]*CElement, 0, numElements) +// for el := l.Front(); el != nil; el = el.Next() { +// els = append(els, el) +// } - for _, i := range cmn.RandPerm(numElements) { - el := els[i] - l.Remove(el) - _ = el.Next() - } +// for _, i := range cmn.RandPerm(numElements) { +// el := els[i] +// l.Remove(el) +// _ = el.Next() +// } - runtime.GC() - time.Sleep(time.Second * 3) +// runtime.GC() +// time.Sleep(time.Second * 3) - if gcCount != numElements { - t.Errorf("Expected gcCount to be %v, got %v", numElements, - gcCount) - } -} +// if gcCount != numElements { +// t.Errorf("Expected gcCount to be %v, got %v", numElements, +// gcCount) +// } +// } func TestScanRightDeleteRandom(t *testing.T) { From 6941d1bb35a04a1d32a027af38d85a0d78374063 Mon Sep 17 00:00:00 2001 From: Anton Kaliaev Date: Wed, 6 Feb 2019 18:20:10 +0400 Subject: [PATCH 254/267] use nolint label instead of commenting --- blockchain/pool.go | 31 +++---- consensus/common_test.go | 30 ------- crypto/merkle/proof_test.go | 23 +++--- libs/clist/clist_test.go | 156 ++++++++++++++++++------------------ lite/proxy/query_test.go | 141 ++++++++++++++++---------------- p2p/trust/metric_test.go | 73 ++++++++--------- 6 files changed, 217 insertions(+), 237 deletions(-) diff --git a/blockchain/pool.go b/blockchain/pool.go index 236bf7e0..804a4325 100644 --- a/blockchain/pool.go +++ b/blockchain/pool.go @@ -364,22 +364,23 @@ func (pool *BlockPool) sendError(err error, peerID p2p.ID) { } // for debugging purposes -// func (pool *BlockPool) debug() string { -// pool.mtx.Lock() -// defer pool.mtx.Unlock() +//nolint:unused +func (pool *BlockPool) debug() string { + pool.mtx.Lock() + defer pool.mtx.Unlock() -// str := "" -// nextHeight := pool.height + pool.requestersLen() -// for h := pool.height; h < nextHeight; h++ { -// if pool.requesters[h] == nil { -// str += fmt.Sprintf("H(%v):X ", h) -// } else { -// str += fmt.Sprintf("H(%v):", h) -// str += fmt.Sprintf("B?(%v) ", pool.requesters[h].block != nil) -// } -// } -// return str -// } + str := "" + nextHeight := pool.height + pool.requestersLen() + for h := pool.height; h < nextHeight; h++ { + if pool.requesters[h] == nil { + str += fmt.Sprintf("H(%v):X ", h) + } else { + str += fmt.Sprintf("H(%v):", h) + str += fmt.Sprintf("B?(%v) ", pool.requesters[h].block != nil) + } + } + return str +} //------------------------------------- diff --git a/consensus/common_test.go b/consensus/common_test.go index 4e3d60e1..e6e64c25 100644 --- a/consensus/common_test.go +++ b/consensus/common_test.go @@ -378,36 +378,6 @@ func ensureNewEvent( } } -// func ensureNewRoundStep(stepCh <-chan interface{}, height int64, round int) { -// ensureNewEvent( -// stepCh, -// height, -// round, -// ensureTimeout, -// "Timeout expired while waiting for NewStep event") -// } - -// func ensureNewVote(voteCh <-chan interface{}, height int64, round int) { -// select { -// case <-time.After(ensureTimeout): -// break -// case v := <-voteCh: -// edv, ok := v.(types.EventDataVote) -// if !ok { -// panic(fmt.Sprintf("expected a *types.Vote, "+ -// "got %v. wrong subscription channel?", -// reflect.TypeOf(v))) -// } -// vote := edv.Vote -// if vote.Height != height { -// panic(fmt.Sprintf("expected height %v, got %v", height, vote.Height)) -// } -// if vote.Round != round { -// panic(fmt.Sprintf("expected round %v, got %v", round, vote.Round)) -// } -// } -// } - func ensureNewRound(roundCh <-chan interface{}, height int64, round int) { select { case <-time.After(ensureTimeout): diff --git a/crypto/merkle/proof_test.go b/crypto/merkle/proof_test.go index 50415624..4de3246f 100644 --- a/crypto/merkle/proof_test.go +++ b/crypto/merkle/proof_test.go @@ -26,17 +26,18 @@ func NewDominoOp(key, input, output string) DominoOp { } } -// func DominoOpDecoder(pop ProofOp) (ProofOperator, error) { -// if pop.Type != ProofOpDomino { -// panic("unexpected proof op type") -// } -// var op DominoOp // a bit strange as we'll discard this, but it works. -// err := amino.UnmarshalBinaryLengthPrefixed(pop.Data, &op) -// if err != nil { -// return nil, cmn.ErrorWrap(err, "decoding ProofOp.Data into SimpleValueOp") -// } -// return NewDominoOp(string(pop.Key), op.Input, op.Output), nil -// } +//nolint:unused +func DominoOpDecoder(pop ProofOp) (ProofOperator, error) { + if pop.Type != ProofOpDomino { + panic("unexpected proof op type") + } + var op DominoOp // a bit strange as we'll discard this, but it works. + err := amino.UnmarshalBinaryLengthPrefixed(pop.Data, &op) + if err != nil { + return nil, cmn.ErrorWrap(err, "decoding ProofOp.Data into SimpleValueOp") + } + return NewDominoOp(string(pop.Key), op.Input, op.Output), nil +} func (dop DominoOp) ProofOp() ProofOp { bz := amino.MustMarshalBinaryLengthPrefixed(dop) diff --git a/libs/clist/clist_test.go b/libs/clist/clist_test.go index edf01375..7fb7db4d 100644 --- a/libs/clist/clist_test.go +++ b/libs/clist/clist_test.go @@ -2,6 +2,8 @@ package clist import ( "fmt" + "runtime" + "sync/atomic" "testing" "time" @@ -65,98 +67,100 @@ func TestSmall(t *testing.T) { // This test is quite hacky because it relies on SetFinalizer // which isn't guaranteed to run at all. -//func TestGCFifo(t *testing.T) { -// if runtime.GOARCH != "amd64" { -// t.Skipf("Skipping on non-amd64 machine") -// } +//nolint:unused,deadcode +func _TestGCFifo(t *testing.T) { + if runtime.GOARCH != "amd64" { + t.Skipf("Skipping on non-amd64 machine") + } -// const numElements = 1000000 -// l := New() -// gcCount := new(uint64) + const numElements = 1000000 + l := New() + gcCount := new(uint64) -// // SetFinalizer doesn't work well with circular structures, -// // so we construct a trivial non-circular structure to -// // track. -// type value struct { -// Int int -// } -// done := make(chan struct{}) + // SetFinalizer doesn't work well with circular structures, + // so we construct a trivial non-circular structure to + // track. + type value struct { + Int int + } + done := make(chan struct{}) -// for i := 0; i < numElements; i++ { -// v := new(value) -// v.Int = i -// l.PushBack(v) -// runtime.SetFinalizer(v, func(v *value) { -// atomic.AddUint64(gcCount, 1) -// }) -// } + for i := 0; i < numElements; i++ { + v := new(value) + v.Int = i + l.PushBack(v) + runtime.SetFinalizer(v, func(v *value) { + atomic.AddUint64(gcCount, 1) + }) + } -// for el := l.Front(); el != nil; { -// l.Remove(el) -// //oldEl := el -// el = el.Next() -// //oldEl.DetachPrev() -// //oldEl.DetachNext() -// } + for el := l.Front(); el != nil; { + l.Remove(el) + //oldEl := el + el = el.Next() + //oldEl.DetachPrev() + //oldEl.DetachNext() + } -// runtime.GC() -// time.Sleep(time.Second * 3) -// runtime.GC() -// time.Sleep(time.Second * 3) -// _ = done + runtime.GC() + time.Sleep(time.Second * 3) + runtime.GC() + time.Sleep(time.Second * 3) + _ = done -// if *gcCount != numElements { -// t.Errorf("Expected gcCount to be %v, got %v", numElements, -// *gcCount) -// } -//} + if *gcCount != numElements { + t.Errorf("Expected gcCount to be %v, got %v", numElements, + *gcCount) + } +} // This test is quite hacky because it relies on SetFinalizer // which isn't guaranteed to run at all. -// func TestGCRandom(t *testing.T) { -// if runtime.GOARCH != "amd64" { -// t.Skipf("Skipping on non-amd64 machine") -// } +//nolint:unused,deadcode +func TestGCRandom(t *testing.T) { + if runtime.GOARCH != "amd64" { + t.Skipf("Skipping on non-amd64 machine") + } -// const numElements = 1000000 -// l := New() -// gcCount := 0 + const numElements = 1000000 + l := New() + gcCount := 0 -// // SetFinalizer doesn't work well with circular structures, -// // so we construct a trivial non-circular structure to -// // track. -// type value struct { -// Int int -// } + // SetFinalizer doesn't work well with circular structures, + // so we construct a trivial non-circular structure to + // track. + type value struct { + Int int + } -// for i := 0; i < numElements; i++ { -// v := new(value) -// v.Int = i -// l.PushBack(v) -// runtime.SetFinalizer(v, func(v *value) { -// gcCount++ -// }) -// } + for i := 0; i < numElements; i++ { + v := new(value) + v.Int = i + l.PushBack(v) + runtime.SetFinalizer(v, func(v *value) { + gcCount++ + }) + } -// els := make([]*CElement, 0, numElements) -// for el := l.Front(); el != nil; el = el.Next() { -// els = append(els, el) -// } + els := make([]*CElement, 0, numElements) + for el := l.Front(); el != nil; el = el.Next() { + els = append(els, el) + } -// for _, i := range cmn.RandPerm(numElements) { -// el := els[i] -// l.Remove(el) -// _ = el.Next() -// } + for _, i := range cmn.RandPerm(numElements) { + el := els[i] + l.Remove(el) + _ = el.Next() + } -// runtime.GC() -// time.Sleep(time.Second * 3) + runtime.GC() + time.Sleep(time.Second * 3) -// if gcCount != numElements { -// t.Errorf("Expected gcCount to be %v, got %v", numElements, -// gcCount) -// } -// } + if gcCount != numElements { + t.Errorf("Expected gcCount to be %v, got %v", numElements, + gcCount) + } +} func TestScanRightDeleteRandom(t *testing.T) { diff --git a/lite/proxy/query_test.go b/lite/proxy/query_test.go index 707430b6..9547f771 100644 --- a/lite/proxy/query_test.go +++ b/lite/proxy/query_test.go @@ -4,6 +4,7 @@ import ( "fmt" "os" "testing" + "time" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -20,7 +21,8 @@ import ( var node *nm.Node var chainID = "tendermint_test" // TODO use from config. -// var waitForEventTimeout = 5 * time.Second +//nolint:unused +var waitForEventTimeout = 5 * time.Second // TODO fix tests!! @@ -41,83 +43,84 @@ func kvstoreTx(k, v []byte) []byte { // TODO: enable it after general proof format has been adapted // in abci/examples/kvstore.go -// func TestAppProofs(t *testing.T) { -// assert, require := assert.New(t), require.New(t) +//nolint:unused,deadcode +func _TestAppProofs(t *testing.T) { + assert, require := assert.New(t), require.New(t) -// prt := defaultProofRuntime() -// cl := client.NewLocal(node) -// client.WaitForHeight(cl, 1, nil) + prt := defaultProofRuntime() + cl := client.NewLocal(node) + client.WaitForHeight(cl, 1, nil) -// // This sets up our trust on the node based on some past point. -// source := certclient.NewProvider(chainID, cl) -// seed, err := source.LatestFullCommit(chainID, 1, 1) -// require.NoError(err, "%#v", err) -// cert := lite.NewBaseVerifier(chainID, seed.Height(), seed.Validators) + // This sets up our trust on the node based on some past point. + source := certclient.NewProvider(chainID, cl) + seed, err := source.LatestFullCommit(chainID, 1, 1) + require.NoError(err, "%#v", err) + cert := lite.NewBaseVerifier(chainID, seed.Height(), seed.Validators) -// // Wait for tx confirmation. -// done := make(chan int64) -// go func() { -// evtTyp := types.EventTx -// _, err = client.WaitForOneEvent(cl, evtTyp, waitForEventTimeout) -// require.Nil(err, "%#v", err) -// close(done) -// }() + // Wait for tx confirmation. + done := make(chan int64) + go func() { + evtTyp := types.EventTx + _, err = client.WaitForOneEvent(cl, evtTyp, waitForEventTimeout) + require.Nil(err, "%#v", err) + close(done) + }() -// // Submit a transaction. -// k := []byte("my-key") -// v := []byte("my-value") -// tx := kvstoreTx(k, v) -// br, err := cl.BroadcastTxCommit(tx) -// require.NoError(err, "%#v", err) -// require.EqualValues(0, br.CheckTx.Code, "%#v", br.CheckTx) -// require.EqualValues(0, br.DeliverTx.Code) -// brh := br.Height + // Submit a transaction. + k := []byte("my-key") + v := []byte("my-value") + tx := kvstoreTx(k, v) + br, err := cl.BroadcastTxCommit(tx) + require.NoError(err, "%#v", err) + require.EqualValues(0, br.CheckTx.Code, "%#v", br.CheckTx) + require.EqualValues(0, br.DeliverTx.Code) + brh := br.Height -// // Fetch latest after tx commit. -// <-done -// latest, err := source.LatestFullCommit(chainID, 1, 1<<63-1) -// require.NoError(err, "%#v", err) -// rootHash := latest.SignedHeader.AppHash -// if rootHash == nil { -// // Fetch one block later, AppHash hasn't been committed yet. -// // TODO find a way to avoid doing this. -// client.WaitForHeight(cl, latest.SignedHeader.Height+1, nil) -// latest, err = source.LatestFullCommit(chainID, latest.SignedHeader.Height+1, 1<<63-1) -// require.NoError(err, "%#v", err) -// rootHash = latest.SignedHeader.AppHash -// } -// require.NotNil(rootHash) + // Fetch latest after tx commit. + <-done + latest, err := source.LatestFullCommit(chainID, 1, 1<<63-1) + require.NoError(err, "%#v", err) + rootHash := latest.SignedHeader.AppHash + if rootHash == nil { + // Fetch one block later, AppHash hasn't been committed yet. + // TODO find a way to avoid doing this. + client.WaitForHeight(cl, latest.SignedHeader.Height+1, nil) + latest, err = source.LatestFullCommit(chainID, latest.SignedHeader.Height+1, 1<<63-1) + require.NoError(err, "%#v", err) + rootHash = latest.SignedHeader.AppHash + } + require.NotNil(rootHash) -// // verify a query before the tx block has no data (and valid non-exist proof) -// bs, height, proof, err := GetWithProof(prt, k, brh-1, cl, cert) -// require.NoError(err, "%#v", err) -// // require.NotNil(proof) -// // TODO: Ensure that *some* keys will be there, ensuring that proof is nil, -// // (currently there's a race condition) -// // and ensure that proof proves absence of k. -// require.Nil(bs) + // verify a query before the tx block has no data (and valid non-exist proof) + bs, height, proof, err := GetWithProof(prt, k, brh-1, cl, cert) + require.NoError(err, "%#v", err) + // require.NotNil(proof) + // TODO: Ensure that *some* keys will be there, ensuring that proof is nil, + // (currently there's a race condition) + // and ensure that proof proves absence of k. + require.Nil(bs) -// // but given that block it is good -// bs, height, proof, err = GetWithProof(prt, k, brh, cl, cert) -// require.NoError(err, "%#v", err) -// require.NotNil(proof) -// require.Equal(height, brh) + // but given that block it is good + bs, height, proof, err = GetWithProof(prt, k, brh, cl, cert) + require.NoError(err, "%#v", err) + require.NotNil(proof) + require.Equal(height, brh) -// assert.EqualValues(v, bs) -// err = prt.VerifyValue(proof, rootHash, string(k), bs) // XXX key encoding -// assert.NoError(err, "%#v", err) + assert.EqualValues(v, bs) + err = prt.VerifyValue(proof, rootHash, string(k), bs) // XXX key encoding + assert.NoError(err, "%#v", err) -// // Test non-existing key. -// missing := []byte("my-missing-key") -// bs, _, proof, err = GetWithProof(prt, missing, 0, cl, cert) -// require.NoError(err) -// require.Nil(bs) -// require.NotNil(proof) -// err = prt.VerifyAbsence(proof, rootHash, string(missing)) // XXX VerifyAbsence(), keyencoding -// assert.NoError(err, "%#v", err) -// err = prt.VerifyAbsence(proof, rootHash, string(k)) // XXX VerifyAbsence(), keyencoding -// assert.Error(err, "%#v", err) -// } + // Test non-existing key. + missing := []byte("my-missing-key") + bs, _, proof, err = GetWithProof(prt, missing, 0, cl, cert) + require.NoError(err) + require.Nil(bs) + require.NotNil(proof) + err = prt.VerifyAbsence(proof, rootHash, string(missing)) // XXX VerifyAbsence(), keyencoding + assert.NoError(err, "%#v", err) + err = prt.VerifyAbsence(proof, rootHash, string(k)) // XXX VerifyAbsence(), keyencoding + assert.Error(err, "%#v", err) +} func TestTxProofs(t *testing.T) { assert, require := assert.New(t), require.New(t) diff --git a/p2p/trust/metric_test.go b/p2p/trust/metric_test.go index 89327c0e..0273dad6 100644 --- a/p2p/trust/metric_test.go +++ b/p2p/trust/metric_test.go @@ -65,44 +65,45 @@ func TestTrustMetricCopyNilPointer(t *testing.T) { } // XXX: This test fails non-deterministically -// func _TestTrustMetricStopPause(t *testing.T) { -// // The TestTicker will provide manual control over -// // the passing of time within the metric -// tt := NewTestTicker() -// tm := NewMetric() -// tm.SetTicker(tt) -// tm.Start() -// // Allow some time intervals to pass and pause -// tt.NextTick() -// tt.NextTick() -// tm.Pause() +//nolint:unused,deadcode +func _TestTrustMetricStopPause(t *testing.T) { + // The TestTicker will provide manual control over + // the passing of time within the metric + tt := NewTestTicker() + tm := NewMetric() + tm.SetTicker(tt) + tm.Start() + // Allow some time intervals to pass and pause + tt.NextTick() + tt.NextTick() + tm.Pause() -// // could be 1 or 2 because Pause and NextTick race -// first := tm.Copy().numIntervals + // could be 1 or 2 because Pause and NextTick race + first := tm.Copy().numIntervals -// // Allow more time to pass and check the intervals are unchanged -// tt.NextTick() -// tt.NextTick() -// assert.Equal(t, first, tm.Copy().numIntervals) + // Allow more time to pass and check the intervals are unchanged + tt.NextTick() + tt.NextTick() + assert.Equal(t, first, tm.Copy().numIntervals) -// // Get the trust metric activated again -// tm.GoodEvents(5) -// // Allow some time intervals to pass and stop -// tt.NextTick() -// tt.NextTick() -// tm.Stop() -// tm.Wait() + // Get the trust metric activated again + tm.GoodEvents(5) + // Allow some time intervals to pass and stop + tt.NextTick() + tt.NextTick() + tm.Stop() + tm.Wait() -// second := tm.Copy().numIntervals -// // Allow more intervals to pass while the metric is stopped -// // and check that the number of intervals match -// tm.NextTimeInterval() -// tm.NextTimeInterval() -// // XXX: fails non-deterministically: -// // expected 5, got 6 -// assert.Equal(t, second+2, tm.Copy().numIntervals) + second := tm.Copy().numIntervals + // Allow more intervals to pass while the metric is stopped + // and check that the number of intervals match + tm.NextTimeInterval() + tm.NextTimeInterval() + // XXX: fails non-deterministically: + // expected 5, got 6 + assert.Equal(t, second+2, tm.Copy().numIntervals) -// if first > second { -// t.Fatalf("numIntervals should always increase or stay the same over time") -// } -// } + if first > second { + t.Fatalf("numIntervals should always increase or stay the same over time") + } +} From 1a35895ac80caa50418ed1172ec80e71a0a6b692 Mon Sep 17 00:00:00 2001 From: Anton Kaliaev Date: Wed, 6 Feb 2019 18:23:25 +0400 Subject: [PATCH 255/267] remove gotype linter WARN [runner/nolint] Found unknown linters in //nolint directives: gotype --- consensus/byzantine_test.go | 3 +-- consensus/state.go | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/consensus/byzantine_test.go b/consensus/byzantine_test.go index 6f46c04d..ba69d0cc 100644 --- a/consensus/byzantine_test.go +++ b/consensus/byzantine_test.go @@ -76,8 +76,7 @@ func TestByzantine(t *testing.T) { conR.SetLogger(logger.With("validator", i)) conR.SetEventBus(eventBus) - var conRI p2p.Reactor // nolint: gotype, gosimple - conRI = conR + var conRI p2p.Reactor = conR // make first val byzantine if i == 0 { diff --git a/consensus/state.go b/consensus/state.go index c8185d46..74165801 100644 --- a/consensus/state.go +++ b/consensus/state.go @@ -454,7 +454,7 @@ func (cs *ConsensusState) updateRoundStep(round int, step cstypes.RoundStepType) // enterNewRound(height, 0) at cs.StartTime. func (cs *ConsensusState) scheduleRound0(rs *cstypes.RoundState) { //cs.Logger.Info("scheduleRound0", "now", tmtime.Now(), "startTime", cs.StartTime) - sleepDuration := rs.StartTime.Sub(tmtime.Now()) // nolint: gotype, gosimple + sleepDuration := rs.StartTime.Sub(tmtime.Now()) cs.scheduleTimeout(sleepDuration, rs.Height, 0, cstypes.RoundStepNewHeight) } From 1386707ceb922f8cd390105c86c6a598c044f748 Mon Sep 17 00:00:00 2001 From: Anton Kaliaev Date: Wed, 6 Feb 2019 18:36:54 +0400 Subject: [PATCH 256/267] rename TestGCRandom to _TestGCRandom --- libs/clist/clist_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/clist/clist_test.go b/libs/clist/clist_test.go index 7fb7db4d..13aca357 100644 --- a/libs/clist/clist_test.go +++ b/libs/clist/clist_test.go @@ -117,7 +117,7 @@ func _TestGCFifo(t *testing.T) { // This test is quite hacky because it relies on SetFinalizer // which isn't guaranteed to run at all. //nolint:unused,deadcode -func TestGCRandom(t *testing.T) { +func _TestGCRandom(t *testing.T) { if runtime.GOARCH != "amd64" { t.Skipf("Skipping on non-amd64 machine") } From 4429826229a1b036c6396d4faf4f60ed9a0065ab Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Wed, 6 Feb 2019 10:14:03 -0500 Subject: [PATCH 257/267] cmn: GetFreePort (#3255) --- libs/common/net.go | 17 +++++++++++++++++ p2p/test_util.go | 22 ++++++---------------- rpc/test/helpers.go | 15 +++++++++------ 3 files changed, 32 insertions(+), 22 deletions(-) diff --git a/libs/common/net.go b/libs/common/net.go index bdbe38f7..c7fff4cc 100644 --- a/libs/common/net.go +++ b/libs/common/net.go @@ -24,3 +24,20 @@ func ProtocolAndAddress(listenAddr string) (string, string) { } return protocol, address } + +// GetFreePort gets a free port from the operating system. +// Ripped from https://github.com/phayes/freeport. +// BSD-licensed. +func GetFreePort() (int, error) { + addr, err := net.ResolveTCPAddr("tcp", "localhost:0") + if err != nil { + return 0, err + } + + l, err := net.ListenTCP("tcp", addr) + if err != nil { + return 0, err + } + defer l.Close() + return l.Addr().(*net.TCPAddr).Port, nil +} diff --git a/p2p/test_util.go b/p2p/test_util.go index 04629fca..aad32ef8 100644 --- a/p2p/test_util.go +++ b/p2p/test_util.go @@ -247,35 +247,25 @@ func testNodeInfo(id ID, name string) NodeInfo { } func testNodeInfoWithNetwork(id ID, name, network string) NodeInfo { - port, err := getFreePort() - if err != nil { - panic(err) - } return DefaultNodeInfo{ ProtocolVersion: defaultProtocolVersion, ID_: id, - ListenAddr: fmt.Sprintf("127.0.0.1:%d", port), + ListenAddr: fmt.Sprintf("127.0.0.1:%d", getFreePort()), Network: network, Version: "1.2.3-rc0-deadbeef", Channels: []byte{testCh}, Moniker: name, Other: DefaultNodeInfoOther{ TxIndex: "on", - RPCAddress: fmt.Sprintf("127.0.0.1:%d", port), + RPCAddress: fmt.Sprintf("127.0.0.1:%d", getFreePort()), }, } } -func getFreePort() (int, error) { - addr, err := net.ResolveTCPAddr("tcp", "localhost:0") +func getFreePort() int { + port, err := cmn.GetFreePort() if err != nil { - return 0, err + panic(err) } - - l, err := net.ListenTCP("tcp", addr) - if err != nil { - return 0, err - } - defer l.Close() - return l.Addr().(*net.TCPAddr).Port, nil + return port } diff --git a/rpc/test/helpers.go b/rpc/test/helpers.go index b89c0a17..67439b1d 100644 --- a/rpc/test/helpers.go +++ b/rpc/test/helpers.go @@ -11,9 +11,9 @@ import ( "github.com/tendermint/tendermint/libs/log" abci "github.com/tendermint/tendermint/abci/types" - cmn "github.com/tendermint/tendermint/libs/common" cfg "github.com/tendermint/tendermint/config" + cmn "github.com/tendermint/tendermint/libs/common" nm "github.com/tendermint/tendermint/node" "github.com/tendermint/tendermint/p2p" "github.com/tendermint/tendermint/privval" @@ -64,14 +64,17 @@ func makePathname() string { } func randPort() int { - return int(cmn.RandUint16()/2 + 10000) + port, err := cmn.GetFreePort() + if err != nil { + panic(err) + } + return port } func makeAddrs() (string, string, string) { - start := randPort() - return fmt.Sprintf("tcp://0.0.0.0:%d", start), - fmt.Sprintf("tcp://0.0.0.0:%d", start+1), - fmt.Sprintf("tcp://0.0.0.0:%d", start+2) + return fmt.Sprintf("tcp://0.0.0.0:%d", randPort()), + fmt.Sprintf("tcp://0.0.0.0:%d", randPort()), + fmt.Sprintf("tcp://0.0.0.0:%d", randPort()) } // GetConfig returns a config for the test cases as a singleton From 45b70ae031f18bb5b95794205785bd90e36ccd4b Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Wed, 6 Feb 2019 10:24:43 -0500 Subject: [PATCH 258/267] fix non deterministic test failures and race in privval socket (#3258) * node: decrease retry conn timeout in test Should fix #3256 The retry timeout was set to the default, which is the same as the accept timeout, so it's no wonder this would fail. Here we decrease the retry timeout so we can try many times before the accept timeout. * p2p: increase handshake timeout in test This fails sometimes, presumably because the handshake timeout is so low (only 50ms). So increase it to 1s. Should fix #3187 * privval: fix race with ping. closes #3237 Pings happen in a go-routine and can happen concurrently with other messages. Since we use a request/response protocol, we expect to send a request and get back the corresponding response. But with pings happening concurrently, this assumption could be violated. We were using a mutex, but only a RWMutex, where the RLock was being held for sending messages - this was to allow the underlying connection to be replaced if it fails. Turns out we actually need to use a full lock (not just a read lock) to prevent multiple requests from happening concurrently. * node: fix test name. DelayedStop -> DelayedStart * autofile: Wait() method In the TestWALTruncate in consensus/wal_test.go we remove the WAL directory at the end of the test. However the wal.Stop() does not properly wait for the autofile group to finish shutting down. Hence it was possible that the group's go-routine is still running when the cleanup happens, which causes a panic since the directory disappeared. Here we add a Wait() method to properly wait until the go-routine exits so we can safely clean up. This fixes #2852. --- consensus/wal.go | 9 +++++++++ consensus/wal_test.go | 7 ++++++- libs/autofile/group.go | 12 ++++++++++++ node/node_test.go | 21 ++++++++++----------- p2p/test_util.go | 2 +- privval/client.go | 24 +++++++++++++----------- 6 files changed, 51 insertions(+), 24 deletions(-) diff --git a/consensus/wal.go b/consensus/wal.go index ba89cd1a..d56ede26 100644 --- a/consensus/wal.go +++ b/consensus/wal.go @@ -112,11 +112,20 @@ func (wal *baseWAL) OnStart() error { return err } +// Stop the underlying autofile group. +// Use Wait() to ensure it's finished shutting down +// before cleaning up files. func (wal *baseWAL) OnStop() { wal.group.Stop() wal.group.Close() } +// Wait for the underlying autofile group to finish shutting down +// so it's safe to cleanup files. +func (wal *baseWAL) Wait() { + wal.group.Wait() +} + // Write is called in newStep and for each receive on the // peerMsgQueue and the timeoutTicker. // NOTE: does not call fsync() diff --git a/consensus/wal_test.go b/consensus/wal_test.go index c2a7d8bb..93beb68b 100644 --- a/consensus/wal_test.go +++ b/consensus/wal_test.go @@ -39,7 +39,12 @@ func TestWALTruncate(t *testing.T) { wal.SetLogger(log.TestingLogger()) err = wal.Start() require.NoError(t, err) - defer wal.Stop() + defer func() { + wal.Stop() + // wait for the wal to finish shutting down so we + // can safely remove the directory + wal.Wait() + }() //60 block's size nearly 70K, greater than group's headBuf size(4096 * 10), when headBuf is full, truncate content will Flush to the file. //at this time, RotateFile is called, truncate content exist in each file. diff --git a/libs/autofile/group.go b/libs/autofile/group.go index 1ec6b240..7e926946 100644 --- a/libs/autofile/group.go +++ b/libs/autofile/group.go @@ -67,6 +67,11 @@ type Group struct { minIndex int // Includes head maxIndex int // Includes head, where Head will move to + // close this when the processTicks routine is done. + // this ensures we can cleanup the dir after calling Stop + // and the routine won't be trying to access it anymore + doneProcessTicks chan struct{} + // TODO: When we start deleting files, we need to start tracking GroupReaders // and their dependencies. } @@ -90,6 +95,7 @@ func OpenGroup(headPath string, groupOptions ...func(*Group)) (g *Group, err err groupCheckDuration: defaultGroupCheckDuration, minIndex: 0, maxIndex: 0, + doneProcessTicks: make(chan struct{}), } for _, option := range groupOptions { @@ -140,6 +146,11 @@ func (g *Group) OnStop() { g.Flush() // flush any uncommitted data } +func (g *Group) Wait() { + // wait for processTicks routine to finish + <-g.doneProcessTicks +} + // Close closes the head file. The group must be stopped by this moment. func (g *Group) Close() { g.Flush() // flush any uncommitted data @@ -211,6 +222,7 @@ func (g *Group) Flush() error { } func (g *Group) processTicks() { + defer close(g.doneProcessTicks) for { select { case <-g.ticker.C: diff --git a/node/node_test.go b/node/node_test.go index 06561e07..3218c832 100644 --- a/node/node_test.go +++ b/node/node_test.go @@ -88,13 +88,13 @@ func TestSplitAndTrimEmpty(t *testing.T) { } } -func TestNodeDelayedStop(t *testing.T) { - config := cfg.ResetTestRoot("node_delayed_node_test") +func TestNodeDelayedStart(t *testing.T) { + config := cfg.ResetTestRoot("node_delayed_start_test") now := tmtime.Now() // create & start node n, err := DefaultNewNode(config, log.TestingLogger()) - n.GenesisDoc().GenesisTime = now.Add(5 * time.Second) + n.GenesisDoc().GenesisTime = now.Add(2 * time.Second) require.NoError(t, err) n.Start() @@ -133,6 +133,7 @@ func TestNodeSetPrivValTCP(t *testing.T) { types.NewMockPV(), dialer, ) + privval.RemoteSignerConnDeadline(100 * time.Millisecond)(pvsc) go func() { err := pvsc.Start() @@ -172,20 +173,18 @@ func TestNodeSetPrivValIPC(t *testing.T) { types.NewMockPV(), dialer, ) + privval.RemoteSignerConnDeadline(100 * time.Millisecond)(pvsc) - done := make(chan struct{}) go func() { - defer close(done) - n, err := DefaultNewNode(config, log.TestingLogger()) + err := pvsc.Start() require.NoError(t, err) - assert.IsType(t, &privval.SocketVal{}, n.PrivValidator()) }() - - err := pvsc.Start() - require.NoError(t, err) defer pvsc.Stop() - <-done + n, err := DefaultNewNode(config, log.TestingLogger()) + require.NoError(t, err) + assert.IsType(t, &privval.SocketVal{}, n.PrivValidator()) + } // testFreeAddr claims a free port so we don't block on listener being ready. diff --git a/p2p/test_util.go b/p2p/test_util.go index aad32ef8..2d320df8 100644 --- a/p2p/test_util.go +++ b/p2p/test_util.go @@ -125,7 +125,7 @@ func (sw *Switch) addPeerWithConnection(conn net.Conn) error { return err } - ni, err := handshake(conn, 50*time.Millisecond, sw.nodeInfo) + ni, err := handshake(conn, time.Second, sw.nodeInfo) if err != nil { if err := conn.Close(); err != nil { sw.Logger.Error("Error closing connection", "err", err) diff --git a/privval/client.go b/privval/client.go index 1ad104d8..11151fee 100644 --- a/privval/client.go +++ b/privval/client.go @@ -53,9 +53,11 @@ type SocketVal struct { // reset if the connection fails. // failures are detected by a background // ping routine. + // All messages are request/response, so we hold the mutex + // so only one request/response pair can happen at a time. // Methods on the underlying net.Conn itself // are already gorountine safe. - mtx sync.RWMutex + mtx sync.Mutex signer *RemoteSignerClient } @@ -82,22 +84,22 @@ func NewSocketVal( // GetPubKey implements PrivValidator. func (sc *SocketVal) GetPubKey() crypto.PubKey { - sc.mtx.RLock() - defer sc.mtx.RUnlock() + sc.mtx.Lock() + defer sc.mtx.Unlock() return sc.signer.GetPubKey() } // SignVote implements PrivValidator. func (sc *SocketVal) SignVote(chainID string, vote *types.Vote) error { - sc.mtx.RLock() - defer sc.mtx.RUnlock() + sc.mtx.Lock() + defer sc.mtx.Unlock() return sc.signer.SignVote(chainID, vote) } // SignProposal implements PrivValidator. func (sc *SocketVal) SignProposal(chainID string, proposal *types.Proposal) error { - sc.mtx.RLock() - defer sc.mtx.RUnlock() + sc.mtx.Lock() + defer sc.mtx.Unlock() return sc.signer.SignProposal(chainID, proposal) } @@ -106,15 +108,15 @@ func (sc *SocketVal) SignProposal(chainID string, proposal *types.Proposal) erro // Ping is used to check connection health. func (sc *SocketVal) Ping() error { - sc.mtx.RLock() - defer sc.mtx.RUnlock() + sc.mtx.Lock() + defer sc.mtx.Unlock() return sc.signer.Ping() } // Close closes the underlying net.Conn. func (sc *SocketVal) Close() { - sc.mtx.RLock() - defer sc.mtx.RUnlock() + sc.mtx.Lock() + defer sc.mtx.Unlock() if sc.signer != nil { if err := sc.signer.Close(); err != nil { sc.Logger.Error("OnStop", "err", err) From 9e9026452cb337fa7110ccc62fd3f6707894e37f Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Wed, 6 Feb 2019 10:29:51 -0500 Subject: [PATCH 259/267] p2p/conn: don't hold stopMtx while waiting (#3254) * p2p/conn: fix deadlock in FlushStop/OnStop * makefile: set_with_deadlock * close doneSendRoutine at end of sendRoutine * conn: initialize channs in OnStart --- Makefile | 7 ++-- p2p/conn/connection.go | 79 ++++++++++++++++++++++-------------------- 2 files changed, 46 insertions(+), 40 deletions(-) diff --git a/Makefile b/Makefile index 8c0928d0..57e46543 100644 --- a/Makefile +++ b/Makefile @@ -228,11 +228,14 @@ test_race: # uses https://github.com/sasha-s/go-deadlock/ to detect potential deadlocks test_with_deadlock: + make set_with_deadlock + make test + make cleanup_after_test_with_deadlock + +set_with_deadlock: find . -name "*.go" | grep -v "vendor/" | xargs -n 1 sed -i.bak 's/sync.RWMutex/deadlock.RWMutex/' find . -name "*.go" | grep -v "vendor/" | xargs -n 1 sed -i.bak 's/sync.Mutex/deadlock.Mutex/' find . -name "*.go" | grep -v "vendor/" | xargs -n 1 goimports -w - make test - make cleanup_after_test_with_deadlock # cleanes up after you ran test_with_deadlock cleanup_after_test_with_deadlock: diff --git a/p2p/conn/connection.go b/p2p/conn/connection.go index 92073491..c1e90ab7 100644 --- a/p2p/conn/connection.go +++ b/p2p/conn/connection.go @@ -85,8 +85,8 @@ type MConnection struct { errored uint32 config MConnConfig - // Closing quitSendRoutine will cause - // doneSendRoutine to close. + // Closing quitSendRoutine will cause the sendRoutine to eventually quit. + // doneSendRoutine is closed when the sendRoutine actually quits. quitSendRoutine chan struct{} doneSendRoutine chan struct{} @@ -200,29 +200,28 @@ func (c *MConnection) OnStart() error { if err := c.BaseService.OnStart(); err != nil { return err } - c.quitSendRoutine = make(chan struct{}) - c.doneSendRoutine = make(chan struct{}) c.flushTimer = cmn.NewThrottleTimer("flush", c.config.FlushThrottle) c.pingTimer = cmn.NewRepeatTimer("ping", c.config.PingInterval) c.pongTimeoutCh = make(chan bool, 1) c.chStatsTimer = cmn.NewRepeatTimer("chStats", updateStats) + c.quitSendRoutine = make(chan struct{}) + c.doneSendRoutine = make(chan struct{}) go c.sendRoutine() go c.recvRoutine() return nil } -// FlushStop replicates the logic of OnStop. -// It additionally ensures that all successful -// .Send() calls will get flushed before closing -// the connection. -func (c *MConnection) FlushStop() { +// stopServices stops the BaseService and timers and closes the quitSendRoutine. +// if the quitSendRoutine was already closed, it returns true, otherwise it returns false. +// It uses the stopMtx to ensure only one of FlushStop and OnStop can do this at a time. +func (c *MConnection) stopServices() (alreadyStopped bool) { c.stopMtx.Lock() defer c.stopMtx.Unlock() select { case <-c.quitSendRoutine: - // already quit via OnStop - return + // already quit via FlushStop or OnStop + return true default: } @@ -230,25 +229,40 @@ func (c *MConnection) FlushStop() { c.flushTimer.Stop() c.pingTimer.Stop() c.chStatsTimer.Stop() - if c.quitSendRoutine != nil { - close(c.quitSendRoutine) + + close(c.quitSendRoutine) + return false +} + +// FlushStop replicates the logic of OnStop. +// It additionally ensures that all successful +// .Send() calls will get flushed before closing +// the connection. +func (c *MConnection) FlushStop() { + if c.stopServices() { + return + } + + // this block is unique to FlushStop + { // wait until the sendRoutine exits // so we dont race on calling sendSomePacketMsgs <-c.doneSendRoutine + + // Send and flush all pending msgs. + // By now, IsRunning == false, + // so any concurrent attempts to send will fail. + // Since sendRoutine has exited, we can call this + // safely + eof := c.sendSomePacketMsgs() + for !eof { + eof = c.sendSomePacketMsgs() + } + c.flush() + + // Now we can close the connection } - // Send and flush all pending msgs. - // By now, IsRunning == false, - // so any concurrent attempts to send will fail. - // Since sendRoutine has exited, we can call this - // safely - eof := c.sendSomePacketMsgs() - for !eof { - eof = c.sendSomePacketMsgs() - } - c.flush() - - // Now we can close the connection c.conn.Close() // nolint: errcheck // We can't close pong safely here because @@ -261,21 +275,10 @@ func (c *MConnection) FlushStop() { // OnStop implements BaseService func (c *MConnection) OnStop() { - c.stopMtx.Lock() - defer c.stopMtx.Unlock() - - select { - case <-c.quitSendRoutine: - // already quit via FlushStop + if c.stopServices() { return - default: } - c.BaseService.OnStop() - c.flushTimer.Stop() - c.pingTimer.Stop() - c.chStatsTimer.Stop() - close(c.quitSendRoutine) c.conn.Close() // nolint: errcheck // We can't close pong safely here because @@ -433,7 +436,6 @@ FOR_LOOP: c.sendMonitor.Update(int(_n)) c.flush() case <-c.quitSendRoutine: - close(c.doneSendRoutine) break FOR_LOOP case <-c.send: // Send some PacketMsgs @@ -459,6 +461,7 @@ FOR_LOOP: // Cleanup c.stopPongTimer() + close(c.doneSendRoutine) } // Returns true if messages from channels were exhausted. From e70f27c8e45848687fb1b5ee73b6e1f4ae765262 Mon Sep 17 00:00:00 2001 From: Thane Thomson Date: Thu, 7 Feb 2019 08:32:32 +0200 Subject: [PATCH 260/267] Add remote signer test harness (KMS) (#3149) * WIP: Starts adding remote signer test harness This commit adds a new command to Tendermint to allow for us to build a standalone binary to test remote signers such as KMS (https://github.com/tendermint/kms). Right now, all it does is test that the local public key matches the public key reported by the client, and fails at the point where it attempts to get the client to sign a proposal. * Fixes typo * Fixes proposal validation test This commit fixes the proposal validation test as per #3149. It also moves the test harness into its own internal package to isolate its exports from the `privval` package. * Adds vote signing validation * Applying recommendations from #3149 * Adds function descriptions for test harness * Adds ability to ask remote signer to shut down Prior to this commit, the remote signer needs to manually be shut down, which is not ideal for automated testing. This commit allows us to send a poison pill message to the KMS to let it shut down gracefully once testing is done (whether the tests pass or fail). * Adds tests for remote signer test harness This commit makes some minor modifications to a few files to allow for testing of the remote signer test harness. Two tests are added here: checking for a fully successful (the ideal) case, and for the case where the maximum number of retries has been reached when attempting to accept incoming connections from the remote signer. * Condenses serialization of proposals and votes using existing Tendermint functions * Removes now-unnecessary amino import and codec * Adds error message for vote signing failure * Adds key extraction command for integration test Took the code from here: https://gist.github.com/Liamsi/a80993f24bff574bbfdbbfa9efa84bc7 to create a simple utility command to extract a key from a local Tendermint validator for use in KMS integration testing. * Makes path expansion success non-compulsory * Fixes segfault on SIGTERM We need an additional variable to keep track of whether we're successfully connected, otherwise hitting Ctrl+Break during execution causes a segmentation fault. This now allows for a clean shutdown. * Consolidates shutdown checks * Adds comments indicating codes for easy lookup * Adds Docker build for remote signer harness Updates the `DOCKER/build.sh` and `DOCKER/push.sh` files to allow one to override the image name and Dockerfile using environment variables. Updates the primary `Makefile` as well as the `DOCKER/Makefile` to allow for building the `remote_val_harness` Docker image. * Adds build_remote_val_harness_docker_image to .PHONY * Removes remote signer poison pill messaging functionality * Reduces fluff code in command line parsing As per https://github.com/tendermint/tendermint/pull/3149#pullrequestreview-196171788, this reduces the amount of fluff code in the PR down to the bare minimum. * Fixes ordering of error check and info log * Moves remove_val_harness cmd into tools folder It seems to make sense to rather keep the remote signer test harness in its own tool folder (now rather named `tm-signer-harness` to keep with the tool naming convention). It is actually a separate tool, not meant to be one of the core binaries, but supplementary and supportive. * Updates documentation for tm-signer-harness * Refactors flag parsing to be more compact and less redundant * Adds version sub-command help * Removes extraneous flags parsing * Adds CHANGELOG_PENDING entry for tm-signer-harness * Improves test coverage Adds a few extra parameters to the `MockPV` type to fake broken vote and proposal signing. Also adds some more tests for the test harness so as to increase coverage for failed cases. * Fixes formatting for CHANGELOG_PENDING.md * Fix formatting for documentation config * Point users towards official Tendermint docs for tools documentation * Point users towards official Tendermint docs for tm-signer-harness * Remove extraneous constant * Rename TestHarness.sc to TestHarness.spv for naming consistency * Refactor to remove redundant goroutine * Refactor conditional to cleaner switch statement and better error handling for listener protocol * Remove extraneous goroutine * Add note about installing tmkms via Cargo * Fix typo in naming of output signing key * Add note about where to find chain ID * Replace /home/user with ~/ for brevity * Fixes "signer.key" typo * Minor edits for clarification for tm-signer-harness bulid/setup process --- CHANGELOG_PENDING.md | 7 +- docs/.vuepress/config.js | 9 +- docs/tools/README.md | 7 +- docs/tools/remote-signer-validation.md | 146 +++++++ tools/README.md | 4 +- tools/tm-signer-harness/Dockerfile | 4 + tools/tm-signer-harness/Makefile | 20 + tools/tm-signer-harness/README.md | 5 + .../internal/test_harness.go | 392 ++++++++++++++++++ .../internal/test_harness_test.go | 201 +++++++++ tools/tm-signer-harness/internal/utils.go | 25 ++ tools/tm-signer-harness/main.go | 174 ++++++++ types/priv_validator.go | 27 +- 13 files changed, 1006 insertions(+), 15 deletions(-) create mode 100644 docs/tools/remote-signer-validation.md create mode 100644 tools/tm-signer-harness/Dockerfile create mode 100644 tools/tm-signer-harness/Makefile create mode 100644 tools/tm-signer-harness/README.md create mode 100644 tools/tm-signer-harness/internal/test_harness.go create mode 100644 tools/tm-signer-harness/internal/test_harness_test.go create mode 100644 tools/tm-signer-harness/internal/utils.go create mode 100644 tools/tm-signer-harness/main.go diff --git a/CHANGELOG_PENDING.md b/CHANGELOG_PENDING.md index daa42654..2c93469d 100644 --- a/CHANGELOG_PENDING.md +++ b/CHANGELOG_PENDING.md @@ -20,9 +20,10 @@ Special thanks to external contributors on this release: ### FEATURES: ### IMPROVEMENTS: -- [tools] add go-deadlock tool to help detect deadlocks -- [crypto] \#3163 use ethereum's libsecp256k1 go-wrapper for signatures when cgo is available -- [crypto] \#3162 wrap btcd instead of forking it to keep up with fixes (used if cgo is not available) +- [tools] Add go-deadlock tool to help detect deadlocks +- [tools] \#3106 Add tm-signer-harness test harness for remote signers +- [crypto] \#3163 Use ethereum's libsecp256k1 go-wrapper for signatures when cgo is available +- [crypto] \#3162 Wrap btcd instead of forking it to keep up with fixes (used if cgo is not available) ### BUG FIXES: - [node] \#3186 EventBus and indexerService should be started before first block (for replay last block on handshake) execution diff --git a/docs/.vuepress/config.js b/docs/.vuepress/config.js index 3e55a240..fb3620e3 100644 --- a/docs/.vuepress/config.js +++ b/docs/.vuepress/config.js @@ -79,10 +79,11 @@ module.exports = { title: "Tools", collapsable: false, children: [ - "/tools/", - "/tools/benchmarking", - "/tools/monitoring" - ] + "/tools/", + "/tools/benchmarking", + "/tools/monitoring", + "/tools/remote-signer-validation" + ] }, { title: "Tendermint Spec", diff --git a/docs/tools/README.md b/docs/tools/README.md index ef1ae7c2..0b861621 100644 --- a/docs/tools/README.md +++ b/docs/tools/README.md @@ -1,4 +1,7 @@ # Overview -Tendermint comes with some tools for [benchmarking](./benchmarking.md) -and [monitoring](./monitoring.md). +Tendermint comes with some tools for: + +* [Benchmarking](./benchmarking.md) +* [Monitoring](./monitoring.md) +* [Validation of remote signers](./remote-signer-validation.md) diff --git a/docs/tools/remote-signer-validation.md b/docs/tools/remote-signer-validation.md new file mode 100644 index 00000000..c8a948e3 --- /dev/null +++ b/docs/tools/remote-signer-validation.md @@ -0,0 +1,146 @@ +# tm-signer-harness + +Located under the `tools/tm-signer-harness` folder in the [Tendermint +repository](https://github.com/tendermint/tendermint). + +The Tendermint remote signer test harness facilitates integration testing +between Tendermint and remote signers such as +[KMS](https://github.com/tendermint/kms). Such remote signers allow for signing +of important Tendermint messages using +[HSMs](https://en.wikipedia.org/wiki/Hardware_security_module), providing +additional security. + +When executed, `tm-signer-harness`: + +1. Runs a listener (either TCP or Unix sockets). +2. Waits for a connection from the remote signer. +3. Upon connection from the remote signer, executes a number of automated tests + to ensure compatibility. +4. Upon successful validation, the harness process exits with a 0 exit code. + Upon validation failure, it exits with a particular exit code related to the + error. + +## Prerequisites +Requires the same prerequisites as for building +[Tendermint](https://github.com/tendermint/tendermint). + +## Building +From the `tools/tm-signer-harness` directory in your Tendermint source +repository, simply run: + +```bash +make + +# To have global access to this executable +make install +``` + +## Docker Image +To build a Docker image containing the `tm-signer-harness`, also from the +`tools/tm-signer-harness` directory of your Tendermint source repo, simply run: + +```bash +make docker-image +``` + +## Running against KMS +As an example of how to use `tm-signer-harness`, the following instructions show +you how to execute its tests against [KMS](https://github.com/tendermint/kms). +For this example, we will make use of the **software signing module in KMS**, as +the hardware signing module requires a physical +[YubiHSM](https://www.yubico.com/products/yubihsm/) device. + +### Step 1: Install KMS on your local machine +See the [KMS repo](https://github.com/tendermint/kms) for details on how to set +KMS up on your local machine. + +If you have [Rust](https://www.rust-lang.org/) installed on your local machine, +you can simply install KMS by: + +```bash +cargo install tmkms +``` + +### Step 2: Make keys for KMS +The KMS software signing module needs a key with which to sign messages. In our +example, we will simply export a signing key from our local Tendermint instance. + +```bash +# Will generate all necessary Tendermint configuration files, including: +# - ~/.tendermint/config/priv_validator_key.json +# - ~/.tendermint/data/priv_validator_state.json +tendermint init + +# Extract the signing key from our local Tendermint instance +tm-signer-harness extract_key \ # Use the "extract_key" command + -tmhome ~/.tendermint \ # Where to find the Tendermint home directory + -output ./signing.key # Where to write the key +``` + +Also, because we want KMS to connect to `tm-signer-harness`, we will need to +provide a secret connection key from KMS' side: + +```bash +tmkms keygen secret_connection.key +``` + +### Step 3: Configure and run KMS +KMS needs some configuration to tell it to use the softer signing module as well +as the `signing.key` file we just generated. Save the following to a file called +`tmkms.toml`: + +```toml +[[validator]] +addr = "tcp://127.0.0.1:61219" # This is where we will find tm-signer-harness. +chain_id = "test-chain-0XwP5E" # The Tendermint chain ID for which KMS will be signing (found in ~/.tendermint/config/genesis.json). +reconnect = true # true is the default +secret_key = "./secret_connection.key" # Where to find our secret connection key. + +[[providers.softsign]] +id = "test-chain-0XwP5E" # The Tendermint chain ID for which KMS will be signing (same as validator.chain_id above). +path = "./signing.key" # The signing key we extracted earlier. +``` + +Then run KMS with this configuration: + +```bash +tmkms start -c tmkms.toml +``` + +This will start KMS, which will repeatedly try to connect to +`tcp://127.0.0.1:61219` until it is successful. + +### Step 4: Run tm-signer-harness +Now we get to run the signer test harness: + +```bash +tm-signer-harness run \ # The "run" command executes the tests + -addr tcp://127.0.0.1:61219 \ # The address we promised KMS earlier + -tmhome ~/.tendermint # Where to find our Tendermint configuration/data files. +``` + +If the current version of Tendermint and KMS are compatible, `tm-signer-harness` +should now exit with a 0 exit code. If they are somehow not compatible, it +should exit with a meaningful non-zero exit code (see the exit codes below). + +### Step 5: Shut down KMS +Simply hit Ctrl+Break on your KMS instance (or use the `kill` command in Linux) +to terminate it gracefully. + +## Exit Code Meanings +The following list shows the various exit codes from `tm-signer-harness` and +their meanings: + +| Exit Code | Description | +| --- | --- | +| 0 | Success! | +| 1 | Invalid command line parameters supplied to `tm-signer-harness` | +| 2 | Maximum number of accept retries reached (the `-accept-retries` parameter) | +| 3 | Failed to load `${TMHOME}/config/genesis.json` | +| 4 | Failed to create listener specified by `-addr` parameter | +| 5 | Failed to start listener | +| 6 | Interrupted by `SIGINT` (e.g. when hitting Ctrl+Break or Ctrl+C) | +| 7 | Other unknown error | +| 8 | Test 1 failed: public key mismatch | +| 9 | Test 2 failed: signing of proposals failed | +| 10 | Test 3 failed: signing of votes failed | diff --git a/tools/README.md b/tools/README.md index aeb41141..041067e7 100644 --- a/tools/README.md +++ b/tools/README.md @@ -1,3 +1,5 @@ # tools -Tools for working with tendermint and associated technologies. Documentation can be found in the `README.md` of each the `tm-bench/` and `tm-monitor/` directories. +Tools for working with Tendermint and associated technologies. Documentation for +these tools can be found online in the [Tendermint tools +documentation](https://tendermint.com/docs/tools/). diff --git a/tools/tm-signer-harness/Dockerfile b/tools/tm-signer-harness/Dockerfile new file mode 100644 index 00000000..83f57a3d --- /dev/null +++ b/tools/tm-signer-harness/Dockerfile @@ -0,0 +1,4 @@ +ARG TENDERMINT_VERSION=latest +FROM tendermint/tendermint:${TENDERMINT_VERSION} + +COPY tm-signer-harness /usr/bin/tm-signer-harness diff --git a/tools/tm-signer-harness/Makefile b/tools/tm-signer-harness/Makefile new file mode 100644 index 00000000..47cd0365 --- /dev/null +++ b/tools/tm-signer-harness/Makefile @@ -0,0 +1,20 @@ +.PHONY: build install docker-image + +TENDERMINT_VERSION?=latest +BUILD_TAGS?='tendermint' +BUILD_FLAGS = -ldflags "-X github.com/tendermint/tendermint/version.GitCommit=`git rev-parse --short=8 HEAD`" + +.DEFAULT_GOAL := build + +build: + CGO_ENABLED=0 go build $(BUILD_FLAGS) -tags $(BUILD_TAGS) -o ../../build/tm-signer-harness main.go + +install: + CGO_ENABLED=0 go install $(BUILD_FLAGS) -tags $(BUILD_TAGS) . + +docker-image: + GOOS=linux GOARCH=amd64 go build $(BUILD_FLAGS) -tags $(BUILD_TAGS) -o tm-signer-harness main.go + docker build \ + --build-arg TENDERMINT_VERSION=$(TENDERMINT_VERSION) \ + -t tendermint/tm-signer-harness:$(TENDERMINT_VERSION) . + rm -rf tm-signer-harness diff --git a/tools/tm-signer-harness/README.md b/tools/tm-signer-harness/README.md new file mode 100644 index 00000000..7add3a99 --- /dev/null +++ b/tools/tm-signer-harness/README.md @@ -0,0 +1,5 @@ +# tm-signer-harness + +See the [`tm-signer-harness` +documentation](https://tendermint.com/docs/tools/remote-signer-validation.html) +for more details. diff --git a/tools/tm-signer-harness/internal/test_harness.go b/tools/tm-signer-harness/internal/test_harness.go new file mode 100644 index 00000000..b961f238 --- /dev/null +++ b/tools/tm-signer-harness/internal/test_harness.go @@ -0,0 +1,392 @@ +package internal + +import ( + "fmt" + "net" + "os" + "os/signal" + "time" + + "github.com/tendermint/tendermint/crypto/tmhash" + + "github.com/tendermint/tendermint/crypto/ed25519" + "github.com/tendermint/tendermint/privval" + "github.com/tendermint/tendermint/state" + + cmn "github.com/tendermint/tendermint/libs/common" + "github.com/tendermint/tendermint/libs/log" + "github.com/tendermint/tendermint/types" +) + +// Test harness error codes (which act as exit codes when the test harness fails). +const ( + NoError int = iota // 0 + ErrInvalidParameters // 1 + ErrMaxAcceptRetriesReached // 2 + ErrFailedToLoadGenesisFile // 3 + ErrFailedToCreateListener // 4 + ErrFailedToStartListener // 5 + ErrInterrupted // 6 + ErrOther // 7 + ErrTestPublicKeyFailed // 8 + ErrTestSignProposalFailed // 9 + ErrTestSignVoteFailed // 10 +) + +var voteTypes = []types.SignedMsgType{types.PrevoteType, types.PrecommitType} + +// TestHarnessError allows us to keep track of which exit code should be used +// when exiting the main program. +type TestHarnessError struct { + Code int // The exit code to return + Err error // The original error + Info string // Any additional information +} + +var _ error = (*TestHarnessError)(nil) + +// TestHarness allows for testing of a remote signer to ensure compatibility +// with this version of Tendermint. +type TestHarness struct { + addr string + spv *privval.SocketVal + fpv *privval.FilePV + chainID string + acceptRetries int + logger log.Logger + exitWhenComplete bool + exitCode int +} + +// TestHarnessConfig provides configuration to set up a remote signer test +// harness. +type TestHarnessConfig struct { + BindAddr string + + KeyFile string + StateFile string + GenesisFile string + + AcceptDeadline time.Duration + ConnDeadline time.Duration + AcceptRetries int + + SecretConnKey ed25519.PrivKeyEd25519 + + ExitWhenComplete bool // Whether or not to call os.Exit when the harness has completed. +} + +// timeoutError can be used to check if an error returned from the netp package +// was due to a timeout. +type timeoutError interface { + Timeout() bool +} + +// NewTestHarness will load Tendermint data from the given files (including +// validator public/private keypairs and chain details) and create a new +// harness. +func NewTestHarness(logger log.Logger, cfg TestHarnessConfig) (*TestHarness, error) { + keyFile := ExpandPath(cfg.KeyFile) + stateFile := ExpandPath(cfg.StateFile) + logger.Info("Loading private validator configuration", "keyFile", keyFile, "stateFile", stateFile) + // NOTE: LoadFilePV ultimately calls os.Exit on failure. No error will be + // returned if this call fails. + fpv := privval.LoadFilePV(keyFile, stateFile) + + genesisFile := ExpandPath(cfg.GenesisFile) + logger.Info("Loading chain ID from genesis file", "genesisFile", genesisFile) + st, err := state.MakeGenesisDocFromFile(genesisFile) + if err != nil { + return nil, newTestHarnessError(ErrFailedToLoadGenesisFile, err, genesisFile) + } + logger.Info("Loaded genesis file", "chainID", st.ChainID) + + spv, err := newTestHarnessSocketVal(logger, cfg) + if err != nil { + return nil, newTestHarnessError(ErrFailedToCreateListener, err, "") + } + + return &TestHarness{ + addr: cfg.BindAddr, + spv: spv, + fpv: fpv, + chainID: st.ChainID, + acceptRetries: cfg.AcceptRetries, + logger: logger, + exitWhenComplete: cfg.ExitWhenComplete, + exitCode: 0, + }, nil +} + +// Run will execute the tests associated with this test harness. The intention +// here is to call this from one's `main` function, as the way it succeeds or +// fails at present is to call os.Exit() with an exit code related to the error +// that caused the tests to fail, or exit code 0 on success. +func (th *TestHarness) Run() { + c := make(chan os.Signal, 1) + signal.Notify(c, os.Interrupt) + go func() { + for sig := range c { + th.logger.Info("Caught interrupt, terminating...", "sig", sig) + th.Shutdown(newTestHarnessError(ErrInterrupted, nil, "")) + } + }() + + th.logger.Info("Starting test harness") + accepted := false + var startErr error + for acceptRetries := th.acceptRetries; acceptRetries > 0; acceptRetries-- { + th.logger.Info("Attempting to accept incoming connection", "acceptRetries", acceptRetries) + if err := th.spv.Start(); err != nil { + // if it wasn't a timeout error + if _, ok := err.(timeoutError); !ok { + th.logger.Error("Failed to start listener", "err", err) + th.Shutdown(newTestHarnessError(ErrFailedToStartListener, err, "")) + // we need the return statements in case this is being run + // from a unit test - otherwise this function will just die + // when os.Exit is called + return + } + startErr = err + } else { + accepted = true + break + } + } + if !accepted { + th.logger.Error("Maximum accept retries reached", "acceptRetries", th.acceptRetries) + th.Shutdown(newTestHarnessError(ErrMaxAcceptRetriesReached, startErr, "")) + return + } + + // Run the tests + if err := th.TestPublicKey(); err != nil { + th.Shutdown(err) + return + } + if err := th.TestSignProposal(); err != nil { + th.Shutdown(err) + return + } + if err := th.TestSignVote(); err != nil { + th.Shutdown(err) + return + } + th.logger.Info("SUCCESS! All tests passed.") + th.Shutdown(nil) +} + +// TestPublicKey just validates that we can (1) fetch the public key from the +// remote signer, and (2) it matches the public key we've configured for our +// local Tendermint version. +func (th *TestHarness) TestPublicKey() error { + th.logger.Info("TEST: Public key of remote signer") + th.logger.Info("Local", "pubKey", th.fpv.GetPubKey()) + th.logger.Info("Remote", "pubKey", th.spv.GetPubKey()) + if th.fpv.GetPubKey() != th.spv.GetPubKey() { + th.logger.Error("FAILED: Local and remote public keys do not match") + return newTestHarnessError(ErrTestPublicKeyFailed, nil, "") + } + return nil +} + +// TestSignProposal makes sure the remote signer can successfully sign +// proposals. +func (th *TestHarness) TestSignProposal() error { + th.logger.Info("TEST: Signing of proposals") + // sha256 hash of "hash" + hash := tmhash.Sum([]byte("hash")) + prop := &types.Proposal{ + Type: types.ProposalType, + Height: 12345, + Round: 23456, + POLRound: -1, + BlockID: types.BlockID{ + Hash: hash, + PartsHeader: types.PartSetHeader{ + Hash: hash, + Total: 1000000, + }, + }, + Timestamp: time.Now(), + } + propBytes := prop.SignBytes(th.chainID) + if err := th.spv.SignProposal(th.chainID, prop); err != nil { + th.logger.Error("FAILED: Signing of proposal", "err", err) + return newTestHarnessError(ErrTestSignProposalFailed, err, "") + } + th.logger.Debug("Signed proposal", "prop", prop) + // first check that it's a basically valid proposal + if err := prop.ValidateBasic(); err != nil { + th.logger.Error("FAILED: Signed proposal is invalid", "err", err) + return newTestHarnessError(ErrTestSignProposalFailed, err, "") + } + // now validate the signature on the proposal + if th.spv.GetPubKey().VerifyBytes(propBytes, prop.Signature) { + th.logger.Info("Successfully validated proposal signature") + } else { + th.logger.Error("FAILED: Proposal signature validation failed") + return newTestHarnessError(ErrTestSignProposalFailed, nil, "signature validation failed") + } + return nil +} + +// TestSignVote makes sure the remote signer can successfully sign all kinds of +// votes. +func (th *TestHarness) TestSignVote() error { + th.logger.Info("TEST: Signing of votes") + for _, voteType := range voteTypes { + th.logger.Info("Testing vote type", "type", voteType) + hash := tmhash.Sum([]byte("hash")) + vote := &types.Vote{ + Type: voteType, + Height: 12345, + Round: 23456, + BlockID: types.BlockID{ + Hash: hash, + PartsHeader: types.PartSetHeader{ + Hash: hash, + Total: 1000000, + }, + }, + ValidatorIndex: 0, + ValidatorAddress: tmhash.SumTruncated([]byte("addr")), + Timestamp: time.Now(), + } + voteBytes := vote.SignBytes(th.chainID) + // sign the vote + if err := th.spv.SignVote(th.chainID, vote); err != nil { + th.logger.Error("FAILED: Signing of vote", "err", err) + return newTestHarnessError(ErrTestSignVoteFailed, err, fmt.Sprintf("voteType=%d", voteType)) + } + th.logger.Debug("Signed vote", "vote", vote) + // validate the contents of the vote + if err := vote.ValidateBasic(); err != nil { + th.logger.Error("FAILED: Signed vote is invalid", "err", err) + return newTestHarnessError(ErrTestSignVoteFailed, err, fmt.Sprintf("voteType=%d", voteType)) + } + // now validate the signature on the proposal + if th.spv.GetPubKey().VerifyBytes(voteBytes, vote.Signature) { + th.logger.Info("Successfully validated vote signature", "type", voteType) + } else { + th.logger.Error("FAILED: Vote signature validation failed", "type", voteType) + return newTestHarnessError(ErrTestSignVoteFailed, nil, "signature validation failed") + } + } + return nil +} + +// Shutdown will kill the test harness and attempt to close all open sockets +// gracefully. If the supplied error is nil, it is assumed that the exit code +// should be 0. If err is not nil, it will exit with an exit code related to the +// error. +func (th *TestHarness) Shutdown(err error) { + var exitCode int + + if err == nil { + exitCode = NoError + } else if therr, ok := err.(*TestHarnessError); ok { + exitCode = therr.Code + } else { + exitCode = ErrOther + } + th.exitCode = exitCode + + // in case sc.Stop() takes too long + if th.exitWhenComplete { + go func() { + time.Sleep(time.Duration(5) * time.Second) + th.logger.Error("Forcibly exiting program after timeout") + os.Exit(exitCode) + }() + } + + if th.spv.IsRunning() { + if err := th.spv.Stop(); err != nil { + th.logger.Error("Failed to cleanly stop listener: %s", err.Error()) + } + } + + if th.exitWhenComplete { + os.Exit(exitCode) + } +} + +// newTestHarnessSocketVal creates our client instance which we will use for +// testing. +func newTestHarnessSocketVal(logger log.Logger, cfg TestHarnessConfig) (*privval.SocketVal, error) { + proto, addr := cmn.ProtocolAndAddress(cfg.BindAddr) + if proto == "unix" { + // make sure the socket doesn't exist - if so, try to delete it + if cmn.FileExists(addr) { + if err := os.Remove(addr); err != nil { + logger.Error("Failed to remove existing Unix domain socket", "addr", addr) + return nil, err + } + } + } + ln, err := net.Listen(proto, addr) + if err != nil { + return nil, err + } + logger.Info("Listening at", "proto", proto, "addr", addr) + var svln net.Listener + switch proto { + case "unix": + unixLn := privval.NewUnixListener(ln) + privval.UnixListenerAcceptDeadline(cfg.AcceptDeadline)(unixLn) + privval.UnixListenerConnDeadline(cfg.ConnDeadline)(unixLn) + svln = unixLn + case "tcp": + tcpLn := privval.NewTCPListener(ln, cfg.SecretConnKey) + privval.TCPListenerAcceptDeadline(cfg.AcceptDeadline)(tcpLn) + privval.TCPListenerConnDeadline(cfg.ConnDeadline)(tcpLn) + logger.Info("Resolved TCP address for listener", "addr", tcpLn.Addr()) + svln = tcpLn + default: + logger.Error("Unsupported protocol (must be unix:// or tcp://)", "proto", proto) + return nil, newTestHarnessError(ErrInvalidParameters, nil, fmt.Sprintf("Unsupported protocol: %s", proto)) + } + return privval.NewSocketVal(logger, svln), nil +} + +func newTestHarnessError(code int, err error, info string) *TestHarnessError { + return &TestHarnessError{ + Code: code, + Err: err, + Info: info, + } +} + +func (e *TestHarnessError) Error() string { + var msg string + switch e.Code { + case ErrInvalidParameters: + msg = "Invalid parameters supplied to application" + case ErrMaxAcceptRetriesReached: + msg = "Maximum accept retries reached" + case ErrFailedToLoadGenesisFile: + msg = "Failed to load genesis file" + case ErrFailedToCreateListener: + msg = "Failed to create listener" + case ErrFailedToStartListener: + msg = "Failed to start listener" + case ErrInterrupted: + msg = "Interrupted" + case ErrTestPublicKeyFailed: + msg = "Public key validation test failed" + case ErrTestSignProposalFailed: + msg = "Proposal signing validation test failed" + case ErrTestSignVoteFailed: + msg = "Vote signing validation test failed" + default: + msg = "Unknown error" + } + if len(e.Info) > 0 { + msg = fmt.Sprintf("%s: %s", msg, e.Info) + } + if e.Err != nil { + msg = fmt.Sprintf("%s (original error: %s)", msg, e.Err.Error()) + } + return msg +} diff --git a/tools/tm-signer-harness/internal/test_harness_test.go b/tools/tm-signer-harness/internal/test_harness_test.go new file mode 100644 index 00000000..804aca45 --- /dev/null +++ b/tools/tm-signer-harness/internal/test_harness_test.go @@ -0,0 +1,201 @@ +package internal + +import ( + "fmt" + "io/ioutil" + "net" + "os" + "testing" + "time" + + "github.com/tendermint/tendermint/crypto" + "github.com/tendermint/tendermint/privval" + "github.com/tendermint/tendermint/types" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/tendermint/tendermint/crypto/ed25519" + "github.com/tendermint/tendermint/libs/log" +) + +const ( + keyFileContents = `{ + "address": "D08FCA3BA74CF17CBFC15E64F9505302BB0E2748", + "pub_key": { + "type": "tendermint/PubKeyEd25519", + "value": "ZCsuTjaczEyon70nmKxwvwu+jqrbq5OH3yQjcK0SFxc=" + }, + "priv_key": { + "type": "tendermint/PrivKeyEd25519", + "value": "8O39AkQsoe1sBQwud/Kdul8lg8K9SFsql9aZvwXQSt1kKy5ONpzMTKifvSeYrHC/C76Oqturk4ffJCNwrRIXFw==" + } +}` + + stateFileContents = `{ + "height": "0", + "round": "0", + "step": 0 +}` + + genesisFileContents = `{ + "genesis_time": "2019-01-15T11:56:34.8963Z", + "chain_id": "test-chain-0XwP5E", + "consensus_params": { + "block_size": { + "max_bytes": "22020096", + "max_gas": "-1" + }, + "evidence": { + "max_age": "100000" + }, + "validator": { + "pub_key_types": [ + "ed25519" + ] + } + }, + "validators": [ + { + "address": "D08FCA3BA74CF17CBFC15E64F9505302BB0E2748", + "pub_key": { + "type": "tendermint/PubKeyEd25519", + "value": "ZCsuTjaczEyon70nmKxwvwu+jqrbq5OH3yQjcK0SFxc=" + }, + "power": "10", + "name": "" + } + ], + "app_hash": "" +}` + + defaultConnDeadline = 100 +) + +func TestRemoteSignerTestHarnessMaxAcceptRetriesReached(t *testing.T) { + cfg := makeConfig(t, 1, 2) + defer cleanup(cfg) + + th, err := NewTestHarness(log.TestingLogger(), cfg) + require.NoError(t, err) + th.Run() + assert.Equal(t, ErrMaxAcceptRetriesReached, th.exitCode) +} + +func TestRemoteSignerTestHarnessSuccessfulRun(t *testing.T) { + harnessTest( + t, + func(th *TestHarness) *privval.RemoteSigner { + return newMockRemoteSigner(t, th, th.fpv.Key.PrivKey, false, false) + }, + NoError, + ) +} + +func TestRemoteSignerPublicKeyCheckFailed(t *testing.T) { + harnessTest( + t, + func(th *TestHarness) *privval.RemoteSigner { + return newMockRemoteSigner(t, th, ed25519.GenPrivKey(), false, false) + }, + ErrTestPublicKeyFailed, + ) +} + +func TestRemoteSignerProposalSigningFailed(t *testing.T) { + harnessTest( + t, + func(th *TestHarness) *privval.RemoteSigner { + return newMockRemoteSigner(t, th, th.fpv.Key.PrivKey, true, false) + }, + ErrTestSignProposalFailed, + ) +} + +func TestRemoteSignerVoteSigningFailed(t *testing.T) { + harnessTest( + t, + func(th *TestHarness) *privval.RemoteSigner { + return newMockRemoteSigner(t, th, th.fpv.Key.PrivKey, false, true) + }, + ErrTestSignVoteFailed, + ) +} + +func newMockRemoteSigner(t *testing.T, th *TestHarness, privKey crypto.PrivKey, breakProposalSigning bool, breakVoteSigning bool) *privval.RemoteSigner { + return privval.NewRemoteSigner( + th.logger, + th.chainID, + types.NewMockPVWithParams(privKey, breakProposalSigning, breakVoteSigning), + privval.DialTCPFn( + th.addr, + time.Duration(defaultConnDeadline)*time.Millisecond, + ed25519.GenPrivKey(), + ), + ) +} + +// For running relatively standard tests. +func harnessTest(t *testing.T, rsMaker func(th *TestHarness) *privval.RemoteSigner, expectedExitCode int) { + cfg := makeConfig(t, 100, 3) + defer cleanup(cfg) + + th, err := NewTestHarness(log.TestingLogger(), cfg) + require.NoError(t, err) + donec := make(chan struct{}) + go func() { + defer close(donec) + th.Run() + }() + + rs := rsMaker(th) + require.NoError(t, rs.Start()) + assert.True(t, rs.IsRunning()) + defer rs.Stop() + + <-donec + assert.Equal(t, expectedExitCode, th.exitCode) +} + +func makeConfig(t *testing.T, acceptDeadline, acceptRetries int) TestHarnessConfig { + return TestHarnessConfig{ + BindAddr: testFreeTCPAddr(t), + KeyFile: makeTempFile("tm-testharness-keyfile", keyFileContents), + StateFile: makeTempFile("tm-testharness-statefile", stateFileContents), + GenesisFile: makeTempFile("tm-testharness-genesisfile", genesisFileContents), + AcceptDeadline: time.Duration(acceptDeadline) * time.Millisecond, + ConnDeadline: time.Duration(defaultConnDeadline) * time.Millisecond, + AcceptRetries: acceptRetries, + SecretConnKey: ed25519.GenPrivKey(), + ExitWhenComplete: false, + } +} + +func cleanup(cfg TestHarnessConfig) { + os.Remove(cfg.KeyFile) + os.Remove(cfg.StateFile) + os.Remove(cfg.GenesisFile) +} + +func makeTempFile(name, content string) string { + tempFile, err := ioutil.TempFile("", fmt.Sprintf("%s-*", name)) + if err != nil { + panic(err) + } + if _, err := tempFile.Write([]byte(content)); err != nil { + tempFile.Close() + panic(err) + } + if err := tempFile.Close(); err != nil { + panic(err) + } + return tempFile.Name() +} + +// testFreeTCPAddr claims a free port so we don't block on listener being ready. +func testFreeTCPAddr(t *testing.T) string { + ln, err := net.Listen("tcp", "127.0.0.1:0") + require.NoError(t, err) + defer ln.Close() + + return fmt.Sprintf("127.0.0.1:%d", ln.Addr().(*net.TCPAddr).Port) +} diff --git a/tools/tm-signer-harness/internal/utils.go b/tools/tm-signer-harness/internal/utils.go new file mode 100644 index 00000000..9783ca95 --- /dev/null +++ b/tools/tm-signer-harness/internal/utils.go @@ -0,0 +1,25 @@ +package internal + +import ( + "os/user" + "path/filepath" + "strings" +) + +// ExpandPath will check if the given path begins with a "~" symbol, and if so, +// will expand it to become the user's home directory. If it fails to expand the +// path it will automatically return the original path itself. +func ExpandPath(path string) string { + usr, err := user.Current() + if err != nil { + return path + } + + if path == "~" { + return usr.HomeDir + } else if strings.HasPrefix(path, "~/") { + return filepath.Join(usr.HomeDir, path[2:]) + } + + return path +} diff --git a/tools/tm-signer-harness/main.go b/tools/tm-signer-harness/main.go new file mode 100644 index 00000000..13aaf03a --- /dev/null +++ b/tools/tm-signer-harness/main.go @@ -0,0 +1,174 @@ +package main + +import ( + "flag" + "fmt" + "io/ioutil" + "os" + "path/filepath" + "time" + + "github.com/tendermint/tendermint/crypto/ed25519" + "github.com/tendermint/tendermint/libs/log" + "github.com/tendermint/tendermint/privval" + "github.com/tendermint/tendermint/tools/tm-signer-harness/internal" + "github.com/tendermint/tendermint/version" +) + +const ( + defaultAcceptRetries = 100 + defaultBindAddr = "tcp://127.0.0.1:0" + defaultTMHome = "~/.tendermint" + defaultAcceptDeadline = 1 + defaultConnDeadline = 3 + defaultExtractKeyOutput = "./signing.key" +) + +var logger = log.NewTMLogger(log.NewSyncWriter(os.Stdout)) + +// Command line flags +var ( + flagAcceptRetries int + flagBindAddr string + flagTMHome string + flagKeyOutputPath string +) + +// Command line commands +var ( + rootCmd *flag.FlagSet + runCmd *flag.FlagSet + extractKeyCmd *flag.FlagSet + versionCmd *flag.FlagSet +) + +func init() { + rootCmd = flag.NewFlagSet("root", flag.ExitOnError) + rootCmd.Usage = func() { + fmt.Println(`Remote signer test harness for Tendermint. + +Usage: + tm-signer-harness [flags] + +Available Commands: + extract_key Extracts a signing key from a local Tendermint instance + help Help on the available commands + run Runs the test harness + version Display version information and exit + +Use "tm-signer-harness help " for more information about that command.`) + fmt.Println("") + } + + runCmd = flag.NewFlagSet("run", flag.ExitOnError) + runCmd.IntVar(&flagAcceptRetries, "accept-retries", defaultAcceptRetries, "The number of attempts to listen for incoming connections") + runCmd.StringVar(&flagBindAddr, "addr", defaultBindAddr, "Bind to this address for the testing") + runCmd.StringVar(&flagTMHome, "tmhome", defaultTMHome, "Path to the Tendermint home directory") + runCmd.Usage = func() { + fmt.Println(`Runs the remote signer test harness for Tendermint. + +Usage: + tm-signer-harness run [flags] + +Flags:`) + runCmd.PrintDefaults() + fmt.Println("") + } + + extractKeyCmd = flag.NewFlagSet("extract_key", flag.ExitOnError) + extractKeyCmd.StringVar(&flagKeyOutputPath, "output", defaultExtractKeyOutput, "Path to which signing key should be written") + extractKeyCmd.StringVar(&flagTMHome, "tmhome", defaultTMHome, "Path to the Tendermint home directory") + extractKeyCmd.Usage = func() { + fmt.Println(`Extracts a signing key from a local Tendermint instance for use in the remote +signer under test. + +Usage: + tm-signer-harness extract_key [flags] + +Flags:`) + extractKeyCmd.PrintDefaults() + fmt.Println("") + } + + versionCmd = flag.NewFlagSet("version", flag.ExitOnError) + versionCmd.Usage = func() { + fmt.Println(` +Prints the Tendermint version for which this remote signer harness was built. + +Usage: + tm-signer-harness version`) + fmt.Println("") + } +} + +func runTestHarness(acceptRetries int, bindAddr, tmhome string) { + tmhome = internal.ExpandPath(tmhome) + cfg := internal.TestHarnessConfig{ + BindAddr: bindAddr, + KeyFile: filepath.Join(tmhome, "config", "priv_validator_key.json"), + StateFile: filepath.Join(tmhome, "data", "priv_validator_state.json"), + GenesisFile: filepath.Join(tmhome, "config", "genesis.json"), + AcceptDeadline: time.Duration(defaultAcceptDeadline) * time.Second, + AcceptRetries: acceptRetries, + ConnDeadline: time.Duration(defaultConnDeadline) * time.Second, + SecretConnKey: ed25519.GenPrivKey(), + ExitWhenComplete: true, + } + harness, err := internal.NewTestHarness(logger, cfg) + if err != nil { + logger.Error(err.Error()) + if therr, ok := err.(*internal.TestHarnessError); ok { + os.Exit(therr.Code) + } + os.Exit(internal.ErrOther) + } + harness.Run() +} + +func extractKey(tmhome, outputPath string) { + keyFile := filepath.Join(internal.ExpandPath(tmhome), "config", "priv_validator_key.json") + stateFile := filepath.Join(internal.ExpandPath(tmhome), "data", "priv_validator_state.json") + fpv := privval.LoadFilePV(keyFile, stateFile) + pkb := [64]byte(fpv.Key.PrivKey.(ed25519.PrivKeyEd25519)) + if err := ioutil.WriteFile(internal.ExpandPath(outputPath), pkb[:32], 0644); err != nil { + logger.Info("Failed to write private key", "output", outputPath, "err", err) + os.Exit(1) + } + logger.Info("Successfully wrote private key", "output", outputPath) +} + +func main() { + rootCmd.Parse(os.Args[1:]) + if rootCmd.NArg() == 0 || (rootCmd.NArg() == 1 && rootCmd.Arg(0) == "help") { + rootCmd.Usage() + os.Exit(0) + } + + logger = log.NewFilter(logger, log.AllowInfo()) + + switch rootCmd.Arg(0) { + case "help": + switch rootCmd.Arg(1) { + case "run": + runCmd.Usage() + case "extract_key": + extractKeyCmd.Usage() + case "version": + versionCmd.Usage() + default: + fmt.Printf("Unrecognized command: %s\n", rootCmd.Arg(1)) + os.Exit(1) + } + case "run": + runCmd.Parse(os.Args[2:]) + runTestHarness(flagAcceptRetries, flagBindAddr, flagTMHome) + case "extract_key": + extractKeyCmd.Parse(os.Args[2:]) + extractKey(flagTMHome, flagKeyOutputPath) + case "version": + fmt.Println(version.Version) + default: + fmt.Printf("Unrecognized command: %s\n", flag.Arg(0)) + os.Exit(1) + } +} diff --git a/types/priv_validator.go b/types/priv_validator.go index f0a19f40..8acab243 100644 --- a/types/priv_validator.go +++ b/types/priv_validator.go @@ -43,11 +43,20 @@ func (pvs PrivValidatorsByAddress) Swap(i, j int) { // MockPV implements PrivValidator without any safety or persistence. // Only use it for testing. type MockPV struct { - privKey crypto.PrivKey + privKey crypto.PrivKey + breakProposalSigning bool + breakVoteSigning bool } func NewMockPV() *MockPV { - return &MockPV{ed25519.GenPrivKey()} + return &MockPV{ed25519.GenPrivKey(), false, false} +} + +// NewMockPVWithParams allows one to create a MockPV instance, but with finer +// grained control over the operation of the mock validator. This is useful for +// mocking test failures. +func NewMockPVWithParams(privKey crypto.PrivKey, breakProposalSigning, breakVoteSigning bool) *MockPV { + return &MockPV{privKey, breakProposalSigning, breakVoteSigning} } // Implements PrivValidator. @@ -57,7 +66,11 @@ func (pv *MockPV) GetPubKey() crypto.PubKey { // Implements PrivValidator. func (pv *MockPV) SignVote(chainID string, vote *Vote) error { - signBytes := vote.SignBytes(chainID) + useChainID := chainID + if pv.breakVoteSigning { + useChainID = "incorrect-chain-id" + } + signBytes := vote.SignBytes(useChainID) sig, err := pv.privKey.Sign(signBytes) if err != nil { return err @@ -68,7 +81,11 @@ func (pv *MockPV) SignVote(chainID string, vote *Vote) error { // Implements PrivValidator. func (pv *MockPV) SignProposal(chainID string, proposal *Proposal) error { - signBytes := proposal.SignBytes(chainID) + useChainID := chainID + if pv.breakProposalSigning { + useChainID = "incorrect-chain-id" + } + signBytes := proposal.SignBytes(useChainID) sig, err := pv.privKey.Sign(signBytes) if err != nil { return err @@ -107,5 +124,5 @@ func (pv *erroringMockPV) SignProposal(chainID string, proposal *Proposal) error // NewErroringMockPV returns a MockPV that fails on each signing request. Again, for testing only. func NewErroringMockPV() *erroringMockPV { - return &erroringMockPV{&MockPV{ed25519.GenPrivKey()}} + return &erroringMockPV{&MockPV{ed25519.GenPrivKey(), false, false}} } From 354a08c25afbd7c3971ffa5ac2dac3d2ef0a8c07 Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Thu, 7 Feb 2019 06:58:23 -0500 Subject: [PATCH 261/267] p2p: fix infinite loop in addrbook (#3232) * failing test * fix infinite loop in addrbook There are cases where we only have a small number of addresses marked good ("old"), but the selection mechanism keeps trying to select more of these addresses, and hence ends up in an infinite loop. Here we fix this to only try and select such "old" addresses if we have enough of them. Note this means, if we don't have enough of them, we may return more "new" addresses than otherwise expected by the newSelectionBias. This whole GetSelectionWithBias method probably needs to be rewritten, but this is a quick fix for the issue. * changelog * fix infinite loop if not enough new addrs * fix another potential infinite loop if a.nNew == 0 -> pickFromOldBucket=true, but we don't have enough items (a.nOld > len(oldBucketToAddrsMap) false) * Revert "fix another potential infinite loop" This reverts commit 146540c1125597162bd89820d611f6531f5e5e4b. * check num addresses instead of buckets, new test * fixed the int division * add slack to bias % in test, lint fixes * Added checks for selection content in test * test cleanup * Apply suggestions from code review Co-Authored-By: ebuchman * address review comments * change after Anton's review comments * use the same docker image we use for testing when building a binary for localnet * switch back to circleci classic * more review comments * more review comments * refactor addrbook_test * build linux binary inside docker in attempt to fix ``` --> Running dep + make build-linux GOOS=linux GOARCH=amd64 make build make[1]: Entering directory `/home/circleci/.go_workspace/src/github.com/tendermint/tendermint' CGO_ENABLED=0 go build -ldflags "-X github.com/tendermint/tendermint/version.GitCommit=`git rev-parse --short=8 HEAD`" -tags 'tendermint' -o build/tendermint ./cmd/tendermint/ p2p/pex/addrbook.go:373:13: undefined: math.Round ``` * change dir from /usr to /go * use concrete Go version for localnet binary * check for nil addresses just to be sure --- .circleci/config.yml | 4 +- CHANGELOG_PENDING.md | 1 + Makefile | 2 +- p2p/pex/addrbook.go | 27 +++- p2p/pex/addrbook_test.go | 259 +++++++++++++++++++++++++++++++++++---- 5 files changed, 263 insertions(+), 30 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index d2968094..d6440de1 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -192,9 +192,7 @@ jobs: name: run localnet and exit on failure command: | set -x - make get_tools - make get_vendor_deps - make build-linux + docker run --rm -v "$PWD":/go/src/github.com/tendermint/tendermint -w /go/src/github.com/tendermint/tendermint golang:1.11.4 make build-linux make localnet-start & ./scripts/localnet-blocks-test.sh 40 5 10 localhost diff --git a/CHANGELOG_PENDING.md b/CHANGELOG_PENDING.md index 2c93469d..2c70ee5a 100644 --- a/CHANGELOG_PENDING.md +++ b/CHANGELOG_PENDING.md @@ -27,5 +27,6 @@ Special thanks to external contributors on this release: ### BUG FIXES: - [node] \#3186 EventBus and indexerService should be started before first block (for replay last block on handshake) execution +- [p2p] \#3232 Fix infinite loop leading to addrbook deadlock for seed nodes - [p2p] \#3247 Fix panic in SeedMode when calling FlushStop and OnStop concurrently diff --git a/Makefile b/Makefile index 4ebb88b0..a1ba06aa 100644 --- a/Makefile +++ b/Makefile @@ -269,7 +269,7 @@ build-docker: ### Local testnet using docker # Build linux binary on other platforms -build-linux: +build-linux: get_tools get_vendor_deps GOOS=linux GOARCH=amd64 $(MAKE) build build-docker-localnode: diff --git a/p2p/pex/addrbook.go b/p2p/pex/addrbook.go index cfeefb34..3cda9ac7 100644 --- a/p2p/pex/addrbook.go +++ b/p2p/pex/addrbook.go @@ -369,6 +369,10 @@ func (a *addrBook) GetSelection() []*p2p.NetAddress { return allAddr[:numAddresses] } +func percentageOfNum(p, n int) int { + return int(math.Round((float64(p) / float64(100)) * float64(n))) +} + // GetSelectionWithBias implements AddrBook. // It randomly selects some addresses (old & new). Suitable for peer-exchange protocols. // Must never return a nil address. @@ -408,11 +412,28 @@ func (a *addrBook) GetSelectionWithBias(biasTowardsNewAddrs int) []*p2p.NetAddre newBucketToAddrsMap := make(map[int]map[string]struct{}) var newIndex int + // initialize counters used to count old and new added addresses. + // len(oldBucketToAddrsMap) cannot be used as multiple addresses can endup in the same bucket. + var oldAddressesAdded int + var newAddressesAdded int + + // number of new addresses that, if possible, should be in the beginning of the selection + numRequiredNewAdd := percentageOfNum(biasTowardsNewAddrs, numAddresses) + selectionIndex := 0 ADDRS_LOOP: for selectionIndex < numAddresses { - pickFromOldBucket := int((float64(selectionIndex)/float64(numAddresses))*100) >= biasTowardsNewAddrs - pickFromOldBucket = (pickFromOldBucket && a.nOld > 0) || a.nNew == 0 + // biasedTowardsOldAddrs indicates if the selection can switch to old addresses + biasedTowardsOldAddrs := selectionIndex >= numRequiredNewAdd + // An old addresses is selected if: + // - the bias is for old and old addressees are still available or, + // - there are no new addresses or all new addresses have been selected. + // numAddresses <= a.nOld + a.nNew therefore it is guaranteed that there are enough + // addresses to fill the selection + pickFromOldBucket := + (biasedTowardsOldAddrs && oldAddressesAdded < a.nOld) || + a.nNew == 0 || newAddressesAdded >= a.nNew + bucket := make(map[string]*knownAddress) // loop until we pick a random non-empty bucket @@ -450,6 +471,7 @@ ADDRS_LOOP: oldBucketToAddrsMap[oldIndex] = make(map[string]struct{}) } oldBucketToAddrsMap[oldIndex][selectedAddr.String()] = struct{}{} + oldAddressesAdded++ } else { if addrsMap, ok := newBucketToAddrsMap[newIndex]; ok { if _, ok = addrsMap[selectedAddr.String()]; ok { @@ -459,6 +481,7 @@ ADDRS_LOOP: newBucketToAddrsMap[newIndex] = make(map[string]struct{}) } newBucketToAddrsMap[newIndex][selectedAddr.String()] = struct{}{} + newAddressesAdded++ } selection[selectionIndex] = selectedAddr diff --git a/p2p/pex/addrbook_test.go b/p2p/pex/addrbook_test.go index ade02d49..bac418ff 100644 --- a/p2p/pex/addrbook_test.go +++ b/p2p/pex/addrbook_test.go @@ -4,38 +4,18 @@ import ( "encoding/hex" "fmt" "io/ioutil" + "math" "os" "testing" - "github.com/stretchr/testify/require" - "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" cmn "github.com/tendermint/tendermint/libs/common" "github.com/tendermint/tendermint/libs/log" "github.com/tendermint/tendermint/p2p" ) -func createTempFileName(prefix string) string { - f, err := ioutil.TempFile("", prefix) - if err != nil { - panic(err) - } - fname := f.Name() - err = f.Close() - if err != nil { - panic(err) - } - return fname -} - -func deleteTempFile(fname string) { - err := os.Remove(fname) - if err != nil { - panic(err) - } -} - func TestAddrBookPickAddress(t *testing.T) { fname := createTempFileName("addrbook_test") defer deleteTempFile(fname) @@ -239,6 +219,34 @@ func TestAddrBookRemoveAddress(t *testing.T) { assert.Equal(t, 0, book.Size()) } +func TestAddrBookGetSelectionWithOneMarkedGood(t *testing.T) { + // create a book with 10 addresses, 1 good/old and 9 new + book, fname := createAddrBookWithMOldAndNNewAddrs(t, 1, 9) + defer deleteTempFile(fname) + + addrs := book.GetSelectionWithBias(biasToSelectNewPeers) + assert.NotNil(t, addrs) + assertMOldAndNNewAddrsInSelection(t, 1, 9, addrs, book) +} + +func TestAddrBookGetSelectionWithOneNotMarkedGood(t *testing.T) { + // create a book with 10 addresses, 9 good/old and 1 new + book, fname := createAddrBookWithMOldAndNNewAddrs(t, 9, 1) + defer deleteTempFile(fname) + + addrs := book.GetSelectionWithBias(biasToSelectNewPeers) + assert.NotNil(t, addrs) + assertMOldAndNNewAddrsInSelection(t, 9, 1, addrs, book) +} + +func TestAddrBookGetSelectionReturnsNilWhenAddrBookIsEmpty(t *testing.T) { + book, fname := createAddrBookWithMOldAndNNewAddrs(t, 0, 0) + defer deleteTempFile(fname) + + addrs := book.GetSelectionWithBias(biasToSelectNewPeers) + assert.Nil(t, addrs) +} + func TestAddrBookGetSelection(t *testing.T) { fname := createTempFileName("addrbook_test") defer deleteTempFile(fname) @@ -335,9 +343,16 @@ func TestAddrBookGetSelectionWithBias(t *testing.T) { good++ } } + got, expected := int((float64(good)/float64(len(selection)))*100), (100 - biasTowardsNewAddrs) - if got >= expected { - t.Fatalf("expected more good peers (%% got: %d, %% expected: %d, number of good addrs: %d, total: %d)", got, expected, good, len(selection)) + + // compute some slack to protect against small differences due to rounding: + slack := int(math.Round(float64(100) / float64(len(selection)))) + if got > expected+slack { + t.Fatalf("got more good peers (%% got: %d, %% expected: %d, number of good addrs: %d, total: %d)", got, expected, good, len(selection)) + } + if got < expected-slack { + t.Fatalf("got fewer good peers (%% got: %d, %% expected: %d, number of good addrs: %d, total: %d)", got, expected, good, len(selection)) } } @@ -417,3 +432,199 @@ func TestPrivatePeers(t *testing.T) { assert.True(t, ok) } } + +func testAddrBookAddressSelection(t *testing.T, bookSize int) { + // generate all combinations of old (m) and new addresses + for nOld := 0; nOld <= bookSize; nOld++ { + nNew := bookSize - nOld + dbgStr := fmt.Sprintf("book of size %d (new %d, old %d)", bookSize, nNew, nOld) + + // create book and get selection + book, fname := createAddrBookWithMOldAndNNewAddrs(t, nOld, nNew) + defer deleteTempFile(fname) + addrs := book.GetSelectionWithBias(biasToSelectNewPeers) + assert.NotNil(t, addrs, "%s - expected a non-nil selection", dbgStr) + nAddrs := len(addrs) + assert.NotZero(t, nAddrs, "%s - expected at least one address in selection", dbgStr) + + // check there's no nil addresses + for _, addr := range addrs { + if addr == nil { + t.Fatalf("%s - got nil address in selection %v", dbgStr, addrs) + } + } + + // XXX: shadowing + nOld, nNew := countOldAndNewAddrsInSelection(addrs, book) + + // Given: + // n - num new addrs, m - num old addrs + // k - num new addrs expected in the beginning (based on bias %) + // i=min(n, k), aka expFirstNew + // j=min(m, r-i), aka expOld + // + // We expect this layout: + // indices: 0...i-1 i...i+j-1 i+j...r + // addresses: N0..Ni-1 O0..Oj-1 Ni... + // + // There is at least one partition and at most three. + var ( + k = percentageOfNum(biasToSelectNewPeers, nAddrs) + expFirstNew = cmn.MinInt(nNew, k) + expOld = cmn.MinInt(nOld, nAddrs-expFirstNew) + expNew = nAddrs - expOld + expLastNew = expNew - expFirstNew + ) + + // Verify that the number of old and new addresses are as expected + if nNew < expNew || nNew > expNew { + t.Fatalf("%s - expected new addrs %d, got %d", dbgStr, expNew, nNew) + } + if nOld < expOld || nOld > expOld { + t.Fatalf("%s - expected old addrs %d, got %d", dbgStr, expOld, nOld) + } + + // Verify that the order of addresses is as expected + // Get the sequence types and lengths of the selection + seqLens, seqTypes, err := analyseSelectionLayout(book, addrs) + assert.NoError(t, err, "%s", dbgStr) + + // Build a list with the expected lengths of partitions and another with the expected types, e.g.: + // expSeqLens = [10, 22], expSeqTypes = [1, 2] + // means we expect 10 new (type 1) addresses followed by 22 old (type 2) addresses. + var expSeqLens []int + var expSeqTypes []int + + switch { + case expOld == 0: // all new addresses + expSeqLens = []int{nAddrs} + expSeqTypes = []int{1} + case expFirstNew == 0: // all old addresses + expSeqLens = []int{nAddrs} + expSeqTypes = []int{2} + case nAddrs-expFirstNew-expOld == 0: // new addresses, old addresses + expSeqLens = []int{expFirstNew, expOld} + expSeqTypes = []int{1, 2} + default: // new addresses, old addresses, new addresses + expSeqLens = []int{expFirstNew, expOld, expLastNew} + expSeqTypes = []int{1, 2, 1} + } + + assert.Equal(t, expSeqLens, seqLens, + "%s - expected sequence lengths of old/new %v, got %v", + dbgStr, expSeqLens, seqLens) + assert.Equal(t, expSeqTypes, seqTypes, + "%s - expected sequence types (1-new, 2-old) was %v, got %v", + dbgStr, expSeqTypes, seqTypes) + } +} + +func TestMultipleAddrBookAddressSelection(t *testing.T) { + // test books with smaller size, < N + const N = 32 + for bookSize := 1; bookSize < N; bookSize++ { + testAddrBookAddressSelection(t, bookSize) + } + + // Test for two books with sizes from following ranges + ranges := [...][]int{{33, 100}, {100, 175}} + var bookSizes []int + for _, r := range ranges { + bookSizes = append(bookSizes, cmn.RandIntn(r[1]-r[0])+r[0]) + } + t.Logf("Testing address selection for the following book sizes %v\n", bookSizes) + for _, bookSize := range bookSizes { + testAddrBookAddressSelection(t, bookSize) + } +} + +func assertMOldAndNNewAddrsInSelection(t *testing.T, m, n int, addrs []*p2p.NetAddress, book *addrBook) { + nOld, nNew := countOldAndNewAddrsInSelection(addrs, book) + assert.Equal(t, m, nOld, "old addresses") + assert.Equal(t, n, nNew, "new addresses") +} + +func createTempFileName(prefix string) string { + f, err := ioutil.TempFile("", prefix) + if err != nil { + panic(err) + } + fname := f.Name() + err = f.Close() + if err != nil { + panic(err) + } + return fname +} + +func deleteTempFile(fname string) { + err := os.Remove(fname) + if err != nil { + panic(err) + } +} + +func createAddrBookWithMOldAndNNewAddrs(t *testing.T, nOld, nNew int) (book *addrBook, fname string) { + fname = createTempFileName("addrbook_test") + + book = NewAddrBook(fname, true) + book.SetLogger(log.TestingLogger()) + assert.Zero(t, book.Size()) + + randAddrs := randNetAddressPairs(t, nOld) + for _, addr := range randAddrs { + book.AddAddress(addr.addr, addr.src) + book.MarkGood(addr.addr) + } + + randAddrs = randNetAddressPairs(t, nNew) + for _, addr := range randAddrs { + book.AddAddress(addr.addr, addr.src) + } + + return +} + +func countOldAndNewAddrsInSelection(addrs []*p2p.NetAddress, book *addrBook) (nOld, nNew int) { + for _, addr := range addrs { + if book.IsGood(addr) { + nOld++ + } else { + nNew++ + } + } + return +} + +// Analyse the layout of the selection specified by 'addrs' +// Returns: +// - seqLens - the lengths of the sequences of addresses of same type +// - seqTypes - the types of sequences in selection +func analyseSelectionLayout(book *addrBook, addrs []*p2p.NetAddress) (seqLens, seqTypes []int, err error) { + // address types are: 0 - nil, 1 - new, 2 - old + var ( + prevType = 0 + currentSeqLen = 0 + ) + + for _, addr := range addrs { + addrType := 0 + if book.IsGood(addr) { + addrType = 2 + } else { + addrType = 1 + } + if addrType != prevType && prevType != 0 { + seqLens = append(seqLens, currentSeqLen) + seqTypes = append(seqTypes, prevType) + currentSeqLen = 0 + } + currentSeqLen++ + prevType = addrType + } + + seqLens = append(seqLens, currentSeqLen) + seqTypes = append(seqTypes, prevType) + + return +} From 11e36d0bfb6e62bb3b758aa4a4cdf90fa2e89fdb Mon Sep 17 00:00:00 2001 From: Anton Kaliaev Date: Thu, 7 Feb 2019 17:16:31 +0400 Subject: [PATCH 262/267] addrbook_test: preallocate memory for bookSizes (#3268) Fixes https://circleci.com/gh/tendermint/tendermint/44901 --- p2p/pex/addrbook_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/p2p/pex/addrbook_test.go b/p2p/pex/addrbook_test.go index bac418ff..9effa5d0 100644 --- a/p2p/pex/addrbook_test.go +++ b/p2p/pex/addrbook_test.go @@ -528,7 +528,7 @@ func TestMultipleAddrBookAddressSelection(t *testing.T) { // Test for two books with sizes from following ranges ranges := [...][]int{{33, 100}, {100, 175}} - var bookSizes []int + bookSizes := make([]int, 0, len(ranges)) for _, r := range ranges { bookSizes = append(bookSizes, cmn.RandIntn(r[1]-r[0])+r[0]) } From f571ee8876d56d3b80a96019ba326fe8932ef116 Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Thu, 7 Feb 2019 19:34:01 -0500 Subject: [PATCH 263/267] prepare v0.29.2 (#3272) * update changelog * linkify * bump version * update main changelog * final fixes * entry for wal fix * changelog preamble * remove a line --- CHANGELOG.md | 41 +++++++++++++++++++++++++++++++++++++++++ CHANGELOG_PENDING.md | 23 ++--------------------- version/version.go | 2 +- 3 files changed, 44 insertions(+), 22 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 437b7970..779cb886 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,46 @@ # Changelog +## v0.29.2 + +*February 7th, 2019* + +Special thanks to external contributors on this release: +@ackratos, @rickyyangz + +**Note**: This release contains security sensitive patches in the `p2p` and +`crypto` packages: +- p2p: + - Partial fix for MITM attacks on the p2p connection. MITM conditions may + still exist. See \#3010. +- crypto: + - Eliminate our fork of `btcd` and use the `btcd/btcec` library directly for + native secp256k1 signing. Note we still modify the signature encoding to + prevent malleability. + - Support the libsecp256k1 library via CGo through the `go-ethereum/crypto/secp256k1` package. + +### BREAKING CHANGES: + +* Go API + - [types] [\#3245](https://github.com/tendermint/tendermint/issues/3245) Commit uses `type CommitSig Vote` instead of `Vote` directly. + In preparation for removing redundant fields from the commit [\#1648](https://github.com/tendermint/tendermint/issues/1648) + +### IMPROVEMENTS: +- [consensus] [\#3246](https://github.com/tendermint/tendermint/issues/3246) Better logging and notes on recovery for corrupted WAL file +- [crypto] [\#3163](https://github.com/tendermint/tendermint/issues/3163) Use ethereum's libsecp256k1 go-wrapper for signatures when cgo is available +- [crypto] [\#3162](https://github.com/tendermint/tendermint/issues/3162) Wrap btcd instead of forking it to keep up with fixes (used if cgo is not available) +- [makefile] [\#3233](https://github.com/tendermint/tendermint/issues/3233) Use golangci-lint instead of go-metalinter +- [tools] [\#3218](https://github.com/tendermint/tendermint/issues/3218) Add go-deadlock tool to help detect deadlocks +- [tools] [\#3106](https://github.com/tendermint/tendermint/issues/3106) Add tm-signer-harness test harness for remote signers +- [tests] [\#3258](https://github.com/tendermint/tendermint/issues/3258) Fixed a bunch of non-deterministic test failures + +### BUG FIXES: +- [node] [\#3186](https://github.com/tendermint/tendermint/issues/3186) EventBus and indexerService should be started before first block (for replay last block on handshake) execution (@ackratos) +- [p2p] [\#3232](https://github.com/tendermint/tendermint/issues/3232) Fix infinite loop leading to addrbook deadlock for seed nodes +- [p2p] [\#3247](https://github.com/tendermint/tendermint/issues/3247) Fix panic in SeedMode when calling FlushStop and OnStop + concurrently +- [p2p] [\#3040](https://github.com/tendermint/tendermint/issues/3040) Fix MITM on secret connection by checking low-order points +- [privval] [\#3258](https://github.com/tendermint/tendermint/issues/3258) Fix race between sign requests and ping requests in socket + ## v0.29.1 *January 24, 2019* diff --git a/CHANGELOG_PENDING.md b/CHANGELOG_PENDING.md index 2c70ee5a..4548eb1e 100644 --- a/CHANGELOG_PENDING.md +++ b/CHANGELOG_PENDING.md @@ -1,32 +1,13 @@ -## v0.30.0 +## v0.30 -*TBD* +** Special thanks to external contributors on this release: ### BREAKING CHANGES: -* CLI/RPC/Config - -* Apps - -* Go API - - [types] \#3245 Commit uses `type CommitSig Vote` instead of `Vote` directly. - -* Blockchain Protocol - -* P2P Protocol - ### FEATURES: ### IMPROVEMENTS: -- [tools] Add go-deadlock tool to help detect deadlocks -- [tools] \#3106 Add tm-signer-harness test harness for remote signers -- [crypto] \#3163 Use ethereum's libsecp256k1 go-wrapper for signatures when cgo is available -- [crypto] \#3162 Wrap btcd instead of forking it to keep up with fixes (used if cgo is not available) ### BUG FIXES: -- [node] \#3186 EventBus and indexerService should be started before first block (for replay last block on handshake) execution -- [p2p] \#3232 Fix infinite loop leading to addrbook deadlock for seed nodes -- [p2p] \#3247 Fix panic in SeedMode when calling FlushStop and OnStop - concurrently diff --git a/version/version.go b/version/version.go index 86c38c03..b20223c2 100644 --- a/version/version.go +++ b/version/version.go @@ -20,7 +20,7 @@ const ( // Must be a string because scripts like dist.sh read this file. // XXX: Don't change the name of this variable or you will break // automation :) - TMCoreSemVer = "0.29.1" + TMCoreSemVer = "0.29.2" // ABCISemVer is the semantic version of the ABCI library ABCISemVer = "0.15.0" From ad4bd92fec3bcba8715935c2c42eb27f68f5109a Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Thu, 7 Feb 2019 19:57:30 -0500 Subject: [PATCH 264/267] secp256k1: change build tags (#3277) --- crypto/secp256k1/secp256k1_cgo.go | 2 +- crypto/secp256k1/secp256k1_nocgo.go | 2 +- crypto/secp256k1/secp256k1_nocgo_test.go | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/crypto/secp256k1/secp256k1_cgo.go b/crypto/secp256k1/secp256k1_cgo.go index 30414d2b..3e5b1ddd 100644 --- a/crypto/secp256k1/secp256k1_cgo.go +++ b/crypto/secp256k1/secp256k1_cgo.go @@ -1,4 +1,4 @@ -// +build cgo +// +build libsecp256k1 package secp256k1 diff --git a/crypto/secp256k1/secp256k1_nocgo.go b/crypto/secp256k1/secp256k1_nocgo.go index 34b006fa..052c3d14 100644 --- a/crypto/secp256k1/secp256k1_nocgo.go +++ b/crypto/secp256k1/secp256k1_nocgo.go @@ -1,4 +1,4 @@ -// +build !cgo +// +build !libsecp256k1 package secp256k1 diff --git a/crypto/secp256k1/secp256k1_nocgo_test.go b/crypto/secp256k1/secp256k1_nocgo_test.go index 95966478..a06a0e3d 100644 --- a/crypto/secp256k1/secp256k1_nocgo_test.go +++ b/crypto/secp256k1/secp256k1_nocgo_test.go @@ -1,4 +1,4 @@ -// +build !cgo +// +build !libsecp256k1 package secp256k1 From af6e6cd350541147aed50c4e936f6ba596231027 Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Thu, 7 Feb 2019 20:12:57 -0500 Subject: [PATCH 265/267] remove MixEntropy (#3278) * remove MixEntropy * changelog --- CHANGELOG.md | 3 + README.md | 1 + crypto/random.go | 104 --------------------- crypto/xsalsa20symmetric/symmetric.go | 1 - crypto/xsalsa20symmetric/symmetric_test.go | 4 - 5 files changed, 4 insertions(+), 109 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 779cb886..a0e736bc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,10 +17,13 @@ Special thanks to external contributors on this release: native secp256k1 signing. Note we still modify the signature encoding to prevent malleability. - Support the libsecp256k1 library via CGo through the `go-ethereum/crypto/secp256k1` package. + - Eliminate MixEntropy functions ### BREAKING CHANGES: * Go API + - [crypto] [\#3278](https://github.com/tendermint/tendermint/issues/3278) Remove + MixEntropy functions - [types] [\#3245](https://github.com/tendermint/tendermint/issues/3245) Commit uses `type CommitSig Vote` instead of `Vote` directly. In preparation for removing redundant fields from the commit [\#1648](https://github.com/tendermint/tendermint/issues/1648) diff --git a/README.md b/README.md index 601e3830..9251e3ca 100644 --- a/README.md +++ b/README.md @@ -96,6 +96,7 @@ include the in-process Go APIs. That said, breaking changes in the following packages will be documented in the CHANGELOG even if they don't lead to MINOR version bumps: +- crypto - types - rpc/client - config diff --git a/crypto/random.go b/crypto/random.go index 914c321b..275fb104 100644 --- a/crypto/random.go +++ b/crypto/random.go @@ -1,42 +1,11 @@ package crypto import ( - "crypto/cipher" crand "crypto/rand" - "crypto/sha256" "encoding/hex" "io" - "sync" - - "golang.org/x/crypto/chacha20poly1305" ) -// NOTE: This is ignored for now until we have time -// to properly review the MixEntropy function - https://github.com/tendermint/tendermint/issues/2099. -// -// The randomness here is derived from xoring a chacha20 keystream with -// output from crypto/rand's OS Entropy Reader. (Due to fears of the OS' -// entropy being backdoored) -// -// For forward secrecy of produced randomness, the internal chacha key is hashed -// and thereby rotated after each call. -var gRandInfo *randInfo - -func init() { - gRandInfo = &randInfo{} - - // TODO: uncomment after reviewing MixEntropy - - // https://github.com/tendermint/tendermint/issues/2099 - // gRandInfo.MixEntropy(randBytes(32)) // Init -} - -// WARNING: This function needs review - https://github.com/tendermint/tendermint/issues/2099. -// Mix additional bytes of randomness, e.g. from hardware, user-input, etc. -// It is OK to call it multiple times. It does not diminish security. -func MixEntropy(seedBytes []byte) { - gRandInfo.MixEntropy(seedBytes) -} - // This only uses the OS's randomness func randBytes(numBytes int) []byte { b := make([]byte, numBytes) @@ -52,19 +21,6 @@ func CRandBytes(numBytes int) []byte { return randBytes(numBytes) } -/* TODO: uncomment after reviewing MixEntropy - https://github.com/tendermint/tendermint/issues/2099 -// This uses the OS and the Seed(s). -func CRandBytes(numBytes int) []byte { - return randBytes(numBytes) - b := make([]byte, numBytes) - _, err := gRandInfo.Read(b) - if err != nil { - panic(err) - } - return b -} -*/ - // CRandHex returns a hex encoded string that's floor(numDigits/2) * 2 long. // // Note: CRandHex(24) gives 96 bits of randomness that @@ -77,63 +33,3 @@ func CRandHex(numDigits int) string { func CReader() io.Reader { return crand.Reader } - -/* TODO: uncomment after reviewing MixEntropy - https://github.com/tendermint/tendermint/issues/2099 -// Returns a crand.Reader mixed with user-supplied entropy -func CReader() io.Reader { - return gRandInfo -} -*/ - -//-------------------------------------------------------------------------------- - -type randInfo struct { - mtx sync.Mutex - seedBytes [chacha20poly1305.KeySize]byte - chacha cipher.AEAD - reader io.Reader -} - -// You can call this as many times as you'd like. -// XXX/TODO: review - https://github.com/tendermint/tendermint/issues/2099 -func (ri *randInfo) MixEntropy(seedBytes []byte) { - ri.mtx.Lock() - defer ri.mtx.Unlock() - // Make new ri.seedBytes using passed seedBytes and current ri.seedBytes: - // ri.seedBytes = sha256( seedBytes || ri.seedBytes ) - h := sha256.New() - h.Write(seedBytes) - h.Write(ri.seedBytes[:]) - hashBytes := h.Sum(nil) - copy(ri.seedBytes[:], hashBytes) - chacha, err := chacha20poly1305.New(ri.seedBytes[:]) - if err != nil { - panic("Initializing chacha20 failed") - } - ri.chacha = chacha - // Create new reader - ri.reader = &cipher.StreamReader{S: ri, R: crand.Reader} -} - -func (ri *randInfo) XORKeyStream(dst, src []byte) { - // nonce being 0 is safe due to never re-using a key. - emptyNonce := make([]byte, 12) - tmpDst := ri.chacha.Seal([]byte{}, emptyNonce, src, []byte{0}) - // this removes the poly1305 tag as well, since chacha is a stream cipher - // and we truncate at input length. - copy(dst, tmpDst[:len(src)]) - // hash seedBytes for forward secrecy, and initialize new chacha instance - newSeed := sha256.Sum256(ri.seedBytes[:]) - chacha, err := chacha20poly1305.New(newSeed[:]) - if err != nil { - panic("Initializing chacha20 failed") - } - ri.chacha = chacha -} - -func (ri *randInfo) Read(b []byte) (n int, err error) { - ri.mtx.Lock() - n, err = ri.reader.Read(b) - ri.mtx.Unlock() - return -} diff --git a/crypto/xsalsa20symmetric/symmetric.go b/crypto/xsalsa20symmetric/symmetric.go index 3228a935..10a0f6f3 100644 --- a/crypto/xsalsa20symmetric/symmetric.go +++ b/crypto/xsalsa20symmetric/symmetric.go @@ -17,7 +17,6 @@ const secretLen = 32 // secret must be 32 bytes long. Use something like Sha256(Bcrypt(passphrase)) // The ciphertext is (secretbox.Overhead + 24) bytes longer than the plaintext. -// NOTE: call crypto.MixEntropy() first. func EncryptSymmetric(plaintext []byte, secret []byte) (ciphertext []byte) { if len(secret) != secretLen { cmn.PanicSanity(fmt.Sprintf("Secret must be 32 bytes long, got len %v", len(secret))) diff --git a/crypto/xsalsa20symmetric/symmetric_test.go b/crypto/xsalsa20symmetric/symmetric_test.go index bca0b336..160d49a9 100644 --- a/crypto/xsalsa20symmetric/symmetric_test.go +++ b/crypto/xsalsa20symmetric/symmetric_test.go @@ -13,8 +13,6 @@ import ( func TestSimple(t *testing.T) { - crypto.MixEntropy([]byte("someentropy")) - plaintext := []byte("sometext") secret := []byte("somesecretoflengththirtytwo===32") ciphertext := EncryptSymmetric(plaintext, secret) @@ -26,8 +24,6 @@ func TestSimple(t *testing.T) { func TestSimpleWithKDF(t *testing.T) { - crypto.MixEntropy([]byte("someentropy")) - plaintext := []byte("sometext") secretPass := []byte("somesecret") secret, err := bcrypt.GenerateFromPassword(secretPass, 12) From c1f7399a86f61bcf26791b9e0a6cf30b28e2048c Mon Sep 17 00:00:00 2001 From: Ismail Khoffi Date: Fri, 8 Feb 2019 15:48:09 +0100 Subject: [PATCH 266/267] review comment: cleaner constant for N/2, delete secp256k1N and use (#3279) `secp256k1.S256().N` directly instead --- crypto/secp256k1/secp256k1_nocgo.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/crypto/secp256k1/secp256k1_nocgo.go b/crypto/secp256k1/secp256k1_nocgo.go index 052c3d14..cd1655a5 100644 --- a/crypto/secp256k1/secp256k1_nocgo.go +++ b/crypto/secp256k1/secp256k1_nocgo.go @@ -14,8 +14,7 @@ import ( // see: // - https://github.com/ethereum/go-ethereum/blob/f9401ae011ddf7f8d2d95020b7446c17f8d98dc1/crypto/signature_nocgo.go#L90-L93 // - https://github.com/ethereum/go-ethereum/blob/f9401ae011ddf7f8d2d95020b7446c17f8d98dc1/crypto/crypto.go#L39 -var secp256k1N, _ = new(big.Int).SetString("fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141", 16) -var secp256k1halfN = new(big.Int).Div(secp256k1N, big.NewInt(2)) +var secp256k1halfN = new(big.Int).Rsh(secp256k1.S256().N, 1) // Sign creates an ECDSA signature on curve Secp256k1, using SHA256 on the msg. // The returned signature will be of the form R || S (in lower-S form). From cce4d21ccbaa94163b393dd4dc1fd7c202d4feb7 Mon Sep 17 00:00:00 2001 From: Anca Zamfir Date: Fri, 8 Feb 2019 19:05:09 +0100 Subject: [PATCH 267/267] treat validator updates as set (#3222) * Initial commit for 3181..still early * unit test updates * unit test updates * fix check of dups accross updates and deletes * simplify the processChange() func * added overflow check utest * Added checks for empty valset, new utest * deepcopy changes in processUpdate() * moved to new API, fixed tests * test cleanup * address review comments * make sure votePower > 0 * gofmt fixes * handle duplicates and invalid values * more work on tests, review comments * Renamed and explained K * make TestVal private * split verifyUpdatesAndComputeNewPriorities.., added check for deletes * return error if validator set is empty after processing changes * address review comments * lint err * Fixed the total voting power and added comments * fix lint * fix lint --- state/execution.go | 79 +------- state/execution_test.go | 2 +- state/state_test.go | 137 ++++++++----- types/validator.go | 11 ++ types/validator_set.go | 363 +++++++++++++++++++++++++++------- types/validator_set_test.go | 382 ++++++++++++++++++++++++++++++++++-- 6 files changed, 756 insertions(+), 218 deletions(-) diff --git a/state/execution.go b/state/execution.go index 85477eeb..470e22bc 100644 --- a/state/execution.go +++ b/state/execution.go @@ -2,7 +2,6 @@ package state import ( "fmt" - "strings" "time" abci "github.com/tendermint/tendermint/abci/types" @@ -143,7 +142,7 @@ func (blockExec *BlockExecutor) ApplyBlock(state State, blockID types.BlockID, b return state, err } if len(validatorUpdates) > 0 { - blockExec.logger.Info("Updates to validators", "updates", makeValidatorUpdatesLogString(validatorUpdates)) + blockExec.logger.Info("Updates to validators", "updates", types.ValidatorListString(validatorUpdates)) } // Update the state with the block and responses. @@ -368,70 +367,6 @@ func validateValidatorUpdates(abciUpdates []abci.ValidatorUpdate, return nil } -// If more or equal than 1/3 of total voting power changed in one block, then -// a light client could never prove the transition externally. See -// ./lite/doc.go for details on how a light client tracks validators. -func updateValidators(currentSet *types.ValidatorSet, updates []*types.Validator) error { - for _, valUpdate := range updates { - // should already have been checked - if valUpdate.VotingPower < 0 { - return fmt.Errorf("Voting power can't be negative %v", valUpdate) - } - - address := valUpdate.Address - _, val := currentSet.GetByAddress(address) - // valUpdate.VotingPower is ensured to be non-negative in validation method - if valUpdate.VotingPower == 0 { // remove val - _, removed := currentSet.Remove(address) - if !removed { - return fmt.Errorf("Failed to remove validator %X", address) - } - } else if val == nil { // add val - // make sure we do not exceed MaxTotalVotingPower by adding this validator: - totalVotingPower := currentSet.TotalVotingPower() - updatedVotingPower := valUpdate.VotingPower + totalVotingPower - overflow := updatedVotingPower > types.MaxTotalVotingPower || updatedVotingPower < 0 - if overflow { - return fmt.Errorf( - "Failed to add new validator %v. Adding it would exceed max allowed total voting power %v", - valUpdate, - types.MaxTotalVotingPower) - } - // TODO: issue #1558 update spec according to the following: - // Set ProposerPriority to -C*totalVotingPower (with C ~= 1.125) to make sure validators can't - // unbond/rebond to reset their (potentially previously negative) ProposerPriority to zero. - // - // Contract: totalVotingPower < MaxTotalVotingPower to ensure ProposerPriority does - // not exceed the bounds of int64. - // - // Compute ProposerPriority = -1.125*totalVotingPower == -(totalVotingPower + (totalVotingPower >> 3)). - valUpdate.ProposerPriority = -(totalVotingPower + (totalVotingPower >> 3)) - added := currentSet.Add(valUpdate) - if !added { - return fmt.Errorf("Failed to add new validator %v", valUpdate) - } - } else { // update val - // make sure we do not exceed MaxTotalVotingPower by updating this validator: - totalVotingPower := currentSet.TotalVotingPower() - curVotingPower := val.VotingPower - updatedVotingPower := totalVotingPower - curVotingPower + valUpdate.VotingPower - overflow := updatedVotingPower > types.MaxTotalVotingPower || updatedVotingPower < 0 - if overflow { - return fmt.Errorf( - "Failed to update existing validator %v. Updating it would exceed max allowed total voting power %v", - valUpdate, - types.MaxTotalVotingPower) - } - - updated := currentSet.Update(valUpdate) - if !updated { - return fmt.Errorf("Failed to update validator %X to %v", address, valUpdate) - } - } - } - return nil -} - // updateState returns a new State updated according to the header and responses. func updateState( state State, @@ -448,7 +383,7 @@ func updateState( // Update the validator set with the latest abciResponses. lastHeightValsChanged := state.LastHeightValidatorsChanged if len(validatorUpdates) > 0 { - err := updateValidators(nValSet, validatorUpdates) + err := nValSet.UpdateWithChangeSet(validatorUpdates) if err != nil { return state, fmt.Errorf("Error changing validator set: %v", err) } @@ -552,13 +487,3 @@ func ExecCommitBlock( // ResponseCommit has no error or log, just data return res.Data, nil } - -// Make pretty string for validatorUpdates logging -func makeValidatorUpdatesLogString(vals []*types.Validator) string { - chunks := make([]string, len(vals)) - for i, val := range vals { - chunks[i] = fmt.Sprintf("%s:%d", val.Address, val.VotingPower) - } - - return strings.Join(chunks, ",") -} diff --git a/state/execution_test.go b/state/execution_test.go index 041fb558..e0c9b4b9 100644 --- a/state/execution_test.go +++ b/state/execution_test.go @@ -280,7 +280,7 @@ func TestUpdateValidators(t *testing.T) { t.Run(tc.name, func(t *testing.T) { updates, err := types.PB2TM.ValidatorUpdates(tc.abciUpdates) assert.NoError(t, err) - err = updateValidators(tc.currentSet, updates) + err = tc.currentSet.UpdateWithChangeSet(updates) if tc.shouldErr { assert.Error(t, err) } else { diff --git a/state/state_test.go b/state/state_test.go index 904d7a10..9cbe8342 100644 --- a/state/state_test.go +++ b/state/state_test.go @@ -322,7 +322,8 @@ func TestProposerFrequency(t *testing.T) { vals := make([]*types.Validator, N) totalVotePower := int64(0) for j := 0; j < N; j++ { - votePower := int64(cmn.RandInt() % maxPower) + // make sure votePower > 0 + votePower := int64(cmn.RandInt()%maxPower) + 1 totalVotePower += votePower privVal := types.NewMockPV() pubKey := privVal.GetPubKey() @@ -424,49 +425,71 @@ func TestProposerPriorityDoesNotGetResetToZero(t *testing.T) { require.Equal(t, len(updatedState2.NextValidators.Validators), 2) _, updatedVal1 := updatedState2.NextValidators.GetByAddress(val1PubKey.Address()) _, addedVal2 := updatedState2.NextValidators.GetByAddress(val2PubKey.Address()) + // adding a validator should not lead to a ProposerPriority equal to zero (unless the combination of averaging and // incrementing would cause so; which is not the case here) - totalPowerBefore2 := curTotal - // while adding we compute prio == -1.125 * total: - wantVal2ProposerPrio := -(totalPowerBefore2 + (totalPowerBefore2 >> 3)) - wantVal2ProposerPrio = wantVal2ProposerPrio + val2VotingPower - // then increment: + // Steps from adding new validator: + // 0 - val1 prio is 0, TVP after add: + wantVal1Prio := int64(0) totalPowerAfter := val1VotingPower + val2VotingPower - // mostest: - wantVal2ProposerPrio = wantVal2ProposerPrio - totalPowerAfter - avg := big.NewInt(0).Add(big.NewInt(val1VotingPower), big.NewInt(wantVal2ProposerPrio)) + // 1. Add - Val2 should be initially added with (-123) => + wantVal2Prio := -(totalPowerAfter + (totalPowerAfter >> 3)) + // 2. Scale - noop + // 3. Center - with avg, resulting val2:-61, val1:62 + avg := big.NewInt(0).Add(big.NewInt(wantVal1Prio), big.NewInt(wantVal2Prio)) avg.Div(avg, big.NewInt(2)) - wantVal2ProposerPrio = wantVal2ProposerPrio - avg.Int64() - wantVal1Prio := 0 + val1VotingPower - avg.Int64() + wantVal2Prio = wantVal2Prio - avg.Int64() // -61 + wantVal1Prio = wantVal1Prio - avg.Int64() // 62 + + // 4. Steps from IncrementProposerPriority + wantVal1Prio = wantVal1Prio + val1VotingPower // 72 + wantVal2Prio = wantVal2Prio + val2VotingPower // 39 + wantVal1Prio = wantVal1Prio - totalPowerAfter // -38 as val1 is proposer + assert.Equal(t, wantVal1Prio, updatedVal1.ProposerPriority) - assert.Equal(t, wantVal2ProposerPrio, addedVal2.ProposerPriority) + assert.Equal(t, wantVal2Prio, addedVal2.ProposerPriority) // Updating a validator does not reset the ProposerPriority to zero: + // 1. Add - Val2 VotingPower change to 1 => updatedVotingPowVal2 := int64(1) updateVal := abci.ValidatorUpdate{PubKey: types.TM2PB.PubKey(val2PubKey), Power: updatedVotingPowVal2} validatorUpdates, err = types.PB2TM.ValidatorUpdates([]abci.ValidatorUpdate{updateVal}) assert.NoError(t, err) - // this will cause the diff of priorities (31) + // this will cause the diff of priorities (77) // to be larger than threshold == 2*totalVotingPower (22): updatedState3, err := updateState(updatedState2, blockID, &block.Header, abciResponses, validatorUpdates) assert.NoError(t, err) require.Equal(t, len(updatedState3.NextValidators.Validators), 2) _, prevVal1 := updatedState3.Validators.GetByAddress(val1PubKey.Address()) + _, prevVal2 := updatedState3.Validators.GetByAddress(val2PubKey.Address()) + _, updatedVal1 = updatedState3.NextValidators.GetByAddress(val1PubKey.Address()) _, updatedVal2 := updatedState3.NextValidators.GetByAddress(val2PubKey.Address()) - // divide previous priorities by 2 == CEIL(31/22) as diff > threshold: - expectedVal1PrioBeforeAvg := prevVal1.ProposerPriority/2 + prevVal1.VotingPower - wantVal2ProposerPrio = wantVal2ProposerPrio/2 + updatedVotingPowVal2 - // val1 will be proposer: - total := val1VotingPower + updatedVotingPowVal2 - expectedVal1PrioBeforeAvg = expectedVal1PrioBeforeAvg - total - avgI64 := (wantVal2ProposerPrio + expectedVal1PrioBeforeAvg) / 2 - wantVal2ProposerPrio = wantVal2ProposerPrio - avgI64 - wantVal1Prio = expectedVal1PrioBeforeAvg - avgI64 - assert.Equal(t, wantVal2ProposerPrio, updatedVal2.ProposerPriority) - _, updatedVal1 = updatedState3.NextValidators.GetByAddress(val1PubKey.Address()) + // 2. Scale + // old prios: v1(10):-38, v2(1):39 + wantVal1Prio = prevVal1.ProposerPriority + wantVal2Prio = prevVal2.ProposerPriority + // scale to diffMax = 22 = 2 * tvp, diff=39-(-38)=77 + // new totalPower + totalPower := updatedVal1.VotingPower + updatedVal2.VotingPower + dist := wantVal2Prio - wantVal1Prio + // ratio := (dist + 2*totalPower - 1) / 2*totalPower = 98/22 = 4 + ratio := (dist + 2*totalPower - 1) / (2 * totalPower) + // v1(10):-38/4, v2(1):39/4 + wantVal1Prio /= ratio // -9 + wantVal2Prio /= ratio // 9 + + // 3. Center - noop + // 4. IncrementProposerPriority() -> + // v1(10):-9+10, v2(1):9+1 -> v2 proposer so subsract tvp(11) + // v1(10):1, v2(1):-1 + wantVal2Prio += updatedVal2.VotingPower // 10 -> prop + wantVal1Prio += updatedVal1.VotingPower // 1 + wantVal2Prio -= totalPower // -1 + + assert.Equal(t, wantVal2Prio, updatedVal2.ProposerPriority) assert.Equal(t, wantVal1Prio, updatedVal1.ProposerPriority) } @@ -527,22 +550,22 @@ func TestProposerPriorityProposerAlternates(t *testing.T) { _, oldVal1 := updatedState2.Validators.GetByAddress(val1PubKey.Address()) _, updatedVal2 := updatedState2.NextValidators.GetByAddress(val2PubKey.Address()) - totalPower = val1VotingPower // no update - v2PrioWhenAddedVal2 := -(totalPower + (totalPower >> 3)) - v2PrioWhenAddedVal2 = v2PrioWhenAddedVal2 + val1VotingPower // -11 + 10 == -1 - v1PrioWhenAddedVal2 := oldVal1.ProposerPriority + val1VotingPower // -10 + 10 == 0 - totalPower = 2 * val1VotingPower // now we have to validators with that power - v1PrioWhenAddedVal2 = v1PrioWhenAddedVal2 - totalPower // mostest - // have to express the AVG in big.Ints as -1/2 == -1 in big.Int while -1/2 == 0 in int64 - avgSum := big.NewInt(0).Add(big.NewInt(v2PrioWhenAddedVal2), big.NewInt(v1PrioWhenAddedVal2)) - avg := avgSum.Div(avgSum, big.NewInt(2)) - expectedVal2Prio := v2PrioWhenAddedVal2 - avg.Int64() - totalPower = 2 * val1VotingPower // 10 + 10 - expectedVal1Prio := oldVal1.ProposerPriority + val1VotingPower - avg.Int64() - totalPower - // val1's ProposerPriority story: -10 (see above) + 10 (voting pow) - (-1) (avg) - 20 (total) == -19 + // 1. Add + val2VotingPower := val1VotingPower + totalPower = val1VotingPower + val2VotingPower // 20 + v2PrioWhenAddedVal2 := -(totalPower + (totalPower >> 3)) // -22 + // 2. Scale - noop + // 3. Center + avgSum := big.NewInt(0).Add(big.NewInt(v2PrioWhenAddedVal2), big.NewInt(oldVal1.ProposerPriority)) + avg := avgSum.Div(avgSum, big.NewInt(2)) // -11 + expectedVal2Prio := v2PrioWhenAddedVal2 - avg.Int64() // -11 + expectedVal1Prio := oldVal1.ProposerPriority - avg.Int64() // 11 + // 4. Increment + expectedVal2Prio = expectedVal2Prio + val2VotingPower // -11 + 10 = -1 + expectedVal1Prio = expectedVal1Prio + val1VotingPower // 11 + 10 == 21 + expectedVal1Prio = expectedVal1Prio - totalPower // 1, val1 proposer + assert.EqualValues(t, expectedVal1Prio, updatedVal1.ProposerPriority) - // val2 prio when added: -(totalVotingPower + (totalVotingPower >> 3)) == -11 - // -> -11 + 10 (voting power) - (-1) (avg) == 0 assert.EqualValues(t, expectedVal2Prio, updatedVal2.ProposerPriority, "unexpected proposer priority for validator: %v", updatedVal2) validatorUpdates, err = types.PB2TM.ValidatorUpdates(abciResponses.EndBlock.ValidatorUpdates) @@ -551,34 +574,40 @@ func TestProposerPriorityProposerAlternates(t *testing.T) { updatedState3, err := updateState(updatedState2, blockID, &block.Header, abciResponses, validatorUpdates) assert.NoError(t, err) - // proposer changes from now on (every iteration) as long as there are no changes in the validator set: - assert.NotEqual(t, updatedState3.Validators.Proposer.Address, updatedState3.NextValidators.Proposer.Address) + assert.Equal(t, updatedState3.Validators.Proposer.Address, updatedState3.NextValidators.Proposer.Address) assert.Equal(t, updatedState3.Validators, updatedState2.NextValidators) _, updatedVal1 = updatedState3.NextValidators.GetByAddress(val1PubKey.Address()) - _, oldVal1 = updatedState3.Validators.GetByAddress(val1PubKey.Address()) _, updatedVal2 = updatedState3.NextValidators.GetByAddress(val2PubKey.Address()) - _, oldVal2 := updatedState3.Validators.GetByAddress(val2PubKey.Address()) - // val2 will be proposer: - assert.Equal(t, val2PubKey.Address(), updatedState3.NextValidators.Proposer.Address) + // val1 will still be proposer: + assert.Equal(t, val1PubKey.Address(), updatedState3.NextValidators.Proposer.Address) + // check if expected proposer prio is matched: + // Increment + expectedVal2Prio2 := expectedVal2Prio + val2VotingPower // -1 + 10 = 9 + expectedVal1Prio2 := expectedVal1Prio + val1VotingPower // 1 + 10 == 11 + expectedVal1Prio2 = expectedVal1Prio2 - totalPower // -9, val1 proposer - expectedVal1Prio2 := oldVal1.ProposerPriority + val1VotingPower - expectedVal2Prio2 := oldVal2.ProposerPriority + val1VotingPower - totalPower - avgSum = big.NewInt(expectedVal1Prio + expectedVal2Prio) - avg = avgSum.Div(avgSum, big.NewInt(2)) - expectedVal1Prio -= avg.Int64() - expectedVal2Prio -= avg.Int64() - - // -19 + 10 - 0 (avg) == -9 assert.EqualValues(t, expectedVal1Prio2, updatedVal1.ProposerPriority, "unexpected proposer priority for validator: %v", updatedVal2) - // 0 + 10 - 0 (avg) - 20 (total) == -10 assert.EqualValues(t, expectedVal2Prio2, updatedVal2.ProposerPriority, "unexpected proposer priority for validator: %v", updatedVal2) // no changes in voting power and both validators have same voting power // -> proposers should alternate: oldState := updatedState3 + abciResponses = &ABCIResponses{ + EndBlock: &abci.ResponseEndBlock{ValidatorUpdates: nil}, + } + validatorUpdates, err = types.PB2TM.ValidatorUpdates(abciResponses.EndBlock.ValidatorUpdates) + require.NoError(t, err) + + oldState, err = updateState(oldState, blockID, &block.Header, abciResponses, validatorUpdates) + assert.NoError(t, err) + expectedVal1Prio2 = 1 + expectedVal2Prio2 = -1 + expectedVal1Prio = -9 + expectedVal2Prio = 9 + for i := 0; i < 1000; i++ { // no validator updates: abciResponses := &ABCIResponses{ diff --git a/types/validator.go b/types/validator.go index 0b8967b2..325d20f5 100644 --- a/types/validator.go +++ b/types/validator.go @@ -3,6 +3,7 @@ package types import ( "bytes" "fmt" + "strings" "github.com/tendermint/tendermint/crypto" cmn "github.com/tendermint/tendermint/libs/common" @@ -68,6 +69,16 @@ func (v *Validator) String() string { v.ProposerPriority) } +// ValidatorListString returns a prettified validator list for logging purposes. +func ValidatorListString(vals []*Validator) string { + chunks := make([]string, len(vals)) + for i, val := range vals { + chunks[i] = fmt.Sprintf("%s:%d", val.Address, val.VotingPower) + } + + return strings.Join(chunks, ",") +} + // Bytes computes the unique encoding of a validator with a given voting power. // These are the bytes that gets hashed in consensus. It excludes address // as its redundant with the pubkey. This also excludes ProposerPriority diff --git a/types/validator_set.go b/types/validator_set.go index 2edec595..c70f3396 100644 --- a/types/validator_set.go +++ b/types/validator_set.go @@ -12,14 +12,20 @@ import ( cmn "github.com/tendermint/tendermint/libs/common" ) -// The maximum allowed total voting power. -// It needs to be sufficiently small to, in all cases:: +// MaxTotalVotingPower - the maximum allowed total voting power. +// It needs to be sufficiently small to, in all cases: // 1. prevent clipping in incrementProposerPriority() -// 2. let (diff+diffMax-1) not overflow in IncrementPropposerPriotity() +// 2. let (diff+diffMax-1) not overflow in IncrementProposerPriority() // (Proof of 1 is tricky, left to the reader). // It could be higher, but this is sufficiently large for our purposes, // and leaves room for defensive purposes. -const MaxTotalVotingPower = int64(math.MaxInt64) / 8 +// PriorityWindowSizeFactor - is a constant that when multiplied with the total voting power gives +// the maximum allowed distance between validator priorities. + +const ( + MaxTotalVotingPower = int64(math.MaxInt64) / 8 + PriorityWindowSizeFactor = 2 +) // ValidatorSet represent a set of *Validator at a given height. // The validators can be fetched by address or index. @@ -42,19 +48,17 @@ type ValidatorSet struct { // NewValidatorSet initializes a ValidatorSet by copying over the // values from `valz`, a list of Validators. If valz is nil or empty, // the new ValidatorSet will have an empty list of Validators. +// The addresses of validators in `valz` must be unique otherwise the +// function panics. func NewValidatorSet(valz []*Validator) *ValidatorSet { - validators := make([]*Validator, len(valz)) - for i, val := range valz { - validators[i] = val.Copy() - } - sort.Sort(ValidatorsByAddress(validators)) - vals := &ValidatorSet{ - Validators: validators, + vals := &ValidatorSet{} + err := vals.updateWithChangeSet(valz, false) + if err != nil { + panic(fmt.Sprintf("cannot create validator set: %s", err)) } if len(valz) > 0 { vals.IncrementProposerPriority(1) } - return vals } @@ -74,6 +78,9 @@ func (vals *ValidatorSet) CopyIncrementProposerPriority(times int) *ValidatorSet // proposer. Panics if validator set is empty. // `times` must be positive. func (vals *ValidatorSet) IncrementProposerPriority(times int) { + if vals.IsNilOrEmpty() { + panic("empty validator set") + } if times <= 0 { panic("Cannot call IncrementProposerPriority with non-positive times") } @@ -81,20 +88,23 @@ func (vals *ValidatorSet) IncrementProposerPriority(times int) { // Cap the difference between priorities to be proportional to 2*totalPower by // re-normalizing priorities, i.e., rescale all priorities by multiplying with: // 2*totalVotingPower/(maxPriority - minPriority) - diffMax := 2 * vals.TotalVotingPower() + diffMax := PriorityWindowSizeFactor * vals.TotalVotingPower() vals.RescalePriorities(diffMax) + vals.shiftByAvgProposerPriority() var proposer *Validator // call IncrementProposerPriority(1) times times: for i := 0; i < times; i++ { proposer = vals.incrementProposerPriority() } - vals.shiftByAvgProposerPriority() vals.Proposer = proposer } func (vals *ValidatorSet) RescalePriorities(diffMax int64) { + if vals.IsNilOrEmpty() { + panic("empty validator set") + } // NOTE: This check is merely a sanity check which could be // removed if all tests would init. voting power appropriately; // i.e. diffMax should always be > 0 @@ -102,7 +112,7 @@ func (vals *ValidatorSet) RescalePriorities(diffMax int64) { return } - // Caculating ceil(diff/diffMax): + // Calculating ceil(diff/diffMax): // Re-normalization is performed by dividing by an integer for simplicity. // NOTE: This may make debugging priority issues easier as well. diff := computeMaxMinPriorityDiff(vals) @@ -146,6 +156,9 @@ func (vals *ValidatorSet) computeAvgProposerPriority() int64 { // compute the difference between the max and min ProposerPriority of that set func computeMaxMinPriorityDiff(vals *ValidatorSet) int64 { + if vals.IsNilOrEmpty() { + panic("empty validator set") + } max := int64(math.MinInt64) min := int64(math.MaxInt64) for _, v := range vals.Validators { @@ -173,21 +186,31 @@ func (vals *ValidatorSet) getValWithMostPriority() *Validator { } func (vals *ValidatorSet) shiftByAvgProposerPriority() { + if vals.IsNilOrEmpty() { + panic("empty validator set") + } avgProposerPriority := vals.computeAvgProposerPriority() for _, val := range vals.Validators { val.ProposerPriority = safeSubClip(val.ProposerPriority, avgProposerPriority) } } +// Makes a copy of the validator list +func validatorListCopy(valsList []*Validator) []*Validator { + if valsList == nil { + return nil + } + valsCopy := make([]*Validator, len(valsList)) + for i, val := range valsList { + valsCopy[i] = val.Copy() + } + return valsCopy +} + // Copy each validator into a new ValidatorSet func (vals *ValidatorSet) Copy() *ValidatorSet { - validators := make([]*Validator, len(vals.Validators)) - for i, val := range vals.Validators { - // NOTE: must copy, since IncrementProposerPriority updates in place. - validators[i] = val.Copy() - } return &ValidatorSet{ - Validators: validators, + Validators: validatorListCopy(vals.Validators), Proposer: vals.Proposer, totalVotingPower: vals.totalVotingPower, } @@ -284,57 +307,6 @@ func (vals *ValidatorSet) Hash() []byte { return merkle.SimpleHashFromByteSlices(bzs) } -// Add adds val to the validator set and returns true. It returns false if val -// is already in the set. -func (vals *ValidatorSet) Add(val *Validator) (added bool) { - val = val.Copy() - idx := sort.Search(len(vals.Validators), func(i int) bool { - return bytes.Compare(val.Address, vals.Validators[i].Address) <= 0 - }) - if idx >= len(vals.Validators) { - vals.Validators = append(vals.Validators, val) - // Invalidate cache - vals.Proposer = nil - vals.totalVotingPower = 0 - return true - } else if bytes.Equal(vals.Validators[idx].Address, val.Address) { - return false - } else { - newValidators := make([]*Validator, len(vals.Validators)+1) - copy(newValidators[:idx], vals.Validators[:idx]) - newValidators[idx] = val - copy(newValidators[idx+1:], vals.Validators[idx:]) - vals.Validators = newValidators - // Invalidate cache - vals.Proposer = nil - vals.totalVotingPower = 0 - return true - } -} - -// Update updates the ValidatorSet by copying in the val. -// If the val is not found, it returns false; otherwise, -// it returns true. The val.ProposerPriority field is ignored -// and unchanged by this method. -func (vals *ValidatorSet) Update(val *Validator) (updated bool) { - index, sameVal := vals.GetByAddress(val.Address) - if sameVal == nil { - return false - } - // Overwrite the ProposerPriority so it doesn't change. - // During block execution, the val passed in here comes - // from ABCI via PB2TM.ValidatorUpdates. Since ABCI - // doesn't know about ProposerPriority, PB2TM.ValidatorUpdates - // uses the default value of 0, which would cause issues for - // proposer selection every time a validator's voting power changes. - val.ProposerPriority = sameVal.ProposerPriority - vals.Validators[index] = val.Copy() - // Invalidate cache - vals.Proposer = nil - vals.totalVotingPower = 0 - return true -} - // Remove deletes the validator with address. It returns the validator removed // and true. If returns nil and false if validator is not present in the set. func (vals *ValidatorSet) Remove(address []byte) (val *Validator, removed bool) { @@ -366,6 +338,253 @@ func (vals *ValidatorSet) Iterate(fn func(index int, val *Validator) bool) { } } +// Checks changes against duplicates, splits the changes in updates and removals, sorts them by address +// +// Returns: +// updates, removals - the sorted lists of updates and removals +// err - non-nil if duplicate entries or entries with negative voting power are seen +// +// No changes are made to 'origChanges' +func processChanges(origChanges []*Validator) (updates, removals []*Validator, err error) { + // Make a deep copy of the changes and sort by address + changes := validatorListCopy(origChanges) + sort.Sort(ValidatorsByAddress(changes)) + + removals = make([]*Validator, 0, len(changes)) + updates = make([]*Validator, 0, len(changes)) + var prevAddr Address + + // Scan changes by address and append valid validators to updates or removals lists + for _, valUpdate := range changes { + if bytes.Equal(valUpdate.Address, prevAddr) { + err = fmt.Errorf("duplicate entry %v in %v", valUpdate, changes) + return nil, nil, err + } + if valUpdate.VotingPower < 0 { + err = fmt.Errorf("voting power can't be negative %v", valUpdate) + return nil, nil, err + } + if valUpdate.VotingPower == 0 { + removals = append(removals, valUpdate) + } else { + updates = append(updates, valUpdate) + } + prevAddr = valUpdate.Address + } + return updates, removals, err +} + +// Verifies a list of updates against a validator set, making sure the allowed +// total voting power would not be exceeded if these updates would be applied to the set. +// It also computes the total voting power of the set that would result after the updates but +// before the removals. +// +// Returns: +// updatedTotalVotingPower - the new total voting power if these updates would be applied +// err - non-nil if the maximum allowed total voting power would be exceeded +// +// 'updates' should be a list of proper validator changes, i.e. they have been scanned +// by processChanges for duplicates and invalid values. +// No changes are made to the validator set 'vals'. +func verifyUpdates(updates []*Validator, vals *ValidatorSet) (updatedTotalVotingPower int64, err error) { + + // Scan the updates, compute new total voting power, check for overflow + updatedTotalVotingPower = vals.TotalVotingPower() + + for _, valUpdate := range updates { + address := valUpdate.Address + _, val := vals.GetByAddress(address) + if val == nil { + // new validator, add its voting power the the total + updatedTotalVotingPower += valUpdate.VotingPower + } else { + // updated validator, add the difference in power to the total + updatedTotalVotingPower += valUpdate.VotingPower - val.VotingPower + } + + if updatedTotalVotingPower < 0 { + err = fmt.Errorf( + "failed to add/update validator with negative voting power %v", + valUpdate) + return 0, err + } + overflow := updatedTotalVotingPower > MaxTotalVotingPower + if overflow { + err = fmt.Errorf( + "failed to add/update validator %v, total voting power would exceed the max allowed %v", + valUpdate, MaxTotalVotingPower) + return 0, err + } + } + + return updatedTotalVotingPower, nil +} + +// Computes the proposer priority for the validators not present in the set based on 'updatedTotalVotingPower' +// Leaves unchanged the priorities of validators that are changed. +// +// 'updates' parameter must be a list of unique validators to be added or updated. +// No changes are made to the validator set 'vals'. +func computeNewPriorities(updates []*Validator, vals *ValidatorSet, updatedTotalVotingPower int64) int { + + numNew := 0 + // Scan and update the proposerPriority for newly added and updated validators + for _, valUpdate := range updates { + address := valUpdate.Address + _, val := vals.GetByAddress(address) + if val == nil { + // add val + // Set ProposerPriority to -C*totalVotingPower (with C ~= 1.125) to make sure validators can't + // un-bond and then re-bond to reset their (potentially previously negative) ProposerPriority to zero. + // + // Contract: updatedVotingPower < MaxTotalVotingPower to ensure ProposerPriority does + // not exceed the bounds of int64. + // + // Compute ProposerPriority = -1.125*totalVotingPower == -(updatedVotingPower + (updatedVotingPower >> 3)). + valUpdate.ProposerPriority = -(updatedTotalVotingPower + (updatedTotalVotingPower >> 3)) + numNew++ + } else { + valUpdate.ProposerPriority = val.ProposerPriority + } + } + + return numNew +} + +// Merges the vals' validator list with the updates list. +// When two elements with same address are seen, the one from updates is selected. +// Expects updates to be a list of updates sorted by address with no duplicates or errors, +// must have been validated with verifyUpdates() and priorities computed with computeNewPriorities(). +func (vals *ValidatorSet) applyUpdates(updates []*Validator) { + + existing := make([]*Validator, len(vals.Validators)) + copy(existing, vals.Validators) + + merged := make([]*Validator, len(existing)+len(updates)) + i := 0 + + for len(existing) > 0 && len(updates) > 0 { + if bytes.Compare(existing[0].Address, updates[0].Address) < 0 { + merged[i] = existing[0] + existing = existing[1:] + } else { + merged[i] = updates[0] + if bytes.Equal(existing[0].Address, updates[0].Address) { + // validator present in both, advance existing + existing = existing[1:] + } + updates = updates[1:] + } + i++ + } + + for j := 0; j < len(existing); j++ { + merged[i] = existing[j] + i++ + } + + for j := 0; j < len(updates); j++ { + merged[i] = updates[j] + i++ + } + + vals.Validators = merged[:i] + vals.totalVotingPower = 0 +} + +// Checks that the validators to be removed are part of the validator set. +// No changes are made to the validator set 'vals'. +func verifyRemovals(deletes []*Validator, vals *ValidatorSet) error { + + for _, valUpdate := range deletes { + address := valUpdate.Address + _, val := vals.GetByAddress(address) + if val == nil { + return fmt.Errorf("failed to find validator %X to remove", address) + } + } + return nil +} + +// Removes the validators specified in 'deletes' from validator set 'vals'. +// Should not fail as verification has been done before. +func (vals *ValidatorSet) applyRemovals(deletes []*Validator) { + + for _, valUpdate := range deletes { + address := valUpdate.Address + _, removed := vals.Remove(address) + if !removed { + // Should never happen + panic(fmt.Sprintf("failed to remove validator %X", address)) + } + } +} + +// UpdateWithChangeSet attempts to update the validator set with 'changes' +// It performs the following steps: +// - validates the changes making sure there are no duplicates and splits them in updates and deletes +// - verifies that applying the changes will not result in errors +// - computes the total voting power BEFORE removals to ensure that in the next steps the relative priorities +// across old and newly added validators is fair +// - computes the priorities of new validators against the final set +// - applies the updates against the validator set +// - applies the removals against the validator set +// - performs scaling and centering of priority values +// If error is detected during verification steps it is returned and the validator set +// is not changed. +func (vals *ValidatorSet) UpdateWithChangeSet(changes []*Validator) error { + return vals.updateWithChangeSet(changes, true) +} + +// main function used by UpdateWithChangeSet() and NewValidatorSet() +// If 'allowDeletes' is false then delete operations are not allowed and must be reported if +// present in 'changes' +func (vals *ValidatorSet) updateWithChangeSet(changes []*Validator, allowDeletes bool) error { + + if len(changes) <= 0 { + return nil + } + + // Check for duplicates within changes, split in 'updates' and 'deletes' lists (sorted) + updates, deletes, err := processChanges(changes) + if err != nil { + return err + } + + if !allowDeletes && len(deletes) != 0 { + err = fmt.Errorf("cannot process validators with voting power 0: %v", deletes) + return err + } + + // Verify that applying the 'deletes' against 'vals' will not result in error. + if err := verifyRemovals(deletes, vals); err != nil { + return err + } + + // Verify that applying the 'updates' against 'vals' will not result in error. + updatedTotalVotingPower, err := verifyUpdates(updates, vals) + if err != nil { + return err + } + + // Compute the priorities for updates + numNewValidators := computeNewPriorities(updates, vals, updatedTotalVotingPower) + if len(vals.Validators)+numNewValidators <= len(deletes) { + err = fmt.Errorf("applying the validator changes would result in empty set") + return err + } + + // Apply updates and removals + vals.applyUpdates(updates) + vals.applyRemovals(deletes) + + // Scale and center + vals.RescalePriorities(PriorityWindowSizeFactor * vals.TotalVotingPower()) + vals.shiftByAvgProposerPriority() + + return nil +} + // Verify that +2/3 of the set had signed the given signBytes. func (vals *ValidatorSet) VerifyCommit(chainID string, blockID BlockID, height int64, commit *Commit) error { diff --git a/types/validator_set_test.go b/types/validator_set_test.go index 72b2f661..04874c19 100644 --- a/types/validator_set_test.go +++ b/types/validator_set_test.go @@ -4,6 +4,7 @@ import ( "bytes" "fmt" "math" + "math/rand" "strings" "testing" "testing/quick" @@ -45,31 +46,29 @@ func TestValidatorSetBasic(t *testing.T) { assert.Nil(t, vset.Hash()) // add - val = randValidator_(vset.TotalVotingPower()) - assert.True(t, vset.Add(val)) + assert.NoError(t, vset.UpdateWithChangeSet([]*Validator{val})) + assert.True(t, vset.HasAddress(val.Address)) - idx, val2 := vset.GetByAddress(val.Address) + idx, _ = vset.GetByAddress(val.Address) assert.Equal(t, 0, idx) - assert.Equal(t, val, val2) - addr, val2 = vset.GetByIndex(0) + addr, _ = vset.GetByIndex(0) assert.Equal(t, []byte(val.Address), addr) - assert.Equal(t, val, val2) assert.Equal(t, 1, vset.Size()) assert.Equal(t, val.VotingPower, vset.TotalVotingPower()) - assert.Equal(t, val, vset.GetProposer()) assert.NotNil(t, vset.Hash()) assert.NotPanics(t, func() { vset.IncrementProposerPriority(1) }) + assert.Equal(t, val.Address, vset.GetProposer().Address) // update - assert.False(t, vset.Update(randValidator_(vset.TotalVotingPower()))) + val = randValidator_(vset.TotalVotingPower()) + assert.NoError(t, vset.UpdateWithChangeSet([]*Validator{val})) _, val = vset.GetByAddress(val.Address) val.VotingPower += 100 proposerPriority := val.ProposerPriority - // Mimic update from types.PB2TM.ValidatorUpdates which does not know about ProposerPriority - // and hence defaults to 0. + val.ProposerPriority = 0 - assert.True(t, vset.Update(val)) + assert.NoError(t, vset.UpdateWithChangeSet([]*Validator{val})) _, val = vset.GetByAddress(val.Address) assert.Equal(t, proposerPriority, val.ProposerPriority) @@ -116,8 +115,9 @@ func BenchmarkValidatorSetCopy(b *testing.B) { for i := 0; i < 1000; i++ { privKey := ed25519.GenPrivKey() pubKey := privKey.PubKey() - val := NewValidator(pubKey, 0) - if !vset.Add(val) { + val := NewValidator(pubKey, 10) + err := vset.UpdateWithChangeSet([]*Validator{val}) + if err != nil { panic("Failed to add validator") } } @@ -284,7 +284,7 @@ func randPubKey() crypto.PubKey { 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 := NewValidator(randPubKey(), int64(cmn.RandUint64()%uint64((MaxTotalVotingPower-totalVotingPower)))) val.ProposerPriority = cmn.RandInt64() % (MaxTotalVotingPower - totalVotingPower) return val } @@ -599,3 +599,357 @@ func TestValidatorSetVerifyCommit(t *testing.T) { err = vset.VerifyCommit(chainID, blockID, height, commit) assert.Nil(t, err) } + +func TestEmptySet(t *testing.T) { + + var valList []*Validator + valSet := NewValidatorSet(valList) + assert.Panics(t, func() { valSet.IncrementProposerPriority(1) }) + assert.Panics(t, func() { valSet.RescalePriorities(100) }) + assert.Panics(t, func() { valSet.shiftByAvgProposerPriority() }) + assert.Panics(t, func() { assert.Zero(t, computeMaxMinPriorityDiff(valSet)) }) + valSet.GetProposer() + + // Add to empty set + v1 := newValidator([]byte("v1"), 100) + v2 := newValidator([]byte("v2"), 100) + valList = []*Validator{v1, v2} + assert.NoError(t, valSet.UpdateWithChangeSet(valList)) + verifyValidatorSet(t, valSet) + + // Delete all validators from set + v1 = newValidator([]byte("v1"), 0) + v2 = newValidator([]byte("v2"), 0) + delList := []*Validator{v1, v2} + assert.Error(t, valSet.UpdateWithChangeSet(delList)) + + // Attempt delete from empty set + assert.Error(t, valSet.UpdateWithChangeSet(delList)) + +} + +func TestUpdatesForNewValidatorSet(t *testing.T) { + + v1 := newValidator([]byte("v1"), 100) + v2 := newValidator([]byte("v2"), 100) + valList := []*Validator{v1, v2} + valSet := NewValidatorSet(valList) + verifyValidatorSet(t, valSet) + + // Verify duplicates are caught in NewValidatorSet() and it panics + v111 := newValidator([]byte("v1"), 100) + v112 := newValidator([]byte("v1"), 123) + v113 := newValidator([]byte("v1"), 234) + valList = []*Validator{v111, v112, v113} + assert.Panics(t, func() { NewValidatorSet(valList) }) + + // Verify set including validator with voting power 0 cannot be created + v1 = newValidator([]byte("v1"), 0) + v2 = newValidator([]byte("v2"), 22) + v3 := newValidator([]byte("v3"), 33) + valList = []*Validator{v1, v2, v3} + assert.Panics(t, func() { NewValidatorSet(valList) }) + + // Verify set including validator with negative voting power cannot be created + v1 = newValidator([]byte("v1"), 10) + v2 = newValidator([]byte("v2"), -20) + v3 = newValidator([]byte("v3"), 30) + valList = []*Validator{v1, v2, v3} + assert.Panics(t, func() { NewValidatorSet(valList) }) + +} + +type testVal struct { + name string + power int64 +} + +func TestValSetUpdatesBasicTestsExecute(t *testing.T) { + valSetUpdatesBasicTests := []struct { + startVals []testVal + updateVals []testVal + expectedVals []testVal + expError bool + }{ + // Operations that should result in error + 0: { // updates leading to overflows + []testVal{{"v1", 10}, {"v2", 10}}, + []testVal{{"v1", math.MaxInt64}}, + []testVal{{"v1", 10}, {"v2", 10}}, + true}, + 1: { // duplicate entries in changes + []testVal{{"v1", 10}, {"v2", 10}}, + []testVal{{"v1", 11}, {"v1", 22}}, + []testVal{{"v1", 10}, {"v2", 10}}, + true}, + 2: { // duplicate entries in removes + []testVal{{"v1", 10}, {"v2", 10}}, + []testVal{{"v1", 0}, {"v1", 0}}, + []testVal{{"v1", 10}, {"v2", 10}}, + true}, + 3: { // duplicate entries in removes + changes + []testVal{{"v1", 10}, {"v2", 10}}, + []testVal{{"v1", 0}, {"v2", 20}, {"v2", 30}, {"v1", 0}}, + []testVal{{"v1", 10}, {"v2", 10}}, + true}, + 4: { // update with negative voting power + []testVal{{"v1", 10}, {"v2", 10}}, + []testVal{{"v1", -123}}, + []testVal{{"v1", 10}, {"v2", 10}}, + true}, + 5: { // delete non existing validator + []testVal{{"v1", 10}, {"v2", 10}}, + []testVal{{"v3", 0}}, + []testVal{{"v1", 10}, {"v2", 10}}, + true}, + + // Operations that should be successful + 6: { // no changes + []testVal{{"v1", 10}, {"v2", 10}}, + []testVal{}, + []testVal{{"v1", 10}, {"v2", 10}}, + false}, + 7: { // voting power changes + []testVal{{"v1", 10}, {"v2", 10}}, + []testVal{{"v1", 11}, {"v2", 22}}, + []testVal{{"v1", 11}, {"v2", 22}}, + false}, + 8: { // add new validators + []testVal{{"v1", 10}, {"v2", 20}}, + []testVal{{"v3", 30}, {"v4", 40}}, + []testVal{{"v1", 10}, {"v2", 20}, {"v3", 30}, {"v4", 40}}, + false}, + 9: { // delete validators + []testVal{{"v1", 10}, {"v2", 20}, {"v3", 30}}, + []testVal{{"v2", 0}}, + []testVal{{"v1", 10}, {"v3", 30}}, + false}, + 10: { // delete all validators + []testVal{{"v1", 10}, {"v2", 20}, {"v3", 30}}, + []testVal{{"v1", 0}, {"v2", 0}, {"v3", 0}}, + []testVal{{"v1", 10}, {"v2", 20}, {"v3", 30}}, + true}, + } + + for i, tt := range valSetUpdatesBasicTests { + // create a new set and apply updates, keeping copies for the checks + valSet := createNewValidatorSet(tt.startVals) + valSetCopy := valSet.Copy() + valList := createNewValidatorList(tt.updateVals) + valListCopy := validatorListCopy(valList) + err := valSet.UpdateWithChangeSet(valList) + + if tt.expError { + // for errors check the validator set has not been changed + assert.Error(t, err, "test %d", i) + assert.Equal(t, valSet, valSetCopy, "test %v", i) + } else { + assert.NoError(t, err, "test %d", i) + } + // check the parameter list has not changed + assert.Equal(t, valList, valListCopy, "test %v", i) + + // check the final validator list is as expected and the set is properly scaled and centered. + assert.Equal(t, getValidatorResults(valSet.Validators), tt.expectedVals, "test %v", i) + verifyValidatorSet(t, valSet) + } +} + +func getValidatorResults(valList []*Validator) []testVal { + testList := make([]testVal, len(valList)) + for i, val := range valList { + testList[i].name = string(val.Address) + testList[i].power = val.VotingPower + } + return testList +} + +// Test that different permutations of an update give the same result. +func TestValSetUpdatesOrderTestsExecute(t *testing.T) { + // startVals - initial validators to create the set with + // updateVals - a sequence of updates to be applied to the set. + // updateVals is shuffled a number of times during testing to check for same resulting validator set. + valSetUpdatesOrderTests := []struct { + startVals []testVal + updateVals []testVal + }{ + 0: { // order of changes should not matter, the final validator sets should be the same + []testVal{{"v1", 10}, {"v2", 10}, {"v3", 30}, {"v4", 40}}, + []testVal{{"v1", 11}, {"v2", 22}, {"v3", 33}, {"v4", 44}}}, + + 1: { // order of additions should not matter + []testVal{{"v1", 10}, {"v2", 20}}, + []testVal{{"v3", 30}, {"v4", 40}, {"v5", 50}, {"v6", 60}}}, + + 2: { // order of removals should not matter + []testVal{{"v1", 10}, {"v2", 20}, {"v3", 30}, {"v4", 40}}, + []testVal{{"v1", 0}, {"v3", 0}, {"v4", 0}}}, + + 3: { // order of mixed operations should not matter + []testVal{{"v1", 10}, {"v2", 20}, {"v3", 30}, {"v4", 40}}, + []testVal{{"v1", 0}, {"v3", 0}, {"v2", 22}, {"v5", 50}, {"v4", 44}}}, + } + + for i, tt := range valSetUpdatesOrderTests { + // create a new set and apply updates + valSet := createNewValidatorSet(tt.startVals) + valSetCopy := valSet.Copy() + valList := createNewValidatorList(tt.updateVals) + assert.NoError(t, valSetCopy.UpdateWithChangeSet(valList)) + + // save the result as expected for next updates + valSetExp := valSetCopy.Copy() + + // perform at most 20 permutations on the updates and call UpdateWithChangeSet() + n := len(tt.updateVals) + maxNumPerms := cmn.MinInt(20, n*n) + for j := 0; j < maxNumPerms; j++ { + // create a copy of original set and apply a random permutation of updates + valSetCopy := valSet.Copy() + valList := createNewValidatorList(permutation(tt.updateVals)) + + // check there was no error and the set is properly scaled and centered. + assert.NoError(t, valSetCopy.UpdateWithChangeSet(valList), + "test %v failed for permutation %v", i, valList) + verifyValidatorSet(t, valSetCopy) + + // verify the resulting test is same as the expected + assert.Equal(t, valSetCopy, valSetExp, + "test %v failed for permutation %v", i, valList) + } + } +} + +// This tests the private function validator_set.go:applyUpdates() function, used only for additions and changes. +// Should perform a proper merge of updatedVals and startVals +func TestValSetApplyUpdatesTestsExecute(t *testing.T) { + valSetUpdatesBasicTests := []struct { + startVals []testVal + updateVals []testVal + expectedVals []testVal + }{ + // additions + 0: { // prepend + []testVal{{"v4", 44}, {"v5", 55}}, + []testVal{{"v1", 11}}, + []testVal{{"v1", 11}, {"v4", 44}, {"v5", 55}}}, + 1: { // append + []testVal{{"v4", 44}, {"v5", 55}}, + []testVal{{"v6", 66}}, + []testVal{{"v4", 44}, {"v5", 55}, {"v6", 66}}}, + 2: { // insert + []testVal{{"v4", 44}, {"v6", 66}}, + []testVal{{"v5", 55}}, + []testVal{{"v4", 44}, {"v5", 55}, {"v6", 66}}}, + 3: { // insert multi + []testVal{{"v4", 44}, {"v6", 66}, {"v9", 99}}, + []testVal{{"v5", 55}, {"v7", 77}, {"v8", 88}}, + []testVal{{"v4", 44}, {"v5", 55}, {"v6", 66}, {"v7", 77}, {"v8", 88}, {"v9", 99}}}, + // changes + 4: { // head + []testVal{{"v1", 111}, {"v2", 22}}, + []testVal{{"v1", 11}}, + []testVal{{"v1", 11}, {"v2", 22}}}, + 5: { // tail + []testVal{{"v1", 11}, {"v2", 222}}, + []testVal{{"v2", 22}}, + []testVal{{"v1", 11}, {"v2", 22}}}, + 6: { // middle + []testVal{{"v1", 11}, {"v2", 222}, {"v3", 33}}, + []testVal{{"v2", 22}}, + []testVal{{"v1", 11}, {"v2", 22}, {"v3", 33}}}, + 7: { // multi + []testVal{{"v1", 111}, {"v2", 222}, {"v3", 333}}, + []testVal{{"v1", 11}, {"v2", 22}, {"v3", 33}}, + []testVal{{"v1", 11}, {"v2", 22}, {"v3", 33}}}, + // additions and changes + 8: { + []testVal{{"v1", 111}, {"v2", 22}}, + []testVal{{"v1", 11}, {"v3", 33}, {"v4", 44}}, + []testVal{{"v1", 11}, {"v2", 22}, {"v3", 33}, {"v4", 44}}}, + } + + for i, tt := range valSetUpdatesBasicTests { + // create a new validator set with the start values + valSet := createNewValidatorSet(tt.startVals) + + // applyUpdates() with the update values + valList := createNewValidatorList(tt.updateVals) + valSet.applyUpdates(valList) + + // check the new list of validators for proper merge + assert.Equal(t, getValidatorResults(valSet.Validators), tt.expectedVals, "test %v", i) + verifyValidatorSet(t, valSet) + } +} + +func permutation(valList []testVal) []testVal { + if len(valList) == 0 { + return nil + } + permList := make([]testVal, len(valList)) + perm := rand.Perm(len(valList)) + for i, v := range perm { + permList[v] = valList[i] + } + return permList +} + +func createNewValidatorList(testValList []testVal) []*Validator { + valList := make([]*Validator, 0, len(testValList)) + for _, val := range testValList { + valList = append(valList, newValidator([]byte(val.name), val.power)) + } + return valList +} + +func createNewValidatorSet(testValList []testVal) *ValidatorSet { + valList := createNewValidatorList(testValList) + valSet := NewValidatorSet(valList) + return valSet +} + +func verifyValidatorSet(t *testing.T, valSet *ValidatorSet) { + // verify that the vals' tvp is set to the sum of the all vals voting powers + tvp := valSet.TotalVotingPower() + assert.Equal(t, valSet.totalVotingPower, tvp, + "expected TVP %d. Got %d, valSet=%s", tvp, valSet.totalVotingPower, valSet) + + // verify that validator priorities are centered + l := int64(len(valSet.Validators)) + tpp := valSet.TotalVotingPower() + assert.True(t, tpp <= l || tpp >= -l, + "expected total priority in (-%d, %d). Got %d", l, l, tpp) + + // verify that priorities are scaled + dist := computeMaxMinPriorityDiff(valSet) + assert.True(t, dist <= PriorityWindowSizeFactor*tvp, + "expected priority distance < %d. Got %d", PriorityWindowSizeFactor*tvp, dist) +} + +func BenchmarkUpdates(b *testing.B) { + const ( + n = 100 + m = 2000 + ) + // Init with n validators + vs := make([]*Validator, n) + for j := 0; j < n; j++ { + vs[j] = newValidator([]byte(fmt.Sprintf("v%d", j)), 100) + } + valSet := NewValidatorSet(vs) + l := len(valSet.Validators) + + // Make m new validators + newValList := make([]*Validator, m) + for j := 0; j < m; j++ { + newValList[j] = newValidator([]byte(fmt.Sprintf("v%d", j+l)), 1000) + } + b.ResetTimer() + + for i := 0; i < b.N; i++ { + // Add m validators to valSetCopy + valSetCopy := valSet.Copy() + assert.NoError(b, valSetCopy.UpdateWithChangeSet(newValList)) + } +}