Merge branch 'master' into feature/2998/addressbooks-hash

This commit is contained in:
tau3
2020-02-19 21:39:38 +04:00
37 changed files with 2248 additions and 1033 deletions

View File

@@ -20,3 +20,7 @@ program](https://hackerone.com/tendermint).
### IMPROVEMENTS:
### BUG FIXES:
- [rpc] [\#4437](https://github.com/tendermint/tendermint/pull/4437) Fix tx_search pagination with ordered results (@erikgrinaker)
- [rpc] [\#4406](https://github.com/tendermint/tendermint/pull/4406) Fix issue with multiple subscriptions on the websocket (@antho1404)

View File

@@ -19,7 +19,7 @@ type SwitchReporter struct {
}
// NewSwitchReporter return a new SwitchReporter instance which wraps the Switch.
func NewSwitcReporter(sw *p2p.Switch) *SwitchReporter {
func NewSwitchReporter(sw *p2p.Switch) *SwitchReporter {
return &SwitchReporter{
sw: sw,
}

View File

@@ -41,7 +41,7 @@ const (
type consensusReactor interface {
// for when we switch from blockchain reactor and fast sync to
// the consensus machine
SwitchToConsensus(sm.State, int)
SwitchToConsensus(sm.State, uint64)
}
type peerError struct {
@@ -214,7 +214,7 @@ func (bcR *BlockchainReactor) poolRoutine() {
statusUpdateTicker := time.NewTicker(statusUpdateIntervalSeconds * time.Second)
switchToConsensusTicker := time.NewTicker(switchToConsensusIntervalSeconds * time.Second)
blocksSynced := 0
blocksSynced := uint64(0)
chainID := bcR.initialState.ChainID
state := bcR.initialState

View File

@@ -43,7 +43,7 @@ var (
type consensusReactor interface {
// for when we switch from blockchain reactor and fast sync to
// the consensus machine
SwitchToConsensus(sm.State, int)
SwitchToConsensus(sm.State, uint64)
}
// BlockchainReactor handles long-term catchup syncing.
@@ -59,7 +59,7 @@ type BlockchainReactor struct {
fastSync bool
fsm *BcReactorFSM
blocksSynced int
blocksSynced uint64
// Receive goroutine forwards messages to this channel to be processed in the context of the poolRoutine.
messagesForFSMCh chan bcReactorMessage
@@ -103,7 +103,7 @@ func NewBlockchainReactor(state sm.State, blockExec *sm.BlockExecutor, store *st
fsm := NewFSM(startHeight, bcR)
bcR.fsm = fsm
bcR.BaseReactor = *p2p.NewBaseReactor("BlockchainReactor", bcR)
//bcR.swReporter = behaviour.NewSwitcReporter(bcR.BaseReactor.Switch)
//bcR.swReporter = behaviour.NewSwitchReporter(bcR.BaseReactor.Switch)
return bcR
}
@@ -141,7 +141,7 @@ func (bcR *BlockchainReactor) SetLogger(l log.Logger) {
// OnStart implements service.Service.
func (bcR *BlockchainReactor) OnStart() error {
bcR.swReporter = behaviour.NewSwitcReporter(bcR.BaseReactor.Switch)
bcR.swReporter = behaviour.NewSwitchReporter(bcR.BaseReactor.Switch)
if bcR.fastSync {
go bcR.poolRoutine()
}

View File

@@ -157,7 +157,7 @@ type consensusReactorTest struct {
mtx sync.Mutex
}
func (conR *consensusReactorTest) SwitchToConsensus(state sm.State, blocksSynced int) {
func (conR *consensusReactorTest) SwitchToConsensus(state sm.State, blocksSynced uint64) {
conR.mtx.Lock()
defer conR.mtx.Unlock()
conR.switchedToConsensus = true

13
blockchain/v2/codec.go Normal file
View File

@@ -0,0 +1,13 @@
package v2
import (
amino "github.com/tendermint/go-amino"
"github.com/tendermint/tendermint/types"
)
var cdc = amino.NewCodec()
func init() {
RegisterBlockchainMessages(cdc)
types.RegisterBlockAmino(cdc)
}

111
blockchain/v2/io.go Normal file
View File

@@ -0,0 +1,111 @@
package v2
import (
"fmt"
"github.com/tendermint/tendermint/p2p"
"github.com/tendermint/tendermint/state"
"github.com/tendermint/tendermint/types"
)
type iIO interface {
sendBlockRequest(peerID p2p.ID, height int64) error
sendBlockToPeer(block *types.Block, peerID p2p.ID) error
sendBlockNotFound(height int64, peerID p2p.ID) error
sendStatusResponse(height int64, peerID p2p.ID) error
broadcastStatusRequest(height int64)
trySwitchToConsensus(state state.State, blocksSynced int)
}
type switchIO struct {
sw *p2p.Switch
}
func newSwitchIo(sw *p2p.Switch) *switchIO {
return &switchIO{
sw: sw,
}
}
const (
// BlockchainChannel is a channel for blocks and status updates (`BlockStore` height)
BlockchainChannel = byte(0x40)
)
type consensusReactor interface {
// for when we switch from blockchain reactor and fast sync to
// the consensus machine
SwitchToConsensus(state.State, int)
}
func (sio *switchIO) sendBlockRequest(peerID p2p.ID, height int64) error {
peer := sio.sw.Peers().Get(peerID)
if peer == nil {
return fmt.Errorf("peer not found")
}
msgBytes := cdc.MustMarshalBinaryBare(&bcBlockRequestMessage{Height: height})
queued := peer.TrySend(BlockchainChannel, msgBytes)
if !queued {
return fmt.Errorf("send queue full")
}
return nil
}
func (sio *switchIO) sendStatusResponse(height int64, peerID p2p.ID) error {
peer := sio.sw.Peers().Get(peerID)
if peer == nil {
return fmt.Errorf("peer not found")
}
msgBytes := cdc.MustMarshalBinaryBare(&bcStatusResponseMessage{Height: height})
if queued := peer.TrySend(BlockchainChannel, msgBytes); !queued {
return fmt.Errorf("peer queue full")
}
return nil
}
func (sio *switchIO) sendBlockToPeer(block *types.Block, peerID p2p.ID) error {
peer := sio.sw.Peers().Get(peerID)
if peer == nil {
return fmt.Errorf("peer not found")
}
if block == nil {
panic("trying to send nil block")
}
msgBytes := cdc.MustMarshalBinaryBare(&bcBlockResponseMessage{Block: block})
if queued := peer.TrySend(BlockchainChannel, msgBytes); !queued {
return fmt.Errorf("peer queue full")
}
return nil
}
func (sio *switchIO) sendBlockNotFound(height int64, peerID p2p.ID) error {
peer := sio.sw.Peers().Get(peerID)
if peer == nil {
return fmt.Errorf("peer not found")
}
msgBytes := cdc.MustMarshalBinaryBare(&bcNoBlockResponseMessage{Height: height})
if queued := peer.TrySend(BlockchainChannel, msgBytes); !queued {
return fmt.Errorf("peer queue full")
}
return nil
}
func (sio *switchIO) trySwitchToConsensus(state state.State, blocksSynced int) {
conR, ok := sio.sw.Reactor("CONSENSUS").(consensusReactor)
if ok {
conR.SwitchToConsensus(state, blocksSynced)
}
}
func (sio *switchIO) broadcastStatusRequest(height int64) {
msgBytes := cdc.MustMarshalBinaryBare(&bcStatusRequestMessage{height})
// XXX: maybe we should use an io specific peer list here
sio.sw.Broadcast(BlockchainChannel, msgBytes)
}

View File

@@ -37,6 +37,7 @@ type Metrics struct {
ErrorsShed metrics.Counter
}
// PrometheusMetrics returns metrics for in and out events, errors, etc. handled by routines.
// Can we burn in the routine name here?
func PrometheusMetrics(namespace string, labelsAndValues ...string) *Metrics {
labels := []string{}

View File

@@ -4,23 +4,12 @@ import (
"fmt"
"github.com/tendermint/tendermint/p2p"
tdState "github.com/tendermint/tendermint/state"
tmState "github.com/tendermint/tendermint/state"
"github.com/tendermint/tendermint/types"
)
type peerError struct {
priorityHigh
peerID p2p.ID
}
type pcDuplicateBlock struct {
priorityNormal
}
type pcShortBlock struct {
priorityNormal
}
// Events generated by the processor:
// block execution failure, event will indicate the peer(s) that caused the error
type pcBlockVerificationFailure struct {
priorityNormal
height int64
@@ -28,24 +17,18 @@ type pcBlockVerificationFailure struct {
secondPeerID p2p.ID
}
// successful block execution
type pcBlockProcessed struct {
priorityNormal
height int64
peerID p2p.ID
}
type pcProcessBlock struct {
priorityNormal
}
type pcStop struct {
priorityNormal
}
// processor has finished
type pcFinished struct {
priorityNormal
height int64
blocksSynced int64
blocksSynced int
tmState tmState.State
}
func (p pcFinished) Error() string {
@@ -60,37 +43,38 @@ type queueItem struct {
type blockQueue map[int64]queueItem
type pcState struct {
height int64 // height of the last synced block
queue blockQueue // blocks waiting to be processed
chainID string
blocksSynced int64
draining bool
tdState tdState.State
context processorContext
// blocks waiting to be processed
queue blockQueue
// draining indicates that the next rProcessBlock event with a queue miss constitutes completion
draining bool
// the number of blocks successfully synced by the processor
blocksSynced int
// the processorContext which contains the processor dependencies
context processorContext
}
func (state *pcState) String() string {
return fmt.Sprintf("height: %d queue length: %d draining: %v blocks synced: %d",
state.height, len(state.queue), state.draining, state.blocksSynced)
state.height(), len(state.queue), state.draining, state.blocksSynced)
}
// newPcState returns a pcState initialized with the last verified block enqueued
func newPcState(initHeight int64, tdState tdState.State, chainID string, context processorContext) *pcState {
func newPcState(context processorContext) *pcState {
return &pcState{
height: initHeight,
queue: blockQueue{},
chainID: chainID,
draining: false,
blocksSynced: 0,
context: context,
tdState: tdState,
}
}
// nextTwo returns the next two unverified blocks
func (state *pcState) nextTwo() (queueItem, queueItem, error) {
if first, ok := state.queue[state.height+1]; ok {
if second, ok := state.queue[state.height+2]; ok {
if first, ok := state.queue[state.height()+1]; ok {
if second, ok := state.queue[state.height()+2]; ok {
return first, second, nil
}
}
@@ -102,18 +86,15 @@ func (state *pcState) synced() bool {
return len(state.queue) <= 1
}
func (state *pcState) advance() {
state.height++
delete(state.queue, state.height)
state.blocksSynced++
}
func (state *pcState) enqueue(peerID p2p.ID, block *types.Block, height int64) error {
func (state *pcState) enqueue(peerID p2p.ID, block *types.Block, height int64) {
if _, ok := state.queue[height]; ok {
return fmt.Errorf("duplicate queue item")
panic("duplicate block enqueued by processor")
}
state.queue[height] = queueItem{block: block, peerID: peerID}
return nil
}
func (state *pcState) height() int64 {
return state.context.tmState().LastBlockHeight
}
// purgePeer moves all unprocessed blocks from the queue
@@ -129,23 +110,34 @@ func (state *pcState) purgePeer(peerID p2p.ID) {
// handle processes FSM events
func (state *pcState) handle(event Event) (Event, error) {
switch event := event.(type) {
case *scBlockReceived:
case scFinishedEv:
if state.synced() {
return pcFinished{tmState: state.context.tmState(), blocksSynced: state.blocksSynced}, nil
}
state.draining = true
return noOp, nil
case scPeerError:
state.purgePeer(event.peerID)
return noOp, nil
case scBlockReceived:
if event.block == nil {
panic("processor received an event with a nil block")
}
if event.block.Height <= state.height {
return pcShortBlock{}, nil
}
err := state.enqueue(event.peerID, event.block, event.block.Height)
if err != nil {
return pcDuplicateBlock{}, nil
return noOp, nil
}
case pcProcessBlock:
// enqueue block if height is higher than state height, else ignore it
if event.block.Height > state.height() {
state.enqueue(event.peerID, event.block, event.block.Height)
}
return noOp, nil
case rProcessBlock:
tmState := state.context.tmState()
firstItem, secondItem, err := state.nextTwo()
if err != nil {
if state.draining {
return noOp, pcFinished{height: state.height}
return pcFinished{tmState: tmState, blocksSynced: state.blocksSynced}, nil
}
return noOp, nil
}
@@ -155,7 +147,7 @@ func (state *pcState) handle(event Event) (Event, error) {
firstPartsHeader := firstParts.Header()
firstID := types.BlockID{Hash: first.Hash(), PartsHeader: firstPartsHeader}
err = state.context.verifyCommit(state.chainID, firstID, first.Height, second.LastCommit)
err = state.context.verifyCommit(tmState.ChainID, firstID, first.Height, second.LastCommit)
if err != nil {
state.purgePeer(firstItem.peerID)
state.purgePeer(secondItem.peerID)
@@ -166,21 +158,15 @@ func (state *pcState) handle(event Event) (Event, error) {
state.context.saveBlock(first, firstParts, second.LastCommit)
state.tdState, err = state.context.applyBlock(state.tdState, firstID, first)
if err != nil {
if err := state.context.applyBlock(firstID, first); err != nil {
panic(fmt.Sprintf("failed to process committed block (%d:%X): %v", first.Height, first.Hash(), err))
}
state.advance()
delete(state.queue, first.Height)
state.blocksSynced++
return pcBlockProcessed{height: first.Height, peerID: firstItem.peerID}, nil
case *peerError:
state.purgePeer(event.peerID)
case pcStop:
if state.synced() {
return noOp, pcFinished{height: state.height, blocksSynced: state.blocksSynced}
}
state.draining = true
}
return noOp, nil

View File

@@ -4,37 +4,41 @@ import (
"fmt"
"github.com/tendermint/tendermint/state"
"github.com/tendermint/tendermint/store"
"github.com/tendermint/tendermint/types"
)
type processorContext interface {
applyBlock(state state.State, blockID types.BlockID, block *types.Block) (state.State, error)
applyBlock(blockID types.BlockID, block *types.Block) error
verifyCommit(chainID string, blockID types.BlockID, height int64, commit *types.Commit) error
saveBlock(block *types.Block, blockParts *types.PartSet, seenCommit *types.Commit)
tmState() state.State
}
// nolint:unused
type pContext struct {
store *store.BlockStore
executor *state.BlockExecutor
state *state.State
store blockStore
applier blockApplier
state state.State
}
// nolint:unused,deadcode
func newProcessorContext(st *store.BlockStore, ex *state.BlockExecutor, s *state.State) *pContext {
func newProcessorContext(st blockStore, ex blockApplier, s state.State) *pContext {
return &pContext{
store: st,
executor: ex,
state: s,
store: st,
applier: ex,
state: s,
}
}
func (pc *pContext) applyBlock(state state.State, blockID types.BlockID, block *types.Block) (state.State, error) {
return pc.executor.ApplyBlock(state, blockID, block)
func (pc *pContext) applyBlock(blockID types.BlockID, block *types.Block) error {
newState, err := pc.applier.ApplyBlock(pc.state, blockID, block)
pc.state = newState
return err
}
func (pc *pContext) verifyCommit(chainID string, blockID types.BlockID, height int64, commit *types.Commit) error {
func (pc pContext) tmState() state.State {
return pc.state
}
func (pc pContext) verifyCommit(chainID string, blockID types.BlockID, height int64, commit *types.Commit) error {
return pc.state.Validators.VerifyCommit(chainID, blockID, height, commit)
}
@@ -45,22 +49,28 @@ func (pc *pContext) saveBlock(block *types.Block, blockParts *types.PartSet, see
type mockPContext struct {
applicationBL []int64
verificationBL []int64
state state.State
}
func newMockProcessorContext(verificationBlackList []int64, applicationBlackList []int64) *mockPContext {
func newMockProcessorContext(
state state.State,
verificationBlackList []int64,
applicationBlackList []int64) *mockPContext {
return &mockPContext{
applicationBL: applicationBlackList,
verificationBL: verificationBlackList,
state: state,
}
}
func (mpc *mockPContext) applyBlock(state state.State, blockID types.BlockID, block *types.Block) (state.State, error) {
func (mpc *mockPContext) applyBlock(blockID types.BlockID, block *types.Block) error {
for _, h := range mpc.applicationBL {
if h == block.Height {
return state, fmt.Errorf("generic application error")
return fmt.Errorf("generic application error")
}
}
return state, nil
mpc.state.LastBlockHeight = block.Height
return nil
}
func (mpc *mockPContext) verifyCommit(chainID string, blockID types.BlockID, height int64, commit *types.Commit) error {
@@ -73,4 +83,9 @@ func (mpc *mockPContext) verifyCommit(chainID string, blockID types.BlockID, hei
}
func (mpc *mockPContext) saveBlock(block *types.Block, blockParts *types.PartSet, seenCommit *types.Commit) {
}
func (mpc *mockPContext) tmState() state.State {
return mpc.state
}

View File

@@ -5,7 +5,7 @@ import (
"github.com/stretchr/testify/assert"
"github.com/tendermint/tendermint/p2p"
tdState "github.com/tendermint/tendermint/state"
tmState "github.com/tendermint/tendermint/state"
"github.com/tendermint/tendermint/types"
)
@@ -19,7 +19,7 @@ type pcBlock struct {
type params struct {
height int64
items []pcBlock
blocksSynced int64
blocksSynced int
verBL []int64
appBL []int64
draining bool
@@ -33,13 +33,13 @@ func makePcBlock(height int64) *types.Block {
// makeState takes test parameters and creates a specific processor state.
func makeState(p *params) *pcState {
var (
tdState = tdState.State{}
context = newMockProcessorContext(p.verBL, p.appBL)
tmState = tmState.State{LastBlockHeight: p.height}
context = newMockProcessorContext(tmState, p.verBL, p.appBL)
)
state := newPcState(p.height, tdState, "test", context)
state := newPcState(context)
for _, item := range p.items {
_ = state.enqueue(p2p.ID(item.pid), makePcBlock(item.height), item.height)
state.enqueue(p2p.ID(item.pid), makePcBlock(item.height), item.height)
}
state.blocksSynced = p.blocksSynced
@@ -47,8 +47,8 @@ func makeState(p *params) *pcState {
return state
}
func mBlockResponse(peerID p2p.ID, height int64) *scBlockReceived {
return &scBlockReceived{
func mBlockResponse(peerID p2p.ID, height int64) scBlockReceived {
return scBlockReceived{
peerID: peerID,
block: makePcBlock(height),
}
@@ -101,9 +101,37 @@ func executeProcessorTests(t *testing.T, tests []testFields) {
}
}
func TestRProcessPeerError(t *testing.T) {
tests := []testFields{
{
name: "error for existing peer",
steps: []pcFsmMakeStateValues{
{
currentState: &params{items: []pcBlock{{"P1", 1}, {"P2", 2}}},
event: scPeerError{peerID: "P2"},
wantState: &params{items: []pcBlock{{"P1", 1}}},
wantNextEvent: noOp,
},
},
},
{
name: "error for unknown peer",
steps: []pcFsmMakeStateValues{
{
currentState: &params{items: []pcBlock{{"P1", 1}, {"P2", 2}}},
event: scPeerError{peerID: "P3"},
wantState: &params{items: []pcBlock{{"P1", 1}, {"P2", 2}}},
wantNextEvent: noOp,
},
},
},
}
executeProcessorTests(t, tests)
}
func TestPcBlockResponse(t *testing.T) {
tests := []testFields{
{
name: "add one block",
steps: []pcFsmMakeStateValues{
@@ -113,6 +141,7 @@ func TestPcBlockResponse(t *testing.T) {
},
},
},
{
name: "add two blocks",
steps: []pcFsmMakeStateValues{
@@ -126,62 +155,18 @@ func TestPcBlockResponse(t *testing.T) {
},
},
},
{
name: "add duplicate block from same peer",
steps: []pcFsmMakeStateValues{
{
currentState: &params{}, event: mBlockResponse("P1", 3),
wantState: &params{items: []pcBlock{{"P1", 3}}}, wantNextEvent: noOp,
},
{ // use previous wantState as currentState,
event: mBlockResponse("P1", 3),
wantState: &params{items: []pcBlock{{"P1", 3}}}, wantNextEvent: pcDuplicateBlock{},
},
},
},
{
name: "add duplicate block from different peer",
steps: []pcFsmMakeStateValues{
{
currentState: &params{}, event: mBlockResponse("P1", 3),
wantState: &params{items: []pcBlock{{"P1", 3}}}, wantNextEvent: noOp,
},
{ // use previous wantState as currentState,
event: mBlockResponse("P2", 3),
wantState: &params{items: []pcBlock{{"P1", 3}}}, wantNextEvent: pcDuplicateBlock{},
},
},
},
{
name: "attempt to add block with height equal to state.height",
steps: []pcFsmMakeStateValues{
{
currentState: &params{height: 2, items: []pcBlock{{"P1", 3}}}, event: mBlockResponse("P1", 2),
wantState: &params{height: 2, items: []pcBlock{{"P1", 3}}}, wantNextEvent: pcShortBlock{},
},
},
},
{
name: "attempt to add block with height smaller than state.height",
steps: []pcFsmMakeStateValues{
{
currentState: &params{height: 2, items: []pcBlock{{"P1", 3}}}, event: mBlockResponse("P1", 1),
wantState: &params{height: 2, items: []pcBlock{{"P1", 3}}}, wantNextEvent: pcShortBlock{},
},
},
},
}
executeProcessorTests(t, tests)
}
func TestPcProcessBlockSuccess(t *testing.T) {
func TestRProcessBlockSuccess(t *testing.T) {
tests := []testFields{
{
name: "noop - no blocks over current height",
steps: []pcFsmMakeStateValues{
{
currentState: &params{}, event: pcProcessBlock{},
currentState: &params{}, event: rProcessBlock{},
wantState: &params{}, wantNextEvent: noOp,
},
},
@@ -190,7 +175,7 @@ func TestPcProcessBlockSuccess(t *testing.T) {
name: "noop - high new blocks",
steps: []pcFsmMakeStateValues{
{
currentState: &params{height: 5, items: []pcBlock{{"P1", 30}, {"P2", 31}}}, event: pcProcessBlock{},
currentState: &params{height: 5, items: []pcBlock{{"P1", 30}, {"P2", 31}}}, event: rProcessBlock{},
wantState: &params{height: 5, items: []pcBlock{{"P1", 30}, {"P2", 31}}}, wantNextEvent: noOp,
},
},
@@ -199,7 +184,7 @@ func TestPcProcessBlockSuccess(t *testing.T) {
name: "blocks H+1 and H+2 present",
steps: []pcFsmMakeStateValues{
{
currentState: &params{items: []pcBlock{{"P1", 1}, {"P2", 2}}}, event: pcProcessBlock{},
currentState: &params{items: []pcBlock{{"P1", 1}, {"P2", 2}}}, event: rProcessBlock{},
wantState: &params{height: 1, items: []pcBlock{{"P2", 2}}, blocksSynced: 1},
wantNextEvent: pcBlockProcessed{height: 1, peerID: "P1"},
},
@@ -209,20 +194,20 @@ func TestPcProcessBlockSuccess(t *testing.T) {
name: "blocks H+1 and H+2 present after draining",
steps: []pcFsmMakeStateValues{
{ // some contiguous blocks - on stop check draining is set
currentState: &params{items: []pcBlock{{"P1", 1}, {"P2", 2}, {"P1", 4}}}, event: pcStop{},
currentState: &params{items: []pcBlock{{"P1", 1}, {"P2", 2}, {"P1", 4}}},
event: scFinishedEv{},
wantState: &params{items: []pcBlock{{"P1", 1}, {"P2", 2}, {"P1", 4}}, draining: true},
wantNextEvent: noOp,
},
{
event: pcProcessBlock{},
event: rProcessBlock{},
wantState: &params{height: 1, items: []pcBlock{{"P2", 2}, {"P1", 4}}, blocksSynced: 1, draining: true},
wantNextEvent: pcBlockProcessed{height: 1, peerID: "P1"},
},
{ // finish when H+1 or/and H+2 are missing
event: pcProcessBlock{},
event: rProcessBlock{},
wantState: &params{height: 1, items: []pcBlock{{"P2", 2}, {"P1", 4}}, blocksSynced: 1, draining: true},
wantNextEvent: noOp,
wantErr: pcFinished{height: 1},
wantNextEvent: pcFinished{tmState: tmState.State{LastBlockHeight: 1}, blocksSynced: 1},
},
},
},
@@ -231,13 +216,13 @@ func TestPcProcessBlockSuccess(t *testing.T) {
executeProcessorTests(t, tests)
}
func TestPcProcessBlockFailures(t *testing.T) {
func TestRProcessBlockFailures(t *testing.T) {
tests := []testFields{
{
name: "blocks H+1 and H+2 present from different peers - H+1 verification fails ",
steps: []pcFsmMakeStateValues{
{
currentState: &params{items: []pcBlock{{"P1", 1}, {"P2", 2}}, verBL: []int64{1}}, event: pcProcessBlock{},
currentState: &params{items: []pcBlock{{"P1", 1}, {"P2", 2}}, verBL: []int64{1}}, event: rProcessBlock{},
wantState: &params{items: []pcBlock{}, verBL: []int64{1}},
wantNextEvent: pcBlockVerificationFailure{height: 1, firstPeerID: "P1", secondPeerID: "P2"},
},
@@ -247,7 +232,7 @@ func TestPcProcessBlockFailures(t *testing.T) {
name: "blocks H+1 and H+2 present from same peer - H+1 applyBlock fails ",
steps: []pcFsmMakeStateValues{
{
currentState: &params{items: []pcBlock{{"P1", 1}, {"P2", 2}}, appBL: []int64{1}}, event: pcProcessBlock{},
currentState: &params{items: []pcBlock{{"P1", 1}, {"P2", 2}}, appBL: []int64{1}}, event: rProcessBlock{},
wantState: &params{items: []pcBlock{}, appBL: []int64{1}}, wantPanic: true,
},
},
@@ -256,9 +241,9 @@ func TestPcProcessBlockFailures(t *testing.T) {
name: "blocks H+1 and H+2 present from same peers - H+1 verification fails ",
steps: []pcFsmMakeStateValues{
{
currentState: &params{items: []pcBlock{{"P1", 1}, {"P1", 2}, {"P2", 3}}, verBL: []int64{1}},
event: pcProcessBlock{},
wantState: &params{items: []pcBlock{{"P2", 3}}, verBL: []int64{1}},
currentState: &params{height: 0, items: []pcBlock{{"P1", 1}, {"P1", 2}, {"P2", 3}},
verBL: []int64{1}}, event: rProcessBlock{},
wantState: &params{height: 0, items: []pcBlock{{"P2", 3}}, verBL: []int64{1}},
wantNextEvent: pcBlockVerificationFailure{height: 1, firstPeerID: "P1", secondPeerID: "P1"},
},
},
@@ -268,7 +253,7 @@ func TestPcProcessBlockFailures(t *testing.T) {
steps: []pcFsmMakeStateValues{
{
currentState: &params{items: []pcBlock{{"P1", 1}, {"P2", 2}, {"P2", 3}}, appBL: []int64{1}},
event: pcProcessBlock{},
event: rProcessBlock{},
wantState: &params{items: []pcBlock{{"P2", 3}}, appBL: []int64{1}}, wantPanic: true,
},
},
@@ -278,53 +263,15 @@ func TestPcProcessBlockFailures(t *testing.T) {
executeProcessorTests(t, tests)
}
func TestPcPeerError(t *testing.T) {
tests := []testFields{
{
name: "peer not present",
steps: []pcFsmMakeStateValues{
{
currentState: &params{items: []pcBlock{{"P1", 1}, {"P2", 2}}}, event: &peerError{peerID: "P3"},
wantState: &params{items: []pcBlock{{"P1", 1}, {"P2", 2}}},
wantNextEvent: noOp,
},
},
},
{
name: "some blocks are from errored peer",
steps: []pcFsmMakeStateValues{
{
currentState: &params{items: []pcBlock{{"P1", 100}, {"P1", 99}, {"P2", 101}}}, event: &peerError{peerID: "P1"},
wantState: &params{items: []pcBlock{{"P2", 101}}},
wantNextEvent: noOp,
},
},
},
{
name: "all blocks are from errored peer",
steps: []pcFsmMakeStateValues{
{
currentState: &params{items: []pcBlock{{"P1", 100}, {"P1", 99}}}, event: &peerError{peerID: "P1"},
wantState: &params{},
wantNextEvent: noOp,
},
},
},
}
executeProcessorTests(t, tests)
}
func TestStop(t *testing.T) {
func TestScFinishedEv(t *testing.T) {
tests := []testFields{
{
name: "no blocks",
steps: []pcFsmMakeStateValues{
{
currentState: &params{height: 100, items: []pcBlock{}, blocksSynced: 100}, event: pcStop{},
currentState: &params{height: 100, items: []pcBlock{}, blocksSynced: 100}, event: scFinishedEv{},
wantState: &params{height: 100, items: []pcBlock{}, blocksSynced: 100},
wantNextEvent: noOp,
wantErr: pcFinished{height: 100, blocksSynced: 100},
wantNextEvent: pcFinished{tmState: tmState.State{LastBlockHeight: 100}, blocksSynced: 100},
},
},
},
@@ -332,10 +279,10 @@ func TestStop(t *testing.T) {
name: "maxHeight+1 block present",
steps: []pcFsmMakeStateValues{
{
currentState: &params{height: 100, items: []pcBlock{{"P1", 101}}, blocksSynced: 100}, event: pcStop{},
currentState: &params{height: 100, items: []pcBlock{
{"P1", 101}}, blocksSynced: 100}, event: scFinishedEv{},
wantState: &params{height: 100, items: []pcBlock{{"P1", 101}}, blocksSynced: 100},
wantNextEvent: noOp,
wantErr: pcFinished{height: 100, blocksSynced: 100},
wantNextEvent: pcFinished{tmState: tmState.State{LastBlockHeight: 100}, blocksSynced: 100},
},
},
},
@@ -343,8 +290,10 @@ func TestStop(t *testing.T) {
name: "more blocks present",
steps: []pcFsmMakeStateValues{
{
currentState: &params{height: 100, items: []pcBlock{{"P1", 101}, {"P1", 102}}, blocksSynced: 100}, event: pcStop{},
wantState: &params{height: 100, items: []pcBlock{{"P1", 101}, {"P1", 102}}, blocksSynced: 100, draining: true},
currentState: &params{height: 100, items: []pcBlock{
{"P1", 101}, {"P1", 102}}, blocksSynced: 100}, event: scFinishedEv{},
wantState: &params{height: 100, items: []pcBlock{
{"P1", 101}, {"P1", 102}}, blocksSynced: 100, draining: true},
wantNextEvent: noOp,
wantErr: nil,
},

View File

@@ -1,118 +1,529 @@
package v2
import (
"errors"
"fmt"
"sync"
"time"
"github.com/tendermint/go-amino"
"github.com/tendermint/tendermint/behaviour"
"github.com/tendermint/tendermint/libs/log"
"github.com/tendermint/tendermint/p2p"
"github.com/tendermint/tendermint/state"
"github.com/tendermint/tendermint/types"
)
type timeCheck struct {
priorityHigh
time time.Time
//-------------------------------------
type bcBlockRequestMessage struct {
Height int64
}
func schedulerHandle(event Event) (Event, error) {
if _, ok := event.(timeCheck); ok {
fmt.Println("scheduler handle timeCheck")
// ValidateBasic performs basic validation.
func (m *bcBlockRequestMessage) ValidateBasic() error {
if m.Height < 0 {
return errors.New("negative Height")
}
return noOp, nil
return nil
}
func processorHandle(event Event) (Event, error) {
if _, ok := event.(timeCheck); ok {
fmt.Println("processor handle timeCheck")
func (m *bcBlockRequestMessage) String() string {
return fmt.Sprintf("[bcBlockRequestMessage %v]", m.Height)
}
type bcNoBlockResponseMessage struct {
Height int64
}
// ValidateBasic performs basic validation.
func (m *bcNoBlockResponseMessage) ValidateBasic() error {
if m.Height < 0 {
return errors.New("negative Height")
}
return noOp, nil
return nil
}
type Reactor struct {
events chan Event
func (m *bcNoBlockResponseMessage) String() string {
return fmt.Sprintf("[bcNoBlockResponseMessage %d]", m.Height)
}
//-------------------------------------
type bcBlockResponseMessage struct {
Block *types.Block
}
// ValidateBasic performs basic validation.
func (m *bcBlockResponseMessage) ValidateBasic() error {
if m.Block == nil {
return errors.New("block response message has nil block")
}
return m.Block.ValidateBasic()
}
func (m *bcBlockResponseMessage) String() string {
return fmt.Sprintf("[bcBlockResponseMessage %v]", m.Block.Height)
}
//-------------------------------------
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)
}
//-------------------------------------
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)
}
type blockStore interface {
LoadBlock(height int64) *types.Block
SaveBlock(*types.Block, *types.PartSet, *types.Commit)
Height() int64
}
// BlockchainReactor handles fast sync protocol.
type BlockchainReactor struct {
p2p.BaseReactor
events chan Event // XXX: Rename eventsFromPeers
stopDemux chan struct{}
scheduler *Routine
processor *Routine
ticker *time.Ticker
logger log.Logger
mtx sync.RWMutex
maxPeerHeight int64
syncHeight int64
reporter behaviour.Reporter
io iIO
store blockStore
}
func NewReactor(bufferSize int) *Reactor {
return &Reactor{
//nolint:unused,deadcode
type blockVerifier interface {
VerifyCommit(chainID string, blockID types.BlockID, height int64, commit *types.Commit) error
}
//nolint:deadcode
type blockApplier interface {
ApplyBlock(state state.State, blockID types.BlockID, block *types.Block) (state.State, error)
}
// XXX: unify naming in this package around tmState
// XXX: V1 stores a copy of state as initialState, which is never mutated. Is that nessesary?
func newReactor(state state.State, store blockStore, reporter behaviour.Reporter,
blockApplier blockApplier, bufferSize int) *BlockchainReactor {
scheduler := newScheduler(state.LastBlockHeight, time.Now())
pContext := newProcessorContext(store, blockApplier, state)
// TODO: Fix naming to just newProcesssor
// newPcState requires a processorContext
processor := newPcState(pContext)
return &BlockchainReactor{
events: make(chan Event, bufferSize),
stopDemux: make(chan struct{}),
scheduler: newRoutine("scheduler", schedulerHandle, bufferSize),
processor: newRoutine("processor", processorHandle, bufferSize),
ticker: time.NewTicker(1 * time.Second),
scheduler: newRoutine("scheduler", scheduler.handle, bufferSize),
processor: newRoutine("processor", processor.handle, bufferSize),
store: store,
reporter: reporter,
logger: log.NewNopLogger(),
}
}
// nolint:unused
func (r *Reactor) setLogger(logger log.Logger) {
// NewBlockchainReactor creates a new reactor instance.
func NewBlockchainReactor(
state state.State,
blockApplier blockApplier,
store blockStore,
fastSync bool) *BlockchainReactor {
reporter := behaviour.NewMockReporter()
return newReactor(state, store, reporter, blockApplier, 1000)
}
// SetSwitch implements Reactor interface.
func (r *BlockchainReactor) SetSwitch(sw *p2p.Switch) {
if sw == nil {
panic("set nil switch")
}
r.Switch = sw
r.io = newSwitchIo(sw)
}
func (r *BlockchainReactor) setMaxPeerHeight(height int64) {
r.mtx.Lock()
defer r.mtx.Unlock()
if height > r.maxPeerHeight {
r.maxPeerHeight = height
}
}
func (r *BlockchainReactor) setSyncHeight(height int64) {
r.mtx.Lock()
defer r.mtx.Unlock()
r.syncHeight = height
}
// SyncHeight returns the height to which the BlockchainReactor has synced.
func (r *BlockchainReactor) SyncHeight() int64 {
r.mtx.RLock()
defer r.mtx.RUnlock()
return r.syncHeight
}
// SetLogger sets the logger of the reactor.
func (r *BlockchainReactor) SetLogger(logger log.Logger) {
r.logger = logger
r.scheduler.setLogger(logger)
r.processor.setLogger(logger)
}
func (r *Reactor) Start() {
// Start implements cmn.Service interface
func (r *BlockchainReactor) Start() error {
r.reporter = behaviour.NewSwitchReporter(r.BaseReactor.Switch)
go r.scheduler.start()
go r.processor.start()
go r.demux()
<-r.scheduler.ready()
<-r.processor.ready()
go func() {
for t := range r.ticker.C {
r.events <- timeCheck{time: t}
}
}()
return nil
}
// XXX: How to make this deterministic?
// XXX: Would it be possible here to provide some kind of type safety for the types
// of events that each routine can produce and consume?
func (r *Reactor) demux() {
// reactor generated ticker events:
// ticker for cleaning peers
type rTryPrunePeer struct {
priorityHigh
time time.Time
}
func (e rTryPrunePeer) String() string {
return fmt.Sprintf(": %v", e.time)
}
// ticker event for scheduling block requests
type rTrySchedule struct {
priorityHigh
time time.Time
}
func (e rTrySchedule) String() string {
return fmt.Sprintf(": %v", e.time)
}
// ticker for block processing
type rProcessBlock struct {
priorityNormal
}
// reactor generated events based on blockchain related messages from peers:
// blockResponse message received from a peer
type bcBlockResponse struct {
priorityNormal
time time.Time
peerID p2p.ID
size int64
block *types.Block
}
// blockNoResponse message received from a peer
type bcNoBlockResponse struct {
priorityNormal
time time.Time
peerID p2p.ID
height int64
}
// statusResponse message received from a peer
type bcStatusResponse struct {
priorityNormal
time time.Time
peerID p2p.ID
height int64
}
// new peer is connected
type bcAddNewPeer struct {
priorityNormal
peerID p2p.ID
}
// existing peer is removed
type bcRemovePeer struct {
priorityHigh
peerID p2p.ID
reason interface{}
}
func (r *BlockchainReactor) demux() {
var lastRate = 0.0
var lastHundred = time.Now()
var (
processBlockFreq = 20 * time.Millisecond
doProcessBlockCh = make(chan struct{}, 1)
doProcessBlockTk = time.NewTicker(processBlockFreq)
prunePeerFreq = 1 * time.Second
doPrunePeerCh = make(chan struct{}, 1)
doPrunePeerTk = time.NewTicker(prunePeerFreq)
scheduleFreq = 20 * time.Millisecond
doScheduleCh = make(chan struct{}, 1)
doScheduleTk = time.NewTicker(scheduleFreq)
statusFreq = 10 * time.Second
doStatusCh = make(chan struct{}, 1)
doStatusTk = time.NewTicker(statusFreq)
)
// XXX: Extract timers to make testing atemporal
for {
select {
// Pacers: send at most per frequency but don't saturate
case <-doProcessBlockTk.C:
select {
case doProcessBlockCh <- struct{}{}:
default:
}
case <-doPrunePeerTk.C:
select {
case doPrunePeerCh <- struct{}{}:
default:
}
case <-doScheduleTk.C:
select {
case doScheduleCh <- struct{}{}:
default:
}
case <-doStatusTk.C:
select {
case doStatusCh <- struct{}{}:
default:
}
// Tickers: perform tasks periodically
case <-doScheduleCh:
r.scheduler.send(rTrySchedule{time: time.Now()})
case <-doPrunePeerCh:
r.scheduler.send(rTryPrunePeer{time: time.Now()})
case <-doProcessBlockCh:
r.processor.send(rProcessBlock{})
case <-doStatusCh:
r.io.broadcastStatusRequest(r.SyncHeight())
// Events from peers
case event := <-r.events:
// XXX: check for backpressure
r.scheduler.send(event)
r.processor.send(event)
switch event := event.(type) {
case bcStatusResponse:
r.setMaxPeerHeight(event.height)
r.scheduler.send(event)
case bcAddNewPeer, bcRemovePeer, bcBlockResponse, bcNoBlockResponse:
r.scheduler.send(event)
}
// Incremental events form scheduler
case event := <-r.scheduler.next():
switch event := event.(type) {
case scBlockReceived:
r.processor.send(event)
case scPeerError:
r.processor.send(event)
r.reporter.Report(behaviour.BadMessage(event.peerID, "scPeerError"))
case scBlockRequest:
r.io.sendBlockRequest(event.peerID, event.height)
case scFinishedEv:
r.processor.send(event)
r.scheduler.stop()
}
// Incremental events from processor
case event := <-r.processor.next():
switch event := event.(type) {
case pcBlockProcessed:
r.setSyncHeight(event.height)
if r.syncHeight%100 == 0 {
lastRate = 0.9*lastRate + 0.1*(100/time.Since(lastHundred).Seconds())
r.logger.Info("Fast Syncc Rate", "height", r.syncHeight,
"max_peer_height", r.maxPeerHeight, "blocks/s", lastRate)
lastHundred = time.Now()
}
r.scheduler.send(event)
case pcBlockVerificationFailure:
r.scheduler.send(event)
case pcFinished:
r.io.trySwitchToConsensus(event.tmState, event.blocksSynced)
r.processor.stop()
}
// Terminal events from scheduler
case err := <-r.scheduler.final():
r.logger.Info(fmt.Sprintf("scheduler final %s", err))
// send the processor stop?
// Terminal event from processor
case event := <-r.processor.final():
r.logger.Info(fmt.Sprintf("processor final %s", event))
case <-r.stopDemux:
r.logger.Info("demuxing stopped")
return
case event := <-r.scheduler.next():
r.processor.send(event)
case event := <-r.processor.next():
r.scheduler.send(event)
case err := <-r.scheduler.final():
r.logger.Info(fmt.Sprintf("scheduler final %s", err))
case err := <-r.processor.final():
r.logger.Info(fmt.Sprintf("processor final %s", err))
// XXX: switch to consensus
}
}
}
func (r *Reactor) Stop() {
// Stop implements cmn.Service interface.
func (r *BlockchainReactor) Stop() error {
r.logger.Info("reactor stopping")
r.ticker.Stop()
r.scheduler.stop()
r.processor.stop()
close(r.stopDemux)
close(r.events)
r.logger.Info("reactor stopped")
return nil
}
func (r *Reactor) Receive(event Event) {
// XXX: decode and serialize write events
// TODO: backpressure
const (
// NOTE: keep up to date with bcBlockResponseMessage
bcBlockResponseMessagePrefixSize = 4
bcBlockResponseMessageFieldKeySize = 1
maxMsgSize = types.MaxBlockSizeBytes +
bcBlockResponseMessagePrefixSize +
bcBlockResponseMessageFieldKeySize
)
// BlockchainMessage is a generic message for this reactor.
type BlockchainMessage interface {
ValidateBasic() error
}
// RegisterBlockchainMessages registers the fast sync messages for amino encoding.
func RegisterBlockchainMessages(cdc *amino.Codec) {
cdc.RegisterInterface((*BlockchainMessage)(nil), nil)
cdc.RegisterConcrete(&bcBlockRequestMessage{}, "tendermint/blockchain/BlockRequest", nil)
cdc.RegisterConcrete(&bcBlockResponseMessage{}, "tendermint/blockchain/BlockResponse", nil)
cdc.RegisterConcrete(&bcNoBlockResponseMessage{}, "tendermint/blockchain/NoBlockResponse", nil)
cdc.RegisterConcrete(&bcStatusResponseMessage{}, "tendermint/blockchain/StatusResponse", nil)
cdc.RegisterConcrete(&bcStatusRequestMessage{}, "tendermint/blockchain/StatusRequest", nil)
}
func decodeMsg(bz []byte) (msg BlockchainMessage, err error) {
if len(bz) > maxMsgSize {
return msg, fmt.Errorf("msg exceeds max size (%d > %d)", len(bz), maxMsgSize)
}
err = cdc.UnmarshalBinaryBare(bz, &msg)
return
}
// Receive implements Reactor by handling different message types.
func (r *BlockchainReactor) Receive(chID byte, src p2p.Peer, msgBytes []byte) {
msg, err := decodeMsg(msgBytes)
if err != nil {
r.logger.Error("error decoding message",
"src", src.ID(), "chId", chID, "msg", msg, "err", err, "bytes", msgBytes)
_ = r.reporter.Report(behaviour.BadMessage(src.ID(), err.Error()))
return
}
if err = msg.ValidateBasic(); err != nil {
r.logger.Error("peer sent us invalid msg", "peer", src, "msg", msg, "err", err)
_ = r.reporter.Report(behaviour.BadMessage(src.ID(), err.Error()))
return
}
r.logger.Debug("Receive", "src", src.ID(), "chID", chID, "msg", msg)
switch msg := msg.(type) {
case *bcStatusRequestMessage:
if err := r.io.sendStatusResponse(r.store.Height(), src.ID()); err != nil {
r.logger.Error("Could not send status message to peer", "src", src)
}
case *bcBlockRequestMessage:
block := r.store.LoadBlock(msg.Height)
if block != nil {
if err = r.io.sendBlockToPeer(block, src.ID()); err != nil {
r.logger.Error("Could not send block message to peer: ", err)
}
} else {
r.logger.Info("peer asking for a block we don't have", "src", src, "height", msg.Height)
peerID := src.ID()
if err = r.io.sendBlockNotFound(msg.Height, peerID); err != nil {
r.logger.Error("Couldn't send block not found: ", err)
}
}
case *bcStatusResponseMessage:
r.events <- bcStatusResponse{peerID: src.ID(), height: msg.Height}
case *bcBlockResponseMessage:
r.events <- bcBlockResponse{
peerID: src.ID(),
block: msg.Block,
size: int64(len(msgBytes)),
time: time.Now(),
}
case *bcNoBlockResponseMessage:
r.events <- bcNoBlockResponse{peerID: src.ID(), height: msg.Height, time: time.Now()}
}
}
// AddPeer implements Reactor interface
func (r *BlockchainReactor) AddPeer(peer p2p.Peer) {
err := r.io.sendStatusResponse(r.store.Height(), peer.ID())
if err != nil {
r.logger.Error("Could not send status message to peer new", "src", peer.ID, "height", r.SyncHeight())
}
r.events <- bcAddNewPeer{peerID: peer.ID()}
}
// RemovePeer implements Reactor interface.
func (r *BlockchainReactor) RemovePeer(peer p2p.Peer, reason interface{}) {
event := bcRemovePeer{
peerID: peer.ID(),
reason: reason,
}
r.events <- event
}
func (r *Reactor) AddPeer() {
// TODO: add peer event and send to demuxer
// GetChannels implements Reactor
func (r *BlockchainReactor) GetChannels() []*p2p.ChannelDescriptor {
return []*p2p.ChannelDescriptor{
{
ID: BlockchainChannel,
Priority: 10,
SendQueueCapacity: 2000,
RecvBufferCapacity: 50 * 4096,
RecvMessageCapacity: maxMsgSize,
},
}
}

View File

@@ -1,22 +1,517 @@
package v2
import (
"net"
"os"
"sort"
"sync"
"testing"
"time"
"github.com/pkg/errors"
"github.com/stretchr/testify/assert"
abci "github.com/tendermint/tendermint/abci/types"
"github.com/tendermint/tendermint/behaviour"
cfg "github.com/tendermint/tendermint/config"
"github.com/tendermint/tendermint/libs/log"
"github.com/tendermint/tendermint/libs/service"
"github.com/tendermint/tendermint/mock"
"github.com/tendermint/tendermint/p2p"
"github.com/tendermint/tendermint/p2p/conn"
"github.com/tendermint/tendermint/proxy"
sm "github.com/tendermint/tendermint/state"
"github.com/tendermint/tendermint/store"
"github.com/tendermint/tendermint/types"
tmtime "github.com/tendermint/tendermint/types/time"
dbm "github.com/tendermint/tm-db"
)
func TestReactor(t *testing.T) {
type mockPeer struct {
service.Service
id p2p.ID
}
func (mp mockPeer) FlushStop() {}
func (mp mockPeer) ID() p2p.ID { return mp.id }
func (mp mockPeer) RemoteIP() net.IP { return net.IP{} }
func (mp mockPeer) RemoteAddr() net.Addr { return &net.TCPAddr{IP: mp.RemoteIP(), Port: 8800} }
func (mp mockPeer) IsOutbound() bool { return true }
func (mp mockPeer) IsPersistent() bool { return true }
func (mp mockPeer) CloseConn() error { return nil }
func (mp mockPeer) NodeInfo() p2p.NodeInfo {
return p2p.DefaultNodeInfo{
DefaultNodeID: "",
ListenAddr: "",
}
}
func (mp mockPeer) Status() conn.ConnectionStatus { return conn.ConnectionStatus{} }
func (mp mockPeer) SocketAddr() *p2p.NetAddress { return &p2p.NetAddress{} }
func (mp mockPeer) Send(byte, []byte) bool { return true }
func (mp mockPeer) TrySend(byte, []byte) bool { return true }
func (mp mockPeer) Set(string, interface{}) {}
func (mp mockPeer) Get(string) interface{} { return struct{}{} }
//nolint:unused
type mockBlockStore struct {
blocks map[int64]*types.Block
}
func (ml *mockBlockStore) Height() int64 {
return int64(len(ml.blocks))
}
func (ml *mockBlockStore) LoadBlock(height int64) *types.Block {
return ml.blocks[height]
}
func (ml *mockBlockStore) SaveBlock(block *types.Block, part *types.PartSet, commit *types.Commit) {
ml.blocks[block.Height] = block
}
type mockBlockApplier struct {
}
// XXX: Add whitelist/blacklist?
func (mba *mockBlockApplier) ApplyBlock(state sm.State, blockID types.BlockID, block *types.Block) (sm.State, error) {
state.LastBlockHeight++
return state, nil
}
type mockSwitchIo struct {
mtx sync.Mutex
switchedToConsensus bool
numStatusResponse int
numBlockResponse int
numNoBlockResponse int
}
func (sio *mockSwitchIo) sendBlockRequest(peerID p2p.ID, height int64) error {
return nil
}
func (sio *mockSwitchIo) sendStatusResponse(height int64, peerID p2p.ID) error {
sio.mtx.Lock()
defer sio.mtx.Unlock()
sio.numStatusResponse++
return nil
}
func (sio *mockSwitchIo) sendBlockToPeer(block *types.Block, peerID p2p.ID) error {
sio.mtx.Lock()
defer sio.mtx.Unlock()
sio.numBlockResponse++
return nil
}
func (sio *mockSwitchIo) sendBlockNotFound(height int64, peerID p2p.ID) error {
sio.mtx.Lock()
defer sio.mtx.Unlock()
sio.numNoBlockResponse++
return nil
}
func (sio *mockSwitchIo) trySwitchToConsensus(state sm.State, blocksSynced int) {
sio.mtx.Lock()
defer sio.mtx.Unlock()
sio.switchedToConsensus = true
}
func (sio *mockSwitchIo) hasSwitchedToConsensus() bool {
sio.mtx.Lock()
defer sio.mtx.Unlock()
return sio.switchedToConsensus
}
func (sio *mockSwitchIo) broadcastStatusRequest(height int64) {
}
type testReactorParams struct {
logger log.Logger
genDoc *types.GenesisDoc
privVals []types.PrivValidator
startHeight int64
bufferSize int
mockA bool
}
func newTestReactor(p testReactorParams) *BlockchainReactor {
store, state, _ := newReactorStore(p.genDoc, p.privVals, p.startHeight)
reporter := behaviour.NewMockReporter()
var appl blockApplier
if p.mockA {
appl = &mockBlockApplier{}
} else {
app := &testApp{}
cc := proxy.NewLocalClientCreator(app)
proxyApp := proxy.NewAppConns(cc)
err := proxyApp.Start()
if err != nil {
panic(errors.Wrap(err, "error start app"))
}
db := dbm.NewMemDB()
appl = sm.NewBlockExecutor(db, p.logger, proxyApp.Consensus(), mock.Mempool{}, sm.MockEvidencePool{})
sm.SaveState(db, state)
}
r := newReactor(state, store, reporter, appl, p.bufferSize)
logger := log.TestingLogger()
r.SetLogger(logger.With("module", "blockchain"))
return r
}
func TestReactorTerminationScenarios(t *testing.T) {
config := cfg.ResetTestRoot("blockchain_reactor_v2_test")
defer os.RemoveAll(config.RootDir)
genDoc, privVals := randGenesisDoc(config.ChainID(), 1, false, 30)
refStore, _, _ := newReactorStore(genDoc, privVals, 20)
params := testReactorParams{
logger: log.TestingLogger(),
genDoc: genDoc,
privVals: privVals,
startHeight: 10,
bufferSize: 100,
mockA: true,
}
type testEvent struct {
evType string
peer string
height int64
}
tests := []struct {
name string
params testReactorParams
msgs []testEvent
}{
{
name: "simple termination on max peer height - one peer",
params: params,
msgs: []testEvent{
{evType: "AddPeer", peer: "P1"},
{evType: "ReceiveS", peer: "P1", height: 13},
{evType: "BlockReq"},
{evType: "ReceiveB", peer: "P1", height: 11},
{evType: "BlockReq"},
{evType: "BlockReq"},
{evType: "ReceiveB", peer: "P1", height: 12},
{evType: "Process"},
{evType: "ReceiveB", peer: "P1", height: 13},
{evType: "Process"},
},
},
{
name: "simple termination on max peer height - two peers",
params: params,
msgs: []testEvent{
{evType: "AddPeer", peer: "P1"},
{evType: "AddPeer", peer: "P2"},
{evType: "ReceiveS", peer: "P1", height: 13},
{evType: "ReceiveS", peer: "P2", height: 15},
{evType: "BlockReq"},
{evType: "BlockReq"},
{evType: "ReceiveB", peer: "P1", height: 11},
{evType: "ReceiveB", peer: "P2", height: 12},
{evType: "Process"},
{evType: "BlockReq"},
{evType: "BlockReq"},
{evType: "ReceiveB", peer: "P1", height: 13},
{evType: "Process"},
{evType: "ReceiveB", peer: "P2", height: 14},
{evType: "Process"},
{evType: "BlockReq"},
{evType: "ReceiveB", peer: "P2", height: 15},
{evType: "Process"},
},
},
{
name: "termination on max peer height - two peers, noBlock error",
params: params,
msgs: []testEvent{
{evType: "AddPeer", peer: "P1"},
{evType: "AddPeer", peer: "P2"},
{evType: "ReceiveS", peer: "P1", height: 13},
{evType: "ReceiveS", peer: "P2", height: 15},
{evType: "BlockReq"},
{evType: "BlockReq"},
{evType: "ReceiveNB", peer: "P1", height: 11},
{evType: "BlockReq"},
{evType: "ReceiveB", peer: "P2", height: 12},
{evType: "ReceiveB", peer: "P2", height: 11},
{evType: "Process"},
{evType: "BlockReq"},
{evType: "BlockReq"},
{evType: "ReceiveB", peer: "P2", height: 13},
{evType: "Process"},
{evType: "ReceiveB", peer: "P2", height: 14},
{evType: "Process"},
{evType: "BlockReq"},
{evType: "ReceiveB", peer: "P2", height: 15},
{evType: "Process"},
},
},
{
name: "termination on max peer height - two peers, remove one peer",
params: params,
msgs: []testEvent{
{evType: "AddPeer", peer: "P1"},
{evType: "AddPeer", peer: "P2"},
{evType: "ReceiveS", peer: "P1", height: 13},
{evType: "ReceiveS", peer: "P2", height: 15},
{evType: "BlockReq"},
{evType: "BlockReq"},
{evType: "RemovePeer", peer: "P1"},
{evType: "BlockReq"},
{evType: "ReceiveB", peer: "P2", height: 12},
{evType: "ReceiveB", peer: "P2", height: 11},
{evType: "Process"},
{evType: "BlockReq"},
{evType: "BlockReq"},
{evType: "ReceiveB", peer: "P2", height: 13},
{evType: "Process"},
{evType: "ReceiveB", peer: "P2", height: 14},
{evType: "Process"},
{evType: "BlockReq"},
{evType: "ReceiveB", peer: "P2", height: 15},
{evType: "Process"},
},
},
}
for _, tt := range tests {
tt := tt
t.Run(tt.name, func(t *testing.T) {
reactor := newTestReactor(params)
reactor.Start()
reactor.reporter = behaviour.NewMockReporter()
mockSwitch := &mockSwitchIo{switchedToConsensus: false}
reactor.io = mockSwitch
// time for go routines to start
time.Sleep(time.Millisecond)
for _, step := range tt.msgs {
switch step.evType {
case "AddPeer":
reactor.scheduler.send(bcAddNewPeer{peerID: p2p.ID(step.peer)})
case "RemovePeer":
reactor.scheduler.send(bcRemovePeer{peerID: p2p.ID(step.peer)})
case "ReceiveS":
reactor.scheduler.send(bcStatusResponse{
peerID: p2p.ID(step.peer),
height: step.height,
time: time.Now(),
})
case "ReceiveB":
reactor.scheduler.send(bcBlockResponse{
peerID: p2p.ID(step.peer),
block: refStore.LoadBlock(step.height),
size: 10,
time: time.Now(),
})
case "ReceiveNB":
reactor.scheduler.send(bcNoBlockResponse{
peerID: p2p.ID(step.peer),
height: step.height,
time: time.Now(),
})
case "BlockReq":
reactor.scheduler.send(rTrySchedule{time: time.Now()})
case "Process":
reactor.processor.send(rProcessBlock{})
}
// give time for messages to propagate between routines
time.Sleep(time.Millisecond)
}
// time for processor to finish and reactor to switch to consensus
time.Sleep(20 * time.Millisecond)
assert.True(t, mockSwitch.hasSwitchedToConsensus())
reactor.Stop()
})
}
}
func TestReactorHelperMode(t *testing.T) {
var (
bufferSize = 10
reactor = NewReactor(bufferSize)
channelID = byte(0x40)
)
reactor.Start()
script := []Event{
// TODO
config := cfg.ResetTestRoot("blockchain_reactor_v2_test")
defer os.RemoveAll(config.RootDir)
genDoc, privVals := randGenesisDoc(config.ChainID(), 1, false, 30)
params := testReactorParams{
logger: log.TestingLogger(),
genDoc: genDoc,
privVals: privVals,
startHeight: 20,
bufferSize: 100,
mockA: true,
}
for _, event := range script {
reactor.Receive(event)
type testEvent struct {
peer string
event interface{}
}
tests := []struct {
name string
params testReactorParams
msgs []testEvent
}{
{
name: "status request",
params: params,
msgs: []testEvent{
{"P1", bcStatusRequestMessage{}},
{"P1", bcBlockRequestMessage{Height: 13}},
{"P1", bcBlockRequestMessage{Height: 20}},
{"P1", bcBlockRequestMessage{Height: 22}},
},
},
}
for _, tt := range tests {
tt := tt
t.Run(tt.name, func(t *testing.T) {
reactor := newTestReactor(params)
reactor.Start()
mockSwitch := &mockSwitchIo{switchedToConsensus: false}
reactor.io = mockSwitch
for i := 0; i < len(tt.msgs); i++ {
step := tt.msgs[i]
switch ev := step.event.(type) {
case bcStatusRequestMessage:
old := mockSwitch.numStatusResponse
reactor.Receive(channelID, mockPeer{id: p2p.ID(step.peer)}, cdc.MustMarshalBinaryBare(ev))
assert.Equal(t, old+1, mockSwitch.numStatusResponse)
case bcBlockRequestMessage:
if ev.Height > params.startHeight {
old := mockSwitch.numNoBlockResponse
reactor.Receive(channelID, mockPeer{id: p2p.ID(step.peer)}, cdc.MustMarshalBinaryBare(ev))
assert.Equal(t, old+1, mockSwitch.numNoBlockResponse)
} else {
old := mockSwitch.numBlockResponse
reactor.Receive(channelID, mockPeer{id: p2p.ID(step.peer)}, cdc.MustMarshalBinaryBare(ev))
assert.Equal(t, old+1, mockSwitch.numBlockResponse)
}
}
}
reactor.Stop()
})
}
reactor.Stop()
}
//----------------------------------------------
// utility funcs
func makeTxs(height int64) (txs []types.Tx) {
for i := 0; i < 10; i++ {
txs = append(txs, types.Tx([]byte{byte(height), byte(i)}))
}
return txs
}
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
}
type testApp struct {
abci.BaseApplication
}
func randGenesisDoc(chainID string, 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: chainID,
Validators: validators,
}, privValidators
}
// Why are we importing the entire blockExecutor dependency graph here
// when we have the facilities to
func newReactorStore(
genDoc *types.GenesisDoc,
privVals []types.PrivValidator,
maxBlockHeight int64) (*store.BlockStore, sm.State, *sm.BlockExecutor) {
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(errors.Wrap(err, "error start app"))
}
stateDB := dbm.NewMemDB()
blockStore := store.NewBlockStore(dbm.NewMemDB())
state, err := sm.LoadStateFromDBOrGenesisDoc(stateDB, genDoc)
if err != nil {
panic(errors.Wrap(err, "error constructing state from genesis file"))
}
db := dbm.NewMemDB()
blockExec := sm.NewBlockExecutor(db, log.TestingLogger(), proxyApp.Consensus(),
mock.Mempool{}, sm.MockEvidencePool{})
sm.SaveState(db, state)
// add blocks in
for blockHeight := int64(1); blockHeight <= maxBlockHeight; blockHeight++ {
lastCommit := types.NewCommit(blockHeight-1, 0, types.BlockID{}, nil)
if blockHeight > 1 {
lastBlockMeta := blockStore.LoadBlockMeta(blockHeight - 1)
lastBlock := blockStore.LoadBlock(blockHeight - 1)
vote, err := types.MakeVote(
lastBlock.Header.Height,
lastBlockMeta.BlockID,
state.Validators,
privVals[0],
lastBlock.Header.ChainID)
if err != nil {
panic(err)
}
lastCommit = types.NewCommit(vote.Height, vote.Round,
lastBlockMeta.BlockID, []types.CommitSig{vote.CommitSig()})
}
thisBlock := makeBlock(blockHeight, state, lastCommit)
thisParts := thisBlock.MakePartSet(types.BlockPartSizeBytes)
blockID := types.BlockID{Hash: thisBlock.Hash(), PartsHeader: thisParts.Header()}
state, err = blockExec.ApplyBlock(state, blockID, thisBlock)
if err != nil {
panic(errors.Wrap(err, "error apply block"))
}
blockStore.SaveBlock(thisBlock, thisParts, lastCommit)
}
return blockStore, state, blockExec
}

View File

@@ -10,7 +10,7 @@ import (
type handleFunc = func(event Event) (Event, error)
// Routines are a structure which model a finite state machine as serialized
// Routine is a structure that models a finite state machine as serialized
// stream of events processed by a handle function. This Routine structure
// handles the concurrency and messaging guarantees. Events are sent via
// `send` are handled by the `handle` function to produce an iterator
@@ -80,7 +80,7 @@ func (rt *Routine) start() {
return
}
rt.metrics.EventsOut.With("routine", rt.name).Add(1)
rt.logger.Debug(fmt.Sprintf("%s produced %T %+v\n", rt.name, oEvent, oEvent))
rt.logger.Debug(fmt.Sprintf("%s: produced %T %+v\n", rt.name, oEvent, oEvent))
rt.out <- oEvent
}
@@ -98,6 +98,7 @@ func (rt *Routine) send(event Event) bool {
rt.logger.Info(fmt.Sprintf("%s: send failed, queue was full/stopped \n", rt.name))
return false
}
rt.metrics.EventsSent.With("routine", rt.name).Add(1)
return true
}

View File

@@ -11,52 +11,11 @@ import (
"github.com/tendermint/tendermint/types"
)
// Events
// XXX: The handle API would be much simpler if it return a single event, an
// Event, which embeds a terminationEvent if it wants to terminate the routine.
// Input events into the scheduler:
// ticker event for cleaning peers
type tryPrunePeer struct {
priorityHigh
time time.Time
}
// ticker event for scheduling block requests
type trySchedule struct {
priorityHigh
time time.Time
}
// blockResponse message received from a peer
type bcBlockResponse struct {
priorityNormal
time time.Time
peerID p2p.ID
height int64
size int64
block *types.Block
}
// statusResponse message received from a peer
type bcStatusResponse struct {
priorityNormal
time time.Time
peerID p2p.ID
height int64
}
// new peer is connected
type addNewPeer struct {
priorityNormal
peerID p2p.ID
}
// Output events issued by the scheduler:
// Events generated by the scheduler:
// all blocks have been processed
type scFinishedEv struct {
priorityNormal
reason string
}
// send a blockRequest message
@@ -80,6 +39,10 @@ type scPeerError struct {
reason error
}
func (e scPeerError) String() string {
return fmt.Sprintf("scPeerError - peerID %s, err %s", e.peerID, e.reason)
}
// scheduler removed a set of peers (timed out or slow peer)
type scPeersPruned struct {
priorityHigh
@@ -160,9 +123,10 @@ func (p scPeer) String() string {
func newScPeer(peerID p2p.ID) *scPeer {
return &scPeer{
peerID: peerID,
state: peerStateNew,
height: -1,
peerID: peerID,
state: peerStateNew,
height: -1,
lastTouched: time.Time{},
}
}
@@ -176,11 +140,17 @@ type scheduler struct {
// in Processed state.
height int64
// lastAdvance tracks the last time a block execution happened.
// syncTimeout is the maximum time the scheduler waits to advance in the fast sync process before finishing.
// This covers the cases where there are no peers or all peers have a lower height.
lastAdvance time.Time
syncTimeout time.Duration
// a map of peerID to scheduler specific peer struct `scPeer` used to keep
// track of peer specific state
peers map[p2p.ID]*scPeer
peerTimeout time.Duration
minRecvRate int64 // minimum receive rate from peer otherwise prune
peerTimeout time.Duration // maximum response time from a peer otherwise prune
minRecvRate int64 // minimum receive rate from peer otherwise prune
// the maximum number of blocks that should be New, Received or Pending at any point
// in time. This is used to enforce a limit on the blockStates map.
@@ -204,15 +174,20 @@ func (sc scheduler) String() string {
sc.initHeight, sc.blockStates, sc.peers, sc.pendingBlocks, sc.pendingTime, sc.receivedBlocks)
}
func newScheduler(initHeight int64) *scheduler {
func newScheduler(initHeight int64, startTime time.Time) *scheduler {
sc := scheduler{
initHeight: initHeight,
lastAdvance: startTime,
syncTimeout: 60 * time.Second,
height: initHeight + 1,
blockStates: make(map[int64]blockState),
peers: make(map[p2p.ID]*scPeer),
pendingBlocks: make(map[int64]p2p.ID),
pendingTime: make(map[int64]time.Time),
receivedBlocks: make(map[int64]p2p.ID),
targetPending: 10, // TODO - pass as param
peerTimeout: 15 * time.Second, // TODO - pass as param
minRecvRate: 0, //int64(7680), TODO - pass as param
}
return &sc
@@ -316,6 +291,7 @@ func (sc *scheduler) setPeerHeight(peerID p2p.ID, height int64) error {
}
if height < peer.height {
sc.removePeer(peerID)
return fmt.Errorf("cannot move peer height lower. from %d to %d", peer.height, height)
}
@@ -327,7 +303,7 @@ func (sc *scheduler) setPeerHeight(peerID p2p.ID, height int64) error {
}
func (sc *scheduler) getStateAtHeight(height int64) blockState {
if height <= sc.initHeight {
if height < sc.height {
return blockStateProcessed
} else if state, ok := sc.blockStates[height]; ok {
return state
@@ -349,41 +325,8 @@ func (sc *scheduler) getPeersAtHeightOrAbove(height int64) []p2p.ID {
return peers
}
func (sc *scheduler) peersInactiveSince(duration time.Duration, now time.Time) []p2p.ID {
peers := []p2p.ID{}
for _, peer := range sc.peers {
if peer.state != peerStateReady {
continue
}
if now.Sub(peer.lastTouched) > duration {
peers = append(peers, peer.peerID)
}
}
// Ensure the order is deterministic for testing
sort.Sort(PeerByID(peers))
return peers
}
// will return peers who's lastRate i slower than minSpeed denominated in bytes
func (sc *scheduler) peersSlowerThan(minSpeed int64) []p2p.ID {
peers := []p2p.ID{}
for peerID, peer := range sc.peers {
if peer.state != peerStateReady {
continue
}
if peer.lastRate < minSpeed {
peers = append(peers, peerID)
}
}
// Ensure the order is deterministic for testing
sort.Sort(PeerByID(peers))
return peers
}
func (sc *scheduler) prunablePeers(peerTimout time.Duration, minRecvRate int64, now time.Time) []p2p.ID {
prunable := []p2p.ID{}
prunable := make([]p2p.ID, 0)
for peerID, peer := range sc.peers {
if peer.state != peerStateReady {
continue
@@ -407,8 +350,8 @@ func (sc *scheduler) markReceived(peerID p2p.ID, height int64, size int64, now t
return fmt.Errorf("couldn't find peer %s", peerID)
}
if peer.state == peerStateRemoved {
return fmt.Errorf("cannot receive blocks from removed peer %s", peerID)
if peer.state != peerStateReady {
return fmt.Errorf("cannot receive blocks from not ready peer %s", peerID)
}
if state := sc.getStateAtHeight(height); state != blockStatePending || sc.pendingBlocks[height] != peerID {
@@ -454,14 +397,13 @@ func (sc *scheduler) markPending(peerID p2p.ID, height int64, time time.Time) er
sc.setStateAtHeight(height, blockStatePending)
sc.pendingBlocks[height] = peerID
// XXX: to make this more accurate we can introduce a message from
// the IO routine which indicates the time the request was put on the wire
sc.pendingTime[height] = time
return nil
}
func (sc *scheduler) markProcessed(height int64) error {
sc.lastAdvance = time.Now()
state := sc.getStateAtHeight(height)
if state != blockStateReceived {
return fmt.Errorf("cannot mark height %d received from block state %s", height, state)
@@ -476,6 +418,9 @@ func (sc *scheduler) markProcessed(height int64) error {
}
func (sc *scheduler) allBlocksProcessed() bool {
if len(sc.peers) == 0 {
return false
}
return sc.height >= sc.maxHeight()
}
@@ -486,7 +431,7 @@ func (sc *scheduler) maxHeight() int64 {
if peer.state != peerStateReady {
continue
}
if peer.height > max {
if max < peer.height {
max = peer.height
}
}
@@ -532,15 +477,15 @@ func (sc *scheduler) selectPeer(height int64) (p2p.ID, error) {
}
// find the set of peers with minimum number of pending requests.
minPending := math.MaxInt64
var minPending int64 = math.MaxInt64
for mp := range pendingFrom {
if mp < minPending {
minPending = mp
if int64(mp) < minPending {
minPending = int64(mp)
}
}
sort.Sort(PeerByID(pendingFrom[minPending]))
return pendingFrom[minPending][0], nil
sort.Sort(PeerByID(pendingFrom[int(minPending)]))
return pendingFrom[int(minPending)][0], nil
}
// PeerByID is a list of peers sorted by peerID.
@@ -570,12 +515,30 @@ func (sc *scheduler) handleBlockResponse(event bcBlockResponse) (Event, error) {
err = sc.markReceived(event.peerID, event.block.Height, event.size, event.time)
if err != nil {
_ = sc.removePeer(event.peerID)
return scPeerError{peerID: event.peerID, reason: err}, nil
}
return scBlockReceived{peerID: event.peerID, block: event.block}, nil
}
func (sc *scheduler) handleNoBlockResponse(event bcNoBlockResponse) (Event, error) {
if len(sc.peers) == 0 {
return noOp, nil
}
peer, ok := sc.peers[event.peerID]
if !ok || peer.state == peerStateRemoved {
return noOp, nil
}
// The peer may have been just removed due to errors, low speed or timeouts.
_ = sc.removePeer(event.peerID)
return scPeerError{peerID: event.peerID,
reason: fmt.Errorf("peer %v with height %d claims no block for %d",
event.peerID, peer.height, event.height)}, nil
}
func (sc *scheduler) handleBlockProcessed(event pcBlockProcessed) (Event, error) {
if event.height != sc.height {
panic(fmt.Sprintf("processed height %d but expected height %d", event.height, sc.height))
@@ -584,12 +547,12 @@ func (sc *scheduler) handleBlockProcessed(event pcBlockProcessed) (Event, error)
if err != nil {
// It is possible that a peer error or timeout is handled after the processor
// has processed the block but before the scheduler received this event,
// so when pcBlockProcessed event is received the block had been requested again
// so when pcBlockProcessed event is received the block had been requested again.
return scSchedulerFail{reason: err}, nil
}
if sc.allBlocksProcessed() {
return scFinishedEv{}, nil
return scFinishedEv{reason: "processed all blocks"}, nil
}
return noOp, nil
@@ -608,13 +571,13 @@ func (sc *scheduler) handleBlockProcessError(event pcBlockVerificationFailure) (
}
if sc.allBlocksProcessed() {
return scFinishedEv{}, nil
return scFinishedEv{reason: "error on last block"}, nil
}
return noOp, nil
}
func (sc *scheduler) handleAddNewPeer(event addNewPeer) (Event, error) {
func (sc *scheduler) handleAddNewPeer(event bcAddNewPeer) (Event, error) {
err := sc.addPeer(event.peerID)
if err != nil {
return scSchedulerFail{reason: err}, nil
@@ -622,8 +585,7 @@ func (sc *scheduler) handleAddNewPeer(event addNewPeer) (Event, error) {
return noOp, nil
}
// XXX: unify types peerError
func (sc *scheduler) handlePeerError(event peerError) (Event, error) {
func (sc *scheduler) handleRemovePeer(event bcRemovePeer) (Event, error) {
err := sc.removePeer(event.peerID)
if err != nil {
// XXX - It is possible that the removePeer fails here for legitimate reasons
@@ -631,12 +593,23 @@ func (sc *scheduler) handlePeerError(event peerError) (Event, error) {
return scSchedulerFail{reason: err}, nil
}
if sc.allBlocksProcessed() {
return scFinishedEv{}, nil
return scFinishedEv{reason: "removed peer"}, nil
}
return noOp, nil
}
func (sc *scheduler) handleTryPrunePeer(event tryPrunePeer) (Event, error) {
func (sc *scheduler) handleTryPrunePeer(event rTryPrunePeer) (Event, error) {
// Check behavior of peer responsible to deliver block at sc.height.
timeHeightAsked, ok := sc.pendingTime[sc.height]
if ok && time.Since(timeHeightAsked) > sc.peerTimeout {
// A request was sent to a peer for block at sc.height but a response was not received
// from that peer within sc.peerTimeout. Remove the peer. This is to ensure that a peer
// will be timed out even if it sends blocks at higher heights but prevents progress by
// not sending the block at current height.
sc.removePeer(sc.pendingBlocks[sc.height])
}
prunablePeers := sc.prunablePeers(sc.peerTimeout, sc.minRecvRate, event.time)
if len(prunablePeers) == 0 {
return noOp, nil
@@ -649,17 +622,19 @@ func (sc *scheduler) handleTryPrunePeer(event tryPrunePeer) (Event, error) {
}
}
// If all blocks are processed we should finish even some peers were pruned.
// If all blocks are processed we should finish.
if sc.allBlocksProcessed() {
return scFinishedEv{}, nil
return scFinishedEv{reason: "after try prune"}, nil
}
return scPeersPruned{peers: prunablePeers}, nil
}
// TODO - Schedule multiple block requests
func (sc *scheduler) handleTrySchedule(event trySchedule) (Event, error) {
func (sc *scheduler) handleTrySchedule(event rTrySchedule) (Event, error) {
if time.Since(sc.lastAdvance) > sc.syncTimeout {
return scFinishedEv{reason: "timeout, no advance"}, nil
}
nextHeight := sc.nextHeightToSchedule()
if nextHeight == -1 {
@@ -693,17 +668,20 @@ func (sc *scheduler) handle(event Event) (Event, error) {
case bcBlockResponse:
nextEvent, err := sc.handleBlockResponse(event)
return nextEvent, err
case trySchedule:
case bcNoBlockResponse:
nextEvent, err := sc.handleNoBlockResponse(event)
return nextEvent, err
case rTrySchedule:
nextEvent, err := sc.handleTrySchedule(event)
return nextEvent, err
case addNewPeer:
case bcAddNewPeer:
nextEvent, err := sc.handleAddNewPeer(event)
return nextEvent, err
case tryPrunePeer:
nextEvent, err := sc.handleTryPrunePeer(event)
case bcRemovePeer:
nextEvent, err := sc.handleRemovePeer(event)
return nextEvent, err
case peerError:
nextEvent, err := sc.handlePeerError(event)
case rTryPrunePeer:
nextEvent, err := sc.handleTryPrunePeer(event)
return nextEvent, err
case pcBlockProcessed:
nextEvent, err := sc.handleBlockProcessed(event)
@@ -714,5 +692,4 @@ func (sc *scheduler) handle(event Event) (Event, error) {
default:
return scSchedulerFail{reason: fmt.Errorf("unknown event %v", event)}, nil
}
//return noOp, nil
}

View File

@@ -23,6 +23,8 @@ type scTestParams struct {
peerTimeout time.Duration
minRecvRate int64
targetPending int
startTime time.Time
syncTimeout time.Duration
}
func verifyScheduler(sc *scheduler) {
@@ -37,8 +39,9 @@ func verifyScheduler(sc *scheduler) {
func newTestScheduler(params scTestParams) *scheduler {
peers := make(map[p2p.ID]*scPeer)
var maxHeight int64
sc := newScheduler(params.initHeight)
sc := newScheduler(params.initHeight, params.startTime)
if params.height != 0 {
sc.height = params.height
}
@@ -46,6 +49,9 @@ func newTestScheduler(params scTestParams) *scheduler {
for id, peer := range params.peers {
peer.peerID = p2p.ID(id)
peers[p2p.ID(id)] = peer
if maxHeight < peer.height {
maxHeight = peer.height
}
}
for _, h := range params.allB {
sc.blockStates[h] = blockStateNew
@@ -64,6 +70,12 @@ func newTestScheduler(params scTestParams) *scheduler {
sc.peers = peers
sc.peerTimeout = params.peerTimeout
if params.syncTimeout == 0 {
sc.syncTimeout = 10 * time.Second
} else {
sc.syncTimeout = params.syncTimeout
}
if params.targetPending == 0 {
sc.targetPending = 10
} else {
@@ -80,7 +92,7 @@ func newTestScheduler(params scTestParams) *scheduler {
func TestScInit(t *testing.T) {
var (
initHeight int64 = 5
sc = newScheduler(initHeight)
sc = newScheduler(initHeight, time.Now())
)
assert.Equal(t, blockStateProcessed, sc.getStateAtHeight(initHeight))
assert.Equal(t, blockStateUnknown, sc.getStateAtHeight(initHeight+1))
@@ -181,21 +193,21 @@ func TestScAddPeer(t *testing.T) {
name: "add first peer",
fields: scTestParams{},
args: args{peerID: "P1"},
wantFields: scTestParams{peers: map[string]*scPeer{"P1": {state: peerStateNew, height: -1}}},
wantFields: scTestParams{peers: map[string]*scPeer{"P1": {height: -1, state: peerStateNew}}},
},
{
name: "add second peer",
fields: scTestParams{peers: map[string]*scPeer{"P1": {state: peerStateNew, height: -1}}},
fields: scTestParams{peers: map[string]*scPeer{"P1": {height: -1, state: peerStateNew}}},
args: args{peerID: "P2"},
wantFields: scTestParams{peers: map[string]*scPeer{
"P1": {state: peerStateNew, height: -1},
"P2": {state: peerStateNew, height: -1}}},
"P1": {height: -1, state: peerStateNew},
"P2": {height: -1, state: peerStateNew}}},
},
{
name: "attempt to add duplicate peer",
fields: scTestParams{peers: map[string]*scPeer{"P1": {state: peerStateNew, height: -1}}},
fields: scTestParams{peers: map[string]*scPeer{"P1": {height: -1}}},
args: args{peerID: "P1"},
wantFields: scTestParams{peers: map[string]*scPeer{"P1": {state: peerStateNew, height: -1}}},
wantFields: scTestParams{peers: map[string]*scPeer{"P1": {height: -1}}},
wantErr: true,
},
{
@@ -271,8 +283,8 @@ func TestScTouchPeer(t *testing.T) {
name: "touch peer in state Ready",
fields: scTestParams{peers: map[string]*scPeer{"P1": {state: peerStateReady, lastTouched: now}}},
args: args{peerID: "P1", time: now.Add(3 * time.Second)},
wantFields: scTestParams{peers: map[string]*scPeer{"P1": {state: peerStateReady,
lastTouched: now.Add(3 * time.Second)}}},
wantFields: scTestParams{peers: map[string]*scPeer{
"P1": {state: peerStateReady, lastTouched: now.Add(3 * time.Second)}}},
},
}
@@ -289,195 +301,6 @@ func TestScTouchPeer(t *testing.T) {
}
}
func TestScPeersInactiveSince(t *testing.T) {
now := time.Now()
type args struct {
threshold time.Duration
time time.Time
}
tests := []struct {
name string
fields scTestParams
args args
wantResult []p2p.ID
}{
{
name: "no peers",
fields: scTestParams{peers: map[string]*scPeer{}},
args: args{threshold: time.Second, time: now},
wantResult: []p2p.ID{},
},
{
name: "one active peer",
fields: scTestParams{peers: map[string]*scPeer{"P1": {state: peerStateReady, lastTouched: now}}},
args: args{threshold: time.Second, time: now},
wantResult: []p2p.ID{},
},
{
name: "one inactive peer",
fields: scTestParams{peers: map[string]*scPeer{"P1": {state: peerStateReady, lastTouched: now}}},
args: args{threshold: time.Second, time: now.Add(time.Second + time.Millisecond)},
wantResult: []p2p.ID{"P1"},
},
{
name: "one active and one inactive peer",
fields: scTestParams{peers: map[string]*scPeer{
"P1": {state: peerStateReady, lastTouched: now},
"P2": {state: peerStateReady, lastTouched: now.Add(time.Second)}}},
args: args{threshold: time.Second, time: now.Add(time.Second + time.Millisecond)},
wantResult: []p2p.ID{"P1"},
},
{
name: "one New peer",
fields: scTestParams{peers: map[string]*scPeer{"P1": {state: peerStateNew}}},
args: args{threshold: time.Second, time: now.Add(time.Millisecond)},
wantResult: []p2p.ID{},
},
{
name: "one Removed peer",
fields: scTestParams{peers: map[string]*scPeer{"P1": {state: peerStateRemoved, lastTouched: now}}},
args: args{threshold: time.Second, time: now.Add(time.Millisecond)},
wantResult: []p2p.ID{},
},
{
name: "one Ready active peer and one New",
fields: scTestParams{peers: map[string]*scPeer{
"P1": {state: peerStateRemoved, lastTouched: now},
"P2": {state: peerStateReady, lastTouched: now.Add(time.Millisecond)}}},
args: args{threshold: time.Second, time: now.Add(2 * time.Millisecond)},
wantResult: []p2p.ID{},
},
{
name: "one Ready inactive peer and one New",
fields: scTestParams{peers: map[string]*scPeer{
"P1": {state: peerStateRemoved, lastTouched: now},
"P2": {state: peerStateReady, lastTouched: now.Add(time.Millisecond)}}},
args: args{threshold: time.Second, time: now.Add(time.Second + 2*time.Millisecond)},
wantResult: []p2p.ID{"P2"},
},
{
name: "combination of New, Removed and, active and non active Ready peers",
fields: scTestParams{peers: map[string]*scPeer{
"P1": {state: peerStateNew},
"P2": {state: peerStateRemoved, lastTouched: now},
"P3": {state: peerStateRemoved, lastTouched: now.Add(time.Second)},
"P4": {state: peerStateReady, lastTouched: now.Add(time.Millisecond)},
"P5": {state: peerStateReady, lastTouched: now.Add(3 * time.Millisecond)}}},
args: args{threshold: time.Second, time: now.Add(time.Second + 2*time.Millisecond)},
wantResult: []p2p.ID{"P4"},
},
}
for _, tt := range tests {
tt := tt
t.Run(tt.name, func(t *testing.T) {
sc := newTestScheduler(tt.fields)
// peersInactiveSince should not mutate the scheduler
wantSc := sc
res := sc.peersInactiveSince(tt.args.threshold, tt.args.time)
sort.Sort(PeerByID(res))
assert.Equal(t, tt.wantResult, res)
assert.Equal(t, wantSc, sc)
})
}
}
func TestScPeersSlowerThan(t *testing.T) {
type args struct {
minSpeed int64
}
tests := []struct {
name string
fields scTestParams
args args
wantResult []p2p.ID
}{
{
name: "no peers",
fields: scTestParams{peers: map[string]*scPeer{}},
args: args{minSpeed: 100},
wantResult: []p2p.ID{},
},
{
name: "one Ready faster peer",
fields: scTestParams{peers: map[string]*scPeer{"P1": {state: peerStateReady, lastRate: 101}}},
args: args{minSpeed: 100},
wantResult: []p2p.ID{},
},
{
name: "one Ready equal peer",
fields: scTestParams{peers: map[string]*scPeer{"P1": {state: peerStateReady, lastRate: 100}}},
args: args{minSpeed: 100},
wantResult: []p2p.ID{},
},
{
name: "one Ready slow peer",
fields: scTestParams{peers: map[string]*scPeer{"P1": {state: peerStateReady, lastRate: 99}}},
args: args{minSpeed: 100},
wantResult: []p2p.ID{"P1"},
},
{
name: "one Removed faster peer",
fields: scTestParams{peers: map[string]*scPeer{"P1": {state: peerStateRemoved, lastRate: 101}}},
args: args{minSpeed: 100},
wantResult: []p2p.ID{},
}, {
name: "one Removed equal peer",
fields: scTestParams{peers: map[string]*scPeer{"P1": {state: peerStateRemoved, lastRate: 100}}},
args: args{minSpeed: 100},
wantResult: []p2p.ID{},
},
{
name: "one Removed slow peer",
fields: scTestParams{peers: map[string]*scPeer{"P1": {state: peerStateRemoved, lastRate: 99}}},
args: args{minSpeed: 100},
wantResult: []p2p.ID{},
},
{
name: "one New peer",
fields: scTestParams{peers: map[string]*scPeer{"P1": {state: peerStateNew}}},
args: args{minSpeed: 100},
wantResult: []p2p.ID{},
},
{
name: "one New peer",
fields: scTestParams{peers: map[string]*scPeer{"P1": {state: peerStateNew}}},
args: args{minSpeed: 100},
wantResult: []p2p.ID{},
},
{
name: "mixed peers",
fields: scTestParams{peers: map[string]*scPeer{
"P1": {state: peerStateRemoved, lastRate: 101},
"P2": {state: peerStateReady, lastRate: 101},
"P3": {state: peerStateRemoved, lastRate: 100},
"P4": {state: peerStateReady, lastRate: 100},
"P5": {state: peerStateReady, lastRate: 99},
"P6": {state: peerStateNew},
"P7": {state: peerStateRemoved, lastRate: 99},
"P8": {state: peerStateReady, lastRate: 99},
}},
args: args{minSpeed: 100},
wantResult: []p2p.ID{"P5", "P8"},
},
}
for _, tt := range tests {
tt := tt
t.Run(tt.name, func(t *testing.T) {
sc := newTestScheduler(tt.fields)
// peersSlowerThan should not mutate the scheduler
wantSc := sc
res := sc.peersSlowerThan(tt.args.minSpeed)
assert.Equal(t, tt.wantResult, res)
assert.Equal(t, wantSc, sc)
})
}
}
func TestScPrunablePeers(t *testing.T) {
now := time.Now()
@@ -716,8 +539,8 @@ func TestScSetPeerHeight(t *testing.T) {
allB: []int64{1, 2, 3, 4}},
args: args{peerID: "P1", height: 2},
wantFields: scTestParams{
peers: map[string]*scPeer{"P1": {height: 4, state: peerStateReady}},
allB: []int64{1, 2, 3, 4}},
peers: map[string]*scPeer{"P1": {height: 4, state: peerStateRemoved}},
allB: []int64{}},
wantErr: true,
},
{
@@ -845,7 +668,7 @@ func TestScGetPeersAtHeight(t *testing.T) {
tt := tt
t.Run(tt.name, func(t *testing.T) {
sc := newTestScheduler(tt.fields)
// getPeersAtHeightOrAbove should not mutate the scheduler
// getPeersAtHeight should not mutate the scheduler
wantSc := sc
res := sc.getPeersAtHeightOrAbove(tt.args.height)
sort.Sort(PeerByID(res))
@@ -1082,8 +905,11 @@ func TestScMarkReceived(t *testing.T) {
tt := tt
t.Run(tt.name, func(t *testing.T) {
sc := newTestScheduler(tt.fields)
if err := sc.markReceived(tt.args.peerID,
tt.args.height, tt.args.size, now.Add(time.Second)); (err != nil) != tt.wantErr {
if err := sc.markReceived(
tt.args.peerID,
tt.args.height,
tt.args.size,
now.Add(time.Second)); (err != nil) != tt.wantErr {
t.Errorf("markReceived() wantErr %v, error = %v", tt.wantErr, err)
}
wantSc := newTestScheduler(tt.wantFields)
@@ -1145,11 +971,17 @@ func TestScMarkProcessed(t *testing.T) {
tt := tt
t.Run(tt.name, func(t *testing.T) {
sc := newTestScheduler(tt.fields)
oldBlockState := sc.getStateAtHeight(tt.args.height)
if err := sc.markProcessed(tt.args.height); (err != nil) != tt.wantErr {
t.Errorf("markProcessed() wantErr %v, error = %v", tt.wantErr, err)
}
if tt.wantErr {
assert.Equal(t, oldBlockState, sc.getStateAtHeight(tt.args.height))
} else {
assert.Equal(t, blockStateProcessed, sc.getStateAtHeight(tt.args.height))
}
wantSc := newTestScheduler(tt.wantFields)
assert.Equal(t, wantSc, sc)
checkSameScheduler(t, wantSc, sc)
})
}
}
@@ -1163,9 +995,9 @@ func TestScAllBlocksProcessed(t *testing.T) {
wantResult bool
}{
{
name: "no blocks",
name: "no blocks, no peers",
fields: scTestParams{},
wantResult: true,
wantResult: false,
},
{
name: "only New blocks",
@@ -1225,7 +1057,7 @@ func TestScAllBlocksProcessed(t *testing.T) {
wantSc := sc
res := sc.allBlocksProcessed()
assert.Equal(t, tt.wantResult, res)
assert.Equal(t, wantSc, sc)
checkSameScheduler(t, wantSc, sc)
})
}
}
@@ -1305,8 +1137,7 @@ func TestScNextHeightToSchedule(t *testing.T) {
resMin := sc.nextHeightToSchedule()
assert.Equal(t, tt.wantHeight, resMin)
assert.Equal(t, wantSc, sc)
checkSameScheduler(t, wantSc, sc)
})
}
}
@@ -1414,7 +1245,7 @@ func TestScSelectPeer(t *testing.T) {
res, err := sc.selectPeer(tt.args.height)
assert.Equal(t, tt.wantResult, res)
assert.Equal(t, tt.wantError, err != nil)
assert.Equal(t, wantSc, sc)
checkSameScheduler(t, wantSc, sc)
})
}
}
@@ -1424,6 +1255,20 @@ func makeScBlock(height int64) *types.Block {
return &types.Block{Header: types.Header{Height: height}}
}
// used in place of assert.Equal(t, want, actual) to avoid failures due to
// scheduler.lastAdvanced timestamp inequalities.
func checkSameScheduler(t *testing.T, want *scheduler, actual *scheduler) {
assert.Equal(t, want.initHeight, actual.initHeight)
assert.Equal(t, want.height, actual.height)
assert.Equal(t, want.peers, actual.peers)
assert.Equal(t, want.blockStates, actual.blockStates)
assert.Equal(t, want.pendingBlocks, actual.pendingBlocks)
assert.Equal(t, want.pendingTime, actual.pendingTime)
assert.Equal(t, want.blockStates, actual.blockStates)
assert.Equal(t, want.receivedBlocks, actual.receivedBlocks)
assert.Equal(t, want.blockStates, actual.blockStates)
}
// checkScResults checks scheduler handler test results
func checkScResults(t *testing.T, wantErr bool, err error, wantEvent Event, event Event) {
if (err != nil) != wantErr {
@@ -1439,8 +1284,6 @@ func checkScResults(t *testing.T, wantErr bool, err error, wantEvent Event, even
assert.Equal(t, wantEvent.block, event.(scBlockReceived).block)
case scSchedulerFail:
assert.Equal(t, wantEvent.reason != nil, event.(scSchedulerFail).reason != nil)
default:
assert.Equal(t, wantEvent, event)
}
}
@@ -1449,7 +1292,6 @@ func TestScHandleBlockResponse(t *testing.T) {
block6FromP1 := bcBlockResponse{
time: now.Add(time.Millisecond),
peerID: p2p.ID("P1"),
height: 6,
size: 100,
block: makeScBlock(6),
}
@@ -1530,6 +1372,82 @@ func TestScHandleBlockResponse(t *testing.T) {
}
}
func TestScHandleNoBlockResponse(t *testing.T) {
now := time.Now()
noBlock6FromP1 := bcNoBlockResponse{
time: now.Add(time.Millisecond),
peerID: p2p.ID("P1"),
height: 6,
}
tests := []struct {
name string
fields scTestParams
wantEvent Event
wantFields scTestParams
wantErr bool
}{
{
name: "empty scheduler",
fields: scTestParams{},
wantEvent: noOpEvent{},
wantFields: scTestParams{},
},
{
name: "noBlock from removed peer",
fields: scTestParams{peers: map[string]*scPeer{"P1": {height: 8, state: peerStateRemoved}}},
wantEvent: noOpEvent{},
wantFields: scTestParams{peers: map[string]*scPeer{"P1": {height: 8, state: peerStateRemoved}}},
},
{
name: "for block we haven't asked for",
fields: scTestParams{
peers: map[string]*scPeer{"P1": {height: 8, state: peerStateReady}},
allB: []int64{1, 2, 3, 4, 5, 6, 7, 8}},
wantEvent: scPeerError{peerID: "P1", reason: fmt.Errorf("some error")},
wantFields: scTestParams{peers: map[string]*scPeer{"P1": {height: 8, state: peerStateRemoved}}},
},
{
name: "noBlock from peer we don't have",
fields: scTestParams{
peers: map[string]*scPeer{"P2": {height: 8, state: peerStateReady}},
allB: []int64{1, 2, 3, 4, 5, 6, 7, 8},
pending: map[int64]p2p.ID{6: "P2"},
pendingTime: map[int64]time.Time{6: now},
},
wantEvent: noOpEvent{},
wantFields: scTestParams{
peers: map[string]*scPeer{"P2": {height: 8, state: peerStateReady}},
allB: []int64{1, 2, 3, 4, 5, 6, 7, 8},
pending: map[int64]p2p.ID{6: "P2"},
pendingTime: map[int64]time.Time{6: now},
},
},
{
name: "noBlock from existing peer",
fields: scTestParams{
peers: map[string]*scPeer{"P1": {height: 8, state: peerStateReady}},
allB: []int64{1, 2, 3, 4, 5, 6, 7, 8},
pending: map[int64]p2p.ID{6: "P1"},
pendingTime: map[int64]time.Time{6: now},
},
wantEvent: scPeerError{peerID: "P1", reason: fmt.Errorf("some error")},
wantFields: scTestParams{peers: map[string]*scPeer{"P1": {height: 8, state: peerStateRemoved}}},
},
}
for _, tt := range tests {
tt := tt
t.Run(tt.name, func(t *testing.T) {
sc := newTestScheduler(tt.fields)
event, err := sc.handleNoBlockResponse(noBlock6FromP1)
checkScResults(t, tt.wantErr, err, tt.wantEvent, event)
wantSc := newTestScheduler(tt.wantFields)
assert.Equal(t, wantSc, sc)
})
}
}
func TestScHandleBlockProcessed(t *testing.T) {
now := time.Now()
processed6FromP1 := pcBlockProcessed{
@@ -1702,11 +1620,11 @@ func TestScHandleBlockVerificationFailure(t *testing.T) {
}
func TestScHandleAddNewPeer(t *testing.T) {
addP1 := addNewPeer{
addP1 := bcAddNewPeer{
peerID: p2p.ID("P1"),
}
type args struct {
event addNewPeer
event bcAddNewPeer
}
tests := []struct {
@@ -1754,76 +1672,14 @@ func TestScHandleAddNewPeer(t *testing.T) {
}
}
func TestScHandlePeerError(t *testing.T) {
errP1 := peerError{
peerID: p2p.ID("P1"),
}
type args struct {
event peerError
}
tests := []struct {
name string
fields scTestParams
args args
wantEvent Event
wantErr bool
}{
{
name: "no peers",
fields: scTestParams{},
args: args{event: errP1},
wantEvent: scSchedulerFail{reason: fmt.Errorf("some error")},
},
{
name: "error finds no peer",
fields: scTestParams{
height: 6,
peers: map[string]*scPeer{"P2": {height: 8, state: peerStateReady}},
allB: []int64{6, 7, 8},
},
args: args{event: errP1},
wantEvent: scSchedulerFail{reason: fmt.Errorf("some error")},
},
{
name: "error finds peer, only peer is removed",
fields: scTestParams{
height: 6,
peers: map[string]*scPeer{"P1": {height: 8, state: peerStateReady}},
allB: []int64{6, 7, 8},
},
args: args{event: errP1},
wantEvent: scFinishedEv{},
},
{
name: "error finds peer, one of two peers are removed",
fields: scTestParams{
peers: map[string]*scPeer{"P1": {height: 8, state: peerStateReady}, "P2": {height: 8, state: peerStateReady}},
allB: []int64{1, 2, 3, 4, 5, 6, 7, 8},
},
args: args{event: errP1},
wantEvent: noOpEvent{},
},
}
for _, tt := range tests {
tt := tt
t.Run(tt.name, func(t *testing.T) {
sc := newTestScheduler(tt.fields)
event, err := sc.handlePeerError(tt.args.event)
checkScResults(t, tt.wantErr, err, tt.wantEvent, event)
})
}
}
func TestScHandleTryPrunePeer(t *testing.T) {
now := time.Now()
pruneEv := tryPrunePeer{
pruneEv := rTryPrunePeer{
time: now.Add(time.Second + time.Millisecond),
}
type args struct {
event tryPrunePeer
event rTryPrunePeer
}
tests := []struct {
@@ -1914,14 +1770,14 @@ func TestScHandleTryPrunePeer(t *testing.T) {
}
}
func TestHandleTrySchedule(t *testing.T) {
func TestScHandleTrySchedule(t *testing.T) {
now := time.Now()
tryEv := trySchedule{
tryEv := rTrySchedule{
time: now.Add(time.Second + time.Millisecond),
}
type args struct {
event trySchedule
event rTrySchedule
}
tests := []struct {
name string
@@ -1932,41 +1788,44 @@ func TestHandleTrySchedule(t *testing.T) {
}{
{
name: "no peers",
fields: scTestParams{peers: map[string]*scPeer{}},
fields: scTestParams{startTime: now, peers: map[string]*scPeer{}},
args: args{event: tryEv},
wantEvent: noOpEvent{},
},
{
name: "only new peers",
fields: scTestParams{peers: map[string]*scPeer{"P1": {height: -1, state: peerStateNew}}},
fields: scTestParams{startTime: now, peers: map[string]*scPeer{"P1": {height: -1, state: peerStateNew}}},
args: args{event: tryEv},
wantEvent: noOpEvent{},
},
{
name: "only Removed peers",
fields: scTestParams{peers: map[string]*scPeer{"P1": {height: 4, state: peerStateRemoved}}},
fields: scTestParams{startTime: now, peers: map[string]*scPeer{"P1": {height: 4, state: peerStateRemoved}}},
args: args{event: tryEv},
wantEvent: noOpEvent{},
},
{
name: "one Ready shorter peer",
fields: scTestParams{
height: 6,
peers: map[string]*scPeer{"P1": {height: 4, state: peerStateReady}}},
startTime: now,
height: 6,
peers: map[string]*scPeer{"P1": {height: 4, state: peerStateReady}}},
args: args{event: tryEv},
wantEvent: noOpEvent{},
},
{
name: "one Ready equal peer",
fields: scTestParams{
peers: map[string]*scPeer{"P1": {height: 4, state: peerStateReady}},
allB: []int64{1, 2, 3, 4}},
startTime: now,
peers: map[string]*scPeer{"P1": {height: 4, state: peerStateReady}},
allB: []int64{1, 2, 3, 4}},
args: args{event: tryEv},
wantEvent: scBlockRequest{peerID: "P1", height: 1},
},
{
name: "many Ready higher peers with different number of pending requests",
fields: scTestParams{
startTime: now,
peers: map[string]*scPeer{
"P1": {height: 4, state: peerStateReady},
"P2": {height: 5, state: peerStateReady}},
@@ -1983,6 +1842,7 @@ func TestHandleTrySchedule(t *testing.T) {
{
name: "many Ready higher peers with same number of pending requests",
fields: scTestParams{
startTime: now,
peers: map[string]*scPeer{
"P2": {height: 8, state: peerStateReady},
"P1": {height: 8, state: peerStateReady},
@@ -2084,6 +1944,8 @@ func TestScHandleStatusResponse(t *testing.T) {
}
func TestScHandle(t *testing.T) {
now := time.Now()
type unknownEv struct {
priorityNormal
}
@@ -2123,24 +1985,27 @@ func TestScHandle(t *testing.T) {
name: "single peer, sync 3 blocks",
steps: []scStep{
{ // add P1
currentSc: &scTestParams{peers: map[string]*scPeer{}, height: 1},
args: args{event: addNewPeer{peerID: "P1"}},
currentSc: &scTestParams{startTime: now, peers: map[string]*scPeer{}, height: 1},
args: args{event: bcAddNewPeer{peerID: "P1"}},
wantEvent: noOpEvent{},
wantSc: &scTestParams{peers: map[string]*scPeer{"P1": {height: -1, state: peerStateNew}}, height: 1},
wantSc: &scTestParams{startTime: now, peers: map[string]*scPeer{
"P1": {height: -1, state: peerStateNew}}, height: 1},
},
{ // set height of P1
args: args{event: bcStatusResponse{peerID: "P1", time: tick[0], height: 3}},
wantEvent: noOpEvent{},
wantSc: &scTestParams{
peers: map[string]*scPeer{"P1": {height: 3, state: peerStateReady}},
allB: []int64{1, 2, 3},
height: 1,
startTime: now,
peers: map[string]*scPeer{"P1": {height: 3, state: peerStateReady}},
allB: []int64{1, 2, 3},
height: 1,
},
},
{ // schedule block 1
args: args{event: trySchedule{time: tick[1]}},
args: args{event: rTrySchedule{time: tick[1]}},
wantEvent: scBlockRequest{peerID: "P1", height: 1},
wantSc: &scTestParams{
startTime: now,
peers: map[string]*scPeer{"P1": {height: 3, state: peerStateReady}},
allB: []int64{1, 2, 3},
pending: map[int64]p2p.ID{1: "P1"},
@@ -2149,9 +2014,10 @@ func TestScHandle(t *testing.T) {
},
},
{ // schedule block 2
args: args{event: trySchedule{time: tick[2]}},
args: args{event: rTrySchedule{time: tick[2]}},
wantEvent: scBlockRequest{peerID: "P1", height: 2},
wantSc: &scTestParams{
startTime: now,
peers: map[string]*scPeer{"P1": {height: 3, state: peerStateReady}},
allB: []int64{1, 2, 3},
pending: map[int64]p2p.ID{1: "P1", 2: "P1"},
@@ -2160,9 +2026,10 @@ func TestScHandle(t *testing.T) {
},
},
{ // schedule block 3
args: args{event: trySchedule{time: tick[3]}},
args: args{event: rTrySchedule{time: tick[3]}},
wantEvent: scBlockRequest{peerID: "P1", height: 3},
wantSc: &scTestParams{
startTime: now,
peers: map[string]*scPeer{"P1": {height: 3, state: peerStateReady}},
allB: []int64{1, 2, 3},
pending: map[int64]p2p.ID{1: "P1", 2: "P1", 3: "P1"},
@@ -2171,9 +2038,10 @@ func TestScHandle(t *testing.T) {
},
},
{ // block response 1
args: args{event: bcBlockResponse{peerID: "P1", height: 1, time: tick[4], size: 100, block: makeScBlock(1)}},
args: args{event: bcBlockResponse{peerID: "P1", time: tick[4], size: 100, block: makeScBlock(1)}},
wantEvent: scBlockReceived{peerID: "P1", block: makeScBlock(1)},
wantSc: &scTestParams{
startTime: now,
peers: map[string]*scPeer{"P1": {height: 3, state: peerStateReady, lastTouched: tick[4]}},
allB: []int64{1, 2, 3},
pending: map[int64]p2p.ID{2: "P1", 3: "P1"},
@@ -2183,9 +2051,10 @@ func TestScHandle(t *testing.T) {
},
},
{ // block response 2
args: args{event: bcBlockResponse{peerID: "P1", height: 2, time: tick[5], size: 100, block: makeScBlock(2)}},
args: args{event: bcBlockResponse{peerID: "P1", time: tick[5], size: 100, block: makeScBlock(2)}},
wantEvent: scBlockReceived{peerID: "P1", block: makeScBlock(2)},
wantSc: &scTestParams{
startTime: now,
peers: map[string]*scPeer{"P1": {height: 3, state: peerStateReady, lastTouched: tick[5]}},
allB: []int64{1, 2, 3},
pending: map[int64]p2p.ID{3: "P1"},
@@ -2195,33 +2064,36 @@ func TestScHandle(t *testing.T) {
},
},
{ // block response 3
args: args{event: bcBlockResponse{peerID: "P1", height: 3, time: tick[6], size: 100, block: makeScBlock(3)}},
args: args{event: bcBlockResponse{peerID: "P1", time: tick[6], size: 100, block: makeScBlock(3)}},
wantEvent: scBlockReceived{peerID: "P1", block: makeScBlock(3)},
wantSc: &scTestParams{
peers: map[string]*scPeer{"P1": {height: 3, state: peerStateReady, lastTouched: tick[6]}},
allB: []int64{1, 2, 3},
received: map[int64]p2p.ID{1: "P1", 2: "P1", 3: "P1"},
height: 1,
startTime: now,
peers: map[string]*scPeer{"P1": {height: 3, state: peerStateReady, lastTouched: tick[6]}},
allB: []int64{1, 2, 3},
received: map[int64]p2p.ID{1: "P1", 2: "P1", 3: "P1"},
height: 1,
},
},
{ // processed block 1
args: args{event: pcBlockProcessed{peerID: p2p.ID("P1"), height: 1}},
wantEvent: noOpEvent{},
wantSc: &scTestParams{
peers: map[string]*scPeer{"P1": {height: 3, state: peerStateReady, lastTouched: tick[6]}},
allB: []int64{2, 3},
received: map[int64]p2p.ID{2: "P1", 3: "P1"},
height: 2,
startTime: now,
peers: map[string]*scPeer{"P1": {height: 3, state: peerStateReady, lastTouched: tick[6]}},
allB: []int64{2, 3},
received: map[int64]p2p.ID{2: "P1", 3: "P1"},
height: 2,
},
},
{ // processed block 2
args: args{event: pcBlockProcessed{peerID: p2p.ID("P1"), height: 2}},
wantEvent: scFinishedEv{},
wantSc: &scTestParams{
peers: map[string]*scPeer{"P1": {height: 3, state: peerStateReady, lastTouched: tick[6]}},
allB: []int64{3},
received: map[int64]p2p.ID{3: "P1"},
height: 3,
startTime: now,
peers: map[string]*scPeer{"P1": {height: 3, state: peerStateReady, lastTouched: tick[6]}},
allB: []int64{3},
received: map[int64]p2p.ID{3: "P1"},
height: 3,
},
},
},
@@ -2231,6 +2103,7 @@ func TestScHandle(t *testing.T) {
steps: []scStep{
{ // failure processing block 1
currentSc: &scTestParams{
startTime: now,
peers: map[string]*scPeer{
"P1": {height: 4, state: peerStateReady, lastTouched: tick[6]},
"P2": {height: 3, state: peerStateReady, lastTouched: tick[6]}},
@@ -2241,6 +2114,7 @@ func TestScHandle(t *testing.T) {
args: args{event: pcBlockVerificationFailure{height: 1, firstPeerID: "P1", secondPeerID: "P1"}},
wantEvent: noOpEvent{},
wantSc: &scTestParams{
startTime: now,
peers: map[string]*scPeer{
"P1": {height: 4, state: peerStateRemoved, lastTouched: tick[6]},
"P2": {height: 3, state: peerStateReady, lastTouched: tick[6]}},
@@ -2249,19 +2123,6 @@ func TestScHandle(t *testing.T) {
height: 1,
},
},
/*
{ // processed block 2
args: args{event: pcBlockProcessed{peerID: p2p.ID("P1"), height: 2}},
wantEvent: scFinishedEv{},
wantSc: &scTestParams{
peers: map[string]*scPeer{"P1": {height: 3, state: peerStateReady, lastTouched: tick[6]}},
allB: []int64{3},
received: map[int64]p2p.ID{3: "P1"},
height: 3,
},
},
*/
},
},
}
@@ -2280,8 +2141,10 @@ func TestScHandle(t *testing.T) {
}
nextEvent, err := sc.handle(step.args.event)
assert.Equal(t, newTestScheduler(*step.wantSc), sc)
wantSc := newTestScheduler(*step.wantSc)
t.Logf("step %d(%v): %s", i, step.args.event, sc)
checkSameScheduler(t, wantSc, sc)
checkScResults(t, step.wantErr, err, step.wantEvent, nextEvent)

View File

@@ -4,6 +4,7 @@ import (
"github.com/Workiva/go-datastructures/queue"
)
// Event is the type that can be added to the priority queue.
type Event queue.Item
type priority interface {

View File

@@ -98,7 +98,7 @@ func (conR *Reactor) OnStop() {
// SwitchToConsensus switches from fast_sync mode to consensus mode.
// It resets the state, turns off fast_sync, and starts the consensus state-machine
func (conR *Reactor) SwitchToConsensus(state sm.State, blocksSynced int) {
func (conR *Reactor) SwitchToConsensus(state sm.State, blocksSynced uint64) {
conR.Logger.Info("SwitchToConsensus")
conR.conS.reconstructLastCommit(state)
// NOTE: The line below causes broadcastNewRoundStepRoutine() to

View File

@@ -1661,6 +1661,8 @@ func (cs *State) tryAddVote(vote *types.Vote, peerID p2p.ID) (bool, error) {
}
cs.evpool.AddEvidence(voteErr.DuplicateVoteEvidence)
return added, err
} else if err == types.ErrVoteNonDeterministicSignature {
cs.Logger.Debug("Vote has non-deterministic signature", "err", err)
} else {
// Either
// 1) bad peer OR

View File

@@ -19,12 +19,6 @@ module.exports = {
gutter: {
title: "Help & Support",
editLink: true,
chat: {
title: "Riot Chat",
text: "Chat with Tendermint developers on Riot Chat.",
url: "https://riot.im/app/#/room/#tendermint:matrix.org",
bg: "#222"
},
forum: {
title: "Tendermint Forum",
text: "Join the Tendermint forum to learn more",
@@ -38,6 +32,7 @@ module.exports = {
}
},
footer: {
questionsText: "Chat with Cosmos developers in [Discord](https://discordapp.com/channels/669268347736686612) or reach out on the [SDK Developer Forum](https://forum.cosmos.network/c/tendermint) to learn more.",
logo: "/logo-bw.svg",
textLink: {
text: "tendermint.com",
@@ -95,10 +90,6 @@ module.exports = {
{
title: "Forum",
url: "https://forum.cosmos.network/c/tendermint"
},
{
title: "Chat",
url: "https://riot.im/app/#/room/#tendermint:matrix.org"
}
]
},
@@ -138,11 +129,6 @@ module.exports = {
}
]
},
markdown: {
anchor: {
permalinkSymbol: ""
}
},
plugins: [
[
"@vuepress/google-analytics",

View File

@@ -4,6 +4,7 @@
* 18-06-2019: Initial draft
* 08-07-2019: Reviewed
* 29-11-2019: Implemented
* 14-02-2020: Updated with the implementation details
## Context
@@ -27,7 +28,15 @@ This ADR is meant to specify the missing components and control necessary to ach
Partition the responsibilities of the blockchain reactor into a set of components which communicate exclusively with events. Events will contain timestamps allowing each component to track time as internal state. The internal state will be mutated by a set of `handle*` which will produce event(s). The integration between components will happen in the reactor and reactor tests will then become integration tests between components. This design will be known as `v2`.
![v2 Blockchain Reactor Architecture
Diagram](https://github.com/tendermint/tendermint/blob/f9e556481654a24aeb689bdadaf5eab3ccd66829/docs/architecture/img/blockchain-reactor-v2.png)
Diagram](https://github.com/tendermint/tendermint/blob/584e67ac3fac220c5c3e0652e3582eca8231e814/docs/architecture/img/blockchain-reactor-v2.png)
### Fast Sync Related Communication Channels
The diagram below shows the fast sync routines and the types of channels and queues used to communicate with each other.
In addition the per reactor channels used by the sendRoutine to send messages over the Peer MConnection are shown.
![v2 Blockchain Channels and Queues
Diagram](https://github.com/tendermint/tendermint/blob/5cf570690f989646fb3b615b734da503f038891f/docs/architecture/img/blockchain-v2-channels.png)
### Reactor changes in detail

View File

@@ -27,7 +27,7 @@ We would like to suggest a double signing risk reduction method.
2. If there exists votes from the validator's consensus key, exit state machine program
- Configuration
- We would like to suggest by introducing `double_sign_check_height` parameter in `config.toml` and cli, how many blocks state machine looks back to check votes
- `double_sign_check_height = {{ .Consensus.DoubleSignCheckHeight }}` in `config.toml`
- <span v-pre>`double_sign_check_height = {{ .Consensus.DoubleSignCheckHeight }}`</span> in `config.toml`
- `tendermint node --double_sign_check_height` in cli
- State machine ignore checking procedure when `vote-check-height == 0`

View File

@@ -42,7 +42,7 @@ We would like to suggest a simple Tendermint mode abstraction. These modes will
- p2p/pex
- Configuration, cli command
- We would like to suggest by introducing `mode` parameter in `config.toml` and cli
- `mode = "{{ .BaseConfig.Mode }}"` in `config.toml`
- <span v-pre>`mode = "{{ .BaseConfig.Mode }}"`</span> in `config.toml`
- `tendermint node --mode validator` in cli
- fullnode | validator | seed (default: "fullnode")
- RPC modification

Binary file not shown.

Before

Width:  |  Height:  |  Size: 118 KiB

After

Width:  |  Height:  |  Size: 167 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 107 KiB

62
docs/package-lock.json generated
View File

@@ -1157,12 +1157,6 @@
"lodash.debounce": "^4.0.8"
}
},
"@vuepress/plugin-google-analytics": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/@vuepress/plugin-google-analytics/-/plugin-google-analytics-1.2.0.tgz",
"integrity": "sha512-0zol5D4Efb5GKel7ADO/s65MLtKSLnOEGkeWzuipkWomSQPzP7TJ3+/RcYBnGdyBFHd1BSpTUHGK0b/IGwM3UA==",
"dev": true
},
"@vuepress/plugin-last-updated": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/@vuepress/plugin-last-updated/-/plugin-last-updated-1.3.0.tgz",
@@ -2378,9 +2372,9 @@
}
},
"caniuse-lite": {
"version": "1.0.30001023",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001023.tgz",
"integrity": "sha512-C5TDMiYG11EOhVOA62W1p3UsJ2z4DsHtMBQtjzp3ZsUglcQn62WOUgW0y795c7A5uZ+GCEIvzkMatLIlAsbNTA=="
"version": "1.0.30001027",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001027.tgz",
"integrity": "sha512-7xvKeErvXZFtUItTHgNtLgS9RJpVnwBlWX8jSo/BO8VsF6deszemZSkJJJA1KOKrXuzZH4WALpAJdq5EyfgMLg=="
},
"caseless": {
"version": "0.12.0",
@@ -3571,9 +3565,9 @@
"integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0="
},
"electron-to-chromium": {
"version": "1.3.344",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.344.tgz",
"integrity": "sha512-tvbx2Wl8WBR+ym3u492D0L6/jH+8NoQXqe46+QhbWH3voVPauGuZYeb1QAXYoOAWuiP2dbSvlBx0kQ1F3hu/Mw=="
"version": "1.3.346",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.346.tgz",
"integrity": "sha512-Yy4jF5hJd57BWmGPt0KjaXc25AmWZeQK75kdr4zIzksWVtiT6DwaNtvTb9dt+LkQKwUpvBfCyyPsXXtbY/5GYw=="
},
"elliptic": {
"version": "6.5.2",
@@ -6086,9 +6080,9 @@
"integrity": "sha512-xLIjLQmtym3QpoY9llBgApknl7pxAcN3WDRc2d3rwpl+/YvDZHPmKscGs+L6E05xf2KrCXPBvosWt7MZukwSpQ=="
},
"markdown-it-attrs": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/markdown-it-attrs/-/markdown-it-attrs-3.0.1.tgz",
"integrity": "sha512-fcpdmxdEsctDVJEunPyrirVtU/6zcTMxPxAu4Ofz51PKAa8vRMpmGQXsmXx1HTdIdUPoDonm/RhS/+jTNywj/Q=="
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/markdown-it-attrs/-/markdown-it-attrs-3.0.2.tgz",
"integrity": "sha512-q45vdXU9TSWaHgFkWEFM97YHEoCmOyG9csLLdv3oVC6ARjT77u4wfng9rRtSOMb5UpxzT7zTX5GBbwm15H40dw=="
},
"markdown-it-chain": {
"version": "1.3.0",
@@ -6559,9 +6553,9 @@
}
},
"node-releases": {
"version": "1.1.47",
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.47.tgz",
"integrity": "sha512-k4xjVPx5FpwBUj0Gw7uvFOTF4Ep8Hok1I6qjwL3pLfwe7Y0REQSAqOwwv9TWBCUtMHxcXfY4PgRLRozcChvTcA==",
"version": "1.1.48",
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.48.tgz",
"integrity": "sha512-Hr8BbmUl1ujAST0K0snItzEA5zkJTQup8VNTKNfT6Zw8vTJkIiagUPNfxHmgDOyfFYNfKAul40sD0UEYTvwebw==",
"requires": {
"semver": "^6.3.0"
}
@@ -7630,11 +7624,11 @@
}
},
"postcss-safe-parser": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/postcss-safe-parser/-/postcss-safe-parser-4.0.1.tgz",
"integrity": "sha512-xZsFA3uX8MO3yAda03QrG3/Eg1LN3EPfjjf07vke/46HERLZyHrTsQ9E1r1w1W//fWEhtYNndo2hQplN2cVpCQ==",
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/postcss-safe-parser/-/postcss-safe-parser-4.0.2.tgz",
"integrity": "sha512-Uw6ekxSWNLCPesSv/cmqf2bY/77z11O7jZGPax3ycZMFU/oi2DMH9i89AdHc1tRwFg/arFoEwX0IS3LCUxJh1g==",
"requires": {
"postcss": "^7.0.0"
"postcss": "^7.0.26"
}
},
"postcss-selector-parser": {
@@ -8276,9 +8270,9 @@
"integrity": "sha1-79qpjqdFEyTQkrKyFjpqHXqaIUc="
},
"resolve": {
"version": "1.15.0",
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.15.0.tgz",
"integrity": "sha512-+hTmAldEGE80U2wJJDC1lebb5jWqvTYAfm3YZ1ckk1gBr0MnCqUKlwK1e+anaFljIl+F5tR5IoZcm4ZDA1zMQw==",
"version": "1.15.1",
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.15.1.tgz",
"integrity": "sha512-84oo6ZTtoTUpjgNEr5SJyzQhzL72gaRodsSfyxC/AXRvwu0Yse9H8eF9IpGo7b8YetZhlI6v7ZQ6bKBFV/6S7w==",
"requires": {
"path-parse": "^1.0.6"
}
@@ -10096,9 +10090,9 @@
}
},
"vuepress-theme-cosmos": {
"version": "1.0.148",
"resolved": "https://registry.npmjs.org/vuepress-theme-cosmos/-/vuepress-theme-cosmos-1.0.148.tgz",
"integrity": "sha512-IVzX339e9k25YNC62UZPfkLg50IGCctvlI/TQF6s63eCdh+ayHOPLTFq5zAiYTpP09Dbz4tSC74cQyO3fUyPOQ==",
"version": "1.0.150",
"resolved": "https://registry.npmjs.org/vuepress-theme-cosmos/-/vuepress-theme-cosmos-1.0.150.tgz",
"integrity": "sha512-f4McVndkB+CqJ6mWpOG4UZSR14LJyXqwcgwoDoDUx149g2PKU3qI/AF5AcrM25+4UKMCXFKcJloQCl/aWq+1ig==",
"requires": {
"@cosmos-ui/vue": "^0.5.20",
"axios": "^0.19.0",
@@ -10181,9 +10175,9 @@
}
},
"webpack-chain": {
"version": "6.3.1",
"resolved": "https://registry.npmjs.org/webpack-chain/-/webpack-chain-6.3.1.tgz",
"integrity": "sha512-PLWPY92p5Vj0hOylUGjRoRY7Kgrns5vmPFAQ9BpSHnBbVbh2akRQVUlbRb2mmGYRSY1FklTULtyVChNmcQjIVg==",
"version": "6.4.0",
"resolved": "https://registry.npmjs.org/webpack-chain/-/webpack-chain-6.4.0.tgz",
"integrity": "sha512-f97PYqxU+9/u0IUqp/ekAHRhBD1IQwhBv3wlJo2nvyELpr2vNnUqO3XQEk+qneg0uWGP54iciotszpjfnEExFA==",
"requires": {
"deepmerge": "^1.5.2",
"javascript-stringify": "^2.0.1"
@@ -10209,9 +10203,9 @@
}
},
"webpack-dev-server": {
"version": "3.10.2",
"resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-3.10.2.tgz",
"integrity": "sha512-pxZKPYb+n77UN8u9YxXT4IaIrGcNtijh/mi8TXbErHmczw0DtPnMTTjHj+eNjkqLOaAZM/qD7V59j/qJsEiaZA==",
"version": "3.10.3",
"resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-3.10.3.tgz",
"integrity": "sha512-e4nWev8YzEVNdOMcNzNeCN947sWJNd43E5XvsJzbAL08kGc2frm1tQ32hTJslRS+H65LCb/AaUCYU7fjHCpDeQ==",
"requires": {
"ansi-html": "0.0.7",
"bonjour": "^3.5.0",

View File

@@ -4,19 +4,16 @@
"description": "Welcome to the Tendermint Core documentation!",
"main": "index.js",
"dependencies": {
"vuepress-theme-cosmos": "^1.0.148"
},
"devDependencies": {
"@vuepress/plugin-google-analytics": "^1.2.0"
"vuepress-theme-cosmos": "^1.0.150"
},
"scripts": {
"preserve": "./pre.sh",
"serve": "trap 'exit 0' SIGINT; vuepress dev",
"serve": "trap 'exit 0' SIGINT; vuepress dev --no-cache",
"postserve": "./post.sh",
"prebuild": "./pre.sh",
"build": "trap 'exit 0' SIGINT; vuepress build",
"build": "trap 'exit 0' SIGINT; vuepress build --no-cache",
"postbuild": "./post.sh"
},
"author": "",
"license": "ISC"
}
}

15
go.mod
View File

@@ -1,33 +1,30 @@
module github.com/tendermint/tendermint
go 1.12
go 1.13
require (
github.com/ChainSafe/go-schnorrkel v0.0.0-20200102211924-4bcbc698314f
github.com/VividCortex/gohistogram v1.0.0 // indirect
github.com/Workiva/go-datastructures v1.0.50
github.com/btcsuite/btcd v0.0.0-20190115013929-ed77733ec07d
github.com/btcsuite/btcutil v0.0.0-20180706230648-ab6388e0c60a
github.com/fortytw2/leaktest v1.3.0
github.com/go-kit/kit v0.9.0
github.com/go-kit/kit v0.10.0
github.com/go-logfmt/logfmt v0.5.0
github.com/gogo/protobuf v1.3.1
github.com/golang/protobuf v1.3.3
github.com/google/gofuzz v1.0.0 // indirect
github.com/gorilla/websocket v1.4.1
github.com/gtank/merlin v0.1.1-0.20191105220539-8318aed1a79f
github.com/inconshreveable/mousetrap v1.0.0 // indirect
github.com/libp2p/go-buffer-pool v0.0.2
github.com/magiconair/properties v1.8.1
github.com/minio/highwayhash v1.0.0
github.com/pkg/errors v0.9.1
github.com/prometheus/client_golang v0.9.3
github.com/rcrowley/go-metrics v0.0.0-20180503174638-e2704e165165
github.com/prometheus/client_golang v1.4.1
github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a
github.com/rs/cors v1.7.0
github.com/snikch/goodman v0.0.0-20171125024755-10e37e294daa
github.com/spf13/cobra v0.0.1
github.com/spf13/cobra v0.0.3
github.com/spf13/viper v1.6.2
github.com/stretchr/testify v1.4.0
github.com/stretchr/testify v1.5.0
github.com/tendermint/go-amino v0.14.1
github.com/tendermint/tm-db v0.4.0
golang.org/x/crypto v0.0.0-20191206172530-e9b2fee46413

262
go.sum
View File

@@ -1,21 +1,40 @@
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/ChainSafe/go-schnorrkel v0.0.0-20200102211924-4bcbc698314f h1:4O1om+UVU+Hfcihr1timk8YNXHxzZWgCo7ofnrZRApw=
github.com/ChainSafe/go-schnorrkel v0.0.0-20200102211924-4bcbc698314f/go.mod h1:URdX5+vg25ts3aCh8H5IFZybJYKWhJHYMTnf+ULtoC4=
github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0=
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo=
github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI=
github.com/VividCortex/gohistogram v1.0.0 h1:6+hBz+qvs0JOrrNhhmR7lFxo5sINxBCGXrdtl/UvroE=
github.com/VividCortex/gohistogram v1.0.0/go.mod h1:Pf5mBqqDxYaXu3hDrrU+w6nw50o/4+TcAqDqk/vUH7g=
github.com/Workiva/go-datastructures v1.0.50 h1:slDmfW6KCHcC7U+LP3DDBbm4fqTwZGn1beOFPfGaLvo=
github.com/Workiva/go-datastructures v1.0.50/go.mod h1:Z+F2Rca0qCsVYDS8z7bAGm8f3UkzuWYS/oBZz5a7VVA=
github.com/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBAUSII=
github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5/go.mod h1:SkGFH1ia65gfNATL8TAiHDNxPzPdmEL5uirI2Uyuz6c=
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ=
github.com/apache/thrift v0.13.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ=
github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
github.com/aryann/difflib v0.0.0-20170710044230-e206f873d14a/go.mod h1:DAHtR1m6lCRdSC2Tm3DSWRPvIPr6xNKyeHdqDQSQT+A=
github.com/aws/aws-lambda-go v1.13.3/go.mod h1:4UKl9IzQMoD+QF79YdCuzCwp8VbmG4VAQwij/eHl5CU=
github.com/aws/aws-sdk-go v1.27.0/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
github.com/aws/aws-sdk-go-v2 v0.18.0/go.mod h1:JWVYvqSMppoMJC0x5wdwiImzgXTI9FuZwxzkQq9wy+g=
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973 h1:xJ4a3vCFaGF/jqvzLMYoU8P317H5OQ+Via4RmuPwCS0=
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
github.com/beorn7/perks v1.0.0 h1:HWo1m869IqiPhD389kmkxeTalrjNbbJTC8LXupb+sl0=
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
github.com/btcsuite/btcd v0.0.0-20190115013929-ed77733ec07d h1:xG8Pj6Y6J760xwETNmMzmlt38QSwz0BLp1cZ09g27uw=
github.com/btcsuite/btcd v0.0.0-20190115013929-ed77733ec07d/go.mod h1:d3C0AkH6BRcvO8T0UEPu53cnw4IbV63x1bEjildYhO0=
github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f/go.mod h1:TdznJufoqS23FtqVCzL0ZqgP5MqXbb4fg/WgDys70nA=
@@ -26,22 +45,40 @@ github.com/btcsuite/goleveldb v0.0.0-20160330041536-7834afc9e8cd/go.mod h1:F+uVa
github.com/btcsuite/snappy-go v0.0.0-20151229074030-0bdef8d06723/go.mod h1:8woku9dyThutzjeg+3xrA5iCpBRH8XEEg3lh6TiUghc=
github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792/go.mod h1:ghJtEyQwv5/p4Mg4C0fgbePVuGr935/5ddU9Z3TmDRY=
github.com/btcsuite/winsvc v1.0.0/go.mod h1:jsenWakMcC0zFBFurPLEAyrnc/teJEM1O46fmI40EZs=
github.com/casbin/casbin/v2 v2.1.2/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n9yuLkIJQ=
github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko=
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY=
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/clbanning/x2j v0.0.0-20191024224557-825249438eec/go.mod h1:jMjuTZXRI4dUb/I5gc9Hdhagfvm9+RyrPryS/auMzxE=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8=
github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI=
github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
github.com/coreos/pkg v0.0.0-20160727233714-3ac0863d7acf/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
github.com/cosmos/go-bip39 v0.0.0-20180819234021-555e2067c45d h1:49RLWk1j44Xu4fjHb6JFYmeUnDORVwHNkDxaQ0ctCVU=
github.com/cosmos/go-bip39 v0.0.0-20180819234021-555e2067c45d/go.mod h1:tSxLoYXyBmiFeKpvmq4dzayMdCjCnu8uqmCysIGBT2Y=
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
github.com/davecgh/go-spew v0.0.0-20171005155431-ecdeabc65495/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs=
github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU=
github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I=
github.com/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M=
github.com/envoyproxy/go-control-plane v0.6.9/go.mod h1:SBwIajubJHhxtWwsL9s8ss4safvEdbitLhGGK48rN6g=
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/etcd-io/bbolt v1.3.3 h1:gSJmxrs37LgTqR/oyJBWok6k6SvXEUerFTbltIhXkBM=
@@ -52,8 +89,11 @@ github.com/facebookgo/stack v0.0.0-20160209184415-751773369052 h1:JWuenKqqX8nojt
github.com/facebookgo/stack v0.0.0-20160209184415-751773369052/go.mod h1:UbMTZqLaRiH3MsBH8va0n7s1pQYcu3uTb8G4tygF4Zg=
github.com/facebookgo/subset v0.0.0-20150612182917-8dac2c3c4870 h1:E2s37DuLxFhQDg5gKsWoLBOB0n+ZW8s599zru8FJ2/Y=
github.com/facebookgo/subset v0.0.0-20150612182917-8dac2c3c4870/go.mod h1:5tD+neXqOorC30/tWg0LCSkrqj/AR6gu8yY8/fpw1q0=
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8vw=
github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g=
github.com/franela/goblin v0.0.0-20200105215937-c9ffbefa60db/go.mod h1:7dvUGVsVBjqR7JHJk0brhHOZYGmfBYOrK0ZhYMEtBr4=
github.com/franela/goreq v0.0.0-20171204163338-bcd34c9993f8/go.mod h1:ZhphrRTfi2rbfLwlschooIH4+wKKDR4Pdxhh+TRoA20=
github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
@@ -61,22 +101,29 @@ github.com/go-kit/kit v0.8.0 h1:Wz+5lgoB0kkuqLEc6NVmwRknTKP6dTGbSqvhZtBI/j0=
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-kit/kit v0.9.0 h1:wDJmvq38kDhkVxi50ni9ykkdUr1PKgqKOoi01fa0Mdk=
github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-kit/kit v0.10.0 h1:dXFJfIHVvUcpSgDOV+Ne6t7jXri8Tfv2uOLHUZ2XNuo=
github.com/go-kit/kit v0.10.0/go.mod h1:xUsJbQ/Fp4kEt7AFgCuvyX4a71u8h9jB8tj/ORgOZ7o=
github.com/go-logfmt/logfmt v0.3.0 h1:8HUsc87TaSWLKwrnumgC8/YconD2fJQsRJAsWaPg2ic=
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
github.com/go-logfmt/logfmt v0.4.0 h1:MP4Eh7ZCb31lleYCFuwm0oe4/YGak+5l1vA2NOE80nA=
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
github.com/go-logfmt/logfmt v0.5.0 h1:TrB8swr/68K7m9CcGut2g3UOihhbcbiMAYiuTXdEih4=
github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/gogo/googleapis v1.1.0/go.mod h1:gf4bu3Q80BeJ6H1S1vYPm8/ELATdvryBaNFGgqEef3s=
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/gogo/protobuf v1.2.1 h1:/s5zKNz0uPFCZ5hddgPdo2TK2TVrUNMn0OOX8/aZMTE=
github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
github.com/gogo/protobuf v1.3.1 h1:DqDEcV5aeaTmdFBePNpYsp3FlcVH/2ISVVM9Qf8PSls=
github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
@@ -84,36 +131,78 @@ github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.3 h1:gyjaxf+svBWX08ZjK86iN9geUJF0H6gp2IRKX6Nf6/I=
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4=
github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/go-cmp v0.2.0 h1:+dTQ8DZQJz0Mb/HjFlkptS1FeQ4cWSnN941F8aEG4SQ=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.1 h1:Xye71clBPdm5HgqGwUkwhbynsUJZhDbS20FvLhQ2izg=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/gofuzz v1.0.0 h1:A8PeW59pxE9IoFRqBp37U+mSNaQoZ46F1f0f863XSXw=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
github.com/gorilla/websocket v1.4.1 h1:q7AeDBpnBk8AogcD4DSag/Ukw/KV+YhzLj2bP5HvKCM=
github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
github.com/gtank/merlin v0.1.1-0.20191105220539-8318aed1a79f h1:8N8XWLZelZNibkhM1FuF+3Ad3YIbgirjdMiVA0eUkaM=
github.com/gtank/merlin v0.1.1-0.20191105220539-8318aed1a79f/go.mod h1:T86dnYJhcGOh5BjZFCJWTDeTK7XW8uE+E21Cy/bIQ+s=
github.com/gtank/ristretto255 v0.1.2 h1:JEqUCPA1NvLq5DwYtuzigd7ss8fwbYay9fi4/5uMzcc=
github.com/gtank/ristretto255 v0.1.2/go.mod h1:Ph5OpO6c7xKUGROZfWVLiJf9icMDwUeIvY4OmlYW69o=
github.com/hashicorp/consul/api v1.3.0/go.mod h1:MmDNSzIMUjNpY/mQ398R4bk2FnqQLoPndWW5VkKPlCE=
github.com/hashicorp/consul/sdk v0.3.0/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8=
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM=
github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk=
github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU=
github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU=
github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4=
github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90=
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64=
github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ=
github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I=
github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc=
github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/hudl/fargo v1.3.0/go.mod h1:y3CKSmjA+wD2gak7sUSXTAoopbhU08POFhmITJgmKTg=
github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d/go.mod h1:qj24IKcXYK6Iy9ceXlo3Tc+vtHo9lIhSX5JddghvEPo=
github.com/jessevdk/go-flags v0.0.0-20141203071132-1679536dcc89/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
github.com/jmhodges/levigo v1.0.0 h1:q5EC36kV79HWeTBWsod3mG11EgStG3qArTKcvlksN1U=
github.com/jmhodges/levigo v1.0.0/go.mod h1:Q6Qx+uH3RAqyK4rFQroq9RL7mdkABMcfhEI+nNuzMJQ=
github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
github.com/jrick/logrotate v1.0.0/go.mod h1:LNinyqDIJnpAur+b8yyulnQw/wDuN1+BYKlTRt3OuAQ=
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/json-iterator/go v1.1.8/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
@@ -131,56 +220,129 @@ github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/libp2p/go-buffer-pool v0.0.2 h1:QNK2iAFa8gjAe1SPz6mHSMuCcjs+X1wlHzeOSqcmlfs=
github.com/libp2p/go-buffer-pool v0.0.2/go.mod h1:MvaB6xw5vOrDl8rYZGLFdKAuk/hRoRZd1Vi32+RXyFM=
github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20190605223551-bc2310a04743/go.mod h1:qklhhLq1aX+mtWk9cPHPzaBjWImj5ULL6C7HFJtXQMM=
github.com/lightstep/lightstep-tracer-go v0.18.1/go.mod h1:jlF1pusYV4pidLvZ+XD0UBX0ZE6WURAspgAczcDHrL4=
github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0QoUACkjt2znoq26NVQ=
github.com/magiconair/properties v1.8.1 h1:ZC2Vc7/ZFkGmsVC9KvOjumD+G5lXy2RtTKyzRKO2BQ4=
github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
github.com/mimoo/StrobeGo v0.0.0-20181016162300-f8f6d4d2b643 h1:hLDRPB66XQT/8+wG9WsDpiCvZf1yKO7sz7scAjSlBa0=
github.com/mimoo/StrobeGo v0.0.0-20181016162300-f8f6d4d2b643/go.mod h1:43+3pMjjKimDBf5Kr4ZFNGbLql1zKkbImw+fZbw3geM=
github.com/minio/highwayhash v1.0.0 h1:iMSDhgUILCr0TNm8LWlSjF8N0ZIj2qbO8WHp6Q/J2BA=
github.com/minio/highwayhash v1.0.0/go.mod h1:xQboMTeM9nY9v/LlAOxFctujiv5+Aq2hR5dxBpaMbdc=
github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc=
github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI=
github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg=
github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY=
github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE=
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/nats-io/jwt v0.3.0/go.mod h1:fRYCDE99xlTsqUzISS1Bi75UBJ6ljOJQOAAu5VglpSg=
github.com/nats-io/jwt v0.3.2/go.mod h1:/euKqTS1ZD+zzjYrY7pseZrTtWQSjujC7xjPc8wL6eU=
github.com/nats-io/nats-server/v2 v2.1.2/go.mod h1:Afk+wRZqkMQs/p45uXdrVLuab3gwv3Z8C4HTBu8GD/k=
github.com/nats-io/nats.go v1.9.1/go.mod h1:ZjDU1L/7fJ09jvUSRVBR2e7+RnLiiIQyqyzEE/Zbp4w=
github.com/nats-io/nkeys v0.1.0/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w=
github.com/nats-io/nkeys v0.1.3/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w=
github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c=
github.com/oklog/oklog v0.3.2/go.mod h1:FCV+B7mhrz4o+ueLpx+KqkyXRGMWOYEvfiXtdGtbWGs=
github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA=
github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo=
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.7.0 h1:WSHQ+IS43OoUrWtD1/bbclrwK8TTH5hzp+umCiuxHgs=
github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/gomega v1.4.3 h1:RE1xgDvH7imwFD45h+u2SgIfERHlS2yNG4DObb5BSKU=
github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk=
github.com/opentracing-contrib/go-observer v0.0.0-20170622124052-a52f23424492/go.mod h1:Ngi6UdF0k5OKD5t5wlmGhe/EDKPoUM3BXZSSfIuJbis=
github.com/opentracing/basictracer-go v1.0.0/go.mod h1:QfBfYuafItcjQuMwinw9GhYKwFXS9KnPs5lxoYwgW74=
github.com/opentracing/opentracing-go v1.0.2/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
github.com/openzipkin-contrib/zipkin-go-opentracing v0.4.5/go.mod h1:/wsWhb9smxSfWAKL3wpBW7V8scJMt8N8gnaMCS9E/cA=
github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJc5AZX7/PBEpw=
github.com/openzipkin/zipkin-go v0.2.1/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4=
github.com/openzipkin/zipkin-go v0.2.2/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4=
github.com/pact-foundation/pact-go v1.0.4/go.mod h1:uExwJY4kCzNPcHRj+hCR/HBbOOIwwtUjcrb0b5/5kLM=
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k=
github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc=
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
github.com/performancecopilot/speed v3.0.0+incompatible/go.mod h1:/CLtqpZ5gBg1M9iaPbIdPPGyKcA8hKdoy6hAWba7Yac=
github.com/pierrec/lz4 v1.0.2-0.20190131084431-473cd7ce01a1/go.mod h1:3/3N9NVKO0jef7pBehbT1qWhCMrIgbYNnFAZCqQ5LRc=
github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/profile v1.2.1/go.mod h1:hJw3o1OdXxsrSjjVksARp5W95eeEaEfptyVZyv6JUPA=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=
github.com/prometheus/client_golang v0.9.1 h1:K47Rk0v/fkEfwfQet2KWhscE0cJzjgCCDBG2KHZoVno=
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
github.com/prometheus/client_golang v0.9.3-0.20190127221311-3c4408c8b829/go.mod h1:p2iRAGwDERtqlqzRXnrOVns+ignqQo//hLXqYxZYVNs=
github.com/prometheus/client_golang v0.9.3 h1:9iH4JKXLzFbOAdtqv/a+j8aewx2Y8lAjAydhbaScPF8=
github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso=
github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
github.com/prometheus/client_golang v1.3.0 h1:miYCvYqFXtl/J9FIy8eNpBfYthAEFg+Ys0XyUVEcDsc=
github.com/prometheus/client_golang v1.3.0/go.mod h1:hJaj2vgQTGQmVCsAACORcieXFeDPbaTKGT+JTgUa3og=
github.com/prometheus/client_golang v1.4.1 h1:FFSuS004yOQEtDdTq+TAOLP5xUq63KqAFYyOi8zA+Y8=
github.com/prometheus/client_golang v1.4.1/go.mod h1:e9GMxYsXl05ICDXkRhurwBS4Q3OK1iX/F2sw+iXX5zU=
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910 h1:idejC8f05m9MGOsuEi1ATq9shN03HrxNkD/luQvxCv8=
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
github.com/prometheus/client_model v0.0.0-20190115171406-56726106282f/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90 h1:S/YWwWx/RA8rT8tKFRuGUZhuA90OyIBpPCXkcbwU8DE=
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4 h1:gQz4mCbXsO+nc9n1hCxHcGA3Zx3Eo+UHZoInFGUIXNM=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/client_model v0.1.0 h1:ElTg5tNp4DqfV7UQjDqv2+RJlNzsDtvNAWccbItceIE=
github.com/prometheus/client_model v0.1.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/client_model v0.2.0 h1:uq5h0d+GuxiXLJLNABMgp2qUWDPiLvgCzz2dUR+/W/M=
github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
github.com/prometheus/common v0.2.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
github.com/prometheus/common v0.4.0 h1:7etb9YClo3a6HjLzfl6rIQaU+FDfi0VSX39io3aQ+DM=
github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
github.com/prometheus/common v0.7.0 h1:L+1lyG48J1zAQXA3RBX/nG/B3gjlHq0zTt2tlbJLyCY=
github.com/prometheus/common v0.7.0/go.mod h1:DjGbpBbp5NYNiECxcL/VnbXCCaQpKd3tt26CguLLsqA=
github.com/prometheus/common v0.9.1 h1:KOMtN28tlbam3/7ZKEYKHhKoJZYYj3gMH4uc62x7X7U=
github.com/prometheus/common v0.9.1/go.mod h1:yhUN8i9wzaXS3w1O07YhxHEBxD+W35wd8bs7vj7HSQ4=
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d h1:GoAlyOgbOEIFdaDqxJVlbOQ1DtGmZWs/Qau0hIlk+WQ=
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084 h1:sofwID9zm4tzrgykg80hfFph1mryUeLRsUfoocVVmRY=
github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
github.com/prometheus/procfs v0.0.8 h1:+fpWZdT24pJBiqJdAwYBjPSk+5YmQzYNPYzQsdzLkt8=
github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A=
github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
github.com/rcrowley/go-metrics v0.0.0-20180503174638-e2704e165165 h1:nkcn14uNmFEuGCb2mBZbBb24RdNRL08b/wb+xBOYpuk=
github.com/rcrowley/go-metrics v0.0.0-20180503174638-e2704e165165/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a h1:9ZKAASQSHhDYGoxY8uLVpewe1GDZ2vu2Tr/vTdVAkFQ=
github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/rs/cors v1.7.0 h1:+88SsELBHx5r+hZ8TCkggzSstaWNbDvThkVK8H6f9ik=
github.com/rs/cors v1.7.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU=
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
github.com/samuel/go-zookeeper v0.0.0-20190923202752-2cc03de413da/go.mod h1:gi+0XIa01GRL2eRQVjQkKGqKF3SF9vZR/HnPullcV2E=
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s=
@@ -188,26 +350,34 @@ github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9
github.com/snikch/goodman v0.0.0-20171125024755-10e37e294daa h1:YJfZp12Z3AFhSBeXOlv4BO55RMwPn2NoQeDsrdWnBtY=
github.com/snikch/goodman v0.0.0-20171125024755-10e37e294daa/go.mod h1:oJyF+mSPHbB5mVY2iO9KV3pTt/QbIkGaO8gQ2WrDbP4=
github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
github.com/sony/gobreaker v0.4.1/go.mod h1:ZKptC7FHNvhBz7dN2LGjPVBz2sZJmc0/PkyDJOjmxWY=
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
github.com/spf13/afero v1.1.2 h1:m8/z1t7/fwjysjQRYbP0RD+bUIF/8tJwPdEZsI83ACI=
github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
github.com/spf13/cast v1.3.0 h1:oget//CVOEoFewqQxwr0Ej5yjygnqGkvggSE/gB35Q8=
github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
github.com/spf13/cobra v0.0.1 h1:zZh3X5aZbdnoj+4XkaBxKfhO4ot82icYdhhREIAXIj8=
github.com/spf13/cobra v0.0.1/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
github.com/spf13/cobra v0.0.3 h1:ZlrZ4XsMRm04Fr5pSFxBgfND2EBVa1nLpiy1stUsX/8=
github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
github.com/spf13/jwalterweatherman v1.0.0 h1:XHEdyB+EcvlqZamSM4ZOMGlc93t6AcsBEu9Gc1vn7yk=
github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg=
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/spf13/viper v1.6.2 h1:7aKfF+e8/k68gda3LOjo5RxiUqddoFxVq4BKBPrxk5E=
github.com/spf13/viper v1.6.2/go.mod h1:t3iDnF5Jlj76alVNuyFBk5oUMCvsrkbvZK0WQdfDi5k=
github.com/streadway/amqp v0.0.0-20190404075320-75d898a42a94/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw=
github.com/streadway/amqp v0.0.0-20190827072141-edfb9018d271/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw=
github.com/streadway/handy v0.0.0-20190108123426-d5acb3125c2a/go.mod h1:qNTQ5P5JnDBl6z3cMAg/SywNDC5ABu5ApDIw6lUbRmI=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1 h1:2vfRuCMp5sSVIDSqO8oNnWJq7mPa6KVP3iPIwFBuy8A=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.5.0 h1:DMOzIV76tmoDNE9pX6RSN0aDtCYeCg5VueieJaAo1uw=
github.com/stretchr/testify v1.5.0/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s=
github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
github.com/syndtr/goleveldb v1.0.1-0.20190923125748-758128399b1d h1:gZZadD8H+fF+n9CmNhYL1Y0dJB+kLOmKd7FbPJLeGHs=
@@ -218,74 +388,138 @@ github.com/tendermint/go-amino v0.14.1 h1:o2WudxNfdLNBwMyl2dqOJxiro5rfrEaU0Ugs6o
github.com/tendermint/go-amino v0.14.1/go.mod h1:i/UKE5Uocn+argJJBb12qTZsCDBcAYMbR92AaJVmKso=
github.com/tendermint/tm-db v0.4.0 h1:iPbCcLbf4nwDFhS39Zo1lpdS1X/cT9CkTlUx17FHQgA=
github.com/tendermint/tm-db v0.4.0/go.mod h1:+Cwhgowrf7NBGXmsqFMbwEtbo80XmyrlY5Jsk95JubQ=
github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc=
github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA=
github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
go.etcd.io/bbolt v1.3.3 h1:MUGmc65QhB3pIlaQ5bB4LwqSj6GIonVJXpZiaKNyaKk=
go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
go.etcd.io/etcd v0.0.0-20191023171146-3cf2f69b5738/go.mod h1:dnLIgRNXwCJa5e+c6mIZCrds/GIG4ncV9HhK5PX7jPg=
go.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk=
go.opencensus.io v0.20.2/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk=
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4=
go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA=
go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM=
golang.org/x/crypto v0.0.0-20170930174604-9419663f5a44/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2 h1:VklqNMn3ovrHsnt90PveolxSbWFaJdECFbxSq0Mqo2M=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191206172530-e9b2fee46413 h1:ULYEB3JvPRE/IfO+9uO7vKV/xzVTO7XPAwm8xbf4w2g=
golang.org/x/crypto v0.0.0-20191206172530-e9b2fee46413/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd h1:nTDtHvHSdCn1m6ITfMRqtOd/9+7a3s8RBNOZ3eYZzJA=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190125091013-d26f9f9a57f3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190628185345-da137c7871d7 h1:rTIdg5QFRR7XCaK4LCjBiPbx8j4DQRpdYMnGn/bJUEU=
golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7 h1:fHDIZ2oxGnUZRN6WgWFCbYBjH9uqVPRCUVUDhs0wnbA=
golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f h1:wMNYb4v58l5UBM7MYRLPG6ZhfOqbKu7X5eyFl8ZhKvA=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58 h1:8gQV6CLnAEikrhgkHFbMAEhagSSnXWGV915qUMm9mrU=
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190130150945-aca44879d564/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a h1:1BGLXjeY4akVXGgbC9HugT3Jv3hCI0z56oJR5vAMgBU=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4 h1:sfkvUWPNGwSV+8/fNqctR5lS2AqCSqYwXdrjCxp/dXo=
golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191220142924-d4481acd189f h1:68K/z8GLUxV76xGSqwTWw2gyk/jwn79LUL43rES2g8o=
golang.org/x/sys v0.0.0-20191220142924-d4481acd189f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200122134326-e047566fdf82 h1:ywK/j/KkyTHcdyYSZNXGjMwgmDSfjglYZ3vStQ/gSCU=
golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/api v0.3.1/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190530194941-fb225487d101/go.mod h1:z3L6/3dTEVtUr6QSP8miRzeRqwQOioJ9I66odjN4I7s=
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55 h1:gSJIx1SDwno+2ElGhA4+qG2zF97qiUzTM+rQ0klBOcE=
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.20.0/go.mod h1:chYK+tFQF0nDUGJgXMSgLCQk3phJEuONr2DCgLDdAQM=
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
google.golang.org/grpc v1.22.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.23.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.26.0 h1:2dTRdpdFEEhJYQD8EMLB61nnrzSCTbG38PhqdhvOltg=
google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.27.1 h1:zvIju4sqAGvwKspUQOhwnpcqSbzi7/H6QomNNjTL4sk=
@@ -295,13 +529,19 @@ gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/cheggaaa/pb.v1 v1.0.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
gopkg.in/gcfg.v1 v1.2.3/go.mod h1:yesOnuUOFQAhST5vPY4nbZsb/huCgGGXlipJsBn0b3o=
gopkg.in/ini.v1 v1.51.0 h1:AQvPpx3LzTDM0AjnIRlVFwFFGC+npRopjZxLJj6gdno=
gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI=
gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
gopkg.in/yaml.v2 v2.2.1 h1:mUhvW9EsL+naU5Q3cakzfE91YhliOondGd6ZrsDBHQE=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
@@ -309,5 +549,11 @@ gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.4 h1:/eiJrUcujPVeJ3xlSWaiNi3uSVmDGBK1pDHUHAnao1I=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.5 h1:ymVxjfMaHvXD8RqPRmzHHsB3VvucivSkIAvJFDI5O3c=
gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o=
sourcegraph.com/sourcegraph/appdash v0.0.0-20190731080439-ebfcffb1b5c0/go.mod h1:hI742Nqp5OhwiqlzhgfbWU4mW4yO10fP+LoT9WOswdU=

View File

@@ -294,8 +294,8 @@ func (c *Client) checkTrustedHeaderUsingOptions(options TrustOptions) error {
case options.Height < c.trustedHeader.Height:
c.logger.Info("Client initialized with old header (trusted is more recent)",
"old", options.Height,
"trusted", c.trustedHeader.Height,
"trusted-hash", hash2str(c.trustedHeader.Hash()))
"trustedHeight", c.trustedHeader.Height,
"trustedHash", hash2str(c.trustedHeader.Hash()))
action := fmt.Sprintf(
"Rollback to %d (%X)? Note this will remove newer headers up to %d (%X)",
@@ -373,14 +373,14 @@ func (c *Client) initializeWithTrustOptions(options TrustOptions) error {
}
// 3) Fetch and verify the next vals (verification happens in
// updateTrustedHeaderAndVals).
// updateTrustedHeaderAndNextVals).
nextVals, err := c.validatorSetFromPrimary(options.Height + 1)
if err != nil {
return err
}
// 4) Persist both of them and continue.
return c.updateTrustedHeaderAndVals(h, nextVals)
return c.updateTrustedHeaderAndNextVals(h, nextVals)
}
// Start starts two processes: 1) auto updating 2) removing outdated headers.
@@ -560,6 +560,10 @@ func (c *Client) VerifyHeaderAtHeight(height int64, now time.Time) (*types.Signe
// If, at any moment, SignedHeader or ValidatorSet are not found by the primary
// provider, provider.ErrSignedHeaderNotFound /
// provider.ErrValidatorSetNotFound error is returned.
//
// NOTE: although newVals is entered as input, trustedStore will only store the
// validator set at height newHeader.Height+1 (i.e.
// newHeader.NextValidatorsHash).
func (c *Client) VerifyHeader(newHeader *types.SignedHeader, newVals *types.ValidatorSet, now time.Time) error {
c.logger.Info("VerifyHeader", "height", newHeader.Height, "hash", hash2str(newHeader.Hash()),
"vals", hash2str(newVals.Hash()))
@@ -592,7 +596,7 @@ func (c *Client) VerifyHeader(newHeader *types.SignedHeader, newVals *types.Vali
if err != nil {
return err
}
return c.updateTrustedHeaderAndVals(newHeader, nextVals)
return c.updateTrustedHeaderAndNextVals(newHeader, nextVals)
}
// Primary returns the primary provider.
@@ -678,12 +682,12 @@ func (c *Client) sequence(
}
c.logger.Debug("Verify newHeader against trustedHeader",
"lastHeight", c.trustedHeader.Height,
"lastHash", c.trustedHeader.Hash(),
"trustedHeight", c.trustedHeader.Height,
"trustedHash", hash2str(c.trustedHeader.Hash()),
"newHeight", interimHeader.Height,
"newHash", interimHeader.Hash())
err = Verify(c.chainID, trustedHeader, trustedNextVals, interimHeader, trustedNextVals,
c.trustingPeriod, now, c.trustLevel)
"newHash", hash2str(interimHeader.Hash()))
err = VerifyAdjacent(c.chainID, trustedHeader, interimHeader, trustedNextVals,
c.trustingPeriod, now)
if err != nil {
return errors.Wrapf(err, "failed to verify the header #%d", height)
}
@@ -697,7 +701,7 @@ func (c *Client) sequence(
return errors.Wrapf(err, "failed to obtain the vals #%d", height+1)
}
}
err = c.updateTrustedHeaderAndVals(interimHeader, interimNextVals)
err = c.updateTrustedHeaderAndNextVals(interimHeader, interimNextVals)
if err != nil {
return errors.Wrapf(err, "failed to update trusted state #%d", height)
}
@@ -705,67 +709,60 @@ func (c *Client) sequence(
}
// 2) Verify the new header.
return Verify(c.chainID, c.trustedHeader, c.trustedNextVals, newHeader, newVals, c.trustingPeriod, now, c.trustLevel)
return VerifyAdjacent(c.chainID, c.trustedHeader, newHeader, newVals, c.trustingPeriod, now)
}
// see VerifyHeader
func (c *Client) bisection(
lastHeader *types.SignedHeader,
lastVals *types.ValidatorSet,
newHeader *types.SignedHeader,
newVals *types.ValidatorSet,
trustedHeader *types.SignedHeader, // height h
trustedNextVals *types.ValidatorSet, // height h + 1
newHeader *types.SignedHeader, // height g
newVals *types.ValidatorSet, // height g
now time.Time) error {
c.logger.Debug("Verify newHeader against lastHeader",
"lastHeight", lastHeader.Height,
"lastHash", lastHeader.Hash(),
"newHeight", newHeader.Height,
"newHash", newHeader.Hash())
err := Verify(c.chainID, lastHeader, lastVals, newHeader, newVals, c.trustingPeriod, now, c.trustLevel)
switch err.(type) {
case nil:
return nil
case ErrNewValSetCantBeTrusted:
// continue bisection
default:
return errors.Wrapf(err, "failed to verify the header #%d", newHeader.Height)
}
interimVals := newVals
interimHeader := newHeader
pivot := (c.trustedHeader.Height + newHeader.Header.Height) / 2
pivotHeader, pivotVals, err := c.fetchHeaderAndValsAtHeight(pivot)
if err != nil {
return err
}
for trustedHeader.Height < newHeader.Height {
c.logger.Debug("Verify newHeader against trustedHeader",
"trustedHeight", trustedHeader.Height,
"trustedHash", hash2str(trustedHeader.Hash()),
"newHeight", newHeader.Height,
"newHash", hash2str(newHeader.Hash()))
err := Verify(c.chainID, trustedHeader, trustedNextVals, interimHeader, interimVals, c.trustingPeriod, now,
c.trustLevel)
switch err.(type) {
case nil:
// Update the lower bound to the previous upper bound
trustedHeader = interimHeader
trustedNextVals, err = c.validatorSetFromPrimary(interimHeader.Height + 1)
if err != nil {
return err
}
if !bytes.Equal(trustedHeader.NextValidatorsHash, trustedNextVals.Hash()) {
return errors.Errorf("expected next validator's hash %X, but got %X (height #%d)",
trustedHeader.NextValidatorsHash,
trustedNextVals.Hash(),
trustedHeader.Height)
}
// left branch
{
err := c.bisection(lastHeader, lastVals, pivotHeader, pivotVals, now)
if err != nil {
return errors.Wrapf(err, "bisection of #%d and #%d", lastHeader.Height, pivot)
}
}
err = c.updateTrustedHeaderAndNextVals(trustedHeader, trustedNextVals)
if err != nil {
return err
}
// right branch
{
nextVals, err := c.validatorSetFromPrimary(pivot + 1)
if err != nil {
return errors.Wrapf(err, "failed to obtain the vals #%d", pivot+1)
}
if !bytes.Equal(pivotHeader.NextValidatorsHash, nextVals.Hash()) {
return errors.Errorf("expected next validator's hash %X, but got %X (height #%d)",
pivotHeader.NextValidatorsHash,
nextVals.Hash(),
pivot)
}
// Update the upper bound to the untrustedHeader
interimHeader, interimVals = newHeader, newVals
err = c.updateTrustedHeaderAndVals(pivotHeader, nextVals)
if err != nil {
return errors.Wrapf(err, "failed to update trusted state #%d", pivot)
}
case ErrNewValSetCantBeTrusted:
pivotHeight := (interimHeader.Height + trustedHeader.Height) / 2
interimHeader, interimVals, err = c.fetchHeaderAndValsAtHeight(pivotHeight)
if err != nil {
return err
}
err = c.bisection(pivotHeader, nextVals, newHeader, newVals, now)
if err != nil {
return errors.Wrapf(err, "bisection of #%d and #%d", pivot, newHeader.Height)
default:
return errors.Wrapf(err, "failed to verify the header #%d", newHeader.Height)
}
}
@@ -773,7 +770,7 @@ func (c *Client) bisection(
}
// persist header and next validators to trustedStore.
func (c *Client) updateTrustedHeaderAndVals(h *types.SignedHeader, nextVals *types.ValidatorSet) error {
func (c *Client) updateTrustedHeaderAndNextVals(h *types.SignedHeader, nextVals *types.ValidatorSet) error {
if !bytes.Equal(h.NextValidatorsHash, nextVals.Hash()) {
return errors.Errorf("expected next validator's hash %X, but got %X", h.NextValidatorsHash, nextVals.Hash())
}

View File

@@ -27,8 +27,10 @@ var (
bTime, _ = time.Parse(time.RFC3339, "2006-01-02T15:04:05Z")
h1 = keys.GenSignedHeader(chainID, 1, bTime, nil, vals, vals,
[]byte("app_hash"), []byte("cons_hash"), []byte("results_hash"), 0, len(keys))
// 3/3 signed
h2 = keys.GenSignedHeaderLastBlockID(chainID, 2, bTime.Add(30*time.Minute), nil, vals, vals,
[]byte("app_hash"), []byte("cons_hash"), []byte("results_hash"), 0, len(keys), types.BlockID{Hash: h1.Hash()})
// 3/3 signed
h3 = keys.GenSignedHeaderLastBlockID(chainID, 3, bTime.Add(1*time.Hour), nil, vals, vals,
[]byte("app_hash"), []byte("cons_hash"), []byte("results_hash"), 0, len(keys), types.BlockID{Hash: h2.Hash()})
trustPeriod = 4 * time.Hour
@@ -56,13 +58,14 @@ var (
func TestClient_SequentialVerification(t *testing.T) {
testCases := []struct {
name string
otherHeaders map[int64]*types.SignedHeader // all except ^
vals map[int64]*types.ValidatorSet
initErr bool
verifyErr bool
}{
// good
{
"good",
map[int64]*types.SignedHeader{
// trusted header
1: h1,
@@ -80,8 +83,8 @@ func TestClient_SequentialVerification(t *testing.T) {
false,
false,
},
// bad: different first header
{
"bad: different first header",
map[int64]*types.SignedHeader{
// different header
1: keys.GenSignedHeader(chainID, 1, bTime.Add(1*time.Hour), nil, vals, vals,
@@ -93,8 +96,8 @@ func TestClient_SequentialVerification(t *testing.T) {
true,
false,
},
// bad: 1/3 signed interim header
{
"bad: 1/3 signed interim header",
map[int64]*types.SignedHeader{
// trusted header
1: h1,
@@ -114,8 +117,8 @@ func TestClient_SequentialVerification(t *testing.T) {
false,
true,
},
// bad: 1/3 signed last header
{
"bad: 1/3 signed last header",
map[int64]*types.SignedHeader{
// trusted header
1: h1,
@@ -138,39 +141,42 @@ func TestClient_SequentialVerification(t *testing.T) {
}
for _, tc := range testCases {
c, err := NewClient(
chainID,
trustOptions,
mockp.New(
tc := tc
t.Run(tc.name, func(t *testing.T) {
c, err := NewClient(
chainID,
tc.otherHeaders,
tc.vals,
),
[]provider.Provider{mockp.New(
chainID,
tc.otherHeaders,
tc.vals,
)},
dbs.New(dbm.NewMemDB(), chainID),
SequentialVerification(),
)
trustOptions,
mockp.New(
chainID,
tc.otherHeaders,
tc.vals,
),
[]provider.Provider{mockp.New(
chainID,
tc.otherHeaders,
tc.vals,
)},
dbs.New(dbm.NewMemDB(), chainID),
SequentialVerification(),
)
if tc.initErr {
require.Error(t, err)
return
}
if tc.initErr {
require.Error(t, err)
continue
} else {
require.NoError(t, err)
}
err = c.Start()
require.NoError(t, err)
defer c.Stop()
err = c.Start()
require.NoError(t, err)
defer c.Stop()
_, err = c.VerifyHeaderAtHeight(3, bTime.Add(3*time.Hour))
if tc.verifyErr {
assert.Error(t, err)
} else {
assert.NoError(t, err)
}
_, err = c.VerifyHeaderAtHeight(3, bTime.Add(3*time.Hour))
if tc.verifyErr {
assert.Error(t, err)
} else {
assert.NoError(t, err)
}
})
}
}
@@ -179,12 +185,19 @@ func TestClient_SkippingVerification(t *testing.T) {
newKeys := genPrivKeys(4)
newVals := newKeys.ToValidators(10, 1)
// 1/3+ of vals, 2/3- of newVals
transitKeys := keys.Extend(3)
transitVals := transitKeys.ToValidators(10, 1)
testCases := []struct {
name string
otherHeaders map[int64]*types.SignedHeader // all except ^
vals map[int64]*types.ValidatorSet
initErr bool
verifyErr bool
}{
// good
{
"good",
map[int64]*types.SignedHeader{
// trusted header
1: h1,
@@ -197,9 +210,28 @@ func TestClient_SkippingVerification(t *testing.T) {
3: vals,
4: vals,
},
false,
false,
},
// good, val set changes 100% at height 2
{
"good, but val set changes by 2/3 (1/3 of vals is still present)",
map[int64]*types.SignedHeader{
// trusted header
1: h1,
3: transitKeys.GenSignedHeader(chainID, 3, bTime.Add(2*time.Hour), nil, transitVals, transitVals,
[]byte("app_hash"), []byte("cons_hash"), []byte("results_hash"), 0, len(transitKeys)),
},
map[int64]*types.ValidatorSet{
1: vals,
2: vals,
3: transitVals,
4: transitVals,
},
false,
false,
},
{
"good, but val set changes 100% at height 2",
map[int64]*types.SignedHeader{
// trusted header
1: h1,
@@ -216,33 +248,68 @@ func TestClient_SkippingVerification(t *testing.T) {
3: newVals,
4: newVals,
},
false,
false,
},
{
"bad: last header signed by newVals, interim header has no signers",
map[int64]*types.SignedHeader{
// trusted header
1: h1,
// last header (0/4 of the original val set signed)
2: keys.GenSignedHeader(chainID, 2, bTime.Add(1*time.Hour), nil, vals, newVals,
[]byte("app_hash"), []byte("cons_hash"), []byte("results_hash"), 0, 0),
// last header (0/4 of the original val set signed)
3: newKeys.GenSignedHeader(chainID, 3, bTime.Add(2*time.Hour), nil, newVals, newVals,
[]byte("app_hash"), []byte("cons_hash"), []byte("results_hash"), 0, len(newKeys)),
},
map[int64]*types.ValidatorSet{
1: vals,
2: vals,
3: newVals,
4: newVals,
},
false,
true,
},
}
for _, tc := range testCases {
c, err := NewClient(
chainID,
trustOptions,
mockp.New(
tc := tc
t.Run(tc.name, func(t *testing.T) {
c, err := NewClient(
chainID,
tc.otherHeaders,
tc.vals,
),
[]provider.Provider{mockp.New(
chainID,
tc.otherHeaders,
tc.vals,
)},
dbs.New(dbm.NewMemDB(), chainID),
SkippingVerification(DefaultTrustLevel),
)
require.NoError(t, err)
err = c.Start()
require.NoError(t, err)
defer c.Stop()
trustOptions,
mockp.New(
chainID,
tc.otherHeaders,
tc.vals,
),
[]provider.Provider{mockp.New(
chainID,
tc.otherHeaders,
tc.vals,
)},
dbs.New(dbm.NewMemDB(), chainID),
SkippingVerification(DefaultTrustLevel),
)
if tc.initErr {
require.Error(t, err)
return
}
_, err = c.VerifyHeaderAtHeight(3, bTime.Add(3*time.Hour))
assert.NoError(t, err)
require.NoError(t, err)
err = c.Start()
require.NoError(t, err)
defer c.Stop()
_, err = c.VerifyHeaderAtHeight(3, bTime.Add(3*time.Hour))
if tc.verifyErr {
assert.Error(t, err)
} else {
assert.NoError(t, err)
}
})
}
}

View File

@@ -36,11 +36,11 @@ func genPrivKeys(n int) privKeys {
// return res
// }
// // Extend adds n more keys (to remove, just take a slice).
// func (pkz privKeys) Extend(n int) privKeys {
// extra := genPrivKeys(n)
// return append(pkz, extra...)
// }
// Extend adds n more keys (to remove, just take a slice).
func (pkz privKeys) Extend(n int) privKeys {
extra := genPrivKeys(n)
return append(pkz, extra...)
}
// // GenSecpPrivKeys produces an array of secp256k1 private keys to generate commits.
// func GenSecpPrivKeys(n int) privKeys {

View File

@@ -11,106 +11,167 @@ import (
)
var (
// DefaultTrustLevel - new header can be trusted if at least one correct old
// DefaultTrustLevel - new header can be trusted if at least one correct
// validator signed it.
DefaultTrustLevel = tmmath.Fraction{Numerator: 1, Denominator: 3}
)
// Verify verifies the new header (h2) against the old header (h1). It ensures that:
// VerifyNonAdjacent verifies non-adjacent untrustedHeader against
// trustedHeader. It ensures that:
//
// a) h1 can still be trusted (if not, ErrOldHeaderExpired is returned);
// b) h2 is valid;
// c) either h2.ValidatorsHash equals h1NextVals.Hash()
// OR trustLevel ([1/3, 1]) of last trusted validators (h1NextVals) signed
// correctly (if not, ErrNewValSetCantBeTrusted is returned);
// c) more than 2/3 of new validators (h2Vals) have signed h2 (if not,
// ErrNotEnoughVotingPowerSigned is returned).
func Verify(
// a) trustedHeader can still be trusted (if not, ErrOldHeaderExpired is returned);
// b) untrustedHeader is valid;
// c) trustLevel ([1/3, 1]) of trustedHeaderNextVals signed correctly
// (if not, ErrNewValSetCantBeTrusted is returned);
// d) more than 2/3 of untrustedVals have signed h2 (if not,
// ErrNotEnoughVotingPowerSigned is returned);
// e) headers are non-adjacent.
func VerifyNonAdjacent(
chainID string,
h1 *types.SignedHeader,
h1NextVals *types.ValidatorSet,
h2 *types.SignedHeader,
h2Vals *types.ValidatorSet,
trustedHeader *types.SignedHeader, // height=X
trustedNextVals *types.ValidatorSet, // height=X+1
untrustedHeader *types.SignedHeader, // height=Y
untrustedVals *types.ValidatorSet, // height=Y
trustingPeriod time.Duration,
now time.Time,
trustLevel tmmath.Fraction) error {
if err := ValidateTrustLevel(trustLevel); err != nil {
if untrustedHeader.Height == trustedHeader.Height+1 {
return errors.New("headers must be non adjacent in height")
}
if HeaderExpired(trustedHeader, trustingPeriod, now) {
return ErrOldHeaderExpired{trustedHeader.Time.Add(trustingPeriod), now}
}
if err := verifyNewHeaderAndVals(chainID, untrustedHeader, untrustedVals, trustedHeader, now); err != nil {
return err
}
// Ensure last header can still be trusted.
if HeaderExpired(h1, trustingPeriod, now) {
return ErrOldHeaderExpired{h1.Time.Add(trustingPeriod), now}
}
if err := verifyNewHeaderAndVals(chainID, h2, h2Vals, h1, now); err != nil {
return err
}
if h2.Height == h1.Height+1 {
if !bytes.Equal(h2.ValidatorsHash, h1NextVals.Hash()) {
err := errors.Errorf("expected old header next validators (%X) to match those from new header (%X)",
h1NextVals.Hash(),
h2.ValidatorsHash,
)
return err
}
} else {
// Ensure that +`trustLevel` (default 1/3) or more of last trusted validators signed correctly.
err := h1NextVals.VerifyCommitTrusting(chainID, h2.Commit.BlockID, h2.Height, h2.Commit, trustLevel)
if err != nil {
switch e := err.(type) {
case types.ErrNotEnoughVotingPowerSigned:
return ErrNewValSetCantBeTrusted{e}
default:
return e
}
// Ensure that +`trustLevel` (default 1/3) or more of last trusted validators signed correctly.
err := trustedNextVals.VerifyCommitTrusting(chainID, untrustedHeader.Commit.BlockID, untrustedHeader.Height,
untrustedHeader.Commit, trustLevel)
if err != nil {
switch e := err.(type) {
case types.ErrNotEnoughVotingPowerSigned:
return ErrNewValSetCantBeTrusted{e}
default:
return e
}
}
// Ensure that +2/3 of new validators signed correctly.
err := h2Vals.VerifyCommit(chainID, h2.Commit.BlockID, h2.Height, h2.Commit)
if err != nil {
//
// NOTE: this should always be the last check because untrustedVals can be
// intentionaly made very large to DOS the light client. not the case for
// VerifyAdjacent, where validator set is known in advance.
if err := untrustedVals.VerifyCommit(chainID, untrustedHeader.Commit.BlockID, untrustedHeader.Height,
untrustedHeader.Commit); err != nil {
return err
}
return nil
}
func verifyNewHeaderAndVals(
// VerifyAdjacent verifies directly adjacent untrustedHeader against
// trustedHeader. It ensures that:
//
// a) trustedHeader can still be trusted (if not, ErrOldHeaderExpired is returned);
// b) untrustedHeader is valid;
// c) untrustedHeader.ValidatorsHash equals trustedHeaderNextVals.Hash()
// d) more than 2/3 of new validators (untrustedVals) have signed h2 (if not,
// ErrNotEnoughVotingPowerSigned is returned);
// e) headers are adjacent.
func VerifyAdjacent(
chainID string,
h2 *types.SignedHeader,
h2Vals *types.ValidatorSet,
h1 *types.SignedHeader,
trustedHeader *types.SignedHeader, // height=X
untrustedHeader *types.SignedHeader, // height=X+1
untrustedVals *types.ValidatorSet, // height=X+1
trustingPeriod time.Duration,
now time.Time) error {
if err := h2.ValidateBasic(chainID); err != nil {
return errors.Wrap(err, "h2.ValidateBasic failed")
if untrustedHeader.Height != trustedHeader.Height+1 {
return errors.New("headers must be adjacent in height")
}
if h2.Height <= h1.Height {
if HeaderExpired(trustedHeader, trustingPeriod, now) {
return ErrOldHeaderExpired{trustedHeader.Time.Add(trustingPeriod), now}
}
if err := verifyNewHeaderAndVals(chainID, untrustedHeader, untrustedVals, trustedHeader, now); err != nil {
return err
}
// Check the validator hashes are the same
if !bytes.Equal(untrustedHeader.ValidatorsHash, trustedHeader.NextValidatorsHash) {
err := errors.Errorf("expected old header next validators (%X) to match those from new header (%X)",
trustedHeader.NextValidatorsHash,
untrustedHeader.ValidatorsHash,
)
return err
}
// Ensure that +2/3 of new validators signed correctly.
if err := untrustedVals.VerifyCommit(chainID, untrustedHeader.Commit.BlockID, untrustedHeader.Height,
untrustedHeader.Commit); err != nil {
return err
}
return nil
}
// Verify combines both VerifyAdjacent and VerifyNonAdjacent functions.
func Verify(
chainID string,
trustedHeader *types.SignedHeader, // height=X
trustedNextVals *types.ValidatorSet, // height=X+1
untrustedHeader *types.SignedHeader, // height=Y
untrustedVals *types.ValidatorSet, // height=Y
trustingPeriod time.Duration,
now time.Time,
trustLevel tmmath.Fraction) error {
if untrustedHeader.Height != trustedHeader.Height+1 {
return VerifyNonAdjacent(chainID, trustedHeader, trustedNextVals, untrustedHeader, untrustedVals,
trustingPeriod, now, trustLevel)
}
return VerifyAdjacent(chainID, trustedHeader, untrustedHeader, untrustedVals, trustingPeriod, now)
}
func verifyNewHeaderAndVals(
chainID string,
untrustedHeader *types.SignedHeader,
untrustedVals *types.ValidatorSet,
trustedHeader *types.SignedHeader,
now time.Time) error {
if err := untrustedHeader.ValidateBasic(chainID); err != nil {
return errors.Wrap(err, "untrustedHeader.ValidateBasic failed")
}
if untrustedHeader.Height <= trustedHeader.Height {
return errors.Errorf("expected new header height %d to be greater than one of old header %d",
h2.Height,
h1.Height)
untrustedHeader.Height,
trustedHeader.Height)
}
if !h2.Time.After(h1.Time) {
if !untrustedHeader.Time.After(trustedHeader.Time) {
return errors.Errorf("expected new header time %v to be after old header time %v",
h2.Time,
h1.Time)
untrustedHeader.Time,
trustedHeader.Time)
}
if !h2.Time.Before(now) {
if !untrustedHeader.Time.Before(now) {
return errors.Errorf("new header has a time from the future %v (now: %v)",
h2.Time,
untrustedHeader.Time,
now)
}
if !bytes.Equal(h2.ValidatorsHash, h2Vals.Hash()) {
if !bytes.Equal(untrustedHeader.ValidatorsHash, untrustedVals.Hash()) {
return errors.Errorf("expected new header validators (%X) to match those that were supplied (%X)",
h2.ValidatorsHash,
h2Vals.Hash(),
untrustedHeader.ValidatorsHash,
untrustedVals.Hash(),
)
}

View File

@@ -42,7 +42,7 @@ func TestVerifyAdjacentHeaders(t *testing.T) {
3 * time.Hour,
bTime.Add(2 * time.Hour),
nil,
"expected new header height 1 to be greater than one of old header 1",
"headers must be adjacent in height",
},
// different chainID -> error
1: {
@@ -52,7 +52,8 @@ func TestVerifyAdjacentHeaders(t *testing.T) {
3 * time.Hour,
bTime.Add(2 * time.Hour),
nil,
"h2.ValidateBasic failed: signedHeader belongs to another chain 'different-chainID' not 'TestVerifyAdjacentHeaders'",
"untrustedHeader.ValidateBasic failed: signedHeader belongs to another chain 'different-chainID' not" +
" 'TestVerifyAdjacentHeaders'",
},
// new header's time is before old header's time -> error
2: {
@@ -139,8 +140,7 @@ func TestVerifyAdjacentHeaders(t *testing.T) {
for i, tc := range testCases {
tc := tc
t.Run(fmt.Sprintf("#%d", i), func(t *testing.T) {
err := Verify(chainID, header, vals, tc.newHeader, tc.newVals, tc.trustingPeriod, tc.now, DefaultTrustLevel)
err := VerifyAdjacent(chainID, header, tc.newHeader, tc.newVals, tc.trustingPeriod, tc.now)
switch {
case tc.expErr != nil && assert.Error(t, err):
assert.Equal(t, tc.expErr, err)
@@ -254,7 +254,8 @@ func TestVerifyNonAdjacentHeaders(t *testing.T) {
for i, tc := range testCases {
tc := tc
t.Run(fmt.Sprintf("#%d", i), func(t *testing.T) {
err := Verify(chainID, header, vals, tc.newHeader, tc.newVals, tc.trustingPeriod, tc.now, DefaultTrustLevel)
err := VerifyNonAdjacent(chainID, header, vals, tc.newHeader, tc.newVals, tc.trustingPeriod, tc.now,
DefaultTrustLevel)
switch {
case tc.expErr != nil && assert.Error(t, err):

View File

@@ -3,6 +3,7 @@ package client_test
import (
"bytes"
"fmt"
"math"
"math/rand"
"net/http"
"strings"
@@ -17,6 +18,7 @@ import (
"github.com/tendermint/tendermint/crypto/ed25519"
"github.com/tendermint/tendermint/crypto/tmhash"
tmbytes "github.com/tendermint/tendermint/libs/bytes"
"github.com/tendermint/tendermint/libs/log"
tmmath "github.com/tendermint/tendermint/libs/math"
mempl "github.com/tendermint/tendermint/mempool"
@@ -414,14 +416,25 @@ func TestTx(t *testing.T) {
}
func TestTxSearch(t *testing.T) {
// first we broadcast a tx
c := getHTTPClient()
_, _, tx := MakeTxKV()
bres, err := c.BroadcastTxCommit(tx)
require.Nil(t, err)
txHeight := bres.Height
txHash := bres.Hash
// first we broadcast a few txs
var tx []byte
var txHeight int64
var txHash tmbytes.HexBytes
for i := 0; i < 10; i++ {
_, _, tx = MakeTxKV()
res, err := c.BroadcastTxCommit(tx)
require.NoError(t, err)
txHeight = res.Height
txHash = res.Hash
}
// Since we're not using an isolated test server, we'll have lingering transactions
// from other tests as well
result, err := c.TxSearch("tx.height >= 0", true, 1, 100, "asc")
require.NoError(t, err)
txCount := len(result.Txs)
anotherTxHash := types.Tx("a different tx").Hash()
@@ -433,6 +446,7 @@ func TestTxSearch(t *testing.T) {
result, err := c.TxSearch(fmt.Sprintf("tx.hash='%v'", txHash), true, 1, 30, "asc")
require.Nil(t, err)
require.Len(t, result.Txs, 1)
require.Equal(t, txHash, result.Txs[0].Hash)
ptx := result.Txs[0]
assert.EqualValues(t, txHeight, ptx.Height)
@@ -476,12 +490,7 @@ func TestTxSearch(t *testing.T) {
require.Nil(t, err)
require.Len(t, result.Txs, 0)
// broadcast another transaction to make sure we have at least two.
_, _, tx2 := MakeTxKV()
_, err = c.BroadcastTxCommit(tx2)
require.Nil(t, err)
// chech sorting
// check sorting
result, err = c.TxSearch(fmt.Sprintf("tx.height >= 1"), false, 1, 30, "asc")
require.Nil(t, err)
for k := 0; k < len(result.Txs)-1; k++ {
@@ -495,6 +504,31 @@ func TestTxSearch(t *testing.T) {
require.GreaterOrEqual(t, result.Txs[k].Height, result.Txs[k+1].Height)
require.GreaterOrEqual(t, result.Txs[k].Index, result.Txs[k+1].Index)
}
// check pagination
seen := map[int64]bool{}
maxHeight := int64(0)
perPage := 3
pages := int(math.Ceil(float64(txCount) / float64(perPage)))
for page := 1; page <= pages; page++ {
result, err = c.TxSearch("tx.height >= 1", false, page, perPage, "asc")
require.NoError(t, err)
if page < pages {
require.Len(t, result.Txs, perPage)
} else {
require.LessOrEqual(t, len(result.Txs), perPage)
}
require.Equal(t, txCount, result.TotalCount)
for _, tx := range result.Txs {
require.False(t, seen[tx.Height],
"Found duplicate height %v in page %v", tx.Height, page)
require.Greater(t, tx.Height, maxHeight,
"Found decreasing height %v (max seen %v) in page %v", tx.Height, maxHeight, page)
seen[tx.Height] = true
maxHeight = tx.Height
}
}
require.Len(t, seen, txCount)
}
}

View File

@@ -72,6 +72,27 @@ func TxSearch(ctx *rpctypes.Context, query string, prove bool, page, perPage int
return nil, err
}
// sort results (must be done before pagination)
switch orderBy {
case "desc":
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
})
case "asc", "":
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
})
default:
return nil, errors.New("expected order_by to be either `asc` or `desc` or empty")
}
// paginate results
totalCount := len(results)
perPage = validatePerPage(perPage)
page, err = validatePage(page, perPage, totalCount)
@@ -79,49 +100,26 @@ func TxSearch(ctx *rpctypes.Context, query string, prove bool, page, perPage int
return nil, err
}
skipCount := validateSkipCount(page, perPage)
pageSize := tmmath.MinInt(perPage, totalCount-skipCount)
apiResults := make([]*ctypes.ResultTx, tmmath.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
index := r.Index
apiResults := make([]*ctypes.ResultTx, 0, pageSize)
for i := skipCount; i < skipCount+pageSize; i++ {
r := results[i]
var proof types.TxProof
if prove {
block := blockStore.LoadBlock(height)
proof = block.Data.Txs.Proof(int(index)) // XXX: overflow on 32-bit machines
block := blockStore.LoadBlock(r.Height)
proof = block.Data.Txs.Proof(int(r.Index)) // XXX: overflow on 32-bit machines
}
apiResults[i] = &ctypes.ResultTx{
apiResults = append(apiResults, &ctypes.ResultTx{
Hash: r.Tx.Hash(),
Height: height,
Index: index,
Height: r.Height,
Index: r.Index,
TxResult: r.Result,
Tx: r.Tx,
Proof: proof,
}
}
if len(apiResults) > 1 {
switch orderBy {
case "desc":
sort.Slice(apiResults, func(i, j int) bool {
if apiResults[i].Height == apiResults[j].Height {
return apiResults[i].Index > apiResults[j].Index
}
return apiResults[i].Height > apiResults[j].Height
})
case "asc", "":
sort.Slice(apiResults, func(i, j int) bool {
if apiResults[i].Height == apiResults[j].Height {
return apiResults[i].Index < apiResults[j].Index
}
return apiResults[i].Height < apiResults[j].Height
})
default:
return nil, errors.New("expected order_by to be either `asc` or `desc` or empty")
}
})
}
return &ctypes.ResultTxSearch{Txs: apiResults, TotalCount: totalCount}, nil

View File

@@ -299,8 +299,6 @@ func (wsc *wsConnection) Context() context.Context {
// Read from the socket and subscribe to or unsubscribe from events
func (wsc *wsConnection) readRoutine() {
var request types.RPCRequest
defer func() {
if r := recover(); r != nil {
err, ok := r.(error)
@@ -308,7 +306,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(request.ID, err))
wsc.WriteRPCResponse(types.RPCInternalError(types.JSONRPCIntID(-1), err))
go wsc.readRoutine()
}
}()
@@ -339,6 +337,7 @@ func (wsc *wsConnection) readRoutine() {
return
}
var request types.RPCRequest
err = json.Unmarshal(in, &request)
if err != nil {
wsc.WriteRPCResponse(types.RPCParseError(errors.Wrap(err, "error unmarshaling request")))