From 1c568c290aab104157a053d421c95d9c1f3fdba1 Mon Sep 17 00:00:00 2001 From: tnasu Date: Wed, 1 Dec 2021 18:55:34 +0900 Subject: [PATCH 01/32] rpc/jsonrpc: Unmarshal RPCRequest correctly (bp #6191) (#6193) * rpc/jsonrpc: Unmarshal RPCRequest correctly (#6191) i.e. without double pointer. With double pointer, it was possible to submit `null` value, which will crash the server. ``` panic: runtime error: invalid memory address or nil pointer dereference [signal SIGSEGV: segmentation violation code=0x1 addr=0x0 pc=0x189ddc0] goroutine 1 [running]: github.com/tendermint/tendermint/rpc/jsonrpc/types.(*RPCRequest).UnmarshalJSON(0xc0000147e0, 0xc00029f201, 0x4, 0x1ff, 0x883baa0, 0xc0000147e0) /Users/anton/go/src/github.com/tendermint/tendermint/rpc/jsonrpc/types/types.go:70 +0x100 encoding/json.(*decodeState).literalStore(0xc000216bb0, 0xc00029f201, 0x4, 0x1ff, 0x1998800, 0xc0000147e0, 0x199, 0xc000231700, 0x10e0a5e, 0x197) /usr/local/Cellar/go/1.16/libexec/src/encoding/json/decode.go:860 +0x30ce encoding/json.(*decodeState).value(0xc000216bb0, 0x1998800, 0xc0000147e0, 0x199, 0x1998800, 0xc0000147e0) /usr/local/Cellar/go/1.16/libexec/src/encoding/json/decode.go:384 +0x40c encoding/json.(*decodeState).array(0xc000216bb0, 0x18df040, 0xc0001be540, 0x16, 0xc000216bd8, 0x10e405b) /usr/local/Cellar/go/1.16/libexec/src/encoding/json/decode.go:558 +0x365 encoding/json.(*decodeState).value(0xc000216bb0, 0x18df040, 0xc0001be540, 0x16, 0x16, 0x6e) /usr/local/Cellar/go/1.16/libexec/src/encoding/json/decode.go:360 +0x22f encoding/json.(*decodeState).unmarshal(0xc000216bb0, 0x18df040, 0xc0001be540, 0xc000216bd8, 0x0) /usr/local/Cellar/go/1.16/libexec/src/encoding/json/decode.go:180 +0x2c9 encoding/json.Unmarshal(0xc00029f200, 0x6, 0x200, 0x18df040, 0xc0001be540, 0x0, 0x0) /usr/local/Cellar/go/1.16/libexec/src/encoding/json/decode.go:107 +0x15d ``` (cherry picked from commit fe4e97afe0dc5f3afd5b550777d051b7c6c6f9a0) * fix conflict Co-authored-by: Anton Kaliaev --- rpc/jsonrpc/types/types.go | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/rpc/jsonrpc/types/types.go b/rpc/jsonrpc/types/types.go index 726cb401d..c9e3a4ef0 100644 --- a/rpc/jsonrpc/types/types.go +++ b/rpc/jsonrpc/types/types.go @@ -57,27 +57,31 @@ type RPCRequest struct { // UnmarshalJSON custom JSON unmarshalling due to jsonrpcid being string or int func (req *RPCRequest) UnmarshalJSON(data []byte) error { - unsafeReq := &struct { + unsafeReq := struct { JSONRPC string `json:"jsonrpc"` ID interface{} `json:"id,omitempty"` Method string `json:"method"` Params json.RawMessage `json:"params"` // must be map[string]interface{} or []interface{} }{} + err := json.Unmarshal(data, &unsafeReq) if err != nil { return err } + + if unsafeReq.ID == nil { // notification + return nil + } + req.JSONRPC = unsafeReq.JSONRPC req.Method = unsafeReq.Method req.Params = unsafeReq.Params - if unsafeReq.ID == nil { - return nil - } id, err := idFromInterface(unsafeReq.ID) if err != nil { return err } req.ID = id + return nil } From 9b3faee97471e1b0169b57790f92e43cf75da5a7 Mon Sep 17 00:00:00 2001 From: tnasu Date: Tue, 7 Dec 2021 13:14:15 +0900 Subject: [PATCH 02/32] Fix codecov for `rpc/jsonrpc: Unmarshal RPCRequest correctly (bp #6191) (#6193)` --- rpc/jsonrpc/types/types_test.go | 41 +++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/rpc/jsonrpc/types/types_test.go b/rpc/jsonrpc/types/types_test.go index 8434f208b..f1ee30da2 100644 --- a/rpc/jsonrpc/types/types_test.go +++ b/rpc/jsonrpc/types/types_test.go @@ -4,6 +4,7 @@ import ( "encoding/json" "errors" "fmt" + "reflect" "testing" "github.com/stretchr/testify/assert" @@ -81,3 +82,43 @@ func TestRPCError(t *testing.T) { Message: "Badness", })) } + +func TestRPCRequestUnmarshalJSON(t *testing.T) { + tcs := []struct { + assert bool + expected RPCRequest + }{ + { + assert: true, + expected: RPCRequest{ID: JSONRPCIntID(1), Method: "id int", Params: json.RawMessage(`{"p1":"v1"}`)}, + }, { + assert: true, + expected: RPCRequest{ID: JSONRPCStringID("s"), Method: "id string", Params: json.RawMessage(`{"p1":"v1"}`)}, + }, { + assert: true, + expected: RPCRequest{ID: JSONRPCStringID(""), Method: "id empty", Params: json.RawMessage(`{"p1":"v1"}`)}, + }, { + // can't treat nil as jsonrpcid + assert: false, + expected: RPCRequest{ID: nil, Method: "id nil", Params: json.RawMessage(`{"p1":"v1"}`)}, + }, { + assert: true, + expected: RPCRequest{ID: JSONRPCIntID(1), Method: "params null", Params: json.RawMessage(`null`)}, + }, { + // can't treat nil as json.RawMessage: the value of `nil` become "null" string + assert: false, + expected: RPCRequest{ID: JSONRPCIntID(1), Method: "params nil", Params: nil}, + }, + } + for _, tc := range tcs { + data, _ := json.Marshal(tc.expected) + actual := RPCRequest{} + json.Unmarshal(data, &actual) // nolint: errcheck + assert.Equal(t, reflect.DeepEqual(tc.expected, actual), tc.assert, + "expected:", tc.expected, "actual:", actual) + actual2 := RPCRequest{} + actual2.UnmarshalJSON(data) // nolint: errcheck + assert.Equal(t, reflect.DeepEqual(tc.expected, actual2), tc.assert, + "expected:", tc.expected, "actual2:", actual2) + } +} From 93235ac0887fd8876f588c8b367b1cdf9b2e5b1f Mon Sep 17 00:00:00 2001 From: tnasu Date: Wed, 1 Dec 2021 18:56:23 +0900 Subject: [PATCH 03/32] logs: cleanup (#6198) Co-authored-by: Marko --- consensus/state.go | 11 +++++------ mempool/clist_mempool.go | 12 +++--------- rpc/jsonrpc/server/http_json_handler.go | 2 +- 3 files changed, 9 insertions(+), 16 deletions(-) diff --git a/consensus/state.go b/consensus/state.go index 7984db10b..9f0420f41 100644 --- a/consensus/state.go +++ b/consensus/state.go @@ -1190,8 +1190,7 @@ func (cs *State) defaultDecideProposal(height int64, round int32) { cs.sendInternalMessage(msgInfo{&BlockPartMessage{cs.Height, cs.Round, part}, ""}) } - cs.Logger.Info("signed proposal", "height", height, "round", round, "proposal", proposal) - cs.Logger.Debug("signed proposal block", "block", block) + cs.Logger.Debug("signed proposal", "height", height, "round", round, "proposal", proposal) } else if !cs.replayMode { cs.Logger.Error("propose step; failed signing proposal", "height", height, "round", round, "err", err) } @@ -1719,7 +1718,7 @@ func (cs *State) finalizeCommit(height int64) { if err != nil { logger.Error("failed to prune blocks", "retain_height", retainHeight, "err", err) } else { - logger.Info("pruned blocks", "pruned", pruned, "retain_height", retainHeight) + logger.Debug("pruned blocks", "pruned", pruned, "retain_height", retainHeight) } } @@ -2070,7 +2069,7 @@ func (cs *State) tryAddVote(vote *types.Vote, peerID p2p.ID) (bool, error) { // report conflicting votes to the evidence pool cs.evpool.ReportConflictingVotes(voteErr.VoteA, voteErr.VoteB) - cs.Logger.Info( + cs.Logger.Debug( "found and sent conflicting votes to the evidence pool", "vote_a", voteErr.VoteA, "vote_b", voteErr.VoteB, @@ -2235,7 +2234,7 @@ func (cs *State) addVote(vote *types.Vote, peerID p2p.ID) (added bool, err error case tmproto.PrecommitType: precommits := cs.Votes.Precommits(vote.Round) - cs.Logger.Info("added vote to precommit", "vote", vote, "precommits", precommits.StringShort()) + cs.Logger.Debug("added vote to precommit", "vote", vote, "precommits", precommits.StringShort()) blockID, ok := precommits.TwoThirdsMajority() if ok { @@ -2339,7 +2338,7 @@ func (cs *State) signAddVote(msgType tmproto.SignedMsgType, hash []byte, header vote, err := cs.signVote(msgType, hash, header) if err == nil { cs.sendInternalMessage(msgInfo{&VoteMessage{vote}, ""}) - cs.Logger.Info("signed and pushed vote", "height", cs.Height, "round", cs.Round, "vote", vote) + cs.Logger.Debug("signed and pushed vote", "height", cs.Height, "round", cs.Round, "vote", vote) return vote } diff --git a/mempool/clist_mempool.go b/mempool/clist_mempool.go index f8161eb76..c88de1fde 100644 --- a/mempool/clist_mempool.go +++ b/mempool/clist_mempool.go @@ -505,7 +505,7 @@ func (mem *CListMempool) resCbFirstTime( } memTx.senders.Store(peerID, true) mem.addTx(memTx) - mem.logger.Info("Added good transaction", + mem.logger.Debug("added good transaction", "tx", txID(tx), "res", r, "height", memTx.height, @@ -545,7 +545,7 @@ func (mem *CListMempool) resCbRecheck(req *abci.Request, res *abci.Response) { // Good, nothing to do. } else { // Tx became invalidated due to newly committed block. - mem.logger.Info("tx is no longer valid", "tx", txID(tx), "res", r) + mem.logger.Debug("tx is no longer valid", "tx", txID(tx), "res", r) // NOTE: we remove tx from the cache because it might be good later mem.removeTx(tx, celem, true) } @@ -713,10 +713,8 @@ func (mem *CListMempool) Update( if err != nil { mem.logger.Error("error in proxyAppConn.BeginRecheckTxSync", "err", err) } - - mem.logger.Info("recheck txs", "numtxs", mem.Size(), "height", block.Height) + mem.logger.Debug("recheck txs", "numtxs", mem.Size(), "height", block.Height) mem.recheckTxs() - _, err = mem.proxyAppConn.EndRecheckTxSync(abci.RequestEndRecheckTx{Height: block.Height}) if err != nil { mem.logger.Error("error in proxyAppConn.EndRecheckTxSync", "err", err) @@ -726,10 +724,6 @@ func (mem *CListMempool) Update( recheckTimeMs := float64(recheckEndTime-recheckStartTime) / 1000000 mem.metrics.RecheckTime.Set(recheckTimeMs) - - // At this point, mem.txs are being rechecked. - // mem.recheckCursor re-scans mem.txs and possibly removes some txs. - // Before mem.Reap(), we should wait for mem.recheckCursor to be nil. } // notify there're some txs left. diff --git a/rpc/jsonrpc/server/http_json_handler.go b/rpc/jsonrpc/server/http_json_handler.go index 8866a5780..163175c4b 100644 --- a/rpc/jsonrpc/server/http_json_handler.go +++ b/rpc/jsonrpc/server/http_json_handler.go @@ -98,7 +98,7 @@ func makeJSONRPCHandler(funcMap map[string]*RPCFunc, logger log.Logger) http.Han args = append(args, fnArgs...) } returns := rpcFunc.f.Call(args) - logger.Info("HTTPJSONRPC", "method", request.Method, "args", args, "returns", returns) + logger.Debug("HTTPJSONRPC", "method", request.Method, "args", args, "returns", returns) result, err := unreflectResult(returns) if err != nil { responses = append(responses, types.RPCInternalError(request.ID, err)) From b22dbc3f8d4f3b8855e55fd37d6b8e8da58a3049 Mon Sep 17 00:00:00 2001 From: tnasu Date: Mon, 6 Dec 2021 15:47:01 +0900 Subject: [PATCH 04/32] Fix codecov for `logs: cleanup (#6198)` --- abci/types/application.go | 2 + abci/types/mocks/application.go | 228 ++++++++++++++++++++++++++++++++ consensus/common_test.go | 45 ++++--- consensus/state_test.go | 38 +++++- 4 files changed, 293 insertions(+), 20 deletions(-) create mode 100644 abci/types/mocks/application.go diff --git a/abci/types/application.go b/abci/types/application.go index 37ecae3d9..2e85344f0 100644 --- a/abci/types/application.go +++ b/abci/types/application.go @@ -4,6 +4,8 @@ import ( context "golang.org/x/net/context" ) +//go:generate mockery --case underscore --name Application + type CheckTxCallback func(ResponseCheckTx) // Application is an interface that enables any finite, deterministic state machine diff --git a/abci/types/mocks/application.go b/abci/types/mocks/application.go new file mode 100644 index 000000000..de9eb77cb --- /dev/null +++ b/abci/types/mocks/application.go @@ -0,0 +1,228 @@ +// Code generated by mockery v2.9.4. DO NOT EDIT. + +package mocks + +import ( + types "github.com/line/ostracon/abci/types" + mock "github.com/stretchr/testify/mock" +) + +// Application is an autogenerated mock type for the Application type +type Application struct { + mock.Mock +} + +// ApplySnapshotChunk provides a mock function with given fields: _a0 +func (_m *Application) ApplySnapshotChunk(_a0 types.RequestApplySnapshotChunk) types.ResponseApplySnapshotChunk { + ret := _m.Called(_a0) + + var r0 types.ResponseApplySnapshotChunk + if rf, ok := ret.Get(0).(func(types.RequestApplySnapshotChunk) types.ResponseApplySnapshotChunk); ok { + r0 = rf(_a0) + } else { + r0 = ret.Get(0).(types.ResponseApplySnapshotChunk) + } + + return r0 +} + +// BeginBlock provides a mock function with given fields: _a0 +func (_m *Application) BeginBlock(_a0 types.RequestBeginBlock) types.ResponseBeginBlock { + ret := _m.Called(_a0) + + var r0 types.ResponseBeginBlock + if rf, ok := ret.Get(0).(func(types.RequestBeginBlock) types.ResponseBeginBlock); ok { + r0 = rf(_a0) + } else { + r0 = ret.Get(0).(types.ResponseBeginBlock) + } + + return r0 +} + +// BeginRecheckTx provides a mock function with given fields: _a0 +func (_m *Application) BeginRecheckTx(_a0 types.RequestBeginRecheckTx) types.ResponseBeginRecheckTx { + ret := _m.Called(_a0) + + var r0 types.ResponseBeginRecheckTx + if rf, ok := ret.Get(0).(func(types.RequestBeginRecheckTx) types.ResponseBeginRecheckTx); ok { + r0 = rf(_a0) + } else { + r0 = ret.Get(0).(types.ResponseBeginRecheckTx) + } + + return r0 +} + +// CheckTxAsync provides a mock function with given fields: _a0, _a1 +func (_m *Application) CheckTxAsync(_a0 types.RequestCheckTx, _a1 types.CheckTxCallback) { + _m.Called(_a0, _a1) +} + +// CheckTxSync provides a mock function with given fields: _a0 +func (_m *Application) CheckTxSync(_a0 types.RequestCheckTx) types.ResponseCheckTx { + ret := _m.Called(_a0) + + var r0 types.ResponseCheckTx + if rf, ok := ret.Get(0).(func(types.RequestCheckTx) types.ResponseCheckTx); ok { + r0 = rf(_a0) + } else { + r0 = ret.Get(0).(types.ResponseCheckTx) + } + + return r0 +} + +// Commit provides a mock function with given fields: +func (_m *Application) Commit() types.ResponseCommit { + ret := _m.Called() + + var r0 types.ResponseCommit + if rf, ok := ret.Get(0).(func() types.ResponseCommit); ok { + r0 = rf() + } else { + r0 = ret.Get(0).(types.ResponseCommit) + } + + return r0 +} + +// DeliverTx provides a mock function with given fields: _a0 +func (_m *Application) DeliverTx(_a0 types.RequestDeliverTx) types.ResponseDeliverTx { + ret := _m.Called(_a0) + + var r0 types.ResponseDeliverTx + if rf, ok := ret.Get(0).(func(types.RequestDeliverTx) types.ResponseDeliverTx); ok { + r0 = rf(_a0) + } else { + r0 = ret.Get(0).(types.ResponseDeliverTx) + } + + return r0 +} + +// EndBlock provides a mock function with given fields: _a0 +func (_m *Application) EndBlock(_a0 types.RequestEndBlock) types.ResponseEndBlock { + ret := _m.Called(_a0) + + var r0 types.ResponseEndBlock + if rf, ok := ret.Get(0).(func(types.RequestEndBlock) types.ResponseEndBlock); ok { + r0 = rf(_a0) + } else { + r0 = ret.Get(0).(types.ResponseEndBlock) + } + + return r0 +} + +// EndRecheckTx provides a mock function with given fields: _a0 +func (_m *Application) EndRecheckTx(_a0 types.RequestEndRecheckTx) types.ResponseEndRecheckTx { + ret := _m.Called(_a0) + + var r0 types.ResponseEndRecheckTx + if rf, ok := ret.Get(0).(func(types.RequestEndRecheckTx) types.ResponseEndRecheckTx); ok { + r0 = rf(_a0) + } else { + r0 = ret.Get(0).(types.ResponseEndRecheckTx) + } + + return r0 +} + +// Info provides a mock function with given fields: _a0 +func (_m *Application) Info(_a0 types.RequestInfo) types.ResponseInfo { + ret := _m.Called(_a0) + + var r0 types.ResponseInfo + if rf, ok := ret.Get(0).(func(types.RequestInfo) types.ResponseInfo); ok { + r0 = rf(_a0) + } else { + r0 = ret.Get(0).(types.ResponseInfo) + } + + return r0 +} + +// InitChain provides a mock function with given fields: _a0 +func (_m *Application) InitChain(_a0 types.RequestInitChain) types.ResponseInitChain { + ret := _m.Called(_a0) + + var r0 types.ResponseInitChain + if rf, ok := ret.Get(0).(func(types.RequestInitChain) types.ResponseInitChain); ok { + r0 = rf(_a0) + } else { + r0 = ret.Get(0).(types.ResponseInitChain) + } + + return r0 +} + +// ListSnapshots provides a mock function with given fields: _a0 +func (_m *Application) ListSnapshots(_a0 types.RequestListSnapshots) types.ResponseListSnapshots { + ret := _m.Called(_a0) + + var r0 types.ResponseListSnapshots + if rf, ok := ret.Get(0).(func(types.RequestListSnapshots) types.ResponseListSnapshots); ok { + r0 = rf(_a0) + } else { + r0 = ret.Get(0).(types.ResponseListSnapshots) + } + + return r0 +} + +// LoadSnapshotChunk provides a mock function with given fields: _a0 +func (_m *Application) LoadSnapshotChunk(_a0 types.RequestLoadSnapshotChunk) types.ResponseLoadSnapshotChunk { + ret := _m.Called(_a0) + + var r0 types.ResponseLoadSnapshotChunk + if rf, ok := ret.Get(0).(func(types.RequestLoadSnapshotChunk) types.ResponseLoadSnapshotChunk); ok { + r0 = rf(_a0) + } else { + r0 = ret.Get(0).(types.ResponseLoadSnapshotChunk) + } + + return r0 +} + +// OfferSnapshot provides a mock function with given fields: _a0 +func (_m *Application) OfferSnapshot(_a0 types.RequestOfferSnapshot) types.ResponseOfferSnapshot { + ret := _m.Called(_a0) + + var r0 types.ResponseOfferSnapshot + if rf, ok := ret.Get(0).(func(types.RequestOfferSnapshot) types.ResponseOfferSnapshot); ok { + r0 = rf(_a0) + } else { + r0 = ret.Get(0).(types.ResponseOfferSnapshot) + } + + return r0 +} + +// Query provides a mock function with given fields: _a0 +func (_m *Application) Query(_a0 types.RequestQuery) types.ResponseQuery { + ret := _m.Called(_a0) + + var r0 types.ResponseQuery + if rf, ok := ret.Get(0).(func(types.RequestQuery) types.ResponseQuery); ok { + r0 = rf(_a0) + } else { + r0 = ret.Get(0).(types.ResponseQuery) + } + + return r0 +} + +// SetOption provides a mock function with given fields: _a0 +func (_m *Application) SetOption(_a0 types.RequestSetOption) types.ResponseSetOption { + ret := _m.Called(_a0) + + var r0 types.ResponseSetOption + if rf, ok := ret.Get(0).(func(types.RequestSetOption) types.ResponseSetOption); ok { + r0 = rf(_a0) + } else { + r0 = ret.Get(0).(types.ResponseSetOption) + } + + return r0 +} diff --git a/consensus/common_test.go b/consensus/common_test.go index 01f5bb11e..b69d4ec8d 100644 --- a/consensus/common_test.go +++ b/consensus/common_test.go @@ -466,36 +466,42 @@ func loadPrivValidator(config *cfg.Config) *privval.FilePV { } func randState(nValidators int) (*State, []*validatorStub) { - return randStateWithVoterParams(nValidators, types.DefaultVoterParams()) + return randStateWithVoterParamsWithApp( + nValidators, + types.DefaultVoterParams(), + counter.NewApplication(true)) } -func randStateWithVoterParams(nValidators int, voterParams *types.VoterParams) (*State, []*validatorStub) { - // Get State - state, privVals := randGenesisState(nValidators, false, 10, voterParams) - state.LastProofHash = []byte{2} - - vss := make([]*validatorStub, nValidators) - - cs := newState(state, privVals[0], counter.NewApplication(true)) - - for i := 0; i < nValidators; i++ { - vss[i] = newValidatorStub(privVals[i], int32(i)) - } - // since cs1 starts at 1 - incrementHeight(vss[1:]...) +func randStateWithVoterParams( + nValidators int, + voterParams *types.VoterParams) (*State, []*validatorStub) { + return randStateWithVoterParamsWithApp( + nValidators, + voterParams, + counter.NewApplication(true)) +} - return cs, vss +func randStateWithVoterParamsWithPersistentKVStoreApp( + nValidators int, + voterParams *types.VoterParams, + testName string) (*State, []*validatorStub) { + return randStateWithVoterParamsWithApp( + nValidators, + voterParams, + newPersistentKVStoreWithPath(path.Join(config.DBDir(), testName))) } -func randStateWithVoterParamsWithApp(nValidators int, voterParams *types.VoterParams, testName string) ( - *State, []*validatorStub) { +func randStateWithVoterParamsWithApp( + nValidators int, + voterParams *types.VoterParams, + app abci.Application) (*State, []*validatorStub) { + // Get State state, privVals := randGenesisState(nValidators, false, 10, voterParams) state.LastProofHash = []byte{2} vss := make([]*validatorStub, nValidators) - app := newPersistentKVStoreWithPath(path.Join(config.DBDir(), testName)) cs := newState(state, privVals[0], app) for i := 0; i < nValidators; i++ { @@ -795,6 +801,7 @@ func DefaultTestLoggers() TestLoggers { log.TestingLogger(), log.TestingLogger(), log.TestingLogger(), log.TestingLogger(), log.TestingLogger()) } +// nolint:unused func NopTestLoggers() TestLoggers { return NewTestLoggers( log.NewNopLogger(), log.NewNopLogger(), log.NewNopLogger(), log.NewNopLogger(), log.NewNopLogger()) diff --git a/consensus/state_test.go b/consensus/state_test.go index 3b6949c4f..74b08788b 100644 --- a/consensus/state_test.go +++ b/consensus/state_test.go @@ -7,6 +7,10 @@ import ( "testing" "time" + abci "github.com/line/ostracon/abci/types" + "github.com/line/ostracon/abci/types/mocks" + "github.com/stretchr/testify/mock" + "github.com/line/ostracon/abci/example/kvstore" "github.com/stretchr/testify/assert" @@ -2354,7 +2358,7 @@ func addValidator(cs *State, vssMap map[string]*validatorStub, height int64) { func TestStateAllVoterToSelectedVoter(t *testing.T) { startValidators := 5 - cs, vss := randStateWithVoterParamsWithApp(startValidators, &types.VoterParams{ + cs, vss := randStateWithVoterParamsWithPersistentKVStoreApp(startValidators, &types.VoterParams{ VoterElectionThreshold: int32(startValidators), MaxTolerableByzantinePercentage: 20}, "TestStateAllVoterToSelectedVoter") @@ -2448,3 +2452,35 @@ func TestStateAllVoterToSelectedVoter(t *testing.T) { ensureNewRound(newRoundCh, height+1, 0) } } + +func TestPruneBlocks(t *testing.T) { + // Based behaviour is counter.Application + mockApp := &mocks.Application{} + mockApp.On("BeginBlock", mock.Anything).Return(abci.ResponseBeginBlock{}) + mockApp.On("EndBlock", mock.Anything).Return(abci.ResponseEndBlock{}) + mockApp.On("BeginRecheckTx", mock.Anything).Return(abci.ResponseBeginRecheckTx{Code: abci.CodeTypeOK}) + mockApp.On("EndRecheckTx", mock.Anything).Return(abci.ResponseEndRecheckTx{Code: abci.CodeTypeOK}) + // Mocking behaviour to response `RetainHeight` for pruneBlocks + mockApp.On("Commit", mock.Anything, mock.Anything).Return(abci.ResponseCommit{RetainHeight: 1}) + + cs1, vss := randStateWithVoterParamsWithApp( + 4, types.DefaultVoterParams(), mockApp) + height, round := cs1.Height, cs1.Round + + newRoundCh := subscribe(cs1.eventBus, types.EventQueryNewRound) + proposalCh := subscribe(cs1.eventBus, types.EventQueryCompleteProposal) + + startTestRound(cs1, height, round) + + // Wait for new round so proposer is set. + ensureNewRound(newRoundCh, height, round) + + // Wait for complete proposal. + ensureNewProposal(proposalCh, height, round) + + rs := cs1.GetRoundState() + signAddVotes(cs1, tmproto.PrecommitType, rs.ProposalBlock.Hash(), rs.ProposalBlockParts.Header(), vss[1:]...) + + // Wait for new round so next validator is set. + ensureNewRound(newRoundCh, height+1, 0) +} From 9243a45436029882ff51b559dbd88d775dccee31 Mon Sep 17 00:00:00 2001 From: tnasu Date: Wed, 1 Dec 2021 18:56:28 +0900 Subject: [PATCH 05/32] mempool/rpc: log grooming (bp #6201) (#6203) --- rpc/jsonrpc/server/http_json_handler.go | 3 ++- rpc/jsonrpc/server/http_server.go | 8 +++++--- test/e2e/runner/rpc.go | 8 ++++++-- 3 files changed, 13 insertions(+), 6 deletions(-) diff --git a/rpc/jsonrpc/server/http_json_handler.go b/rpc/jsonrpc/server/http_json_handler.go index 163175c4b..b8e0e5dcf 100644 --- a/rpc/jsonrpc/server/http_json_handler.go +++ b/rpc/jsonrpc/server/http_json_handler.go @@ -97,8 +97,8 @@ func makeJSONRPCHandler(funcMap map[string]*RPCFunc, logger log.Logger) http.Han } args = append(args, fnArgs...) } + returns := rpcFunc.f.Call(args) - logger.Debug("HTTPJSONRPC", "method", request.Method, "args", args, "returns", returns) result, err := unreflectResult(returns) if err != nil { responses = append(responses, types.RPCInternalError(request.ID, err)) @@ -106,6 +106,7 @@ func makeJSONRPCHandler(funcMap map[string]*RPCFunc, logger log.Logger) http.Han } responses = append(responses, types.NewRPCSuccessResponse(request.ID, result)) } + if len(responses) > 0 { WriteRPCResponseHTTP(w, responses...) } diff --git a/rpc/jsonrpc/server/http_server.go b/rpc/jsonrpc/server/http_server.go index b1b8fdb22..323b17afc 100644 --- a/rpc/jsonrpc/server/http_server.go +++ b/rpc/jsonrpc/server/http_server.go @@ -202,9 +202,11 @@ func RecoverAndLogHandler(handler http.Handler, logger log.Logger) http.Handler if rww.Status == -1 { rww.Status = 200 } - logger.Info("Served RPC HTTP response", - "method", r.Method, "url", r.URL, - "status", rww.Status, "duration", durationMS, + logger.Debug("served RPC HTTP response", + "method", r.Method, + "url", r.URL, + "status", rww.Status, + "duration", durationMS, "remoteAddr", r.RemoteAddr, ) }() diff --git a/test/e2e/runner/rpc.go b/test/e2e/runner/rpc.go index ef6adc799..369f95d14 100644 --- a/test/e2e/runner/rpc.go +++ b/test/e2e/runner/rpc.go @@ -90,18 +90,22 @@ func waitForNode(node *e2e.Node, height int64, timeout time.Duration) (*rpctypes // waitForAllNodes waits for all nodes to become available and catch up to the given block height. func waitForAllNodes(testnet *e2e.Testnet, height int64, timeout time.Duration) (int64, error) { - lastHeight := int64(0) + var lastHeight int64 + for _, node := range testnet.Nodes { if node.Mode == e2e.ModeSeed { continue } - status, err := waitForNode(node, height, 20*time.Second) + + status, err := waitForNode(node, height, timeout) if err != nil { return 0, err } + if status.SyncInfo.LatestBlockHeight > lastHeight { lastHeight = status.SyncInfo.LatestBlockHeight } } + return lastHeight, nil } From d1ca5e984a02bd7cf3b00ee9579519230c40df41 Mon Sep 17 00:00:00 2001 From: tnasu Date: Wed, 1 Dec 2021 18:56:30 +0900 Subject: [PATCH 06/32] indexer: remove info log (#6194) Co-authored-by: Aleksandr Bezobchuk Co-authored-by: Marko --- state/execution.go | 2 +- state/txindex/indexer_service.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/state/execution.go b/state/execution.go index 4295efa59..65010480f 100644 --- a/state/execution.go +++ b/state/execution.go @@ -200,7 +200,7 @@ func (blockExec *BlockExecutor) ApplyBlock( return state, 0, err } if len(validatorUpdates) > 0 { - blockExec.logger.Info("updates to validators", "updates", types.ValidatorListString(validatorUpdates)) + blockExec.logger.Debug("updates to validators", "updates", types.ValidatorListString(validatorUpdates)) } // Update the state with the block and responses. diff --git a/state/txindex/indexer_service.go b/state/txindex/indexer_service.go index 308995954..3886c4140 100644 --- a/state/txindex/indexer_service.go +++ b/state/txindex/indexer_service.go @@ -67,7 +67,7 @@ func (is *IndexerService) OnStart() error { if err = is.idr.AddBatch(batch); err != nil { is.Logger.Error("Failed to index block", "height", height, "err", err) } else { - is.Logger.Info("Indexed block", "height", height) + is.Logger.Debug("indexed block", "height", height) } } }() From 4fccda26268cd119b4fb33f72269a54891239c55 Mon Sep 17 00:00:00 2001 From: tnasu Date: Wed, 1 Dec 2021 18:56:33 +0900 Subject: [PATCH 07/32] e2e: add benchmarking functionality (bp #6210) (#6216) --- test/e2e/README.md | 10 ++ test/e2e/runner/benchmark.go | 192 +++++++++++++++++++++++++++++++++++ test/e2e/runner/main.go | 57 +++++++++++ test/e2e/runner/wait.go | 8 +- 4 files changed, 266 insertions(+), 1 deletion(-) create mode 100644 test/e2e/runner/benchmark.go diff --git a/test/e2e/README.md b/test/e2e/README.md index 600c59148..c98549e4d 100644 --- a/test/e2e/README.md +++ b/test/e2e/README.md @@ -50,6 +50,8 @@ The test runner has the following stages, which can also be executed explicitly * `cleanup`: removes configuration files and Docker containers/networks. +Auxiliary commands: + * `logs`: outputs all node logs. * `tail`: tails (follows) node logs until cancelled. @@ -119,3 +121,11 @@ Docker does not enable IPv6 by default. To do so, enter the following in "fixed-cidr-v6": "2001:db8:1::/64" } ``` + +## Benchmarking testnets + +It is also possible to run a simple benchmark on a testnet. This is done through the `benchmark` command. This manages the entire process: setting up the environment, starting the test net, waiting for a considerable amount of blocks to be used (currently 100), and then returning the following metrics from the sample of the blockchain: + +- Average time to produce a block +- Standard deviation of producing a block +- Minimum and maximum time to produce a block diff --git a/test/e2e/runner/benchmark.go b/test/e2e/runner/benchmark.go new file mode 100644 index 000000000..b491d60af --- /dev/null +++ b/test/e2e/runner/benchmark.go @@ -0,0 +1,192 @@ +package main + +import ( + "context" + "fmt" + "math" + "time" + + e2e "github.com/line/ostracon/test/e2e/pkg" + "github.com/line/ostracon/types" +) + +// Benchmark is a simple function for fetching, calculating and printing +// the following metrics: +// 1. Average block production time +// 2. Block interval standard deviation +// 3. Max block interval (slowest block) +// 4. Min block interval (fastest block) +// +// Metrics are based of the `benchmarkLength`, the amount of consecutive blocks +// sampled from in the testnet +func Benchmark(testnet *e2e.Testnet, benchmarkLength int64) error { + block, _, err := waitForHeight(testnet, 0) + if err != nil { + return err + } + + logger.Info("Beginning benchmark period...", "height", block.Height) + + // wait for the length of the benchmark period in blocks to pass. We allow 5 seconds for each block + // which should be sufficient. + waitingTime := time.Duration(benchmarkLength*5) * time.Second + endHeight, err := waitForAllNodes(testnet, block.Height+benchmarkLength, waitingTime) + if err != nil { + return err + } + + logger.Info("Ending benchmark period", "height", endHeight) + + // fetch a sample of blocks + blocks, err := fetchBlockChainSample(testnet, benchmarkLength) + if err != nil { + return err + } + + // slice into time intervals and collate data + timeIntervals := splitIntoBlockIntervals(blocks) + testnetStats := extractTestnetStats(timeIntervals) + testnetStats.startHeight = blocks[0].Header.Height + testnetStats.endHeight = blocks[len(blocks)-1].Header.Height + + // print and return + logger.Info(testnetStats.String()) + return nil +} + +type testnetStats struct { + startHeight int64 + endHeight int64 + + // average time to produce a block + mean time.Duration + // standard deviation of block production + std float64 + // longest time to produce a block + max time.Duration + // shortest time to produce a block + min time.Duration +} + +func (t *testnetStats) String() string { + return fmt.Sprintf(`Benchmarked from height %v to %v + Mean Block Interval: %v + Standard Deviation: %f + Max Block Interval: %v + Min Block Interval: %v + `, + t.startHeight, + t.endHeight, + t.mean, + t.std, + t.max, + t.min, + ) +} + +// fetchBlockChainSample waits for `benchmarkLength` amount of blocks to pass, fetching +// all of the headers for these blocks from an archive node and returning it. +func fetchBlockChainSample(testnet *e2e.Testnet, benchmarkLength int64) ([]*types.BlockMeta, error) { + var blocks []*types.BlockMeta + + // Find the first archive node + archiveNode := testnet.ArchiveNodes()[0] + c, err := archiveNode.Client() + if err != nil { + return nil, err + } + + // find the latest height + ctx := context.Background() + s, err := c.Status(ctx) + if err != nil { + return nil, err + } + + to := s.SyncInfo.LatestBlockHeight + from := to - benchmarkLength + 1 + if from <= testnet.InitialHeight { + return nil, fmt.Errorf("tesnet was unable to reach required height for benchmarking (latest height %d)", to) + } + + // Fetch blocks + for from < to { + // fetch the blockchain metas. Currently we can only fetch 20 at a time + resp, err := c.BlockchainInfo(ctx, from, min(from+19, to)) + if err != nil { + return nil, err + } + + blockMetas := resp.BlockMetas + // we receive blocks in descending order so we have to add them in reverse + for i := len(blockMetas) - 1; i >= 0; i-- { + if blockMetas[i].Header.Height != from { + return nil, fmt.Errorf("node gave us another header. Wanted %d, got %d", + from, + blockMetas[i].Header.Height, + ) + } + from++ + blocks = append(blocks, blockMetas[i]) + } + } + + return blocks, nil +} + +func splitIntoBlockIntervals(blocks []*types.BlockMeta) []time.Duration { + intervals := make([]time.Duration, len(blocks)-1) + lastTime := blocks[0].Header.Time + for i, block := range blocks { + // skip the first block + if i == 0 { + continue + } + + intervals[i-1] = block.Header.Time.Sub(lastTime) + lastTime = block.Header.Time + } + return intervals +} + +func extractTestnetStats(intervals []time.Duration) testnetStats { + var ( + sum, mean time.Duration + std float64 + max = intervals[0] + min = intervals[0] + ) + + for _, interval := range intervals { + sum += interval + + if interval > max { + max = interval + } + + if interval < min { + min = interval + } + } + mean = sum / time.Duration(len(intervals)) + + for _, interval := range intervals { + diff := (interval - mean).Seconds() + std += math.Pow(diff, 2) + } + std = math.Sqrt(std / float64(len(intervals))) + + return testnetStats{ + mean: mean, + std: std, + max: max, + min: min, + } +} + +func min(a, b int64) int64 { + if a > b { + return b + } + return a +} diff --git a/test/e2e/runner/main.go b/test/e2e/runner/main.go index d701a5712..cfb55e4f8 100644 --- a/test/e2e/runner/main.go +++ b/test/e2e/runner/main.go @@ -204,6 +204,63 @@ func NewCLI() *CLI { }, }) + cli.root.AddCommand(&cobra.Command{ + Use: "benchmark", + Short: "Benchmarks testnet", + Long: `Benchmarks the following metrics: + Mean Block Interval + Standard Deviation + Min Block Interval + Max Block Interval +over a 100 block sampling period. + +Does not run any perbutations. + `, + RunE: func(cmd *cobra.Command, args []string) error { + if err := Cleanup(cli.testnet); err != nil { + return err + } + if err := Setup(cli.testnet); err != nil { + return err + } + + chLoadResult := make(chan error) + ctx, loadCancel := context.WithCancel(context.Background()) + defer loadCancel() + go func() { + err := Load(ctx, cli.testnet) + if err != nil { + logger.Error(fmt.Sprintf("Transaction load failed: %v", err.Error())) + } + chLoadResult <- err + }() + + if err := Start(cli.testnet); err != nil { + return err + } + + if err := Wait(cli.testnet, 5); err != nil { // allow some txs to go through + return err + } + + // we benchmark performance over the next 100 blocks + if err := Benchmark(cli.testnet, 100); err != nil { + return err + } + + loadCancel() + if err := <-chLoadResult; err != nil { + return err + } + + if err := Cleanup(cli.testnet); err != nil { + return err + } + + return nil + }, + }) + return cli } diff --git a/test/e2e/runner/wait.go b/test/e2e/runner/wait.go index 9bd877173..d49b0e723 100644 --- a/test/e2e/runner/wait.go +++ b/test/e2e/runner/wait.go @@ -20,9 +20,15 @@ func Wait(testnet *e2e.Testnet, blocks int64) error { // WaitUntil waits until a given height has been reached. func WaitUntil(testnet *e2e.Testnet, height int64) error { logger.Info(fmt.Sprintf("Waiting for all nodes to reach height %v...", height)) - _, err := waitForAllNodes(testnet, height, 20*time.Second) + _, err := waitForAllNodes(testnet, height, waitingTime(len(testnet.Nodes))) if err != nil { return err } return nil } + +// waitingTime estimates how long it should take for a node to reach the height. +// More nodes in a network implies we may expect a slower network and may have to wait longer. +func waitingTime(nodes int) time.Duration { + return time.Duration(20+(nodes*2)) * time.Second +} From c6b50452721d99d2634b7ffe33ba53c166e66ab5 Mon Sep 17 00:00:00 2001 From: tnasu Date: Wed, 1 Dec 2021 18:56:35 +0900 Subject: [PATCH 08/32] note: add nondeterministic note to events (#6220) (#6225) Since events are not hashed into the header they can be non deterministic. Changing an event is not consensus breaking. Will update docs in the spec (cherry picked from commit 884d4d525299e4d43ab881ac19062501c1e09ddf) Co-authored-by: Marko --- proto/ostracon/abci/types.proto | 2 +- types/block.go | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/proto/ostracon/abci/types.proto b/proto/ostracon/abci/types.proto index 0819058fc..631a59d58 100644 --- a/proto/ostracon/abci/types.proto +++ b/proto/ostracon/abci/types.proto @@ -234,7 +234,7 @@ message ResponseDeliverTx { int64 gas_wanted = 5 [json_name = "gas_wanted"]; int64 gas_used = 6 [json_name = "gas_used"]; repeated Event events = 7 - [(gogoproto.nullable) = false, (gogoproto.jsontag) = "events,omitempty"]; + [(gogoproto.nullable) = false, (gogoproto.jsontag) = "events,omitempty"]; // nondeterministic string codespace = 8; } diff --git a/types/block.go b/types/block.go index 03442b8ac..0adb88deb 100644 --- a/types/block.go +++ b/types/block.go @@ -355,6 +355,7 @@ type Header struct { ConsensusHash tmbytes.HexBytes `json:"consensus_hash"` // consensus params for current block AppHash tmbytes.HexBytes `json:"app_hash"` // state after txs from the previous block // root hash of all results from the txs from the previous block + // see `deterministicResponseDeliverTx` to understand which parts of a tx is hashed into here LastResultsHash tmbytes.HexBytes `json:"last_results_hash"` // consensus info From 1843507ab7a0453eb0d53d63834902e6abdad00f Mon Sep 17 00:00:00 2001 From: tnasu Date: Wed, 1 Dec 2021 18:56:44 +0900 Subject: [PATCH 09/32] use error.Is to check for nondeterminstic vote error type (#6237) (#6239) (cherry picked from commit bf8cce83db15ee2645924799a4b2dfba260788dc) Co-authored-by: Callum Waters --- consensus/state.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/consensus/state.go b/consensus/state.go index 9f0420f41..e4a106929 100644 --- a/consensus/state.go +++ b/consensus/state.go @@ -2076,7 +2076,7 @@ func (cs *State) tryAddVote(vote *types.Vote, peerID p2p.ID) (bool, error) { ) return added, err - } else if err == types.ErrVoteNonDeterministicSignature { + } else if errors.Is(err, types.ErrVoteNonDeterministicSignature) { cs.Logger.Debug("vote has non-deterministic signature", "err", err) } else { // Either From 896ad949a2980bf4ca39dc518968e197e7121dbc Mon Sep 17 00:00:00 2001 From: tnasu Date: Wed, 1 Dec 2021 18:56:53 +0900 Subject: [PATCH 10/32] rpc/jsonrpc/server: return an error in WriteRPCResponseHTTP(Error) (bp #6204) (#6230) * rpc/jsonrpc/server: return an error in WriteRPCResponseHTTP(Error) (#6204) instead of panicking Closes #5529 (cherry picked from commit 00b952416836cb568ee904903d30b35a6178bad1) * resolve conflicts * fix linting * fix conflict Co-authored-by: Anton Kaliaev Co-authored-by: Marko Baricevic --- blockchain/v2/reactor_test.go | 5 ++- crypto/merkle/proof_value.go | 4 +- p2p/conn/secret_connection.go | 1 - rpc/jsonrpc/server/http_json_handler.go | 27 ++++++------- rpc/jsonrpc/server/http_server.go | 50 ++++++++++++------------- rpc/jsonrpc/server/http_server_test.go | 10 +++-- rpc/jsonrpc/server/http_uri_handler.go | 30 +++++++++------ 7 files changed, 68 insertions(+), 59 deletions(-) diff --git a/blockchain/v2/reactor_test.go b/blockchain/v2/reactor_test.go index 3cf94c1b0..bfd549a2b 100644 --- a/blockchain/v2/reactor_test.go +++ b/blockchain/v2/reactor_test.go @@ -60,19 +60,22 @@ func (mp mockPeer) Get(string) interface{} { return struct{}{} } func (mp mockPeer) String() string { return fmt.Sprintf("%v", mp.id) } -//nolint:unused +// nolint:unused // ignore type mockBlockStore struct { blocks map[int64]*types.Block } +// nolint:unused // ignore func (ml *mockBlockStore) Height() int64 { return int64(len(ml.blocks)) } +// nolint:unused // ignore func (ml *mockBlockStore) LoadBlock(height int64) *types.Block { return ml.blocks[height] } +// nolint:unused // ignore func (ml *mockBlockStore) SaveBlock(block *types.Block, part *types.PartSet, commit *types.Commit) { ml.blocks[block.Height] = block } diff --git a/crypto/merkle/proof_value.go b/crypto/merkle/proof_value.go index 428f29943..49f22ee94 100644 --- a/crypto/merkle/proof_value.go +++ b/crypto/merkle/proof_value.go @@ -85,8 +85,8 @@ func (op ValueOp) Run(args [][]byte) ([][]byte, error) { bz := new(bytes.Buffer) // Wrap to hash the KVPair. - encodeByteSlice(bz, op.key) //nolint: errcheck // does not error - encodeByteSlice(bz, vhash) //nolint: errcheck // does not error + encodeByteSlice(bz, op.key) // nolint: errcheck // does not error + encodeByteSlice(bz, vhash) // nolint: errcheck // does not error kvhash := leafHash(bz.Bytes()) if !bytes.Equal(kvhash, op.Proof.LeafHash) { diff --git a/p2p/conn/secret_connection.go b/p2p/conn/secret_connection.go index ac8fe1109..78b128ae7 100644 --- a/p2p/conn/secret_connection.go +++ b/p2p/conn/secret_connection.go @@ -275,7 +275,6 @@ func (sc *SecretConnection) Read(data []byte) (n int, err error) { } // Implements net.Conn -// nolint func (sc *SecretConnection) Close() error { return sc.conn.Close() } func (sc *SecretConnection) LocalAddr() net.Addr { return sc.conn.(net.Conn).LocalAddr() } func (sc *SecretConnection) RemoteAddr() net.Addr { return sc.conn.(net.Conn).RemoteAddr() } diff --git a/rpc/jsonrpc/server/http_json_handler.go b/rpc/jsonrpc/server/http_json_handler.go index b8e0e5dcf..1e8178052 100644 --- a/rpc/jsonrpc/server/http_json_handler.go +++ b/rpc/jsonrpc/server/http_json_handler.go @@ -21,14 +21,12 @@ func makeJSONRPCHandler(funcMap map[string]*RPCFunc, logger log.Logger) http.Han return func(w http.ResponseWriter, r *http.Request) { b, err := ioutil.ReadAll(r.Body) if err != nil { - WriteRPCResponseHTTPError( - w, - http.StatusBadRequest, - types.RPCInvalidRequestError( - nil, - fmt.Errorf("error reading request body: %w", err), - ), + res := types.RPCInvalidRequestError(nil, + fmt.Errorf("error reading request body: %w", err), ) + if wErr := WriteRPCResponseHTTPError(w, http.StatusBadRequest, res); wErr != nil { + logger.Error("failed to write response", "res", res, "err", wErr) + } return } @@ -48,13 +46,10 @@ func makeJSONRPCHandler(funcMap map[string]*RPCFunc, logger log.Logger) http.Han // next, try to unmarshal as a single request var request types.RPCRequest if err := json.Unmarshal(b, &request); err != nil { - WriteRPCResponseHTTPError( - w, - http.StatusInternalServerError, - types.RPCParseError( - fmt.Errorf("error unmarshalling request: %w", err), - ), - ) + res := types.RPCParseError(fmt.Errorf("error unmarshaling request: %w", err)) + if wErr := WriteRPCResponseHTTPError(w, http.StatusInternalServerError, res); wErr != nil { + logger.Error("failed to write response", "res", res, "err", wErr) + } return } requests = []types.RPCRequest{request} @@ -108,7 +103,9 @@ func makeJSONRPCHandler(funcMap map[string]*RPCFunc, logger log.Logger) http.Han } if len(responses) > 0 { - WriteRPCResponseHTTP(w, responses...) + if wErr := WriteRPCResponseHTTP(w, responses...); wErr != nil { + logger.Error("failed to write responses", "res", responses, "err", wErr) + } } } } diff --git a/rpc/jsonrpc/server/http_server.go b/rpc/jsonrpc/server/http_server.go index 323b17afc..c2497de0c 100644 --- a/rpc/jsonrpc/server/http_server.go +++ b/rpc/jsonrpc/server/http_server.go @@ -94,30 +94,32 @@ func ServeTLS( return err } -// WriteRPCResponseHTTPError marshals res as JSON and writes it to w. +// WriteRPCResponseHTTPError marshals res as JSON (with indent) and writes it +// to w. // -// Panics if it can't Marshal res or write to w. +// source: https://www.jsonrpc.org/historical/json-rpc-over-http.html func WriteRPCResponseHTTPError( w http.ResponseWriter, httpCode int, res types.RPCResponse, -) { +) error { + if res.Error == nil { + panic("tried to write http error response without RPC error") + } + jsonBytes, err := json.MarshalIndent(res, "", " ") if err != nil { - panic(err) + return fmt.Errorf("json marshal: %w", err) } w.Header().Set("Content-Type", "application/json") w.WriteHeader(httpCode) - if _, err := w.Write(jsonBytes); err != nil { - panic(err) - } + _, err = w.Write(jsonBytes) + return err } -// WriteRPCResponseHTTP marshals res as JSON and writes it to w. -// -// Panics if it can't Marshal res or write to w. -func WriteRPCResponseHTTP(w http.ResponseWriter, res ...types.RPCResponse) { +// WriteRPCResponseHTTP marshals res as JSON (with indent) and writes it to w. +func WriteRPCResponseHTTP(w http.ResponseWriter, res ...types.RPCResponse) error { var v interface{} if len(res) == 1 { v = res[0] @@ -127,13 +129,12 @@ func WriteRPCResponseHTTP(w http.ResponseWriter, res ...types.RPCResponse) { jsonBytes, err := json.MarshalIndent(v, "", " ") if err != nil { - panic(err) + return fmt.Errorf("json marshal: %w", err) } w.Header().Set("Content-Type", "application/json") w.WriteHeader(200) - if _, err := w.Write(jsonBytes); err != nil { - panic(err) - } + _, err = w.Write(jsonBytes) + return err } //----------------------------------------------------------------------------- @@ -171,7 +172,9 @@ func RecoverAndLogHandler(handler http.Handler, logger log.Logger) http.Handler // If RPCResponse if res, ok := e.(types.RPCResponse); ok { - WriteRPCResponseHTTP(rww, res) + if wErr := WriteRPCResponseHTTP(rww, res); wErr != nil { + logger.Error("failed to write response", "res", res, "err", wErr) + } } else { // Panics can contain anything, attempt to normalize it as an error. var err error @@ -185,15 +188,12 @@ func RecoverAndLogHandler(handler http.Handler, logger log.Logger) http.Handler default: } - logger.Error( - "Panic in RPC HTTP handler", "err", e, "stack", - string(debug.Stack()), - ) - WriteRPCResponseHTTPError( - rww, - http.StatusInternalServerError, - types.RPCInternalError(types.JSONRPCIntID(-1), err), - ) + logger.Error("panic in RPC HTTP handler", "err", e, "stack", string(debug.Stack())) + + res := types.RPCInternalError(types.JSONRPCIntID(-1), err) + if wErr := WriteRPCResponseHTTPError(rww, http.StatusInternalServerError, res); wErr != nil { + logger.Error("failed to write response", "res", res, "err", wErr) + } } } diff --git a/rpc/jsonrpc/server/http_server_test.go b/rpc/jsonrpc/server/http_server_test.go index 3eeea24e6..890d0d299 100644 --- a/rpc/jsonrpc/server/http_server_test.go +++ b/rpc/jsonrpc/server/http_server_test.go @@ -112,7 +112,8 @@ func TestWriteRPCResponseHTTP(t *testing.T) { // one argument w := httptest.NewRecorder() - WriteRPCResponseHTTP(w, types.NewRPCSuccessResponse(id, &sampleResult{"hello"})) + err := WriteRPCResponseHTTP(w, types.NewRPCSuccessResponse(id, &sampleResult{"hello"})) + require.NoError(t, err) resp := w.Result() body, err := ioutil.ReadAll(resp.Body) _ = resp.Body.Close() @@ -129,9 +130,10 @@ func TestWriteRPCResponseHTTP(t *testing.T) { // multiple arguments w = httptest.NewRecorder() - WriteRPCResponseHTTP(w, + err = WriteRPCResponseHTTP(w, types.NewRPCSuccessResponse(id, &sampleResult{"hello"}), types.NewRPCSuccessResponse(id, &sampleResult{"world"})) + require.NoError(t, err) resp = w.Result() body, err = ioutil.ReadAll(resp.Body) _ = resp.Body.Close() @@ -159,9 +161,11 @@ func TestWriteRPCResponseHTTP(t *testing.T) { func TestWriteRPCResponseHTTPError(t *testing.T) { w := httptest.NewRecorder() - WriteRPCResponseHTTPError(w, + err := WriteRPCResponseHTTPError( + w, http.StatusInternalServerError, types.RPCInternalError(types.JSONRPCIntID(-1), errors.New("foo"))) + require.NoError(t, err) resp := w.Result() body, err := ioutil.ReadAll(resp.Body) _ = resp.Body.Close() diff --git a/rpc/jsonrpc/server/http_uri_handler.go b/rpc/jsonrpc/server/http_uri_handler.go index e2c3c9c37..350a23ffd 100644 --- a/rpc/jsonrpc/server/http_uri_handler.go +++ b/rpc/jsonrpc/server/http_uri_handler.go @@ -25,8 +25,10 @@ func makeHTTPHandler(rpcFunc *RPCFunc, logger log.Logger) func(http.ResponseWrit // Exception for websocket endpoints if rpcFunc.ws { return func(w http.ResponseWriter, r *http.Request) { - WriteRPCResponseHTTPError(w, http.StatusNotFound, - types.RPCMethodNotFoundError(dummyID)) + res := types.RPCMethodNotFoundError(dummyID) + if wErr := WriteRPCResponseHTTPError(w, http.StatusNotFound, res); wErr != nil { + logger.Error("failed to write response", "res", res, "err", wErr) + } } } @@ -39,14 +41,12 @@ func makeHTTPHandler(rpcFunc *RPCFunc, logger log.Logger) func(http.ResponseWrit fnArgs, err := httpParamsToArgs(rpcFunc, r) if err != nil { - WriteRPCResponseHTTPError( - w, - http.StatusInternalServerError, - types.RPCInvalidParamsError( - dummyID, - fmt.Errorf("error converting http params to arguments: %w", err), - ), + res := types.RPCInvalidParamsError(dummyID, + fmt.Errorf("error converting http params to arguments: %w", err), ) + if wErr := WriteRPCResponseHTTPError(w, http.StatusInternalServerError, res); wErr != nil { + logger.Error("failed to write response", "res", res, "err", wErr) + } return } args = append(args, fnArgs...) @@ -56,11 +56,17 @@ func makeHTTPHandler(rpcFunc *RPCFunc, logger log.Logger) func(http.ResponseWrit logger.Debug("HTTPRestRPC", "method", r.URL.Path, "args", args, "returns", returns) result, err := unreflectResult(returns) if err != nil { - WriteRPCResponseHTTPError(w, http.StatusInternalServerError, - types.RPCInternalError(dummyID, err)) + if err := WriteRPCResponseHTTPError(w, http.StatusInternalServerError, + types.RPCInternalError(dummyID, err)); err != nil { + logger.Error("failed to write response", "res", result, "err", err) + return + } + return + } + if err := WriteRPCResponseHTTP(w, types.NewRPCSuccessResponse(dummyID, result)); err != nil { + logger.Error("failed to write response", "res", result, "err", err) return } - WriteRPCResponseHTTP(w, types.NewRPCSuccessResponse(dummyID, result)) } } From 647a2e0c2d687683899f615a1c4999acf2620a04 Mon Sep 17 00:00:00 2001 From: tnasu Date: Wed, 8 Dec 2021 11:41:16 +0900 Subject: [PATCH 11/32] Fix codecov for `rpc/jsonrpc/server: return an error in WriteRPCResponseHTTP(Error) (bp #6204) (#6230)` --- blockchain/v2/reactor_test.go | 3 - crypto/merkle/proof_value.go | 4 +- rpc/jsonrpc/server/common_test.go | 114 +++++++++++++++++++ rpc/jsonrpc/server/http_json_handler_test.go | 24 ++++ rpc/jsonrpc/server/http_server_test.go | 96 ++++++++++++++++ rpc/jsonrpc/server/http_uri_handler_test.go | 68 +++++++++++ 6 files changed, 304 insertions(+), 5 deletions(-) create mode 100644 rpc/jsonrpc/server/common_test.go create mode 100644 rpc/jsonrpc/server/http_uri_handler_test.go diff --git a/blockchain/v2/reactor_test.go b/blockchain/v2/reactor_test.go index bfd549a2b..6dfacf49b 100644 --- a/blockchain/v2/reactor_test.go +++ b/blockchain/v2/reactor_test.go @@ -65,17 +65,14 @@ type mockBlockStore struct { blocks map[int64]*types.Block } -// nolint:unused // ignore func (ml *mockBlockStore) Height() int64 { return int64(len(ml.blocks)) } -// nolint:unused // ignore func (ml *mockBlockStore) LoadBlock(height int64) *types.Block { return ml.blocks[height] } -// nolint:unused // ignore func (ml *mockBlockStore) SaveBlock(block *types.Block, part *types.PartSet, commit *types.Commit) { ml.blocks[block.Height] = block } diff --git a/crypto/merkle/proof_value.go b/crypto/merkle/proof_value.go index 49f22ee94..428f29943 100644 --- a/crypto/merkle/proof_value.go +++ b/crypto/merkle/proof_value.go @@ -85,8 +85,8 @@ func (op ValueOp) Run(args [][]byte) ([][]byte, error) { bz := new(bytes.Buffer) // Wrap to hash the KVPair. - encodeByteSlice(bz, op.key) // nolint: errcheck // does not error - encodeByteSlice(bz, vhash) // nolint: errcheck // does not error + encodeByteSlice(bz, op.key) //nolint: errcheck // does not error + encodeByteSlice(bz, vhash) //nolint: errcheck // does not error kvhash := leafHash(bz.Bytes()) if !bytes.Equal(kvhash, op.Proof.LeafHash) { diff --git a/rpc/jsonrpc/server/common_test.go b/rpc/jsonrpc/server/common_test.go new file mode 100644 index 000000000..27520dbdc --- /dev/null +++ b/rpc/jsonrpc/server/common_test.go @@ -0,0 +1,114 @@ +package server + +import ( + "encoding/json" + "errors" + "fmt" + "net/http" + "strconv" + + "github.com/line/ostracon/libs/log" + "github.com/line/ostracon/rpc/jsonrpc/types" +) + +var ( + TestJSONIntID = types.JSONRPCIntID(-1) + TestRPCError = &types.RPCError{} + TestRawMSG = json.RawMessage(`{"p1":"v1"}`) + TestText = "foo" + ErrFoo = errors.New(TestText) + + TestRPCFunc = NewRPCFunc( + func(ctx *types.Context, s string, i int) (string, error) { return TestText, nil }, "s,i") + TestRPCErrorFunc = NewRPCFunc( + func(ctx *types.Context, s string, i int) (string, error) { return "", ErrFoo }, "s,i") + TestWSRPCFunc = NewWSRPCFunc( + func(ctx *types.Context, s string, i int) (string, error) { return TestText, nil }, "s,i") + + TestFuncMap = map[string]*RPCFunc{"c": TestRPCFunc} + TestGoodBody = `{"jsonrpc": "2.0", "method": "c", "id": "0", "params": null}` + TestBadParams = `{"jsonrpc": "2.0", "method": "c", "id": "0", "params": "s=a,i=b"}` +) + +type FailManager struct { + counter int + failedCounter int + throwPanic bool +} + +func (fm *FailManager) checkAndDo( + encounter func() (int, error), + throwing func(), +) (int, error) { + if fm.counter == fm.failedCounter { + fmt.Println("FailManager:do encounter") + return encounter() + } + fm.counter++ + if fm.throwPanic { + fmt.Println("FailManager:do throwing") + throwing() + } + return 0, nil +} + +type FailedWriteResponseWriter struct { + header http.Header + fm *FailManager + code int + error error +} + +func NewFailedWriteResponseWriter() FailedWriteResponseWriter { + return FailedWriteResponseWriter{ + header: make(http.Header), + fm: &FailManager{}, + code: -1, + error: fmt.Errorf("error"), + } +} +func (frw FailedWriteResponseWriter) Header() http.Header { + return frw.header +} +func (frw FailedWriteResponseWriter) Write(buf []byte) (int, error) { + fmt.Println("FailedWriteResponseWriter:" + strconv.Itoa(frw.fm.counter) + ":" + string(buf)) + return frw.fm.checkAndDo( + func() (int, error) { + return frw.code, frw.error + }, + func() { + res := types.RPCResponse{} + res.UnmarshalJSON(buf) // nolint: errcheck + panic(res) + }, + ) +} +func (frw FailedWriteResponseWriter) WriteHeader(code int) { + frw.header.Set(http.StatusText(code), strconv.Itoa(code)) +} + +type FailedLogger struct { + fm *FailManager +} + +func NewFailedLogger() FailedLogger { + return FailedLogger{ + fm: &FailManager{}, + } +} +func (l *FailedLogger) Info(msg string, keyvals ...interface{}) { + fmt.Println("FailedLogger.Info:" + msg) +} +func (l *FailedLogger) Debug(msg string, keyvals ...interface{}) { + fmt.Println("FailedLogger.Debug:" + msg) +} +func (l *FailedLogger) Error(msg string, keyvals ...interface{}) { + fmt.Println("FailedLogger.Error:" + strconv.Itoa(l.fm.counter) + ":" + msg) + l.fm.checkAndDo( // nolint: errcheck + func() (int, error) { panic(l.fm.counter) }, + func() {}, + ) +} +func (l *FailedLogger) With(keyvals ...interface{}) log.Logger { + return l +} diff --git a/rpc/jsonrpc/server/http_json_handler_test.go b/rpc/jsonrpc/server/http_json_handler_test.go index 8928ee6a7..2d46a6c6f 100644 --- a/rpc/jsonrpc/server/http_json_handler_test.go +++ b/rpc/jsonrpc/server/http_json_handler_test.go @@ -6,6 +6,7 @@ import ( "io/ioutil" "net/http" "net/http/httptest" + "strconv" "strings" "testing" @@ -227,3 +228,26 @@ func TestUnknownRPCPath(t *testing.T) { require.Equal(t, http.StatusNotFound, res.StatusCode, "should always return 404") res.Body.Close() } + +func TestMakeJSONRPCHandler_Unmarshal_WriteRPCResponseHTTPError_error(t *testing.T) { + handlerFunc := makeJSONRPCHandler(nil, log.TestingLogger()) + // json.Unmarshal error + req, _ := http.NewRequest("GET", "http://localhost/", strings.NewReader("hoge")) + // WriteRPCResponseHTTPError error + rec := NewFailedWriteResponseWriter() + handlerFunc.ServeHTTP(rec, req) + assert.Equal(t, + strconv.Itoa(http.StatusInternalServerError), + rec.Header().Get(http.StatusText(http.StatusInternalServerError))) +} + +func TestMakeJSONRPCHandler_last_WriteRPCResponseHTTP_error(t *testing.T) { + handlerFunc := makeJSONRPCHandler(TestFuncMap, log.TestingLogger()) + req, _ := http.NewRequest("GET", "http://localhost/", strings.NewReader(TestGoodBody)) + // WriteRPCResponseHTTP error + rec := NewFailedWriteResponseWriter() + handlerFunc.ServeHTTP(rec, req) + assert.Equal(t, + strconv.Itoa(http.StatusOK), + rec.Header().Get(http.StatusText(http.StatusOK))) +} diff --git a/rpc/jsonrpc/server/http_server_test.go b/rpc/jsonrpc/server/http_server_test.go index 890d0d299..f52d2c83c 100644 --- a/rpc/jsonrpc/server/http_server_test.go +++ b/rpc/jsonrpc/server/http_server_test.go @@ -8,6 +8,8 @@ import ( "net" "net/http" "net/http/httptest" + "strconv" + "strings" "sync" "sync/atomic" "testing" @@ -182,3 +184,97 @@ func TestWriteRPCResponseHTTPError(t *testing.T) { } }`, string(body)) } +func TestWriteRPCResponseHTTP_MarshalIndent_error(t *testing.T) { + w := NewFailedWriteResponseWriter() + result, _ := TestRawMSG.MarshalJSON() + // json.MarshalIndent error + err := WriteRPCResponseHTTP(w, + types.RPCResponse{Result: result[1:]}) // slice json for error + require.Error(t, err) + assert.NotEqual(t, w.error, err) +} + +func TestWriteRPCResponseHTTP_Write_error(t *testing.T) { + w := NewFailedWriteResponseWriter() + result, _ := TestRawMSG.MarshalJSON() + // w.Write error + err := WriteRPCResponseHTTP(w, + types.NewRPCSuccessResponse(TestJSONIntID, result)) + require.Error(t, err) + assert.Equal(t, w.error, err) +} + +func TestWriteRPCResponseHTTPError_MarshallIndent_error(t *testing.T) { + w := NewFailedWriteResponseWriter() + // json.MarshalIndent error + result, _ := TestRawMSG.MarshalJSON() + err := WriteRPCResponseHTTPError(w, http.StatusInternalServerError, + types.RPCResponse{Result: result[1:], Error: TestRPCError}) // slice json for error + require.Error(t, err) + assert.NotEqual(t, w.error, err) +} + +func TestWriteRPCResponseHTTPError_Write_error(t *testing.T) { + w := NewFailedWriteResponseWriter() + // w.Write error + err := WriteRPCResponseHTTPError(w, http.StatusInternalServerError, + types.RPCInternalError(TestJSONIntID, ErrFoo)) + require.Error(t, err) + assert.Equal(t, w.error, err) +} + +func TestWriteRPCResponseHTTPError_rerErrorIsNil_panic(t *testing.T) { + w := NewFailedWriteResponseWriter() + // panic: res.Error == nil + defer func() { + e := recover() + assert.True(t, e != nil) + }() + result, _ := TestRawMSG.MarshalJSON() + WriteRPCResponseHTTPError(w, http.StatusInternalServerError, types.RPCResponse{Result: result}) // nolint: errcheck +} + +func TestRecoverAndLogHandler_RPCResponseOK_WriteRPCResponseHTTPError_error(t *testing.T) { + // RPCResponse == ok and WriteRPCResponseHTTPError is error + handlerFunc := makeJSONRPCHandler(TestFuncMap, log.TestingLogger()) + handler := RecoverAndLogHandler(handlerFunc, log.TestingLogger()) + assert.NotNil(t, handler) + req, _ := http.NewRequest("GET", "http://localhost/", strings.NewReader(TestGoodBody)) + // throwing and encounter + rec := NewFailedWriteResponseWriter() + rec.fm.throwPanic = true + rec.fm.failedCounter = 1 + handler.ServeHTTP(rec, req) + assert.Equal(t, + strconv.Itoa(http.StatusOK), + rec.Header().Get(http.StatusText(http.StatusOK))) +} + +func TestRecoverAndLogHandler_RPCResponseNG_WriteRPCResponseHTTPError_error(t *testing.T) { + // RPCResponse != ok and WriteRPCResponseHTTPError is error + handler := RecoverAndLogHandler(nil, log.TestingLogger()) + assert.NotNil(t, handler) + req, _ := http.NewRequest("GET", "http://localhost/", strings.NewReader(TestGoodBody)) + // encounter + rec := NewFailedWriteResponseWriter() + handler.ServeHTTP(rec, req) + assert.Equal(t, + strconv.Itoa(http.StatusInternalServerError), + rec.Header().Get(http.StatusText(http.StatusInternalServerError))) +} + +func TestRecoverAndLogHandler_RPCResponseNG_WriteRPCResponseHTTPError_error_panic(t *testing.T) { + // RPCResponse != ok and WriteRPCResponseHTTPError is error and logger.Error is panic on 2nd times + // encounter + logger := NewFailedLogger() + logger.fm.failedCounter = 1 + handler := RecoverAndLogHandler(nil, &logger) + assert.NotNil(t, handler) + req, _ := http.NewRequest("GET", "http://localhost/", strings.NewReader(TestGoodBody)) + // encounter + rec := NewFailedWriteResponseWriter() + handler.ServeHTTP(rec, req) + assert.Equal(t, + strconv.Itoa(http.StatusInternalServerError), + rec.Header().Get(http.StatusText(http.StatusInternalServerError))) +} diff --git a/rpc/jsonrpc/server/http_uri_handler_test.go b/rpc/jsonrpc/server/http_uri_handler_test.go new file mode 100644 index 000000000..6111d91af --- /dev/null +++ b/rpc/jsonrpc/server/http_uri_handler_test.go @@ -0,0 +1,68 @@ +package server + +import ( + "net/http" + "net/http/httptest" + "strconv" + "strings" + "testing" + + "github.com/line/ostracon/libs/log" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestMakeHTTPHandler(t *testing.T) { + handlerFunc := makeHTTPHandler(TestRPCFunc, log.TestingLogger()) + req, _ := http.NewRequest("GET", "http://localhost/", strings.NewReader(TestGoodBody)) + rec := httptest.NewRecorder() + handlerFunc(rec, req) + res := rec.Result() + require.Equal(t, http.StatusOK, res.StatusCode) + res.Body.Close() +} + +func TestMakeHTTPHandler_WS_WriteRPCResponseHTTPError_error(t *testing.T) { + handlerFunc := makeHTTPHandler(TestWSRPCFunc, log.TestingLogger()) + req, _ := http.NewRequest("GET", "http://localhost/", nil) + rec := NewFailedWriteResponseWriter() + handlerFunc(rec, req) + assert.Equal(t, + strconv.Itoa(http.StatusNotFound), + rec.Header().Get(http.StatusText(http.StatusNotFound))) +} + +func TestMakeHTTPHandler_httpParamsToArgs_WriteRPCResponseHTTPError_error(t *testing.T) { + handlerFunc := makeHTTPHandler(TestRPCFunc, log.TestingLogger()) + // httpParamsToArgs error + req, _ := http.NewRequest("GET", "http://localhost/c?s=1", nil) + // WriteRPCResponseHTTPError error + rec := NewFailedWriteResponseWriter() + handlerFunc(rec, req) + assert.Equal(t, + strconv.Itoa(http.StatusInternalServerError), + rec.Header().Get(http.StatusText(http.StatusInternalServerError))) +} + +func TestMakeHTTPHandler_unreflectResult_WriteRPCResponseHTTPError_error(t *testing.T) { + // unreflectResult error + handlerFunc := makeHTTPHandler(TestRPCErrorFunc, log.TestingLogger()) + req, _ := http.NewRequest("GET", "http://localhost/", nil) + // WriteRPCResponseHTTPError error + rec := NewFailedWriteResponseWriter() + handlerFunc(rec, req) + assert.Equal(t, + strconv.Itoa(http.StatusInternalServerError), + rec.Header().Get(http.StatusText(http.StatusInternalServerError))) +} + +func TestMakeHTTPHandler_last_WriteRPCResponseHTTP_error(t *testing.T) { + handlerFunc := makeHTTPHandler(TestRPCFunc, log.TestingLogger()) + req, _ := http.NewRequest("GET", "http://localhost/", strings.NewReader(TestGoodBody)) + // WriteRPCResponseHTTP error + rec := NewFailedWriteResponseWriter() + handlerFunc(rec, req) + assert.Equal(t, + strconv.Itoa(http.StatusOK), + rec.Header().Get(http.StatusText(http.StatusOK))) +} From d56369c10c37d74d4e7b3982e696963c8f3dbce3 Mon Sep 17 00:00:00 2001 From: tnasu Date: Thu, 23 Dec 2021 11:05:38 +0900 Subject: [PATCH 12/32] e2e: integrate light clients (bp #6196) integrate light clients (#6196) fix e2e app test (#6223) fix light client generator (#6236) --- .github/workflows/lint.yaml | 6 +-- cmd/ostracon/commands/light.go | 31 ++--------- light/client.go | 6 ++- light/proxy/proxy.go | 24 +++++++++ light/rpc/client.go | 22 ++++++++ test/e2e/app/app.go | 2 +- test/e2e/app/config.go | 1 + test/e2e/app/main.go | 95 ++++++++++++++++++++++++++++++++-- test/e2e/generator/generate.go | 33 ++++++++++-- test/e2e/networks/ci.toml | 11 ++++ test/e2e/networks/simple.toml | 2 +- test/e2e/pkg/manifest.go | 9 ++-- test/e2e/pkg/testnet.go | 13 ++++- test/e2e/runner/setup.go | 24 ++++++--- test/e2e/runner/start.go | 2 +- test/e2e/tests/app_test.go | 25 +++++++-- 16 files changed, 249 insertions(+), 57 deletions(-) diff --git a/.github/workflows/lint.yaml b/.github/workflows/lint.yaml index b5033cbd3..448d78fa1 100644 --- a/.github/workflows/lint.yaml +++ b/.github/workflows/lint.yaml @@ -18,10 +18,10 @@ jobs: **/**.go go.mod go.sum - - uses: golangci/golangci-lint-action@v2.2.1 + - uses: golangci/golangci-lint-action@v2.5.1 with: # Required: the version of golangci-lint is required and must be specified without patch version: we always use the latest patch version. - version: v1.31 - args: --timeout 10m --skip-files "_test\.go" # for skip unused linter (https://github.com/golangci/golangci-lint/issues/791) + version: v1.38 + args: --timeout 10m github-token: ${{ secrets.github_token }} if: env.GIT_DIFF diff --git a/cmd/ostracon/commands/light.go b/cmd/ostracon/commands/light.go index 14cf6fd71..fb3913f95 100644 --- a/cmd/ostracon/commands/light.go +++ b/cmd/ostracon/commands/light.go @@ -5,10 +5,10 @@ import ( "context" "errors" "fmt" + rpchttp "github.com/line/ostracon/rpc/client/http" "net/http" "os" "path/filepath" - "regexp" "strings" "time" @@ -17,7 +17,6 @@ import ( dbm "github.com/line/tm-db/v2" - "github.com/line/ostracon/crypto/merkle" "github.com/line/ostracon/libs/log" tmmath "github.com/line/ostracon/libs/math" tmos "github.com/line/ostracon/libs/os" @@ -25,7 +24,6 @@ import ( lproxy "github.com/line/ostracon/light/proxy" lrpc "github.com/line/ostracon/light/rpc" dbs "github.com/line/ostracon/light/store/db" - rpchttp "github.com/line/ostracon/rpc/client/http" rpcserver "github.com/line/ostracon/rpc/jsonrpc/server" ) @@ -233,12 +231,11 @@ func runProxy(cmd *cobra.Command, args []string) error { cfg.WriteTimeout = config.RPC.TimeoutBroadcastTxCommit + 1*time.Second } - p := lproxy.Proxy{ - Addr: listenAddr, - Config: cfg, - Client: lrpc.NewClient(rpcClient, c, lrpc.KeyPathFn(defaultMerkleKeyPathFn())), - Logger: logger, + p, err := lproxy.NewProxy(c, listenAddr, primaryAddr, cfg, logger, lrpc.KeyPathFn(lrpc.DefaultMerkleKeyPathFn())) + if err != nil { + return err } + // Stop upon receiving SIGTERM or CTRL-C. tmos.TrapSignal(logger, func() { p.Listener.Close() @@ -277,21 +274,3 @@ func saveProviders(db dbm.DB, primaryAddr, witnessesAddrs string) error { } return nil } - -func defaultMerkleKeyPathFn() lrpc.KeyPathFunc { - // regexp for extracting store name from /abci_query path - storeNameRegexp := regexp.MustCompile(`\/store\/(.+)\/key`) - - return func(path string, key []byte) (merkle.KeyPath, error) { - matches := storeNameRegexp.FindStringSubmatch(path) - if len(matches) != 2 { - return nil, fmt.Errorf("can't find store name in %s using %s", path, storeNameRegexp) - } - storeName := matches[1] - - kp := merkle.KeyPath{} - kp = kp.AppendKey([]byte(storeName), merkle.KeyEncodingURL) - kp = kp.AppendKey(key, merkle.KeyEncodingURL) - return kp, nil - } -} diff --git a/light/client.go b/light/client.go index e674118be..61b105eeb 100644 --- a/light/client.go +++ b/light/client.go @@ -913,6 +913,8 @@ func (c *Client) cleanupAfter(height int64) error { } func (c *Client) updateTrustedLightBlock(l *types.LightBlock) error { + c.logger.Debug("updating trusted light block", "light_block", l) + if err := c.trustedStore.SaveLightBlock(l); err != nil { return fmt.Errorf("failed to save trusted header: %w", err) } @@ -1055,10 +1057,12 @@ and remove witness. Otherwise, use the different primary`, e.WitnessIndex), "wit // respond or couldn't find the block, then we ignore it and move on to // the next witness. if _, ok := e.Reason.(provider.ErrBadLightBlock); ok { - c.logger.Info("Witness sent us invalid header / vals -> removing it", "witness", c.witnesses[e.WitnessIndex]) + c.logger.Info("Witness sent us invalid header / vals -> removing it", + "witness", c.witnesses[e.WitnessIndex], "err", err) witnessesToRemove = append(witnessesToRemove, e.WitnessIndex) } } + } // we need to make sure that we remove witnesses by index in the reverse diff --git a/light/proxy/proxy.go b/light/proxy/proxy.go index fec083bc8..3babbb0aa 100644 --- a/light/proxy/proxy.go +++ b/light/proxy/proxy.go @@ -8,7 +8,9 @@ import ( "github.com/line/ostracon/libs/log" tmpubsub "github.com/line/ostracon/libs/pubsub" + "github.com/line/ostracon/light" lrpc "github.com/line/ostracon/light/rpc" + rpchttp "github.com/line/ostracon/rpc/client/http" rpcserver "github.com/line/ostracon/rpc/jsonrpc/server" ) @@ -21,6 +23,28 @@ type Proxy struct { Listener net.Listener } +// NewProxy creates the struct used to run an HTTP server for serving light +// client rpc requests. +func NewProxy( + lightClient *light.Client, + listenAddr, providerAddr string, + config *rpcserver.Config, + logger log.Logger, + opts ...lrpc.Option, +) (*Proxy, error) { + rpcClient, err := rpchttp.NewWithTimeout(providerAddr, "/websocket", uint(config.WriteTimeout.Seconds())) + if err != nil { + return nil, fmt.Errorf("failed to create http client for %s: %w", providerAddr, err) + } + + return &Proxy{ + Addr: listenAddr, + Config: config, + Client: lrpc.NewClient(rpcClient, lightClient, opts...), + Logger: logger, + }, nil +} + // ListenAndServe configures the rpcserver.WebsocketManager, sets up the RPC // routes to proxy via Client, and starts up an HTTP server on the TCP network // address p.Addr. diff --git a/light/rpc/client.go b/light/rpc/client.go index fe34d905b..d2bdb9af5 100644 --- a/light/rpc/client.go +++ b/light/rpc/client.go @@ -5,6 +5,7 @@ import ( "context" "errors" "fmt" + "regexp" "time" "github.com/gogo/protobuf/proto" @@ -61,6 +62,27 @@ func KeyPathFn(fn KeyPathFunc) Option { } } +// DefaultMerkleKeyPathFn creates a function used to generate merkle key paths +// from a path string and a key. This is the default used by the cosmos SDK. +// This merkle key paths are required when verifying /abci_query calls +func DefaultMerkleKeyPathFn() KeyPathFunc { + // regexp for extracting store name from /abci_query path + storeNameRegexp := regexp.MustCompile(`\/store\/(.+)\/key`) + + return func(path string, key []byte) (merkle.KeyPath, error) { + matches := storeNameRegexp.FindStringSubmatch(path) + if len(matches) != 2 { + return nil, fmt.Errorf("can't find store name in %s using %s", path, storeNameRegexp) + } + storeName := matches[1] + + kp := merkle.KeyPath{} + kp = kp.AppendKey([]byte(storeName), merkle.KeyEncodingURL) + kp = kp.AppendKey(key, merkle.KeyEncodingURL) + return kp, nil + } +} + // NewClient returns a new client. func NewClient(next rpcclient.Client, lc LightClient, opts ...Option) *Client { c := &Client{ diff --git a/test/e2e/app/app.go b/test/e2e/app/app.go index 43dea19d1..8f3dced00 100644 --- a/test/e2e/app/app.go +++ b/test/e2e/app/app.go @@ -120,7 +120,7 @@ func (app *Application) Commit() abci.ResponseCommit { if err != nil { panic(err) } - logger.Info("Created state sync snapshot", "height", snapshot.Height) + app.logger.Info("Created state sync snapshot", "height", snapshot.Height) } retainHeight := int64(0) if app.cfg.RetainBlocks > 0 { diff --git a/test/e2e/app/config.go b/test/e2e/app/config.go index 281419160..c8c982241 100644 --- a/test/e2e/app/config.go +++ b/test/e2e/app/config.go @@ -14,6 +14,7 @@ type Config struct { Listen string Protocol string Dir string + Mode string `toml:"mode"` PersistInterval uint64 `toml:"persist_interval"` SnapshotInterval uint64 `toml:"snapshot_interval"` RetainBlocks uint64 `toml:"retain_blocks"` diff --git a/test/e2e/app/main.go b/test/e2e/app/main.go index 5dbdf268d..09a3a23b3 100644 --- a/test/e2e/app/main.go +++ b/test/e2e/app/main.go @@ -1,11 +1,15 @@ package main import ( + "context" "errors" "fmt" + "github.com/line/ostracon/types" + "net/http" "os" "path/filepath" "strconv" + "strings" "time" "github.com/spf13/viper" @@ -16,10 +20,16 @@ import ( tmflags "github.com/line/ostracon/libs/cli/flags" "github.com/line/ostracon/libs/log" tmnet "github.com/line/ostracon/libs/net" + "github.com/line/ostracon/light" + lproxy "github.com/line/ostracon/light/proxy" + lrpc "github.com/line/ostracon/light/rpc" + dbs "github.com/line/ostracon/light/store/db" "github.com/line/ostracon/node" "github.com/line/ostracon/p2p" "github.com/line/ostracon/privval" "github.com/line/ostracon/proxy" + rpcserver "github.com/line/ostracon/rpc/jsonrpc/server" + e2e "github.com/line/ostracon/test/e2e/pkg" mcs "github.com/line/ostracon/test/maverick/consensus" maverick "github.com/line/ostracon/test/maverick/node" ) @@ -66,7 +76,11 @@ func run(configFile string) error { err = startApp(cfg) case "builtin": if len(cfg.Misbehaviors) == 0 { - err = startNode(cfg) + if cfg.Mode == string(e2e.ModeLight) { + err = startLightClient(cfg) + } else { + err = startNode(cfg) + } } else { err = startMaverick(cfg) } @@ -139,8 +153,67 @@ func startNode(cfg *Config) error { return n.Start() } -// startMaverick starts a Maverick node that runs the application directly. It assumes the Ostracon -// configuration is in $OCHOME/config/ostracon.toml. +func startLightClient(cfg *Config) error { + tmcfg, nodeLogger, _, err := setupNode() + if err != nil { + return err + } + + dbContext := &node.DBContext{ID: "light", Config: tmcfg} + lightDB, err := node.DefaultDBProvider(dbContext) + if err != nil { + return err + } + + providers := rpcEndpoints(tmcfg.P2P.PersistentPeers) + + c, err := light.NewHTTPClient( + context.Background(), + cfg.ChainID, + light.TrustOptions{ + Period: tmcfg.StateSync.TrustPeriod, + Height: tmcfg.StateSync.TrustHeight, + Hash: tmcfg.StateSync.TrustHashBytes(), + }, + providers[0], + providers[1:], + dbs.New(lightDB, "light"), + types.DefaultVoterParams(), + light.Logger(nodeLogger), + ) + if err != nil { + return err + } + + rpccfg := rpcserver.DefaultConfig() + rpccfg.MaxBodyBytes = tmcfg.RPC.MaxBodyBytes + rpccfg.MaxHeaderBytes = tmcfg.RPC.MaxHeaderBytes + rpccfg.MaxOpenConnections = tmcfg.RPC.MaxOpenConnections + // If necessary adjust global WriteTimeout to ensure it's greater than + // TimeoutBroadcastTxCommit. + // See https://github.com/tendermint/tendermint/issues/3435 + if rpccfg.WriteTimeout <= tmcfg.RPC.TimeoutBroadcastTxCommit { + rpccfg.WriteTimeout = tmcfg.RPC.TimeoutBroadcastTxCommit + 1*time.Second + } + + p, err := lproxy.NewProxy(c, tmcfg.RPC.ListenAddress, providers[0], rpccfg, nodeLogger, + lrpc.KeyPathFn(lrpc.DefaultMerkleKeyPathFn())) + if err != nil { + return err + } + + logger.Info("Starting proxy...", "laddr", tmcfg.RPC.ListenAddress) + if err := p.ListenAndServe(); err != http.ErrServerClosed { + // Error starting or closing listener: + logger.Error("proxy ListenAndServe", "err", err) + } + + return nil +} + +// FIXME: Temporarily disconnected maverick until it is redesigned +// startMaverick starts a Maverick node that runs the application directly. It assumes the Tendermint +// configuration is in $TMHOME/config/tendermint.toml. func startMaverick(cfg *Config) error { app, err := NewApplication(cfg) if err != nil { @@ -247,3 +320,19 @@ func setupNode() (*config.Config, log.Logger, *p2p.NodeKey, error) { return tmcfg, nodeLogger, nodeKey, nil } + +// rpcEndpoints takes a list of persistent peers and splits them into a list of rpc endpoints +// using 26657 as the port number +func rpcEndpoints(peers string) []string { + arr := strings.Split(peers, ",") + endpoints := make([]string, len(arr)) + for i, v := range arr { + urlString := strings.SplitAfter(v, "@")[1] + hostName := strings.Split(urlString, ":26656")[0] + // use RPC port instead + port := 26657 + rpcEndpoint := "http://" + hostName + ":" + fmt.Sprint(port) + endpoints[i] = rpcEndpoint + } + return endpoints +} diff --git a/test/e2e/generator/generate.go b/test/e2e/generator/generate.go index aa02b1655..b91ab6b51 100644 --- a/test/e2e/generator/generate.go +++ b/test/e2e/generator/generate.go @@ -74,7 +74,7 @@ func generateTestnet(r *rand.Rand, opt map[string]interface{}) (e2e.Manifest, er Nodes: map[string]*e2e.ManifestNode{}, } - var numSeeds, numValidators, numFulls int + var numSeeds, numValidators, numFulls, numLightClients int switch opt["topology"].(string) { case "single": numValidators = 1 @@ -82,7 +82,8 @@ func generateTestnet(r *rand.Rand, opt map[string]interface{}) (e2e.Manifest, er numValidators = 4 case "large": // FIXME Networks are kept small since large ones use too much CPU. - numSeeds = r.Intn(4) + numSeeds = r.Intn(3) + numLightClients = r.Intn(3) numValidators = 4 + r.Intn(7) numFulls = r.Intn(5) default: @@ -143,11 +144,16 @@ func generateTestnet(r *rand.Rand, opt map[string]interface{}) (e2e.Manifest, er // We now set up peer discovery for nodes. Seed nodes are fully meshed with // each other, while non-seed nodes either use a set of random seeds or a // set of random peers that start before themselves. - var seedNames, peerNames []string + var seedNames, peerNames, lightProviders []string for name, node := range manifest.Nodes { if node.Mode == string(e2e.ModeSeed) { seedNames = append(seedNames, name) } else { + // if the full node or validator is an ideal candidate, it is added as a light provider. + // There are at least two archive nodes so there should be at least two ideal candidates + if (node.StartAt == 0 || node.StartAt == manifest.InitialHeight) && node.RetainBlocks == 0 { + lightProviders = append(lightProviders, name) + } peerNames = append(peerNames, name) } } @@ -179,6 +185,14 @@ func generateTestnet(r *rand.Rand, opt map[string]interface{}) (e2e.Manifest, er } } + // lastly, set up the light clients + for i := 1; i <= numLightClients; i++ { + startAt := manifest.InitialHeight + 5 + manifest.Nodes[fmt.Sprintf("light%02d", i)] = generateLightNode( + r, startAt+(5*int64(i)), lightProviders, + ) + } + return manifest, nil } @@ -210,7 +224,7 @@ func generateNode( node.SnapshotInterval = 3 } - if node.Mode == "validator" { + if node.Mode == string(e2e.ModeValidator) { misbehaveAt := startAt + 5 + int64(r.Intn(10)) if startAt == 0 { misbehaveAt += initialHeight - 1 @@ -245,6 +259,17 @@ func generateNode( return &node } +func generateLightNode(r *rand.Rand, startAt int64, providers []string) *e2e.ManifestNode { + return &e2e.ManifestNode{ + Mode: string(e2e.ModeLight), + StartAt: startAt, + Database: nodeDatabases.Choose(r).(string), + ABCIProtocol: "builtin", + PersistInterval: ptrUint64(0), + PersistentPeers: providers, + } +} + func ptrUint64(i uint64) *uint64 { return &i } diff --git a/test/e2e/networks/ci.toml b/test/e2e/networks/ci.toml index d0dc9db6d..e1bbc68e8 100644 --- a/test/e2e/networks/ci.toml +++ b/test/e2e/networks/ci.toml @@ -1,6 +1,7 @@ # This testnet is run by CI, and attempts to cover a broad range of # functionality with a single network. +ipv6 = true initial_height = 1000 initial_state = { initial01 = "a", initial02 = "b", initial03 = "c" } @@ -89,3 +90,13 @@ fast_sync = "v0" state_sync = true seeds = ["seed01"] perturb = ["restart"] + +[node.light01] +mode= "light" +start_at= 1005 +persistent_peers = ["validator01", "validator02", "validator03"] + +[node.light02] +mode= "light" +start_at= 1015 +persistent_peers = ["validator04", "full01", "validator05"] \ No newline at end of file diff --git a/test/e2e/networks/simple.toml b/test/e2e/networks/simple.toml index 37f711a91..05cda1819 100644 --- a/test/e2e/networks/simple.toml +++ b/test/e2e/networks/simple.toml @@ -2,4 +2,4 @@ [node.validator02] [node.validator03] [node.validator04] - + diff --git a/test/e2e/pkg/manifest.go b/test/e2e/pkg/manifest.go index 3d919a449..5e8529c17 100644 --- a/test/e2e/pkg/manifest.go +++ b/test/e2e/pkg/manifest.go @@ -54,9 +54,9 @@ type Manifest struct { // ManifestNode represents a node in a testnet manifest. type ManifestNode struct { - // Mode specifies the type of node: "validator", "full", or "seed". Defaults to - // "validator". Full nodes do not get a signing key (a dummy key is generated), - // and seed nodes run in seed mode with the PEX reactor enabled. + // Mode specifies the type of node: "validator", "full", "light" or "seed". + // Defaults to "validator". Full nodes do not get a signing key (a dummy key + // is generated), and seed nodes run in seed mode with the PEX reactor enabled. Mode string `toml:"mode"` // Seeds is the list of node names to use as P2P seed nodes. Defaults to none. @@ -64,7 +64,8 @@ type ManifestNode struct { // PersistentPeers is a list of node names to maintain persistent P2P // connections to. If neither seeds nor persistent peers are specified, - // this defaults to all other nodes in the network. + // this defaults to all other nodes in the network. For light clients, + // this relates to the providers the light client is connected to. PersistentPeers []string `toml:"persistent_peers"` // Database specifies the database backend: "goleveldb", "cleveldb", diff --git a/test/e2e/pkg/testnet.go b/test/e2e/pkg/testnet.go index 34fbd5c93..a5e81f30d 100644 --- a/test/e2e/pkg/testnet.go +++ b/test/e2e/pkg/testnet.go @@ -33,6 +33,7 @@ type Perturbation string const ( ModeValidator Mode = "validator" ModeFull Mode = "full" + ModeLight Mode = "light" ModeSeed Mode = "seed" ProtocolBuiltin Protocol = "builtin" @@ -144,7 +145,7 @@ func LoadTestnet(file string) (*Testnet, error) { ProxyPort: proxyPortGen.Next(), Mode: ModeValidator, Database: "goleveldb", - ABCIProtocol: ProtocolUNIX, + ABCIProtocol: ProtocolBuiltin, PrivvalProtocol: ProtocolFile, StartAt: nodeManifest.StartAt, FastSync: nodeManifest.FastSync, @@ -311,6 +312,9 @@ func (n Node) Validate(testnet Testnet) error { default: return fmt.Errorf("invalid ABCI protocol setting %q", n.ABCIProtocol) } + if n.Mode == ModeLight && n.ABCIProtocol != ProtocolBuiltin { + return errors.New("light client must use builtin protocol") + } switch n.PrivvalProtocol { case ProtocolFile, ProtocolUNIX, ProtocolTCP: default: @@ -385,7 +389,7 @@ func (t Testnet) LookupNode(name string) *Node { func (t Testnet) ArchiveNodes() []*Node { nodes := []*Node{} for _, node := range t.Nodes { - if node.Mode != ModeSeed && node.StartAt == 0 && node.RetainBlocks == 0 { + if !node.Stateless() && node.StartAt == 0 && node.RetainBlocks == 0 { nodes = append(nodes, node) } } @@ -459,6 +463,11 @@ func (n Node) Client() (*rpchttp.HTTP, error) { return rpchttp.New(fmt.Sprintf("http://127.0.0.1:%v", n.ProxyPort), "/websocket") } +// Stateless returns true if the node is either a seed node or a light node +func (n Node) Stateless() bool { + return n.Mode == ModeLight || n.Mode == ModeSeed +} + // keyGenerator generates pseudorandom Ed25519 keys based on a seed. type keyGenerator struct { random *rand.Rand diff --git a/test/e2e/runner/setup.go b/test/e2e/runner/setup.go index 2f1e01855..739aafc26 100644 --- a/test/e2e/runner/setup.go +++ b/test/e2e/runner/setup.go @@ -65,23 +65,23 @@ func Setup(testnet *e2e.Testnet) error { for _, node := range testnet.Nodes { nodeDir := filepath.Join(testnet.Dir, node.Name) + dirs := []string{ filepath.Join(nodeDir, "config"), filepath.Join(nodeDir, "data"), filepath.Join(nodeDir, "data", "app"), } for _, dir := range dirs { + // light clients don't need an app directory + if node.Mode == e2e.ModeLight && strings.Contains(dir, "app") { + continue + } err := os.MkdirAll(dir, 0755) if err != nil { return err } } - err = genesis.SaveAs(filepath.Join(nodeDir, "config", "genesis.json")) - if err != nil { - return err - } - cfg, err := MakeConfig(node) if err != nil { return err @@ -97,6 +97,16 @@ func Setup(testnet *e2e.Testnet) error { return err } + if node.Mode == e2e.ModeLight { + // stop early if a light client + continue + } + + err = genesis.SaveAs(filepath.Join(nodeDir, "config", "genesis.json")) + if err != nil { + return err + } + err = (&p2p.NodeKey{PrivKey: node.NodeKey}).SaveAs(filepath.Join(nodeDir, "config", "node_key.json")) if err != nil { return err @@ -267,7 +277,7 @@ func MakeConfig(node *e2e.Node) (*config.Config, error) { case e2e.ModeSeed: cfg.P2P.SeedMode = true cfg.P2P.PexReactor = true - case e2e.ModeFull: + case e2e.ModeFull, e2e.ModeLight: // Don't need to do anything, since we're using a dummy privval key by default. default: return nil, fmt.Errorf("unexpected mode %q", node.Mode) @@ -316,6 +326,8 @@ func MakeAppConfig(node *e2e.Node) ([]byte, error) { "chain_id": node.Testnet.Name, "dir": "data/app", "listen": AppAddressUNIX, + "mode": node.Mode, + "proxy_port": node.ProxyPort, "protocol": "socket", "persist_interval": node.PersistInterval, "snapshot_interval": node.SnapshotInterval, diff --git a/test/e2e/runner/start.go b/test/e2e/runner/start.go index fea3def6b..985334d56 100644 --- a/test/e2e/runner/start.go +++ b/test/e2e/runner/start.go @@ -58,7 +58,7 @@ func Start(testnet *e2e.Testnet) error { // Update any state sync nodes with a trusted height and hash for _, node := range nodeQueue { - if node.StateSync { + if node.StateSync || node.Mode == e2e.ModeLight { err = UpdateConfigStateSync(node, block.Height, blockID.Hash.Bytes()) if err != nil { return err diff --git a/test/e2e/tests/app_test.go b/test/e2e/tests/app_test.go index 9338d0487..e11679373 100644 --- a/test/e2e/tests/app_test.go +++ b/test/e2e/tests/app_test.go @@ -16,7 +16,7 @@ import ( // Tests that any initial state given in genesis has made it into the app. func TestApp_InitialState(t *testing.T) { testNode(t, func(t *testing.T, node e2e.Node) { - if node.Mode == e2e.ModeSeed { + if node.Stateless() { return } if len(node.Testnet.InitialState) == 0 { @@ -81,12 +81,27 @@ func TestApp_Tx(t *testing.T) { value := fmt.Sprintf("%x", bz) tx := types.Tx(fmt.Sprintf("%v=%v", key, value)) - _, err = client.BroadcastTxCommit(ctx, tx) + resp, err := client.BroadcastTxCommit(ctx, tx) + require.NoError(t, err) + + // wait for the tx to be persisted in the tx indexer + time.Sleep(500 * time.Millisecond) + + hash := tx.Hash() + txResp, err := client.Tx(ctx, hash, false) require.NoError(t, err) + assert.Equal(t, txResp.Tx, tx) + assert.Equal(t, txResp.Height, resp.Height) - resp, err := client.ABCIQuery(ctx, "", []byte(key)) + // NOTE: we don't test abci query of the light client + if node.Mode == e2e.ModeLight { + return + } + + abciResp, err := client.ABCIQuery(ctx, "", []byte(key)) require.NoError(t, err) - assert.Equal(t, key, string(resp.Response.Key)) - assert.Equal(t, value, string(resp.Response.Value)) + assert.Equal(t, key, string(abciResp.Response.Key)) + assert.Equal(t, value, string(abciResp.Response.Value)) + }) } From 0643021719b83ca2b3cc85008ec75f8e7636b953 Mon Sep 17 00:00:00 2001 From: tnasu Date: Thu, 23 Dec 2021 11:39:56 +0900 Subject: [PATCH 13/32] Fix lint for `e2e: integrate light clients (bp #6196)` --- abci/client/grpc_client_test.go | 2 +- abci/client/local_client_test.go | 6 +- abci/client/socket_client_test.go | 6 +- blockchain/v2/reactor_test.go | 3 + cmd/ostracon/commands/light.go | 3 +- cmd/ostracon/commands/version_test.go | 3 +- consensus/byzantine_test.go | 128 +++++++++++++------------- consensus/common_test.go | 1 - consensus/replay_test.go | 2 +- crypto/ed25519/ed25519_test.go | 3 +- crypto/hash.go | 2 +- crypto/merkle/proof_value.go | 2 +- evidence/verify_test.go | 1 + mempool/clist_mempool_test.go | 2 +- p2p/pex/addrbook.go | 2 +- statesync/snapshots.go | 6 +- test/e2e/app/main.go | 3 +- 17 files changed, 91 insertions(+), 84 deletions(-) diff --git a/abci/client/grpc_client_test.go b/abci/client/grpc_client_test.go index 4e04b502d..7b1d386ad 100644 --- a/abci/client/grpc_client_test.go +++ b/abci/client/grpc_client_test.go @@ -26,7 +26,7 @@ func TestGrpcClientCalls(t *testing.T) { }) err0 = c.Start() require.NoError(t, err0) - + c.EchoAsync("msg", getResponseCallback(t)) c.FlushAsync(getResponseCallback(t)) c.InfoAsync(types.RequestInfo{}, getResponseCallback(t)) diff --git a/abci/client/local_client_test.go b/abci/client/local_client_test.go index 4ad2e6a24..c8aab0ba1 100644 --- a/abci/client/local_client_test.go +++ b/abci/client/local_client_test.go @@ -14,7 +14,7 @@ type sampleApp struct { func newDoneChan(t *testing.T) chan struct{} { result := make(chan struct{}) - go func(){ + go func() { select { case <-time.After(time.Second): require.Fail(t, "callback is not called for a second") @@ -27,9 +27,9 @@ func newDoneChan(t *testing.T) chan struct{} { func getResponseCallback(t *testing.T) ResponseCallback { doneChan := newDoneChan(t) - return func (res *types.Response) { + return func(res *types.Response) { require.NotNil(t, res) - doneChan<- struct{}{} + doneChan <- struct{}{} } } diff --git a/abci/client/socket_client_test.go b/abci/client/socket_client_test.go index b18c08225..1d7de4d97 100644 --- a/abci/client/socket_client_test.go +++ b/abci/client/socket_client_test.go @@ -213,7 +213,7 @@ type sampleApp struct { func newDoneChan(t *testing.T) chan struct{} { result := make(chan struct{}) - go func(){ + go func() { select { case <-time.After(time.Second): require.Fail(t, "callback is not called for a second") @@ -226,8 +226,8 @@ func newDoneChan(t *testing.T) chan struct{} { func getResponseCallback(t *testing.T) abcicli.ResponseCallback { doneChan := newDoneChan(t) - return func (res *types.Response) { + return func(res *types.Response) { require.NotNil(t, res) - doneChan<- struct{}{} + doneChan <- struct{}{} } } diff --git a/blockchain/v2/reactor_test.go b/blockchain/v2/reactor_test.go index 6dfacf49b..bfd549a2b 100644 --- a/blockchain/v2/reactor_test.go +++ b/blockchain/v2/reactor_test.go @@ -65,14 +65,17 @@ type mockBlockStore struct { blocks map[int64]*types.Block } +// nolint:unused // ignore func (ml *mockBlockStore) Height() int64 { return int64(len(ml.blocks)) } +// nolint:unused // ignore func (ml *mockBlockStore) LoadBlock(height int64) *types.Block { return ml.blocks[height] } +// nolint:unused // ignore func (ml *mockBlockStore) SaveBlock(block *types.Block, part *types.PartSet, commit *types.Commit) { ml.blocks[block.Height] = block } diff --git a/cmd/ostracon/commands/light.go b/cmd/ostracon/commands/light.go index fb3913f95..86678e254 100644 --- a/cmd/ostracon/commands/light.go +++ b/cmd/ostracon/commands/light.go @@ -5,13 +5,14 @@ import ( "context" "errors" "fmt" - rpchttp "github.com/line/ostracon/rpc/client/http" "net/http" "os" "path/filepath" "strings" "time" + rpchttp "github.com/line/ostracon/rpc/client/http" + "github.com/line/tm-db/v2/goleveldb" "github.com/spf13/cobra" diff --git a/cmd/ostracon/commands/version_test.go b/cmd/ostracon/commands/version_test.go index ce70d679f..560e5a9e1 100644 --- a/cmd/ostracon/commands/version_test.go +++ b/cmd/ostracon/commands/version_test.go @@ -1,8 +1,9 @@ package commands import ( - "github.com/line/ostracon/version" "testing" + + "github.com/line/ostracon/version" ) func TestVersionCmd(t *testing.T) { diff --git a/consensus/byzantine_test.go b/consensus/byzantine_test.go index 37cdab4af..559cf5651 100644 --- a/consensus/byzantine_test.go +++ b/consensus/byzantine_test.go @@ -174,76 +174,76 @@ func TestByzantinePrevoteEquivocation(t *testing.T) { // `failed to verify the aggregated hashes by 1 public keys` // /* - // introducing a lazy proposer means that the time of the block committed is different to the - // timestamp that the other nodes have. This tests to ensure that the evidence that finally gets - // proposed will have a valid timestamp - lazyProposer := css[1] - - lazyProposer.decideProposal = func(height int64, round int32) { - lazyProposer.Logger.Info("Lazy Proposer proposing condensed commit") - if lazyProposer.privValidator == nil { - panic("entered createProposalBlock with privValidator being nil") - } + // introducing a lazy proposer means that the time of the block committed is different to the + // timestamp that the other nodes have. This tests to ensure that the evidence that finally gets + // proposed will have a valid timestamp + lazyProposer := css[1] + + lazyProposer.decideProposal = func(height int64, round int32) { + lazyProposer.Logger.Info("Lazy Proposer proposing condensed commit") + if lazyProposer.privValidator == nil { + panic("entered createProposalBlock with privValidator being nil") + } - var commit *types.Commit - switch { - case lazyProposer.Height == lazyProposer.state.InitialHeight: - // We're creating a proposal for the first block. - // The commit is empty, but not nil. - commit = types.NewCommit(0, 0, types.BlockID{}, nil) - case lazyProposer.LastCommit.HasTwoThirdsMajority(): - // Make the commit from LastCommit - commit = lazyProposer.LastCommit.MakeCommit() - default: // This shouldn't happen. - lazyProposer.Logger.Error("enterPropose: Cannot propose anything: No commit for the previous block") - return - } + var commit *types.Commit + switch { + case lazyProposer.Height == lazyProposer.state.InitialHeight: + // We're creating a proposal for the first block. + // The commit is empty, but not nil. + commit = types.NewCommit(0, 0, types.BlockID{}, nil) + case lazyProposer.LastCommit.HasTwoThirdsMajority(): + // Make the commit from LastCommit + commit = lazyProposer.LastCommit.MakeCommit() + default: // This shouldn't happen. + lazyProposer.Logger.Error("enterPropose: Cannot propose anything: No commit for the previous block") + return + } - // omit the last signature in the commit - // except a proposal for the first block - if commit.Signatures != nil { - commit.Signatures[len(commit.Signatures)-1] = types.NewCommitSigAbsent() - } + // omit the last signature in the commit + // except a proposal for the first block + if commit.Signatures != nil { + commit.Signatures[len(commit.Signatures)-1] = types.NewCommitSigAbsent() + } - if lazyProposer.privValidatorPubKey == nil { - // If this node is a validator & proposer in the current round, it will - // miss the opportunity to create a block. - lazyProposer.Logger.Error(fmt.Sprintf("enterPropose: %v", errPubKeyIsNotSet)) - return - } - proposerAddr := lazyProposer.privValidatorPubKey.Address() - - message := lazyProposer.state.MakeHashMessage(lazyProposer.Round) - proof, _ := lazyProposer.privValidator.GenerateVRFProof(message) - block, blockParts := lazyProposer.blockExec.CreateProposalBlock( - lazyProposer.Height, lazyProposer.state, commit, proposerAddr, lazyProposer.Round, proof, 0, - ) - - // Flush the WAL. Otherwise, we may not recompute the same proposal to sign, - // and the privValidator will refuse to sign anything. - if err := lazyProposer.wal.FlushAndSync(); err != nil { - lazyProposer.Logger.Error("Error flushing to disk") - } + if lazyProposer.privValidatorPubKey == nil { + // If this node is a validator & proposer in the current round, it will + // miss the opportunity to create a block. + lazyProposer.Logger.Error(fmt.Sprintf("enterPropose: %v", errPubKeyIsNotSet)) + return + } + proposerAddr := lazyProposer.privValidatorPubKey.Address() + + message := lazyProposer.state.MakeHashMessage(lazyProposer.Round) + proof, _ := lazyProposer.privValidator.GenerateVRFProof(message) + block, blockParts := lazyProposer.blockExec.CreateProposalBlock( + lazyProposer.Height, lazyProposer.state, commit, proposerAddr, lazyProposer.Round, proof, 0, + ) + + // Flush the WAL. Otherwise, we may not recompute the same proposal to sign, + // and the privValidator will refuse to sign anything. + if err := lazyProposer.wal.FlushAndSync(); err != nil { + lazyProposer.Logger.Error("Error flushing to disk") + } - // Make proposal - propBlockID := types.BlockID{Hash: block.Hash(), PartSetHeader: blockParts.Header()} - proposal := types.NewProposal(height, round, lazyProposer.ValidRound, propBlockID) - p := proposal.ToProto() - if err := lazyProposer.privValidator.SignProposal(lazyProposer.state.ChainID, p); err == nil { - proposal.Signature = p.Signature - - // send proposal and block parts on internal msg queue - lazyProposer.sendInternalMessage(msgInfo{&ProposalMessage{proposal}, ""}) - for i := 0; i < int(blockParts.Total()); i++ { - part := blockParts.GetPart(i) - lazyProposer.sendInternalMessage(msgInfo{&BlockPartMessage{lazyProposer.Height, lazyProposer.Round, part}, ""}) + // Make proposal + propBlockID := types.BlockID{Hash: block.Hash(), PartSetHeader: blockParts.Header()} + proposal := types.NewProposal(height, round, lazyProposer.ValidRound, propBlockID) + p := proposal.ToProto() + if err := lazyProposer.privValidator.SignProposal(lazyProposer.state.ChainID, p); err == nil { + proposal.Signature = p.Signature + + // send proposal and block parts on internal msg queue + lazyProposer.sendInternalMessage(msgInfo{&ProposalMessage{proposal}, ""}) + for i := 0; i < int(blockParts.Total()); i++ { + part := blockParts.GetPart(i) + lazyProposer.sendInternalMessage(msgInfo{&BlockPartMessage{lazyProposer.Height, lazyProposer.Round, part}, ""}) + } + lazyProposer.Logger.Info("Signed proposal", "height", height, "round", round, "proposal", proposal) + lazyProposer.Logger.Debug(fmt.Sprintf("Signed proposal block: %v", block)) + } else if !lazyProposer.replayMode { + lazyProposer.Logger.Error("enterPropose: Error signing proposal", "height", height, "round", round, "err", err) } - lazyProposer.Logger.Info("Signed proposal", "height", height, "round", round, "proposal", proposal) - lazyProposer.Logger.Debug(fmt.Sprintf("Signed proposal block: %v", block)) - } else if !lazyProposer.replayMode { - lazyProposer.Logger.Error("enterPropose: Error signing proposal", "height", height, "round", round, "err", err) } - } */ diff --git a/consensus/common_test.go b/consensus/common_test.go index b69d4ec8d..9e0ad24cb 100644 --- a/consensus/common_test.go +++ b/consensus/common_test.go @@ -801,7 +801,6 @@ func DefaultTestLoggers() TestLoggers { log.TestingLogger(), log.TestingLogger(), log.TestingLogger(), log.TestingLogger(), log.TestingLogger()) } -// nolint:unused func NopTestLoggers() TestLoggers { return NewTestLoggers( log.NewNopLogger(), log.NewNopLogger(), log.NewNopLogger(), log.NewNopLogger(), log.NewNopLogger()) diff --git a/consensus/replay_test.go b/consensus/replay_test.go index 250dfb180..c6d5a9af0 100644 --- a/consensus/replay_test.go +++ b/consensus/replay_test.go @@ -219,7 +219,7 @@ LOOP: cs.Stop() //nolint:errcheck // Logging this error causes failure cancel() // For safety since nobody stops and writing WAL continue sometimes. - cs.wal.Stop() + cs.wal.Stop() //nolint:errcheck // make sure we can make blocks after a crash startNewStateAndWaitForBlock(t, i, consensusReplayConfig, blockDB, stateStore) diff --git a/crypto/ed25519/ed25519_test.go b/crypto/ed25519/ed25519_test.go index b978ee345..a1ba7ad9c 100644 --- a/crypto/ed25519/ed25519_test.go +++ b/crypto/ed25519/ed25519_test.go @@ -2,9 +2,10 @@ package ed25519_test import ( "encoding/hex" - coniks "github.com/coniks-sys/coniks-go/crypto/vrf" "testing" + coniks "github.com/coniks-sys/coniks-go/crypto/vrf" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" diff --git a/crypto/hash.go b/crypto/hash.go index dd1b4c1dd..e1d22523f 100644 --- a/crypto/hash.go +++ b/crypto/hash.go @@ -6,6 +6,6 @@ import ( func Sha256(bytes []byte) []byte { hasher := sha256.New() - hasher.Write(bytes) //nolint:errcheck // ignore error + hasher.Write(bytes) return hasher.Sum(nil) } diff --git a/crypto/merkle/proof_value.go b/crypto/merkle/proof_value.go index 428f29943..ce3214938 100644 --- a/crypto/merkle/proof_value.go +++ b/crypto/merkle/proof_value.go @@ -80,7 +80,7 @@ func (op ValueOp) Run(args [][]byte) ([][]byte, error) { } value := args[0] hasher := tmhash.New() - hasher.Write(value) //nolint: errcheck // does not error + hasher.Write(value) vhash := hasher.Sum(nil) bz := new(bytes.Buffer) diff --git a/evidence/verify_test.go b/evidence/verify_test.go index be811d597..0e74d66c7 100644 --- a/evidence/verify_test.go +++ b/evidence/verify_test.go @@ -31,6 +31,7 @@ func TestVerifyLightClientAttack_Lunatic(t *testing.T) { // use the correct Proof to bypass the checks in libsodium var proof []byte proof, err := commonPrivVals[0].GenerateVRFProof([]byte{}) + require.NoError(t, err) newVal, newPrivVal := types.RandValidatorForPrivKey(types.PrivKeyEd25519, false, 9) diff --git a/mempool/clist_mempool_test.go b/mempool/clist_mempool_test.go index f0716cfbe..2e42e0297 100644 --- a/mempool/clist_mempool_test.go +++ b/mempool/clist_mempool_test.go @@ -682,7 +682,7 @@ func newRemoteApp( func checksumIt(data []byte) string { h := sha256.New() - h.Write(data) // nolint: errcheck // ignore errcheck + h.Write(data) return fmt.Sprintf("%x", h.Sum(nil)) } diff --git a/p2p/pex/addrbook.go b/p2p/pex/addrbook.go index 014186262..a58ba717e 100644 --- a/p2p/pex/addrbook.go +++ b/p2p/pex/addrbook.go @@ -941,6 +941,6 @@ func (a *addrBook) hash(b []byte) ([]byte, error) { if err != nil { return nil, err } - hasher.Write(b) //nolint:errcheck // ignore error + hasher.Write(b) return hasher.Sum(nil), nil } diff --git a/statesync/snapshots.go b/statesync/snapshots.go index ba62b52b8..c9155f3bb 100644 --- a/statesync/snapshots.go +++ b/statesync/snapshots.go @@ -32,9 +32,9 @@ type snapshot struct { func (s *snapshot) Key() snapshotKey { // Hash.Write() never returns an error. hasher := sha256.New() - hasher.Write([]byte(fmt.Sprintf("%v:%v:%v", s.Height, s.Format, s.Chunks))) //nolint:errcheck // ignore error - hasher.Write(s.Hash) //nolint:errcheck // ignore error - hasher.Write(s.Metadata) //nolint:errcheck // ignore error + hasher.Write([]byte(fmt.Sprintf("%v:%v:%v", s.Height, s.Format, s.Chunks))) + hasher.Write(s.Hash) + hasher.Write(s.Metadata) var key snapshotKey copy(key[:], hasher.Sum(nil)) return key diff --git a/test/e2e/app/main.go b/test/e2e/app/main.go index 09a3a23b3..33f7679af 100644 --- a/test/e2e/app/main.go +++ b/test/e2e/app/main.go @@ -4,7 +4,6 @@ import ( "context" "errors" "fmt" - "github.com/line/ostracon/types" "net/http" "os" "path/filepath" @@ -12,6 +11,8 @@ import ( "strings" "time" + "github.com/line/ostracon/types" + "github.com/spf13/viper" "github.com/line/ostracon/abci/server" From 4925f8ed8b952d318c6f12cb2dae4737c70ea9b8 Mon Sep 17 00:00:00 2001 From: tnasu Date: Thu, 23 Dec 2021 11:49:30 +0900 Subject: [PATCH 14/32] Fix codecov for `e2e: integrate light clients (bp #6196)` --- light/rpc/client_test.go | 16 ++++++++++++++++ test/e2e/generator/generate_test.go | 14 ++++++++++++++ 2 files changed, 30 insertions(+) create mode 100644 test/e2e/generator/generate_test.go diff --git a/light/rpc/client_test.go b/light/rpc/client_test.go index 2505fc7bd..8470595f9 100644 --- a/light/rpc/client_test.go +++ b/light/rpc/client_test.go @@ -160,3 +160,19 @@ func testOpDecoder(pop tmcrypto.ProofOp) (merkle.ProofOperator, error) { } return op, nil } + +func TestDefaultMerkleKeyPathFn(t *testing.T) { + f := DefaultMerkleKeyPathFn() + require.NotNil(t, f) + { + path, err := f("", nil) + require.Error(t, err) + require.Nil(t, path) + } + { + path, err := f("/store/test-merkle-path/key", []byte("test")) + require.NoError(t, err) + require.NotNil(t, path) + require.Equal(t, "/test-merkle-path/test", path.String()) + } +} diff --git a/test/e2e/generator/generate_test.go b/test/e2e/generator/generate_test.go new file mode 100644 index 000000000..22b19f79c --- /dev/null +++ b/test/e2e/generator/generate_test.go @@ -0,0 +1,14 @@ +package main + +import ( + "math/rand" + "testing" + + "github.com/stretchr/testify/require" +) + +func TestGenerate(t *testing.T) { + manifests, err := Generate(rand.New(rand.NewSource(randomSeed))) + require.NoError(t, err) + require.NotNil(t, manifests) +} From 378009782e592aa27e46eb169f4799618faa975c Mon Sep 17 00:00:00 2001 From: tnasu Date: Tue, 14 Dec 2021 16:34:47 +0900 Subject: [PATCH 15/32] rpc: index block events to support block event queries (bp #6226) (#6261) --- go.mod | 1 + go.sum | 931 ++++++++++++++++++++++++++ light/proxy/routes.go | 39 +- light/rpc/client.go | 33 +- node/node.go | 37 +- rpc/client/http/http.go | 35 +- rpc/client/interface.go | 21 +- rpc/client/local/local.go | 11 +- rpc/client/mocks/client.go | 23 + rpc/core/blocks.go | 69 ++ rpc/core/env.go | 2 + rpc/core/routes.go | 1 + rpc/core/tx.go | 12 +- rpc/core/types/responses.go | 6 + rpc/openapi/openapi.yaml | 56 ++ state/indexer/block.go | 22 + state/indexer/block/kv/kv.go | 489 ++++++++++++++ state/indexer/block/kv/kv_test.go | 141 ++++ state/indexer/block/kv/util.go | 96 +++ state/indexer/block/null/null.go | 27 + state/indexer/query_range.go | 123 ++++ state/txindex/indexer.go | 9 +- state/txindex/indexer_service.go | 44 +- state/txindex/indexer_service_test.go | 20 +- state/txindex/kv/kv.go | 117 +--- test/e2e/app/app.go | 26 +- test/maverick/node/node.go | 37 +- types/events.go | 4 + 28 files changed, 2258 insertions(+), 174 deletions(-) create mode 100644 state/indexer/block.go create mode 100644 state/indexer/block/kv/kv.go create mode 100644 state/indexer/block/kv/kv_test.go create mode 100644 state/indexer/block/kv/util.go create mode 100644 state/indexer/block/null/null.go create mode 100644 state/indexer/query_range.go diff --git a/go.mod b/go.mod index 922d2b857..23e9237ed 100644 --- a/go.mod +++ b/go.mod @@ -15,6 +15,7 @@ require ( github.com/go-logfmt/logfmt v0.5.0 github.com/gogo/protobuf v1.3.2 github.com/golang/protobuf v1.4.3 + github.com/google/orderedcode v0.0.1 github.com/gorilla/websocket v1.4.2 github.com/gtank/merlin v0.1.1 github.com/herumi/bls-eth-go-binary v0.0.0-20200923072303-32b29e5d8cbf diff --git a/go.sum b/go.sum index b47f74c33..39ce3f049 100644 --- a/go.sum +++ b/go.sum @@ -1,3 +1,6 @@ +4d63.com/gochecknoglobals v0.1.0/go.mod h1:wfdC5ZjKSPr7CybKEcgJhUOgeAQW1+7WcyK8OvUilfo= +bazil.org/fuse v0.0.0-20200407214033-5883e5a4b512/go.mod h1:FbcW6z/2VytnFDhZfumh8Ss8zxHE6qpMP5sHTRe0EaM= +bitbucket.org/creachadair/shell v0.0.6/go.mod h1:8Qqi/cYk7vPnsOePHroKXDJYmb5x7ENhtiFtfZq8K+M= 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= cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= @@ -5,29 +8,88 @@ cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6A cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= +cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= +cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= +cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= +cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= +cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk= +cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= +cloud.google.com/go v0.60.0/go.mod h1:yw2G51M9IfRboUH61Us8GqCeF1PzPblB823Mn2q2eAU= +cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= +cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= +cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI= +cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk= +cloud.google.com/go v0.78.0/go.mod h1:QjdrLG0uq+YwhjoVOLsS1t7TW8fs36kLs4XO5R5ECHg= +cloud.google.com/go v0.79.0/go.mod h1:3bzgcEeQlzbuEAYu4mrWhKqWjmpprinYgKJLgKHnbb8= +cloud.google.com/go v0.81.0/go.mod h1:mk/AM35KwGk/Nm2YSeZbxXdrNK3KZOYHmLkOqC2V6E0= +cloud.google.com/go v0.83.0/go.mod h1:Z7MJUsANfY0pYPdw0lbnivPx4/vhy/e2FEkSkF7vAVY= +cloud.google.com/go v0.84.0/go.mod h1:RazrYuxIK6Kb7YrzzhPoLmCVzl7Sup4NrbKPg8KHSUM= +cloud.google.com/go v0.87.0/go.mod h1:TpDYlFy7vuLzZMMZ+B6iRiELaY7z/gJPaqbMx6mlWcY= +cloud.google.com/go v0.90.0/go.mod h1:kRX0mNRHe0e2rC6oNakvwQqzyDmg57xJ+SZU1eT2aDQ= +cloud.google.com/go v0.93.3/go.mod h1:8utlLll2EF5XMAV15woO4lSbWQlk8rer9aLOfLh7+YI= cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= +cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= +cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= +cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= +cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= +cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= +cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk= +cloud.google.com/go/firestore v1.6.0/go.mod h1:afJwI0vaXwAG54kI7A//lP/lSPDkQORQuMkv56TxEPU= cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= +cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= +cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= +cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= +cloud.google.com/go/pubsub v1.5.0/go.mod h1:ZEwJccE3z93Z2HWvstpri00jOg7oO4UZDtKhwDwqF0w= +cloud.google.com/go/spanner v1.7.0/go.mod h1:sd3K2gZ9Fd0vMPLXzeCrF6fq4i63Q7aTLW/lBIfBkIk= cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= +cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= +cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= +cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= +cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= +contrib.go.opencensus.io/exporter/stackdriver v0.13.4/go.mod h1:aXENhDJ1Y4lIg4EUaVTwzvYETVNZk10Pu26tevFKLUc= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= gioui.org v0.0.0-20210308172011-57750fc8a0a6/go.mod h1:RSH6KIUZ0p2xy5zHDxgAM4zumjgTw83q2ge/PI+yyw8= +github.com/Antonboom/errname v0.1.5/go.mod h1:DugbBstvPFQbv/5uLcRRzfrNqKE9tVdVCqWCLp6Cifo= +github.com/Antonboom/nilnil v0.1.0/go.mod h1:PhHLvRPSghY5Y7mX4TW+BHZQYo1A8flE5H20D3IPZBo= +github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/BurntSushi/toml v0.4.1 h1:GaI7EiDXDRfa8VshkTj7Fym7ha+y8/XxIgD2okUIjLw= +github.com/BurntSushi/toml v0.4.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/ChainSafe/go-schnorrkel v0.0.0-20200405005733-88cbf1b4c40d h1:nalkkPQcITbvhmL4+C4cKA87NW0tfm3Kl9VXRoPywFg= github.com/ChainSafe/go-schnorrkel v0.0.0-20200405005733-88cbf1b4c40d/go.mod h1:URdX5+vg25ts3aCh8H5IFZybJYKWhJHYMTnf+ULtoC4= +github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ= github.com/DataDog/zstd v1.4.1 h1:3oxKN3wbHibqx897utPC2LTQU4J+IHWWJO+glkAkpFM= github.com/DataDog/zstd v1.4.1/go.mod h1:1jcaCB/ufaK+sKp1NBhlGmpz41jOoPQ35bpF36t7BBo= +github.com/Djarvur/go-err113 v0.0.0-20210108212216-aea10b59be24/go.mod h1:4UJr5HIiMZrwgkSPdsjy2uOQExX/WEILpIrO9UPGuXs= +github.com/HdrHistogram/hdrhistogram-go v1.1.0/go.mod h1:yDgFjdqOqDEKOvasDdhWNXYg9BVp4O+o5f6V/ehm6Oo= +github.com/HdrHistogram/hdrhistogram-go v1.1.2/go.mod h1:yDgFjdqOqDEKOvasDdhWNXYg9BVp4O+o5f6V/ehm6Oo= github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0= +github.com/Masterminds/goutils v1.1.0/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU= +github.com/Masterminds/semver v1.4.2/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= +github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= +github.com/Masterminds/sprig v2.15.0+incompatible/go.mod h1:y6hNFY5UBTIWBxnzTeuNhlNS5hqE0NB0E6fgfo2Br3o= +github.com/Masterminds/sprig v2.22.0+incompatible/go.mod h1:y6hNFY5UBTIWBxnzTeuNhlNS5hqE0NB0E6fgfo2Br3o= +github.com/Microsoft/go-winio v0.5.0/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84= +github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5/go.mod h1:lmUJ/7eu/Q8D7ML55dXQrVaamCz2vxCfdQBasLZfHKk= github.com/OneOfOne/xxhash v1.2.2 h1:KMrpdQIwFcEqXDklaen+P1axHaj9BSKzvpUUfnHldSE= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= +github.com/OpenPeeDeeP/depguard v1.0.1/go.mod h1:xsIw86fROiiwelg+jB2uM9PiKihMMmUx/1V+TNhjQvM= 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/StackExchange/wmi v1.2.1/go.mod h1:rcmrprowKIVzvc+NUiLncP2uuArMWLCbu9SBzvHz7e8= 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.2 h1:gYV7WT8obGQ3dgnzndt+7Ukr9IYRkkRaPNkahB2v8iI= +github.com/Workiva/go-datastructures v1.0.2/go.mod h1:Z+F2Rca0qCsVYDS8z7bAGm8f3UkzuWYS/oBZz5a7VVA= github.com/Workiva/go-datastructures v1.0.52 h1:PLSK6pwn8mYdaoaCZEMsXBpBotr4HHn9abU0yMQt0NI= github.com/Workiva/go-datastructures v1.0.52/go.mod h1:Z+F2Rca0qCsVYDS8z7bAGm8f3UkzuWYS/oBZz5a7VVA= +github.com/Workiva/go-datastructures v1.0.53 h1:J6Y/52yX10Xc5JjXmGtWoSSxs3mZnGSaq37xZZh7Yig= +github.com/Workiva/go-datastructures v1.0.53/go.mod h1:1yZL+zfsztete+ePzZz/Zb1/t5BnDuE2Ya2MMGhzP6A= +github.com/adlio/schema v1.1.14/go.mod h1:hQveFEMiDlG/M9yz9RAajnH5DzT6nAfqOG9YkEQU2pg= 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/ajstarks/svgo v0.0.0-20180226025133-644b8db467af/go.mod h1:K08gAheRH3/J6wwsYMMT4xOr94bZjxIelGM0+d/wbFw= @@ -36,30 +98,58 @@ github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuy 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/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= +github.com/alexkohler/prealloc v1.0.0/go.mod h1:VetnK3dIgFBBKmg0YnD9F9x6Icjd+9cvfHR56wJVlKE= +github.com/andybalholm/brotli v1.0.2/go.mod h1:loMXtMfwqflxFJPmdbJO0a3KNoPuLBgiu3qAvBg8x/Y= +github.com/andybalholm/brotli v1.0.3/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= +github.com/antihax/optional v0.0.0-20180407024304-ca021399b1a6/go.mod h1:V8iCPQYkqmusNa815XgQio277wI47sdRh1dUOLdyC6Q= +github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= +github.com/aokoli/goutils v1.0.1/go.mod h1:SijmP0QR8LtwsmDs8Yii5Z/S4trXFGFC2oO5g9DP+DQ= 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-metrics v0.3.9/go.mod h1:4O98XIr/9W0sxpJ8UaYkvjk10Iff7SnFrb4QAOwNTFc= github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= +github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= github.com/aryann/difflib v0.0.0-20170710044230-e206f873d14a/go.mod h1:DAHtR1m6lCRdSC2Tm3DSWRPvIPr6xNKyeHdqDQSQT+A= +github.com/ashanbrown/forbidigo v1.2.0/go.mod h1:vVW7PEdqEFqapJe95xHkTfB1+XvZXBFg8t0sG2FIxmI= +github.com/ashanbrown/makezero v0.0.0-20210520155254-b6261585ddde/go.mod h1:oG9Dnez7/ESBqc4EdrdNlryeo7d0KcW1ftXHm7nU/UU= github.com/aws/aws-lambda-go v1.13.3/go.mod h1:4UKl9IzQMoD+QF79YdCuzCwp8VbmG4VAQwij/eHl5CU= +github.com/aws/aws-sdk-go v1.23.20/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= +github.com/aws/aws-sdk-go v1.25.37/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= github.com/aws/aws-sdk-go v1.27.0/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= +github.com/aws/aws-sdk-go v1.36.30/go.mod h1:hcU610XS61/+aQV88ixoOzUoG7v3b31pl2zKMmprdro= +github.com/aws/aws-sdk-go v1.40.45/go.mod h1:585smgzpB/KqRA+K3y/NL/oYRqQvpNJYvLm+LY1U59Q= github.com/aws/aws-sdk-go-v2 v0.18.0/go.mod h1:JWVYvqSMppoMJC0x5wdwiImzgXTI9FuZwxzkQq9wy+g= +github.com/aws/aws-sdk-go-v2 v1.9.1/go.mod h1:cK/D0BBs0b/oWPIcX/Z/obahJK1TT7IPVjy53i/mX/4= +github.com/aws/aws-sdk-go-v2/service/cloudwatch v1.8.1/go.mod h1:CM+19rL1+4dFWnOQKwDc7H1KwXTz+h61oUSHyhV0b3o= +github.com/aws/smithy-go v1.8.0/go.mod h1:SObp3lf9smib00L/v3U2eAKG8FyQ7iLrJnQiAmR5n+E= +github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= 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/bits-and-blooms/bitset v1.2.0/go.mod h1:gIdJ4wp64HaoK2YrL1Q5/N7Y16edYb8uY+O0FJTyyDA= github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84= +github.com/bketelsen/crypt v0.0.4/go.mod h1:aI6NrJ0pMGgvZKL1iVgXLnfIFJtfV+bKCoqOes/6LfM= +github.com/bkielbasa/cyclop v1.2.0/go.mod h1:qOI0yy6A7dYC4Zgsa72Ppm9kONl0RoIlPbzot9mhmeI= +github.com/blizzy78/varnamelen v0.3.0/go.mod h1:hbwRdBvoBqxk34XyQ6HA0UH3G0/1TKuv5AC4eaBT0Ec= +github.com/bombsimon/wsl/v3 v3.3.0/go.mod h1:st10JtZYLE4D5sC7b8xV4zTKZwAQjCH/Hy2Pm1FNZIc= github.com/boombuler/barcode v1.0.0/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8= +github.com/breml/bidichk v0.1.1/go.mod h1:zbfeitpevDUGI7V91Uzzuwrn4Vls8MoBMrwtt78jmso= github.com/btcsuite/btcd v0.20.1-beta/go.mod h1:wVuoA8VJLEcwgqHBwHmzLRazpKxTv13Px/pDuV7OomQ= github.com/btcsuite/btcd v0.21.0-beta h1:At9hIZdJW0s9E/fAz28nrz6AmcNlSVucCH796ZteX1M= github.com/btcsuite/btcd v0.21.0-beta/go.mod h1:ZSWyehm27aAuS9bvkATT+Xte3hjHZ+MRgMY/8NJ7K94= +github.com/btcsuite/btcd v0.22.0-beta h1:LTDpDKUM5EeOFBPM8IXpinEcmZ6FWfNZbE3lfrfdnWo= +github.com/btcsuite/btcd v0.22.0-beta/go.mod h1:9n5ntfhhHQBIhUvlhDvD3Qg6fRUj4jkN0VB8L8svzOA= github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f/go.mod h1:TdznJufoqS23FtqVCzL0ZqgP5MqXbb4fg/WgDys70nA= github.com/btcsuite/btcutil v0.0.0-20190425235716-9e5f4b9a998d/go.mod h1:+5NJ2+qvTyV9exUAL/rxXi3DcLg2Ts+ymUAY5y4NvMg= github.com/btcsuite/btcutil v1.0.2 h1:9iZ1Terx9fMIOtq1VrwdqfsATL9MC2l8ZrUY6YZ2uts= github.com/btcsuite/btcutil v1.0.2/go.mod h1:j9HUFwoQRsZL3V4n+qG+CUnEGHOarIxfC3Le2Yhbcts= +github.com/btcsuite/btcutil v1.0.3-0.20201208143702-a53e38424cce h1:YtWJF7RHm2pYCvA5t0RPmAaLUhREsKuKd+SLhxFbFeQ= +github.com/btcsuite/btcutil v1.0.3-0.20201208143702-a53e38424cce/go.mod h1:0DVlHczLPewLcPGEIeUEzfOJhqGPQ0mJJRDBtD307+o= github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd/go.mod h1:HHNXQzUsZCxOoE+CPiyCTO6x34Zs86zZUiwtpXoGdtg= github.com/btcsuite/goleveldb v0.0.0-20160330041536-7834afc9e8cd/go.mod h1:F+uVaaLLH7j4eDXPRvw78tMflu7Ie2bzYOH4Y8rRKBY= github.com/btcsuite/goleveldb v1.0.0/go.mod h1:QiK9vBlgftBg6rWQIj6wFzbPfRjiykIEhBH4obrXJ/I= @@ -67,22 +157,46 @@ github.com/btcsuite/snappy-go v0.0.0-20151229074030-0bdef8d06723/go.mod h1:8woku github.com/btcsuite/snappy-go v1.0.0/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/butuzov/ireturn v0.1.1/go.mod h1:Wh6Zl3IMtTpaIKbmwzqi6olnM9ptYQxxVacMsOEFPoc= github.com/casbin/casbin/v2 v2.1.2/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n9yuLkIJQ= +github.com/casbin/casbin/v2 v2.37.0/go.mod h1:vByNa/Fchek0KZUgG5wEsl7iFsiviAYKRtgrQfcJqHg= github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM= +github.com/cenkalti/backoff/v4 v4.1.1/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw= 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/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE= +github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/charithe/durationcheck v0.0.9/go.mod h1:SSbRIBVfMjCi/kEB6K65XEA83D6prSM8ap1UCpNKtgg= +github.com/chavacava/garif v0.0.0-20210405164556-e8a0a408d6af/go.mod h1:Qjyv4H3//PWVzTeCezG2b9IRn6myJxJSr4TD/xo6ojU= +github.com/checkpoint-restore/go-criu/v5 v5.0.0/go.mod h1:cfwC0EG7HMUenopBsUf9d89JlCLQIfgVcNsNN0t6T2M= +github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= +github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= +github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= +github.com/cilium/ebpf v0.6.2/go.mod h1:4tRaxcgiL706VnOzHOdBlY8IEAIdxINsQBcU4xJJXRs= +github.com/circonus-labs/circonus-gometrics v2.3.1+incompatible/go.mod h1:nmEj6Dob7S7YxXgwXpfOuvO54S+tGdZdw9fuRZt25Ag= +github.com/circonus-labs/circonusllhist v0.1.3/go.mod h1:kMXHVDlOchFAehlya5ePtbp5jckzBHf4XRpQvBOLI+I= +github.com/clbanning/mxj v1.8.4/go.mod h1:BVjHeAH+rl9rs6f+QIpeRl0tfu10SXn1pUSa5PVGJng= 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/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= +github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= +github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI= +github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= 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/confio/ics23/go v0.6.3 h1:PuGK2V1NJWZ8sSkNDq91jgT/cahFEW9RGp4Y5jxulf0= github.com/confio/ics23/go v0.6.3/go.mod h1:E45NqnlpxGnpfTWL/xauN7MRwEE28T4Dd4uraToOaKg= github.com/coniks-sys/coniks-go v0.0.0-20180722014011-11acf4819b71 h1:MFLTqgfJclmtaQ1SRUrWwmDX/1UBok3XWUethkJ2swQ= github.com/coniks-sys/coniks-go v0.0.0-20180722014011-11acf4819b71/go.mod h1:TrHYHH4Wze7v7Hkwu1MH1W+mCPQKM+gs+PicdEV14o8= +github.com/containerd/console v1.0.2/go.mod h1:ytZPjGgY2oeTkAONYafi2kSj0aYggsf8acV1PGKCbzQ= +github.com/containerd/continuity v0.2.0/go.mod h1:wCYX+dRqZdImhGucXOqTQn05AhX6EUDaGEMUzTFFpLg= 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/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= @@ -91,6 +205,8 @@ github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3Ee github.com/coreos/go-semver v0.3.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/go-systemd v0.0.0-20190620071333-e64a0ec8b42a/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= 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= @@ -99,11 +215,16 @@ github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwc github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/cyphar/filepath-securejoin v0.2.2/go.mod h1:FpkQEhXnPnOthhzymB7CGsFk2G9VLXONKD9G7QGMM+4= +github.com/daixiang0/gci v0.2.9/go.mod h1:+4dZ7TISfSmqfAGv59ePaHfNzgGtIkHAhhdKggP1JAc= +github.com/davecgh/go-spew v0.0.0-20161028175848-04cdfd42973b/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 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/decred/dcrd/lru v1.0.0/go.mod h1:mxKOwFd7lFjN2GZYsiz/ecgqR6kkYAl+0pz0tEMk218= +github.com/denis-tingajkin/go-header v0.4.2/go.mod h1:eLRHAVXzE5atsKAnNRDB90WHCFFnBUn4RN0nRcs1LJA= github.com/dgraph-io/badger/v2 v2.2007.2 h1:EjjK0KqwaFMlPin1ajhP943VPENHJdEz1KLIegjaI3k= github.com/dgraph-io/badger/v2 v2.2007.2/go.mod h1:26P/7fbL4kUZVEVKLAKXkBXKOydDmM2p1e+NhhnBCAE= github.com/dgraph-io/ristretto v0.0.3-0.20200630154024-f66de99634de h1:t0UHb5vdojIDUqktM6+xJAfScFBsVpXZmqC9dsgJmeA= @@ -112,6 +233,8 @@ github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZm github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2 h1:tdlZCpZ/P9DhczCTSixgIKmwPv6+wP5DGjqLYw5SUiA= github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw= github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= +github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec= +github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo= github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= @@ -122,61 +245,134 @@ github.com/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaB github.com/envoyproxy/go-control-plane v0.6.9/go.mod h1:SBwIajubJHhxtWwsL9s8ss4safvEdbitLhGGK48rN6g= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= +github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po= github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= +github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= +github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ= +github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0= +github.com/envoyproxy/protoc-gen-validate v0.0.14/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/esimonov/ifshort v1.0.3/go.mod h1:yZqNJUrNn20K8Q9n2CrjTKYyVEmX209Hgu+M1LBpeZE= +github.com/ettle/strcase v0.1.1/go.mod h1:hzDLsPC7/lwKyBOywSHEP89nt2pDgdy+No1NBA9o9VY= +github.com/facebookgo/ensure v0.0.0-20160127193407-b4ab57deab51 h1:0JZ+dUmQeA8IIVUMzysrX4/AKuQwWhV2dYQuPZdvdSQ= +github.com/facebookgo/ensure v0.0.0-20160127193407-b4ab57deab51/go.mod h1:Yg+htXGokKKdzcwhuNDwVvN+uBxDGXJ7G/VN1d8fa64= github.com/facebookgo/ensure v0.0.0-20200202191622-63f1cf65ac4c h1:8ISkoahWXwZR41ois5lSJBSVw4D0OV19Ht/JSTzvSv0= github.com/facebookgo/ensure v0.0.0-20200202191622-63f1cf65ac4c/go.mod h1:Yg+htXGokKKdzcwhuNDwVvN+uBxDGXJ7G/VN1d8fa64= github.com/facebookgo/stack v0.0.0-20160209184415-751773369052 h1:JWuenKqqX8nojtoVVWjGfOF9635RETekkoH6Cc9SX0A= 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/facebookgo/subset v0.0.0-20200203212716-c811ad88dec4 h1:7HZCaLC5+BZpmbhCOZJ293Lz68O7PYrF2EzeiFMwCLk= github.com/facebookgo/subset v0.0.0-20200203212716-c811ad88dec4/go.mod h1:5tD+neXqOorC30/tWg0LCSkrqj/AR6gu8yY8/fpw1q0= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= +github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU= +github.com/fatih/color v1.10.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM= +github.com/fatih/color v1.12.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM= +github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= +github.com/fatih/structtag v1.2.0/go.mod h1:mBJUNpUnHmRKrKlQQlmCrh5PuhftFbNv8Ys4/aAZl94= github.com/fogleman/gg v1.2.1-0.20190220221249-0403632d5b90/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k= github.com/fogleman/gg v1.3.0/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k= 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/goblin v0.0.0-20210519012713-85d372ac71e2/go.mod h1:VzmDKDJVZI3aJmnRI9VjAn9nJ8qPPsN1fqzr9dqInIo= github.com/franela/goreq v0.0.0-20171204163338-bcd34c9993f8/go.mod h1:ZhphrRTfi2rbfLwlschooIH4+wKKDR4Pdxhh+TRoA20= +github.com/frankban/quicktest v1.11.3/go.mod h1:wRf/ReqHper53s+kmmSZizM8NamnL3IM0I9ntUbOk+k= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= +github.com/fsnotify/fsnotify v1.5.1 h1:mZcQUHVQUQWoPXXtuf9yuEXKudkV2sx1E06UadKWpgI= +github.com/fsnotify/fsnotify v1.5.1/go.mod h1:T3375wBYaZdLLcVNkcVbzGHY7f1l/uK5T5Ai1i3InKU= +github.com/fullstorydev/grpcurl v1.6.0/go.mod h1:ZQ+ayqbKMJNhzLmbpCiurTVlaK2M/3nqZCxaQ2Ze/sM= +github.com/fzipp/gocyclo v0.3.1/go.mod h1:DJHO6AUmbdqj2ET4Z9iArSuwWgYDRryYt2wASxc7x3E= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= +github.com/gin-gonic/gin v1.5.0/go.mod h1:Nd6IXA8m5kNZdNEHMBd93KT+mdY3+bewLgRvmCsR2Do= +github.com/go-critic/go-critic v0.6.1/go.mod h1:SdNCfU0yF3UBjtaZGw6586/WocupMOJuiqgom5DsQxM= github.com/go-fonts/dejavu v0.1.0/go.mod h1:4Wt4I4OU2Nq9asgDCteaAaWZOV24E+0/Pwo0gppep4g= github.com/go-fonts/latin-modern v0.2.0/go.mod h1:rQVLdDMK+mK1xscDwsqM5J8U2jrRa3T0ecnM9pNujks= github.com/go-fonts/liberation v0.1.1/go.mod h1:K6qoJYypsmfVjWg8KOVDQhLc8UDgIK2HYqyqAO9z7GY= github.com/go-fonts/stix v0.1.0/go.mod h1:w/c1f0ldAUlJmLBvlbkvVXLAD+tAMqobIIQpmnUIzUY= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= 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-kit/kit v0.12.0 h1:e4o3o3IsBfAKQh5Qbbiqyfu97Ku7jrO/JbohvztANh4= +github.com/go-kit/kit v0.12.0/go.mod h1:lHd+EkCZPIwYItmGDDRdhinkzX2A1sj+M9biaEaizzs= +github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= +github.com/go-kit/log v0.2.0 h1:7i2K3eKTos3Vc0enKCfnVcgHh2olr/MyfboYq7cAcFw= +github.com/go-kit/log v0.2.0/go.mod h1:NwTd00d/i8cPZ3xOwwiv2PO5MOcx78fFErGNcVmBjv0= github.com/go-latex/latex v0.0.0-20210118124228-b3d85cf34e07/go.mod h1:CO1AlKB2CSIqUrmQPqA0gdRIlnLEY0gK5JGjh37zN5U= github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= 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-logfmt/logfmt v0.5.1 h1:otpy5pqBCBZ1ng9RQ0dPu4PN7ba75Y/aA+UpowDyNVA= +github.com/go-logfmt/logfmt v0.5.1/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs= +github.com/go-ole/go-ole v1.2.5/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= +github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= +github.com/go-playground/locales v0.12.1/go.mod h1:IUMDtCfWo/w/mtMfIE/IG2K+Ey3ygWanZIBtBW0W2TM= +github.com/go-playground/universal-translator v0.16.0/go.mod h1:1AnU7NaIRDWWzGEKwgtJRd2xk99HeFyHw3yid4rvQIY= +github.com/go-redis/redis v6.15.8+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA= github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= +github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= 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/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= +github.com/go-toolsmith/astcast v1.0.0/go.mod h1:mt2OdQTeAQcY4DQgPSArJjHCcOwlX+Wl/kwN+LbLGQ4= +github.com/go-toolsmith/astcopy v1.0.0/go.mod h1:vrgyG+5Bxrnz4MZWPF+pI4R8h3qKRjjyvV/DSez4WVQ= +github.com/go-toolsmith/astequal v1.0.0/go.mod h1:H+xSiq0+LtiDC11+h1G32h7Of5O3CYFJ99GVbS5lDKY= +github.com/go-toolsmith/astequal v1.0.1/go.mod h1:4oGA3EZXTVItV/ipGiOx7NWkY5veFfcsOJVS2YxltLw= +github.com/go-toolsmith/astfmt v1.0.0/go.mod h1:cnWmsOAuq4jJY6Ct5YWlVLmcmLMn1JUPuQIHCY7CJDw= +github.com/go-toolsmith/astinfo v0.0.0-20180906194353-9809ff7efb21/go.mod h1:dDStQCHtmZpYOmjRP/8gHHnCCch3Zz3oEgCdZVdtweU= +github.com/go-toolsmith/astp v1.0.0/go.mod h1:RSyrtpVlfTFGDYRbrjyWP1pYu//tSFcvdYrA8meBmLI= +github.com/go-toolsmith/pkgload v1.0.0/go.mod h1:5eFArkbO80v7Z0kdngIxsRXRMTaX4Ilcwuh3clNrQJc= +github.com/go-toolsmith/strparse v1.0.0/go.mod h1:YI2nUKP9YGZnL/L1/DLFBfixrcjslWct4wyljWhSRy8= +github.com/go-toolsmith/typep v1.0.0/go.mod h1:JSQCQMUPdRlMZFswiq3TGpNp1GMktqkR2Ns5AIQkATU= +github.com/go-toolsmith/typep v1.0.2/go.mod h1:JSQCQMUPdRlMZFswiq3TGpNp1GMktqkR2Ns5AIQkATU= +github.com/go-xmlfmt/xmlfmt v0.0.0-20191208150333-d5b6f63a941b/go.mod h1:aUCEOzzezBEjDBbFBoSiya/gduyIiWYRP6CnSFIV8AM= +github.com/go-zookeeper/zk v1.0.2/go.mod h1:nOB03cncLtlp4t+UAkGSV+9beXP/akpekBwL+UX1Qcw= +github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8= +github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= +github.com/gofrs/flock v0.8.1/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU= 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/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= +github.com/gogo/protobuf v1.3.0/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/golang-jwt/jwt/v4 v4.0.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg= github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k= 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/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= +github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= +github.com/golang/mock v1.5.0/go.mod h1:CWnOUgYIOo4TcNZ0wHX3YZCqsaM1I1Jvs6v3mP3KVu8= +github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs= +github.com/golang/protobuf v1.1.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.0/go.mod h1:Qd/q+1AKNOZr9uGQzbzCmRO6sUih6GTPZv6a1/R87v0= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= @@ -186,42 +382,117 @@ github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QD github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.4.3 h1:JjCZWpVbqXDqFVmTfYWEVTMIYrL/NPdPSCHPJ0T/raM= github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx4u74HPM= +github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= +github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= 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/golang/snappy v0.0.3 h1:fHPg5GQYlCeLIPB9BZqMVR5nR9A+IM5zcgeTdjMYmLA= +github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/golangci/check v0.0.0-20180506172741-cfe4005ccda2/go.mod h1:k9Qvh+8juN+UKMCS/3jFtGICgW8O96FVaZsaxdzDkR4= +github.com/golangci/dupl v0.0.0-20180902072040-3e9179ac440a/go.mod h1:ryS0uhF+x9jgbj/N71xsEqODy9BN81/GonCZiOzirOk= +github.com/golangci/go-misc v0.0.0-20180628070357-927a3d87b613/go.mod h1:SyvUF2NxV+sN8upjjeVYr5W7tyxaT1JVtvhKhOn2ii8= +github.com/golangci/gofmt v0.0.0-20190930125516-244bba706f1a/go.mod h1:9qCChq59u/eW8im404Q2WWTrnBUQKjpNYKMbU4M7EFU= +github.com/golangci/golangci-lint v1.43.0/go.mod h1:VIFlUqidx5ggxDfQagdvd9E67UjMXtTHBkBQ7sHoC5Q= +github.com/golangci/lint-1 v0.0.0-20191013205115-297bf364a8e0/go.mod h1:66R6K6P6VWk9I95jvqGxkqJxVWGFy9XlDwLwVz1RCFg= +github.com/golangci/maligned v0.0.0-20180506175553-b1d89398deca/go.mod h1:tvlJhZqDe4LMs4ZHD0oMUlt9G2LWuDGoisJTBzLMV9o= +github.com/golangci/misspell v0.3.5/go.mod h1:dEbvlSfYbMQDtrpRMQU675gSDLDNa8sCPPChZ7PhiVA= +github.com/golangci/revgrep v0.0.0-20210930125155-c22e5001d4f2/go.mod h1:LK+zW4MpyytAWQRz0M4xnzEk50lSvqDQKfx304apFkY= +github.com/golangci/unconvert v0.0.0-20180507085042-28b1c447d1f4/go.mod h1:Izgrg8RkN3rCIMLGE9CyYmU9pY2Jer6DgANEnZ/L/cQ= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.0 h1:0udJVsspx3VBr5FwtLhQQtuAsVc79tTq0ocGIPAU6qo= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/certificate-transparency-go v1.0.21/go.mod h1:QeJfpSbVSfYc7RgB3gJFj9cbuQMMchQxrWXz8Ruopmg= +github.com/google/certificate-transparency-go v1.1.1/go.mod h1:FDKqPvSXawb2ecErVRrD+nfy23RCzyl7eqVCEmlT1Zs= 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/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.0 h1:/QaMHBdZ26BB3SSst0Iwl10Epc+xhTquomWX0oZEB6w= github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ= +github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/gofuzz v0.0.0-20170612174753-24818f796faf/go.mod h1:HP5RmnzzSNb993RKQDq4+1A4ia9nllfqcQFTQJedwGI= 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/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= +github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= +github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= +github.com/google/martian/v3 v3.2.1/go.mod h1:oBOf6HBosgwRXnUGWUB05QECsc6uvmMiJ3+6W4l/CUk= +github.com/google/orderedcode v0.0.1 h1:UzfcAexk9Vhv8+9pNOgRu41f16lHq725vPwnSeiG/Us= +github.com/google/orderedcode v0.0.1/go.mod h1:iVyU4/qPKHY5h/wSd6rZZCDcLJNxiWO6dvsYES2Sb20= github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200507031123-427632fa3b1c/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210122040257-d980be63207e/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210601050228-01bbb1931b22/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210609004039-a478d1d731e9/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= +github.com/google/trillian v1.3.11/go.mod h1:0tPraVHrSDkA3BO6vKX67zgLXs6SsOAbHEivX+9mPgw= +github.com/google/uuid v0.0.0-20161128191214-064e2069ce9c/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= +github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= +github.com/googleapis/gax-go/v2 v2.1.0/go.mod h1:Q3nei7sK6ybPYH7twZdmQpAd1MKb7pfu6SK+H1/DsU0= +github.com/gookit/color v1.4.2/go.mod h1:fqRyamkC1W8uxl+lxCQxOT09l/vYfZ+QeiX3rKQHCoQ= 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/gordonklaus/ineffassign v0.0.0-20200309095847-7953dde2c7bf/go.mod h1:cuNKsD1zp2v6XfE/orVX2QE1LC+i254ceGcVeDT3pTU= +github.com/gordonklaus/ineffassign v0.0.0-20210225214923-2e10b2664254/go.mod h1:M9mZEtGIsR1oDaZagNPNG9iq9n2HrhZ17dsXk73V3Lw= +github.com/gorhill/cronexpr v0.0.0-20180427100037-88b0669f7d75/go.mod h1:g2644b03hfBX9Ov0ZBDgXXens4rxSxmqFBbhvKv2yVA= 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/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= 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/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc= github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/gostaticanalysis/analysisutil v0.0.0-20190318220348-4088753ea4d3/go.mod h1:eEOZF4jCKGi+aprrirO9e7WKB3beBRtWgqGunKl6pKE= +github.com/gostaticanalysis/analysisutil v0.0.3/go.mod h1:eEOZF4jCKGi+aprrirO9e7WKB3beBRtWgqGunKl6pKE= +github.com/gostaticanalysis/analysisutil v0.1.0/go.mod h1:dMhHRU9KTiDcuLGdy87/2gTR8WruwYZrKdRq9m1O6uw= +github.com/gostaticanalysis/analysisutil v0.4.1/go.mod h1:18U/DLpRgIUd459wGxVHE0fRgmo1UgHDcbw7F5idXu0= +github.com/gostaticanalysis/analysisutil v0.7.1/go.mod h1:v21E3hY37WKMGSnbsw2S/ojApNWb6C1//mXO48CXbVc= +github.com/gostaticanalysis/comment v1.3.0/go.mod h1:xMicKDx7XRXYdVwY9f9wQpDJVnqWxw9wCauCMKp+IBI= +github.com/gostaticanalysis/comment v1.4.1/go.mod h1:ih6ZxzTHLdadaiSnF5WY3dxUoXfXAlTaRzuaNDlSado= +github.com/gostaticanalysis/comment v1.4.2/go.mod h1:KLUTGDv6HOCotCH8h2erHKmpci2ZoR8VPu34YA2uzdM= +github.com/gostaticanalysis/forcetypeassert v0.0.0-20200621232751-01d4955beaa5/go.mod h1:qZEedyP/sY1lTGV1uJ3VhWZ2mqag3IkWsDHVbplHXak= +github.com/gostaticanalysis/nilerr v0.1.1/go.mod h1:wZYb6YI5YAxxq0i1+VJbY0s2YONW0HU0GPE3+5PWN4A= +github.com/gostaticanalysis/testutil v0.3.1-0.20210208050101-bfb5c8eec0e4/go.mod h1:D+FIZ+7OahH3ePw/izIEeH5I06eKs1IKI4Xr64/Am3M= +github.com/gostaticanalysis/testutil v0.4.0/go.mod h1:bLIoPefWXrRi/ssLFWX1dx7Repi5x3CuviD3dgAZaBU= +github.com/gotestyourself/gotestyourself v2.2.0+incompatible/go.mod h1:zZKM6oeNM8k+FRljX1mnzVYeS8wiGgQyvST1/GafPbY= +github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= 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-middleware v1.2.2/go.mod h1:EaizFBKfUKtMIF5iaDEhniwNedqGo9FuLFzppDr3uwI= +github.com/grpc-ecosystem/go-grpc-middleware v1.3.0/go.mod h1:z0ButlSOZa5vEBq9m2m2hlwIgKw+rp3sdCBRoJY+30Y= 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/grpc-ecosystem/grpc-gateway v1.12.1/go.mod h1:8XEsbTttt/W+VvjtQhLACqCisSPWTxCZ7sBRjU6iH9c= +github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= github.com/gtank/merlin v0.1.1-0.20191105220539-8318aed1a79f/go.mod h1:T86dnYJhcGOh5BjZFCJWTDeTK7XW8uE+E21Cy/bIQ+s= github.com/gtank/merlin v0.1.1 h1:eQ90iG7K9pOhtereWsmyRJ6RAwcP4tHTDBHXNg+u5is= github.com/gtank/merlin v0.1.1/go.mod h1:T86dnYJhcGOh5BjZFCJWTDeTK7XW8uE+E21Cy/bIQ+s= @@ -229,146 +500,310 @@ github.com/gtank/ristretto255 v0.1.2 h1:JEqUCPA1NvLq5DwYtuzigd7ss8fwbYay9fi4/5uM github.com/gtank/ristretto255 v0.1.2/go.mod h1:Ph5OpO6c7xKUGROZfWVLiJf9icMDwUeIvY4OmlYW69o= github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q= github.com/hashicorp/consul/api v1.3.0/go.mod h1:MmDNSzIMUjNpY/mQ398R4bk2FnqQLoPndWW5VkKPlCE= +github.com/hashicorp/consul/api v1.10.1/go.mod h1:XjsvQN+RJGWI2TWy1/kqaE16HrR2J/FWgkYjdZQsX9M= github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= github.com/hashicorp/consul/sdk v0.3.0/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= +github.com/hashicorp/consul/sdk v0.8.0/go.mod h1:GBvyrGALthsZObzUGsfgHZQDXjg4lOjagTIwIR1vPms= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= +github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48= +github.com/hashicorp/go-hclog v0.12.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= +github.com/hashicorp/go-hclog v0.16.2/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= +github.com/hashicorp/go-immutable-radix v1.3.1/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-multierror v1.1.0/go.mod h1:spPvp8C1qA32ftKqdAHm4hHTbPw+vmowP0z+KUhOZdA= +github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= +github.com/hashicorp/go-retryablehttp v0.5.3/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs= github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU= +github.com/hashicorp/go-rootcerts v1.0.2/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8= 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-version v1.2.1/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/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= 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/mdns v1.0.1/go.mod h1:4gW7WsVCke5TE7EPeYliwHlRUyBtfCwuFwuMg2DmyNY= github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= +github.com/hashicorp/memberlist v0.2.2/go.mod h1:MS2lj3INKhZjWNqd3N0m3J+Jxf3DAOnAH9VT3Sh9MUE= github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= +github.com/hashicorp/serf v0.9.5/go.mod h1:UWDWwZeL5cuWDJdl0C6wrvrUwEqtQ4ZKBKKENpqIUyk= github.com/herumi/bls-eth-go-binary v0.0.0-20200923072303-32b29e5d8cbf h1:Lw7EOMVxu3O+7Ro5bqn9M20a7GwuCqZQsmdXNzmcKE4= github.com/herumi/bls-eth-go-binary v0.0.0-20200923072303-32b29e5d8cbf/go.mod h1:luAnRm3OsMQeokhGzpYmc0ZKwawY7o87PUEP11Z7r7U= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/huandu/xstrings v1.0.0/go.mod h1:4qWG/gcEcfX4z/mBDHJ++3ReCw9ibxbsNJbcucJdbSo= +github.com/huandu/xstrings v1.2.0/go.mod h1:DvyZB1rfVYsBIigL8HwpZgxHwXozlTgGqn63UyNX5k4= github.com/hudl/fargo v1.3.0/go.mod h1:y3CKSmjA+wD2gak7sUSXTAoopbhU08POFhmITJgmKTg= +github.com/hudl/fargo v1.4.0/go.mod h1:9Ai6uvFy5fQNq6VPKtg+Ceq1+eTY4nKUlR2JElEOcDo= +github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/imdario/mergo v0.3.4/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= +github.com/imdario/mergo v0.3.8/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= 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/influxdata/influxdb1-client v0.0.0-20200827194710-b269163b24ab/go.mod h1:qj24IKcXYK6Iy9ceXlo3Tc+vtHo9lIhSX5JddghvEPo= github.com/jessevdk/go-flags v0.0.0-20141203071132-1679536dcc89/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= +github.com/jgautheron/goconst v1.5.1/go.mod h1:aAosetZ5zaeC/2EfMeRswtxUFBpe2Hr7HzkgX4fanO4= +github.com/jhump/protoreflect v1.6.1/go.mod h1:RZQ/lnuN+zqeRVpQigTwO6o0AJUkxbnSnpuG7toUTG4= +github.com/jingyugao/rowserrcheck v1.1.1/go.mod h1:4yvlZSDb3IyDTUZJUmpZfm2Hwok+Dtp+nu2qOq+er9c= +github.com/jirfag/go-printf-func-name v0.0.0-20200119135958-7558a9eaa5af/go.mod h1:HEWGJkRDzjJY2sqdDwxccsGicWEf9BQOZsq2tV+xzM0= github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= +github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= +github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= github.com/jmhodges/levigo v1.0.0 h1:q5EC36kV79HWeTBWsod3mG11EgStG3qArTKcvlksN1U= github.com/jmhodges/levigo v1.0.0/go.mod h1:Q6Qx+uH3RAqyK4rFQroq9RL7mdkABMcfhEI+nNuzMJQ= +github.com/jmoiron/sqlx v1.2.0/go.mod h1:1FEQNm3xlJgrMD+FBdI9+xvCksHtbpVBBw5dYhBSsks= github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= +github.com/jonboulle/clockwork v0.2.0/go.mod h1:Pkfl5aHPm1nk2H9h0bjmnJD/BcgbGXUBGnn1kMkgxc8= +github.com/josharian/txtarfs v0.0.0-20210218200122-0702f000015a/go.mod h1:izVPOvVRsHiKkeGCT6tYBNWyDVuzj9wAaBb5R9qamfw= github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= 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/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= +github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= 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/juju/ratelimit v1.0.1/go.mod h1:qapgC/Gy+xNh9UxzV13HGGl/6UXNN+ct+vwSgWNm/qk= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= +github.com/julz/importas v0.0.0-20210419104244-841f0c0fe66d/go.mod h1:oSFU2R4XK/P7kNBrnL/FEQlDGN1/6WoxXEjSSXO0DV0= github.com/jung-kurt/gofpdf v1.0.0/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes= github.com/jung-kurt/gofpdf v1.0.3-0.20190309125859-24315acbbda5/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes= +github.com/k0kubun/colorstring v0.0.0-20150214042306-9440f1994b88/go.mod h1:3w7q1U84EfirKl04SVQ/s7nPm1ZPhiXd34z40TNz36k= github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= +github.com/kisielk/errcheck v1.6.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/kkdai/bstream v0.0.0-20161212061736-f391b8402d23/go.mod h1:J+Gs4SYgM6CZQHDETBtE9HaSEkGmuNXF86RwHhHUvq4= +github.com/klauspost/compress v1.13.4/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8eO+e+Dq5Gzg= +github.com/klauspost/compress v1.13.5/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= +github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 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/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/kulti/thelper v0.4.0/go.mod h1:vMu2Cizjy/grP+jmsvOFDx1kYP6+PD1lqg4Yu5exl2U= +github.com/kunwardeep/paralleltest v1.0.3/go.mod h1:vLydzomDFpk7yu5UX02RmP0H8QfRPOV/oFhWN85Mjb4= +github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= +github.com/kyoh86/exportloopref v0.1.8/go.mod h1:1tUcJeiioIs7VWe5gcOObrux3lb66+sBqGZrRkMwPgg= +github.com/ldez/gomoddirectives v0.2.2/go.mod h1:cpgBogWITnCfRq2qGoDkKMEVSaarhdBr6g8G04uz6d0= +github.com/ldez/tagliatelle v0.2.0/go.mod h1:8s6WJQwEYHbKZDsp/LjArytKOG8qaMrKQQ3mFukHs88= +github.com/leodido/go-urn v1.1.0/go.mod h1:+cyI34gQWZcE1eQU7NVgKkkzdXDQHr1dBMtdAPozLkw= +github.com/letsencrypt/pkcs11key/v4 v4.0.0/go.mod h1:EFUvBDay26dErnNb70Nd0/VW3tJiIbETBPTl9ATXQag= +github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= +github.com/lib/pq v1.8.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= +github.com/lib/pq v1.9.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= +github.com/lib/pq v1.10.3/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= 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/line/gorocksdb v0.0.0-20210406043732-d4bea34b6d55 h1:cXVtMiJkvQ4kn0pxM2svH1ncJbFgQsLHtnFC9qJj2VM= github.com/line/gorocksdb v0.0.0-20210406043732-d4bea34b6d55/go.mod h1:DHRJroSL7NaRkpvocRx3OtRsleXVsYSxBI9SfHFlTQ0= +github.com/line/tm-db v0.6.4 h1:J6JGXu2uI/PWGjHF+2GdByrwRKgihcY53aTimPRipkA= +github.com/line/tm-db/v2 v2.0.0-init.1 h1:oYM+IJk1GuxDx/OPhCbyvGLnpyQYa7dWg1IDjTOqdjQ= +github.com/line/tm-db/v2 v2.0.0-init.1/go.mod h1:TiTwPFffNAqep0nV0YWaxPjElbCp6yG4K8SCxy69mE4= github.com/line/tm-db/v2 v2.0.0-init.1.0.20210824011847-fcfa67dd3c70 h1:Izv/u19P8salnSZGAgNHFugNlzWLgREiL+AmWK8C/lE= github.com/line/tm-db/v2 v2.0.0-init.1.0.20210824011847-fcfa67dd3c70/go.mod h1:wmkyPabXjtVZ1dvRofmurjaceghywtCSYGqFuFS+TbI= +github.com/logrusorgru/aurora v0.0.0-20181002194514-a7b3b318ed4e/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4= +github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I= github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0QoUACkjt2znoq26NVQ= github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= 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/magiconair/properties v1.8.5 h1:b6kJs+EmPFMYGkow9GiUyCyOvIwYetYJ3fSaWak/Gls= +github.com/magiconair/properties v1.8.5/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60= +github.com/maratori/testpackage v1.0.1/go.mod h1:ddKdw+XG0Phzhx8BFDTKgpWP4i7MpApTE5fXSKAqwDU= +github.com/matoous/godox v0.0.0-20210227103229-6504466cf951/go.mod h1:1BELzlh859Sh1c6+90blK8lbYy0kwQf1bYlBhBysy1s= +github.com/matryer/is v1.4.0/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= +github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= +github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-colorable v0.1.11/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= 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-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= +github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ= +github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84= +github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE= +github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= +github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= +github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= +github.com/mattn/go-runewidth v0.0.6/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= +github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= +github.com/mattn/go-sqlite3 v1.9.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= +github.com/mattn/goveralls v0.0.2/go.mod h1:8d1ZMHsd7fW6IRPKQh46F2WRpyib5/X4FOpevwGNQEw= 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/mbilski/exhaustivestruct v1.2.0/go.mod h1:OeTBVxQWoEmB2J2JCHmXWPJ0aksxSUOUy+nvtVEfzXc= +github.com/mgechev/dots v0.0.0-20210922191527-e955255bf517/go.mod h1:KQ7+USdGKfpPjXk4Ga+5XxQM4Lm4e3gAogrreFAYpOg= +github.com/mgechev/revive v1.1.2/go.mod h1:bnXsMr+ZTH09V5rssEI+jHAZ4z+ZdyhgO/zsy3EhK+0= github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= +github.com/miekg/dns v1.1.26/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso= +github.com/miekg/dns v1.1.35/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM= +github.com/miekg/dns v1.1.43/go.mod h1:+evo5L0630/F6ca/Z9+GAqzhjGyn8/c+TBaOyfEl0V4= +github.com/miekg/pkcs11 v1.0.2/go.mod h1:XsNlhZGX73bx86s2hdc/FuaLm2CPZJemRLMA+WTFxgs= +github.com/miekg/pkcs11 v1.0.3/go.mod h1:XsNlhZGX73bx86s2hdc/FuaLm2CPZJemRLMA+WTFxgs= 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.1 h1:dZ6IIu8Z14VlC0VpfKofAhCy74wu/Qb5gcn52yWoz/0= github.com/minio/highwayhash v1.0.1/go.mod h1:BQskDq+xkJ12lmlUUi7U0M5Swg3EWR+dLTk+kldvVxY= +github.com/minio/highwayhash v1.0.2 h1:Aak5U0nElisjDCfPSG79Tgzkn2gl66NxOMspRrKnA/g= +github.com/minio/highwayhash v1.0.2/go.mod h1:BQskDq+xkJ12lmlUUi7U0M5Swg3EWR+dLTk+kldvVxY= github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= +github.com/mitchellh/cli v1.1.0/go.mod h1:xcISNoH86gajksDmfB23e/pu+B+GeFRMYmoHXxx3xhI= +github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw= github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/go-ps v1.0.0/go.mod h1:J4lOc8z8yJs6vUwklHw2XEIiT4z4C40KtWVN3nvg8Pg= 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/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/mitchellh/mapstructure v1.4.2 h1:6h7AQ0yhTcIsmFmnAwQls75jp2Gzs4iB8W7pjMO+rqo= +github.com/mitchellh/mapstructure v1.4.2/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= +github.com/mitchellh/reflectwalk v1.0.1/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= +github.com/moby/sys/mountinfo v0.4.1/go.mod h1:rEr8tzG/lsIZHBtN/JjGG+LMYx9eXgW2JI+6q0qou+A= 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/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8= +github.com/moricho/tparallel v0.2.1/go.mod h1:fXEIZxG2vdfl0ZF8b42f5a78EhjjD5mX8qUplsoSU4k= +github.com/mozilla/scribe v0.0.0-20180711195314-fb71baf557c1/go.mod h1:FIczTrinKo8VaLxe6PWTPEXRXDIHz2QAwiaBaP5/4a8= +github.com/mozilla/tls-observatory v0.0.0-20210609171429-7bc42856d2e5/go.mod h1:FUqVoUPHSEdDR0MnFM3Dh8AU0pZHLXUD127SAJGER/s= +github.com/mroth/weightedrand v0.4.1/go.mod h1:3p2SIcC8al1YMzGhAIoXD+r9olo/g/cdJgAD905gyNE= +github.com/mrunalp/fileutils v0.5.0/go.mod h1:M1WthSahJixYnrXQl/DFQuteStB1weuxD2QJNHXfbSQ= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/mwitkow/go-proto-validators v0.0.0-20180403085117-0950a7990007/go.mod h1:m2XC9Qq0AlmmVksL6FktJCdTYyLk7V3fKyp0sl1yWQo= +github.com/mwitkow/go-proto-validators v0.2.0/go.mod h1:ZfA1hW+UH/2ZHOWvQ3HnQaU0DtnpXu850MZiy+YUgcc= +github.com/nakabonne/nestif v0.3.1/go.mod h1:9EtoZochLn5iUprVDmDjqGKPofoUEBL8U4Ngq6aY7OE= 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/jwt v1.2.2/go.mod h1:/xX356yQA6LuXI9xWW7mZNpxgF2mBmGecH+Fj34sP5Q= +github.com/nats-io/jwt/v2 v2.0.3/go.mod h1:VRP+deawSXyhNjXmxPCHskrR6Mq50BqpEI5SEcNiGlY= github.com/nats-io/nats-server/v2 v2.1.2/go.mod h1:Afk+wRZqkMQs/p45uXdrVLuab3gwv3Z8C4HTBu8GD/k= +github.com/nats-io/nats-server/v2 v2.5.0/go.mod h1:Kj86UtrXAL6LwYRA6H4RqzkHhK0Vcv2ZnKD5WbQ1t3g= github.com/nats-io/nats.go v1.9.1/go.mod h1:ZjDU1L/7fJ09jvUSRVBR2e7+RnLiiIQyqyzEE/Zbp4w= +github.com/nats-io/nats.go v1.12.1/go.mod h1:BPko4oXsySz4aSWeFgOHLZs3G4Jq4ZAyE6/zMCxRT6w= 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/nkeys v0.2.0/go.mod h1:XdZpAbhgyyODYqjTawOnIOI7VlbKSarI9Gfy1tqEu/s= +github.com/nats-io/nkeys v0.3.0/go.mod h1:gvUNGjVcM2IPr5rCsRsC6Wb3Hr2CQAm08dsxtV6A5y4= github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c= +github.com/nbutton23/zxcvbn-go v0.0.0-20210217022336-fa2cb2858354/go.mod h1:KSVJerMDfblTH7p5MZaTt+8zaT2iEk3AkVb9PQdZuE8= +github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= +github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= +github.com/nishanths/exhaustive v0.2.3/go.mod h1:bhIX678Nx8inLM9PbpvK1yv6oGtoP8BfaIeMzgBNKvc= +github.com/nishanths/predeclared v0.0.0-20190419143655-18a43bb90ffc/go.mod h1:62PewwiQTlm/7Rj+cxVYqZvDIUc+JjZq6GHAC1fsObQ= +github.com/nishanths/predeclared v0.2.1/go.mod h1:HvkGJcA3naj4lOwnFXFDkFxVtSqQMB9sbB1usJ+xjQE= github.com/nxadm/tail v1.4.4 h1:DQuhQpB1tVlglWS2hLQ5OV6B5r8aGxSrPc5Qo6uTN78= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= +github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= +github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= +github.com/oasisprotocol/curve25519-voi v0.0.0-20210609091139-0a56a4bca00b h1:MKwruh+HeCSKWphkxuzvRzU4QzDkg7yiPkDVV0cDFgI= +github.com/oasisprotocol/curve25519-voi v0.0.0-20210609091139-0a56a4bca00b/go.mod h1:TLJifjWF6eotcfzDjKZsDqWJ+73Uvj/N85MvVyrvynM= 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/olekukonko/tablewriter v0.0.1/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo= +github.com/olekukonko/tablewriter v0.0.2/go.mod h1:rSAaSIOAGT9odnlyGlUfAJaoc5w2fSBUmeGDbRWPxyQ= +github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.10.3/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= github.com/onsi/ginkgo v1.14.0 h1:2mOpI4JVVPBN+WQRa0WKH2eXR+Ey+uK4n7Zj0aYpIQA= github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY= +github.com/onsi/ginkgo v1.16.2/go.mod h1:CObGmKUOKaSC0RjmoAK7tKyn4Azo5P2IWuoMnvwxz1E= +github.com/onsi/ginkgo v1.16.4 h1:29JGrr5oVBm5ulCWet69zQkzWipVXIol6ygQUe/EzNc= +github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0= github.com/onsi/gomega v1.4.1/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= github.com/onsi/gomega v1.10.1 h1:o0+MgICZLuZ7xjH7Vx6zS/zcu93/BEp1VwkIW1mEXCE= github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= +github.com/onsi/gomega v1.13.0/go.mod h1:lRk9szgn8TxENtWd0Tp4c3wjlRfMTMH27I+3Je41yGY= +github.com/onsi/gomega v1.16.0 h1:6gjqkI8iiRHMvdccRJM8rVKjCWk6ZIm6FTm3ddIe4/c= +github.com/onsi/gomega v1.16.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY= github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk= +github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= +github.com/opencontainers/image-spec v1.0.1/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= +github.com/opencontainers/runc v1.0.2/go.mod h1:aTaHFFwQXuA71CiyxOdFFIorAoemI04suvGRQFzWTD0= +github.com/opencontainers/runtime-spec v1.0.3-0.20210326190908-1c3f411f0417/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= +github.com/opencontainers/selinux v1.8.2/go.mod h1:MUIHuUEvKB1wtJjQdOyYRgOnLD2xAPP8dBsCoU0KuF8= 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/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc= 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/openzipkin/zipkin-go v0.2.5/go.mod h1:KpXfKdgRDnnhsxw4pNIH9Md5lyFqKUa4YDFlwRYAMyE= +github.com/ory/dockertest v3.3.5+incompatible/go.mod h1:1vX4m9wsvi00u5bseYwXaSnhNrne+V0E6LAcBILJdPs= +github.com/otiai10/copy v1.2.0/go.mod h1:rrF5dJ5F0t/EWSYODDu4j9/vEeYHMkc8jt0zJChqQWw= +github.com/otiai10/curr v0.0.0-20150429015615-9b4961190c95/go.mod h1:9qAhocn7zKJG+0mI8eUu6xqkFDYS2kb2saOteoSB3cE= +github.com/otiai10/curr v1.0.0/go.mod h1:LskTG5wDwr8Rs+nNQ+1LlxRjAtTZZjtJW4rMXl6j4vs= +github.com/otiai10/mint v1.3.0/go.mod h1:F5AjcsTsWUqX+Na9fpHb52P8pcRX2CI6A3ctIT91xUo= +github.com/otiai10/mint v1.3.1/go.mod h1:/yxELlJQ0ufhjUwhshSj+wFjZ78CnZ48/1wtmBH1OTc= 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/pascaldekloe/goe v0.1.0/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/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= github.com/pelletier/go-toml v1.6.0 h1:aetoXYr0Tv7xRU/V4B4IZJ2QcbtMUFoNb3ORp7TzIK4= github.com/pelletier/go-toml v1.6.0/go.mod h1:5N711Q9dKgbdkxHL+MEfF31hpT7l0S0s/t2kKREewys= +github.com/pelletier/go-toml v1.9.3/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= +github.com/pelletier/go-toml v1.9.4 h1:tjENF6MfZAg8e4ZmZTeWaWiT2vXtsoO6+iuOjFhECwM= +github.com/pelletier/go-toml v1.9.4/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= github.com/performancecopilot/speed v3.0.0+incompatible/go.mod h1:/CLtqpZ5gBg1M9iaPbIdPPGyKcA8hKdoy6hAWba7Yac= +github.com/performancecopilot/speed/v4 v4.0.0/go.mod h1:qxrSyuDGrTOWfV+uKRFhfxw6h/4HXRGUiZiufxo49BM= +github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= github.com/petermattis/goid v0.0.0-20180202154549-b0b1615b78e5 h1:q2e307iGHPdTGp0hoxKjt1H5pDo6utceo3dQVK3I5XQ= github.com/petermattis/goid v0.0.0-20180202154549-b0b1615b78e5/go.mod h1:jvVRKCrJTQWu0XVbaOlby/2lO20uSCHEMzzplHXte1o= +github.com/phayes/checkstyle v0.0.0-20170904204023-bfd46e6a821d/go.mod h1:3OzsM7FXDQlpCiw2j81fOmAwQLnZnLGXVKUzeKQXIAw= +github.com/philhofer/fwd v1.1.1/go.mod h1:gk3iGcWd9+svBvR0sR+KPcfE+RNWozjowpeBVG3ZVNU= github.com/phpdave11/gofpdf v1.4.2/go.mod h1:zpO6xFn9yxo3YLyMvW8HcKWVdbNqgIfOOp2dXMnm1mY= github.com/phpdave11/gofpdi v1.0.12/go.mod h1:vBmVV0Do6hSBHC8uKUQ71JGW+ZGQq74llk/7bXwjDoI= github.com/pierrec/lz4 v1.0.2-0.20190131084431-473cd7ce01a1/go.mod h1:3/3N9NVKO0jef7pBehbT1qWhCMrIgbYNnFAZCqQ5LRc= @@ -378,17 +813,24 @@ github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE 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/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI= +github.com/pmezard/go-difflib v0.0.0-20151028094244-d8ed2627bdf0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 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/polyfloyd/go-errorlint v0.0.0-20210722154253-910bb7978349/go.mod h1:wi9BfjxjF/bwiZ701TzmfKu6UKC357IOAtNr0Td0Lvw= github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= +github.com/posener/complete v1.2.3/go.mod h1:WZIdtGGp+qx0sLrYKtIRAruyNpv6hFCicSgv7Sy7s/s= 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/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/go.mod h1:hJaj2vgQTGQmVCsAACORcieXFeDPbaTKGT+JTgUa3og= +github.com/prometheus/client_golang v1.4.0/go.mod h1:e9GMxYsXl05ICDXkRhurwBS4Q3OK1iX/F2sw+iXX5zU= github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M= github.com/prometheus/client_golang v1.8.0 h1:zvJNkoCFAnYFNC24FV8nW4JdRJ3GIFcLbg65lL/JDcw= github.com/prometheus/client_golang v1.8.0/go.mod h1:O9VU6huf47PktckDQfMTX0Y8tY0/7TSWwj+ITvv0TnM= +github.com/prometheus/client_golang v1.11.0 h1:HNkLOAEQMIDv/K+04rukrLx6ch7msSRwf3/SASFAGtQ= +github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0= 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/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= @@ -401,9 +843,13 @@ github.com/prometheus/common v0.2.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y8 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/go.mod h1:DjGbpBbp5NYNiECxcL/VnbXCCaQpKd3tt26CguLLsqA= +github.com/prometheus/common v0.9.1/go.mod h1:yhUN8i9wzaXS3w1O07YhxHEBxD+W35wd8bs7vj7HSQ4= github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo= github.com/prometheus/common v0.14.0 h1:RHRyE8UocrbjU+6UvRzwi6HjiDfxrrBU91TtbKzkGp4= github.com/prometheus/common v0.14.0/go.mod h1:U+gB1OBLb1lF3O42bTCL+FK18tX9Oar16Clt/msog/s= +github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc= +github.com/prometheus/common v0.30.0 h1:JEkYlQnpzrzQFxi6gnukFPdQ+ac82oRhzMcIduJu/Ug= +github.com/prometheus/common v0.30.0/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls= 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/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= @@ -412,28 +858,64 @@ github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+Gx github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= github.com/prometheus/procfs v0.2.0 h1:wH4vA7pcjKuZzjF7lM8awk4fnuJO6idemZXoKnULUx4= github.com/prometheus/procfs v0.2.0/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= +github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= +github.com/prometheus/procfs v0.7.3 h1:4jVXhlkAyzOScmCkXBTOLRLTz8EeU+eyjrwB/EPq0VU= +github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= +github.com/pseudomuto/protoc-gen-doc v1.3.2/go.mod h1:y5+P6n3iGrbKG+9O04V5ld71in3v/bX88wUwgt+U8EA= +github.com/pseudomuto/protokit v0.2.0/go.mod h1:2PdH30hxVHsup8KpBTOXTBeMVhJZVio3Q8ViKSAXT0Q= +github.com/quasilyte/go-consistent v0.0.0-20190521200055-c6f3937de18c/go.mod h1:5STLWrekHfjyYwxBRVRXNOSewLJ3PWfDJd1VyTS21fI= +github.com/quasilyte/go-ruleguard v0.3.1-0.20210203134552-1b5a410e1cc8/go.mod h1:KsAh3x0e7Fkpgs+Q9pNLS5XpFSvYCEVl5gP9Pp1xp30= +github.com/quasilyte/go-ruleguard v0.3.13/go.mod h1:Ul8wwdqR6kBVOCt2dipDBkE+T6vAV/iixkrKuRTN1oQ= +github.com/quasilyte/go-ruleguard/dsl v0.3.0/go.mod h1:KeCP03KrjuSO0H1kTuZQCWlQPulDV6YMIXmpQss17rU= +github.com/quasilyte/go-ruleguard/dsl v0.3.10/go.mod h1:KeCP03KrjuSO0H1kTuZQCWlQPulDV6YMIXmpQss17rU= +github.com/quasilyte/go-ruleguard/rules v0.0.0-20201231183845-9e62ed36efe1/go.mod h1:7JTjp89EGyU1d6XfBiXihJNG37wB2VRkd125Q1u7Plc= +github.com/quasilyte/go-ruleguard/rules v0.0.0-20210428214800-545e0d2e0bf7/go.mod h1:4cgAphtvu7Ftv7vOT2ZOYhC6CvBxZixcasr8qIOTA50= +github.com/quasilyte/regex/syntax v0.0.0-20200407221936-30656e2c4a95/go.mod h1:rlzQ04UMyJXu/aOvhd8qT+hvDrFpiwqp8MRXDY9szc0= github.com/r2ishiguro/vrf v0.0.0-20180716233122-192de52975eb h1:3kW8n+FfBaUoqlHxCa6e90PXWpGCWWkdyTZ6F7c9m2I= github.com/r2ishiguro/vrf v0.0.0-20180716233122-192de52975eb/go.mod h1:2NzHJUkr/ERaPNQ2IUuNbB2jMTWYp2DxhcraWbzZj00= github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= github.com/rcrowley/go-metrics v0.0.0-20200313005456-10cdbea86bc0 h1:MkV+77GLUNo5oJ0jf870itWm3D0Sjh7+Za9gazKc5LQ= github.com/rcrowley/go-metrics v0.0.0-20200313005456-10cdbea86bc0/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= +github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/rogpeppe/go-internal v1.6.2/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= 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/rs/cors v1.8.0 h1:P2KMzcFwrPoSjkF1WLRPsp3UMLyql8L4v9hQpVeK5so= +github.com/rs/cors v1.8.0/go.mod h1:EBwu+T5AvHOcXwvZIkQFjUN6s8Czyqw12GL/Y0tUyRM= +github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ= +github.com/rs/xid v1.3.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= +github.com/rs/zerolog v1.18.0/go.mod h1:9nvC1axdVrAHcu/s9taAVfBuIdTZLVQmKQyvrUjF5+I= +github.com/rs/zerolog v1.26.0 h1:ORM4ibhEZeTeQlCojCK2kPz1ogAY4bGs4tD+SaAdGaE= +github.com/rs/zerolog v1.26.0/go.mod h1:yBiM87lvSqX8h0Ww4sdzNSkVYZ8dL2xjZJG1lAuGZEo= github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/ruudk/golang-pdf417 v0.0.0-20181029194003-1af4ab5afa58/go.mod h1:6lfFZQK844Gfx8o5WFuvpxWRwnSoipWe/p622j1v06w= +github.com/ryancurrah/gomodguard v1.2.3/go.mod h1:rYbA/4Tg5c54mV1sv4sQTP5WOPBcoLtnBZ7/TEhXAbg= +github.com/ryanrolds/sqlclosecheck v0.3.0/go.mod h1:1gREqxyTGR3lVtpngyFo3hZAgk0KCtEdgEkHwDbigdA= github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= +github.com/sagikazarmark/crypt v0.1.0/go.mod h1:B/mN0msZuINBtQ1zZLEQcegFJJf9vnYIR88KRMEuODE= github.com/samuel/go-zookeeper v0.0.0-20190923202752-2cc03de413da/go.mod h1:gi+0XIa01GRL2eRQVjQkKGqKF3SF9vZR/HnPullcV2E= +github.com/sanposhiho/wastedassign/v2 v2.0.6/go.mod h1:KyZ0MWTwxxBmfwn33zh3k1dmsbF2ud9pAAGfoLfjhtI= github.com/sasha-s/go-deadlock v0.2.1-0.20190427202633-1595213edefa h1:0U2s5loxrTy6/VgfVoLuVLFJcURKLH49ie0zSch7gh4= github.com/sasha-s/go-deadlock v0.2.1-0.20190427202633-1595213edefa/go.mod h1:F73l+cr82YSh10GxyRI6qZiCgK64VaZjwesgfQ1/iLM= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= +github.com/seccomp/libseccomp-golang v0.9.1/go.mod h1:GbW5+tmTXfcxTToHLXlScSlAvWlF4P2Ca7zGrPiEpWo= +github.com/securego/gosec/v2 v2.9.1/go.mod h1:oDcDLcatOJxkCGaCaq8lua1jTnYf6Sou4wdiJ1n4iHc= +github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= +github.com/shazow/go-diff v0.0.0-20160112020656-b6b7b6733b8c/go.mod h1:/PevMnwAxekIXwN8qQyfc5gl2NlkB3CQlkizAbOkeBs= +github.com/shirou/gopsutil/v3 v3.21.10/go.mod h1:t75NhzCZ/dYyPQjyQmrAYP6c8+LCdFANeBMdLPCNnew= +github.com/shurcooL/go v0.0.0-20180423040247-9e1955d9fb6e/go.mod h1:TDJrrUr11Vxrven61rcy3hJMUqaf/CLWYhHNPmT14Lk= +github.com/shurcooL/go-goon v0.0.0-20170922171312-37c2f522c041/go.mod h1:N5mDOmsrJOB+vfqUK+7DmDyjhSLIIBnXo9lvZJj3MWQ= 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/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= +github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= +github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= +github.com/sivchari/tenv v1.4.7/go.mod h1:5nF+bITvkebQVanjU6IuMbvIot/7ReNsUV7I5NbprB0= 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= @@ -441,19 +923,29 @@ 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/sonatard/noctx v0.0.1/go.mod h1:9D2D/EoULe8Yy2joDHJj7bv3sZoq9AaSb8B4lqBjiZI= github.com/sony/gobreaker v0.4.1/go.mod h1:ZKptC7FHNvhBz7dN2LGjPVBz2sZJmc0/PkyDJOjmxWY= +github.com/sourcegraph/go-diff v0.6.1/go.mod h1:iBszgVvyxdc8SFZ7gm69go2KDdt3ag071iBaWPF6cjs= github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI= github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= github.com/spf13/afero v1.2.2 h1:5jhuqJyZCZf2JRofRvN/nIFgIWNzPa3/Vz8mYylgbWc= github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk= +github.com/spf13/afero v1.6.0 h1:xoax2sJ2DT8S8xA2paPFjDCScCNeWsg75VG0DLRreiY= +github.com/spf13/afero v1.6.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I= 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/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= +github.com/spf13/cast v1.4.1 h1:s0hze+J0196ZfEMTs80N7UlFt0BDuQ7Q+JDnHiMWKdA= +github.com/spf13/cast v1.4.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU= +github.com/spf13/cobra v1.0.0/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE= github.com/spf13/cobra v1.1.1 h1:KfztREH0tPxJJ+geloSLaAkaPkr4ki2Er5quFV1TDo4= github.com/spf13/cobra v1.1.1/go.mod h1:WnodtKOvamDL/PwE2M4iKs8aMDBZ5Q5klgD3qfVJQMI= +github.com/spf13/cobra v1.2.1 h1:+KmjbUw1hriSNMF55oPrkZcb27aECyrj8V2ytv7kWDw= +github.com/spf13/cobra v1.2.1/go.mod h1:ExllRjgxM/piMAM+3tAZvg8fsklGAf3tPfi+i8t68Nk= github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk= github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= @@ -462,58 +954,139 @@ github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnIn github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s= +github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE= github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg= github.com/spf13/viper v1.7.1 h1:pM5oEahlgWv/WnHXpgbKz7iLIxRf65tye2Ci+XFK5sk= github.com/spf13/viper v1.7.1/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg= +github.com/spf13/viper v1.8.1/go.mod h1:o0Pch8wJ9BVSWGQMbra6iw0oQ5oktSIBaujf1rJH9Ns= +github.com/spf13/viper v1.9.0 h1:yR6EXjTp0y0cLN8OZg1CRZmOBdI88UcGkhgyJhu6nZk= +github.com/spf13/viper v1.9.0/go.mod h1:+i6ajR7OX2XaiBkrcZJFK21htRk7eDeLg7+O6bhUPP4= +github.com/ssgreg/nlreturn/v2 v2.2.1/go.mod h1:E/iiPB78hV7Szg2YfRgyIrk1AD6JVMTRkkxBiELzh2I= 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/amqp v1.0.0/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw= github.com/streadway/handy v0.0.0-20190108123426-d5acb3125c2a/go.mod h1:qNTQ5P5JnDBl6z3cMAg/SywNDC5ABu5ApDIw6lUbRmI= +github.com/streadway/handy v0.0.0-20200128134331-0f66f006fb2e/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/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.2.0 h1:Hbg2NidpLE8veEBkEZTL3CvlkUIVzuU9jDplZO54c48= github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= +github.com/stretchr/testify v0.0.0-20170130113145-4d4bfba8f1d1/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.1.4/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= 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/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 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/sylvia7788/contextcheck v1.0.4/go.mod h1:vuPKJMQ7MQ91ZTqfdyreNKwZjyUg6KO+IebVyQDedZQ= +github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww= github.com/syndtr/goleveldb v1.0.1-0.20200815110645-5c35d600f0ca h1:Ld/zXl5t4+D69SiV4JoN7kkfvJdOWlPpfxrzxpLMoUk= github.com/syndtr/goleveldb v1.0.1-0.20200815110645-5c35d600f0ca/go.mod h1:u2MKkTVTVJWe5D1rCvame8WqhBd88EuIwODJZ1VHCPM= +github.com/tdakkota/asciicheck v0.0.0-20200416200610-e657995f937b/go.mod h1:yHp0ai0Z9gUljN3o0xMhYJnH/IcvkdTBOX2fmJ93JEM= +github.com/tecbot/gorocksdb v0.0.0-20191217155057-f0fad39f321c h1:g+WoO5jjkqGAzHWCjJB1zZfXPIAaDpzXIEJ0eS6B5Ok= +github.com/tecbot/gorocksdb v0.0.0-20191217155057-f0fad39f321c/go.mod h1:ahpPrc7HpcfEWDQRZEmnXMzHY03mLDYMCxeDzy46i+8= github.com/tendermint/go-amino v0.16.0 h1:GyhmgQKvqF82e2oZeuMSp9JTN0N09emoSZlb2lyGa2E= github.com/tendermint/go-amino v0.16.0/go.mod h1:TQU0M1i/ImAo+tYpZi73AU3V/dKeCoMC9Sphe2ZwGME= +github.com/tendermint/tendermint v0.35.0 h1:YxMBeDGo+FbWwe4964XBup9dwxv/1f/uHE3pLqaM7eM= +github.com/tendermint/tendermint v0.35.0/go.mod h1:BEA2df6j2yFbETYq7IljixC1EqRTvRqJwyNcExddJ8U= +github.com/tendermint/tm-db v0.6.4/go.mod h1:dptYhIpJ2M5kUuenLr+Yyf3zQOv1SgBZcl8/BmWlMBw= +github.com/tendermint/tm-db v0.6.6 h1:EzhaOfR0bdKyATqcd5PNeyeq8r+V4bRPHBfyFdD9kGM= +github.com/tendermint/tm-db v0.6.6/go.mod h1:wP8d49A85B7/erz/r4YbKssKw6ylsO/hKtFk7E1aWZI= +github.com/tenntenn/modver v1.0.1/go.mod h1:bePIyQPb7UeioSRkw3Q0XeMhYZSMx9B8ePqg6SAMGH0= +github.com/tenntenn/text/transform v0.0.0-20200319021203-7eef512accb3/go.mod h1:ON8b8w4BN/kE1EOhwT0o+d62W65a6aPw1nouo9LMgyY= +github.com/tetafro/godot v1.4.11/go.mod h1:LR3CJpxDVGlYOWn3ZZg1PgNZdTUvzsZWu8xaEohUpn8= +github.com/timakin/bodyclose v0.0.0-20200424151742-cb6215831a94/go.mod h1:Qimiffbc6q9tBWlVV6x0P9sat/ao1xEkREYPPj9hphk= +github.com/tinylib/msgp v1.1.5/go.mod h1:eQsjooMTnV42mHu917E26IogZ2930nFyBQdofk10Udg= +github.com/tklauser/go-sysconf v0.3.9/go.mod h1:11DU/5sG7UexIrp/O6g35hrWzu0JxlwQ3LSFUzyeuhs= +github.com/tklauser/numcpus v0.3.0/go.mod h1:yFGUr7TUHQRAhyqBcEg0Ge34zDBAsIvJJcyE6boqnA8= 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/tmc/grpc-websocket-proxy v0.0.0-20200427203606-3cfed13b9966/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= +github.com/tomarrell/wrapcheck/v2 v2.4.0/go.mod h1:68bQ/eJg55BROaRTbMjC7vuhL2OgfoG8bLp9ZyoBfyY= +github.com/tomasen/realip v0.0.0-20180522021738-f0c99a92ddce/go.mod h1:o8v6yHRoik09Xen7gje4m9ERNah1d1PPsVq1VEx9vE4= +github.com/tommy-muehle/go-mnd/v2 v2.4.0/go.mod h1:WsUAkMJMYww6l/ufffCD3m+P7LEvr8TnZn9lwVDlgzw= +github.com/ttacon/chalk v0.0.0-20160626202418-22c06c80ed31/go.mod h1:onvgF043R+lC5RZ8IT9rBXDaEDnpnw/Cl+HFiw+v/7Q= +github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM= +github.com/tv42/httpunix v0.0.0-20191220191345-2ba4b9c3382c/go.mod h1:hzIxponao9Kjc7aWznkXaL4U4TWaDSs8zcsY4Ka08nM= +github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc= +github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw= github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= +github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY= +github.com/ultraware/funlen v0.0.3/go.mod h1:Dp4UiAus7Wdb9KUZsYWZEWiRzGuM2kXM1lPbfaF6xhA= +github.com/ultraware/whitespace v0.0.4/go.mod h1:aVMh/gQve5Maj9hQ/hg+F75lr/X5A89uZnzAmWSineA= 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/uudashr/gocognit v1.0.5/go.mod h1:wgYz0mitoKOTysqxTDMOUXg+Jb5SvtihkfmugIZYpEA= +github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= +github.com/valyala/fasthttp v1.30.0/go.mod h1:2rsYD01CKFrjjsvFxx75KlEUNpWNBY9JWD3K/7o2Cus= +github.com/valyala/quicktemplate v1.7.0/go.mod h1:sqKJnoaOF88V07vkO+9FL8fb9uZg/VPSJnLYn+LmLk8= +github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc= +github.com/vektra/mockery/v2 v2.9.4/go.mod h1:2gU4Cf/f8YyC8oEaSXfCnZBMxMjMl/Ko205rlP0fO90= +github.com/viki-org/dnscache v0.0.0-20130720023526-c70c1f23c5d8/go.mod h1:dniwbG03GafCjFohMDmz6Zc6oCuiqgH6tGNyXTkHzXE= +github.com/vishvananda/netlink v1.1.0/go.mod h1:cTgwzPIzzgDAYoQrMm0EdrjRUBkTqKYppBueQtXaqoE= +github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df/go.mod h1:JP3t17pCcGlemwknint6hfoeCVQrEMVwxRLRjXpq+BU= github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= +github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778/go.mod h1:2MuV+tbUrU1zIOPMxZ5EncGwgmMJsa+9ucAQZXxsObs= github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= github.com/yahoo/coname v0.0.0-20170609175141-84592ddf8673 h1:PSg2cEFd+9Ae/r5x5iO8cJ3VmTbZNQp6X8tHDmVJAbA= github.com/yahoo/coname v0.0.0-20170609175141-84592ddf8673/go.mod h1:Wq2sZrP++Us4tAw1h58MHS8BGIpC4NmKHfvw2QWBe9U= +github.com/yeya24/promlinter v0.1.0/go.mod h1:rs5vtZzeBHqqMwXqFScncpCF6u06lezhZepno9AB1Oc= +github.com/yudai/gojsondiff v1.0.0/go.mod h1:AY32+k2cwILAkW1fbgxQ5mUmMiZFgLIV+FBNExI05xg= +github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82/go.mod h1:lgjkn3NuSvDfVJdfcVVdX+jpBxNmX4rDAzaS45IcYoM= +github.com/yudai/pp v2.0.1+incompatible/go.mod h1:PuxR/8QJ7cyCkFp/aUDS+JY727OFEZkTdatxwunjIkc= +github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +github.com/yuin/goldmark v1.4.0/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q= go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= +go.etcd.io/bbolt v1.3.4/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ= go.etcd.io/bbolt v1.3.5 h1:XAzx9gjCb0Rxj7EoqcClPD1d5ZBxZJk0jbuoPHenBt0= go.etcd.io/bbolt v1.3.5/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ= +go.etcd.io/bbolt v1.3.6 h1:/ecaJf0sk1l4l6V4awd65v2C3ILy7MSj+s/x1ADCIMU= +go.etcd.io/bbolt v1.3.6/go.mod h1:qXsaaIqmgQH0T+OPdb99Bf+PKfBBQVAdyD6TY9G8XM4= go.etcd.io/etcd v0.0.0-20191023171146-3cf2f69b5738/go.mod h1:dnLIgRNXwCJa5e+c6mIZCrds/GIG4ncV9HhK5PX7jPg= +go.etcd.io/etcd v0.0.0-20200513171258-e048e166ab9c/go.mod h1:xCI7ZzBfRuGgBXyXO6yfWfDmlWd35khcWpUa4L0xI/k= +go.etcd.io/etcd/api/v3 v3.5.0/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs= +go.etcd.io/etcd/client/pkg/v3 v3.5.0/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g= +go.etcd.io/etcd/client/v2 v2.305.0/go.mod h1:h9puh54ZTgAKtEbut2oe9P4L/oqKCVB6xsXlzd7alYQ= +go.etcd.io/etcd/client/v3 v3.5.0/go.mod h1:AIKXXVX/DQXtfTEqBryiLTUXwON+GuvO6Z7lLS/oTh0= +go.mozilla.org/mozlog v0.0.0-20170222151521-4bb13139d403/go.mod h1:jHoPAGnDrCy6kaI2tAze5Prf0Nr0w/oNkROt2lw3n3o= 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.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= +go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= +go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= 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/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= +go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= +go.uber.org/goleak v1.1.11-0.20210813005559-691160354723/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= 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/multierr v1.4.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4= +go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= +go.uber.org/multierr v1.7.0/go.mod h1:7EAYxJLBy9rStEaz58O2t4Uvip6FSURkq8/ppBp95ak= 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= +go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo= +go.uber.org/zap v1.19.1/go.mod h1:j3DNczoxDZroyBnOT1L/Q79cfUMGZxlv/9dzN7SM1rI= golang.org/x/crypto v0.0.0-20170930174604-9419663f5a44/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20180501155221-613d6eafa307/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-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= @@ -521,13 +1094,24 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392/go.mod h1:/lpIB1dKB+9EgE3H3cr1v9wB50oz8l4C4h62xy7jSTY= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191206172530-e9b2fee46413/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200115085410-6d4e4cb37c7d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20200323165209-0ec3e9974c59/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200510223506-06a226fb4e37/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20201117144127-c1f2f97bffc9 h1:phUcVbl53swtrUN8kQEXFhUxPlIlWyBfKmidCu7P95o= golang.org/x/crypto v0.0.0-20201117144127-c1f2f97bffc9/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= +golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= +golang.org/x/crypto v0.0.0-20210314154223-e6e6c4f2bb5b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= +golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8= +golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20210915214749-c084706c2272/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519 h1:7I4JAnoQBe7ZtJcBaYHi5UtiO8tQHbUSXxL+pnGRANg= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= @@ -538,6 +1122,13 @@ golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm0 golang.org/x/exp v0.0.0-20191002040644-a1355ae1e2c3/go.mod h1:NOZ3BPKG0ec/BKJQgnvsSFpcKLM5xXVWnvZS97DWHgE= golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136 h1:A1gGSx58LAGVHUUsOf7IiR0u8Xb6W51gRwfDBhkdcaw= golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= +golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= +golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= +golang.org/x/exp v0.0.0-20200331195152-e8c3332aa8e5 h1:FR+oGxGfbQu1d+jglI3rCkjAjUnhRSZcUxr+DqlDLNo= +golang.org/x/exp v0.0.0-20200331195152-e8c3332aa8e5/go.mod h1:4M0jN8W1tt0AVLNr8HDosyJCDCDuyL9N9+3m7wDWgKw= golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= @@ -554,13 +1145,23 @@ golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHl golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= +golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.5.0/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro= golang.org/x/net v0.0.0-20180719180050-a680a1efc54d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 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= @@ -576,27 +1177,79 @@ golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/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/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20191002035440-2ec189313ef0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200421231249-e086a090c8fd/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200813134508-3edf25e44fcc/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4 h1:4nGaVu0QrbjT/AK2PRLuQfQuh6DJve+pELhqTdAj3x0= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= +golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= +golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20210510120150-4163338589ed/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20210614182718-04defd469f4e/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20210917221730-978cfadd31cf/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20211005001312-d4b1ae081e3b h1:SXy8Ld8oKlcogOvUAh0J5Pm5RKzgYBMMxLxt6n5XW50= +golang.org/x/net v0.0.0-20211005001312-d4b1ae081e3b/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= 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/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210220000619-9bb904979d93/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210313182246-cd4f82c27b84/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210402161424-2e8d93401602/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210628180205-a41e5a781914/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210805134026-6f1e6394065a/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= 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-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190412183630-56d357773e84/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/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/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= @@ -608,58 +1261,128 @@ golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5h golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/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-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/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-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190606203320-7fc4e5ec1444/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190922100055-0a153f010e69/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191115151921-52ab43148777/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191210023423-ac6580df4449/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191220142924-d4481acd189f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200124204421-9fbb57f87de9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200420163511-1957bb5e6d1f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200814200057-3d37ad5750ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200909081042-eff7692f9009/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200923182605-d9f96fdee20d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201015000850-e3ed0017c211/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210303074136-134d130e1a04/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210304124612-50617c2ba197/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210426230700-d19ff857e887/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210514084401-e8d321eab015/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210603125802-9665404d3644/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c h1:F1jZWGFhYfh0Ci55sIpILtKKK8p3i2/krTr0H1rg74I= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210816074244-15123e1e1f71/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210915083310-ed5796bab164/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210917161153-d61c044b1678/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211004093028-2c5d950f24ef/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211013075003-97ac67df715c h1:taxlMj0D/1sOAuv/CbSD+MMDof2vbyPTqz5FNYKpXt8= +golang.org/x/sys v0.0.0-20211013075003-97ac67df715c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.5 h1:i6eZZ+zk0SOf0xgBpEpPD18qWcJda6q1sxt3S0kzyUQ= golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/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/time v0.0.0-20200416051211-89c76fbcd5d1/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac/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-20180525024113-a5b4c53f6e8b/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-20190110163146-51295c7ec13a/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-20190206041539-40960b6deb8e/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-20190307163923-6a08e3108db3/go.mod h1:25r3+/G6/xytQM8iWZKq3Hn0kr0rgFKPUNVEL/dr3z4= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190311215038-5c2858a9cfe5/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190321232350-e250d351ecad/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190322203728-c1a832b0ad89/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= @@ -668,16 +1391,90 @@ golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgw golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20190828213141-aed303cbaa74/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20190907020128-2ca718005c18/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20190910044552-dd2b5c81c578/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20190916130336-e45ffcd953cc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20190927191325-030b2cf1153e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191010075000-0337d82405ff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 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-20191108193012-7d206e10da11/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191216052735-49a3e744a425/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200117220505-0cba7a3a9ee9/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= +golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= +golang.org/x/tools v0.0.0-20200323144430-8dcfad9e016e/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= +golang.org/x/tools v0.0.0-20200324003944-a576cf524670/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= +golang.org/x/tools v0.0.0-20200329025819-fd4102a86c65/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= +golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= +golang.org/x/tools v0.0.0-20200414032229-332987a829c3/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200422022333-3d57cf2e726e/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200426102838-f3a5411a4c3b/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200622203043-20e05c1c8ffa/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200624225443-88f3c62a19ff/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200625211823-6506e20df31f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200626171337-aa94e735be7f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200630154851-b2d8b0336632/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200706234117-b22de6825cf7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200724022722-7017fd6b1305/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200812195022-5ae4c3c160a0/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200820010801-b793a1359eac/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200831203904-5a2aa26beb65/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE= +golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE= +golang.org/x/tools v0.0.0-20201001104356-43ebab892c4c/go.mod h1:z6u4i615ZeAfBE4XtMziQW1fSVJXACjjbWkB/mvPzlU= +golang.org/x/tools v0.0.0-20201002184944-ecd9fd270d5d/go.mod h1:z6u4i615ZeAfBE4XtMziQW1fSVJXACjjbWkB/mvPzlU= +golang.org/x/tools v0.0.0-20201022035929-9cf592e881e9/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20201023174141-c8cfbd0f21e6/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20201028025901-8cd080b735b3/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20201114224030-61ea331ec02b/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20201118003311-bd56c0adb394/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20201230224404-63754364767c/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20210101214203-2dba1e4ea05c/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20210104081019-d8d6ddbec6ee/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= +golang.org/x/tools v0.1.1-0.20210205202024-ef80cdb6ec6d/go.mod h1:9bzcO0MWcOuT0tm1iBGzDVPshzfwoVvREIui8C+MHqU= +golang.org/x/tools v0.1.1-0.20210302220138-2ac05c832e1a/go.mod h1:9bzcO0MWcOuT0tm1iBGzDVPshzfwoVvREIui8C+MHqU= +golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.6/go.mod h1:LGqMHiF4EqQNHR1JncWGqT5BVaXmza+X+BDGol+dOxo= +golang.org/x/tools v0.1.7/go.mod h1:LGqMHiF4EqQNHR1JncWGqT5BVaXmza+X+BDGol+dOxo= 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/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -696,14 +1493,44 @@ google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEt google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.10.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= +google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= +google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= +google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= +google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg= +google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE= +google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8= +google.golang.org/api v0.41.0/go.mod h1:RkxM5lITDfTzmyKFPt+wGrCJbVfniCr2ool8kTBzRTU= +google.golang.org/api v0.43.0/go.mod h1:nQsDGjRXMo4lvh5hP0TKqF244gqhGcr/YSIykhUk/94= +google.golang.org/api v0.44.0/go.mod h1:EBOGZqzyhtvMDoxwS97ctnh0zUmYY6CxqXsc1AvkYD8= +google.golang.org/api v0.47.0/go.mod h1:Wbvgpq1HddcWVtzsVLyfLp8lDg6AA241LmgIL59tHXo= +google.golang.org/api v0.48.0/go.mod h1:71Pr1vy+TAZRPkPs/xlCf5SsU8WjuAWv1Pfjbtukyy4= +google.golang.org/api v0.50.0/go.mod h1:4bNT5pAuq5ji4SRZm+5QIkjny9JAyVD/3gaSihNefaw= +google.golang.org/api v0.51.0/go.mod h1:t4HdrdoNgyN5cbEfm7Lum0lcLDLiise1F8qDKX00sOU= +google.golang.org/api v0.54.0/go.mod h1:7C4bFFOvVDGXjfDTAsgGwDgAxRDeQ4X8NvUedIt6z3k= +google.golang.org/api v0.56.0/go.mod h1:38yMfeP1kfjsl8isn0tliTjIb1rJXcQi4UXlbqivdVE= 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/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= +google.golang.org/appengine v1.6.2/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= +google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/genproto v0.0.0-20170818010345-ee236bd376b0/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20180831171423-11092d34479b/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20181107211654-5fc9ac540362/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-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= @@ -712,9 +1539,58 @@ google.golang.org/genproto v0.0.0-20190530194941-fb225487d101/go.mod h1:z3L6/3dT google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= +google.golang.org/genproto v0.0.0-20190927181202-20e1ac93f88c/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= +google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200423170343-7949de9c1215/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013 h1:+kGHl1aib/qcwaRi1CbqBZ1rk19r85MNUf8HaBghugY= google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= +google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= +google.golang.org/genproto v0.0.0-20200626011028-ee7919e894b5/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200707001353-8e8330bf89df/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210222152913-aa3ee6e6a81c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210303154014-9728d6b83eeb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210310155132-4ce2db91004e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210319143718-93e7006c17a6/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210402141018-6c239bbf2bb1/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A= +google.golang.org/genproto v0.0.0-20210513213006-bf773b8c8384/go.mod h1:P3QM42oQyzQSnHPnZ/vqoCdDmzH28fzWByN9asMeM8A= +google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= +google.golang.org/genproto v0.0.0-20210604141403-392c879c8b08/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= +google.golang.org/genproto v0.0.0-20210608205507-b6d2f5bf0d7d/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= +google.golang.org/genproto v0.0.0-20210624195500-8bfb893ecb84/go.mod h1:SzzZ/N+nwJDaO1kznhnlzqS8ocJICar6hYhVyhi++24= +google.golang.org/genproto v0.0.0-20210713002101-d411969a0d9a/go.mod h1:AxrInvYm1dci+enl5hChSFPOmmUF1+uAa/UsgNRWd7k= +google.golang.org/genproto v0.0.0-20210716133855-ce7ef5c701ea/go.mod h1:AxrInvYm1dci+enl5hChSFPOmmUF1+uAa/UsgNRWd7k= +google.golang.org/genproto v0.0.0-20210728212813-7823e685a01f/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48= +google.golang.org/genproto v0.0.0-20210805201207-89edb61ffb67/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48= +google.golang.org/genproto v0.0.0-20210813162853-db860fec028c/go.mod h1:cFeNkxwySK631ADgubI+/XFU/xp8FD5KIVV4rj8UC5w= +google.golang.org/genproto v0.0.0-20210821163610-241b8fcbd6c8/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= +google.golang.org/genproto v0.0.0-20210828152312-66f60bf46e71/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= +google.golang.org/genproto v0.0.0-20210917145530-b395a37504d4 h1:ysnBoUyeL/H6RCvNRhWHjKoDEmguI+mPU+qHgK8qv/w= +google.golang.org/genproto v0.0.0-20210917145530-b395a37504d4/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= +google.golang.org/grpc v1.8.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= 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= @@ -724,11 +1600,34 @@ google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ij 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.24.0/go.mod h1:XDChyiUovWa60DnaeDeZmSW86xtLtjtZbwvSiRnRtcA= google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= +google.golang.org/grpc v1.29.0/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= +google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= +google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= +google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= +google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8= google.golang.org/grpc v1.35.0 h1:TwIQcH3es+MojMVojxxfQ3l3OF2KzlRxML2xZq0kRo8= google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/grpc v1.36.1/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/grpc v1.37.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= +google.golang.org/grpc v1.37.1/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= +google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= +google.golang.org/grpc v1.39.0/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE= +google.golang.org/grpc v1.39.1/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE= +google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= +google.golang.org/grpc v1.41.0/go.mod h1:U3l9uK9J0sini8mHphKoXyaqDA/8VyGnDee1zzIUK6k= +google.golang.org/grpc v1.42.0 h1:XT2/MFpuPFsEX2fWh3YQtHkZ+WYZFQRfaUgLZYj/p6A= +google.golang.org/grpc v1.42.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU= +google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= @@ -737,19 +1636,33 @@ google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzi google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= google.golang.org/protobuf v1.25.0 h1:Ejskq+SyPohKW+1uil0JJMtmHCgJPJ/qWTxr8qp+R4c= google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.27.1 h1:SnqbnDw1V7RiZcXPx5MEeqPv2s79L9i7BJUlG/+RurQ= +google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 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/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b h1:QRR6H1YWRnHb4Y/HeNFCTJLFVxaq6wH4YuVdsUOr75U= +gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/cheggaaa/pb.v1 v1.0.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw= +gopkg.in/cheggaaa/pb.v1 v1.0.28/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/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/gcfg.v1 v1.2.3/go.mod h1:yesOnuUOFQAhST5vPY4nbZsb/huCgGGXlipJsBn0b3o= +gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8bDuhia5mkpMnE= +gopkg.in/go-playground/validator.v9 v9.29.1/go.mod h1:+c9/zcJMFNgbLvly1L1V+PpxWdVbfP1avr/N00E2vyQ= gopkg.in/ini.v1 v1.51.0 h1:AQvPpx3LzTDM0AjnIRlVFwFFGC+npRopjZxLJj6gdno= gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/ini.v1 v1.63.2 h1:tGK/CyBg7SMzb60vP1M03vNZ3VDu3wGQJwn7Sxi9r3c= +gopkg.in/ini.v1 v1.63.2/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= @@ -757,20 +1670,38 @@ gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRN gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.6/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw= 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-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/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= +honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +honnef.co/go/tools v0.2.1/go.mod h1:lPVVZ2BS5TfnjLyizF7o7hv7j9/L+8cZY2hLyjP9cGY= +mvdan.cc/gofumpt v0.1.1/go.mod h1:yXG1r1WqZVKWbVRtBWKWX9+CxGYfA51nSomhM0woR48= +mvdan.cc/interfacer v0.0.0-20180901003855-c20040233aed/go.mod h1:Xkxe497xwlCKkIaQYRfC7CSLworTXY9RMqwhhCm+8Nc= +mvdan.cc/lint v0.0.0-20170908181259-adc824a0674b/go.mod h1:2odslEg/xrtNQqCYg2/jCoyKnw3vv5biOc3JnIcYfL4= +mvdan.cc/unparam v0.0.0-20210104141923-aac4ce9116a7/go.mod h1:hBpJkZE8H/sb+VRFvw2+rBpHNsTBcvSpk61hr8mzXZE= +pgregory.net/rapid v0.4.7/go.mod h1:UYpPVyjFHzYBGHIxLFoupi8vwk6rXNzRY9OMvVxFIOU= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= +rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= +rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o= +sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc= sourcegraph.com/sourcegraph/appdash v0.0.0-20190731080439-ebfcffb1b5c0/go.mod h1:hI742Nqp5OhwiqlzhgfbWU4mW4yO10fP+LoT9WOswdU= diff --git a/light/proxy/routes.go b/light/proxy/routes.go index e3f139dc6..4cbf26117 100644 --- a/light/proxy/routes.go +++ b/light/proxy/routes.go @@ -29,6 +29,7 @@ func RPCRoutes(c *lrpc.Client) map[string]*rpcserver.RPCFunc { "commit": rpcserver.NewRPCFunc(makeCommitFunc(c), "height"), "tx": rpcserver.NewRPCFunc(makeTxFunc(c), "hash,prove"), "tx_search": rpcserver.NewRPCFunc(makeTxSearchFunc(c), "query,prove,page,per_page,order_by"), + "block_search": rpcserver.NewRPCFunc(makeBlockSearchFunc(c), "query,page,per_page,order_by"), "validators": rpcserver.NewRPCFunc(makeValidatorsFunc(c), "height,page,per_page"), "voters": rpcserver.NewRPCFunc(makeVotersFunc(c), "height,page,per_page"), "dump_consensus_state": rpcserver.NewRPCFunc(makeDumpConsensusStateFunc(c), ""), @@ -132,16 +133,46 @@ func makeTxFunc(c *lrpc.Client) rpcTxFunc { } } -type rpcTxSearchFunc func(ctx *rpctypes.Context, query string, prove bool, - page, perPage *int, orderBy string) (*ctypes.ResultTxSearch, error) +type rpcTxSearchFunc func( + ctx *rpctypes.Context, + query string, + prove bool, + page, perPage *int, + orderBy string, +) (*ctypes.ResultTxSearch, error) func makeTxSearchFunc(c *lrpc.Client) rpcTxSearchFunc { - return func(ctx *rpctypes.Context, query string, prove bool, page, perPage *int, orderBy string) ( - *ctypes.ResultTxSearch, error) { + return func( + ctx *rpctypes.Context, + query string, + prove bool, + page, perPage *int, + orderBy string, + ) (*ctypes.ResultTxSearch, error) { return c.TxSearch(ctx.Context(), query, prove, page, perPage, orderBy) } } +type rpcBlockSearchFunc func( + ctx *rpctypes.Context, + query string, + prove bool, + page, perPage *int, + orderBy string, +) (*ctypes.ResultBlockSearch, error) + +func makeBlockSearchFunc(c *lrpc.Client) rpcBlockSearchFunc { + return func( + ctx *rpctypes.Context, + query string, + prove bool, + page, perPage *int, + orderBy string, + ) (*ctypes.ResultBlockSearch, error) { + return c.BlockSearch(ctx.Context(), query, page, perPage, orderBy) + } +} + type rpcValidatorsFunc func(ctx *rpctypes.Context, height *int64, page, perPage *int) (*ctypes.ResultValidators, error) diff --git a/light/rpc/client.go b/light/rpc/client.go index d2bdb9af5..110e8ca1b 100644 --- a/light/rpc/client.go +++ b/light/rpc/client.go @@ -35,15 +35,18 @@ type LightClient interface { TrustedLightBlock(height int64) (*types.LightBlock, error) } +var _ rpcclient.Client = (*Client)(nil) + // Client is an RPC client, which uses light#Client to verify data (if it can -// be proved!). merkle.DefaultProofRuntime is used to verify values returned by -// ABCIQuery. +// be proved). Note, merkle.DefaultProofRuntime is used to verify values +// returned by ABCI#Query. type Client struct { service.BaseService next rpcclient.Client lc LightClient - // Proof runtime used to verify values returned by ABCIQuery + + // proof runtime used to verify values returned by ABCIQuery prt *merkle.ProofRuntime keyPathFn KeyPathFunc } @@ -470,11 +473,25 @@ func (c *Client) Tx(ctx context.Context, hash []byte, prove bool) (*ctypes.Resul return res, res.Proof.Validate(l.DataHash) } -func (c *Client) TxSearch(ctx context.Context, query string, prove bool, page, perPage *int, orderBy string) ( - *ctypes.ResultTxSearch, error) { +func (c *Client) TxSearch( + ctx context.Context, + query string, + prove bool, + page, perPage *int, + orderBy string, +) (*ctypes.ResultTxSearch, error) { return c.next.TxSearch(ctx, query, prove, page, perPage, orderBy) } +func (c *Client) BlockSearch( + ctx context.Context, + query string, + page, perPage *int, + orderBy string, +) (*ctypes.ResultBlockSearch, error) { + return c.next.BlockSearch(ctx, query, page, perPage, orderBy) +} + // Validators fetches and verifies validators. // // WARNING: only full validator sets are verified (when length of validators is @@ -484,8 +501,9 @@ func (c *Client) Validators( height *int64, pagePtr, perPagePtr *int, ) (*ctypes.ResultValidators, error) { - // Update the light client if we're behind and retrieve the light block at the requested height - // or at the latest height if no height is provided. + + // Update the light client if we're behind and retrieve the light block at the + // requested height or at the latest height if no height is provided. l, err := c.updateLightClientIfNeededTo(ctx, height) if err != nil { return nil, err @@ -499,7 +517,6 @@ func (c *Client) Validators( } skipCount := validateSkipCount(page, perPage) - v := l.ValidatorSet.Validators[skipCount : skipCount+tmmath.MinInt(perPage, totalCount-skipCount)] return &ctypes.ResultValidators{ diff --git a/node/node.go b/node/node.go index d1dd3bfdd..1ec3fa5fc 100644 --- a/node/node.go +++ b/node/node.go @@ -17,6 +17,7 @@ import ( "github.com/rs/cors" dbm "github.com/line/tm-db/v2" + pdbm "github.com/line/tm-db/v2/prefixdb" abci "github.com/line/ostracon/abci/types" bcv0 "github.com/line/ostracon/blockchain/v0" @@ -40,6 +41,9 @@ import ( grpccore "github.com/line/ostracon/rpc/grpc" rpcserver "github.com/line/ostracon/rpc/jsonrpc/server" sm "github.com/line/ostracon/state" + "github.com/line/ostracon/state/indexer" + blockidxkv "github.com/line/ostracon/state/indexer/block/kv" + blockidxnull "github.com/line/ostracon/state/indexer/block/null" "github.com/line/ostracon/state/txindex" "github.com/line/ostracon/state/txindex/kv" "github.com/line/ostracon/state/txindex/null" @@ -210,6 +214,7 @@ type Node struct { proxyApp proxy.AppConns // connection to the application rpcListeners []net.Listener // rpc servers txIndexer txindex.TxIndexer + blockIndexer indexer.BlockIndexer indexerService *txindex.IndexerService prometheusSrv *http.Server } @@ -248,27 +253,40 @@ func createAndStartEventBus(logger log.Logger) (*types.EventBus, error) { return eventBus, nil } -func createAndStartIndexerService(config *cfg.Config, dbProvider DBProvider, - eventBus *types.EventBus, logger log.Logger) (*txindex.IndexerService, txindex.TxIndexer, error) { +func createAndStartIndexerService( + config *cfg.Config, + dbProvider DBProvider, + eventBus *types.EventBus, + logger log.Logger, +) (*txindex.IndexerService, txindex.TxIndexer, indexer.BlockIndexer, error) { + + var ( + txIndexer txindex.TxIndexer + blockIndexer indexer.BlockIndexer + ) - var txIndexer txindex.TxIndexer switch config.TxIndex.Indexer { case "kv": store, err := dbProvider(&DBContext{"tx_index", config}) if err != nil { - return nil, nil, err + return nil, nil, nil, err } + txIndexer = kv.NewTxIndex(store) + blockIndexer = blockidxkv.New(pdbm.NewDB(store, []byte("block_events"))) default: txIndexer = &null.TxIndex{} + blockIndexer = &blockidxnull.BlockerIndexer{} } - indexerService := txindex.NewIndexerService(txIndexer, eventBus) + indexerService := txindex.NewIndexerService(txIndexer, blockIndexer, eventBus) indexerService.SetLogger(logger.With("module", "txindex")) + if err := indexerService.Start(); err != nil { - return nil, nil, err + return nil, nil, nil, err } - return indexerService, txIndexer, nil + + return indexerService, txIndexer, blockIndexer, nil } func doHandshake( @@ -670,8 +688,7 @@ func NewNode(config *cfg.Config, return nil, err } - // Transaction indexing - indexerService, txIndexer, err := createAndStartIndexerService(config, dbProvider, eventBus, logger) + indexerService, txIndexer, blockIndexer, err := createAndStartIndexerService(config, dbProvider, eventBus, logger) if err != nil { return nil, err } @@ -848,6 +865,7 @@ func NewNode(config *cfg.Config, proxyApp: proxyApp, txIndexer: txIndexer, indexerService: indexerService, + blockIndexer: blockIndexer, eventBus: eventBus, } node.BaseService = *service.NewBaseService(logger, "Node", node) @@ -1004,6 +1022,7 @@ func (n *Node) ConfigureRPC() error { PubKey: pubKey, GenDoc: n.genesisDoc, TxIndexer: n.txIndexer, + BlockIndexer: n.blockIndexer, ConsensusReactor: n.consensusReactor, EventBus: n.eventBus, Mempool: n.mempool, diff --git a/rpc/client/http/http.go b/rpc/client/http/http.go index d83a2b000..834a04940 100644 --- a/rpc/client/http/http.go +++ b/rpc/client/http/http.go @@ -468,24 +468,55 @@ func (c *baseRPCClient) TxSearch( page, perPage *int, orderBy string, -) ( - *ctypes.ResultTxSearch, error) { +) (*ctypes.ResultTxSearch, error) { + result := new(ctypes.ResultTxSearch) params := map[string]interface{}{ "query": query, "prove": prove, "order_by": orderBy, } + if page != nil { params["page"] = page } if perPage != nil { params["per_page"] = perPage } + _, err := c.caller.Call(ctx, "tx_search", params, result) if err != nil { return nil, err } + + return result, nil +} + +func (c *baseRPCClient) BlockSearch( + ctx context.Context, + query string, + page, perPage *int, + orderBy string, +) (*ctypes.ResultBlockSearch, error) { + + result := new(ctypes.ResultBlockSearch) + params := map[string]interface{}{ + "query": query, + "order_by": orderBy, + } + + if page != nil { + params["page"] = page + } + if perPage != nil { + params["per_page"] = perPage + } + + _, err := c.caller.Call(ctx, "block_search", params, result) + if err != nil { + return nil, err + } + return result, nil } diff --git a/rpc/client/interface.go b/rpc/client/interface.go index de4a56173..e70b165c5 100644 --- a/rpc/client/interface.go +++ b/rpc/client/interface.go @@ -72,8 +72,25 @@ type SignClient interface { Validators(ctx context.Context, height *int64, page, perPage *int) (*ctypes.ResultValidators, error) Voters(ctx context.Context, height *int64, page, perPage *int) (*ctypes.ResultVoters, error) Tx(ctx context.Context, hash []byte, prove bool) (*ctypes.ResultTx, error) - TxSearch(ctx context.Context, query string, prove bool, page, perPage *int, - orderBy string) (*ctypes.ResultTxSearch, error) + + // TxSearch defines a method to search for a paginated set of transactions by + // DeliverTx event search criteria. + TxSearch( + ctx context.Context, + query string, + prove bool, + page, perPage *int, + orderBy string, + ) (*ctypes.ResultTxSearch, error) + + // BlockSearch defines a method to search for a paginated set of blocks by + // BeginBlock and EndBlock event search criteria. + BlockSearch( + ctx context.Context, + query string, + page, perPage *int, + orderBy string, + ) (*ctypes.ResultBlockSearch, error) } // HistoryClient provides access to data from genesis to now in large chunks. diff --git a/rpc/client/local/local.go b/rpc/client/local/local.go index 6e2b66ac5..fb00e2d76 100644 --- a/rpc/client/local/local.go +++ b/rpc/client/local/local.go @@ -182,7 +182,7 @@ func (c *Local) Tx(ctx context.Context, hash []byte, prove bool) (*ctypes.Result } func (c *Local) TxSearch( - ctx context.Context, + _ context.Context, query string, prove bool, page, @@ -192,6 +192,15 @@ func (c *Local) TxSearch( return core.TxSearch(c.ctx, query, prove, page, perPage, orderBy) } +func (c *Local) BlockSearch( + _ context.Context, + query string, + page, perPage *int, + orderBy string, +) (*ctypes.ResultBlockSearch, error) { + return core.BlockSearch(c.ctx, query, page, perPage, orderBy) +} + func (c *Local) BroadcastEvidence(ctx context.Context, ev types.Evidence) (*ctypes.ResultBroadcastEvidence, error) { return core.BroadcastEvidence(c.ctx, ev) } diff --git a/rpc/client/mocks/client.go b/rpc/client/mocks/client.go index 990c5e846..1881a3b92 100644 --- a/rpc/client/mocks/client.go +++ b/rpc/client/mocks/client.go @@ -160,6 +160,29 @@ func (_m *Client) BlockResults(ctx context.Context, height *int64) (*coretypes.R return r0, r1 } +// BlockSearch provides a mock function with given fields: ctx, query, page, perPage, orderBy +func (_m *Client) BlockSearch(ctx context.Context, query string, page *int, perPage *int, orderBy string) (*coretypes.ResultBlockSearch, error) { + ret := _m.Called(ctx, query, page, perPage, orderBy) + + var r0 *coretypes.ResultBlockSearch + if rf, ok := ret.Get(0).(func(context.Context, string, *int, *int, string) *coretypes.ResultBlockSearch); ok { + r0 = rf(ctx, query, page, perPage, orderBy) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*coretypes.ResultBlockSearch) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, string, *int, *int, string) error); ok { + r1 = rf(ctx, query, page, perPage, orderBy) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + // BlockchainInfo provides a mock function with given fields: ctx, minHeight, maxHeight func (_m *Client) BlockchainInfo(ctx context.Context, minHeight int64, maxHeight int64) (*coretypes.ResultBlockchainInfo, error) { ret := _m.Called(ctx, minHeight, maxHeight) diff --git a/rpc/core/blocks.go b/rpc/core/blocks.go index f980aee8d..a8f44d04b 100644 --- a/rpc/core/blocks.go +++ b/rpc/core/blocks.go @@ -1,11 +1,15 @@ package core import ( + "errors" "fmt" + "sort" tmmath "github.com/line/ostracon/libs/math" + tmquery "github.com/line/ostracon/libs/pubsub/query" ctypes "github.com/line/ostracon/rpc/core/types" rpctypes "github.com/line/ostracon/rpc/jsonrpc/types" + blockidxnull "github.com/line/ostracon/state/indexer/block/null" "github.com/line/ostracon/types" ) @@ -154,3 +158,68 @@ func BlockResults(ctx *rpctypes.Context, heightPtr *int64) (*ctypes.ResultBlockR ConsensusParamUpdates: results.EndBlock.ConsensusParamUpdates, }, nil } + +// BlockSearch searches for a paginated set of blocks matching BeginBlock and +// EndBlock event search criteria. +func BlockSearch( + ctx *rpctypes.Context, + query string, + pagePtr, perPagePtr *int, + orderBy string, +) (*ctypes.ResultBlockSearch, error) { + + // skip if block indexing is disabled + if _, ok := env.BlockIndexer.(*blockidxnull.BlockerIndexer); ok { + return nil, errors.New("block indexing is disabled") + } + + q, err := tmquery.New(query) + if err != nil { + return nil, err + } + + results, err := env.BlockIndexer.Search(ctx.Context(), q) + if err != nil { + return nil, err + } + + // sort results (must be done before pagination) + switch orderBy { + case "desc", "": + sort.Slice(results, func(i, j int) bool { return results[i] > results[j] }) + + case "asc": + sort.Slice(results, func(i, j int) bool { return results[i] < results[j] }) + + default: + return nil, errors.New("expected order_by to be either `asc` or `desc` or empty") + } + + // paginate results + totalCount := len(results) + perPage := validatePerPage(perPagePtr) + + page, err := validatePage(pagePtr, perPage, totalCount) + if err != nil { + return nil, err + } + + skipCount := validateSkipCount(page, perPage) + pageSize := tmmath.MinInt(perPage, totalCount-skipCount) + + apiResults := make([]*ctypes.ResultBlock, 0, pageSize) + for i := skipCount; i < skipCount+pageSize; i++ { + block := env.BlockStore.LoadBlock(results[i]) + if block != nil { + blockMeta := env.BlockStore.LoadBlockMeta(block.Height) + if blockMeta != nil { + apiResults = append(apiResults, &ctypes.ResultBlock{ + Block: block, + BlockID: blockMeta.BlockID, + }) + } + } + } + + return &ctypes.ResultBlockSearch{Blocks: apiResults, TotalCount: totalCount}, nil +} diff --git a/rpc/core/env.go b/rpc/core/env.go index 6016c4fba..945a9c6d3 100644 --- a/rpc/core/env.go +++ b/rpc/core/env.go @@ -12,6 +12,7 @@ import ( "github.com/line/ostracon/p2p" "github.com/line/ostracon/proxy" sm "github.com/line/ostracon/state" + "github.com/line/ostracon/state/indexer" "github.com/line/ostracon/state/txindex" "github.com/line/ostracon/types" ) @@ -85,6 +86,7 @@ type Environment struct { PubKey crypto.PubKey GenDoc *types.GenesisDoc // cache the genesis structure TxIndexer txindex.TxIndexer + BlockIndexer indexer.BlockIndexer ConsensusReactor *consensus.Reactor EventBus *types.EventBus // thread safe Mempool mempl.Mempool diff --git a/rpc/core/routes.go b/rpc/core/routes.go index 0381e7657..af0974043 100644 --- a/rpc/core/routes.go +++ b/rpc/core/routes.go @@ -26,6 +26,7 @@ var Routes = map[string]*rpc.RPCFunc{ "check_tx": rpc.NewRPCFunc(CheckTx, "tx"), "tx": rpc.NewRPCFunc(Tx, "hash,prove"), "tx_search": rpc.NewRPCFunc(TxSearch, "query,prove,page,per_page,order_by"), + "block_search": rpc.NewRPCFunc(BlockSearch, "query,page,per_page,order_by"), "validators": rpc.NewRPCFunc(Validators, "height,page,per_page"), "voters": rpc.NewRPCFunc(Voters, "height,page,per_page"), "dump_consensus_state": rpc.NewRPCFunc(DumpConsensusState, ""), diff --git a/rpc/core/tx.go b/rpc/core/tx.go index ce0a08632..792822902 100644 --- a/rpc/core/tx.go +++ b/rpc/core/tx.go @@ -54,8 +54,14 @@ func Tx(ctx *rpctypes.Context, hash []byte, prove bool) (*ctypes.ResultTx, error // TxSearch allows you to query for multiple transactions results. It returns a // list of transactions (maximum ?per_page entries) and the total count. // More: https://docs.tendermint.com/master/rpc/#/Info/tx_search -func TxSearch(ctx *rpctypes.Context, query string, prove bool, pagePtr, perPagePtr *int, orderBy string) ( - *ctypes.ResultTxSearch, error) { +func TxSearch( + ctx *rpctypes.Context, + query string, + prove bool, + pagePtr, perPagePtr *int, + orderBy string, +) (*ctypes.ResultTxSearch, error) { + // if index is disabled, return error if _, ok := env.TxIndexer.(*null.TxIndex); ok { return nil, errors.New("transaction indexing is disabled") @@ -94,10 +100,12 @@ func TxSearch(ctx *rpctypes.Context, query string, prove bool, pagePtr, perPageP // paginate results totalCount := len(results) perPage := validatePerPage(perPagePtr) + page, err := validatePage(pagePtr, perPage, totalCount) if err != nil { return nil, err } + skipCount := validateSkipCount(page, perPage) pageSize := tmmath.MinInt(perPage, totalCount-skipCount) diff --git a/rpc/core/types/responses.go b/rpc/core/types/responses.go index c69a087ec..2eeda5e3d 100644 --- a/rpc/core/types/responses.go +++ b/rpc/core/types/responses.go @@ -206,6 +206,12 @@ type ResultTxSearch struct { TotalCount int `json:"total_count"` } +// ResultBlockSearch defines the RPC response type for a block search by events. +type ResultBlockSearch struct { + Blocks []*ResultBlock `json:"blocks"` + TotalCount int `json:"total_count"` +} + // List of mempool txs type ResultUnconfirmedTxs struct { Count int `json:"n_txs"` diff --git a/rpc/openapi/openapi.yaml b/rpc/openapi/openapi.yaml index dbc5d3069..b6f6451c0 100644 --- a/rpc/openapi/openapi.yaml +++ b/rpc/openapi/openapi.yaml @@ -1014,6 +1014,62 @@ paths: application/json: schema: $ref: "#/components/schemas/ErrorResponse" + /block_search: + get: + summary: Search for blocks by BeginBlock and EndBlock events + description: | + Search for blocks by BeginBlock and EndBlock events. + + See /subscribe for the query syntax. + operationId: block_search + parameters: + - in: query + name: query + description: Query + required: true + schema: + type: string + example: "block.height > 1000 AND valset.changed > 0" + - in: query + name: page + description: "Page number (1-based)" + required: false + schema: + type: integer + default: 1 + example: 1 + - in: query + name: per_page + description: "Number of entries per page (max: 100)" + required: false + schema: + type: integer + default: 30 + example: 30 + - in: query + name: order_by + description: Order in which blocks are sorted ("asc" or "desc"), by height. If empty, default sorting will be still applied. + required: false + schema: + type: string + default: "desc" + example: "asc" + tags: + - Info + responses: + "200": + description: List of paginated blocks matching the search criteria. + content: + application/json: + schema: + $ref: "#/components/schemas/BlockSearchResponse" + "500": + description: Error + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorResponse" + /tx: get: summary: Get transactions by hash diff --git a/state/indexer/block.go b/state/indexer/block.go new file mode 100644 index 000000000..941705d2f --- /dev/null +++ b/state/indexer/block.go @@ -0,0 +1,22 @@ +package indexer + +import ( + "context" + + "github.com/line/ostracon/libs/pubsub/query" + "github.com/line/ostracon/types" +) + +// BlockIndexer defines an interface contract for indexing block events. +type BlockIndexer interface { + // Has returns true if the given height has been indexed. An error is returned + // upon database query failure. + Has(height int64) (bool, error) + + // Index indexes BeginBlock and EndBlock events for a given block by its height. + Index(types.EventDataNewBlockHeader) error + + // Search performs a query for block heights that match a given BeginBlock + // and Endblock event search criteria. + Search(ctx context.Context, q *query.Query) ([]int64, error) +} diff --git a/state/indexer/block/kv/kv.go b/state/indexer/block/kv/kv.go new file mode 100644 index 000000000..799a2434c --- /dev/null +++ b/state/indexer/block/kv/kv.go @@ -0,0 +1,489 @@ +package kv + +import ( + "context" + "errors" + "fmt" + "sort" + "strconv" + "strings" + + "github.com/google/orderedcode" + db "github.com/line/tm-db/v2" + + abci "github.com/line/ostracon/abci/types" + "github.com/line/ostracon/libs/pubsub/query" + "github.com/line/ostracon/state/indexer" + "github.com/line/ostracon/types" +) + +var _ indexer.BlockIndexer = (*BlockerIndexer)(nil) + +// BlockerIndexer implements a block indexer, indexing BeginBlock and EndBlock +// events with an underlying KV store. Block events are indexed by their height, +// such that matching search criteria returns the respective block height(s). +type BlockerIndexer struct { + store db.DB +} + +func New(store db.DB) *BlockerIndexer { + return &BlockerIndexer{ + store: store, + } +} + +// Has returns true if the given height has been indexed. An error is returned +// upon database query failure. +func (idx *BlockerIndexer) Has(height int64) (bool, error) { + key, err := heightKey(height) + if err != nil { + return false, fmt.Errorf("failed to create block height index key: %w", err) + } + + return idx.store.Has(key) +} + +// Index indexes BeginBlock and EndBlock events for a given block by its height. +// The following is indexed: +// +// primary key: encode(block.height | height) => encode(height) +// BeginBlock events: encode(eventType.eventAttr|eventValue|height|begin_block) => encode(height) +// EndBlock events: encode(eventType.eventAttr|eventValue|height|end_block) => encode(height) +func (idx *BlockerIndexer) Index(bh types.EventDataNewBlockHeader) error { + batch := idx.store.NewBatch() + defer batch.Close() + + height := bh.Header.Height + + // 1. index by height + key, err := heightKey(height) + if err != nil { + return fmt.Errorf("failed to create block height index key: %w", err) + } + if err := batch.Set(key, int64ToBytes(height)); err != nil { + return err + } + + // 2. index BeginBlock events + if err := idx.indexEvents(batch, bh.ResultBeginBlock.Events, "begin_block", height); err != nil { + return fmt.Errorf("failed to index BeginBlock events: %w", err) + } + + // 3. index EndBlock events + if err := idx.indexEvents(batch, bh.ResultEndBlock.Events, "end_block", height); err != nil { + return fmt.Errorf("failed to index EndBlock events: %w", err) + } + + return batch.WriteSync() +} + +// Search performs a query for block heights that match a given BeginBlock +// and Endblock event search criteria. The given query can match against zero, +// one or more block heights. In the case of height queries, i.e. block.height=H, +// if the height is indexed, that height alone will be returned. An error and +// nil slice is returned. Otherwise, a non-nil slice and nil error is returned. +func (idx *BlockerIndexer) Search(ctx context.Context, q *query.Query) ([]int64, error) { + results := make([]int64, 0) + select { + case <-ctx.Done(): + return results, nil + + default: + } + + conditions, err := q.Conditions() + if err != nil { + return nil, fmt.Errorf("failed to parse query conditions: %w", err) + } + + // If there is an exact height query, return the result immediately + // (if it exists). + height, ok := lookForHeight(conditions) + if ok { + ok, err := idx.Has(height) + if err != nil { + return nil, err + } + + if ok { + return []int64{height}, nil + } + + return results, nil + } + + var heightsInitialized bool + filteredHeights := make(map[string][]byte) + + // conditions to skip because they're handled before "everything else" + skipIndexes := make([]int, 0) + + // Extract ranges. If both upper and lower bounds exist, it's better to get + // them in order as to not iterate over kvs that are not within range. + ranges, rangeIndexes := indexer.LookForRanges(conditions) + if len(ranges) > 0 { + skipIndexes = append(skipIndexes, rangeIndexes...) + + for _, qr := range ranges { + prefix, err := orderedcode.Append(nil, qr.Key) + if err != nil { + return nil, fmt.Errorf("failed to create prefix key: %w", err) + } + + if !heightsInitialized { + filteredHeights, err = idx.matchRange(ctx, qr, prefix, filteredHeights, true) + if err != nil { + return nil, err + } + + heightsInitialized = true + + // Ignore any remaining conditions if the first condition resulted in no + // matches (assuming implicit AND operand). + if len(filteredHeights) == 0 { + break + } + } else { + filteredHeights, err = idx.matchRange(ctx, qr, prefix, filteredHeights, false) + if err != nil { + return nil, err + } + } + } + } + + // for all other conditions + for i, c := range conditions { + if intInSlice(i, skipIndexes) { + continue + } + + startKey, err := orderedcode.Append(nil, c.CompositeKey, fmt.Sprintf("%v", c.Operand)) + if err != nil { + return nil, err + } + + if !heightsInitialized { + filteredHeights, err = idx.match(ctx, c, startKey, filteredHeights, true) + if err != nil { + return nil, err + } + + heightsInitialized = true + + // Ignore any remaining conditions if the first condition resulted in no + // matches (assuming implicit AND operand). + if len(filteredHeights) == 0 { + break + } + } else { + filteredHeights, err = idx.match(ctx, c, startKey, filteredHeights, false) + if err != nil { + return nil, err + } + } + } + + // fetch matching heights + results = make([]int64, 0, len(filteredHeights)) + for _, hBz := range filteredHeights { + h := int64FromBytes(hBz) + + ok, err := idx.Has(h) + if err != nil { + return nil, err + } + if ok { + results = append(results, h) + } + + select { + case <-ctx.Done(): + break + + default: + } + } + + sort.Slice(results, func(i, j int) bool { return results[i] < results[j] }) + + return results, nil +} + +// matchRange returns all matching block heights that match a given QueryRange +// and start key. An already filtered result (filteredHeights) is provided such +// that any non-intersecting matches are removed. +// +// NOTE: The provided filteredHeights may be empty if no previous condition has +// matched. +func (idx *BlockerIndexer) matchRange( + ctx context.Context, + qr indexer.QueryRange, + startKey []byte, + filteredHeights map[string][]byte, + firstRun bool, +) (map[string][]byte, error) { + + // A previous match was attempted but resulted in no matches, so we return + // no matches (assuming AND operand). + if !firstRun && len(filteredHeights) == 0 { + return filteredHeights, nil + } + + tmpHeights := make(map[string][]byte) + lowerBound := qr.LowerBoundValue() + upperBound := qr.UpperBoundValue() + + it, err := idx.store.PrefixIterator(startKey) + if err != nil { + return nil, fmt.Errorf("failed to create prefix iterator: %w", err) + } + defer it.Close() + +LOOP: + for ; it.Valid(); it.Next() { + var ( + eventValue string + err error + ) + + if qr.Key == types.BlockHeightKey { + eventValue, err = parseValueFromPrimaryKey(it.Key()) + } else { + eventValue, err = parseValueFromEventKey(it.Key()) + } + + if err != nil { + continue + } + + if _, ok := qr.AnyBound().(int64); ok { + v, err := strconv.ParseInt(eventValue, 10, 64) + if err != nil { + continue LOOP + } + + include := true + if lowerBound != nil && v < lowerBound.(int64) { + include = false + } + + if upperBound != nil && v > upperBound.(int64) { + include = false + } + + if include { + tmpHeights[string(it.Value())] = it.Value() + } + } + + select { + case <-ctx.Done(): + break + + default: + } + } + + if err := it.Error(); err != nil { + return nil, err + } + + if len(tmpHeights) == 0 || firstRun { + // Either: + // + // 1. Regardless if a previous match was attempted, which may have had + // results, but no match was found for the current condition, then we + // return no matches (assuming AND operand). + // + // 2. A previous match was not attempted, so we return all results. + return tmpHeights, nil + } + + // Remove/reduce matches in filteredHashes that were not found in this + // match (tmpHashes). + for k := range filteredHeights { + if tmpHeights[k] == nil { + delete(filteredHeights, k) + + select { + case <-ctx.Done(): + break + + default: + } + } + } + + return filteredHeights, nil +} + +// match returns all matching heights that meet a given query condition and start +// key. An already filtered result (filteredHeights) is provided such that any +// non-intersecting matches are removed. +// +// NOTE: The provided filteredHeights may be empty if no previous condition has +// matched. +func (idx *BlockerIndexer) match( + ctx context.Context, + c query.Condition, + startKeyBz []byte, + filteredHeights map[string][]byte, + firstRun bool, +) (map[string][]byte, error) { + + // A previous match was attempted but resulted in no matches, so we return + // no matches (assuming AND operand). + if !firstRun && len(filteredHeights) == 0 { + return filteredHeights, nil + } + + tmpHeights := make(map[string][]byte) + + switch { + case c.Op == query.OpEqual: + it, err := idx.store.PrefixIterator(startKeyBz) + if err != nil { + return nil, fmt.Errorf("failed to create prefix iterator: %w", err) + } + defer it.Close() + + for ; it.Valid(); it.Next() { + tmpHeights[string(it.Value())] = it.Value() + + if err := ctx.Err(); err != nil { + break + } + } + + if err := it.Error(); err != nil { + return nil, err + } + + case c.Op == query.OpExists: + prefix, err := orderedcode.Append(nil, c.CompositeKey) + if err != nil { + return nil, err + } + + it, err := idx.store.PrefixIterator(prefix) + if err != nil { + return nil, fmt.Errorf("failed to create prefix iterator: %w", err) + } + defer it.Close() + + for ; it.Valid(); it.Next() { + tmpHeights[string(it.Value())] = it.Value() + + select { + case <-ctx.Done(): + break + + default: + } + } + + if err := it.Error(); err != nil { + return nil, err + } + + case c.Op == query.OpContains: + prefix, err := orderedcode.Append(nil, c.CompositeKey) + if err != nil { + return nil, err + } + + it, err := idx.store.PrefixIterator(prefix) + if err != nil { + return nil, fmt.Errorf("failed to create prefix iterator: %w", err) + } + defer it.Close() + + for ; it.Valid(); it.Next() { + eventValue, err := parseValueFromEventKey(it.Key()) + if err != nil { + continue + } + + if strings.Contains(eventValue, c.Operand.(string)) { + tmpHeights[string(it.Value())] = it.Value() + } + + select { + case <-ctx.Done(): + break + + default: + } + } + if err := it.Error(); err != nil { + return nil, err + } + + default: + return nil, errors.New("other operators should be handled already") + } + + if len(tmpHeights) == 0 || firstRun { + // Either: + // + // 1. Regardless if a previous match was attempted, which may have had + // results, but no match was found for the current condition, then we + // return no matches (assuming AND operand). + // + // 2. A previous match was not attempted, so we return all results. + return tmpHeights, nil + } + + // Remove/reduce matches in filteredHeights that were not found in this + // match (tmpHeights). + for k := range filteredHeights { + if tmpHeights[k] == nil { + delete(filteredHeights, k) + + select { + case <-ctx.Done(): + break + + default: + } + } + } + + return filteredHeights, nil +} + +func (idx *BlockerIndexer) indexEvents(batch db.Batch, events []abci.Event, typ string, height int64) error { + heightBz := int64ToBytes(height) + + for _, event := range events { + // only index events with a non-empty type + if len(event.Type) == 0 { + continue + } + + for _, attr := range event.Attributes { + if len(attr.Key) == 0 { + continue + } + + // index iff the event specified index:true and it's not a reserved event + compositeKey := fmt.Sprintf("%s.%s", event.Type, string(attr.Key)) + if compositeKey == types.TxHashKey || compositeKey == types.TxHeightKey { + return fmt.Errorf("event type and attribute key \"%s\" is reserved; please use a different key", compositeKey) + } + if attr.GetIndex() { + key, err := eventKey(compositeKey, typ, string(attr.Value), height) + if err != nil { + return fmt.Errorf("failed to create block index key: %w", err) + } + + if err := batch.Set(key, heightBz); err != nil { + return err + } + } + } + } + + return nil +} diff --git a/state/indexer/block/kv/kv_test.go b/state/indexer/block/kv/kv_test.go new file mode 100644 index 000000000..4239e6c43 --- /dev/null +++ b/state/indexer/block/kv/kv_test.go @@ -0,0 +1,141 @@ +package kv_test + +import ( + "context" + "fmt" + "testing" + + abci "github.com/line/ostracon/abci/types" + "github.com/line/ostracon/libs/pubsub/query" + blockidxkv "github.com/line/ostracon/state/indexer/block/kv" + "github.com/line/ostracon/types" + dbm "github.com/line/tm-db/v2/memdb" + dbp "github.com/line/tm-db/v2/prefixdb" + "github.com/stretchr/testify/require" +) + +func TestBlockIndexer(t *testing.T) { + store := dbp.NewDB(dbm.NewDB(), []byte("block_events")) + indexer := blockidxkv.New(store) + require.NoError(t, indexer.Index(types.EventDataNewBlockHeader{ + Header: types.Header{Height: 1}, + ResultBeginBlock: abci.ResponseBeginBlock{ + Events: []abci.Event{ + { + Type: "begin_event", + Attributes: []abci.EventAttribute{ + { + Key: []byte("proposer"), + Value: []byte("FCAA001"), + Index: true, + }, + }, + }, + }, + }, + ResultEndBlock: abci.ResponseEndBlock{ + Events: []abci.Event{ + { + Type: "end_event", + Attributes: []abci.EventAttribute{ + { + Key: []byte("foo"), + Value: []byte("100"), + Index: true, + }, + }, + }, + }, + }, + })) + + for i := 2; i < 12; i++ { + var index bool + if i%2 == 0 { + index = true + } + + require.NoError(t, indexer.Index(types.EventDataNewBlockHeader{ + Header: types.Header{Height: int64(i)}, + ResultBeginBlock: abci.ResponseBeginBlock{ + Events: []abci.Event{ + { + Type: "begin_event", + Attributes: []abci.EventAttribute{ + { + Key: []byte("proposer"), + Value: []byte("FCAA001"), + Index: true, + }, + }, + }, + }, + }, + ResultEndBlock: abci.ResponseEndBlock{ + Events: []abci.Event{ + { + Type: "end_event", + Attributes: []abci.EventAttribute{ + { + Key: []byte("foo"), + Value: []byte(fmt.Sprintf("%d", i)), + Index: index, + }, + }, + }, + }, + }, + })) + } + + testCases := map[string]struct { + q *query.Query + results []int64 + }{ + "block.height = 100": { + q: query.MustParse("block.height = 100"), + results: []int64{}, + }, + "block.height = 5": { + q: query.MustParse("block.height = 5"), + results: []int64{5}, + }, + "begin_event.key1 = 'value1'": { + q: query.MustParse("begin_event.key1 = 'value1'"), + results: []int64{}, + }, + "begin_event.proposer = 'FCAA001'": { + q: query.MustParse("begin_event.proposer = 'FCAA001'"), + results: []int64{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11}, + }, + "end_event.foo <= 5": { + q: query.MustParse("end_event.foo <= 5"), + results: []int64{2, 4}, + }, + "end_event.foo >= 100": { + q: query.MustParse("end_event.foo >= 100"), + results: []int64{1}, + }, + "block.height > 2 AND end_event.foo <= 8": { + q: query.MustParse("block.height > 2 AND end_event.foo <= 8"), + results: []int64{4, 6, 8}, + }, + "begin_event.proposer CONTAINS 'FFFFFFF'": { + q: query.MustParse("begin_event.proposer CONTAINS 'FFFFFFF'"), + results: []int64{}, + }, + "begin_event.proposer CONTAINS 'FCAA001'": { + q: query.MustParse("begin_event.proposer CONTAINS 'FCAA001'"), + results: []int64{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11}, + }, + } + + for name, tc := range testCases { + tc := tc + t.Run(name, func(t *testing.T) { + results, err := indexer.Search(context.Background(), tc.q) + require.NoError(t, err) + require.Equal(t, tc.results, results) + }) + } +} diff --git a/state/indexer/block/kv/util.go b/state/indexer/block/kv/util.go new file mode 100644 index 000000000..fe96158e7 --- /dev/null +++ b/state/indexer/block/kv/util.go @@ -0,0 +1,96 @@ +package kv + +import ( + "encoding/binary" + "fmt" + "strconv" + + "github.com/google/orderedcode" + "github.com/line/ostracon/libs/pubsub/query" + "github.com/line/ostracon/types" +) + +func intInSlice(a int, list []int) bool { + for _, b := range list { + if b == a { + return true + } + } + + return false +} + +func int64FromBytes(bz []byte) int64 { + v, _ := binary.Varint(bz) + return v +} + +func int64ToBytes(i int64) []byte { + buf := make([]byte, binary.MaxVarintLen64) + n := binary.PutVarint(buf, i) + return buf[:n] +} + +func heightKey(height int64) ([]byte, error) { + return orderedcode.Append( + nil, + types.BlockHeightKey, + height, + ) +} + +func eventKey(compositeKey, typ, eventValue string, height int64) ([]byte, error) { + return orderedcode.Append( + nil, + compositeKey, + eventValue, + height, + typ, + ) +} + +func parseValueFromPrimaryKey(key []byte) (string, error) { + var ( + compositeKey string + height int64 + ) + + remaining, err := orderedcode.Parse(string(key), &compositeKey, &height) + if err != nil { + return "", fmt.Errorf("failed to parse event key: %w", err) + } + + if len(remaining) != 0 { + return "", fmt.Errorf("unexpected remainder in key: %s", remaining) + } + + return strconv.FormatInt(height, 10), nil +} + +func parseValueFromEventKey(key []byte) (string, error) { + var ( + compositeKey, typ, eventValue string + height int64 + ) + + remaining, err := orderedcode.Parse(string(key), &compositeKey, &eventValue, &height, &typ) + if err != nil { + return "", fmt.Errorf("failed to parse event key: %w", err) + } + + if len(remaining) != 0 { + return "", fmt.Errorf("unexpected remainder in key: %s", remaining) + } + + return eventValue, nil +} + +func lookForHeight(conditions []query.Condition) (int64, bool) { + for _, c := range conditions { + if c.CompositeKey == types.BlockHeightKey && c.Op == query.OpEqual { + return c.Operand.(int64), true + } + } + + return 0, false +} diff --git a/state/indexer/block/null/null.go b/state/indexer/block/null/null.go new file mode 100644 index 000000000..ea833ac6a --- /dev/null +++ b/state/indexer/block/null/null.go @@ -0,0 +1,27 @@ +package null + +import ( + "context" + "errors" + + "github.com/line/ostracon/libs/pubsub/query" + "github.com/line/ostracon/state/indexer" + "github.com/line/ostracon/types" +) + +var _ indexer.BlockIndexer = (*BlockerIndexer)(nil) + +// TxIndex implements a no-op block indexer. +type BlockerIndexer struct{} + +func (idx *BlockerIndexer) Has(height int64) (bool, error) { + return false, errors.New(`indexing is disabled (set 'tx_index = "kv"' in config)`) +} + +func (idx *BlockerIndexer) Index(types.EventDataNewBlockHeader) error { + return nil +} + +func (idx *BlockerIndexer) Search(ctx context.Context, q *query.Query) ([]int64, error) { + return []int64{}, nil +} diff --git a/state/indexer/query_range.go b/state/indexer/query_range.go new file mode 100644 index 000000000..bcedd176b --- /dev/null +++ b/state/indexer/query_range.go @@ -0,0 +1,123 @@ +package indexer + +import ( + "time" + + "github.com/line/ostracon/libs/pubsub/query" +) + +// QueryRanges defines a mapping between a composite event key and a QueryRange. +// +// e.g.account.number => queryRange{lowerBound: 1, upperBound: 5} +type QueryRanges map[string]QueryRange + +// QueryRange defines a range within a query condition. +type QueryRange struct { + LowerBound interface{} // int || time.Time + UpperBound interface{} // int || time.Time + Key string + IncludeLowerBound bool + IncludeUpperBound bool +} + +// AnyBound returns either the lower bound if non-nil, otherwise the upper bound. +func (qr QueryRange) AnyBound() interface{} { + if qr.LowerBound != nil { + return qr.LowerBound + } + + return qr.UpperBound +} + +// LowerBoundValue returns the value for the lower bound. If the lower bound is +// nil, nil will be returned. +func (qr QueryRange) LowerBoundValue() interface{} { + if qr.LowerBound == nil { + return nil + } + + if qr.IncludeLowerBound { + return qr.LowerBound + } + + switch t := qr.LowerBound.(type) { + case int64: + return t + 1 + + case time.Time: + return t.Unix() + 1 + + default: + panic("not implemented") + } +} + +// UpperBoundValue returns the value for the upper bound. If the upper bound is +// nil, nil will be returned. +func (qr QueryRange) UpperBoundValue() interface{} { + if qr.UpperBound == nil { + return nil + } + + if qr.IncludeUpperBound { + return qr.UpperBound + } + + switch t := qr.UpperBound.(type) { + case int64: + return t - 1 + + case time.Time: + return t.Unix() - 1 + + default: + panic("not implemented") + } +} + +// LookForRanges returns a mapping of QueryRanges and the matching indexes in +// the provided query conditions. +func LookForRanges(conditions []query.Condition) (ranges QueryRanges, indexes []int) { + ranges = make(QueryRanges) + for i, c := range conditions { + if IsRangeOperation(c.Op) { + r, ok := ranges[c.CompositeKey] + if !ok { + r = QueryRange{Key: c.CompositeKey} + } + + switch c.Op { + case query.OpGreater: + r.LowerBound = c.Operand + + case query.OpGreaterEqual: + r.IncludeLowerBound = true + r.LowerBound = c.Operand + + case query.OpLess: + r.UpperBound = c.Operand + + case query.OpLessEqual: + r.IncludeUpperBound = true + r.UpperBound = c.Operand + } + + ranges[c.CompositeKey] = r + indexes = append(indexes, i) + } + } + + return ranges, indexes +} + +// IsRangeOperation returns a boolean signifying if a query Operator is a range +// operation or not. +func IsRangeOperation(op query.Operator) bool { + switch op { + case query.OpGreater, query.OpGreaterEqual, query.OpLess, query.OpLessEqual: + return true + + default: + return false + } +} diff --git a/state/txindex/indexer.go b/state/txindex/indexer.go index 738f41d40..621ab6e4e 100644 --- a/state/txindex/indexer.go +++ b/state/txindex/indexer.go @@ -8,9 +8,10 @@ import ( "github.com/line/ostracon/libs/pubsub/query" ) +// XXX/TODO: These types should be moved to the indexer package. + // TxIndexer interface defines methods to index and search transactions. type TxIndexer interface { - // AddBatch analyzes, indexes and stores a batch of transactions. AddBatch(b *Batch) error @@ -25,9 +26,6 @@ type TxIndexer interface { Search(ctx context.Context, q *query.Query) ([]*abci.TxResult, error) } -//---------------------------------------------------- -// Txs are written as a batch - // Batch groups together multiple Index operations to be performed at the same time. // NOTE: Batch is NOT thread-safe and must not be modified after starting its execution. type Batch struct { @@ -52,8 +50,5 @@ func (b *Batch) Size() int { return len(b.Ops) } -//---------------------------------------------------- -// Errors - // ErrorEmptyHash indicates empty hash var ErrorEmptyHash = errors.New("transaction hash cannot be empty") diff --git a/state/txindex/indexer_service.go b/state/txindex/indexer_service.go index 3886c4140..4d746b9e7 100644 --- a/state/txindex/indexer_service.go +++ b/state/txindex/indexer_service.go @@ -4,26 +4,34 @@ import ( "context" "github.com/line/ostracon/libs/service" - + "github.com/line/ostracon/state/indexer" "github.com/line/ostracon/types" ) +// XXX/TODO: These types should be moved to the indexer package. + const ( subscriber = "IndexerService" ) -// IndexerService connects event bus and transaction indexer together in order -// to index transactions coming from event bus. +// IndexerService connects event bus, transaction and block indexers together in +// order to index transactions and blocks coming from the event bus. type IndexerService struct { service.BaseService - idr TxIndexer - eventBus *types.EventBus + txIdxr TxIndexer + blockIdxr indexer.BlockIndexer + eventBus *types.EventBus } // NewIndexerService returns a new service instance. -func NewIndexerService(idr TxIndexer, eventBus *types.EventBus) *IndexerService { - is := &IndexerService{idr: idr, eventBus: eventBus} +func NewIndexerService( + txIdxr TxIndexer, + blockIdxr indexer.BlockIndexer, + eventBus *types.EventBus, +) *IndexerService { + + is := &IndexerService{txIdxr: txIdxr, blockIdxr: blockIdxr, eventBus: eventBus} is.BaseService = *service.NewBaseService(nil, "IndexerService", is) return is } @@ -34,7 +42,6 @@ func (is *IndexerService) OnStart() error { // Use SubscribeUnbuffered here to ensure both subscriptions does not get // cancelled due to not pulling messages fast enough. Cause this might // sometimes happen when there are no other subscribers. - blockHeadersSub, err := is.eventBus.SubscribeUnbuffered( context.Background(), subscriber, @@ -54,20 +61,31 @@ func (is *IndexerService) OnStart() error { eventDataHeader := msg.Data().(types.EventDataNewBlockHeader) height := eventDataHeader.Header.Height batch := NewBatch(eventDataHeader.NumTxs) + for i := int64(0); i < eventDataHeader.NumTxs; i++ { msg2 := <-txsSub.Out() txResult := msg2.Data().(types.EventDataTx).TxResult + if err = batch.Add(&txResult); err != nil { - is.Logger.Error("Can't add tx to batch", + is.Logger.Error( + "failed to add tx to batch", "height", height, "index", txResult.Index, - "err", err) + "err", err, + ) } } - if err = is.idr.AddBatch(batch); err != nil { - is.Logger.Error("Failed to index block", "height", height, "err", err) + + if err := is.blockIdxr.Index(eventDataHeader); err != nil { + is.Logger.Error("failed to index block", "height", height, "err", err) + } else { + is.Logger.Error("indexed block", "height", height) + } + + if err = is.txIdxr.AddBatch(batch); err != nil { + is.Logger.Error("failed to index block txs", "height", height, "err", err) } else { - is.Logger.Debug("indexed block", "height", height) + is.Logger.Debug("indexed block txs", "height", height, "num_txs", eventDataHeader.NumTxs) } } }() diff --git a/state/txindex/indexer_service_test.go b/state/txindex/indexer_service_test.go index 941768625..7ee97c012 100644 --- a/state/txindex/indexer_service_test.go +++ b/state/txindex/indexer_service_test.go @@ -5,11 +5,12 @@ import ( "time" "github.com/line/tm-db/v2/memdb" - "github.com/stretchr/testify/assert" + "github.com/line/tm-db/v2/prefixdb" "github.com/stretchr/testify/require" abci "github.com/line/ostracon/abci/types" "github.com/line/ostracon/libs/log" + blockidxkv "github.com/line/ostracon/state/indexer/block/kv" "github.com/line/ostracon/state/txindex" "github.com/line/ostracon/state/txindex/kv" "github.com/line/ostracon/types" @@ -30,8 +31,9 @@ func TestIndexerServiceIndexesBlocks(t *testing.T) { // tx indexer store := memdb.NewDB() txIndexer := kv.NewTxIndex(store) + blockIndexer := blockidxkv.New(prefixdb.NewDB(store, []byte("block_events"))) - service := txindex.NewIndexerService(txIndexer, eventBus) + service := txindex.NewIndexerService(txIndexer, blockIndexer, eventBus) service.SetLogger(log.TestingLogger()) err = service.Start() require.NoError(t, err) @@ -66,11 +68,15 @@ func TestIndexerServiceIndexesBlocks(t *testing.T) { time.Sleep(100 * time.Millisecond) - // check the result res, err := txIndexer.Get(types.Tx("foo").Hash()) - assert.NoError(t, err) - assert.Equal(t, txResult1, res) + require.NoError(t, err) + require.Equal(t, txResult1, res) + + ok, err := blockIndexer.Has(1) + require.NoError(t, err) + require.True(t, ok) + res, err = txIndexer.Get(types.Tx("bar").Hash()) - assert.NoError(t, err) - assert.Equal(t, txResult2, res) + require.NoError(t, err) + require.Equal(t, txResult2, res) } diff --git a/state/txindex/kv/kv.go b/state/txindex/kv/kv.go index b15511e5b..46b3cf847 100644 --- a/state/txindex/kv/kv.go +++ b/state/txindex/kv/kv.go @@ -7,13 +7,13 @@ import ( "fmt" "strconv" "strings" - "time" "github.com/gogo/protobuf/proto" dbm "github.com/line/tm-db/v2" abci "github.com/line/ostracon/abci/types" "github.com/line/ostracon/libs/pubsub/query" + "github.com/line/ostracon/state/indexer" "github.com/line/ostracon/state/txindex" "github.com/line/ostracon/types" ) @@ -170,11 +170,10 @@ func (txi *TxIndex) indexEvents(result *abci.TxResult, hash []byte, store dbm.Ba // Search will exit early and return any result fetched so far, // when a message is received on the context chan. func (txi *TxIndex) Search(ctx context.Context, q *query.Query) ([]*abci.TxResult, error) { - // Potentially exit early. select { case <-ctx.Done(): - results := make([]*abci.TxResult, 0) - return results, nil + return make([]*abci.TxResult, 0), nil + default: } @@ -209,13 +208,13 @@ func (txi *TxIndex) Search(ctx context.Context, q *query.Query) ([]*abci.TxResul // extract ranges // if both upper and lower bounds exist, it's better to get them in order not // no iterate over kvs that are not within range. - ranges, rangeIndexes := lookForRanges(conditions) + ranges, rangeIndexes := indexer.LookForRanges(conditions) if len(ranges) > 0 { skipIndexes = append(skipIndexes, rangeIndexes...) - for _, r := range ranges { + for _, qr := range ranges { if !hashesInitialized { - filteredHashes = txi.matchRange(ctx, r, startKey(r.key), filteredHashes, true) + filteredHashes = txi.matchRange(ctx, qr, startKey(qr.Key), filteredHashes, true) hashesInitialized = true // Ignore any remaining conditions if the first condition resulted @@ -224,7 +223,7 @@ func (txi *TxIndex) Search(ctx context.Context, q *query.Query) ([]*abci.TxResul break } } else { - filteredHashes = txi.matchRange(ctx, r, startKey(r.key), filteredHashes, false) + filteredHashes = txi.matchRange(ctx, qr, startKey(qr.Key), filteredHashes, false) } } } @@ -291,100 +290,6 @@ func lookForHeight(conditions []query.Condition) (height int64) { return 0 } -// special map to hold range conditions -// Example: account.number => queryRange{lowerBound: 1, upperBound: 5} -type queryRanges map[string]queryRange - -type queryRange struct { - lowerBound interface{} // int || time.Time - upperBound interface{} // int || time.Time - key string - includeLowerBound bool - includeUpperBound bool -} - -func (r queryRange) lowerBoundValue() interface{} { - if r.lowerBound == nil { - return nil - } - - if r.includeLowerBound { - return r.lowerBound - } - - switch t := r.lowerBound.(type) { - case int64: - return t + 1 - case time.Time: - return t.Unix() + 1 - default: - panic("not implemented") - } -} - -func (r queryRange) AnyBound() interface{} { - if r.lowerBound != nil { - return r.lowerBound - } - - return r.upperBound -} - -func (r queryRange) upperBoundValue() interface{} { - if r.upperBound == nil { - return nil - } - - if r.includeUpperBound { - return r.upperBound - } - - switch t := r.upperBound.(type) { - case int64: - return t - 1 - case time.Time: - return t.Unix() - 1 - default: - panic("not implemented") - } -} - -func lookForRanges(conditions []query.Condition) (ranges queryRanges, indexes []int) { - ranges = make(queryRanges) - for i, c := range conditions { - if isRangeOperation(c.Op) { - r, ok := ranges[c.CompositeKey] - if !ok { - r = queryRange{key: c.CompositeKey} - } - switch c.Op { - case query.OpGreater: - r.lowerBound = c.Operand - case query.OpGreaterEqual: - r.includeLowerBound = true - r.lowerBound = c.Operand - case query.OpLess: - r.upperBound = c.Operand - case query.OpLessEqual: - r.includeUpperBound = true - r.upperBound = c.Operand - } - ranges[c.CompositeKey] = r - indexes = append(indexes, i) - } - } - return ranges, indexes -} - -func isRangeOperation(op query.Operator) bool { - switch op { - case query.OpGreater, query.OpGreaterEqual, query.OpLess, query.OpLessEqual: - return true - default: - return false - } -} - // match returns all matching txs by hash that meet a given condition and start // key. An already filtered result (filteredHashes) is provided such that any // non-intersecting matches are removed. @@ -519,7 +424,7 @@ func (txi *TxIndex) match( // NOTE: filteredHashes may be empty if no previous condition has matched. func (txi *TxIndex) matchRange( ctx context.Context, - r queryRange, + qr indexer.QueryRange, startKey []byte, filteredHashes map[string][]byte, firstRun bool, @@ -531,8 +436,8 @@ func (txi *TxIndex) matchRange( } tmpHashes := make(map[string][]byte) - lowerBound := r.lowerBoundValue() - upperBound := r.upperBoundValue() + lowerBound := qr.LowerBoundValue() + upperBound := qr.UpperBoundValue() it, err := txi.store.PrefixIterator(startKey) if err != nil { @@ -546,7 +451,7 @@ LOOP: continue } - if _, ok := r.AnyBound().(int64); ok { + if _, ok := qr.AnyBound().(int64); ok { v, err := strconv.ParseInt(extractValueFromKey(it.Key()), 10, 64) if err != nil { continue LOOP diff --git a/test/e2e/app/app.go b/test/e2e/app/app.go index 8f3dced00..107eecf0f 100644 --- a/test/e2e/app/app.go +++ b/test/e2e/app/app.go @@ -7,6 +7,7 @@ import ( "fmt" "os" "path/filepath" + "strconv" cryptoenc "github.com/line/ostracon/crypto/encoding" "github.com/line/ostracon/proto/ostracon/crypto" @@ -101,12 +102,29 @@ func (app *Application) DeliverTx(req abci.RequestDeliverTx) abci.ResponseDelive // EndBlock implements ABCI. func (app *Application) EndBlock(req abci.RequestEndBlock) abci.ResponseEndBlock { - var err error - resp := abci.ResponseEndBlock{} - if resp.ValidatorUpdates, err = app.validatorUpdates(uint64(req.Height)); err != nil { + valUpdates, err := app.validatorUpdates(uint64(req.Height)) + if err != nil { panic(err) } - return resp + + return abci.ResponseEndBlock{ + ValidatorUpdates: valUpdates, + Events: []abci.Event{ + { + Type: "val_updates", + Attributes: []abci.EventAttribute{ + { + Key: []byte("size"), + Value: []byte(strconv.Itoa(valUpdates.Len())), + }, + { + Key: []byte("height"), + Value: []byte(strconv.Itoa(int(req.Height))), + }, + }, + }, + }, + } } // Commit implements ABCI. diff --git a/test/maverick/node/node.go b/test/maverick/node/node.go index 066cbd6c5..1770a8722 100644 --- a/test/maverick/node/node.go +++ b/test/maverick/node/node.go @@ -18,6 +18,7 @@ import ( "github.com/rs/cors" dbm "github.com/line/tm-db/v2" + pdbm "github.com/line/tm-db/v2/prefixdb" abci "github.com/line/ostracon/abci/types" bcv0 "github.com/line/ostracon/blockchain/v0" @@ -41,6 +42,9 @@ import ( grpccore "github.com/line/ostracon/rpc/grpc" rpcserver "github.com/line/ostracon/rpc/jsonrpc/server" sm "github.com/line/ostracon/state" + "github.com/line/ostracon/state/indexer" + blockidxkv "github.com/line/ostracon/state/indexer/block/kv" + blockidxnull "github.com/line/ostracon/state/indexer/block/null" "github.com/line/ostracon/state/txindex" "github.com/line/ostracon/state/txindex/kv" "github.com/line/ostracon/state/txindex/null" @@ -257,6 +261,7 @@ type Node struct { proxyApp proxy.AppConns // connection to the application rpcListeners []net.Listener // rpc servers txIndexer txindex.TxIndexer + blockIndexer indexer.BlockIndexer indexerService *txindex.IndexerService prometheusSrv *http.Server } @@ -295,27 +300,40 @@ func createAndStartEventBus(logger log.Logger) (*types.EventBus, error) { return eventBus, nil } -func createAndStartIndexerService(config *cfg.Config, dbProvider DBProvider, - eventBus *types.EventBus, logger log.Logger) (*txindex.IndexerService, txindex.TxIndexer, error) { +func createAndStartIndexerService( + config *cfg.Config, + dbProvider DBProvider, + eventBus *types.EventBus, + logger log.Logger, +) (*txindex.IndexerService, txindex.TxIndexer, indexer.BlockIndexer, error) { + + var ( + txIndexer txindex.TxIndexer + blockIndexer indexer.BlockIndexer + ) - var txIndexer txindex.TxIndexer switch config.TxIndex.Indexer { case "kv": store, err := dbProvider(&DBContext{"tx_index", config}) if err != nil { - return nil, nil, err + return nil, nil, nil, err } + txIndexer = kv.NewTxIndex(store) + blockIndexer = blockidxkv.New(pdbm.NewDB(store, []byte("block_events"))) default: txIndexer = &null.TxIndex{} + blockIndexer = &blockidxnull.BlockerIndexer{} } - indexerService := txindex.NewIndexerService(txIndexer, eventBus) + indexerService := txindex.NewIndexerService(txIndexer, blockIndexer, eventBus) indexerService.SetLogger(logger.With("module", "txindex")) + if err := indexerService.Start(); err != nil { - return nil, nil, err + return nil, nil, nil, err } - return indexerService, txIndexer, nil + + return indexerService, txIndexer, blockIndexer, nil } func doHandshake( @@ -736,8 +754,7 @@ func NewNode(config *cfg.Config, return nil, err } - // Transaction indexing - indexerService, txIndexer, err := createAndStartIndexerService(config, dbProvider, eventBus, logger) + indexerService, txIndexer, blockIndexer, err := createAndStartIndexerService(config, dbProvider, eventBus, logger) if err != nil { return nil, err } @@ -913,6 +930,7 @@ func NewNode(config *cfg.Config, proxyApp: proxyApp, txIndexer: txIndexer, indexerService: indexerService, + blockIndexer: blockIndexer, eventBus: eventBus, } node.BaseService = *service.NewBaseService(logger, "Node", node) @@ -1069,6 +1087,7 @@ func (n *Node) ConfigureRPC() error { PubKey: pubKey, GenDoc: n.genesisDoc, TxIndexer: n.txIndexer, + BlockIndexer: n.blockIndexer, ConsensusReactor: &consensus.Reactor{}, EventBus: n.eventBus, Mempool: n.mempool, diff --git a/types/events.go b/types/events.go index db5b809e1..770b06bab 100644 --- a/types/events.go +++ b/types/events.go @@ -136,6 +136,10 @@ const ( // TxHeightKey is a reserved key, used to specify transaction block's height. // see EventBus#PublishEventTx TxHeightKey = "tx.height" + + // BlockHeightKey is a reserved key used for indexing BeginBlock and Endblock + // events. + BlockHeightKey = "block.height" ) var ( From 98fe33b852b4cfa89665550f5409dc40ead0db45 Mon Sep 17 00:00:00 2001 From: tnasu Date: Fri, 17 Dec 2021 23:31:43 +0900 Subject: [PATCH 16/32] Fix codecov for `rpc: index block events to support block event queries (bp #6226) (#6261)` --- light/rpc/client_test.go | 58 ++++++++++ node/node_test.go | 36 +++++++ rpc/core/blocks_test.go | 204 +++++++++++++++++++++++++++++++++++ rpc/core/tx_test.go | 227 +++++++++++++++++++++++++++++++++++++++ 4 files changed, 525 insertions(+) create mode 100644 rpc/core/tx_test.go diff --git a/light/rpc/client_test.go b/light/rpc/client_test.go index 8470595f9..a272e4226 100644 --- a/light/rpc/client_test.go +++ b/light/rpc/client_test.go @@ -96,6 +96,64 @@ func TestABCIQuery(t *testing.T) { assert.NotNil(t, res) } +func TestTxSearch(t *testing.T) { + + query := "query/test" + prove := false + page := 0 + perPage := 1 + orderBy := "" + + next := &rpcmock.Client{} + next.On( + "TxSearch", + context.Background(), + query, + prove, + &page, + &perPage, + orderBy, + ).Return(&ctypes.ResultTxSearch{ + Txs: nil, + TotalCount: 0, + }, nil) + + lc := &lcmock.LightClient{} + + c := NewClient(next, lc) + res, err := c.TxSearch(context.Background(), query, prove, &page, &perPage, orderBy) + require.NoError(t, err) + assert.NotNil(t, res) +} + +func TestBlockSearch(t *testing.T) { + + query := "query/test" + page := 0 + perPage := 1 + orderBy := "" + + next := &rpcmock.Client{} + next.On( + "BlockSearch", + context.Background(), + query, + &page, + &perPage, + orderBy, + ).Return(&ctypes.ResultBlockSearch{ + Blocks: nil, + TotalCount: 0, + }, nil) + + lc := &lcmock.LightClient{} + + c := NewClient(next, lc) + res, err := c.BlockSearch(context.Background(), query, &page, &perPage, orderBy) + require.NoError(t, err) + assert.NotNil(t, res) +} + type testOp struct { Spec *ics23.ProofSpec Key []byte diff --git a/node/node_test.go b/node/node_test.go index 913a1a937..53902514e 100644 --- a/node/node_test.go +++ b/node/node_test.go @@ -418,6 +418,42 @@ func TestNodeNewNodeCustomReactors(t *testing.T) { assert.Equal(t, customBlockchainReactor, n.Switch().Reactor("BLOCKCHAIN")) } +func TestNodeNewNodeTxIndexIndexer(t *testing.T) { + config := cfg.ResetTestRoot("node_new_node_tx_index_indexer_test") + defer os.RemoveAll(config.RootDir) + + doTest := func(doProvider func(ctx *DBContext) (dbm.DB, error)) (*Node, error) { + nodeKey, err := p2p.LoadOrGenNodeKey(config.NodeKeyFile()) + require.NoError(t, err) + + pvKey, _ := privval.LoadOrGenFilePV(config.PrivValidatorKeyFile(), config.PrivValidatorStateFile(), + config.PrivValidatorKeyType()) + return NewNode(config, + pvKey, + nodeKey, + proxy.DefaultClientCreator(config.ProxyApp, config.ABCI, config.DBDir()), + DefaultGenesisDocProviderFunc(config), + doProvider, + DefaultMetricsProvider(config.Instrumentation), + log.TestingLogger(), + ) + } + + { + // Change to panic-provider for test + n, err := doTest(func(ctx *DBContext) (dbm.DB, error) { return nil, fmt.Errorf("test error") }) + require.Error(t, err) + require.Nil(t, n) + } + { + // Change to non-default-value for test + config.TxIndex.Indexer = "" + n, err := doTest(DefaultDBProvider) + require.NoError(t, err) + require.NotNil(t, n) + } +} + func state(nVals int, height int64) (sm.State, dbm.DB, []types.PrivValidator) { privVals := make([]types.PrivValidator, nVals) vals := make([]types.GenesisValidator, nVals) diff --git a/rpc/core/blocks_test.go b/rpc/core/blocks_test.go index 7d22a5d6e..636d4b6e4 100644 --- a/rpc/core/blocks_test.go +++ b/rpc/core/blocks_test.go @@ -1,8 +1,20 @@ package core import ( + errors "errors" "fmt" + "os" "testing" + "time" + + txidxkv "github.com/line/ostracon/state/txindex/kv" + + cfg "github.com/line/ostracon/config" + "github.com/line/ostracon/crypto" + tmrand "github.com/line/ostracon/libs/rand" + blockidxkv "github.com/line/ostracon/state/indexer/block/kv" + blockidxnull "github.com/line/ostracon/state/indexer/block/null" + "github.com/line/ostracon/store" "github.com/line/tm-db/v2/memdb" "github.com/stretchr/testify/assert" @@ -114,6 +126,198 @@ func TestBlockResults(t *testing.T) { } } +func TestBlockSearchByBlockHeightQuery(t *testing.T) { + height := int64(1) + ctx := &rpctypes.Context{} + + q := fmt.Sprintf("%s=%d", types.BlockHeightKey, height) + page := 1 + perPage := 10 + orderBy := TestOrderByDefault + + state, cleanup := makeTestState() + defer cleanup() + + { + // Get by block.height (not search/range) + res, err := BlockSearch(ctx, q, &page, &perPage, orderBy) + + require.NoError(t, err) + require.NotNil(t, res) + require.Equal(t, 0, res.TotalCount) // Don't have height in db + require.Equal(t, 0, len(res.Blocks)) + } + + numToMakeBlocks := 1 + numToGet := 1 + // Save blocks + storeTestBlocks(height, int64(numToMakeBlocks), 0, state, time.Now()) + + { + // Get by block.height (not search/range) + res, err := BlockSearch(ctx, q, &page, &perPage, orderBy) + + require.NoError(t, err) + require.NotNil(t, res) + require.Equal(t, numToMakeBlocks, res.TotalCount) // Get + require.Equal(t, numToGet, len(res.Blocks)) + } +} + +func TestBlockSearchByRangeQuery(t *testing.T) { + height := int64(1) + ctx := &rpctypes.Context{} + + q := fmt.Sprintf("%s>=%d", types.BlockHeightKey, height) + page := 1 + perPage := 10 + orderBy := TestOrderByDefault + + state, cleanup := makeTestState() + defer cleanup() + + numToMakeBlocks := 15 + numToGet := perPage + // Save blocks + storeTestBlocks(height, int64(numToMakeBlocks), 0, state, time.Now()) + + { + // Search blocks by range query with desc (default) + res, err := BlockSearch(ctx, q, &page, &perPage, orderBy) + + require.NoError(t, err) + require.NotNil(t, res) + require.Equal(t, numToMakeBlocks, res.TotalCount) + require.Equal(t, numToGet, len(res.Blocks)) + require.Equal(t, int64(numToMakeBlocks), res.Blocks[0].Block.Height) + require.Equal(t, height+int64(numToMakeBlocks-numToGet), res.Blocks[numToGet-1].Block.Height) + } + { + orderBy = TestOrderByAsc + // Search blocks by range query with asc + res, err := BlockSearch(ctx, q, &page, &perPage, orderBy) + + require.NoError(t, err) + require.NotNil(t, res) + require.Equal(t, numToMakeBlocks, res.TotalCount) + require.Equal(t, numToGet, len(res.Blocks)) + require.Equal(t, height, res.Blocks[0].Block.Height) + require.Equal(t, int64(numToGet), res.Blocks[numToGet-1].Block.Height) + } +} + +func TestBlockSearch_errors(t *testing.T) { + ctx := &rpctypes.Context{} + + q := "" + page := 0 + perPage := 1 + orderBy := "error" + + { + // error: env.BlockIndexer.(*blockidxnull.BlockerIndexer) + env = &Environment{} + env.BlockIndexer = &blockidxnull.BlockerIndexer{} + + res, err := BlockSearch(ctx, q, &page, &perPage, orderBy) + + require.Error(t, err) + require.Equal(t, errors.New("block indexing is disabled"), err) + require.Nil(t, res) + } + { + // error: tmquery.New(query) + env = &Environment{} + + res, err := BlockSearch(ctx, q, &page, &perPage, orderBy) + + require.Error(t, err) + require.Equal(t, + "\nparse error near Unknown (line 1 symbol 1 - line 1 symbol 1):\n\"\"\n", + err.Error()) + require.Nil(t, res) + } + { + // error: switch orderBy + env = &Environment{} + env.BlockIndexer = blockidxkv.New(memdb.NewDB()) + q = fmt.Sprintf("%s>%d", types.BlockHeightKey, 1) + + res, err := BlockSearch(ctx, q, &page, &perPage, orderBy) + + require.Error(t, err) + require.Equal(t, + "expected order_by to be either `asc` or `desc` or empty", + err.Error()) + require.Nil(t, res) + } + { + // error: validatePage(pagePtr, perPage, totalCount) + env = &Environment{} + env.BlockIndexer = blockidxkv.New(memdb.NewDB()) + q = fmt.Sprintf("%s>%d", types.BlockHeightKey, 1) + orderBy = TestOrderByDesc + + res, err := BlockSearch(ctx, q, &page, &perPage, orderBy) + + require.Error(t, err) + require.Equal(t, + "page should be within [1, 1] range, given 0", + err.Error()) + require.Nil(t, res) + } +} + +func makeTestState() (sm.State, func()) { + config := cfg.ResetTestRoot("rpc_core_test") + env = &Environment{} + env.StateStore = sm.NewStore(memdb.NewDB()) + env.BlockStore = store.NewBlockStore(memdb.NewDB()) + env.BlockIndexer = blockidxkv.New(memdb.NewDB()) + env.TxIndexer = txidxkv.NewTxIndex(memdb.NewDB()) + + state, _ := env.StateStore.LoadFromDBOrGenesisFile(config.GenesisFile()) + return state, func() { os.RemoveAll(config.RootDir) } +} + +func storeTestBlocks(startHeight, numToMakeBlocks, numToMakeTxs int64, state sm.State, timestamp time.Time) { + for i := int64(0); i < numToMakeBlocks; i++ { + commitSigs := []types.CommitSig{{ + BlockIDFlag: types.BlockIDFlagCommit, + ValidatorAddress: tmrand.Bytes(crypto.AddressSize), + Timestamp: timestamp, + Signature: []byte("Signature"), + }} + height := startHeight + i + lastHeight := startHeight - 1 + round := int32(0) + hash := []byte("") + partSize := uint32(2) + blockID := types.BlockID{Hash: hash, PartSetHeader: types.PartSetHeader{Hash: hash, Total: partSize}} + proposer := state.Validators.SelectProposer(state.LastProofHash, startHeight, round) + txs := make([]types.Tx, numToMakeTxs) + for txIndex := int64(0); txIndex < numToMakeTxs; txIndex++ { + tx := []byte{byte(height), byte(txIndex)} + txs[txIndex] = tx + // Indexing + env.TxIndexer.Index(&abci.TxResult{Height: height, Index: uint32(txIndex), Tx: tx}) // nolint:errcheck + } + lastCommit := types.NewCommit(lastHeight, round, blockID, commitSigs) + block, _ := state.MakeBlock(height, txs, lastCommit, nil, proposer.Address, round, nil) + blockPart := block.MakePartSet(partSize) + // Indexing + env.BlockIndexer.Index(types.EventDataNewBlockHeader{Header: block.Header}) // nolint:errcheck + // Save + env.BlockStore.SaveBlock(block, blockPart, lastCommit) + } +} + +const ( + TestOrderByDefault = "" + TestOrderByDesc = "desc" + TestOrderByAsc = "asc" +) + type mockBlockStore struct { height int64 } diff --git a/rpc/core/tx_test.go b/rpc/core/tx_test.go new file mode 100644 index 000000000..3a0e8d2bd --- /dev/null +++ b/rpc/core/tx_test.go @@ -0,0 +1,227 @@ +package core + +import ( + "encoding/hex" + "errors" + "fmt" + "math" + "testing" + "time" + + txidxkv "github.com/line/ostracon/state/txindex/kv" + txidxnull "github.com/line/ostracon/state/txindex/null" + "github.com/line/tm-db/v2/memdb" + "github.com/stretchr/testify/require" + + rpctypes "github.com/line/ostracon/rpc/jsonrpc/types" + "github.com/line/ostracon/types" +) + +func TestTxSearchByTxHashQuery(t *testing.T) { + height := int64(1) + txIndex := 0 + tx := []byte{byte(height), byte(txIndex)} + hash := hex.EncodeToString(types.Tx(tx).Hash()) + ctx := &rpctypes.Context{} + + q := fmt.Sprintf("%s='%s'", types.TxHashKey, hash) + prove := false + page := 1 + perPage := 10 + orderBy := TestOrderByDefault + + state, cleanup := makeTestState() + defer cleanup() + + { + // Get by tx.hash (not search/range) + res, err := TxSearch(ctx, q, prove, &page, &perPage, orderBy) + + require.NoError(t, err) + require.NotNil(t, res) + require.Equal(t, 0, res.TotalCount) // Don't have tx in db + require.Equal(t, 0, len(res.Txs)) + } + + numToMakeBlocks := 1 + numToMakeTxs := 1 + numOfGet := 1 + // SaveBlock + storeTestBlocks(height, int64(numToMakeBlocks), int64(numToMakeTxs), state, time.Now()) + + { + // Get by block.height (not search/range) + res, err := TxSearch(ctx, q, prove, &page, &perPage, orderBy) + + require.NoError(t, err) + require.NotNil(t, res) + require.Equal(t, numToMakeTxs, res.TotalCount) // Get + require.Equal(t, numOfGet, len(res.Txs)) + } +} + +func TestTxSearchByTxHeightQuery(t *testing.T) { + height := int64(1) + ctx := &rpctypes.Context{} + + q := fmt.Sprintf("%s>=%d", types.TxHeightKey, height) + prove := false + page := 1 + perPage := 10 + orderBy := TestOrderByDefault + + state, cleanup := makeTestState() + defer cleanup() + + numToMakeBlocks := 5 + numToMakeTxs := 3 + numToGet := perPage + // SaveBlock + storeTestBlocks(height, int64(numToMakeBlocks), int64(numToMakeTxs), state, time.Now()) + + { + // Search blocks by range query with asc (default) + res, err := TxSearch(ctx, q, prove, &page, &perPage, orderBy) + + require.NoError(t, err) + require.NotNil(t, res) + require.Equal(t, numToMakeBlocks*numToMakeTxs, res.TotalCount) + require.Equal(t, numToGet, len(res.Txs)) + // check first tx + first := res.Txs[0] + require.Equal(t, height, first.Height) + require.Equal(t, uint32(0), first.Index) + // check last tx + last := res.Txs[numToGet-1] + require.Equal(t, int64(math.Ceil(float64(numToGet)/float64(numToMakeTxs))), last.Height) + require.Equal(t, uint32(numToGet%numToMakeTxs-1), last.Index) + } + { + orderBy = TestOrderByDesc + // Search blocks by range query with desc + res, err := TxSearch(ctx, q, prove, &page, &perPage, orderBy) + + require.NoError(t, err) + require.NotNil(t, res) + require.Equal(t, numToMakeBlocks*numToMakeTxs, res.TotalCount) + require.Equal(t, numToGet, len(res.Txs)) + // check first tx + first := res.Txs[0] + require.Equal(t, int64(numToMakeBlocks), first.Height) + require.Equal(t, uint32(numToMakeTxs-1), first.Index) + // check last tx + last := res.Txs[numToGet-1] + require.Equal(t, int64(numToMakeBlocks-numToGet/numToMakeTxs), last.Height) + require.Equal(t, uint32(numToMakeTxs-numToGet%numToMakeTxs), last.Index) + } + { + // Range queries: how to use: see query_test.go + q = fmt.Sprintf("%s>=%d AND %s<=%d", types.TxHeightKey, height, types.TxHeightKey, height+1) + orderBy = TestOrderByAsc + // Search blocks by range query with asc + res, err := TxSearch(ctx, q, prove, &page, &perPage, orderBy) + + require.NoError(t, err) + require.NotNil(t, res) + require.Equal(t, numToMakeTxs*2, res.TotalCount) + require.Equal(t, numToMakeTxs*2, len(res.Txs)) + // check first tx + first := res.Txs[0] + require.Equal(t, height, first.Height) + require.Equal(t, uint32(0), first.Index) + // check last tx + last := res.Txs[len(res.Txs)-1] + require.Equal(t, height+1, last.Height) + require.Equal(t, uint32(numToMakeTxs-1), last.Index) + } + { + // Range queries with illegal key + q = fmt.Sprintf("%s>=%d AND %s<=%d AND test.key>=1", + types.TxHeightKey, height, types.TxHeightKey, height+1) + orderBy = TestOrderByAsc + // Search blocks by range query with asc + res, err := TxSearch(ctx, q, prove, &page, &perPage, orderBy) + + require.NoError(t, err) + require.NotNil(t, res) + require.Equal(t, 0, res.TotalCount) // Cannot Get + require.Equal(t, 0, len(res.Txs)) + } +} + +func TestTxSearch_errors(t *testing.T) { + ctx := &rpctypes.Context{} + + q := "" + prove := false + page := 0 + perPage := 1 + orderBy := "error" + + { + // error: env.TxIndexer.(*txidxnull.TxIndex) + env = &Environment{} + env.TxIndexer = &txidxnull.TxIndex{} + + res, err := TxSearch(ctx, q, prove, &page, &perPage, orderBy) + + require.Error(t, err) + require.Equal(t, errors.New("transaction indexing is disabled"), err) + require.Nil(t, res) + } + { + // error: tmquery.New(query) + env = &Environment{} + + res, err := TxSearch(ctx, q, prove, &page, &perPage, orderBy) + + require.Error(t, err) + require.Equal(t, + "\nparse error near Unknown (line 1 symbol 1 - line 1 symbol 1):\n\"\"\n", + err.Error()) + require.Nil(t, res) + } + { + // error: lookForHash + env = &Environment{} + env.TxIndexer = txidxkv.NewTxIndex(memdb.NewDB()) + q = fmt.Sprintf("%s=%s", types.TxHashKey, "'1'") + + res, err := TxSearch(ctx, q, prove, &page, &perPage, orderBy) + + require.Error(t, err) + require.Equal(t, + "error during searching for a hash in the query: encoding/hex: odd length hex string", + err.Error()) + require.Nil(t, res) + } + { + // error: switch orderBy + env = &Environment{} + env.TxIndexer = txidxkv.NewTxIndex(memdb.NewDB()) + q = fmt.Sprintf("%s=%s", types.TxHashKey, "'1234567890abcdef'") + + res, err := TxSearch(ctx, q, prove, &page, &perPage, orderBy) + + require.Error(t, err) + require.Equal(t, + "expected order_by to be either `asc` or `desc` or empty", + err.Error()) + require.Nil(t, res) + } + { + // error: validatePage(pagePtr, perPage, totalCount) + env = &Environment{} + env.TxIndexer = txidxkv.NewTxIndex(memdb.NewDB()) + q = fmt.Sprintf("%s=%s", types.TxHashKey, "'1234567890abcdef'") + orderBy = TestOrderByAsc + + res, err := TxSearch(ctx, q, prove, &page, &perPage, orderBy) + + require.Error(t, err) + require.Equal(t, + "page should be within [1, 1] range, given 0", + err.Error()) + require.Nil(t, res) + } +} From 3b328eab3f5dd8bc04a1861c428fdf9c91e43b1f Mon Sep 17 00:00:00 2001 From: tnasu Date: Mon, 20 Dec 2021 14:40:15 +0900 Subject: [PATCH 17/32] logging: shorten precommit log message (#6270) (#6274) This is an attempt to clean up the logging message as requested in #6269. (cherry picked from commit 3f9066b290ba2de266b42aa6ad2b076c7be4f5df) Co-authored-by: Sam Kleinman --- consensus/state.go | 7 ++++++- types/block.go | 2 +- types/vote_set.go | 19 +++++++++++++++++-- 3 files changed, 24 insertions(+), 4 deletions(-) diff --git a/consensus/state.go b/consensus/state.go index e4a106929..53fd387ba 100644 --- a/consensus/state.go +++ b/consensus/state.go @@ -2234,7 +2234,12 @@ func (cs *State) addVote(vote *types.Vote, peerID p2p.ID) (added bool, err error case tmproto.PrecommitType: precommits := cs.Votes.Precommits(vote.Round) - cs.Logger.Debug("added vote to precommit", "vote", vote, "precommits", precommits.StringShort()) + cs.Logger.Debug("added vote to precommit", + "height", vote.Height, + "round", vote.Round, + "validator", vote.ValidatorAddress.String(), + "vote_timestamp", vote.Timestamp, + "data", precommits.LogString()) blockID, ok := precommits.TwoThirdsMajority() if ok { diff --git a/types/block.go b/types/block.go index 0adb88deb..796472a56 100644 --- a/types/block.go +++ b/types/block.go @@ -1389,7 +1389,7 @@ func (blockID BlockID) Key() string { panic(err) } - return string(blockID.Hash) + string(bz) + return fmt.Sprint(string(blockID.Hash), string(bz)) } // ValidateBasic performs basic validation. diff --git a/types/vote_set.go b/types/vote_set.go index 3ea3e04f7..ef94c63c0 100644 --- a/types/vote_set.go +++ b/types/vote_set.go @@ -474,12 +474,14 @@ func (voteSet *VoteSet) TwoThirdsMajority() (blockID BlockID, ok bool) { //-------------------------------------------------------------------------------- // Strings and JSON +const nilVoteSetString = "nil-VoteSet" + // String returns a string representation of VoteSet. // // See StringIndented. func (voteSet *VoteSet) String() string { if voteSet == nil { - return "nil-VoteSet" + return nilVoteSetString } return voteSet.StringIndented("") } @@ -583,7 +585,7 @@ func (voteSet *VoteSet) voteStrings() []string { // 7. 2/3+ majority for each peer func (voteSet *VoteSet) StringShort() string { if voteSet == nil { - return "nil-VoteSet" + return nilVoteSetString } voteSet.mtx.Lock() defer voteSet.mtx.Unlock() @@ -592,6 +594,19 @@ func (voteSet *VoteSet) StringShort() string { voteSet.height, voteSet.round, voteSet.signedMsgType, voteSet.maj23, frac, voteSet.votesBitArray, voteSet.peerMaj23s) } +// LogString produces a logging suitable string representation of the +// vote set. +func (voteSet *VoteSet) LogString() string { + if voteSet == nil { + return nilVoteSetString + } + voteSet.mtx.Lock() + defer voteSet.mtx.Unlock() + voted, total, frac := voteSet.sumTotalFrac() + + return fmt.Sprintf("Votes:%d/%d(%.3f)", voted, total, frac) +} + // return the power voted, the total, and the fraction func (voteSet *VoteSet) sumTotalFrac() (int64, int64, float64) { voted, total := voteSet.sum, voteSet.voterSet.TotalVotingPower() From f9ce0699225c118ec735057f2ef8d16d63c40337 Mon Sep 17 00:00:00 2001 From: tnasu Date: Mon, 20 Dec 2021 16:04:37 +0900 Subject: [PATCH 18/32] fix: jsonrpc url parsing and dial function (#6264) (#6288) This PR fixes how the jsonrpc parses the URL, and how the dial function connects to the RPC. Closes: https://github.com/tendermint/tendermint/issues/6260 (cherry picked from commit 9ecfcc93a6364bbecc9e1d7740b53602eda667eb) Co-authored-by: Frojdi Dymylja <33157909+fdymylja@users.noreply.github.com> --- rpc/jsonrpc/client/http_json_client.go | 34 ++++++++++++-- rpc/jsonrpc/client/http_json_client_test.go | 50 +++++++++++++++++++++ 2 files changed, 81 insertions(+), 3 deletions(-) diff --git a/rpc/jsonrpc/client/http_json_client.go b/rpc/jsonrpc/client/http_json_client.go index ceb8b0722..d9f595cd3 100644 --- a/rpc/jsonrpc/client/http_json_client.go +++ b/rpc/jsonrpc/client/http_json_client.go @@ -22,6 +22,7 @@ const ( protoWSS = "wss" protoWS = "ws" protoTCP = "tcp" + protoUNIX = "unix" defaultMaxIdleConns = 10000 defaultIdleConnTimeout = 60 // sec @@ -33,6 +34,8 @@ const ( // Parsed URL structure type parsedURL struct { url.URL + + isUnixSocket bool } // Parse URL and set defaults @@ -47,7 +50,16 @@ func newParsedURL(remoteAddr string) (*parsedURL, error) { u.Scheme = protoTCP } - return &parsedURL{*u}, nil + pu := &parsedURL{ + URL: *u, + isUnixSocket: false, + } + + if u.Scheme == protoUNIX { + pu.isUnixSocket = true + } + + return pu, nil } // Change protocol to HTTP for unknown protocols and TCP protocol - useful for RPC connections @@ -70,10 +82,26 @@ func (u parsedURL) GetHostWithPath() string { // Get a trimmed address - useful for WS connections func (u parsedURL) GetTrimmedHostWithPath() string { - // replace / with . for http requests (kvstore domain) + // if it's not an unix socket we return the normal URL + if !u.isUnixSocket { + return u.GetHostWithPath() + } + // if it's a unix socket we replace the host slashes with a period + // this is because otherwise the http.Client would think that the + // domain is invalid. return strings.ReplaceAll(u.GetHostWithPath(), "/", ".") } +// GetDialAddress returns the endpoint to dial for the parsed URL +func (u parsedURL) GetDialAddress() string { + // if it's not a unix socket we return the host, example: localhost:443 + if !u.isUnixSocket { + return u.Host + } + // otherwise we return the path of the unix socket, ex /tmp/socket + return u.GetHostWithPath() +} + // Get a trimmed address with protocol - useful as address in RPC connections func (u parsedURL) GetTrimmedURL() string { return u.Scheme + "://" + u.GetTrimmedHostWithPath() @@ -355,7 +383,7 @@ func makeHTTPDialer(remoteAddr string) (func(string, string) (net.Conn, error), } dialFn := func(proto, addr string) (net.Conn, error) { - return net.Dial(protocol, u.GetHostWithPath()) + return net.Dial(protocol, u.GetDialAddress()) } return dialFn, nil diff --git a/rpc/jsonrpc/client/http_json_client_test.go b/rpc/jsonrpc/client/http_json_client_test.go index 5c8ef1a25..4b82ff1eb 100644 --- a/rpc/jsonrpc/client/http_json_client_test.go +++ b/rpc/jsonrpc/client/http_json_client_test.go @@ -34,3 +34,53 @@ func TestHTTPClientMakeHTTPDialer(t *testing.T) { require.NotNil(t, addr) } } + +func Test_parsedURL(t *testing.T) { + type test struct { + url string + expectedURL string + expectedHostWithPath string + expectedDialAddress string + } + + tests := map[string]test{ + "unix endpoint": { + url: "unix:///tmp/test", + expectedURL: "unix://.tmp.test", + expectedHostWithPath: "/tmp/test", + expectedDialAddress: "/tmp/test", + }, + + "http endpoint": { + url: "https://example.com", + expectedURL: "https://example.com", + expectedHostWithPath: "example.com", + expectedDialAddress: "example.com", + }, + + "http endpoint with port": { + url: "https://example.com:8080", + expectedURL: "https://example.com:8080", + expectedHostWithPath: "example.com:8080", + expectedDialAddress: "example.com:8080", + }, + + "http path routed endpoint": { + url: "https://example.com:8080/rpc", + expectedURL: "https://example.com:8080/rpc", + expectedHostWithPath: "example.com:8080/rpc", + expectedDialAddress: "example.com:8080", + }, + } + + for name, tt := range tests { + tt := tt // suppressing linter + t.Run(name, func(t *testing.T) { + parsed, err := newParsedURL(tt.url) + require.NoError(t, err) + require.Equal(t, tt.expectedDialAddress, parsed.GetDialAddress()) + require.Equal(t, tt.expectedURL, parsed.GetTrimmedURL()) + require.Equal(t, tt.expectedHostWithPath, parsed.GetHostWithPath()) + }) + } +} From b59f44dd5b46f48c0218c5018a3f144bf6a22a31 Mon Sep 17 00:00:00 2001 From: tnasu Date: Mon, 20 Dec 2021 16:50:37 +0900 Subject: [PATCH 19/32] change index block log to info (#6290) (#6294) Change log from error to info for indexing blocks (cherry picked from commit 32ee737d42d3092dec6c99729927483abc1bd959) Co-authored-by: Marko --- state/txindex/indexer_service.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/state/txindex/indexer_service.go b/state/txindex/indexer_service.go index 4d746b9e7..3d552ad8f 100644 --- a/state/txindex/indexer_service.go +++ b/state/txindex/indexer_service.go @@ -79,7 +79,7 @@ func (is *IndexerService) OnStart() error { if err := is.blockIdxr.Index(eventDataHeader); err != nil { is.Logger.Error("failed to index block", "height", height, "err", err) } else { - is.Logger.Error("indexed block", "height", height) + is.Logger.Info("indexed block", "height", height) } if err = is.txIdxr.AddBatch(batch); err != nil { From 4f22ffe5f39160997d941a4ce012b8ed88f8024c Mon Sep 17 00:00:00 2001 From: tnasu Date: Mon, 20 Dec 2021 16:50:43 +0900 Subject: [PATCH 20/32] p2p: Fix "Unknown Channel" bug on CustomReactors (#6297) --- p2p/peer.go | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/p2p/peer.go b/p2p/peer.go index 29eda1bfb..5a6b4480c 100644 --- a/p2p/peer.go +++ b/p2p/peer.go @@ -135,10 +135,15 @@ func newPeer( onPeerError func(Peer, interface{}), options ...PeerOption, ) *peer { + var channs = make([]byte, 0, len(chDescs)) + for _, desc := range chDescs { + channs = append(channs, desc.ID) + } + p := &peer{ peerConn: pc, nodeInfo: nodeInfo, - channels: nodeInfo.(DefaultNodeInfo).Channels, // TODO + channels: channs, Data: cmap.NewCMap(), metricsTicker: time.NewTicker(metricsTickerDuration), metrics: NopMetrics(), From aea3813ac283693428167021b08636021445d8e5 Mon Sep 17 00:00:00 2001 From: tnasu Date: Thu, 23 Dec 2021 13:50:25 +0900 Subject: [PATCH 21/32] state: fix block event indexing reserved key check (#6314) (#6315) --- state/indexer/block/kv/kv.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/state/indexer/block/kv/kv.go b/state/indexer/block/kv/kv.go index 799a2434c..b8296e584 100644 --- a/state/indexer/block/kv/kv.go +++ b/state/indexer/block/kv/kv.go @@ -469,9 +469,10 @@ func (idx *BlockerIndexer) indexEvents(batch db.Batch, events []abci.Event, typ // index iff the event specified index:true and it's not a reserved event compositeKey := fmt.Sprintf("%s.%s", event.Type, string(attr.Key)) - if compositeKey == types.TxHashKey || compositeKey == types.TxHeightKey { + if compositeKey == types.BlockHeightKey { return fmt.Errorf("event type and attribute key \"%s\" is reserved; please use a different key", compositeKey) } + if attr.GetIndex() { key, err := eventKey(compositeKey, typ, string(attr.Value), height) if err != nil { From 3663fdafbce9c6f52b4a7aa7deb70246fe77f5cd Mon Sep 17 00:00:00 2001 From: tnasu Date: Mon, 20 Dec 2021 20:38:38 +0900 Subject: [PATCH 22/32] light/evidence: handle FLA backport (#6331) --- evidence/mocks/block_store.go | 14 ++ evidence/services.go | 1 + evidence/verify.go | 75 +++++--- evidence/verify_test.go | 44 +++++ light/client.go | 238 +++++++++++++++++------- light/detector.go | 298 ++++++++++++++++++++++++------- light/detector_test.go | 188 ++++++++++++++++++- light/errors.go | 16 ++ light/provider/errors.go | 6 +- light/provider/http/http.go | 17 +- light/provider/http/http_test.go | 5 +- light/provider/mock/mock.go | 49 +++-- rpc/core/env.go | 2 +- 13 files changed, 777 insertions(+), 176 deletions(-) diff --git a/evidence/mocks/block_store.go b/evidence/mocks/block_store.go index ff438d7b3..76a3cc066 100644 --- a/evidence/mocks/block_store.go +++ b/evidence/mocks/block_store.go @@ -12,6 +12,20 @@ type BlockStore struct { mock.Mock } +// Height provides a mock function with given fields: +func (_m *BlockStore) Height() int64 { + ret := _m.Called() + + var r0 int64 + if rf, ok := ret.Get(0).(func() int64); ok { + r0 = rf() + } else { + r0 = ret.Get(0).(int64) + } + + return r0 +} + // LoadBlockCommit provides a mock function with given fields: height func (_m *BlockStore) LoadBlockCommit(height int64) *types.Commit { ret := _m.Called(height) diff --git a/evidence/services.go b/evidence/services.go index 3a3315966..ebf91681c 100644 --- a/evidence/services.go +++ b/evidence/services.go @@ -9,4 +9,5 @@ import ( type BlockStore interface { LoadBlockMeta(height int64) *types.BlockMeta LoadBlockCommit(height int64) *types.Commit + Height() int64 } diff --git a/evidence/verify.go b/evidence/verify.go index ec9706362..228988d66 100644 --- a/evidence/verify.go +++ b/evidence/verify.go @@ -75,7 +75,21 @@ func (evpool *Pool) verify(evidence types.Evidence) error { if evidence.Height() != ev.ConflictingBlock.Height { trustedHeader, err = getSignedHeader(evpool.blockStore, ev.ConflictingBlock.Height) if err != nil { - return err + // FIXME: This multi step process is a bit unergonomic. We may want to consider a more efficient process + // that doesn't require as much io and is atomic. + + // If the node doesn't have a block at the height of the conflicting block, then this could be + // a forward lunatic attack. Thus the node must get the latest height it has + latestHeight := evpool.blockStore.Height() + trustedHeader, err = getSignedHeader(evpool.blockStore, latestHeight) + if err != nil { + return err + } + if trustedHeader.Time.Before(ev.ConflictingBlock.Time) { + return fmt.Errorf("latest block time (%v) is before conflicting block time (%v)", + trustedHeader.Time, ev.ConflictingBlock.Time, + ) + } } } @@ -123,7 +137,11 @@ func (evpool *Pool) verify(evidence types.Evidence) error { // the following checks: // - the common header from the full node has at least 1/3 voting power which is also present in // the conflicting header's commit +// - 2/3+ of the conflicting validator set correctly signed the conflicting block // - the nodes trusted header at the same height as the conflicting header has a different hash +// +// CONTRACT: must run ValidateBasic() on the evidence before verifying +// must check that the evidence has not expired (i.e. is outside the maximum age threshold) func VerifyLightClientAttack( e *types.LightClientAttackEvidence, commonHeader, trustedHeader *types.SignedHeader, @@ -133,37 +151,42 @@ func VerifyLightClientAttack( trustPeriod time.Duration, voterParams *types.VoterParams, ) error { - // In the case of lunatic attack we need to perform a single verification jump between the - // common header and the conflicting one - if commonHeader.Height != trustedHeader.Height { - err := light.Verify(commonHeader, commonVals, e.ConflictingBlock.SignedHeader, e.ConflictingBlock.ValidatorSet, - trustPeriod, now, 0*time.Second, light.DefaultTrustLevel, voterParams) + // In the case of lunatic attack there will be a different commonHeader height. + // Therefore the node perform a single verification jump between the common header and the conflicting one + if commonHeader.Height != e.ConflictingBlock.Height { + err := commonVoters.VerifyCommitLightTrusting( + trustedHeader.ChainID, e.ConflictingBlock.Commit, light.DefaultTrustLevel) if err != nil { - return fmt.Errorf("skipping verification from common to conflicting header failed: %w", err) - } - } else { - // in the case of equivocation and amnesia we expect some header hashes to be correctly derived - if isInvalidHeader(trustedHeader.Header, e.ConflictingBlock.Header) { - return errors.New("common height is the same as conflicting block height so expected the conflicting" + - " block to be correctly derived yet it wasn't") - } - // ensure that 2/3 of the voter set did vote for this block - if err := e.ConflictingBlock.VoterSet.VerifyCommitLight( - trustedHeader.ChainID, - e.ConflictingBlock.Commit.BlockID, - e.ConflictingBlock.Height, - e.ConflictingBlock.Commit, - ); err != nil { - return fmt.Errorf("invalid commit from conflicting block: %w", err) + return fmt.Errorf("skipping verification of conflicting block failed: %w", err) } + + // In the case of equivocation and amnesia we expect all header hashes to be correctly derived + } else if isInvalidHeader(trustedHeader.Header, e.ConflictingBlock.Header) { + return errors.New("common height is the same as conflicting block height so expected the conflicting" + + " block to be correctly derived yet it wasn't") } - if evTotal, votersTotal := e.TotalVotingPower, commonVoters.TotalVotingPower(); evTotal != votersTotal { - return fmt.Errorf("total voting power from the evidence and our voter set does not match (%d != %d)", - evTotal, votersTotal) + // Verify that the 2/3+ commits from the conflicting validator set were for the conflicting header + if err := e.ConflictingBlock.VoterSet.VerifyCommitLight( + trustedHeader.ChainID, e.ConflictingBlock.Commit.BlockID, + e.ConflictingBlock.Height, e.ConflictingBlock.Commit); err != nil { + return fmt.Errorf("invalid commit from conflicting block: %w", err) } - if bytes.Equal(trustedHeader.Hash(), e.ConflictingBlock.Hash()) { + // Assert the correct amount of voting power of the validator set + if evTotal, valsTotal := e.TotalVotingPower, commonVoters.TotalVotingPower(); evTotal != valsTotal { + return fmt.Errorf("total voting power from the evidence and our validator set does not match (%d != %d)", + evTotal, valsTotal) + } + + // check in the case of a forward lunatic attack that monotonically increasing time has been violated + if e.ConflictingBlock.Height > trustedHeader.Height && e.ConflictingBlock.Time.After(trustedHeader.Time) { + return fmt.Errorf("conflicting block doesn't violate monotonically increasing time (%v is after %v)", + e.ConflictingBlock.Time, trustedHeader.Time, + ) + + // In all other cases check that the hashes of the conflicting header and the trusted header are different + } else if bytes.Equal(trustedHeader.Hash(), e.ConflictingBlock.Hash()) { return fmt.Errorf("trusted header hash matches the evidence's conflicting header hash: %X", trustedHeader.Hash()) } diff --git a/evidence/verify_test.go b/evidence/verify_test.go index 0e74d66c7..15246cc52 100644 --- a/evidence/verify_test.go +++ b/evidence/verify_test.go @@ -46,6 +46,7 @@ func TestVerifyLightClientAttack_Lunatic(t *testing.T) { commonHeader.Time = defaultEvidenceTime commonHeader.Proof = proof trustedHeader := makeHeaderRandom(10) + trustedHeader.Time = defaultEvidenceTime.Add(1 * time.Hour) conflictingHeader := makeHeaderRandom(10) conflictingHeader.Time = defaultEvidenceTime.Add(1 * time.Hour) @@ -129,6 +130,38 @@ func TestVerifyLightClientAttack_Lunatic(t *testing.T) { assert.Error(t, err) ev.TotalVotingPower = 20 + forwardConflictingHeader := makeHeaderRandom(11) + forwardConflictingHeader.Time = defaultEvidenceTime.Add(30 * time.Minute) + forwardConflictingHeader.ValidatorsHash = conflictingVals.Hash() + forwardBlockID := makeBlockID(forwardConflictingHeader.Hash(), 1000, []byte("partshash")) + forwardVoteSet := types.NewVoteSet(evidenceChainID, 11, 1, tmproto.SignedMsgType(2), conflictingVoterSet) + forwardCommit, err := types.MakeCommit(forwardBlockID, 11, 1, forwardVoteSet, conflictingPrivVals, defaultEvidenceTime) + require.NoError(t, err) + forwardLunaticEv := &types.LightClientAttackEvidence{ + ConflictingBlock: &types.LightBlock{ + SignedHeader: &types.SignedHeader{ + Header: forwardConflictingHeader, + Commit: forwardCommit, + }, + ValidatorSet: conflictingVals, + }, + CommonHeight: 4, + TotalVotingPower: 20, + ByzantineValidators: commonVals.Validators, + Timestamp: defaultEvidenceTime, + } + err = evidence.VerifyLightClientAttack( + forwardLunaticEv, + commonSignedHeader, + trustedSignedHeader, + commonVals, + commonVoters, + defaultEvidenceTime.Add(2*time.Hour), + 3*time.Hour, + types.DefaultVoterParams(), + ) + assert.NoError(t, err) + state := sm.State{ LastBlockTime: defaultEvidenceTime.Add(2 * time.Hour), LastBlockHeight: 11, @@ -142,8 +175,10 @@ func TestVerifyLightClientAttack_Lunatic(t *testing.T) { blockStore := &mocks.BlockStore{} blockStore.On("LoadBlockMeta", int64(4)).Return(&types.BlockMeta{Header: *commonHeader}) blockStore.On("LoadBlockMeta", int64(10)).Return(&types.BlockMeta{Header: *trustedHeader}) + blockStore.On("LoadBlockMeta", int64(11)).Return(nil) blockStore.On("LoadBlockCommit", int64(4)).Return(commit) blockStore.On("LoadBlockCommit", int64(10)).Return(trustedCommit) + blockStore.On("Height").Return(int64(10)) pool, err := evidence.NewPool(memdb.NewDB(), stateStore, blockStore) require.NoError(t, err) @@ -162,6 +197,15 @@ func TestVerifyLightClientAttack_Lunatic(t *testing.T) { err = pool.CheckEvidence(evList) assert.Error(t, err) ev.ByzantineValidators = commonVals.Validators // restore evidence + + // If evidence is submitted with an altered timestamp it should return an error + ev.Timestamp = defaultEvidenceTime.Add(1 * time.Minute) + err = pool.CheckEvidence(evList) + assert.Error(t, err) + + evList = types.EvidenceList{forwardLunaticEv} + err = pool.CheckEvidence(evList) + assert.NoError(t, err) } func TestVerifyLightClientAttack_Equivocation(t *testing.T) { diff --git a/light/client.go b/light/client.go index 61b105eeb..4aabe4862 100644 --- a/light/client.go +++ b/light/client.go @@ -6,6 +6,7 @@ import ( "errors" "fmt" "sort" + "sync" "time" "github.com/line/ostracon/crypto/vrf" @@ -36,6 +37,9 @@ const ( // - http://vancouver-webpages.com/time/web.html // - https://blog.codinghorror.com/keeping-time-on-the-pc/ defaultMaxClockDrift = 10 * time.Second + + // 10s is sufficient for most networks. + defaultMaxBlockLag = 10 * time.Second ) // Option sets a parameter for the light client. @@ -103,13 +107,27 @@ func MaxRetryAttempts(max uint16) Option { } // MaxClockDrift defines how much new header's time can drift into -// the future. Default: 10s. +// the future relative to the light clients local time. Default: 10s. func MaxClockDrift(d time.Duration) Option { return func(c *Client) { c.maxClockDrift = d } } +// MaxBlockLag represents the maximum time difference between the realtime +// that a block is received and the timestamp of that block. +// One can approximate it to the maximum block production time +// +// As an example, say the light client received block B at a time +// 12:05 (this is the real time) and the time on the block +// was 12:00. Then the lag here is 5 minutes. +// Default: 10s +func MaxBlockLag(d time.Duration) Option { + return func(c *Client) { + c.maxBlockLag = d + } +} + // Client represents a light client, connected to a single chain, which gets // light blocks from a primary provider, verifies them either sequentially or by // skipping some and stores them in a trusted store (usually, a local FS). @@ -122,6 +140,7 @@ type Client struct { trustLevel tmmath.Fraction maxRetryAttempts uint16 // see MaxRetryAttempts option maxClockDrift time.Duration + maxBlockLag time.Duration // Mutex for locking during changes of the light clients providers providerMutex tmsync.Mutex @@ -220,6 +239,7 @@ func NewClientFromTrustedStore( trustLevel: DefaultTrustLevel, maxRetryAttempts: defaultMaxRetryAttempts, maxClockDrift: defaultMaxClockDrift, + maxBlockLag: defaultMaxBlockLag, primary: primary, witnesses: witnesses, trustedStore: trustedStore, @@ -658,17 +678,10 @@ func (c *Client) verifySequential( // If some intermediate header is invalid, replace the primary and try // again. c.logger.Error("primary sent invalid header -> replacing", "err", err) - replaceErr := c.replacePrimaryProvider() - if replaceErr != nil { - c.logger.Error("Can't replace primary", "err", replaceErr) - // return original error - return err - } - replacementBlock, fErr := c.lightBlockFromPrimary(ctx, newLightBlock.Height) - if fErr != nil { - c.logger.Error("Can't fetch light block from primary", "err", fErr) - // return original error + replacementBlock, removeErr := c.findNewPrimary(ctx, newLightBlock.Height, true) + if removeErr != nil { + c.logger.Debug("failed to replace primary. Returning original error", "err", removeErr) return err } @@ -792,17 +805,10 @@ func (c *Client) verifySkippingAgainstPrimary( // If some intermediate header is invalid, replace the primary and try // again. c.logger.Error("primary sent invalid header -> replacing", "err", err) - replaceErr := c.replacePrimaryProvider() - if replaceErr != nil { - c.logger.Error("Can't replace primary", "err", replaceErr) - // return original error - return err - } - replacementBlock, fErr := c.lightBlockFromPrimary(ctx, newLightBlock.Height) - if fErr != nil { - c.logger.Error("Can't fetch light block from primary", "err", fErr) - // return original error + replacementBlock, removeErr := c.findNewPrimary(ctx, newLightBlock.Height, true) + if removeErr != nil { + c.logger.Error("failed to replace primary. Returning original error", "err", removeErr) return err } @@ -957,15 +963,25 @@ func (c *Client) backwards( "newHeight", interimHeader.Height, "newHash", interimHeader.Hash()) if err := VerifyBackwards(interimHeader, verifiedHeader); err != nil { - c.logger.Error("primary sent invalid header -> replacing", "err", err) - if replaceErr := c.replacePrimaryProvider(); replaceErr != nil { - c.logger.Error("Can't replace primary", "err", replaceErr) - // return original error - return fmt.Errorf("verify backwards from %d to %d failed: %w", - verifiedHeader.Height, interimHeader.Height, err) + // verification has failed + c.logger.Error("backwards verification failed, replacing primary...", "err", err, "primary", c.primary) + + // the client tries to see if it can get a witness to continue with the request + newPrimarysBlock, replaceErr := c.findNewPrimary(ctx, newHeader.Height, true) + if replaceErr != nil { + c.logger.Debug("failed to replace primary. Returning original error", "err", replaceErr) + return err } - // we need to verify the header at the same height again - continue + + // before continuing we must check that they have the same target header to validate + if !bytes.Equal(newPrimarysBlock.Hash(), newHeader.Hash()) { + c.logger.Debug("replaced primary but new primary has a different block to the initial one") + // return the original error + return err + } + + // try again with the new primary + return c.backwards(ctx, verifiedHeader, newPrimarysBlock.Header) } verifiedHeader = interimHeader } @@ -973,52 +989,144 @@ func (c *Client) backwards( return nil } -// NOTE: requires a providerMutex locked. -func (c *Client) removeWitness(idx int) { - switch len(c.witnesses) { - case 0: - panic(fmt.Sprintf("wanted to remove %d element from empty witnesses slice", idx)) - case 1: - c.witnesses = make([]provider.Provider, 0) +// lightBlockFromPrimary retrieves the lightBlock from the primary provider +// at the specified height. This method also handles provider behavior as follows: +// +// 1. If the provider does not respond or does not have the block, it tries again +// with a different provider +// 2. If all providers return the same error, the light client forwards the error to +// where the initial request came from +// 3. If the provider provides an invalid light block, is deemed unreliable or returns +// any other error, the primary is permanently dropped and is replaced by a witness. +func (c *Client) lightBlockFromPrimary(ctx context.Context, height int64) (*types.LightBlock, error) { + c.providerMutex.Lock() + l, err := c.primary.LightBlock(ctx, height) + c.providerMutex.Unlock() + + switch err { + case nil: + // Everything went smoothly. We reset the lightBlockRequests and return the light block + return l, nil + + case provider.ErrNoResponse, provider.ErrLightBlockNotFound: + // we find a new witness to replace the primary + c.logger.Debug("error from light block request from primary, replacing...", + "error", err, "height", height, "primary", c.primary) + return c.findNewPrimary(ctx, height, false) + default: - c.witnesses[idx] = c.witnesses[len(c.witnesses)-1] + // The light client has most likely received either provider.ErrUnreliableProvider or provider.ErrBadLightBlock + // These errors mean that the light client should drop the primary and try with another provider instead + c.logger.Error("error from light block request from primary, removing...", + "error", err, "height", height, "primary", c.primary) + return c.findNewPrimary(ctx, height, true) + } +} + +// NOTE: requires a providerMutex lock +func (c *Client) removeWitnesses(indexes []int) error { + // check that we will still have witnesses remaining + if len(c.witnesses) <= len(indexes) { + return ErrNoWitnesses + } + + // we need to make sure that we remove witnesses by index in the reverse + // order so as to not affect the indexes themselves + sort.Ints(indexes) + for i := len(indexes) - 1; i >= 0; i-- { + c.witnesses[indexes[i]] = c.witnesses[len(c.witnesses)-1] c.witnesses = c.witnesses[:len(c.witnesses)-1] } + + return nil } -// replaceProvider takes the first alternative provider and promotes it as the -// primary provider. -func (c *Client) replacePrimaryProvider() error { +type witnessResponse struct { + lb *types.LightBlock + witnessIndex int + err error +} + +// findNewPrimary concurrently sends a light block request, promoting the first witness to return +// a valid light block as the new primary. The remove option indicates whether the primary should be +// entire removed or just appended to the back of the witnesses list. This method also handles witness +// errors. If no witness is available, it returns the last error of the witness. +func (c *Client) findNewPrimary(ctx context.Context, height int64, remove bool) (*types.LightBlock, error) { c.providerMutex.Lock() defer c.providerMutex.Unlock() if len(c.witnesses) <= 1 { - return errNoWitnesses{} + return nil, ErrNoWitnesses } - c.primary = c.witnesses[0] - c.witnesses = c.witnesses[1:] - c.logger.Info("Replacing primary with the first witness", "new_primary", c.primary) - return nil -} + var ( + witnessResponsesC = make(chan witnessResponse, len(c.witnesses)) + witnessesToRemove []int + lastError error + wg sync.WaitGroup + ) -// lightBlockFromPrimary retrieves the lightBlock from the primary provider -// at the specified height. Handles dropout by the primary provider by swapping -// with an alternative provider. -func (c *Client) lightBlockFromPrimary(ctx context.Context, height int64) (*types.LightBlock, error) { - c.providerMutex.Lock() - l, err := c.primary.LightBlock(ctx, height) - c.providerMutex.Unlock() - if err != nil { - c.logger.Debug("Error on light block request from primary", "error", err) - replaceErr := c.replacePrimaryProvider() - if replaceErr != nil { - return nil, fmt.Errorf("%v. Tried to replace primary but: %w", err.Error(), replaceErr) + // send out a light block request to all witnesses + subctx, cancel := context.WithCancel(ctx) + defer cancel() + for index := range c.witnesses { + wg.Add(1) + go func(witnessIndex int, witnessResponsesC chan witnessResponse) { + defer wg.Done() + + lb, err := c.witnesses[witnessIndex].LightBlock(subctx, height) + witnessResponsesC <- witnessResponse{lb, witnessIndex, err} + }(index, witnessResponsesC) + } + + // process all the responses as they come in + for i := 0; i < cap(witnessResponsesC); i++ { + response := <-witnessResponsesC + switch response.err { + // success! We have found a new primary + case nil: + cancel() // cancel all remaining requests to other witnesses + + wg.Wait() // wait for all goroutines to finish + + // if we are not intending on removing the primary then append the old primary to the end of the witness slice + if !remove { + c.witnesses = append(c.witnesses, c.primary) + } + + // promote respondent as the new primary + c.logger.Debug("found new primary", "primary", c.witnesses[response.witnessIndex]) + c.primary = c.witnesses[response.witnessIndex] + + // add promoted witness to the list of witnesses to be removed + witnessesToRemove = append(witnessesToRemove, response.witnessIndex) + + // remove witnesses marked as bad (the client must do this before we alter the witness slice and change the indexes + // of witnesses). Removal is done in descending order + if err := c.removeWitnesses(witnessesToRemove); err != nil { + return nil, err + } + + // return the light block that new primary responded with + return response.lb, nil + + // process benign errors by logging them only + case provider.ErrNoResponse, provider.ErrLightBlockNotFound: + lastError = response.err + c.logger.Debug("error on light block request from witness", + "error", response.err, "primary", c.witnesses[response.witnessIndex]) + continue + + // process malevolent errors like ErrUnreliableProvider and ErrBadLightBlock by removing the witness + default: + lastError = response.err + c.logger.Error("error on light block request from witness, removing...", + "error", response.err, "primary", c.witnesses[response.witnessIndex]) + witnessesToRemove = append(witnessesToRemove, response.witnessIndex) } - // replace primary and request a light block again - return c.lightBlockFromPrimary(ctx, height) } - return l, err + + return nil, lastError } // compareFirstHeaderWithWitnesses compares h with all witnesses. If any @@ -1065,11 +1173,9 @@ and remove witness. Otherwise, use the different primary`, e.WitnessIndex), "wit } - // we need to make sure that we remove witnesses by index in the reverse - // order so as to not affect the indexes themselves - sort.Ints(witnessesToRemove) - for i := len(witnessesToRemove) - 1; i >= 0; i-- { - c.removeWitness(witnessesToRemove[i]) + // remove witnesses that have misbehaved + if err := c.removeWitnesses(witnessesToRemove); err != nil { + return err } return nil diff --git a/light/detector.go b/light/detector.go index d79fc2c05..f3e2f5627 100644 --- a/light/detector.go +++ b/light/detector.go @@ -5,14 +5,13 @@ import ( "context" "errors" "fmt" - "sort" "time" "github.com/line/ostracon/light/provider" "github.com/line/ostracon/types" ) -// The detector component of the light client detect and handles attacks on the light client. +// The detector component of the light client detects and handles attacks on the light client. // More info here: // tendermint/docs/architecture/adr-047-handling-evidence-from-light-client.md @@ -21,7 +20,7 @@ import ( // It takes the target verified header and compares it with the headers of a set of // witness providers that the light client is connected to. If a conflicting header // is returned it verifies and examines the conflicting header against the verified -// trace that was produced from the primary. If successful it produces two sets of evidence +// trace that was produced from the primary. If successful, it produces two sets of evidence // and sends them to the opposite provider before halting. // // If there are no conflictinge headers, the light client deems the verified target header @@ -65,50 +64,14 @@ func (c *Client) detectDivergence(ctx context.Context, primaryTrace []*types.Lig // need to find the point that the headers diverge and examine this for any evidence of an attack. // // We combine these actions together, verifying the witnesses headers and outputting the trace - // which captures the bifurcation point and if successful provides the information to create - supportingWitness := c.witnesses[e.WitnessIndex] - witnessTrace, primaryBlock, err := c.examineConflictingHeaderAgainstTrace( - ctx, - primaryTrace, - e.Block.SignedHeader, - supportingWitness, - now, - ) + // which captures the bifurcation point and if successful provides the information to create valid evidence. + err := c.handleConflictingHeaders(ctx, primaryTrace, e.Block, e.WitnessIndex, now) if err != nil { - c.logger.Info("Error validating witness's divergent header", "witness", supportingWitness, "err", err) - witnessesToRemove = append(witnessesToRemove, e.WitnessIndex) - continue + // return information of the attack + return err } - - // We are suspecting that the primary is faulty, hence we hold the witness as the source of truth - // and generate evidence against the primary that we can send to the witness - primaryEv := newLightClientAttackEvidence(primaryBlock, witnessTrace[len(witnessTrace)-1], witnessTrace[0]) - c.logger.Error("Attempted attack detected. Sending evidence againt primary by witness", "ev", primaryEv, - "primary", c.primary, "witness", supportingWitness) - c.sendEvidence(ctx, primaryEv, supportingWitness) - - // This may not be valid because the witness itself is at fault. So now we reverse it, examining the - // trace provided by the witness and holding the primary as the source of truth. Note: primary may not - // respond but this is okay as we will halt anyway. - primaryTrace, witnessBlock, err := c.examineConflictingHeaderAgainstTrace( - ctx, - witnessTrace, - primaryBlock.SignedHeader, - c.primary, - now, - ) - if err != nil { - c.logger.Info("Error validating primary's divergent header", "primary", c.primary, "err", err) - continue - } - - // We now use the primary trace to create evidence against the witness and send it to the primary - witnessEv := newLightClientAttackEvidence(witnessBlock, primaryTrace[len(primaryTrace)-1], primaryTrace[0]) - c.logger.Error("Sending evidence against witness by primary", "ev", witnessEv, - "primary", c.primary, "witness", supportingWitness) - c.sendEvidence(ctx, witnessEv, c.primary) - // We return the error and don't process anymore witnesses - return e + // if attempt to generate conflicting headers failed then remove witness + witnessesToRemove = append(witnessesToRemove, e.WitnessIndex) case errBadWitness: c.logger.Info("Witness returned an error during header comparison", "witness", c.witnesses[e.WitnessIndex], @@ -122,11 +85,9 @@ func (c *Client) detectDivergence(ctx context.Context, primaryTrace []*types.Lig } } - // we need to make sure that we remove witnesses by index in the reverse - // order so as to not affect the indexes themselves - sort.Ints(witnessesToRemove) - for i := len(witnessesToRemove) - 1; i >= 0; i-- { - c.removeWitness(witnessesToRemove[i]) + // remove witnesses that have misbehaved + if err := c.removeWitnesses(witnessesToRemove); err != nil { + return err } // 1. If we had at least one witness that returned the same header then we @@ -135,7 +96,7 @@ func (c *Client) detectDivergence(ctx context.Context, primaryTrace []*types.Lig return nil } - // 2. ELse all witnesses have either not responded, don't have the block or sent invalid blocks. + // 2. Else all witnesses have either not responded, don't have the block or sent invalid blocks. return ErrFailedHeaderCrossReferencing } @@ -150,7 +111,77 @@ func (c *Client) compareNewHeaderWithWitness(ctx context.Context, errc chan erro witness provider.Provider, witnessIndex int) { lightBlock, err := witness.LightBlock(ctx, h.Height) - if err != nil { + switch err { + // no error means we move on to checking the hash of the two headers + case nil: + break + + // the witness hasn't been helpful in comparing headers, we mark the response and continue + // comparing with the rest of the witnesses + case provider.ErrNoResponse, provider.ErrLightBlockNotFound: + errc <- err + return + + // the witness' head of the blockchain is lower than the height of the primary. This could be one of + // two things: + // 1) The witness is lagging behind + // 2) The primary may be performing a lunatic attack with a height and time in the future + case provider.ErrHeightTooHigh: + // The light client now asks for the latest header that the witness has + var isTargetHeight bool + isTargetHeight, lightBlock, err = c.getTargetBlockOrLatest(ctx, h.Height, witness) + if err != nil { + errc <- err + return + } + + // if the witness caught up and has returned a block of the target height then we can + // break from this switch case and continue to verify the hashes + if isTargetHeight { + break + } + + // witness' last header is below the primary's header. We check the times to see if the blocks + // have conflicting times + if !lightBlock.Time.Before(h.Time) { + errc <- errConflictingHeaders{Block: lightBlock, WitnessIndex: witnessIndex} + return + } + + // the witness is behind. We wait for a period WAITING = 2 * DRIFT + LAG. + // This should give the witness ample time if it is a participating member + // of consensus to produce a block that has a time that is after the primary's + // block time. If not the witness is too far behind and the light client removes it + time.Sleep(2*c.maxClockDrift + c.maxBlockLag) + isTargetHeight, lightBlock, err = c.getTargetBlockOrLatest(ctx, h.Height, witness) + if err != nil { + errc <- errBadWitness{Reason: err, WitnessIndex: witnessIndex} + return + } + if isTargetHeight { + break + } + + // the witness still doesn't have a block at the height of the primary. + // Check if there is a conflicting time + if !lightBlock.Time.Before(h.Time) { + errc <- errConflictingHeaders{Block: lightBlock, WitnessIndex: witnessIndex} + return + } + + // Following this request response procedure, the witness has been unable to produce a block + // that can somehow conflict with the primary's block. We thus conclude that the witness + // is too far behind and thus we return a no response error. + // + // NOTE: If the clock drift / lag has been miscalibrated it is feasible that the light client has + // drifted too far ahead for any witness to be able provide a comparable block and thus may allow + // for a malicious primary to attack it + errc <- provider.ErrNoResponse + return + + default: + // all other errors (i.e. invalid block, closed connection or unreliable provider) we mark the + // witness as bad and remove it errc <- errBadWitness{Reason: err, WitnessIndex: witnessIndex} return } @@ -171,6 +202,67 @@ func (c *Client) sendEvidence(ctx context.Context, ev *types.LightClientAttackEv } } +// handleConflictingHeaders handles the primary style of attack, which is where a primary and witness have +// two headers of the same height but with different hashes +func (c *Client) handleConflictingHeaders( + ctx context.Context, + primaryTrace []*types.LightBlock, + challendingBlock *types.LightBlock, + witnessIndex int, + now time.Time, +) error { + supportingWitness := c.witnesses[witnessIndex] + witnessTrace, primaryBlock, err := c.examineConflictingHeaderAgainstTrace( + ctx, + primaryTrace, + challendingBlock, + supportingWitness, + now, + ) + if err != nil { + c.logger.Info("error validating witness's divergent header", "witness", supportingWitness, "err", err) + return nil + } + + // We are suspecting that the primary is faulty, hence we hold the witness as the source of truth + // and generate evidence against the primary that we can send to the witness + commonBlock, trustedBlock := witnessTrace[0], witnessTrace[len(witnessTrace)-1] + evidenceAgainstPrimary := newLightClientAttackEvidence(primaryBlock, trustedBlock, commonBlock) + c.logger.Error("ATTEMPTED ATTACK DETECTED. Sending evidence againt primary by witness", "ev", evidenceAgainstPrimary, + "primary", c.primary, "witness", supportingWitness) + c.sendEvidence(ctx, evidenceAgainstPrimary, supportingWitness) + + if primaryBlock.Commit.Round != witnessTrace[len(witnessTrace)-1].Commit.Round { + c.logger.Info("The light client has detected, and prevented, an attempted amnesia attack." + + " We think this attack is pretty unlikely, so if you see it, that's interesting to us." + + " Can you let us know by opening an issue through https://github.com/tendermint/tendermint/issues/new?") + } + + // This may not be valid because the witness itself is at fault. So now we reverse it, examining the + // trace provided by the witness and holding the primary as the source of truth. Note: primary may not + // respond but this is okay as we will halt anyway. + primaryTrace, witnessBlock, err := c.examineConflictingHeaderAgainstTrace( + ctx, + witnessTrace, + primaryBlock, + c.primary, + now, + ) + if err != nil { + c.logger.Info("Error validating primary's divergent header", "primary", c.primary, "err", err) + return ErrLightClientAttack + } + + // We now use the primary trace to create evidence against the witness and send it to the primary + commonBlock, trustedBlock = primaryTrace[0], primaryTrace[len(primaryTrace)-1] + evidenceAgainstWitness := newLightClientAttackEvidence(witnessBlock, trustedBlock, commonBlock) + c.logger.Error("Sending evidence against witness by primary", "ev", evidenceAgainstWitness, + "primary", c.primary, "witness", supportingWitness) + c.sendEvidence(ctx, evidenceAgainstWitness, c.primary) + // We return the error and don't process anymore witnesses + return ErrLightClientAttack +} + // examineConflictingHeaderAgainstTrace takes a trace from one provider and a divergent header that // it has received from another and preforms verifySkipping at the heights of each of the intermediate // headers in the trace until it reaches the divergentHeader. 1 of 2 things can happen. @@ -179,22 +271,66 @@ func (c *Client) sendEvidence(ctx context.Context, ev *types.LightClientAttackEv // is the bifurcation point and the light client can create evidence from it // 2. The source stops responding, doesn't have the block or sends an invalid header in which case we // return the error and remove the witness +// +// CONTRACT: +// 1. Trace can not be empty len(trace) > 0 +// 2. The last block in the trace can not be of a lower height than the target block +// trace[len(trace)-1].Height >= targetBlock.Height +// 3. The func (c *Client) examineConflictingHeaderAgainstTrace( ctx context.Context, trace []*types.LightBlock, - divergentHeader *types.SignedHeader, - source provider.Provider, now time.Time) ([]*types.LightBlock, *types.LightBlock, error) { + targetBlock *types.LightBlock, + source provider.Provider, now time.Time, +) ([]*types.LightBlock, *types.LightBlock, error) { - var previouslyVerifiedBlock *types.LightBlock + var ( + previouslyVerifiedBlock, sourceBlock *types.LightBlock + sourceTrace []*types.LightBlock + err error + ) + + if targetBlock.Height < trace[0].Height { + return nil, nil, fmt.Errorf("target block has a height lower than the trusted height (%d < %d)", + targetBlock.Height, trace[0].Height) + } for idx, traceBlock := range trace { - // The first block in the trace MUST be the same to the light block that the source produces - // else we cannot continue with verification. - sourceBlock, err := source.LightBlock(ctx, traceBlock.Height) - if err != nil { - return nil, nil, err + // this case only happens in a forward lunatic attack. We treat the block with the + // height directly after the targetBlock as the divergent block + if traceBlock.Height > targetBlock.Height { + // sanity check that the time of the traceBlock is indeed less than that of the targetBlock. If the trace + // was correctly verified we should expect monotonically increasing time. This means that if the block at + // the end of the trace has a lesser time than the target block then all blocks in the trace should have a + // lesser time + if traceBlock.Time.After(targetBlock.Time) { + return nil, nil, + errors.New("sanity check failed: expected traceblock to have a lesser time than the target block") + } + + // before sending back the divergent block and trace we need to ensure we have verified + // the final gap between the previouslyVerifiedBlock and the targetBlock + if previouslyVerifiedBlock.Height != targetBlock.Height { + sourceTrace, err = c.verifySkipping(ctx, source, previouslyVerifiedBlock, targetBlock, now) + if err != nil { + return nil, nil, fmt.Errorf("verifySkipping of conflicting header failed: %w", err) + } + } + return sourceTrace, traceBlock, nil } + // get the corresponding block from the source to verify and match up against the traceBlock + if traceBlock.Height == targetBlock.Height { + sourceBlock = targetBlock + } else { + sourceBlock, err = source.LightBlock(ctx, traceBlock.Height) + if err != nil { + return nil, nil, fmt.Errorf("failed to examine trace: %w", err) + } + } + + // The first block in the trace MUST be the same to the light block that the source produces + // else we cannot continue with verification. if idx == 0 { if shash, thash := sourceBlock.Hash(), traceBlock.Hash(); !bytes.Equal(shash, thash) { return nil, nil, fmt.Errorf("trusted block is different to the source's first block (%X = %X)", @@ -206,25 +342,55 @@ func (c *Client) examineConflictingHeaderAgainstTrace( // we check that the source provider can verify a block at the same height of the // intermediate height - trace, err := c.verifySkipping(ctx, source, previouslyVerifiedBlock, sourceBlock, now) + sourceTrace, err = c.verifySkipping(ctx, source, previouslyVerifiedBlock, sourceBlock, now) if err != nil { return nil, nil, fmt.Errorf("verifySkipping of conflicting header failed: %w", err) } // check if the headers verified by the source has diverged from the trace if shash, thash := sourceBlock.Hash(), traceBlock.Hash(); !bytes.Equal(shash, thash) { // Bifurcation point found! - return trace, traceBlock, nil + return sourceTrace, traceBlock, nil } // headers are still the same. update the previouslyVerifiedBlock previouslyVerifiedBlock = sourceBlock } - // We have reached the end of the trace without observing a divergence. The last header is thus different - // from the divergent header that the source originally sent us, then we return an error. - return nil, nil, fmt.Errorf("source provided different header to the original header it provided (%X != %X)", - previouslyVerifiedBlock.Hash(), divergentHeader.Hash()) + // We have reached the end of the trace. This should never happen. This can only happen if one of the stated + // prerequisites to this function were not met. Namely that either trace[len(trace)-1].Height < targetBlock.Height + // or that trace[i].Hash() != targetBlock.Hash() + return nil, nil, errNoDivergence + +} + +// getTargetBlockOrLatest gets the latest height, if it is greater than the target height then it queries +// the target heght else it returns the latest. returns true if it successfully managed to acquire the target +// height. +func (c *Client) getTargetBlockOrLatest( + ctx context.Context, + height int64, + witness provider.Provider, +) (bool, *types.LightBlock, error) { + lightBlock, err := witness.LightBlock(ctx, 0) + if err != nil { + return false, nil, err + } + + if lightBlock.Height == height { + // the witness has caught up to the height of the provider's signed header. We + // can resume with checking the hashes. + return true, lightBlock, nil + } + + if lightBlock.Height > height { + // the witness has caught up. We recursively call the function again. However in order + // to avoud a wild goose chase where the witness sends us one header below and one header + // above the height we set a timeout to the context + lightBlock, err := witness.LightBlock(ctx, height) + return true, lightBlock, err + } + return false, lightBlock, nil } // newLightClientAttackEvidence determines the type of attack and then forms the evidence filling out diff --git a/light/detector_test.go b/light/detector_test.go index aaba17985..b6391633f 100644 --- a/light/detector_test.go +++ b/light/detector_test.go @@ -75,7 +75,7 @@ func TestLightClientAttackEvidence_Lunatic(t *testing.T) { // Check verification returns an error. _, err = c.VerifyLightBlockAtHeight(ctx, 10, bTime.Add(1*time.Hour)) if assert.Error(t, err) { - assert.Contains(t, err.Error(), "does not match primary") + assert.Equal(t, light.ErrLightClientAttack, err) } // Check evidence was sent to both full nodes. @@ -168,7 +168,7 @@ func TestLightClientAttackEvidence_Equivocation(t *testing.T) { // Check verification returns an error. _, err = c.VerifyLightBlockAtHeight(ctx, 10, bTime.Add(1*time.Hour)) if assert.Error(t, err) { - assert.Contains(t, err.Error(), "does not match primary") + assert.Equal(t, light.ErrLightClientAttack, err) } // Check evidence was sent to both full nodes. @@ -196,6 +196,150 @@ func TestLightClientAttackEvidence_Equivocation(t *testing.T) { } } +func TestLightClientAttackEvidence_ForwardLunatic(t *testing.T) { + // primary performs a lunatic attack but changes the time of the header to + // something in the future relative to the blockchain + var ( + latestHeight = int64(10) + valSize = 5 + forgedHeight = int64(12) + proofHeight = int64(11) + primaryHeaders = make(map[int64]*types.SignedHeader, forgedHeight) + primaryValidators = make(map[int64]*types.ValidatorSet, forgedHeight) + primaryVoters = make(map[int64]*types.VoterSet, forgedHeight) + ) + + witnessHeaders, witnessValidators, witnessVoters, chainKeys := + genMockNodeWithKeys(chainID, latestHeight, valSize, 2, bTime) + + // primary has the exact same headers except it forges one extra header in the future using keys from 2/5ths of + // the validators + for h := range witnessHeaders { + primaryHeaders[h] = witnessHeaders[h] + primaryValidators[h] = witnessValidators[h] + } + forgedKeys := chainKeys[latestHeight].ChangeKeys(3) // we change 3 out of the 5 validators (still 2/5 remain) + primaryValidators[forgedHeight] = forgedKeys.ToValidators(2, 0) + primaryVoters[forgedHeight] = types.SelectVoter( + primaryValidators[forgedHeight], + proofHash(primaryHeaders[forgedHeight]), + types.DefaultVoterParams(), + ) + primaryHeaders[forgedHeight] = forgedKeys.GenSignedHeader( + chainID, + forgedHeight, + bTime.Add(time.Duration(latestHeight+1)*time.Minute), // 11 mins + nil, + primaryValidators[forgedHeight], + primaryValidators[forgedHeight], + hash("app_hash"), + hash("cons_hash"), + hash("results_hash"), + 0, len(forgedKeys), + types.DefaultVoterParams(), + ) + + witness := mockp.New(chainID, witnessHeaders, witnessValidators, witnessVoters) + primary := mockp.New(chainID, primaryHeaders, primaryValidators, primaryVoters) + + laggingWitness := witness.Copy(chainID) + + // In order to perform the attack, the primary needs at least one accomplice as a witness to also + // send the forged block + accomplice := primary + + c, err := light.NewClient( + ctx, + chainID, + light.TrustOptions{ + Period: 4 * time.Hour, + Height: 1, + Hash: primaryHeaders[1].Hash(), + }, + primary, + []provider.Provider{witness, accomplice}, + dbs.New(memdb.NewDB(), chainID), + types.DefaultVoterParams(), + light.Logger(log.TestingLogger()), + light.MaxClockDrift(1*time.Second), + light.MaxBlockLag(1*time.Second), + ) + require.NoError(t, err) + + // two seconds later, the supporting withness should receive the header that can be used + // to prove that there was an attack + vals := chainKeys[latestHeight].ToValidators(2, 0) + newLb := &types.LightBlock{ + SignedHeader: chainKeys[latestHeight].GenSignedHeader( + chainID, + proofHeight, + bTime.Add(time.Duration(proofHeight+1)*time.Minute), // 12 mins + nil, + vals, + vals, + hash("app_hash"), + hash("cons_hash"), + hash("results_hash"), + 0, len(chainKeys), + types.DefaultVoterParams(), + ), + ValidatorSet: vals, + } + go func() { + time.Sleep(2 * time.Second) + witness.AddLightBlock(newLb) + }() + + // Now assert that verification returns an error. We craft the light clients time to be a little ahead of the chain + // to allow a window for the attack to manifest itself. + _, err = c.Update(ctx, bTime.Add(time.Duration(forgedHeight)*time.Minute)) + if assert.Error(t, err) { + assert.Equal(t, light.ErrLightClientAttack, err) + } + + // Check evidence was sent to the witness against the full node + evAgainstPrimary := &types.LightClientAttackEvidence{ + ConflictingBlock: &types.LightBlock{ + SignedHeader: primaryHeaders[forgedHeight], + ValidatorSet: primaryValidators[forgedHeight], + }, + CommonHeight: latestHeight, + } + assert.True(t, witness.HasEvidence(evAgainstPrimary)) + + // We attempt the same call but now the supporting witness has a block which should + // immediately conflict in time with the primary + _, err = c.VerifyLightBlockAtHeight(ctx, forgedHeight, bTime.Add(time.Duration(forgedHeight)*time.Minute)) + if assert.Error(t, err) { + assert.Equal(t, light.ErrLightClientAttack, err) + } + assert.True(t, witness.HasEvidence(evAgainstPrimary)) + + // Lastly we test the unfortunate case where the light clients supporting witness doesn't update + // in enough time + c, err = light.NewClient( + ctx, + chainID, + light.TrustOptions{ + Period: 4 * time.Hour, + Height: 1, + Hash: primaryHeaders[1].Hash(), + }, + primary, + []provider.Provider{laggingWitness, accomplice}, + dbs.New(memdb.NewDB(), chainID), + types.DefaultVoterParams(), + light.Logger(log.TestingLogger()), + light.MaxClockDrift(1*time.Second), + light.MaxBlockLag(1*time.Second), + ) + require.NoError(t, err) + + _, err = c.Update(ctx, bTime.Add(time.Duration(forgedHeight)*time.Minute)) + assert.NoError(t, err) + +} + // 1. Different nodes therefore a divergent header is produced. // => light client returns an error upon creation because primary and witness // have a different view. @@ -286,5 +430,43 @@ func TestClientDivergentTraces3(t *testing.T) { _, err = c.VerifyLightBlockAtHeight(ctx, 10, bTime.Add(1*time.Hour)) assert.Error(t, err) - assert.Equal(t, 0, len(c.Witnesses())) + assert.Equal(t, 1, len(c.Witnesses())) +} + +// 4. Witness has a divergent header but can not produce a valid trace to back it up. +// It should be ignored +func TestClientDivergentTraces4(t *testing.T) { + _, primaryHeaders, primaryVals, primaryVoters := genMockNode(chainID, 10, 5, 2, bTime) + primary := mockp.New(chainID, primaryHeaders, primaryVals, primaryVoters) + + firstBlock, err := primary.LightBlock(ctx, 1) + require.NoError(t, err) + + _, mockHeaders, mockVals, mockVoters := genMockNode(chainID, 10, 5, 2, bTime) + witness := primary.Copy(chainID) + witness.AddLightBlock(&types.LightBlock{ + SignedHeader: mockHeaders[10], + ValidatorSet: mockVals[10], + VoterSet: mockVoters[10], + }) + + c, err := light.NewClient( + ctx, + chainID, + light.TrustOptions{ + Height: 1, + Hash: firstBlock.Hash(), + Period: 4 * time.Hour, + }, + primary, + []provider.Provider{witness}, + dbs.New(memdb.NewDB(), chainID), + types.DefaultVoterParams(), + light.Logger(log.TestingLogger()), + ) + require.NoError(t, err) + + _, err = c.VerifyLightBlockAtHeight(ctx, 10, bTime.Add(1*time.Hour)) + assert.Error(t, err) + assert.Equal(t, 1, len(c.Witnesses())) } diff --git a/light/errors.go b/light/errors.go index e2e6fc8e4..afc9aa0eb 100644 --- a/light/errors.go +++ b/light/errors.go @@ -63,6 +63,18 @@ func (e ErrVerificationFailed) Error() string { return fmt.Sprintf("verify from #%d to #%d failed: %v", e.From, e.To, e.Reason) } +// ErrLightClientAttack is returned when the light client has detected an attempt +// to verify a false header and has sent the evidence to either a witness or primary. +var ErrLightClientAttack = errors.New(`attempted attack detected. + Light client received valid conflicting header from witness. + Unable to verify header. Evidence has been sent to both providers. + Check logs for full evidence and trace`, +) + +// ErrNoWitnesses means that there are not enough witnesses connected to +// continue running the light client. +var ErrNoWitnesses = errors.New("no witnesses connected. please reset light client") + // ----------------------------- INTERNAL ERRORS --------------------------------- // ErrConflictingHeaders is thrown when two conflicting headers are discovered. @@ -95,3 +107,7 @@ type errBadWitness struct { func (e errBadWitness) Error() string { return fmt.Sprintf("Witness %d returned error: %s", e.WitnessIndex, e.Reason.Error()) } + +var errNoDivergence = errors.New( + "sanity check failed: no divergence between the original trace and the provider's new trace", +) diff --git a/light/provider/errors.go b/light/provider/errors.go index 5d24efd73..398647b3e 100644 --- a/light/provider/errors.go +++ b/light/provider/errors.go @@ -6,8 +6,12 @@ import ( ) var ( + // ErrHeightTooHigh is returned when the height is higher than the last + // block that the provider has. The light client will not remove the provider + ErrHeightTooHigh = errors.New("height requested is too high") // ErrLightBlockNotFound is returned when a provider can't find the - // requested header. + // requested header (i.e. it has been pruned). + // The light client will not remove the provider ErrLightBlockNotFound = errors.New("light block not found") // ErrNoResponse is returned if the provider doesn't respond to the // request in a gieven time diff --git a/light/provider/http/http.go b/light/provider/http/http.go index 5769a8f34..f8348c60b 100644 --- a/light/provider/http/http.go +++ b/light/provider/http/http.go @@ -16,7 +16,8 @@ import ( var ( // This is very brittle, see: https://github.com/tendermint/tendermint/issues/4740 - regexpMissingHeight = regexp.MustCompile(`height \d+ (must be less than or equal to|is not available)`) + regexpMissingHeight = regexp.MustCompile(`height \d+ is not available`) + regexpTooHigh = regexp.MustCompile(`height \d+ must be less than or equal to`) maxRetryAttempts = 10 timeout uint = 5 // sec. @@ -75,6 +76,12 @@ func (p *http) LightBlock(ctx context.Context, height int64) (*types.LightBlock, return nil, err } + if height != 0 && sh.Height != height { + return nil, provider.ErrBadLightBlock{ + Reason: fmt.Errorf("height %d responded doesn't match height %d requested", sh.Height, height), + } + } + valSet, voterSet, err := p.voterSet(ctx, &sh.Height) if err != nil { return nil, err @@ -119,6 +126,10 @@ func (p *http) voterSet(ctx context.Context, height *int64) (*types.ValidatorSet res, err := p.client.Validators(ctx, height, &page, &perPage) if err != nil { // TODO: standardize errors on the RPC side + if regexpTooHigh.MatchString(err.Error()) { + return nil, nil, provider.ErrHeightTooHigh + } + if regexpMissingHeight.MatchString(err.Error()) { return nil, nil, provider.ErrLightBlockNotFound } @@ -167,6 +178,10 @@ func (p *http) signedHeader(ctx context.Context, height *int64) (*types.SignedHe commit, err := p.client.Commit(ctx, height) if err != nil { // TODO: standardize errors on the RPC side + if regexpTooHigh.MatchString(err.Error()) { + return nil, provider.ErrHeightTooHigh + } + if regexpMissingHeight.MatchString(err.Error()) { return nil, provider.ErrLightBlockNotFound } diff --git a/light/provider/http/http_test.go b/light/provider/http/http_test.go index 21cf177a3..4fcf42955 100644 --- a/light/provider/http/http_test.go +++ b/light/provider/http/http_test.go @@ -80,9 +80,10 @@ func TestProvider(t *testing.T) { assert.Equal(t, lower, sh.Height) // fetching missing heights (both future and pruned) should return appropriate errors - _, err = p.LightBlock(context.Background(), 1000) + lb, err := p.LightBlock(context.Background(), 1000) require.Error(t, err) - assert.Equal(t, provider.ErrLightBlockNotFound, err) + require.Nil(t, lb) + assert.Equal(t, provider.ErrHeightTooHigh, err) _, err = p.LightBlock(context.Background(), 1) require.Error(t, err) diff --git a/light/provider/mock/mock.go b/light/provider/mock/mock.go index 1dfb49bf1..68c52fbcc 100644 --- a/light/provider/mock/mock.go +++ b/light/provider/mock/mock.go @@ -5,17 +5,21 @@ import ( "errors" "fmt" "strings" + "sync" "github.com/line/ostracon/light/provider" "github.com/line/ostracon/types" ) type Mock struct { - chainID string + chainID string + + mtx sync.Mutex headers map[int64]*types.SignedHeader vals map[int64]*types.ValidatorSet voters map[int64]*types.VoterSet evidenceToReport map[string]types.Evidence // hash => evidence + latestHeight int64 } var _ provider.Provider = (*Mock)(nil) @@ -28,12 +32,19 @@ func New( vals map[int64]*types.ValidatorSet, voters map[int64]*types.VoterSet, ) *Mock { + height := int64(0) + for h := range headers { + if h > height { + height = h + } + } return &Mock{ chainID: chainID, headers: headers, vals: vals, voters: voters, evidenceToReport: make(map[string]types.Evidence), + latestHeight: height, } } @@ -57,18 +68,18 @@ func (p *Mock) String() string { } func (p *Mock) LightBlock(_ context.Context, height int64) (*types.LightBlock, error) { + p.mtx.Lock() + defer p.mtx.Unlock() var lb *types.LightBlock - if height == 0 && len(p.headers) > 0 { - sh := p.headers[int64(len(p.headers))] - vals := p.vals[int64(len(p.vals))] - voters := p.voters[int64(len(p.voters))] - lb = &types.LightBlock{ - SignedHeader: sh, - ValidatorSet: vals, - VoterSet: voters, - } + if height > p.latestHeight { + return nil, provider.ErrHeightTooHigh } + + if height == 0 && len(p.headers) > 0 { + height = p.latestHeight + } + if _, ok := p.headers[height]; ok { sh := p.headers[height] vals := p.vals[height] @@ -100,3 +111,21 @@ func (p *Mock) HasEvidence(ev types.Evidence) bool { _, ok := p.evidenceToReport[string(ev.Hash())] return ok } + +func (p *Mock) AddLightBlock(lb *types.LightBlock) { + p.mtx.Lock() + defer p.mtx.Unlock() + + if err := lb.ValidateBasic(lb.ChainID); err != nil { + panic(fmt.Sprintf("unable to add light block, err: %v", err)) + } + p.headers[lb.Height] = lb.SignedHeader + p.vals[lb.Height] = lb.ValidatorSet + if lb.Height > p.latestHeight { + p.latestHeight = lb.Height + } +} + +func (p *Mock) Copy(id string) *Mock { + return New(id, p.headers, p.vals, p.voters) +} diff --git a/rpc/core/env.go b/rpc/core/env.go index 945a9c6d3..b020a237c 100644 --- a/rpc/core/env.go +++ b/rpc/core/env.go @@ -155,7 +155,7 @@ func getHeight(latestHeight int64, heightPtr *int64) (int64, error) { } base := env.BlockStore.Base() if height < base { - return 0, fmt.Errorf("height %v is not available, lowest height is %v", + return 0, fmt.Errorf("height %d is not available, lowest height is %d", height, base) } return height, nil From eed4d02be934c923db76f27d210fa2fbb32b8954 Mon Sep 17 00:00:00 2001 From: tnasu Date: Wed, 22 Dec 2021 11:34:44 +0900 Subject: [PATCH 23/32] Revert evidence/pool.go in `Support for VRF implementation with libsodium (#297)` --- evidence/pool.go | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/evidence/pool.go b/evidence/pool.go index de85e6d98..373108cac 100644 --- a/evidence/pool.go +++ b/evidence/pool.go @@ -270,15 +270,14 @@ func (evpool *Pool) fastCheck(ev types.Evidence) bool { evpool.logger.Error("Failed to load light client attack evidence", "err", err, "key(height/hash)", key) return false } - var trustedPbEv tmproto.Evidence - err = trustedPbEv.Unmarshal(evBytes) - trustedPb := trustedPbEv.GetLightClientAttackEvidence() + var trustedPb tmproto.LightClientAttackEvidence + err = trustedPb.Unmarshal(evBytes) if err != nil { evpool.logger.Error("Failed to convert light client attack evidence from bytes", "err", err, "key(height/hash)", key) return false } - trustedEv, err := types.LightClientAttackEvidenceFromProto(trustedPb) + trustedEv, err := types.LightClientAttackEvidenceFromProto(&trustedPb) if err != nil { evpool.logger.Error("Failed to convert light client attack evidence from protobuf", "err", err, "key(height/hash)", key) From 1d47c8b5a7b528b8a74a1d620723ecfd36e06d25 Mon Sep 17 00:00:00 2001 From: tnasu Date: Wed, 22 Dec 2021 11:37:27 +0900 Subject: [PATCH 24/32] Fix test for `light/evidence: handle FLA backport (#6331)` --- evidence/verify_test.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/evidence/verify_test.go b/evidence/verify_test.go index 15246cc52..925d197b7 100644 --- a/evidence/verify_test.go +++ b/evidence/verify_test.go @@ -50,6 +50,7 @@ func TestVerifyLightClientAttack_Lunatic(t *testing.T) { conflictingHeader := makeHeaderRandom(10) conflictingHeader.Time = defaultEvidenceTime.Add(1 * time.Hour) + conflictingHeader.ValidatorsHash = conflictingVals.Hash() conflictingHeader.VotersHash = conflictingVoterSet.Hash() conflictingHeader.Proof = proof @@ -133,6 +134,7 @@ func TestVerifyLightClientAttack_Lunatic(t *testing.T) { forwardConflictingHeader := makeHeaderRandom(11) forwardConflictingHeader.Time = defaultEvidenceTime.Add(30 * time.Minute) forwardConflictingHeader.ValidatorsHash = conflictingVals.Hash() + forwardConflictingHeader.VotersHash = conflictingVoterSet.Hash() forwardBlockID := makeBlockID(forwardConflictingHeader.Hash(), 1000, []byte("partshash")) forwardVoteSet := types.NewVoteSet(evidenceChainID, 11, 1, tmproto.SignedMsgType(2), conflictingVoterSet) forwardCommit, err := types.MakeCommit(forwardBlockID, 11, 1, forwardVoteSet, conflictingPrivVals, defaultEvidenceTime) @@ -144,6 +146,7 @@ func TestVerifyLightClientAttack_Lunatic(t *testing.T) { Commit: forwardCommit, }, ValidatorSet: conflictingVals, + VoterSet: conflictingVoterSet, }, CommonHeight: 4, TotalVotingPower: 20, From 433f0cb99879010ca2626a3dd89a55e84ac0b94d Mon Sep 17 00:00:00 2001 From: tnasu Date: Wed, 22 Dec 2021 00:01:39 +0900 Subject: [PATCH 25/32] Ignore test for `light/evidence: handle FLA backport (#6331)` --- light/detector_test.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/light/detector_test.go b/light/detector_test.go index b6391633f..5e1338abf 100644 --- a/light/detector_test.go +++ b/light/detector_test.go @@ -197,6 +197,8 @@ func TestLightClientAttackEvidence_Equivocation(t *testing.T) { } func TestLightClientAttackEvidence_ForwardLunatic(t *testing.T) { + t.Skip("Voter selection in Ostracon only supports sequential verification mode, " + + "but Ostracon has a few test case for skipping mode.") // primary performs a lunatic attack but changes the time of the header to // something in the future relative to the blockchain var ( From ecf98d0dcde3b4809c5d36e49513cbbaba668c66 Mon Sep 17 00:00:00 2001 From: tnasu Date: Tue, 21 Dec 2021 23:43:00 +0900 Subject: [PATCH 26/32] Imporve the execution time of light test --- light/client_benchmark_test.go | 20 ++++++++++++++++++-- light/client_test.go | 4 ++-- 2 files changed, 20 insertions(+), 4 deletions(-) diff --git a/light/client_benchmark_test.go b/light/client_benchmark_test.go index 89fbb44a6..e09be1628 100644 --- a/light/client_benchmark_test.go +++ b/light/client_benchmark_test.go @@ -5,6 +5,8 @@ import ( "testing" "time" + "github.com/line/ostracon/libs/sync" + "github.com/line/tm-db/v2/memdb" "github.com/line/ostracon/libs/log" @@ -23,11 +25,23 @@ import ( // // Remember that none of these benchmarks account for network latency. var ( - benchmarkFullNode = mockp.New(genMockNode(chainID, 1000, 100, 1, bTime)) - genesisBlock, _ = benchmarkFullNode.LightBlock(context.Background(), 1) + mu sync.Mutex + // Shouldn't initialize variables here since affecting test (this is for benchmark test) + benchmarkFullNode *mockp.Mock + genesisBlock *types.LightBlock ) +func setupData() { + mu.Lock() + defer mu.Unlock() + if benchmarkFullNode == nil || genesisBlock == nil { + benchmarkFullNode = mockp.New(genMockNode(chainID, 1000, 100, 1, bTime)) + genesisBlock, _ = benchmarkFullNode.LightBlock(context.Background(), 1) + } +} + func BenchmarkSequence(b *testing.B) { + setupData() c, err := light.NewClient( context.Background(), chainID, @@ -57,6 +71,7 @@ func BenchmarkSequence(b *testing.B) { } func BenchmarkBisection(b *testing.B) { + setupData() c, err := light.NewClient( context.Background(), chainID, @@ -85,6 +100,7 @@ func BenchmarkBisection(b *testing.B) { } func BenchmarkBackwards(b *testing.B) { + setupData() trustedBlock, _ := benchmarkFullNode.LightBlock(context.Background(), 0) c, err := light.NewClient( context.Background(), diff --git a/light/client_test.go b/light/client_test.go index b4f2eb9f4..e68ee973b 100644 --- a/light/client_test.go +++ b/light/client_test.go @@ -81,8 +81,7 @@ var ( valSet, voterSet, ) - deadNode = mockp.NewDeadMock(chainID) - largeFullNode = mockp.New(genMockNode(chainID, 10, 3, 0, bTime)) + deadNode = mockp.NewDeadMock(chainID) ) func TestValidateTrustOptions(t *testing.T) { @@ -923,6 +922,7 @@ func TestClientReplacesPrimaryWithWitnessIfPrimaryIsUnavailable(t *testing.T) { } func TestClient_BackwardsVerification(t *testing.T) { + largeFullNode := mockp.New(genMockNode(chainID, 10, 3, 0, bTime)) { trustHeader, _ := largeFullNode.LightBlock(ctx, 6) c, err := light.NewClient( From 07b8e1c1ee4a0f7b1d10a3ede5a2dab48c73e582 Mon Sep 17 00:00:00 2001 From: tnasu Date: Tue, 21 Dec 2021 23:47:06 +0900 Subject: [PATCH 27/32] Improve log format --- types/light.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/types/light.go b/types/light.go index a95d0b51b..9c89f624a 100644 --- a/types/light.go +++ b/types/light.go @@ -57,8 +57,10 @@ func (lb LightBlock) StringIndented(indent string) string { return fmt.Sprintf(`LightBlock{ %s %v %s %v +%s %v %s}`, indent, lb.SignedHeader.StringIndented(indent+" "), + indent, lb.ValidatorSet.StringIndented(indent+" "), indent, lb.VoterSet.StringIndented(indent+" "), indent) } From 7404d2f91932588cd2ca4dceefe1eac556a132c9 Mon Sep 17 00:00:00 2001 From: tnasu Date: Tue, 21 Dec 2021 23:47:36 +0900 Subject: [PATCH 28/32] Bugfix logging value --- light/verifier.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/light/verifier.go b/light/verifier.go index b354d939c..07c8bc5f0 100644 --- a/light/verifier.go +++ b/light/verifier.go @@ -118,7 +118,7 @@ func VerifyAdjacent( if !bytes.Equal(untrustedHeader.ValidatorsHash, trustedHeader.NextValidatorsHash) { err := fmt.Errorf("expected old header next validators (%X) to match those from new header (%X)", trustedHeader.NextValidatorsHash, - untrustedHeader.VotersHash, + untrustedHeader.ValidatorsHash, ) return err } From 53712994b4795f85bac00187bf66937884dc86d9 Mon Sep 17 00:00:00 2001 From: tnasu Date: Tue, 21 Dec 2021 23:56:45 +0900 Subject: [PATCH 29/32] Refactor function for usability --- light/helpers_test.go | 134 ++++++++++++++++++++++++++++-------------- 1 file changed, 91 insertions(+), 43 deletions(-) diff --git a/light/helpers_test.go b/light/helpers_test.go index 2daab5af7..9cf8e7d41 100644 --- a/light/helpers_test.go +++ b/light/helpers_test.go @@ -190,7 +190,7 @@ func genHeader(chainID string, height int64, bTime time.Time, txs types.Txs, Proof: proof, ConsensusHash: consHash, LastResultsHash: resHash, - ProposerAddress: voterSet.Voters[0].Address, + ProposerAddress: valset.SelectProposer(proof, height, 0).Address, } } @@ -259,66 +259,114 @@ func (pkz privKeys) ChangeKeys(delta int) privKeys { return newKeys.Extend(delta) } +func genCalcValVariationFunc() func(valVariation float32) int { + totalVariation := float32(0) + valVariationInt := int(totalVariation) + return func(valVariation float32) int { + totalVariation += valVariation + valVariationInt = int(totalVariation) + totalVariation = -float32(valVariationInt) + return valVariationInt + } +} + +func genMockNodeWithKey( + chainID string, + height int64, + keys, newKeys privKeys, + valSet, newValSet *types.ValidatorSet, + lastHeader *types.SignedHeader, + bTime time.Time, + valVariation float32, + calcValVariation func(valVariation float32) int) ( + *types.SignedHeader, *types.ValidatorSet, *types.VoterSet, privKeys) { + + if newKeys == nil { + newKeys = keys.ChangeKeys(calcValVariation(valVariation)) + } + if valSet == nil { + valSet = keys.ToValidators(2, 2) + } + if newValSet == nil { + newValSet = newKeys.ToValidators(2, 2) + } + if lastHeader == nil { + header := keys.GenSignedHeader( + chainID, height, bTime, + nil, valSet, newValSet, + hash("app_hash"), hash("cons_hash"), hash("results_hash"), + 0, len(keys), + types.DefaultVoterParams(), + ) + voterSet := types.SelectVoter(valSet, proofHash(header), types.DefaultVoterParams()) + return header, valSet, voterSet, newKeys + } + header := keys.GenSignedHeaderLastBlockID( + chainID, height, bTime, + nil, valSet, newValSet, + hash("app_hash"), hash("cons_hash"), hash("results_hash"), + 0, len(keys), + types.BlockID{Hash: lastHeader.Hash()}, + types.DefaultVoterParams(), + ) + voterSet := types.SelectVoter(valSet, proofHash(header), types.DefaultVoterParams()) + return header, valSet, voterSet, newKeys +} + // Generates the header and validator set to create a full entire mock node with blocks to height ( // blockSize) and with variation in validator sets. BlockIntervals are in per minute. // NOTE: Expected to have a large validator set size ~ 100 validators. -func genMockNodeWithKeys( - chainID string, - blockSize int64, - valSize int, - valVariation float32, - bTime time.Time) ( +func genMockNodeWithKeys(chainID string, blockSize int64, valSize int, valVariation float32, bTime time.Time) ( map[int64]*types.SignedHeader, map[int64]*types.ValidatorSet, map[int64]*types.VoterSet, map[int64]privKeys) { var ( - headers = make(map[int64]*types.SignedHeader, blockSize) - valSet = make(map[int64]*types.ValidatorSet, blockSize+1) - voterSet = make(map[int64]*types.VoterSet, blockSize+1) - keymap = make(map[int64]privKeys, blockSize+1) - keys = genPrivKeys(valSize) - totalVariation = valVariation - valVariationInt int - newKeys privKeys + headers = make(map[int64]*types.SignedHeader, blockSize) + valSet = make(map[int64]*types.ValidatorSet, blockSize+1) + voterSet = make(map[int64]*types.VoterSet, blockSize+1) + keymap = make(map[int64]privKeys, blockSize+1) ) - valVariationInt = int(totalVariation) - totalVariation = -float32(valVariationInt) - newKeys = keys.ChangeKeys(valVariationInt) - keymap[1] = keys - keymap[2] = newKeys + setter := func(height int64, + header *types.SignedHeader, vals *types.ValidatorSet, voters *types.VoterSet, newKeys privKeys) { + headers[height] = header + valSet[height] = vals + voterSet[height] = voters + keymap[height+1] = newKeys + } + + height := int64(1) + keys := genPrivKeys(valSize) + keymap[height] = keys + calcValVariationFunc := genCalcValVariationFunc() + + header, vals, voters, newKeys := genMockNodeWithKey(chainID, height, + keys, nil, + nil, nil, nil, + bTime.Add(time.Duration(height)*time.Minute), + valVariation, calcValVariationFunc) // genesis header and vals - valSet[1] = keys.ToValidators(2, 2) - lastHeader := keys.GenSignedHeader(chainID, 1, bTime.Add(1*time.Minute), nil, - valSet[1], newKeys.ToValidators(2, 2), - hash("app_hash"), hash("cons_hash"), hash("results_hash"), 0, len(keys), types.DefaultVoterParams()) - currentHeader := lastHeader - headers[1] = currentHeader - voterSet[1] = types.SelectVoter(valSet[1], proofHash(headers[1]), types.DefaultVoterParams()) + setter(height, header, vals, voters, newKeys) + keys = newKeys + lastHeader := header for height := int64(2); height <= blockSize; height++ { - totalVariation += valVariation - valVariationInt = int(totalVariation) - totalVariation = -float32(valVariationInt) - newKeys = keys.ChangeKeys(valVariationInt) - valSet[height] = keys.ToValidators(2, 2) - currentHeader = keys.GenSignedHeaderLastBlockID(chainID, height, bTime.Add(time.Duration(height)*time.Minute), - nil, - valSet[height], newKeys.ToValidators(2, 2), - hash("app_hash"), hash("cons_hash"), hash("results_hash"), 0, len(keys), - types.BlockID{Hash: lastHeader.Hash()}, types.DefaultVoterParams()) - if !bytes.Equal(currentHeader.Hash(), currentHeader.Commit.BlockID.Hash) { - panic(fmt.Sprintf("commit hash didn't match: %X != %X", currentHeader.Hash(), currentHeader.Commit.BlockID.Hash)) + header, vals, voters, newKeys := genMockNodeWithKey(chainID, height, + keys, nil, + nil, nil, lastHeader, + bTime.Add(time.Duration(height)*time.Minute), + valVariation, calcValVariationFunc) + if !bytes.Equal(header.Hash(), header.Commit.BlockID.Hash) { + panic(fmt.Sprintf("commit hash didn't match: %X != %X", header.Hash(), header.Commit.BlockID.Hash)) } - headers[height] = currentHeader - voterSet[height] = types.SelectVoter(valSet[height], proofHash(headers[height]), types.DefaultVoterParams()) - lastHeader = currentHeader + setter(height, header, vals, voters, newKeys) + keys = newKeys - keymap[height+1] = keys + lastHeader = header } return headers, valSet, voterSet, keymap From e4bea390b3b92d78a0e41405dac8e89a5b0cbe35 Mon Sep 17 00:00:00 2001 From: tnasu Date: Wed, 22 Dec 2021 11:58:13 +0900 Subject: [PATCH 30/32] Fix `TestLightClientAttackEvidence_Lunatic` --- light/detector_test.go | 54 ++++++++++++++++++++++++------------------ 1 file changed, 31 insertions(+), 23 deletions(-) diff --git a/light/detector_test.go b/light/detector_test.go index 5e1338abf..65119a50d 100644 --- a/light/detector_test.go +++ b/light/detector_test.go @@ -17,8 +17,6 @@ import ( ) func TestLightClientAttackEvidence_Lunatic(t *testing.T) { - t.Skip("Voter selection in Ostracon only supports sequential verification mode, " + - "but Ostracon has a few test case for skipping mode.") // primary performs a lunatic attack var ( latestHeight = int64(10) @@ -32,25 +30,35 @@ func TestLightClientAttackEvidence_Lunatic(t *testing.T) { witnessHeaders, witnessValidators, witnessVoters, chainKeys := genMockNodeWithKeys(chainID, latestHeight, valSize, 2, bTime) witness := mockp.New(chainID, witnessHeaders, witnessValidators, witnessVoters) - forgedKeys := chainKeys[divergenceHeight-1].ChangeKeys(3) // we change 3 out of the 5 validators (still 2/5 remain) - forgedVals := forgedKeys.ToValidators(2, 0) - for height := int64(1); height <= latestHeight; height++ { - if height < divergenceHeight { - primaryHeaders[height] = witnessHeaders[height] - primaryValidators[height] = witnessValidators[height] - primaryVoters[height] = witnessVoters[height] - continue - } - primaryHeaders[height] = forgedKeys.GenSignedHeader(chainID, height, bTime.Add(time.Duration(height)*time.Minute), - nil, forgedVals, forgedVals, hash("app_hash"), hash("cons_hash"), hash("results_hash"), 0, len(forgedKeys), - types.DefaultVoterParams()) - primaryValidators[height] = forgedVals - primaryVoters[height] = types.SelectVoter( - primaryValidators[height], - proofHash(primaryHeaders[height]), - types.DefaultVoterParams(), - ) + preDivergenceHeight := divergenceHeight - 1 + for height := int64(1); height < preDivergenceHeight; height++ { + primaryHeaders[height] = witnessHeaders[height] + primaryValidators[height] = witnessValidators[height] + primaryVoters[height] = witnessVoters[height] + } + // previous divergence height + curKeys := chainKeys[preDivergenceHeight] + forgedKeys := curKeys.ChangeKeys(3) // we change 3 out of the 5 validators (still 2/5 remain) + forgedVals := forgedKeys.ToValidators(2, 0) + header, vals, voters, _ := genMockNodeWithKey(chainID, preDivergenceHeight, + curKeys, forgedKeys, // Should specify the correct current/next keys + nil, forgedVals, primaryHeaders[preDivergenceHeight-1], + bTime.Add(time.Duration(preDivergenceHeight)*time.Minute), + 0, nil) + primaryHeaders[preDivergenceHeight] = header + primaryValidators[preDivergenceHeight] = vals + primaryVoters[preDivergenceHeight] = voters + // after divergence height + for height := divergenceHeight; height <= latestHeight; height++ { + header, vals, voters, _ := genMockNodeWithKey(chainID, height, + forgedKeys, forgedKeys, + forgedVals, forgedVals, primaryHeaders[height-1], + bTime.Add(time.Duration(height)*time.Minute), + 0, nil) + primaryHeaders[height] = header + primaryValidators[height] = vals + primaryVoters[height] = voters } primary := mockp.New(chainID, primaryHeaders, primaryValidators, primaryVoters) @@ -80,9 +88,9 @@ func TestLightClientAttackEvidence_Lunatic(t *testing.T) { // Check evidence was sent to both full nodes. evAgainstPrimary := &types.LightClientAttackEvidence{ - // after the divergence height the valset doesn't change so we expect the evidence to be for height 10 + // after the divergence height the valset doesn't change, so we expect the evidence to be for height 10 ConflictingBlock: &types.LightBlock{ - SignedHeader: primaryHeaders[10], + SignedHeader: primaryHeaders[preDivergenceHeight], // Need to specify the exact header ValidatorSet: primaryValidators[10], VoterSet: primaryVoters[10], }, @@ -94,7 +102,7 @@ func TestLightClientAttackEvidence_Lunatic(t *testing.T) { // when forming evidence against witness we learn that the canonical chain continued to change validator sets // hence the conflicting block is at 7 ConflictingBlock: &types.LightBlock{ - SignedHeader: witnessHeaders[7], + SignedHeader: witnessHeaders[preDivergenceHeight], // Need to specify the exact header ValidatorSet: witnessValidators[7], VoterSet: witnessVoters[7], }, From 68beef483e77f1335615b24e87aec0f6cdba5741 Mon Sep 17 00:00:00 2001 From: tnasu Date: Wed, 22 Dec 2021 12:52:07 +0900 Subject: [PATCH 31/32] Fix `TestLightClientAttackEvidence_Equivocation` --- light/detector_test.go | 30 ++++++++++++++++-------------- light/helpers_test.go | 12 ++++++++---- 2 files changed, 24 insertions(+), 18 deletions(-) diff --git a/light/detector_test.go b/light/detector_test.go index 65119a50d..98d4f3b15 100644 --- a/light/detector_test.go +++ b/light/detector_test.go @@ -41,20 +41,22 @@ func TestLightClientAttackEvidence_Lunatic(t *testing.T) { curKeys := chainKeys[preDivergenceHeight] forgedKeys := curKeys.ChangeKeys(3) // we change 3 out of the 5 validators (still 2/5 remain) forgedVals := forgedKeys.ToValidators(2, 0) - header, vals, voters, _ := genMockNodeWithKey(chainID, preDivergenceHeight, + header, vals, voters, _ := genMockNodeWithKey(chainID, preDivergenceHeight, nil, curKeys, forgedKeys, // Should specify the correct current/next keys nil, forgedVals, primaryHeaders[preDivergenceHeight-1], bTime.Add(time.Duration(preDivergenceHeight)*time.Minute), + 0, len(curKeys), 0, nil) primaryHeaders[preDivergenceHeight] = header primaryValidators[preDivergenceHeight] = vals primaryVoters[preDivergenceHeight] = voters // after divergence height for height := divergenceHeight; height <= latestHeight; height++ { - header, vals, voters, _ := genMockNodeWithKey(chainID, height, + header, vals, voters, _ := genMockNodeWithKey(chainID, height, nil, forgedKeys, forgedKeys, forgedVals, forgedVals, primaryHeaders[height-1], bTime.Add(time.Duration(height)*time.Minute), + 0, len(forgedKeys), 0, nil) primaryHeaders[height] = header primaryValidators[height] = vals @@ -112,11 +114,9 @@ func TestLightClientAttackEvidence_Lunatic(t *testing.T) { } func TestLightClientAttackEvidence_Equivocation(t *testing.T) { - t.Skip("Voter selection in Ostracon only supports sequential verification mode, " + - "but Ostracon has a few test case for skipping mode.") verificationOptions := map[string]light.Option{ "sequential": light.SequentialVerification(), - "skipping": light.SkippingVerification(light.DefaultTrustLevel), + // "skipping": light.SkippingVerification(light.DefaultTrustLevel), // Cannot do skipping-test } for s, verificationOption := range verificationOptions { @@ -143,15 +143,17 @@ func TestLightClientAttackEvidence_Equivocation(t *testing.T) { primaryVoters[height] = witnessVoters[height] continue } - // we don't have a network partition so we will make 4/5 (greater than 2/3) malicious and vote again for + // we don't have a network partition, so we will make 4/5 (greater than 2/3) malicious and vote again for // a different block (which we do by adding txs) - primaryHeaders[height] = chainKeys[height].GenSignedHeader(chainID, height, - bTime.Add(time.Duration(height)*time.Minute), []types.Tx{[]byte("abcd")}, - witnessValidators[height], witnessValidators[height+1], hash("app_hash"), - hash("cons_hash"), hash("results_hash"), 0, len(chainKeys[height])-1, - types.DefaultVoterParams()) - primaryValidators[height] = witnessValidators[height] - primaryVoters[height] = witnessVoters[height] + header, vals, voters, _ := genMockNodeWithKey(chainID, height, []types.Tx{[]byte("abcd")}, + chainKeys[height], chainKeys[height+1], + witnessValidators[height], witnessValidators[height+1], primaryHeaders[height-1], + bTime.Add(time.Duration(height)*time.Minute), + 0, len(chainKeys[height])-1, // make 4/5 + 0, nil) + primaryHeaders[height] = header + primaryValidators[height] = vals + primaryVoters[height] = voters } primary := mockp.New(chainID, primaryHeaders, primaryValidators, primaryVoters) @@ -196,7 +198,7 @@ func TestLightClientAttackEvidence_Equivocation(t *testing.T) { ConflictingBlock: &types.LightBlock{ SignedHeader: witnessHeaders[divergenceHeight], ValidatorSet: witnessValidators[divergenceHeight], - VoterSet: primaryVoters[divergenceHeight], + VoterSet: witnessVoters[divergenceHeight], }, CommonHeight: divergenceHeight, } diff --git a/light/helpers_test.go b/light/helpers_test.go index 9cf8e7d41..67d20371a 100644 --- a/light/helpers_test.go +++ b/light/helpers_test.go @@ -273,10 +273,12 @@ func genCalcValVariationFunc() func(valVariation float32) int { func genMockNodeWithKey( chainID string, height int64, + txs types.Txs, keys, newKeys privKeys, valSet, newValSet *types.ValidatorSet, lastHeader *types.SignedHeader, bTime time.Time, + first, last int, valVariation float32, calcValVariation func(valVariation float32) int) ( *types.SignedHeader, *types.ValidatorSet, *types.VoterSet, privKeys) { @@ -293,9 +295,9 @@ func genMockNodeWithKey( if lastHeader == nil { header := keys.GenSignedHeader( chainID, height, bTime, - nil, valSet, newValSet, + txs, valSet, newValSet, hash("app_hash"), hash("cons_hash"), hash("results_hash"), - 0, len(keys), + first, last, types.DefaultVoterParams(), ) voterSet := types.SelectVoter(valSet, proofHash(header), types.DefaultVoterParams()) @@ -342,10 +344,11 @@ func genMockNodeWithKeys(chainID string, blockSize int64, valSize int, valVariat keymap[height] = keys calcValVariationFunc := genCalcValVariationFunc() - header, vals, voters, newKeys := genMockNodeWithKey(chainID, height, + header, vals, voters, newKeys := genMockNodeWithKey(chainID, height, nil, keys, nil, nil, nil, nil, bTime.Add(time.Duration(height)*time.Minute), + 0, len(keys), valVariation, calcValVariationFunc) // genesis header and vals @@ -355,10 +358,11 @@ func genMockNodeWithKeys(chainID string, blockSize int64, valSize int, valVariat lastHeader := header for height := int64(2); height <= blockSize; height++ { - header, vals, voters, newKeys := genMockNodeWithKey(chainID, height, + header, vals, voters, newKeys := genMockNodeWithKey(chainID, height, nil, keys, nil, nil, nil, lastHeader, bTime.Add(time.Duration(height)*time.Minute), + 0, len(keys), valVariation, calcValVariationFunc) if !bytes.Equal(header.Hash(), header.Commit.BlockID.Hash) { panic(fmt.Sprintf("commit hash didn't match: %X != %X", header.Hash(), header.Commit.BlockID.Hash)) From d23562afde0a01c9ff62d081d095cc5de044d360 Mon Sep 17 00:00:00 2001 From: tnasu Date: Wed, 22 Dec 2021 21:30:41 +0900 Subject: [PATCH 32/32] Fix `TestLightClientAttackEvidence_ForwardLunatic` --- light/detector_test.go | 75 ++++++++++++++++++------------------- light/provider/mock/mock.go | 1 + 2 files changed, 38 insertions(+), 38 deletions(-) diff --git a/light/detector_test.go b/light/detector_test.go index 98d4f3b15..d75a450e4 100644 --- a/light/detector_test.go +++ b/light/detector_test.go @@ -207,8 +207,6 @@ func TestLightClientAttackEvidence_Equivocation(t *testing.T) { } func TestLightClientAttackEvidence_ForwardLunatic(t *testing.T) { - t.Skip("Voter selection in Ostracon only supports sequential verification mode, " + - "but Ostracon has a few test case for skipping mode.") // primary performs a lunatic attack but changes the time of the header to // something in the future relative to the blockchain var ( @@ -229,27 +227,33 @@ func TestLightClientAttackEvidence_ForwardLunatic(t *testing.T) { for h := range witnessHeaders { primaryHeaders[h] = witnessHeaders[h] primaryValidators[h] = witnessValidators[h] + primaryVoters[h] = witnessVoters[h] } - forgedKeys := chainKeys[latestHeight].ChangeKeys(3) // we change 3 out of the 5 validators (still 2/5 remain) - primaryValidators[forgedHeight] = forgedKeys.ToValidators(2, 0) - primaryVoters[forgedHeight] = types.SelectVoter( - primaryValidators[forgedHeight], - proofHash(primaryHeaders[forgedHeight]), - types.DefaultVoterParams(), - ) - primaryHeaders[forgedHeight] = forgedKeys.GenSignedHeader( - chainID, - forgedHeight, - bTime.Add(time.Duration(latestHeight+1)*time.Minute), // 11 mins - nil, - primaryValidators[forgedHeight], - primaryValidators[forgedHeight], - hash("app_hash"), - hash("cons_hash"), - hash("results_hash"), + + // Make height=proofHeight for forgedHeight + proofKeys := chainKeys[proofHeight] + forgedKeys := proofKeys.ChangeKeys(3) // we change 3 out of the 5 validators (still 2/5 remain) + forgedVals := forgedKeys.ToValidators(2, 0) + header, vals, voters, _ := genMockNodeWithKey(chainID, proofHeight, nil, + proofKeys, forgedKeys, + nil, forgedVals, primaryHeaders[proofHeight-1], + bTime.Add(time.Duration(proofHeight)*time.Minute), // 11 mins + 0, len(proofKeys), + 0, nil) + primaryHeaders[proofHeight] = header + primaryValidators[proofHeight] = vals + primaryVoters[proofHeight] = voters + + // Make height=forgedHeight for forward lunatic + header, vals, voters, _ = genMockNodeWithKey(chainID, forgedHeight, nil, + forgedKeys, forgedKeys, + forgedVals, forgedVals, primaryHeaders[forgedHeight-1], + bTime.Add(time.Duration(forgedHeight)*time.Minute), 0, len(forgedKeys), - types.DefaultVoterParams(), - ) + 0, nil) + primaryHeaders[forgedHeight] = header + primaryValidators[forgedHeight] = vals + primaryVoters[forgedHeight] = voters witness := mockp.New(chainID, witnessHeaders, witnessValidators, witnessVoters) primary := mockp.New(chainID, primaryHeaders, primaryValidators, primaryVoters) @@ -278,24 +282,18 @@ func TestLightClientAttackEvidence_ForwardLunatic(t *testing.T) { ) require.NoError(t, err) - // two seconds later, the supporting withness should receive the header that can be used + // two seconds later, the supporting witness should receive the header that can be used // to prove that there was an attack - vals := chainKeys[latestHeight].ToValidators(2, 0) + header, vals, voters, _ = genMockNodeWithKey(chainID, proofHeight, nil, + proofKeys, proofKeys, + nil, nil, primaryHeaders[proofHeight-1], + bTime.Add(time.Duration(proofHeight+1)*time.Minute), // 12 mins + 0, len(proofKeys), + 0, nil) newLb := &types.LightBlock{ - SignedHeader: chainKeys[latestHeight].GenSignedHeader( - chainID, - proofHeight, - bTime.Add(time.Duration(proofHeight+1)*time.Minute), // 12 mins - nil, - vals, - vals, - hash("app_hash"), - hash("cons_hash"), - hash("results_hash"), - 0, len(chainKeys), - types.DefaultVoterParams(), - ), + SignedHeader: header, ValidatorSet: vals, + VoterSet: voters, } go func() { time.Sleep(2 * time.Second) @@ -312,8 +310,9 @@ func TestLightClientAttackEvidence_ForwardLunatic(t *testing.T) { // Check evidence was sent to the witness against the full node evAgainstPrimary := &types.LightClientAttackEvidence{ ConflictingBlock: &types.LightBlock{ - SignedHeader: primaryHeaders[forgedHeight], - ValidatorSet: primaryValidators[forgedHeight], + SignedHeader: primaryHeaders[proofHeight], + ValidatorSet: primaryValidators[proofHeight], + VoterSet: primaryVoters[proofHeight], }, CommonHeight: latestHeight, } diff --git a/light/provider/mock/mock.go b/light/provider/mock/mock.go index 68c52fbcc..4114e9731 100644 --- a/light/provider/mock/mock.go +++ b/light/provider/mock/mock.go @@ -121,6 +121,7 @@ func (p *Mock) AddLightBlock(lb *types.LightBlock) { } p.headers[lb.Height] = lb.SignedHeader p.vals[lb.Height] = lb.ValidatorSet + p.voters[lb.Height] = lb.VoterSet if lb.Height > p.latestHeight { p.latestHeight = lb.Height }