diff --git a/CHANGELOG_PENDING.md b/CHANGELOG_PENDING.md
index bf41c1b5b..a2fc85ee0 100644
--- a/CHANGELOG_PENDING.md
+++ b/CHANGELOG_PENDING.md
@@ -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)
diff --git a/behaviour/reporter.go b/behaviour/reporter.go
index 96ce32994..1f16b9bb3 100644
--- a/behaviour/reporter.go
+++ b/behaviour/reporter.go
@@ -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,
}
diff --git a/blockchain/v0/reactor.go b/blockchain/v0/reactor.go
index c35a3e6a9..d47e892c2 100644
--- a/blockchain/v0/reactor.go
+++ b/blockchain/v0/reactor.go
@@ -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
diff --git a/blockchain/v1/reactor.go b/blockchain/v1/reactor.go
index c1932d406..1aba26b35 100644
--- a/blockchain/v1/reactor.go
+++ b/blockchain/v1/reactor.go
@@ -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()
}
diff --git a/blockchain/v1/reactor_test.go b/blockchain/v1/reactor_test.go
index 2add11df1..deb73ad6d 100644
--- a/blockchain/v1/reactor_test.go
+++ b/blockchain/v1/reactor_test.go
@@ -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
diff --git a/blockchain/v2/codec.go b/blockchain/v2/codec.go
new file mode 100644
index 000000000..f970d115f
--- /dev/null
+++ b/blockchain/v2/codec.go
@@ -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)
+}
diff --git a/blockchain/v2/io.go b/blockchain/v2/io.go
new file mode 100644
index 000000000..3db48c8c0
--- /dev/null
+++ b/blockchain/v2/io.go
@@ -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)
+}
diff --git a/blockchain/v2/metrics.go b/blockchain/v2/metrics.go
index d865e7360..c68ec6447 100644
--- a/blockchain/v2/metrics.go
+++ b/blockchain/v2/metrics.go
@@ -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{}
diff --git a/blockchain/v2/processor.go b/blockchain/v2/processor.go
index e33b36058..d6a2fe1e8 100644
--- a/blockchain/v2/processor.go
+++ b/blockchain/v2/processor.go
@@ -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
diff --git a/blockchain/v2/processor_context.go b/blockchain/v2/processor_context.go
index c4c8770cd..7e96a3a69 100644
--- a/blockchain/v2/processor_context.go
+++ b/blockchain/v2/processor_context.go
@@ -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
}
diff --git a/blockchain/v2/processor_test.go b/blockchain/v2/processor_test.go
index 61be23663..fc35c4c72 100644
--- a/blockchain/v2/processor_test.go
+++ b/blockchain/v2/processor_test.go
@@ -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: ¶ms{items: []pcBlock{{"P1", 1}, {"P2", 2}}},
+ event: scPeerError{peerID: "P2"},
+ wantState: ¶ms{items: []pcBlock{{"P1", 1}}},
+ wantNextEvent: noOp,
+ },
+ },
+ },
+ {
+ name: "error for unknown peer",
+ steps: []pcFsmMakeStateValues{
+ {
+ currentState: ¶ms{items: []pcBlock{{"P1", 1}, {"P2", 2}}},
+ event: scPeerError{peerID: "P3"},
+ wantState: ¶ms{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: ¶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) {
+func TestRProcessBlockSuccess(t *testing.T) {
tests := []testFields{
{
name: "noop - no blocks over current height",
steps: []pcFsmMakeStateValues{
{
- currentState: ¶ms{}, event: pcProcessBlock{},
+ currentState: ¶ms{}, event: rProcessBlock{},
wantState: ¶ms{}, wantNextEvent: noOp,
},
},
@@ -190,7 +175,7 @@ func TestPcProcessBlockSuccess(t *testing.T) {
name: "noop - high new blocks",
steps: []pcFsmMakeStateValues{
{
- currentState: ¶ms{height: 5, items: []pcBlock{{"P1", 30}, {"P2", 31}}}, event: pcProcessBlock{},
+ currentState: ¶ms{height: 5, items: []pcBlock{{"P1", 30}, {"P2", 31}}}, event: rProcessBlock{},
wantState: ¶ms{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: ¶ms{items: []pcBlock{{"P1", 1}, {"P2", 2}}}, event: pcProcessBlock{},
+ currentState: ¶ms{items: []pcBlock{{"P1", 1}, {"P2", 2}}}, event: rProcessBlock{},
wantState: ¶ms{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: ¶ms{items: []pcBlock{{"P1", 1}, {"P2", 2}, {"P1", 4}}}, event: pcStop{},
+ currentState: ¶ms{items: []pcBlock{{"P1", 1}, {"P2", 2}, {"P1", 4}}},
+ event: scFinishedEv{},
wantState: ¶ms{items: []pcBlock{{"P1", 1}, {"P2", 2}, {"P1", 4}}, draining: true},
wantNextEvent: noOp,
},
{
- event: pcProcessBlock{},
+ event: rProcessBlock{},
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{},
+ event: rProcessBlock{},
wantState: ¶ms{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: ¶ms{items: []pcBlock{{"P1", 1}, {"P2", 2}}, verBL: []int64{1}}, event: pcProcessBlock{},
+ currentState: ¶ms{items: []pcBlock{{"P1", 1}, {"P2", 2}}, verBL: []int64{1}}, event: rProcessBlock{},
wantState: ¶ms{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: ¶ms{items: []pcBlock{{"P1", 1}, {"P2", 2}}, appBL: []int64{1}}, event: pcProcessBlock{},
+ currentState: ¶ms{items: []pcBlock{{"P1", 1}, {"P2", 2}}, appBL: []int64{1}}, event: rProcessBlock{},
wantState: ¶ms{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: ¶ms{items: []pcBlock{{"P1", 1}, {"P1", 2}, {"P2", 3}}, verBL: []int64{1}},
- event: pcProcessBlock{},
- wantState: ¶ms{items: []pcBlock{{"P2", 3}}, verBL: []int64{1}},
+ currentState: ¶ms{height: 0, items: []pcBlock{{"P1", 1}, {"P1", 2}, {"P2", 3}},
+ verBL: []int64{1}}, event: rProcessBlock{},
+ wantState: ¶ms{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: ¶ms{items: []pcBlock{{"P1", 1}, {"P2", 2}, {"P2", 3}}, appBL: []int64{1}},
- event: pcProcessBlock{},
+ event: rProcessBlock{},
wantState: ¶ms{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: ¶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) {
+func TestScFinishedEv(t *testing.T) {
tests := []testFields{
{
name: "no blocks",
steps: []pcFsmMakeStateValues{
{
- currentState: ¶ms{height: 100, items: []pcBlock{}, blocksSynced: 100}, event: pcStop{},
+ currentState: ¶ms{height: 100, items: []pcBlock{}, blocksSynced: 100}, event: scFinishedEv{},
wantState: ¶ms{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: ¶ms{height: 100, items: []pcBlock{{"P1", 101}}, blocksSynced: 100}, event: pcStop{},
+ currentState: ¶ms{height: 100, items: []pcBlock{
+ {"P1", 101}}, blocksSynced: 100}, event: scFinishedEv{},
wantState: ¶ms{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: ¶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},
+ currentState: ¶ms{height: 100, items: []pcBlock{
+ {"P1", 101}, {"P1", 102}}, blocksSynced: 100}, event: scFinishedEv{},
+ wantState: ¶ms{height: 100, items: []pcBlock{
+ {"P1", 101}, {"P1", 102}}, blocksSynced: 100, draining: true},
wantNextEvent: noOp,
wantErr: nil,
},
diff --git a/blockchain/v2/reactor.go b/blockchain/v2/reactor.go
index 8f7143083..767e59819 100644
--- a/blockchain/v2/reactor.go
+++ b/blockchain/v2/reactor.go
@@ -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,
+ },
+ }
}
diff --git a/blockchain/v2/reactor_test.go b/blockchain/v2/reactor_test.go
index 46a2e60c6..10457cdb0 100644
--- a/blockchain/v2/reactor_test.go
+++ b/blockchain/v2/reactor_test.go
@@ -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
}
diff --git a/blockchain/v2/routine.go b/blockchain/v2/routine.go
index 897dd738c..1a883c3c4 100644
--- a/blockchain/v2/routine.go
+++ b/blockchain/v2/routine.go
@@ -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
}
diff --git a/blockchain/v2/scheduler.go b/blockchain/v2/scheduler.go
index ab3892dc5..3cf0b2468 100644
--- a/blockchain/v2/scheduler.go
+++ b/blockchain/v2/scheduler.go
@@ -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
}
diff --git a/blockchain/v2/scheduler_test.go b/blockchain/v2/scheduler_test.go
index a3a6db672..445ba51a7 100644
--- a/blockchain/v2/scheduler_test.go
+++ b/blockchain/v2/scheduler_test.go
@@ -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)
diff --git a/blockchain/v2/types.go b/blockchain/v2/types.go
index 836e87fd8..7a73728e4 100644
--- a/blockchain/v2/types.go
+++ b/blockchain/v2/types.go
@@ -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 {
diff --git a/consensus/reactor.go b/consensus/reactor.go
index 40868ff9d..0f2dad743 100644
--- a/consensus/reactor.go
+++ b/consensus/reactor.go
@@ -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
diff --git a/consensus/state.go b/consensus/state.go
index 05c70b652..d543c9c84 100644
--- a/consensus/state.go
+++ b/consensus/state.go
@@ -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
diff --git a/docs/.vuepress/config.js b/docs/.vuepress/config.js
index dc7a200ce..55613252f 100644
--- a/docs/.vuepress/config.js
+++ b/docs/.vuepress/config.js
@@ -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",
diff --git a/docs/architecture/adr-043-blockchain-riri-org.md b/docs/architecture/adr-043-blockchain-riri-org.md
index 8daef1817..6bb018f51 100644
--- a/docs/architecture/adr-043-blockchain-riri-org.md
+++ b/docs/architecture/adr-043-blockchain-riri-org.md
@@ -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`.

+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.
+
+
### Reactor changes in detail
diff --git a/docs/architecture/adr-051-double-signing-risk-reduction.md b/docs/architecture/adr-051-double-signing-risk-reduction.md
index 04ee41a67..ae663e8b5 100644
--- a/docs/architecture/adr-051-double-signing-risk-reduction.md
+++ b/docs/architecture/adr-051-double-signing-risk-reduction.md
@@ -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`
+ - `double_sign_check_height = {{ .Consensus.DoubleSignCheckHeight }}` in `config.toml`
- `tendermint node --double_sign_check_height` in cli
- State machine ignore checking procedure when `vote-check-height == 0`
diff --git a/docs/architecture/adr-052-tendermint-mode.md b/docs/architecture/adr-052-tendermint-mode.md
index edf217a06..acd5028b4 100644
--- a/docs/architecture/adr-052-tendermint-mode.md
+++ b/docs/architecture/adr-052-tendermint-mode.md
@@ -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`
+ - `mode = "{{ .BaseConfig.Mode }}"` in `config.toml`
- `tendermint node --mode validator` in cli
- fullnode | validator | seed (default: "fullnode")
- RPC modification
diff --git a/docs/architecture/img/blockchain-reactor-v2.png b/docs/architecture/img/blockchain-reactor-v2.png
index 086bf71bd..5f15333a0 100644
Binary files a/docs/architecture/img/blockchain-reactor-v2.png and b/docs/architecture/img/blockchain-reactor-v2.png differ
diff --git a/docs/architecture/img/blockchain-v2-channels.png b/docs/architecture/img/blockchain-v2-channels.png
new file mode 100644
index 000000000..69886da95
Binary files /dev/null and b/docs/architecture/img/blockchain-v2-channels.png differ
diff --git a/docs/package-lock.json b/docs/package-lock.json
index e5e653713..a5e8c0880 100644
--- a/docs/package-lock.json
+++ b/docs/package-lock.json
@@ -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",
diff --git a/docs/package.json b/docs/package.json
index 962531efd..8ce869057 100644
--- a/docs/package.json
+++ b/docs/package.json
@@ -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"
-}
+}
\ No newline at end of file
diff --git a/go.mod b/go.mod
index 0da5c8831..6ab6791c5 100644
--- a/go.mod
+++ b/go.mod
@@ -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
diff --git a/go.sum b/go.sum
index e6f6d994c..b0a96ac4e 100644
--- a/go.sum
+++ b/go.sum
@@ -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=
diff --git a/lite2/client.go b/lite2/client.go
index 717623cbb..2c487f9c9 100644
--- a/lite2/client.go
+++ b/lite2/client.go
@@ -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())
}
diff --git a/lite2/client_test.go b/lite2/client_test.go
index e2931f323..d8f44adc6 100644
--- a/lite2/client_test.go
+++ b/lite2/client_test.go
@@ -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)
+ }
+ })
}
}
diff --git a/lite2/test_helpers.go b/lite2/test_helpers.go
index 11074f8c0..cc1bf4eb9 100644
--- a/lite2/test_helpers.go
+++ b/lite2/test_helpers.go
@@ -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 {
diff --git a/lite2/verifier.go b/lite2/verifier.go
index d2f656e41..5491c8d91 100644
--- a/lite2/verifier.go
+++ b/lite2/verifier.go
@@ -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(),
)
}
diff --git a/lite2/verifier_test.go b/lite2/verifier_test.go
index 1625213cc..6e118d7ae 100644
--- a/lite2/verifier_test.go
+++ b/lite2/verifier_test.go
@@ -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):
diff --git a/rpc/client/rpc_test.go b/rpc/client/rpc_test.go
index 55c7755ac..c1418c816 100644
--- a/rpc/client/rpc_test.go
+++ b/rpc/client/rpc_test.go
@@ -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)
}
}
diff --git a/rpc/core/tx.go b/rpc/core/tx.go
index cd6306060..f894424b7 100644
--- a/rpc/core/tx.go
+++ b/rpc/core/tx.go
@@ -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
diff --git a/rpc/lib/server/ws_handler.go b/rpc/lib/server/ws_handler.go
index 548a244cb..07f424b48 100644
--- a/rpc/lib/server/ws_handler.go
+++ b/rpc/lib/server/ws_handler.go
@@ -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")))