From 67cdd008cd7d3af3bc02d0bf716ec49a58e960c0 Mon Sep 17 00:00:00 2001 From: Sean Braithwaite Date: Mon, 14 Oct 2019 19:06:11 +0200 Subject: [PATCH] Blockchain v2 processor (#4012) * Add processor prototype * Change processor API + expose a simple `handle` function which mutates internal state * processor tests * fix gofmt and ohter golangci issues * scopelint var on range scope * add check for short block received * fix formatting * small test reorg * ignore unused for now * ci fix changes * go.mod revert --- blockchain/v2/processor.go | 186 ++++++++++++++++ blockchain/v2/processor_context.go | 76 +++++++ blockchain/v2/processor_test.go | 336 +++++++++++++++++++++++++++++ blockchain/v2/reactor.go | 1 + blockchain/v2/schedule.go | 1 + go.mod | 2 + go.sum | 12 +- 7 files changed, 608 insertions(+), 6 deletions(-) create mode 100644 blockchain/v2/processor.go create mode 100644 blockchain/v2/processor_context.go create mode 100644 blockchain/v2/processor_test.go diff --git a/blockchain/v2/processor.go b/blockchain/v2/processor.go new file mode 100644 index 000000000..22b46ede3 --- /dev/null +++ b/blockchain/v2/processor.go @@ -0,0 +1,186 @@ +package v2 + +import ( + "fmt" + + "github.com/tendermint/tendermint/p2p" + tdState "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 +} + +type bcBlockResponse struct { + priorityNormal + peerID p2p.ID + block *types.Block + height int64 +} + +type pcBlockVerificationFailure struct { + priorityNormal + peerID p2p.ID + height int64 +} + +type pcBlockProcessed struct { + priorityNormal + height int64 + peerID p2p.ID +} + +type pcProcessBlock struct { + priorityNormal +} + +type pcStop struct { + priorityNormal +} + +type pcFinished struct { + priorityNormal + height int64 + blocksSynced int64 +} + +func (p pcFinished) Error() string { + return "finished" +} + +type queueItem struct { + block *types.Block + peerID p2p.ID +} + +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 +} + +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) +} + +// newPcState returns a pcState initialized with the last verified block enqueued +func newPcState(initHeight int64, tdState tdState.State, chainID string, 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 { + return first, second, nil + } + } + return queueItem{}, queueItem{}, fmt.Errorf("not found") +} + +// synced returns true when at most the last verified block remains in the queue +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 { + if _, ok := state.queue[height]; ok { + return fmt.Errorf("duplicate queue item") + } + state.queue[height] = queueItem{block: block, peerID: peerID} + return nil +} + +// purgePeer moves all unprocessed blocks from the queue +func (state *pcState) purgePeer(peerID p2p.ID) { + // what if height is less than state.height? + for height, item := range state.queue { + if item.peerID == peerID { + delete(state.queue, height) + } + } +} + +// handle processes FSM events +func (state *pcState) handle(event Event) (Event, error) { + switch event := event.(type) { + case *bcBlockResponse: + if event.height <= state.height { + return pcShortBlock{}, nil + } + err := state.enqueue(event.peerID, event.block, event.height) + if err != nil { + return pcDuplicateBlock{}, nil + } + + case pcProcessBlock: + firstItem, secondItem, err := state.nextTwo() + if err != nil { + if state.draining { + return noOp, pcFinished{height: state.height} + } + return noOp, nil + } + first, second := firstItem.block, secondItem.block + + firstParts := first.MakePartSet(types.BlockPartSizeBytes) + firstPartsHeader := firstParts.Header() + firstID := types.BlockID{Hash: first.Hash(), PartsHeader: firstPartsHeader} + + err = state.context.verifyCommit(state.chainID, firstID, first.Height, second.LastCommit) + if err != nil { + return pcBlockVerificationFailure{peerID: firstItem.peerID, height: first.Height}, nil + } + + state.context.saveBlock(first, firstParts, second.LastCommit) + + state.tdState, err = state.context.applyBlock(state.tdState, firstID, first) + if err != nil { + panic(fmt.Sprintf("failed to process committed block (%d:%X): %v", first.Height, first.Hash(), err)) + } + state.advance() + 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 +} diff --git a/blockchain/v2/processor_context.go b/blockchain/v2/processor_context.go new file mode 100644 index 000000000..c4c8770cd --- /dev/null +++ b/blockchain/v2/processor_context.go @@ -0,0 +1,76 @@ +package v2 + +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) + verifyCommit(chainID string, blockID types.BlockID, height int64, commit *types.Commit) error + saveBlock(block *types.Block, blockParts *types.PartSet, seenCommit *types.Commit) +} + +// nolint:unused +type pContext struct { + store *store.BlockStore + executor *state.BlockExecutor + state *state.State +} + +// nolint:unused,deadcode +func newProcessorContext(st *store.BlockStore, ex *state.BlockExecutor, s *state.State) *pContext { + return &pContext{ + store: st, + executor: 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) verifyCommit(chainID string, blockID types.BlockID, height int64, commit *types.Commit) error { + return pc.state.Validators.VerifyCommit(chainID, blockID, height, commit) +} + +func (pc *pContext) saveBlock(block *types.Block, blockParts *types.PartSet, seenCommit *types.Commit) { + pc.store.SaveBlock(block, blockParts, seenCommit) +} + +type mockPContext struct { + applicationBL []int64 + verificationBL []int64 +} + +func newMockProcessorContext(verificationBlackList []int64, applicationBlackList []int64) *mockPContext { + return &mockPContext{ + applicationBL: applicationBlackList, + verificationBL: verificationBlackList, + } +} + +func (mpc *mockPContext) applyBlock(state state.State, blockID types.BlockID, block *types.Block) (state.State, error) { + for _, h := range mpc.applicationBL { + if h == block.Height { + return state, fmt.Errorf("generic application error") + } + } + return state, nil +} + +func (mpc *mockPContext) verifyCommit(chainID string, blockID types.BlockID, height int64, commit *types.Commit) error { + for _, h := range mpc.verificationBL { + if h == height { + return fmt.Errorf("generic verification error") + } + } + return nil +} + +func (mpc *mockPContext) saveBlock(block *types.Block, blockParts *types.PartSet, seenCommit *types.Commit) { +} diff --git a/blockchain/v2/processor_test.go b/blockchain/v2/processor_test.go new file mode 100644 index 000000000..195df3618 --- /dev/null +++ b/blockchain/v2/processor_test.go @@ -0,0 +1,336 @@ +package v2 + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/tendermint/tendermint/p2p" + tdState "github.com/tendermint/tendermint/state" + "github.com/tendermint/tendermint/types" +) + +// pcBlock is a test helper structure with simple types. Its purpose is to help with test readability. +type pcBlock struct { + pid string + height int64 +} + +// params is a test structure used to create processor state. +type params struct { + height int64 + items []pcBlock + blocksSynced int64 + verBL []int64 + appBL []int64 + draining bool +} + +// makePcBlock makes an empty block. +func makePcBlock(height int64) *types.Block { + return &types.Block{Header: types.Header{Height: height}} +} + +// 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) + ) + state := newPcState(p.height, tdState, "test", context) + + for _, item := range p.items { + _ = state.enqueue(p2p.ID(item.pid), makePcBlock(item.height), item.height) + } + + state.blocksSynced = p.blocksSynced + state.draining = p.draining + return state +} + +func mBlockResponse(peerID p2p.ID, height int64) *bcBlockResponse { + return &bcBlockResponse{ + peerID: peerID, + block: makePcBlock(height), + height: height, + } +} + +type pcFsmMakeStateValues struct { + currentState *params + event Event + wantState *params + wantNextEvent Event + wantErr error + wantPanic bool +} + +type testFields struct { + name string + steps []pcFsmMakeStateValues +} + +func executeProcessorTests(t *testing.T, tests []testFields) { + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + var state *pcState + for _, step := range tt.steps { + defer func() { + r := recover() + if (r != nil) != step.wantPanic { + t.Errorf("recover = %v, wantPanic = %v", r, step.wantPanic) + } + }() + + // First step must always initialise the currentState as state. + if step.currentState != nil { + state = makeState(step.currentState) + } + if state == nil { + panic("Bad (initial?) step") + } + + nextEvent, err := state.handle(step.event) + t.Log(state) + assert.Equal(t, step.wantErr, err) + assert.Equal(t, makeState(step.wantState), state) + assert.Equal(t, step.wantNextEvent, nextEvent) + // Next step may use the wantedState as their currentState. + state = makeState(step.wantState) + } + }) + } +} + +func TestPcBlockResponse(t *testing.T) { + tests := []testFields{ + + { + name: "add one block", + steps: []pcFsmMakeStateValues{ + { + currentState: ¶ms{}, event: mBlockResponse("P1", 1), + wantState: ¶ms{items: []pcBlock{{"P1", 1}}}, wantNextEvent: noOp, + }, + }, + }, + { + name: "add two blocks", + steps: []pcFsmMakeStateValues{ + { + currentState: ¶ms{}, event: mBlockResponse("P1", 3), + wantState: ¶ms{items: []pcBlock{{"P1", 3}}}, wantNextEvent: noOp, + }, + { // use previous wantState as currentState, + event: mBlockResponse("P1", 4), + wantState: ¶ms{items: []pcBlock{{"P1", 3}, {"P1", 4}}}, wantNextEvent: noOp, + }, + }, + }, + { + name: "add duplicate block from same peer", + steps: []pcFsmMakeStateValues{ + { + currentState: ¶ms{}, event: mBlockResponse("P1", 3), + wantState: ¶ms{items: []pcBlock{{"P1", 3}}}, wantNextEvent: noOp, + }, + { // use previous wantState as currentState, + event: mBlockResponse("P1", 3), + wantState: ¶ms{items: []pcBlock{{"P1", 3}}}, wantNextEvent: pcDuplicateBlock{}, + }, + }, + }, + { + name: "add duplicate block from different peer", + steps: []pcFsmMakeStateValues{ + { + currentState: ¶ms{}, event: mBlockResponse("P1", 3), + wantState: ¶ms{items: []pcBlock{{"P1", 3}}}, wantNextEvent: noOp, + }, + { // use previous wantState as currentState, + event: mBlockResponse("P2", 3), + wantState: ¶ms{items: []pcBlock{{"P1", 3}}}, wantNextEvent: pcDuplicateBlock{}, + }, + }, + }, + { + name: "attempt to add block with height equal to state.height", + steps: []pcFsmMakeStateValues{ + { + currentState: ¶ms{height: 2, items: []pcBlock{{"P1", 3}}}, event: mBlockResponse("P1", 2), + wantState: ¶ms{height: 2, items: []pcBlock{{"P1", 3}}}, wantNextEvent: pcShortBlock{}, + }, + }, + }, + { + name: "attempt to add block with height smaller than state.height", + steps: []pcFsmMakeStateValues{ + { + currentState: ¶ms{height: 2, items: []pcBlock{{"P1", 3}}}, event: mBlockResponse("P1", 1), + wantState: ¶ms{height: 2, items: []pcBlock{{"P1", 3}}}, wantNextEvent: pcShortBlock{}, + }, + }, + }, + } + + executeProcessorTests(t, tests) +} + +func TestPcProcessBlockSuccess(t *testing.T) { + tests := []testFields{ + { + name: "noop - no blocks over current height", + steps: []pcFsmMakeStateValues{ + { + currentState: ¶ms{}, event: pcProcessBlock{}, + wantState: ¶ms{}, wantNextEvent: noOp, + }, + }, + }, + { + name: "noop - high new blocks", + steps: []pcFsmMakeStateValues{ + { + currentState: ¶ms{height: 5, items: []pcBlock{{"P1", 30}, {"P2", 31}}}, event: pcProcessBlock{}, + wantState: ¶ms{height: 5, items: []pcBlock{{"P1", 30}, {"P2", 31}}}, wantNextEvent: noOp, + }, + }, + }, + { + name: "blocks H+1 and H+2 present", + steps: []pcFsmMakeStateValues{ + { + currentState: ¶ms{items: []pcBlock{{"P1", 1}, {"P2", 2}}}, event: pcProcessBlock{}, + wantState: ¶ms{height: 1, items: []pcBlock{{"P2", 2}}, blocksSynced: 1}, + wantNextEvent: pcBlockProcessed{height: 1, peerID: "P1"}, + }, + }, + }, + { + name: "blocks H+1 and H+2 present after draining", + steps: []pcFsmMakeStateValues{ + { // some contiguous blocks - on stop check draining is set + currentState: ¶ms{items: []pcBlock{{"P1", 1}, {"P2", 2}, {"P1", 4}}}, event: pcStop{}, + wantState: ¶ms{items: []pcBlock{{"P1", 1}, {"P2", 2}, {"P1", 4}}, draining: true}, + wantNextEvent: noOp, + }, + { + event: pcProcessBlock{}, + wantState: ¶ms{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{}, + wantState: ¶ms{height: 1, items: []pcBlock{{"P2", 2}, {"P1", 4}}, blocksSynced: 1, draining: true}, + wantNextEvent: noOp, + wantErr: pcFinished{height: 1}, + }, + }, + }, + } + + executeProcessorTests(t, tests) +} + +func TestPcProcessBlockFailures(t *testing.T) { + tests := []testFields{ + { + name: "blocks H+1 and H+2 present - H+1 verification fails ", + steps: []pcFsmMakeStateValues{ + { + currentState: ¶ms{items: []pcBlock{{"P1", 1}, {"P2", 2}}, verBL: []int64{1}}, event: pcProcessBlock{}, + wantState: ¶ms{items: []pcBlock{{"P1", 1}, {"P2", 2}}, verBL: []int64{1}}, + wantNextEvent: pcBlockVerificationFailure{peerID: "P1", height: 1}, + }, + }, + }, + { + name: "blocks H+1 and H+2 present - H+1 applyBlock fails ", + steps: []pcFsmMakeStateValues{ + { + currentState: ¶ms{items: []pcBlock{{"P1", 1}, {"P2", 2}}, appBL: []int64{1}}, event: pcProcessBlock{}, + wantState: ¶ms{items: []pcBlock{{"P1", 1}, {"P2", 2}}, appBL: []int64{1}}, wantPanic: true, + }, + }, + }, + } + + executeProcessorTests(t, tests) +} + +func TestPcPeerError(t *testing.T) { + tests := []testFields{ + { + name: "peer not present", + steps: []pcFsmMakeStateValues{ + { + currentState: ¶ms{items: []pcBlock{{"P1", 1}, {"P2", 2}}}, event: &peerError{peerID: "P3"}, + wantState: ¶ms{items: []pcBlock{{"P1", 1}, {"P2", 2}}}, + wantNextEvent: noOp, + }, + }, + }, + { + name: "some blocks are from errored peer", + steps: []pcFsmMakeStateValues{ + { + currentState: ¶ms{items: []pcBlock{{"P1", 100}, {"P1", 99}, {"P2", 101}}}, event: &peerError{peerID: "P1"}, + wantState: ¶ms{items: []pcBlock{{"P2", 101}}}, + wantNextEvent: noOp, + }, + }, + }, + { + name: "all blocks are from errored peer", + steps: []pcFsmMakeStateValues{ + { + currentState: ¶ms{items: []pcBlock{{"P1", 100}, {"P1", 99}}}, event: &peerError{peerID: "P1"}, + wantState: ¶ms{}, + wantNextEvent: noOp, + }, + }, + }, + } + + executeProcessorTests(t, tests) +} + +func TestStop(t *testing.T) { + tests := []testFields{ + { + name: "no blocks", + steps: []pcFsmMakeStateValues{ + { + currentState: ¶ms{height: 100, items: []pcBlock{}, blocksSynced: 100}, event: pcStop{}, + wantState: ¶ms{height: 100, items: []pcBlock{}, blocksSynced: 100}, + wantNextEvent: noOp, + wantErr: pcFinished{height: 100, blocksSynced: 100}, + }, + }, + }, + { + name: "maxHeight+1 block present", + steps: []pcFsmMakeStateValues{ + { + currentState: ¶ms{height: 100, items: []pcBlock{{"P1", 101}}, blocksSynced: 100}, event: pcStop{}, + wantState: ¶ms{height: 100, items: []pcBlock{{"P1", 101}}, blocksSynced: 100}, + wantNextEvent: noOp, + wantErr: pcFinished{height: 100, blocksSynced: 100}, + }, + }, + }, + { + name: "more blocks present", + steps: []pcFsmMakeStateValues{ + { + currentState: ¶ms{height: 100, items: []pcBlock{{"P1", 101}, {"P1", 102}}, blocksSynced: 100}, event: pcStop{}, + wantState: ¶ms{height: 100, items: []pcBlock{{"P1", 101}, {"P1", 102}}, blocksSynced: 100, draining: true}, + wantNextEvent: noOp, + wantErr: nil, + }, + }, + }, + } + + executeProcessorTests(t, tests) +} diff --git a/blockchain/v2/reactor.go b/blockchain/v2/reactor.go index 25ec41224..8f7143083 100644 --- a/blockchain/v2/reactor.go +++ b/blockchain/v2/reactor.go @@ -69,6 +69,7 @@ func (r *Reactor) Start() { }() } +// 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() { diff --git a/blockchain/v2/schedule.go b/blockchain/v2/schedule.go index db3819b81..38c6c5d2d 100644 --- a/blockchain/v2/schedule.go +++ b/blockchain/v2/schedule.go @@ -164,6 +164,7 @@ func (sc *schedule) removePeer(peerID p2p.ID) error { return nil } +// TODO - keep track of highest height func (sc *schedule) setPeerHeight(peerID p2p.ID, height int64) error { peer, ok := sc.peers[peerID] if !ok { diff --git a/go.mod b/go.mod index f41d03358..536ab4445 100644 --- a/go.mod +++ b/go.mod @@ -25,9 +25,11 @@ require ( github.com/spf13/cobra v0.0.1 github.com/spf13/viper v1.4.0 github.com/stretchr/testify v1.4.0 + github.com/stumble/gorocksdb v0.0.3 // indirect github.com/tendermint/go-amino v0.14.1 github.com/tendermint/tm-db v0.2.0 golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2 golang.org/x/net v0.0.0-20190628185345-da137c7871d7 + golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a // indirect google.golang.org/grpc v1.24.0 ) diff --git a/go.sum b/go.sum index 4df7e0fac..102a99faa 100644 --- a/go.sum +++ b/go.sum @@ -39,6 +39,9 @@ github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZm github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= github.com/etcd-io/bbolt v1.3.3 h1:gSJmxrs37LgTqR/oyJBWok6k6SvXEUerFTbltIhXkBM= github.com/etcd-io/bbolt v1.3.3/go.mod h1:ZF2nL25h33cCyBtcyWeZ2/I3HQOfTP+0PIEvHjkjCrw= +github.com/facebookgo/ensure v0.0.0-20160127193407-b4ab57deab51/go.mod h1:Yg+htXGokKKdzcwhuNDwVvN+uBxDGXJ7G/VN1d8fa64= +github.com/facebookgo/stack v0.0.0-20160209184415-751773369052/go.mod h1:UbMTZqLaRiH3MsBH8va0n7s1pQYcu3uTb8G4tygF4Zg= +github.com/facebookgo/subset v0.0.0-20150612182917-8dac2c3c4870/go.mod h1:5tD+neXqOorC30/tWg0LCSkrqj/AR6gu8yY8/fpw1q0= github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8vw= github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g= github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= @@ -174,12 +177,11 @@ github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0 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/stumble/gorocksdb v0.0.3/go.mod h1:v6IHdFBXk5DJ1K4FZ0xi+eY737quiiBxYtSWXadLybY= github.com/syndtr/goleveldb v1.0.1-0.20190318030020-c3a204f8e965 h1:1oFLiOyVl+W7bnBzGhf7BbIv9loSFQcieWWYIjLqcAw= github.com/syndtr/goleveldb v1.0.1-0.20190318030020-c3a204f8e965/go.mod h1:9OrXJhf154huy1nPWmuSrkgjPUtUNhA+Zmy+6AESzuA= github.com/tendermint/go-amino v0.14.1 h1:o2WudxNfdLNBwMyl2dqOJxiro5rfrEaU0Ugs6offJMk= github.com/tendermint/go-amino v0.14.1/go.mod h1:i/UKE5Uocn+argJJBb12qTZsCDBcAYMbR92AaJVmKso= -github.com/tendermint/tm-db v0.1.1 h1:G3Xezy3sOk9+ekhjZ/kjArYIs1SmwV+1OUgNkj7RgV0= -github.com/tendermint/tm-db v0.1.1/go.mod h1:0cPKWu2Mou3IlxecH+MEUSYc1Ch537alLe6CpFrKzgw= github.com/tendermint/tm-db v0.2.0 h1:rJxgdqn6fIiVJZy4zLpY1qVlyD0TU6vhkT4kEf71TQQ= github.com/tendermint/tm-db v0.2.0/go.mod h1:0cPKWu2Mou3IlxecH+MEUSYc1Ch537alLe6CpFrKzgw= github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= @@ -220,6 +222,8 @@ golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5h golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/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-20190813064441-fde4db37ae7a h1:aYOabOQFp6Vj6W1F80affTUvO9UxmJRx8K0gsfABByQ= +golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/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/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= @@ -236,10 +240,6 @@ google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZi google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= google.golang.org/grpc v1.22.0 h1:J0UbZOIrCAl+fpTOf8YLs4dJo8L/owV4LYVtAXQoPkw= google.golang.org/grpc v1.22.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= -google.golang.org/grpc v1.23.0 h1:AzbTB6ux+okLTzP8Ru1Xs41C303zdcfEht7MQnYJt5A= -google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= -google.golang.org/grpc v1.23.1 h1:q4XQuHFC6I28BKZpo6IYyb3mNO+l7lSOxRuYTCiDfXk= -google.golang.org/grpc v1.23.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.24.0 h1:vb/1TCsVn3DcJlQ0Gs1yB1pKI6Do2/QNwxdKqmc/b0s= google.golang.org/grpc v1.24.0/go.mod h1:XDChyiUovWa60DnaeDeZmSW86xtLtjtZbwvSiRnRtcA= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=