From 1f76cb154603282c098263ef730836134cf1d00b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 12 Aug 2021 15:23:37 +0000 Subject: [PATCH 01/37] build(deps): Bump google.golang.org/grpc from 1.39.1 to 1.40.0 (#6819) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps [google.golang.org/grpc](https://github.com/grpc/grpc-go) from 1.39.1 to 1.40.0.
Release notes

Sourced from google.golang.org/grpc's releases.

Release 1.40.0

Behavior Changes

Bug Fixes

New Features

Performance Improvements

Commits

[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=google.golang.org/grpc&package-manager=go_modules&previous-version=1.39.1&new-version=1.40.0)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
--- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index a23bc5167..84c6f43ac 100644 --- a/go.mod +++ b/go.mod @@ -37,7 +37,7 @@ require ( github.com/vektra/mockery/v2 v2.9.0 golang.org/x/crypto v0.0.0-20210314154223-e6e6c4f2bb5b golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4 - google.golang.org/grpc v1.39.1 + google.golang.org/grpc v1.40.0 gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b // indirect pgregory.net/rapid v0.4.7 ) diff --git a/go.sum b/go.sum index 3e20b60ab..81abd8a00 100644 --- a/go.sum +++ b/go.sum @@ -1368,8 +1368,8 @@ google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAG 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.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= -google.golang.org/grpc v1.39.1 h1:f37vZbBVTiJ6jKG5mWz8ySOBxNqy6ViPgyhSdVnxF3E= -google.golang.org/grpc v1.39.1/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE= +google.golang.org/grpc v1.40.0 h1:AGJ0Ih4mHjSeibYkFGh1dD9KJ/eOtZ93I6hoHhukQ5Q= +google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= 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= From 511e52c2fcb39cd437c871d08b4c43387dc3901d Mon Sep 17 00:00:00 2001 From: Carlos Rodriguez Date: Fri, 13 Aug 2021 17:02:30 +0200 Subject: [PATCH 02/37] doc: fix typos in /tx_search and /tx. (#6823) Just a few small typos in the descriptions of the /tx_search and /tx endpoints. --- rpc/openapi/openapi.yaml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/rpc/openapi/openapi.yaml b/rpc/openapi/openapi.yaml index bfd49b461..0d8f8ac4a 100644 --- a/rpc/openapi/openapi.yaml +++ b/rpc/openapi/openapi.yaml @@ -1042,7 +1042,7 @@ paths: - Info responses: "200": - description: List of unconfirmed transactions + description: List of transactions content: application/json: schema: @@ -1132,10 +1132,10 @@ paths: tags: - Info description: | - Get a trasasction + Get a transaction responses: "200": - description: Get a transaction` + description: Get a transaction content: application/json: schema: From bf77c0c5443646db5128bb94199113c5336eaab0 Mon Sep 17 00:00:00 2001 From: Sam Kleinman Date: Fri, 13 Aug 2021 15:39:07 -0400 Subject: [PATCH 03/37] rpc: support new p2p infrastructure (#6820) --- CHANGELOG_PENDING.md | 19 ++++++----- UPGRADING.md | 16 ++++++++- crypto/secp256k1/secp256k1.go | 2 +- node/node.go | 4 ++- rpc/core/consensus.go | 64 +++++++++++++++++++++++++++-------- rpc/core/env.go | 16 +++++++-- rpc/core/net.go | 40 +++++++++++++++------- rpc/core/types/responses.go | 7 ++-- rpc/openapi/openapi.yaml | 14 +++----- test/e2e/tests/net_test.go | 11 +++--- 10 files changed, 132 insertions(+), 61 deletions(-) diff --git a/CHANGELOG_PENDING.md b/CHANGELOG_PENDING.md index 9de5b8bcb..a0a47de34 100644 --- a/CHANGELOG_PENDING.md +++ b/CHANGELOG_PENDING.md @@ -25,6 +25,7 @@ Friendly reminder: We have a [bug bounty program](https://hackerone.com/tendermi - [rpc/grpc] \#6725 Mark gRPC in the RPC layer as deprecated. - [blockchain/v2] \#6730 Fast Sync v2 is deprecated, please use v0 - [rpc] Add genesis_chunked method to support paginated and parallel fetching of large genesis documents. + - [rpc] \#6820 Update RPC methods to reflect changes in the p2p layer, disabling support for `UnsafeDialPeers` and `UnsafeDialPeers` when used with the new p2p layer, and changing the response format of the peer list in `NetInfo` for all users. - Apps - [ABCI] \#6408 Change the `key` and `value` fields from `[]byte` to `string` in the `EventAttribute` type. (@alexanderbez) @@ -33,7 +34,7 @@ Friendly reminder: We have a [bug bounty program](https://hackerone.com/tendermi - [ABCI] \#5818 Use protoio for msg length delimitation. Migrates from int64 to uint64 length delimiters. - [ABCI] \#3546 Add `mempool_error` field to `ResponseCheckTx`. This field will contain an error string if Tendermint encountered an error while adding a transaction to the mempool. (@williambanfield) - [Version] \#6494 `TMCoreSemVer` has been renamed to `TMVersion`. - - It is not required any longer to set ldflags to set version strings + - It is not required any longer to set ldflags to set version strings - [abci/counter] \#6684 Delete counter example app - P2P Protocol @@ -56,25 +57,25 @@ Friendly reminder: We have a [bug bounty program](https://hackerone.com/tendermi - [store] \#5848 Remove block store state in favor of using the db iterators directly (@cmwaters) - [state] \#5864 Use an iterator when pruning state (@cmwaters) - [types] \#6023 Remove `tm2pb.Header`, `tm2pb.BlockID`, `tm2pb.PartSetHeader` and `tm2pb.NewValidatorUpdate`. - - Each of the above types has a `ToProto` and `FromProto` method or function which replaced this logic. + - Each of the above types has a `ToProto` and `FromProto` method or function which replaced this logic. - [light] \#6054 Move `MaxRetryAttempt` option from client to provider. - - `NewWithOptions` now sets the max retry attempts and timeouts (@cmwaters) + - `NewWithOptions` now sets the max retry attempts and timeouts (@cmwaters) - [all] \#6077 Change spelling from British English to American (@cmwaters) - - Rename "Subscription.Cancelled()" to "Subscription.Canceled()" in libs/pubsub - - Rename "behaviour" pkg to "behavior" and internalized it in blockchain v2 + - Rename "Subscription.Cancelled()" to "Subscription.Canceled()" in libs/pubsub + - Rename "behaviour" pkg to "behavior" and internalized it in blockchain v2 - [rpc/client/http] \#6176 Remove `endpoint` arg from `New`, `NewWithTimeout` and `NewWithClient` (@melekes) - [rpc/client/http] \#6176 Unexpose `WSEvents` (@melekes) - [rpc/jsonrpc/client/ws_client] \#6176 `NewWS` no longer accepts options (use `NewWSWithOptions` and `OnReconnect` funcs to configure the client) (@melekes) - [internal/libs] \#6366 Move `autofile`, `clist`,`fail`,`flowrate`, `protoio`, `sync`, `tempfile`, `test` and `timer` lib packages to an internal folder - [libs/rand] \#6364 Remove most of libs/rand in favour of standard lib's `math/rand` (@liamsi) - [mempool] \#6466 The original mempool reactor has been versioned as `v0` and moved to a sub-package under the root `mempool` package. - Some core types have been kept in the `mempool` package such as `TxCache` and it's implementations, the `Mempool` interface itself - and `TxInfo`. (@alexanderbez) + Some core types have been kept in the `mempool` package such as `TxCache` and it's implementations, the `Mempool` interface itself + and `TxInfo`. (@alexanderbez) - [crypto/sr25519] \#6526 Do not re-execute the Ed25519-style key derivation step when doing signing and verification. The derivation is now done once and only once. This breaks `sr25519.GenPrivKeyFromSecret` output compatibility. (@Yawning) - - [types] \#6627 Move `NodeKey` to types to make the type public. + - [types] \#6627 Move `NodeKey` to types to make the type public. - [config] \#6627 Extend `config` to contain methods `LoadNodeKeyID` and `LoadorGenNodeKeyID` - [blocksync] \#6755 Rename `FastSync` and `Blockchain` package to `BlockSync` - (@cmwaters) + (@cmwaters) - Blockchain Protocol diff --git a/UPGRADING.md b/UPGRADING.md index e53c34c29..9d1a426ea 100644 --- a/UPGRADING.md +++ b/UPGRADING.md @@ -100,8 +100,22 @@ will need to change to accommodate these changes. Most notably: ### RPC changes +#### gRPC Support + Mark gRPC in the RPC layer as deprecated and to be removed in 0.36. +#### Peer Management Interface + +When running with the new P2P Layer, the methods `UnsafeDialSeeds` and +`UnsafeDialPeers` RPC methods will always return an error. They are +deprecated and will be removed in 0.36 when the legacy peer stack is +removed. + +Additionally the format of the Peer list returned in the `NetInfo` +method changes in this release to accommodate the different way that +the new stack tracks data about peers. This change affects users of +both stacks. + ### Support for Custom Reactor and Mempool Implementations The changes to p2p layer removed existing support for custom @@ -110,7 +124,7 @@ used, the introduction of the prioritized mempool covers nearly all of the use cases for custom reactors. If you are currently running custom reactors and mempools and are having trouble seeing the migration path for your project please feel free to reach out to the Tendermint Core -development team directly. +development team directly. ## v0.34.0 diff --git a/crypto/secp256k1/secp256k1.go b/crypto/secp256k1/secp256k1.go index dfe34ae10..c2c0c6017 100644 --- a/crypto/secp256k1/secp256k1.go +++ b/crypto/secp256k1/secp256k1.go @@ -13,7 +13,7 @@ import ( tmjson "github.com/tendermint/tendermint/libs/json" // necessary for Bitcoin address format - "golang.org/x/crypto/ripemd160" // nolint: staticcheck + "golang.org/x/crypto/ripemd160" // nolint ) //------------------------------------- diff --git a/node/node.go b/node/node.go index 751c78889..a31362b49 100644 --- a/node/node.go +++ b/node/node.go @@ -445,9 +445,11 @@ func makeNode(config *cfg.Config, BlockStore: blockStore, EvidencePool: evPool, ConsensusState: csState, - P2PPeers: sw, BlockSyncReactor: bcReactor.(cs.BlockSyncReactor), + P2PPeers: sw, + PeerManager: peerManager, + GenDoc: genDoc, EventSinks: eventSinks, ConsensusReactor: csReactor, diff --git a/rpc/core/consensus.go b/rpc/core/consensus.go index 1767c4b35..b067e1063 100644 --- a/rpc/core/consensus.go +++ b/rpc/core/consensus.go @@ -1,6 +1,8 @@ package core import ( + "errors" + cm "github.com/tendermint/tendermint/internal/consensus" tmmath "github.com/tendermint/tendermint/libs/math" ctypes "github.com/tendermint/tendermint/rpc/core/types" @@ -54,24 +56,56 @@ func (env *Environment) Validators( // More: https://docs.tendermint.com/master/rpc/#/Info/dump_consensus_state func (env *Environment) DumpConsensusState(ctx *rpctypes.Context) (*ctypes.ResultDumpConsensusState, error) { // Get Peer consensus states. - peers := env.P2PPeers.Peers().List() - peerStates := make([]ctypes.PeerStateInfo, len(peers)) - for i, peer := range peers { - peerState, ok := peer.Get(types.PeerStateKey).(*cm.PeerState) - if !ok { // peer does not have a state yet - continue + + var peerStates []ctypes.PeerStateInfo + switch { + case env.P2PPeers != nil: + peers := env.P2PPeers.Peers().List() + peerStates = make([]ctypes.PeerStateInfo, 0, len(peers)) + for _, peer := range peers { + peerState, ok := peer.Get(types.PeerStateKey).(*cm.PeerState) + if !ok { // peer does not have a state yet + continue + } + peerStateJSON, err := peerState.ToJSON() + if err != nil { + return nil, err + } + peerStates = append(peerStates, ctypes.PeerStateInfo{ + // Peer basic info. + NodeAddress: peer.SocketAddr().String(), + // Peer consensus state. + PeerState: peerStateJSON, + }) } - peerStateJSON, err := peerState.ToJSON() - if err != nil { - return nil, err - } - peerStates[i] = ctypes.PeerStateInfo{ - // Peer basic info. - NodeAddress: peer.SocketAddr().String(), - // Peer consensus state. - PeerState: peerStateJSON, + case env.PeerManager != nil: + peers := env.PeerManager.Peers() + peerStates = make([]ctypes.PeerStateInfo, 0, len(peers)) + for _, pid := range peers { + peerState, ok := env.ConsensusReactor.GetPeerState(pid) + if !ok { + continue + } + + peerStateJSON, err := peerState.ToJSON() + if err != nil { + return nil, err + } + + addr := env.PeerManager.Addresses(pid) + if len(addr) >= 1 { + peerStates = append(peerStates, ctypes.PeerStateInfo{ + // Peer basic info. + NodeAddress: addr[0].String(), + // Peer consensus state. + PeerState: peerStateJSON, + }) + } } + default: + return nil, errors.New("no peer system configured") } + // Get self round state. roundState, err := env.ConsensusState.GetRoundStateJSON() if err != nil { diff --git a/rpc/core/env.go b/rpc/core/env.go index eb7232c01..c35b8229c 100644 --- a/rpc/core/env.go +++ b/rpc/core/env.go @@ -36,7 +36,7 @@ const ( //---------------------------------------------- // These interfaces are used by RPC and must be thread safe -type Consensus interface { +type consensusState interface { GetState() sm.State GetValidators() (int64, []*types.Validator) GetLastHeight() int64 @@ -58,6 +58,11 @@ type peers interface { Peers() p2p.IPeerSet } +type peerManager interface { + Peers() []types.NodeID + Addresses(types.NodeID) []p2p.NodeAddress +} + //---------------------------------------------- // Environment contains objects and interfaces used by the RPC. It is expected // to be setup once during startup. @@ -70,9 +75,14 @@ type Environment struct { StateStore sm.Store BlockStore sm.BlockStore EvidencePool sm.EvidencePool - ConsensusState Consensus + ConsensusState consensusState P2PPeers peers - P2PTransport transport + + // Legacy p2p stack + P2PTransport transport + + // interfaces for new p2p interfaces + PeerManager peerManager // objects PubKey crypto.PubKey diff --git a/rpc/core/net.go b/rpc/core/net.go index 8f3e89d77..5b1672e26 100644 --- a/rpc/core/net.go +++ b/rpc/core/net.go @@ -13,19 +13,35 @@ import ( // NetInfo returns network info. // More: https://docs.tendermint.com/master/rpc/#/Info/net_info func (env *Environment) NetInfo(ctx *rpctypes.Context) (*ctypes.ResultNetInfo, error) { - peersList := env.P2PPeers.Peers().List() - peers := make([]ctypes.Peer, 0, len(peersList)) - for _, peer := range peersList { - peers = append(peers, ctypes.Peer{ - NodeInfo: peer.NodeInfo(), - IsOutbound: peer.IsOutbound(), - ConnectionStatus: peer.Status(), - RemoteIP: peer.RemoteIP().String(), - }) + var peers []ctypes.Peer + + switch { + case env.P2PPeers != nil: + peersList := env.P2PPeers.Peers().List() + peers = make([]ctypes.Peer, 0, len(peersList)) + for _, peer := range peersList { + peers = append(peers, ctypes.Peer{ + ID: peer.ID(), + URL: peer.SocketAddr().String(), + }) + } + case env.PeerManager != nil: + peerList := env.PeerManager.Peers() + for _, peer := range peerList { + addrs := env.PeerManager.Addresses(peer) + if len(addrs) == 0 { + continue + } + + peers = append(peers, ctypes.Peer{ + ID: peer, + URL: addrs[0].String(), + }) + } + default: + return nil, errors.New("peer management system does not support NetInfo responses") } - // TODO: Should we include PersistentPeers and Seeds in here? - // PRO: useful info - // CON: privacy + return &ctypes.ResultNetInfo{ Listening: env.P2PTransport.IsListening(), Listeners: env.P2PTransport.Listeners(), diff --git a/rpc/core/types/responses.go b/rpc/core/types/responses.go index a49e3c0d9..caa9b8732 100644 --- a/rpc/core/types/responses.go +++ b/rpc/core/types/responses.go @@ -7,7 +7,6 @@ import ( abci "github.com/tendermint/tendermint/abci/types" "github.com/tendermint/tendermint/crypto" - "github.com/tendermint/tendermint/internal/p2p" "github.com/tendermint/tendermint/libs/bytes" tmproto "github.com/tendermint/tendermint/proto/tendermint/types" "github.com/tendermint/tendermint/types" @@ -145,10 +144,8 @@ type ResultDialPeers struct { // A peer type Peer struct { - NodeInfo types.NodeInfo `json:"node_info"` - IsOutbound bool `json:"is_outbound"` - ConnectionStatus p2p.ConnectionStatus `json:"connection_status"` - RemoteIP string `json:"remote_ip"` + ID types.NodeID `json:"node_id"` + URL string `json:"url"` } // Validators for a height. diff --git a/rpc/openapi/openapi.yaml b/rpc/openapi/openapi.yaml index 0d8f8ac4a..535320b3f 100644 --- a/rpc/openapi/openapi.yaml +++ b/rpc/openapi/openapi.yaml @@ -1476,16 +1476,12 @@ components: Peer: type: object properties: - node_info: - $ref: "#/components/schemas/NodeInfo" - is_outbound: - type: boolean - example: true - connection_status: - $ref: "#/components/schemas/ConnectionStatus" - remote_ip: + node_id: type: string - example: "95.179.155.35" + example: "" + url: + type: string + example: "@95.179.155.35:2385>" NetInfo: type: object properties: diff --git a/test/e2e/tests/net_test.go b/test/e2e/tests/net_test.go index 1ca43fa05..8d331aff9 100644 --- a/test/e2e/tests/net_test.go +++ b/test/e2e/tests/net_test.go @@ -32,11 +32,12 @@ func TestNet_Peers(t *testing.T) { seen[n.Name] = (n.Name == node.Name) // we've clearly seen ourself } for _, peerInfo := range netInfo.Peers { - peer := node.Testnet.LookupNode(peerInfo.NodeInfo.Moniker) - require.NotNil(t, peer, "unknown node %v", peerInfo.NodeInfo.Moniker) - require.Equal(t, peer.IP.String(), peerInfo.RemoteIP, - "unexpected IP address for peer %v", peer.Name) - seen[peerInfo.NodeInfo.Moniker] = true + id := peerInfo.ID + peer := node.Testnet.LookupNode(string(id)) + require.NotNil(t, peer, "unknown node %v", id) + require.Contains(t, peerInfo.URL, peer.IP.String(), + "unexpected IP address for peer %v", id) + seen[string(id)] = true } for name := range seen { From 393a02a729e9845adf092f053678d426cdd79a22 Mon Sep 17 00:00:00 2001 From: Aleksandr Bezobchuk Date: Fri, 13 Aug 2021 21:40:14 -0400 Subject: [PATCH 04/37] rpc: log update (#6825) We INFO log every `ABCIQuery`. This can output a tremendous amount of noise in the logs, can cause cosmovisor to completely crash and slows down the node due to I/O. This log is completely unnecessary. Let's get this backported into v0.43 and get that into v0.43 and v0.42 releases of the SDK /cc @marbar3778 --- CHANGELOG_PENDING.md | 2 ++ rpc/core/abci.go | 3 ++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGELOG_PENDING.md b/CHANGELOG_PENDING.md index a0a47de34..e8d687f1c 100644 --- a/CHANGELOG_PENDING.md +++ b/CHANGELOG_PENDING.md @@ -108,6 +108,8 @@ Friendly reminder: We have a [bug bounty program](https://hackerone.com/tendermi - [statesync/event] \#6700 Emit statesync status start/end event (@JayT106) ### IMPROVEMENTS + +- [rpc] \#6825 Remove egregious INFO log from `ABCI#Query` RPC. (@alexanderbez) - [libs/log] Console log formatting changes as a result of \#6534 and \#6589. (@tychoish) - [statesync] \#6566 Allow state sync fetchers and request timeout to be configurable. (@alexanderbez) - [types] \#6478 Add `block_id` to `newblock` event (@jeebster) diff --git a/rpc/core/abci.go b/rpc/core/abci.go index 613eaec8b..ce705ba90 100644 --- a/rpc/core/abci.go +++ b/rpc/core/abci.go @@ -26,7 +26,7 @@ func (env *Environment) ABCIQuery( if err != nil { return nil, err } - env.Logger.Info("ABCIQuery", "path", path, "data", data, "result", resQuery) + return &ctypes.ResultABCIQuery{Response: *resQuery}, nil } @@ -37,5 +37,6 @@ func (env *Environment) ABCIInfo(ctx *rpctypes.Context) (*ctypes.ResultABCIInfo, if err != nil { return nil, err } + return &ctypes.ResultABCIInfo{Response: *resInfo}, nil } From 471f83d557f5e501ed45ff5d0eb7014277221a85 Mon Sep 17 00:00:00 2001 From: Tess Rinearson Date: Tue, 17 Aug 2021 15:28:20 +0200 Subject: [PATCH 05/37] contributing: update release instructions to use backport branches (#6827) --- CONTRIBUTING.md | 167 ++++++++++++++++++++++++++++-------------------- 1 file changed, 97 insertions(+), 70 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index a8bc8aa8f..79729cc1b 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -227,16 +227,96 @@ Fixes #nnnn Each PR should have one commit once it lands on `master`; this can be accomplished by using the "squash and merge" button on Github. Be sure to edit your commit message, though! -### Release Procedure +### Release procedure -#### Major Release +#### A note about backport branches +Tendermint's `master` branch is under active development. +Releases are specified using tags and are built from long-lived "backport" branches. +Each release "line" (e.g. 0.34 or 0.33) has its own long-lived backport branch, +and the backport branches have names like `v0.34.x` or `v0.33.x` +(literally, `x`; it is not a placeholder in this case). + +As non-breaking changes land on `master`, they should also be backported (cherry-picked) +to these backport branches. + +We use Mergify's [backport feature](https://mergify.io/features/backports) to automatically backport +to the needed branch. There should be a label for any backport branch that you'll be targeting. +To notify the bot to backport a pull request, mark the pull request with +the label `S:backport-to-`. +Once the original pull request is merged, the bot will try to cherry-pick the pull request +to the backport branch. If the bot fails to backport, it will open a pull request. +The author of the original pull request is responsible for solving the conflicts and +merging the pull request. + +#### Creating a backport branch +If this is the first release candidate for a major release, you get to have the honor of creating +the backport branch! + +Note that, after creating the backport branch, you'll also need to update the tags on `master` +so that `go mod` is able to order the branches correctly. You should tag `master` with a "dev" tag +that is "greater than" the backport branches tags. See #6072 for more context. + +In the following example, we'll assume that we're making a backport branch for +the 0.35.x line. + +1. Start on `master` +2. Create the backport branch: + `git checkout -b v0.35.x` +3. Go back to master and tag it as the dev branch for the _next_ major release and push it back up: + `git tag -a v0.36.0-dev; git push v0.36.0-dev` +4. Create a new workflow to run the e2e nightlies for this backport branch. + (See https://github.com/tendermint/tendermint/blob/master/.github/workflows/e2e-nightly-34x.yml + for an example.) + +#### Release candidates + +Before creating an official release, especially a major release, we may want to create a +release candidate (RC) for our friends and partners to test out. We use git tags to +create RCs, and we build them off of backport branches. + +Tags for RCs should follow the "standard" release naming conventions, with `-rcX` at the end +(for example, `v0.35.0-rc0`). + +(Note that branches and tags _cannot_ have the same names, so it's important that these branches +have distinct names from the tags/release names.) + +If this is the first RC for a major release, you'll have to make a new backport branch (see above). +Otherwise: + +1. Start from the backport branch (e.g. `v0.35.x`). +1. Run the integration tests and the e2e nightlies + (which can be triggered from the Github UI; + e.g., https://github.com/tendermint/tendermint/actions/workflows/e2e-nightly-34x.yml). +1. Prepare the changelog: + - Move the changes included in `CHANGELOG_PENDING.md` into `CHANGELOG.md`. + - Run `python ./scripts/linkify_changelog.py CHANGELOG.md` to add links for + all PRs + - Ensure that UPGRADING.md is up-to-date and includes notes on any breaking changes + or other upgrading flows. + - Bump TMVersionDefault version in `version.go` + - Bump P2P and block protocol versions in `version.go`, if necessary + - Bump ABCI protocol version in `version.go`, if necessary +1. Open a PR with these changes against the backport branch. +1. Once these changes have landed on the backport branch, be sure to pull them back down locally. +2. Once you have the changes locally, create the new tag, specifying a name and a tag "message": + `git tag -a v0.35.0-rc0 -m "Release Candidate v0.35.0-rc0` +3. Push the tag back up to origin: + `git push origin v0.35.0-rc0` + Now the tag should be available on the repo's releases page. +4. Future RCs will continue to be built off of this branch. + +Note that this process should only be used for "true" RCs-- +release candidates that, if successful, will be the next release. +For more experimental "RCs," create a new, short-lived branch and tag that instead. + +#### Major release This major release process assumes that this release was preceded by release candidates. -If there were no release candidates, and you'd like to cut a major release directly from master, see below. +If there were no release candidates, begin by creating a backport branch, as described above. -1. Start on the latest RC branch (`RCx/vX.X.0`). -2. Run integration tests. -3. Branch off of the RC branch (`git checkout -b release-prep`) and prepare the release: +1. Start on the backport branch (e.g. `v0.35.x`) +2. Run integration tests and the e2e nightlies. +3. Prepare the release: - "Squash" changes from the changelog entries for the RCs into a single entry, and add all changes included in `CHANGELOG_PENDING.md`. (Squashing includes both combining all entries, as well as removing or simplifying @@ -249,57 +329,24 @@ If there were no release candidates, and you'd like to cut a major release direc - Bump P2P and block protocol versions in `version.go`, if necessary - Bump ABCI protocol version in `version.go`, if necessary - Add any release notes you would like to be added to the body of the release to `release_notes.md`. -4. Open a PR with these changes against the RC branch (`RCx/vX.X.0`). -5. Once these changes are on the RC branch, branch off of the RC branch again to create a release branch: - - `git checkout RCx/vX.X.0` - - `git checkout -b release/vX.X.0` -6. Push a tag with prepared release details. This will trigger the actual release `vX.X.0`. - - `git tag -a vX.X.0 -m 'Release vX.X.0'` - - `git push origin vX.X.0` +4. Open a PR with these changes against the backport branch. +5. Once these changes are on the backport branch, push a tag with prepared release details. + This will trigger the actual release `v0.35.0`. + - `git tag -a v0.35.0 -m 'Release v0.35.0'` + - `git push origin v0.35.0` 7. Make sure that `master` is updated with the latest `CHANGELOG.md`, `CHANGELOG_PENDING.md`, and `UPGRADING.md`. -8. Create the long-lived minor release branch `RC0/vX.X.1` for the next point release on this - new major release series. -##### Major Release (from `master`) - -1. Start on `master` -2. Run integration tests (see `test_integrations` in Makefile) -3. Prepare release in a pull request against `master` (to be squash merged): - - Copy `CHANGELOG_PENDING.md` to top of `CHANGELOG.md`; if this release - had release candidates, squash all the RC updates into one - - Run `python ./scripts/linkify_changelog.py CHANGELOG.md` to add links for - all issues - - Run `bash ./scripts/authors.sh` to get a list of authors since the latest - release, and add the github aliases of external contributors to the top of - the changelog. To lookup an alias from an email, try `bash ./scripts/authors.sh ` - - Reset the `CHANGELOG_PENDING.md` - - Bump TMVersionDefault version in `version.go` - - Bump P2P and block protocol versions in `version.go`, if necessary - - Bump ABCI protocol version in `version.go`, if necessary - - Make sure all significant breaking changes are covered in `UPGRADING.md` - - Add any release notes you would like to be added to the body of the release to `release_notes.md`. -4. Push a tag with prepared release details (this will trigger the release `vX.X.0`) - - `git tag -a vX.X.x -m 'Release vX.X.x'` - - `git push origin vX.X.x` -5. Update the `CHANGELOG.md` file on master with the releases changelog. -6. Delete any RC branches and tags for this release (if applicable) - -#### Minor Release (Point Releases) +#### Minor release (point releases) Minor releases are done differently from major releases: They are built off of long-lived backport branches, rather than from master. -Each release "line" (e.g. 0.34 or 0.33) has its own long-lived backport branch, and -the backport branches have names like `v0.34.x` or `v0.33.x` (literally, `x`; it is not a placeholder in this case). - As non-breaking changes land on `master`, they should also be backported (cherry-picked) to these backport branches. -We use Mergify's [backport feature](https://mergify.io/features/backports) to automatically backport to the needed branch. Depending on which backport branch you need to backport to there will be labels for them. To notify the bot to backport a pull request, mark the pull request with the label `backport-to-`. Once the original pull request is merged, the bot will try to cherry-pick the pull request to the backport branch. If the bot fails to backport, it will open a pull request. The author of the original pull request is responsible for solving the conflicts and merging the pull request. - Minor releases don't have release candidates by default, although any tricky changes may merit a release candidate. To create a minor release: -1. Checkout the long-lived backport branch: `git checkout vX.X.x` -2. Run integration tests: `make test_integrations` +1. Checkout the long-lived backport branch: `git checkout v0.35.x` +2. Run integration tests (`make test_integrations`) and the nightlies. 3. Check out a new branch and prepare the release: - Copy `CHANGELOG_PENDING.md` to top of `CHANGELOG.md` - Run `python ./scripts/linkify_changelog.py CHANGELOG.md` to add links for all issues @@ -309,34 +356,14 @@ To create a minor release: (Note that ABCI follows semver, and that ABCI versions are the only versions which can change during minor releases, and only field additions are valid minor changes.) - Add any release notes you would like to be added to the body of the release to `release_notes.md`. -4. Open a PR with these changes that will land them back on `vX.X.x` +4. Open a PR with these changes that will land them back on `v0.35.x` 5. Once this change has landed on the backport branch, make sure to pull it locally, then push a tag. - - `git tag -a vX.X.x -m 'Release vX.X.x'` - - `git push origin vX.X.x` + - `git tag -a v0.35.1 -m 'Release v0.35.1'` + - `git push origin v0.35.1` 6. Create a pull request back to master with the CHANGELOG & version changes from the latest release. - Remove all `R:minor` labels from the pull requests that were included in the release. - Do not merge the backport branch into master. -#### Release Candidates - -Before creating an official release, especially a major release, we may want to create a -release candidate (RC) for our friends and partners to test out. We use git tags to -create RCs, and we build them off of RC branches. RC branches typically have names formatted -like `RCX/vX.X.X` (or, concretely, `RC0/v0.34.0`), while the tags themselves follow -the "standard" release naming conventions, with `-rcX` at the end (`vX.X.X-rcX`). - -(Note that branches and tags _cannot_ have the same names, so it's important that these branches -have distinct names from the tags/release names.) - -1. Start from the RC branch (e.g. `RC0/v0.34.0`). -2. Create the new tag, specifying a name and a tag "message": - `git tag -a v0.34.0-rc0 -m "Release Candidate v0.34.0-rc0` -3. Push the tag back up to origin: - `git push origin v0.34.0-rc4` - Now the tag should be available on the repo's releases page. -4. Create a new release candidate branch for any possible updates to the RC: - `git checkout -b RC1/v0.34.0; git push origin RC1/v0.34.0` - ## Testing ### Unit tests From 267aac2e9095cbae113ed6ee55e34d4bf8367ae8 Mon Sep 17 00:00:00 2001 From: Tess Rinearson Date: Tue, 17 Aug 2021 15:45:14 +0200 Subject: [PATCH 06/37] changelog_pending: add missing item (#6829) --- CHANGELOG_PENDING.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/CHANGELOG_PENDING.md b/CHANGELOG_PENDING.md index e8d687f1c..28a983b06 100644 --- a/CHANGELOG_PENDING.md +++ b/CHANGELOG_PENDING.md @@ -157,8 +157,9 @@ Friendly reminder: We have a [bug bounty program](https://hackerone.com/tendermi - [blockchain/v1] [\#5701](https://github.com/tendermint/tendermint/pull/5701) Handle peers without blocks (@melekes) - [blockchain/v1] \#5711 Fix deadlock (@melekes) - [evidence] \#6375 Fix bug with inconsistent LightClientAttackEvidence hashing (cmwaters) -- [rpc] \#6507 fix RPC client doesn't handle url's without ports (@JayT106) +- [rpc] \#6507 Ensure RPC client can handle URLs without ports (@JayT106) - [statesync] \#6463 Adds Reverse Sync feature to fetch historical light blocks after state sync in order to verify any evidence (@cmwaters) - [fastsync] \#6590 Update the metrics during fast-sync (@JayT106) - [gitignore] \#6668 Fix gitignore of abci-cli (@tanyabouman) -- [light] \#6687 Fix bug with incorrecly handled contexts in the light client (@cmwaters) +- [light] \#6687 Fix bug with incorrectly handled contexts in the light client (@cmwaters) +- [privval] \#6748 Fix vote timestamp to prevent chain halt (@JayT106) \ No newline at end of file From 0ed3ba6279c4ee328934753b6614753adb8be43f Mon Sep 17 00:00:00 2001 From: Tess Rinearson Date: Wed, 18 Aug 2021 11:55:57 +0200 Subject: [PATCH 07/37] changelog: update to reflect 0.34.12 release (#6833) --- CHANGELOG.md | 53 +++++++++++++++++++++++++++++++------------- CHANGELOG_PENDING.md | 6 +---- 2 files changed, 38 insertions(+), 21 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cbda6a678..7a4d706e0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,27 @@ Friendly reminder, we have a [bug bounty program](https://hackerone.com/tendermint). +## v0.34.12 + +*August 17, 2021* + +Special thanks to external contributors on this release: @JayT106. + +### FEATURES + +- [rpc] [\#6717](https://github.com/tendermint/tendermint/pull/6717) introduce + `/genesis_chunked` rpc endpoint for handling large genesis files by chunking them (@tychoish) + +### IMPROVEMENTS + +- [rpc] [\#6825](https://github.com/tendermint/tendermint/issues/6825) Remove egregious INFO log from `ABCI#Query` RPC. (@alexanderbez) + +### BUG FIXES + +- [light] [\#6685](https://github.com/tendermint/tendermint/pull/6685) fix bug + with incorrectly handling contexts that would occasionally freeze state sync. (@cmwaters) +- [privval] [\#6748](https://github.com/tendermint/tendermint/issues/6748) Fix vote timestamp to prevent chain halt (@JayT106) + ## v0.34.11 *June 18, 2021* @@ -28,9 +49,9 @@ adding two new parameters to the state sync config. *April 14, 2021* -This release fixes a bug where peers would sometimes try to send messages +This release fixes a bug where peers would sometimes try to send messages on incorrect channels. Special thanks to our friends at Oasis Labs for surfacing -this issue! +this issue! - [p2p/node] [\#6339](https://github.com/tendermint/tendermint/issues/6339) Fix bug with using custom channels (@cmwaters) - [light] [\#6346](https://github.com/tendermint/tendermint/issues/6346) Correctly handle too high errors to improve client robustness (@cmwaters) @@ -39,7 +60,7 @@ this issue! *April 8, 2021* -This release fixes a moderate severity security issue, Security Advisory Alderfly, +This release fixes a moderate severity security issue, Security Advisory Alderfly, which impacts all networks that rely on Tendermint light clients. Further details will be released once networks have upgraded. @@ -112,7 +133,7 @@ shout-out to @marbar3778 for diagnosing it quickly. ## v0.34.6 -*February 18, 2021* +*February 18, 2021* _Tendermint Core v0.34.5 and v0.34.6 have been recalled due to release tooling problems._ @@ -120,9 +141,9 @@ _Tendermint Core v0.34.5 and v0.34.6 have been recalled due to release tooling p *February 11, 2021* -This release includes a fix for a memory leak in the evidence reactor (see #6068, below). -All Tendermint clients are recommended to upgrade. -Thank you to our friends at Crypto.com for the initial report of this memory leak! +This release includes a fix for a memory leak in the evidence reactor (see #6068, below). +All Tendermint clients are recommended to upgrade. +Thank you to our friends at Crypto.com for the initial report of this memory leak! Special thanks to other external contributors on this release: @yayajacky, @odidev, @laniehei, and @c29r3! @@ -132,17 +153,17 @@ Special thanks to other external contributors on this release: @yayajacky, @odid - [light] [\#6026](https://github.com/tendermint/tendermint/pull/6026) Fix a bug when height isn't provided for the rpc calls: `/commit` and `/validators` (@cmwaters) - [evidence] [\#6068](https://github.com/tendermint/tendermint/pull/6068) Terminate broadcastEvidenceRoutine when peer is stopped (@melekes) -## v0.34.3 +## v0.34.3 *January 19, 2021* -This release includes a fix for a high-severity security vulnerability, +This release includes a fix for a high-severity security vulnerability, a DoS-vector that impacted Tendermint Core v0.34.0-v0.34.2. For more details, see -[Security Advisory Mulberry](https://github.com/tendermint/tendermint/security/advisories/GHSA-p658-8693-mhvg) -or https://nvd.nist.gov/vuln/detail/CVE-2021-21271. +[Security Advisory Mulberry](https://github.com/tendermint/tendermint/security/advisories/GHSA-p658-8693-mhvg) +or https://nvd.nist.gov/vuln/detail/CVE-2021-21271. Tendermint Core v0.34.3 also updates GoGo Protobuf to 1.3.2 in order to pick up the fix for -https://nvd.nist.gov/vuln/detail/CVE-2021-3121. +https://nvd.nist.gov/vuln/detail/CVE-2021-3121. ### BUG FIXES @@ -234,14 +255,14 @@ Special thanks to external contributors on this release: @james-ray, @fedekunze, - [blockchain] [\#4637](https://github.com/tendermint/tendermint/pull/4637) Migrate blockchain reactor(s) to Protobuf encoding (@marbar3778) - [evidence] [\#4949](https://github.com/tendermint/tendermint/pull/4949) Migrate evidence reactor to Protobuf encoding (@marbar3778) - [mempool] [\#4940](https://github.com/tendermint/tendermint/pull/4940) Migrate mempool from to Protobuf encoding (@marbar3778) - - [mempool] [\#5321](https://github.com/tendermint/tendermint/pull/5321) Batch transactions when broadcasting them to peers (@melekes) + - [mempool] [\#5321](https://github.com/tendermint/tendermint/pull/5321) Batch transactions when broadcasting them to peers (@melekes) - `MaxBatchBytes` new config setting defines the max size of one batch. - [p2p/pex] [\#4973](https://github.com/tendermint/tendermint/pull/4973) Migrate `p2p/pex` reactor to Protobuf encoding (@marbar3778) - [statesync] [\#4943](https://github.com/tendermint/tendermint/pull/4943) Migrate state sync reactor to Protobuf encoding (@marbar3778) - Blockchain Protocol - - [evidence] [\#4725](https://github.com/tendermint/tendermint/pull/4725) Remove `Pubkey` from `DuplicateVoteEvidence` (@marbar3778) + - [evidence] [\#4725](https://github.com/tendermint/tendermint/pull/4725) Remove `Pubkey` from `DuplicateVoteEvidence` (@marbar3778) - [evidence] [\#5499](https://github.com/tendermint/tendermint/pull/5449) Cap evidence to a maximum number of bytes (supercedes [\#4780](https://github.com/tendermint/tendermint/pull/4780)) (@cmwaters) - [merkle] [\#5193](https://github.com/tendermint/tendermint/pull/5193) Header hashes are no longer empty for empty inputs, notably `DataHash`, `EvidenceHash`, and `LastResultsHash` (@erikgrinaker) - [state] [\#4845](https://github.com/tendermint/tendermint/pull/4845) Include `GasWanted` and `GasUsed` into `LastResultsHash` (@melekes) @@ -300,7 +321,7 @@ Special thanks to external contributors on this release: @james-ray, @fedekunze, - [types] [\#4852](https://github.com/tendermint/tendermint/pull/4852) Vote & Proposal `SignBytes` is now func `VoteSignBytes` & `ProposalSignBytes` (@marbar3778) - [types] [\#4798](https://github.com/tendermint/tendermint/pull/4798) Simplify `VerifyCommitTrusting` func + remove extra validation (@melekes) - [types] [\#4845](https://github.com/tendermint/tendermint/pull/4845) Remove `ABCIResult` (@melekes) - - [types] [\#5029](https://github.com/tendermint/tendermint/pull/5029) Rename all values from `PartsHeader` to `PartSetHeader` to have consistency (@marbar3778) + - [types] [\#5029](https://github.com/tendermint/tendermint/pull/5029) Rename all values from `PartsHeader` to `PartSetHeader` to have consistency (@marbar3778) - [types] [\#4939](https://github.com/tendermint/tendermint/pull/4939) `Total` in `Parts` & `PartSetHeader` has been changed from a `int` to a `uint32` (@marbar3778) - [types] [\#4939](https://github.com/tendermint/tendermint/pull/4939) Vote: `ValidatorIndex` & `Round` are now `int32` (@marbar3778) - [types] [\#4939](https://github.com/tendermint/tendermint/pull/4939) Proposal: `POLRound` & `Round` are now `int32` (@marbar3778) @@ -338,7 +359,7 @@ Special thanks to external contributors on this release: @james-ray, @fedekunze, - [evidence] [\#4722](https://github.com/tendermint/tendermint/pull/4722) Consolidate evidence store and pool types to improve evidence DB (@cmwaters) - [evidence] [\#4839](https://github.com/tendermint/tendermint/pull/4839) Reject duplicate evidence from being proposed (@cmwaters) - [evidence] [\#5219](https://github.com/tendermint/tendermint/pull/5219) Change the source of evidence time to block time (@cmwaters) -- [libs] [\#5126](https://github.com/tendermint/tendermint/pull/5126) Add a sync package which wraps sync.(RW)Mutex & deadlock.(RW)Mutex and use a build flag (deadlock) in order to enable deadlock checking (@marbar3778) +- [libs] [\#5126](https://github.com/tendermint/tendermint/pull/5126) Add a sync package which wraps sync.(RW)Mutex & deadlock.(RW)Mutex and use a build flag (deadlock) in order to enable deadlock checking (@marbar3778) - [light] [\#4935](https://github.com/tendermint/tendermint/pull/4935) Fetch and compare a new header with witnesses in parallel (@melekes) - [light] [\#4929](https://github.com/tendermint/tendermint/pull/4929) Compare header with witnesses only when doing bisection (@melekes) - [light] [\#4916](https://github.com/tendermint/tendermint/pull/4916) Validate basic for inbound validator sets and headers before further processing them (@cmwaters) diff --git a/CHANGELOG_PENDING.md b/CHANGELOG_PENDING.md index 28a983b06..bb5221bb5 100644 --- a/CHANGELOG_PENDING.md +++ b/CHANGELOG_PENDING.md @@ -24,7 +24,6 @@ Friendly reminder: We have a [bug bounty program](https://hackerone.com/tendermi - [fastsync/rpc] \#6620 Add TotalSyncedTime & RemainingTime to SyncInfo in /status RPC (@JayT106) - [rpc/grpc] \#6725 Mark gRPC in the RPC layer as deprecated. - [blockchain/v2] \#6730 Fast Sync v2 is deprecated, please use v0 - - [rpc] Add genesis_chunked method to support paginated and parallel fetching of large genesis documents. - [rpc] \#6820 Update RPC methods to reflect changes in the p2p layer, disabling support for `UnsafeDialPeers` and `UnsafeDialPeers` when used with the new p2p layer, and changing the response format of the peer list in `NetInfo` for all users. - Apps @@ -109,7 +108,6 @@ Friendly reminder: We have a [bug bounty program](https://hackerone.com/tendermi ### IMPROVEMENTS -- [rpc] \#6825 Remove egregious INFO log from `ABCI#Query` RPC. (@alexanderbez) - [libs/log] Console log formatting changes as a result of \#6534 and \#6589. (@tychoish) - [statesync] \#6566 Allow state sync fetchers and request timeout to be configurable. (@alexanderbez) - [types] \#6478 Add `block_id` to `newblock` event (@jeebster) @@ -160,6 +158,4 @@ Friendly reminder: We have a [bug bounty program](https://hackerone.com/tendermi - [rpc] \#6507 Ensure RPC client can handle URLs without ports (@JayT106) - [statesync] \#6463 Adds Reverse Sync feature to fetch historical light blocks after state sync in order to verify any evidence (@cmwaters) - [fastsync] \#6590 Update the metrics during fast-sync (@JayT106) -- [gitignore] \#6668 Fix gitignore of abci-cli (@tanyabouman) -- [light] \#6687 Fix bug with incorrectly handled contexts in the light client (@cmwaters) -- [privval] \#6748 Fix vote timestamp to prevent chain halt (@JayT106) \ No newline at end of file +- [gitignore] \#6668 Fix gitignore of abci-cli (@tanyabouman) \ No newline at end of file From afb6af8bc30ea1013f575826396a781230f8b1d3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 18 Aug 2021 08:02:16 -0400 Subject: [PATCH 08/37] build(deps): Bump github.com/golangci/golangci-lint (#6837) Bumps [github.com/golangci/golangci-lint](https://github.com/golangci/golangci-lint) from 1.41.1 to 1.42.0. - [Release notes](https://github.com/golangci/golangci-lint/releases) - [Changelog](https://github.com/golangci/golangci-lint/blob/master/CHANGELOG.md) - [Commits](https://github.com/golangci/golangci-lint/compare/v1.41.1...v1.42.0) --- updated-dependencies: - dependency-name: github.com/golangci/golangci-lint dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 6 ++-- go.sum | 94 +++++++++++++++++++++++++++++++--------------------------- 2 files changed, 54 insertions(+), 46 deletions(-) diff --git a/go.mod b/go.mod index 84c6f43ac..35272bea1 100644 --- a/go.mod +++ b/go.mod @@ -13,7 +13,7 @@ require ( github.com/go-kit/kit v0.11.0 github.com/gogo/protobuf v1.3.2 github.com/golang/protobuf v1.5.2 - github.com/golangci/golangci-lint v1.41.1 + github.com/golangci/golangci-lint v1.42.0 github.com/google/orderedcode v0.0.1 github.com/google/uuid v1.3.0 github.com/gorilla/websocket v1.4.2 @@ -35,8 +35,8 @@ require ( github.com/stretchr/testify v1.7.0 github.com/tendermint/tm-db v0.6.4 github.com/vektra/mockery/v2 v2.9.0 - golang.org/x/crypto v0.0.0-20210314154223-e6e6c4f2bb5b - golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4 + golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a + golang.org/x/net v0.0.0-20210428140749-89ef3d95e781 google.golang.org/grpc v1.40.0 gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b // indirect pgregory.net/rapid v0.4.7 diff --git a/go.sum b/go.sum index 81abd8a00..74f189d5c 100644 --- a/go.sum +++ b/go.sum @@ -44,6 +44,8 @@ cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RX 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= +github.com/Antonboom/errname v0.1.3 h1:qKV8gSzPzBqrG/q0dgraZXJCymWt6KuD9+Y7K7xtzN8= +github.com/Antonboom/errname v0.1.3/go.mod h1:jRXo3m0E0EuCnK3wbsSVH3X55Z4iTDLl6ZfCxwFj4TM= github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78 h1:w+iIsaOQNcT7OZ575w+acHgRric5iCyQh+xv+KJ4HB8= github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= @@ -73,7 +75,7 @@ github.com/OpenPeeDeeP/depguard v1.0.1 h1:VlW4R6jmBIv3/u1JNlawEvJMM4J+dPORPaZasQ 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 v0.0.0-20190523213315-cbe66965904d/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg= +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.53 h1:J6Y/52yX10Xc5JjXmGtWoSSxs3mZnGSaq37xZZh7Yig= @@ -176,8 +178,8 @@ github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:ma 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/daixiang0/gci v0.2.8 h1:1mrIGMBQsBu0P7j7m1M8Lb+ZeZxsZL+jyGX4YoMJJpg= -github.com/daixiang0/gci v0.2.8/go.mod h1:+4dZ7TISfSmqfAGv59ePaHfNzgGtIkHAhhdKggP1JAc= +github.com/daixiang0/gci v0.2.9 h1:iwJvwQpBZmMg31w+QQ6jsyZ54KEATn6/nfARbBNW294= +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= @@ -257,7 +259,7 @@ github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vb 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/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= -github.com/go-ole/go-ole v1.2.4/go.mod h1:XCwSNxSkXRo4vlyPy93sltvi/qJq0jqQhjqQNIwKuxM= +github.com/go-ole/go-ole v1.2.5/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= @@ -289,8 +291,8 @@ github.com/go-zookeeper/zk v1.0.2/go.mod h1:nOB03cncLtlp4t+UAkGSV+9beXP/akpekBwL github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y= 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.0 h1:MSdYClljsF3PbENUUEx85nkWfJSGfzYI9yEBZOJz6CY= -github.com/gofrs/flock v0.8.0/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU= +github.com/gofrs/flock v0.8.1 h1:+gYjHKf32LDeiEEFhQaotPbLuUXjY5ZqxKgXy7n59aw= +github.com/gofrs/flock v0.8.1/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU= github.com/gogo/protobuf v1.1.1/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= @@ -341,8 +343,8 @@ github.com/golangci/go-misc v0.0.0-20180628070357-927a3d87b613 h1:9kfjN3AdxcbsZB github.com/golangci/go-misc v0.0.0-20180628070357-927a3d87b613/go.mod h1:SyvUF2NxV+sN8upjjeVYr5W7tyxaT1JVtvhKhOn2ii8= github.com/golangci/gofmt v0.0.0-20190930125516-244bba706f1a h1:iR3fYXUjHCR97qWS8ch1y9zPNsgXThGwjKPrYfqMPks= github.com/golangci/gofmt v0.0.0-20190930125516-244bba706f1a/go.mod h1:9qCChq59u/eW8im404Q2WWTrnBUQKjpNYKMbU4M7EFU= -github.com/golangci/golangci-lint v1.41.1 h1:KH28pTSqRu6DTXIAANl1sPXNCmqg4VEH21z6G9Wj4SM= -github.com/golangci/golangci-lint v1.41.1/go.mod h1:LPtcY3aAAU8wydHrKpnanx9Og8K/cblZSyGmI5CJZUk= +github.com/golangci/golangci-lint v1.42.0 h1:hqf1zo6zY3GKGjjBk3ttdH22tGwF6ZRpk6j6xyJmE8I= +github.com/golangci/golangci-lint v1.42.0/go.mod h1:wgkGQnU9lOUFvTFo5QBSOvaSSddEV21Z1zYkJSbppZA= github.com/golangci/lint-1 v0.0.0-20191013205115-297bf364a8e0 h1:MfyDlzVjl1hoaPzPD4Gpb/QgoRfSBR0jdhwGyAWwMSA= github.com/golangci/lint-1 v0.0.0-20191013205115-297bf364a8e0/go.mod h1:66R6K6P6VWk9I95jvqGxkqJxVWGFy9XlDwLwVz1RCFg= github.com/golangci/maligned v0.0.0-20180506175553-b1d89398deca h1:kNY3/svz5T29MYHubXix4aDDuE3RWHkPvopM/EDv/MA= @@ -394,6 +396,7 @@ github.com/google/uuid v0.0.0-20161128191214-064e2069ce9c/go.mod h1:TIyPZe4Mgqvf 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.2.0/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= @@ -549,8 +552,8 @@ github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 h1:SOEGU9fKiNWd/HOJuq github.com/lann/builder v0.0.0-20180802200727-47ae307949d0/go.mod h1:dXGbAdH5GtBTC4WfIxhKZfyBF/HBFgRZSWwZ9g/He9o= github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 h1:P6pPBnrTSX3DEVR4fDembhRWSsG5rVo6hYhAB/ADZrk= github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0/go.mod h1:vmVJ0l/dxyfGW6FmdpVm2joNMFikkuWg0EoCKLGUMNw= -github.com/ldez/gomoddirectives v0.2.1 h1:9pAcW9KRZW7HQjFwbozNvFMcNVwdCBufU7os5QUwLIY= -github.com/ldez/gomoddirectives v0.2.1/go.mod h1:sGicqkRgBOg//JfpXwkB9Hj0X5RyJ7mlACM5B9f6Me4= +github.com/ldez/gomoddirectives v0.2.2 h1:p9/sXuNFArS2RLc+UpYZSI4KQwGMEDWC/LbtF5OPFVg= +github.com/ldez/gomoddirectives v0.2.2/go.mod h1:cpgBogWITnCfRq2qGoDkKMEVSaarhdBr6g8G04uz6d0= github.com/ldez/tagliatelle v0.2.0 h1:693V8Bf1NdShJ8eu/s84QySA0J2VWBanVBa2WwXD/Wk= github.com/ldez/tagliatelle v0.2.0/go.mod h1:8s6WJQwEYHbKZDsp/LjArytKOG8qaMrKQQ3mFukHs88= github.com/leodido/go-urn v1.1.0/go.mod h1:+cyI34gQWZcE1eQU7NVgKkkzdXDQHr1dBMtdAPozLkw= @@ -598,8 +601,8 @@ github.com/mbilski/exhaustivestruct v1.2.0 h1:wCBmUnSYufAHO6J4AVWY6ff+oxWxsVFrwg github.com/mbilski/exhaustivestruct v1.2.0/go.mod h1:OeTBVxQWoEmB2J2JCHmXWPJ0aksxSUOUy+nvtVEfzXc= github.com/mgechev/dots v0.0.0-20190921121421-c36f7dcfbb81 h1:QASJXOGm2RZ5Ardbc86qNFvby9AqkLDibfChMtAg5QM= github.com/mgechev/dots v0.0.0-20190921121421-c36f7dcfbb81/go.mod h1:KQ7+USdGKfpPjXk4Ga+5XxQM4Lm4e3gAogrreFAYpOg= -github.com/mgechev/revive v1.0.7 h1:5kEWTY/W5a0Eiqnkn2BAWsRZpxbs1ft15PsyNC7Rml8= -github.com/mgechev/revive v1.0.7/go.mod h1:vuE5ox/4L/HDd63MCcCk3H6wTLQ6XXezRphJ8cJJOxY= +github.com/mgechev/revive v1.1.0 h1:TvabpsolbtlzZTyJcgMRN38MHrgi8C0DhmGE5dhscGY= +github.com/mgechev/revive v1.1.0/go.mod h1:PKqk4L74K6wVNwY2b6fr+9Qqr/3hIsHVfZCJdbvozrY= 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= @@ -632,7 +635,7 @@ github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwd github.com/moricho/tparallel v0.2.1 h1:95FytivzT6rYzdJLdtfn6m1bfFJylOJK41+lgv/EHf4= 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-20210209181001-cf43108d6880/go.mod h1:FUqVoUPHSEdDR0MnFM3Dh8AU0pZHLXUD127SAJGER/s= +github.com/mozilla/tls-observatory v0.0.0-20210609171429-7bc42856d2e5/go.mod h1:FUqVoUPHSEdDR0MnFM3Dh8AU0pZHLXUD127SAJGER/s= 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= @@ -650,8 +653,8 @@ github.com/nbutton23/zxcvbn-go v0.0.0-20210217022336-fa2cb2858354 h1:4kuARK6Y6Fx 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.1.0 h1:kVlMw8h2LHPMGUVqUj6230oQjjTMFjwcZrnkhXzFfl8= -github.com/nishanths/exhaustive v0.1.0/go.mod h1:S1j9110vxV1ECdCudXRkeMnFQ/DQk9ajLT0Uf2MYZQQ= +github.com/nishanths/exhaustive v0.2.3 h1:+ANTMqRNrqwInnP9aszg/0jDo+zbXa4x66U19Bx/oTk= +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 h1:1TXtjmy4f3YCFjTxRd8zcFHOmoUir+gp0ESzjFzG2sw= github.com/nishanths/predeclared v0.2.1/go.mod h1:HvkGJcA3naj4lOwnFXFDkFxVtSqQMB9sbB1usJ+xjQE= @@ -671,14 +674,15 @@ github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+W 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/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY= -github.com/onsi/ginkgo v1.16.1 h1:foqVmeWDD6yYpK+Yz3fHyNIxFYNxswxqNFjSKe+vI54= -github.com/onsi/ginkgo v1.16.1/go.mod h1:CObGmKUOKaSC0RjmoAK7tKyn4Azo5P2IWuoMnvwxz1E= +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/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= -github.com/onsi/gomega v1.11.0 h1:+CqWgvj0OZycCaqclBD1pxKHAU+tOkHmQIWvDHq2aug= -github.com/onsi/gomega v1.11.0/go.mod h1:azGKhqFUon9Vuj0YmTfLSmx0FUwqXYSTl5re8lQLTUg= +github.com/onsi/gomega v1.13.0 h1:7lLHu94wT9Ij0o6EWWclhu0aOh32VxhkwEJvzuWPeak= +github.com/onsi/gomega v1.13.0/go.mod h1:lRk9szgn8TxENtWd0Tp4c3wjlRfMTMH27I+3Je41yGY= github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk= github.com/opencontainers/go-digest v1.0.0-rc1 h1:WzifXhOVOEOuFYOJAW6aQqW0TooG2iki3E3Ii+WN7gQ= github.com/opencontainers/go-digest v1.0.0-rc1/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= @@ -769,8 +773,8 @@ github.com/rs/zerolog v1.23.0 h1:UskrK+saS9P9Y789yNNulYKdARjPZuS35B8gJF2x60g= github.com/rs/zerolog v1.23.0/go.mod h1:6c7hFfxPOy7TacJc4Fcdi24/J0NKYGzjG8FWRI916Qo= 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/ryancurrah/gomodguard v1.2.2 h1:ZJQeYHZ2kaJpojoQBaGqpsn5g7GMcePY36uUGW1umbs= -github.com/ryancurrah/gomodguard v1.2.2/go.mod h1:tpI+C/nzvfUR3bF28b5QHpTn/jM/zlGniI++6ZlIWeE= +github.com/ryancurrah/gomodguard v1.2.3 h1:ww2fsjqocGCAFamzvv/b8IsRduuHHeK2MHTcTxZTQX8= +github.com/ryancurrah/gomodguard v1.2.3/go.mod h1:rYbA/4Tg5c54mV1sv4sQTP5WOPBcoLtnBZ7/TEhXAbg= github.com/ryanrolds/sqlclosecheck v0.3.0 h1:AZx+Bixh8zdUBxUA1NxbxVAS78vTPq4rCb8OUZI9xFw= 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= @@ -779,12 +783,12 @@ github.com/sanposhiho/wastedassign/v2 v2.0.6/go.mod h1:KyZ0MWTwxxBmfwn33zh3k1dms 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/securego/gosec/v2 v2.8.0 h1:iHg9cVmHWf5n6/ijUJ4F10h5bKlNtvXmcWzRw0lxiKE= -github.com/securego/gosec/v2 v2.8.0/go.mod h1:hJZ6NT5TqoY+jmOsaxAV4cXoEdrMRLVaNPnSpUCvCZs= +github.com/securego/gosec/v2 v2.8.1 h1:Tyy/nsH39TYCOkqf5HAgRE+7B5D8sHDwPdXRgFWokh8= +github.com/securego/gosec/v2 v2.8.1/go.mod h1:pUmsq6+VyFEElJMUX+QB3p3LWNHXg1R3xh2ssVJPs8Q= github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= github.com/shazow/go-diff v0.0.0-20160112020656-b6b7b6733b8c h1:W65qqJCIOVP4jpqPQ0YvHYKwcMEMVWIzWC5iNQQfBTU= github.com/shazow/go-diff v0.0.0-20160112020656-b6b7b6733b8c/go.mod h1:/PevMnwAxekIXwN8qQyfc5gl2NlkB3CQlkizAbOkeBs= -github.com/shirou/gopsutil/v3 v3.21.5/go.mod h1:ghfMypLDrFSWN2c9cDYFLHyynQ+QUht0cv/18ZqVczw= +github.com/shirou/gopsutil/v3 v3.21.7/go.mod h1:RGl11Y7XMTQPmHh8F0ayC6haKNBgH4PXMJuTAcMOlz4= 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= @@ -819,7 +823,6 @@ github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkU 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.3/go.mod h1:pGADOWyqRD/YMrPZigI/zbliZ2wVD/23d+is3pSWzOo= 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= @@ -862,18 +865,18 @@ github.com/tecbot/gorocksdb v0.0.0-20191217155057-f0fad39f321c h1:g+WoO5jjkqGAzH github.com/tecbot/gorocksdb v0.0.0-20191217155057-f0fad39f321c/go.mod h1:ahpPrc7HpcfEWDQRZEmnXMzHY03mLDYMCxeDzy46i+8= github.com/tendermint/tm-db v0.6.4 h1:3N2jlnYQkXNQclQwd/eKV/NzlqPlfK21cpRRIx80XXQ= github.com/tendermint/tm-db v0.6.4/go.mod h1:dptYhIpJ2M5kUuenLr+Yyf3zQOv1SgBZcl8/BmWlMBw= -github.com/tetafro/godot v1.4.7 h1:zBaoSY4JRVVz33y/qnODsdaKj2yAaMr91HCbqHCifVc= -github.com/tetafro/godot v1.4.7/go.mod h1:LR3CJpxDVGlYOWn3ZZg1PgNZdTUvzsZWu8xaEohUpn8= +github.com/tetafro/godot v1.4.8 h1:rhuUH+tBrx24yVAr6Ox3/UxcsiUPPJcGhinfLdbdew0= +github.com/tetafro/godot v1.4.8/go.mod h1:LR3CJpxDVGlYOWn3ZZg1PgNZdTUvzsZWu8xaEohUpn8= github.com/timakin/bodyclose v0.0.0-20200424151742-cb6215831a94 h1:ig99OeTyDwQWhPe2iw9lwfQVF1KB3Q4fpP3X7/2VBG8= 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.4/go.mod h1:Cl2c8ZRWfHD5IrfHo9VN+FX9kCFjIOyVklgXycLB6ek= -github.com/tklauser/numcpus v0.2.1/go.mod h1:9aU+wOc6WjUIZEwWMP62PL/41d65P+iks1gBkr4QyP8= +github.com/tklauser/go-sysconf v0.3.7/go.mod h1:JZIdXh4RmBvZDBZ41ld2bGxRV3n4daiiqA3skYhAoQ4= +github.com/tklauser/numcpus v0.2.3/go.mod h1:vpEPS/JC+oZGGQ/My/vJnNsvMDQL6PwOqt8dsCw5j+E= 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.1.0 h1:LTzwrYlgBUwi9JldazhbJN84fN9nS2UNGrZIo2syqxE= -github.com/tomarrell/wrapcheck/v2 v2.1.0/go.mod h1:crK5eI4RGSUrb9duDTQ5GqcukbKZvi85vX6nbhsBAeI= +github.com/tomarrell/wrapcheck/v2 v2.3.0 h1:i3DNjtyyL1xwaBQOsPPk8LAcpayWfQv2rxNi9b/eEx4= +github.com/tomarrell/wrapcheck/v2 v2.3.0/go.mod h1:aF5rnkdtqNWP/gC7vPUO5pKsB0Oac2FDTQP4F+dpZMU= github.com/tomasen/realip v0.0.0-20180522021738-f0c99a92ddce/go.mod h1:o8v6yHRoik09Xen7gje4m9ERNah1d1PPsVq1VEx9vE4= github.com/tommy-muehle/go-mnd/v2 v2.4.0 h1:1t0f8Uiaq+fqKteUR4N9Umr6E99R+lDnLnq7PwX2PPE= github.com/tommy-muehle/go-mnd/v2 v2.4.0/go.mod h1:WsUAkMJMYww6l/ufffCD3m+P7LEvr8TnZn9lwVDlgzw= @@ -888,8 +891,8 @@ github.com/ultraware/whitespace v0.0.4 h1:If7Va4cM03mpgrNH9k49/VOicWpGoG70XPBFFO 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.1 h1:MoG2fZ0b/Eo7NXoIwCVFLG5JED3qgQz5/NEE+rOsjPs= -github.com/uudashr/gocognit v1.0.1/go.mod h1:j44Ayx2KW4+oB6SWMv8KsmHzZrOInQav7D3cQMJ5JUM= +github.com/uudashr/gocognit v1.0.5 h1:rrSex7oHr3/pPLQ0xoWq108XMU8s678FJcQ+aSfOHa4= +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.16.0/go.mod h1:YOKImeEosDdBPnxc0gy7INqi3m1zK6A+xl6TwOBhHCA= github.com/valyala/quicktemplate v1.6.3/go.mod h1:fwPzK2fHuYEODzJ9pkw0ipCPNHZ2tD5KW4lOuSdPKzY= @@ -958,8 +961,9 @@ golang.org/x/crypto v0.0.0-20200323165209-0ec3e9974c59/go.mod h1:LzIPMQfyMNhhGPh 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-20201221181555-eec23a3978ad/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= -golang.org/x/crypto v0.0.0-20210314154223-e6e6c4f2bb5b h1:wSOdpTq0/eI46Ez/LkDwIsAKA71YP2SRKBODiRWM0as= golang.org/x/crypto v0.0.0-20210314154223-e6e6c4f2bb5b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= +golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a h1:kr2P4QFmQr29mSLA43kwrOcgcReGTfbE9N577tCTuBc= +golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -1041,13 +1045,13 @@ golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81R 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-20201202161906-c7110b5ffcbb/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-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 h1:DzZ89McO9/gWPsQXS/FVKAlG02ZjaQ6AlZRBimEYOd0= +golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= 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= @@ -1095,6 +1099,7 @@ golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7w 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= @@ -1134,16 +1139,17 @@ golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7w 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-20210217105451-b926d437f341/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/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-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40 h1:JWgyZ1qgdTaF3N3oxC+MdTV7qvEEgHo3otj+HB5CM7Q= golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/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/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1 h1:v+OssWQX+hTHEmOBgwxdZxK4zHq3yOs8F9J7mk0PY8E= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= @@ -1153,8 +1159,9 @@ golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3 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 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 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= @@ -1240,7 +1247,6 @@ golang.org/x/tools v0.0.0-20200831203904-5a2aa26beb65/go.mod h1:Cj7w3i3Rnn0Xh82u 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-20201011145850-ed2f50202694/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= @@ -1257,8 +1263,10 @@ golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4f 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.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -golang.org/x/tools v0.1.3 h1:L69ShwSZEyCsLKoAxDKeMvLDZkumEe8gXUZAjab0tX8= 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 h1:ouewzE6p+/VEB31YYnTbEJdi8pFqKp4P4n85vwo3DHA= +golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= 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= @@ -1428,8 +1436,8 @@ honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWh 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.0 h1:ws8AfbgTX3oIczLPNPCu5166oBg9ST2vNs0rcht+mDE= -honnef.co/go/tools v0.2.0/go.mod h1:lPVVZ2BS5TfnjLyizF7o7hv7j9/L+8cZY2hLyjP9cGY= +honnef.co/go/tools v0.2.1 h1:/EPr//+UMMXwMTkXvCCoaJDq8cpjMO80Ou+L4PDo2mY= +honnef.co/go/tools v0.2.1/go.mod h1:lPVVZ2BS5TfnjLyizF7o7hv7j9/L+8cZY2hLyjP9cGY= mvdan.cc/gofumpt v0.1.1 h1:bi/1aS/5W00E2ny5q65w9SnKpWEF/UIOqDYBILpo9rA= mvdan.cc/gofumpt v0.1.1/go.mod h1:yXG1r1WqZVKWbVRtBWKWX9+CxGYfA51nSomhM0woR48= mvdan.cc/interfacer v0.0.0-20180901003855-c20040233aed h1:WX1yoOaKQfddO/mLzdV4wptyWgoH/6hwLs7QHTixo0I= From 69f6eee2e4c1f8ebb43bc1fc5e2b91434ece1c10 Mon Sep 17 00:00:00 2001 From: Tess Rinearson Date: Wed, 18 Aug 2021 14:06:13 +0200 Subject: [PATCH 09/37] changelog: linkify the 0.34.11 release notes (#6836) Co-authored-by: Aleksandr Bezobchuk --- CHANGELOG.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7a4d706e0..df18653c5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -33,17 +33,17 @@ adding two new parameters to the state sync config. ### BREAKING CHANGES - Apps - - [Version] \#6494 `TMCoreSemVer` is not required to be set as a ldflag any longer. + - [Version] [\#6494](https://github.com/tendermint/tendermint/pull/6494) `TMCoreSemVer` is not required to be set as a ldflag any longer. ### IMPROVEMENTS -- [statesync] \#6566 Allow state sync fetchers and request timeout to be configurable. (@alexanderbez) -- [statesync] \#6378 Retry requests for snapshots and add a minimum discovery time (5s) for new snapshots. (@tychoish) -- [statesync] \#6582 Increase chunk priority and add multiple retry chunk requests (@cmwaters) +- [statesync] [\#6566](https://github.com/tendermint/tendermint/pull/6566) Allow state sync fetchers and request timeout to be configurable. (@alexanderbez) +- [statesync] [\#6378](https://github.com/tendermint/tendermint/pull/6378) Retry requests for snapshots and add a minimum discovery time (5s) for new snapshots. (@tychoish) +- [statesync] [\#6582](https://github.com/tendermint/tendermint/pull/6582) Increase chunk priority and add multiple retry chunk requests (@cmwaters) ### BUG FIXES -- [evidence] \#6375 Fix bug with inconsistent LightClientAttackEvidence hashing (@cmwaters) +- [evidence] [\#6375](https://github.com/tendermint/tendermint/pull/6375) Fix bug with inconsistent LightClientAttackEvidence hashing (@cmwaters) ## v0.34.10 From a4cc8317da47ba92d1a97460a953d0e687a46cfb Mon Sep 17 00:00:00 2001 From: Sam Kleinman Date: Wed, 18 Aug 2021 14:33:28 -0400 Subject: [PATCH 10/37] e2e: avoid starting nodes from the future (#6835) --- test/e2e/runner/start.go | 31 ++++++++++++++++++++++++++++--- 1 file changed, 28 insertions(+), 3 deletions(-) diff --git a/test/e2e/runner/start.go b/test/e2e/runner/start.go index 70e496af3..ca3ebfcbd 100644 --- a/test/e2e/runner/start.go +++ b/test/e2e/runner/start.go @@ -25,6 +25,7 @@ func Start(testnet *e2e.Testnet) error { } return false }) + sort.SliceStable(nodeQueue, func(i, j int) bool { return nodeQueue[i].StartAt < nodeQueue[j].StartAt }) @@ -49,9 +50,15 @@ func Start(testnet *e2e.Testnet) error { logger.Info(fmt.Sprintf("Node %v up on http://127.0.0.1:%v", node.Name, node.ProxyPort)) } + networkHeight := testnet.InitialHeight + // Wait for initial height - logger.Info(fmt.Sprintf("Waiting for initial height %v...", testnet.InitialHeight)) - block, blockID, err := waitForHeight(testnet, testnet.InitialHeight) + logger.Info("Waiting for initial height", + "height", networkHeight, + "nodes", len(testnet.Nodes)-len(nodeQueue), + "pending", len(nodeQueue)) + + block, blockID, err := waitForHeight(testnet, networkHeight) if err != nil { return err } @@ -66,8 +73,26 @@ func Start(testnet *e2e.Testnet) error { } } - // Start up remaining nodes for _, node := range nodeQueue { + if node.StartAt > networkHeight { + // if we're starting a node that's ahead of + // the last known height of the network, then + // we should make sure that the rest of the + // network has reached at least the height + // that this node will start at before we + // start the node. + + networkHeight = node.StartAt + + logger.Info("Waiting for network to advance before starting catch up node", + "node", node.Name, + "height", networkHeight) + + if _, _, err := waitForHeight(testnet, networkHeight); err != nil { + return err + } + } + logger.Info(fmt.Sprintf("Starting node %v at height %v...", node.Name, node.StartAt)) if _, _, err := waitForHeight(testnet, node.StartAt); err != nil { return err From cce0a3c17145e10b4a73e3b564ecfd789c308acf Mon Sep 17 00:00:00 2001 From: Aleksandr Bezobchuk Date: Thu, 19 Aug 2021 16:25:17 -0400 Subject: [PATCH 11/37] docs: add package godoc for indexer (#6839) * docs: add indexer godoc * docs++ * docs++ * docs++ * docs++ * docs++ * Update state/indexer/doc.go Co-authored-by: M. J. Fromberger * Update state/indexer/doc.go Co-authored-by: M. J. Fromberger * Update state/indexer/doc.go Co-authored-by: M. J. Fromberger * Update state/indexer/doc.go Co-authored-by: M. J. Fromberger * Update state/indexer/doc.go Co-authored-by: M. J. Fromberger * Update state/indexer/doc.go Co-authored-by: M. J. Fromberger * docs++ Co-authored-by: M. J. Fromberger --- state/indexer/doc.go | 72 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 72 insertions(+) create mode 100644 state/indexer/doc.go diff --git a/state/indexer/doc.go b/state/indexer/doc.go new file mode 100644 index 000000000..61adbabac --- /dev/null +++ b/state/indexer/doc.go @@ -0,0 +1,72 @@ +/* +Package indexer defines Tendermint's block and transaction event indexing logic. + +Tendermint supports two primary means of block and transaction event indexing: + +1. A key-value sink via an embedded database with a proprietary query language. +2. A Postgres-based sink. + +An ABCI application can emit events during block and transaction execution in the form + + .= + +for example "transfer.amount=10000". + +An operator can enable one or both of the supported indexing sinks via the +'tx-index.indexer' Tendermint configuration. + +Example: + + [tx-index] + indexer = ["kv", "psql"] + +If an operator wants to completely disable indexing, they may simply just provide +the "null" sink option in the configuration. All other sinks will be ignored if +"null" is provided. + +If indexing is enabled, the indexer.Service will iterate over all enabled sinks +and invoke block and transaction indexing via the appropriate IndexBlockEvents +and IndexTxEvents methods. + +Note, the "kv" sink is considered deprecated and its query functionality is very +limited, but does allow users to directly query for block and transaction events +against Tendermint's RPC. Instead, operators are encouraged to use the "psql" +indexing sink when more complex queries are required and for reliability purposes +as PostgreSQL can scale. + +Prior to starting Tendermint with the "psql" indexing sink enabled, operators +must ensure the following: + +1. The "psql" indexing sink is provided in Tendermint's configuration. +2. A 'tx-index.psql-conn' value is provided that contains the PostgreSQL connection URI. +3. The block and transaction event schemas have been created in the PostgreSQL database. + +Tendermint provides the block and transaction event schemas in the following +path: state/indexer/sink/psql/schema.sql + +To create the schema in a PostgreSQL database, perform the schema query +manually or invoke schema creation via the CLI: + + $ psql -f state/indexer/sink/psql/schema.sql + +The "psql" indexing sink prohibits queries via RPC. When using a PostgreSQL sink, +queries can and should be made directly against the database using SQL. + +The following are some example SQL queries against the database schema: + +* Query for all transaction events for a given transaction hash: + + SELECT * FROM tx_events WHERE hash = '3E7D1F...'; + +* Query for all transaction events for a given block height: + + SELECT * FROM tx_events WHERE height = 25; + +* Query for transaction events that have a given type (i.e. value wildcard): + + SELECT * FROM tx_events WHERE key LIKE '%transfer.recipient%'; + +Note that if a complete abci.TxResult is needed, you will need to join "tx_events" with +"tx_results" via a foreign key, to obtain contains the raw protobuf-encoded abci.TxResult. +*/ +package indexer From 3a234e1144bb22487f51235b3c49d26b981290d6 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 20 Aug 2021 13:32:11 +0200 Subject: [PATCH 12/37] build(deps): Bump docker/build-push-action from 2.6.1 to 2.7.0 (#6845) Bumps [docker/build-push-action](https://github.com/docker/build-push-action) from 2.6.1 to 2.7.0. - [Release notes](https://github.com/docker/build-push-action/releases) - [Commits](https://github.com/docker/build-push-action/compare/v2.6.1...v2.7.0) --- updated-dependencies: - dependency-name: docker/build-push-action dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/docker.yml | 2 +- .github/workflows/proto-docker.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 009f16898..89797a581 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -50,7 +50,7 @@ jobs: password: ${{ secrets.DOCKERHUB_TOKEN }} - name: Publish to Docker Hub - uses: docker/build-push-action@v2.6.1 + uses: docker/build-push-action@v2.7.0 with: context: . file: ./DOCKER/Dockerfile diff --git a/.github/workflows/proto-docker.yml b/.github/workflows/proto-docker.yml index 8dc612602..ed31025b9 100644 --- a/.github/workflows/proto-docker.yml +++ b/.github/workflows/proto-docker.yml @@ -43,7 +43,7 @@ jobs: password: ${{ secrets.DOCKERHUB_TOKEN }} - name: Publish to Docker Hub - uses: docker/build-push-action@v2.6.1 + uses: docker/build-push-action@v2.7.0 with: context: ./tools/proto file: ./tools/proto/Dockerfile From a374f74f7c914a3c7640694ae80c33bf74e05688 Mon Sep 17 00:00:00 2001 From: Sam Kleinman Date: Fri, 20 Aug 2021 13:26:04 -0400 Subject: [PATCH 13/37] e2e: cleanup node start function (#6842) I realized after my last commit that my change made a following line of code a bit redundant. (alternatively my last change was redunadnt to the existing code.) I took this oppertunity to make some minor cleanups and logging changes to the node changes which I hope will make tests a bit more clear. --- test/e2e/runner/start.go | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/test/e2e/runner/start.go b/test/e2e/runner/start.go index ca3ebfcbd..c8d6163ed 100644 --- a/test/e2e/runner/start.go +++ b/test/e2e/runner/start.go @@ -9,6 +9,9 @@ import ( ) func Start(testnet *e2e.Testnet) error { + if len(testnet.Nodes) == 0 { + return fmt.Errorf("no nodes in testnet") + } // Nodes are already sorted by name. Sort them by name then startAt, // which gives the overall order startAt, mode, name. @@ -29,9 +32,7 @@ func Start(testnet *e2e.Testnet) error { sort.SliceStable(nodeQueue, func(i, j int) bool { return nodeQueue[i].StartAt < nodeQueue[j].StartAt }) - if len(nodeQueue) == 0 { - return fmt.Errorf("no nodes in testnet") - } + if nodeQueue[0].StartAt > 0 { return fmt.Errorf("no initial nodes in testnet") } @@ -93,10 +94,8 @@ func Start(testnet *e2e.Testnet) error { } } - logger.Info(fmt.Sprintf("Starting node %v at height %v...", node.Name, node.StartAt)) - if _, _, err := waitForHeight(testnet, node.StartAt); err != nil { - return err - } + logger.Info("Starting catch up node", "node", node.Name, "height", node.StartAt) + if err := execCompose(testnet.Dir, "up", "-d", node.Name); err != nil { return err } From 8700ca9d1a0c74b10386657591720953f8f9dfd9 Mon Sep 17 00:00:00 2001 From: William Banfield <4561443+williambanfield@users.noreply.github.com> Date: Fri, 20 Aug 2021 16:07:20 -0400 Subject: [PATCH 14/37] ADR 071: Proposer-based Timestamps (#6799) Architectural decision record for Proposer-based timestamps. --- .../adr-071-proposer-based-timestamps.md | 445 ++++++++++++++++++ docs/architecture/img/pbts-message.png | Bin 0 -> 32028 bytes 2 files changed, 445 insertions(+) create mode 100644 docs/architecture/adr-071-proposer-based-timestamps.md create mode 100644 docs/architecture/img/pbts-message.png diff --git a/docs/architecture/adr-071-proposer-based-timestamps.md b/docs/architecture/adr-071-proposer-based-timestamps.md new file mode 100644 index 000000000..c23488005 --- /dev/null +++ b/docs/architecture/adr-071-proposer-based-timestamps.md @@ -0,0 +1,445 @@ +# ADR 71: Proposer-Based Timestamps + +* [Changelog](#changelog) +* [Status](#status) +* [Context](#context) +* [Alternative Approaches](#alternative-approaches) + * [Remove timestamps altogether](#remove-timestamps-altogether) +* [Decision](#decision) +* [Detailed Design](#detailed-design) + * [Overview](#overview) + * [Proposal Timestamp and Block Timestamp](#proposal-timestamp-and-block-timestamp) + * [Saving the timestamp across heights](#saving-the-timestamp-across-heights) + * [Changes to `CommitSig`](#changes-to-commitsig) + * [Changes to `Commit`](#changes-to-commit) + * [Changes to `Vote` messages](#changes-to-vote-messages) + * [New consensus parameters](#new-consensus-parameters) + * [Changes to `Header`](#changes-to-header) + * [Changes to the block proposal step](#changes-to-the-block-proposal-step) + * [Proposer selects proposal timestamp](#proposer-selects-proposal-timestamp) + * [Proposer selects block timestamp](#proposer-selects-block-timestamp) + * [Proposer waits](#proposer-waits) + * [Changes to the propose step timeout](#changes-to-the-propose-step-timeout) + * [Changes to validation rules](#changes-to-validation-rules) + * [Proposal timestamp validation](#proposal-timestamp-validation) + * [Block timestamp validation](#block-timestamp-validation) + * [Changes to the prevote step](#changes-to-the-prevote-step) + * [Changes to the precommit step](#changes-to-the-precommit-step) + * [Changes to locking a block](#changes-to-locking-a-block) + * [Remove voteTime Completely](#remove-votetime-completely) +* [Future Improvements](#future-improvements) +* [Consequences](#consequences) + * [Positive](#positive) + * [Neutral](#neutral) + * [Negative](#negative) +* [References](#references) + +## Changelog + + - July 15 2021: Created by @williambanfield + - Aug 4 2021: Draft completed by @williambanfield + - Aug 5 2021: Draft updated to include data structure changes by @williambanfield + - Aug 20 2021: Language edits completed by @williambanfield + +## Status + + **Accepted** + +## Context + +Tendermint currently provides a monotonically increasing source of time known as [BFTTime](https://github.com/tendermint/spec/blob/master/spec/consensus/bft-time.md). +This mechanism for producing a source of time is reasonably simple. +Each correct validator adds a timestamp to each `Precommit` message it sends. +The timestamp it sends is either the validator's current known Unix time or one millisecond greater than the previous block time, depending on which value is greater. +When a block is produced, the proposer chooses the block timestamp as the weighted median of the times in all of the `Precommit` messages the proposer received. +The weighting is proportional to the amount of voting power, or stake, a validator has on the network. +This mechanism for producing timestamps is both deterministic and byzantine fault tolerant. + +This current mechanism for producing timestamps has a few drawbacks. +Validators do not have to agree at all on how close the selected block timestamp is to their own currently known Unix time. +Additionally, any amount of voting power `>1/3` may directly control the block timestamp. +As a result, it is quite possible that the timestamp is not particularly meaningful. + +These drawbacks present issues in the Tendermint protocol. +Timestamps are used by light clients to verify blocks. +Light clients rely on correspondence between their own currently known Unix time and the block timestamp to verify blocks they see; +However, their currently known Unix time may be greatly divergent from the block timestamp as a result of the limitations of `BFTTime`. + +The proposer-based timestamps specification suggests an alternative approach for producing block timestamps that remedies these issues. +Proposer-based timestamps alter the current mechanism for producing block timestamps in two main ways: + +1. The block proposer is amended to offer up its currently known Unix time as the timestamp for the next block. +1. Correct validators only approve the proposed block timestamp if it is close enough to their own currently known Unix time. + +The result of these changes is a more meaningful timestamp that cannot be controlled by `<= 2/3` of the validator voting power. +This document outlines the necessary code changes in Tendermint to implement the corresponding [proposer-based timestamps specification](https://github.com/tendermint/spec/tree/master/spec/consensus/proposer-based-timestamp). + +## Alternative Approaches + +### Remove timestamps altogether + +Computer clocks are bound to skew for a variety of reasons. +Using timestamps in our protocol means either accepting the timestamps as not reliable or impacting the protocol’s liveness guarantees. +This design requires impacting the protocol’s liveness in order to make the timestamps more reliable. +An alternate approach is to remove timestamps altogether from the block protocol. +`BFTTime` is deterministic but may be arbitrarily inaccurate. +However, having a reliable source of time is quite useful for applications and protocols built on top of a blockchain. + +We therefore decided not to remove the timestamp. +Applications often wish for some transactions to occur on a certain day, on a regular period, or after some time following a different event. +All of these require some meaningful representation of agreed upon time. +The following protocols and application features require a reliable source of time: +* Tendermint Light Clients [rely on correspondence between their known time](https://github.com/tendermint/spec/blob/master/spec/light-client/verification/README.md#definitions-1) and the block time for block verification. +* Tendermint Evidence validity is determined [either in terms of heights or in terms of time](https://github.com/tendermint/spec/blob/8029cf7a0fcc89a5004e173ec065aa48ad5ba3c8/spec/consensus/evidence.md#verification). +* Unbonding of staked assets in the Cosmos Hub [occurs after a period of 21 days](https://github.com/cosmos/governance/blob/ce75de4019b0129f6efcbb0e752cd2cc9e6136d3/params-change/Staking.md#unbondingtime). +* IBC packets can use either a [timestamp or a height to timeout packet delivery](https://docs.cosmos.network/v0.43/ibc/overview.html#acknowledgements). + +Finally, inflation distribution in the Cosmos Hub uses an approximation of time to calculate an annual percentage rate. +This approximation of time is calculated using [block heights with an estimated number of blocks produced in a year](https://github.com/cosmos/governance/blob/master/params-change/Mint.md#blocksperyear). +Proposer-based timestamps will allow this inflation calculation to use a more meaningful and accurate source of time. + + +## Decision + +Implement proposer-based timestamps and remove `BFTTime`. + +## Detailed Design + +### Overview + +Implementing proposer-based timestamps will require a few changes to Tendermint’s code. +These changes will be to the following components: +* The `internal/consensus/` package. +* The `state/` package. +* The `Vote`, `CommitSig`, `Commit` and `Header` types. +* The consensus parameters. + +### Proposal Timestamp and Block Timestamp + +This design discusses two timestamps: (1) The timestamp in the block and (2) the timestamp in the proposal message. +The existence and use of both of these timestamps can get a bit confusing, so some background is given here to clarify their uses. + +The [proposal message currently has a timestamp](https://github.com/tendermint/tendermint/blob/e5312942e30331e7c42b75426da2c6c9c00ae476/types/proposal.go#L31). +This timestamp is the current Unix time known to the proposer when sending the `Proposal` message. +This timestamp is not currently used as part of consensus. +The changes in this ADR will begin using the proposal message timestamp as part of consensus. +We will refer to this as the **proposal timestamp** throughout this design. + +The block has a timestamp field [in the header](https://github.com/tendermint/tendermint/blob/dc7c212c41a360bfe6eb38a6dd8c709bbc39aae7/types/block.go#L338). +This timestamp is set currently as part of Tendermint’s `BFTtime` algorithm. +It is set when a block is proposed and it is checked by the validators when they are deciding to prevote the block. +This field will continue to be used but the logic for creating and validating this timestamp will change. +We will refer to this as the **block timestamp** throughout this design. + +At a high level, the proposal timestamp from height `H` is used as the block timestamp at height `H+1`. +The following image shows this relationship. +The rest of this document describes the code changes that will make this possible. + +![](./img/pbts-message.png) + +### Saving the timestamp across heights + +Currently, `BFTtime` uses `LastCommit` to construct the block timestamp. +The `LastCommit` is created at height `H-1` and is saved in the state store to be included in the block at height `H`. +`BFTtime` takes the weighted median of the timestamps in `LastCommit.CommitSig` to build the timestamp for height `H`. + +For proposer-based timestamps, the `LastCommit.CommitSig` timestamps will no longer be used to build the timestamps for height `H`. +Instead, the proposal timestamp from height `H-1` will become the block timestamp for height `H`. +To enable this, we will add a `Timestamp` field to the `Commit` struct. +This field will be populated at each height with the proposal timestamp decided on at the previous height. +This timestamp will also be saved with the rest of the commit in the state store [when the commit is finalized](https://github.com/tendermint/tendermint/blob/e8013281281985e3ada7819f42502b09623d24a0/internal/consensus/state.go#L1611) so that it can be recovered if Tendermint crashes. +Changes to the `CommitSig` and `Commit` struct are detailed below. + +### Changes to `CommitSig` + +The [CommitSig](https://github.com/tendermint/tendermint/blob/a419f4df76fe4aed668a6c74696deabb9fe73211/types/block.go#L604) struct currently contains a timestamp. +This timestamp is the current Unix time known to the validator when it issued a `Precommit` for the block. +This timestamp is no longer used and will be removed in this change. + +`CommitSig` will be updated as follows: + +```diff +type CommitSig struct { + BlockIDFlag BlockIDFlag `json:"block_id_flag"` + ValidatorAddress Address `json:"validator_address"` +-- Timestamp time.Time `json:"timestamp"` + Signature []byte `json:"signature"` +} +``` + +### Changes to `Commit` + +The [Commit](https://github.com/tendermint/tendermint/blob/a419f4df76fe4aed668a6c74696deabb9fe73211/types/block.go#L746) struct does not currently contain a timestamp. +The timestamps in the `Commit.CommitSig` entries are currently used to build the block timestamp. +With these timestamps removed, the commit time will instead be stored in the `Commit` struct. + +`Commit` will be updated as follows. + +```diff +type Commit struct { + Height int64 `json:"height"` + Round int32 `json:"round"` +++ Timestamp time.Time `json:"timestamp"` + BlockID BlockID `json:"block_id"` + Signatures []CommitSig `json:"signatures"` +} +``` + +### Changes to `Vote` messages + +`Precommit` and `Prevote` messages use a common [Vote struct](https://github.com/tendermint/tendermint/blob/a419f4df76fe4aed668a6c74696deabb9fe73211/types/vote.go#L50). +This struct currently contains a timestamp. +This timestamp is set using the [voteTime](https://github.com/tendermint/tendermint/blob/e8013281281985e3ada7819f42502b09623d24a0/internal/consensus/state.go#L2241) function and therefore vote times correspond to the current Unix time known to the validator. +For precommits, this timestamp is used to construct the [CommitSig that is included in the block in the LastCommit](https://github.com/tendermint/tendermint/blob/e8013281281985e3ada7819f42502b09623d24a0/types/block.go#L754) field. +For prevotes, this field is unused. +Proposer-based timestamps will use the [RoundState.Proposal](https://github.com/tendermint/tendermint/blob/c3ae6f5b58e07b29c62bfdc5715b6bf8ae5ee951/internal/consensus/types/round_state.go#L76) timestamp to construct the `signedBytes` `CommitSig`. +This timestamp is therefore no longer useful and will be dropped. + +`Vote` will be updated as follows: + +```diff +type Vote struct { + Type tmproto.SignedMsgType `json:"type"` + Height int64 `json:"height"` + Round int32 `json:"round"` + BlockID BlockID `json:"block_id"` // zero if vote is nil. +-- Timestamp time.Time `json:"timestamp"` + ValidatorAddress Address `json:"validator_address"` + ValidatorIndex int32 `json:"validator_index"` + Signature []byte `json:"signature"` +} +``` + +### New consensus parameters + +The proposer-based timestamp specification includes multiple new parameters that must be the same among all validators. +These parameters are `PRECISION`, `MSGDELAY`, and `ACCURACY`. + +The `PRECISION` and `MSGDELAY` parameters are used to determine if the proposed timestamp is acceptable. +A validator will only Prevote a proposal if the proposal timestamp is considered `timely`. +A proposal timestamp is considered `timely` if it is within `PRECISION` and `MSGDELAY` of the Unix time known to the validator. +More specifically, a proposal timestamp is `timely` if `validatorLocalTime - PRECISION < proposalTime < validatorLocalTime + PRECISION + MSGDELAY`. + +Because the `PRECISION` and `MSGDELAY` parameters must be the same across all validators, they will be added to the [consensus parameters](https://github.com/tendermint/tendermint/blob/master/proto/tendermint/types/params.proto#L13) as [durations](https://developers.google.com/protocol-buffers/docs/reference/google.protobuf#google.protobuf.Duration). + +The proposer-based timestamp specification also includes a [new ACCURACY parameter](https://github.com/tendermint/spec/blob/master/spec/consensus/proposer-based-timestamp/pbts-sysmodel_001_draft.md#pbts-clocksync-external0). +Intuitively, `ACCURACY` represents the difference between the ‘real’ time and the currently known time of correct validators. +The currently known Unix time of any validator is always somewhat different from real time. +`ACCURACY` is the largest such difference between each validator's time and real time taken as an absolute value. +This is not something a computer can determine on its own and must be specified as an estimate by community running a Tendermint-based chain. +It is used in the new algorithm to [calculate a timeout for the propose step](https://github.com/tendermint/spec/blob/master/spec/consensus/proposer-based-timestamp/pbts-algorithm_001_draft.md#pbts-alg-startround0). +`ACCURACY` is assumed to be the same across all validators and therefore should be included as a consensus parameter. + +The consensus will be updated to include this `Timestamp` field as follows: + +```diff +type ConsensusParams struct { + Block BlockParams `json:"block"` + Evidence EvidenceParams `json:"evidence"` + Validator ValidatorParams `json:"validator"` + Version VersionParams `json:"version"` +++ Timestamp TimestampParams `json:"timestamp"` +} +``` + +```go +type TimestampParams struct { + Accuracy time.Duration `json:"accuracy"` + Precision time.Duration `json:"precision"` + MsgDelay time.Duration `json:"msg_delay"` +} +``` + +### Changes to `Header` + +The [Header](https://github.com/tendermint/tendermint/blob/a419f4df76fe4aed668a6c74696deabb9fe73211/types/block.go#L338) struct currently contains a timestamp. +This timestamp is set as the `BFTtime` derived from the block's `LastCommit.CommitSig` timestamps. +This timestamp will no longer be derived from the `LastCommit.CommitSig` timestamps and will instead be included directly into the block's `LastCommit`. +This timestamp will therfore be identical in both the `Header` and the `LastCommit`. +To clarify that the timestamp in the header corresponds to the `LastCommit`'s time, we will rename this timestamp field to `last_timestamp`. + +`Header` will be updated as follows: + +```diff +type Header struct { + // basic block info + Version version.Consensus `json:"version"` + ChainID string `json:"chain_id"` + Height int64 `json:"height"` +-- Time time.Time `json:"time"` +++ LastTimestamp time.Time `json:"last_timestamp"` + + // prev block info + LastBlockID BlockID `json:"last_block_id"` + + // hashes of block data + LastCommitHash tmbytes.HexBytes `json:"last_commit_hash"` + DataHash tmbytes.HexBytes `json:"data_hash"` + + // hashes from the app output from the prev block + ValidatorsHash tmbytes.HexBytes `json:"validators_hash"` + NextValidatorsHash tmbytes.HexBytes `json:"next_validators_hash"` + ConsensusHash tmbytes.HexBytes `json:"consensus_hash"` + AppHash tmbytes.HexBytes `json:"app_hash"` + + // root hash of all results from the txs from the previous block + LastResultsHash tmbytes.HexBytes `json:"last_results_hash"` + + // consensus info + EvidenceHash tmbytes.HexBytes `json:"evidence_hash"` + ProposerAddress Address `json:"proposer_address"` +} +``` + +### Changes to the block proposal step + +#### Proposer selects proposal timestamp + +The proposal logic already [sets the Unix time known to the validator](https://github.com/tendermint/tendermint/blob/2abfe20114ee3bb3adfee817589033529a804e4d/types/proposal.go#L44) into the `Proposal` message. +This satisfies the proposer-based timestamp specification and does not need to change. + +#### Proposer selects block timestamp + +The proposal timestamp that was decided in height `H-1` will be stored in the `State` struct's in the `RoundState.LastCommit` field. +The proposer will select this timestamp to use as the block timestamp at height `H`. + +#### Proposer waits + +Block timestamps must be monotonically increasing. +In `BFTTime`, if a validator’s clock was behind, the [validator added 1 millisecond to the previous block’s time and used that in its vote messages](https://github.com/tendermint/tendermint/blob/e8013281281985e3ada7819f42502b09623d24a0/internal/consensus/state.go#L2246). +A goal of adding proposer-based timestamps is to enforce some degree of clock synchronization, so having a mechanism that completely ignores the Unix time of the validator time no longer works. + +Validator clocks will not be perfectly in sync. +Therefore, the proposer’s current known Unix time may be less than the `LastCommit.Timestamp`. +If the proposer’s current known Unix time is less than the `LastCommit.Timestamp`, the proposer will sleep until its known Unix time exceeds `LastCommit.Timestamp`. + +This change will require amending the [defaultDecideProposal](https://github.com/tendermint/tendermint/blob/822893615564cb20b002dd5cf3b42b8d364cb7d9/internal/consensus/state.go#L1180) method. +This method should now block until the proposer’s time is greater than `LastCommit.Timestamp`. + +#### Changes to the propose step timeout + +Currently, a validator waiting for a proposal will proceed past the propose step if the configured propose timeout is reached and no proposal is seen. +Proposer-based timestamps requires changing this timeout logic. + +The proposer will now wait until its current known Unix time exceeds the `LastCommit.Timestamp` to propose a block. +The validators must now take this and some other factors into account when deciding when to timeout the propose step. +Specifically, the propose step timeout must also take into account potential inaccuracy in the validator’s clock and in the clock of the proposer. +Additionally, there may be a delay communicating the proposal message from the proposer to the other validators. + +Therefore, validators waiting for a proposal must wait until after the `LastCommit.Timestamp` before timing out. +To account for possible inaccuracy in its own clock, inaccuracy in the proposer’s clock, and message delay, validators waiting for a proposal will wait until `LastCommit.Timesatmp + 2*ACCURACY + MSGDELAY`. + The spec defines this as `waitingTime`. + +The [propose step’s timeout is set in enterPropose](https://github.com/tendermint/tendermint/blob/822893615564cb20b002dd5cf3b42b8d364cb7d9/internal/consensus/state.go#L1108) in `state.go`. +`enterPropose` will be changed to calculate waiting time using the new consensus parameters. +The timeout in `enterPropose` will then be set as the maximum of `waitingTime` and the [configured proposal step timeout](https://github.com/tendermint/tendermint/blob/dc7c212c41a360bfe6eb38a6dd8c709bbc39aae7/config/config.go#L1013). + +### Changes to validation rules + +The rules for validating that a proposal is valid will need slight modification to implement proposer-based timestamps. +Specifically, we will change the validation logic to ensure that the proposal timestamp is `timely` and we will modify the way the block timestamp is validated as well. + +#### Proposal timestamp validation + +Adding proposal timestamp validation is a reasonably straightforward change. +The current Unix time known to the proposer is already included in the [Proposal message](https://github.com/tendermint/tendermint/blob/dc7c212c41a360bfe6eb38a6dd8c709bbc39aae7/types/proposal.go#L31). +Once the proposal is received, the complete message is stored in the `RoundState.Proposal` field. +The precommit and prevote validation logic does not currently use this timestamp. +This validation logic will be updated to check that the proposal timestamp is within `PRECISION` of the current Unix time known to the validators. +If the timestamp is not within `PRECISION` of the current Unix time known to the validator, the proposal will not be considered it valid. +The validator will also check that the proposal time is greater than the block timestamp from the previous height. + +If no valid proposal is received by the proposal timeout, the validator will prevote nil. +This is identical to the current logic. + +#### Block timestamp validation + +The [validBlock function](https://github.com/tendermint/tendermint/blob/c3ae6f5b58e07b29c62bfdc5715b6bf8ae5ee951/state/validation.go#L14) currently [validates the proposed block timestamp in three ways](https://github.com/tendermint/tendermint/blob/c3ae6f5b58e07b29c62bfdc5715b6bf8ae5ee951/state/validation.go#L118). +First, the validation logic checks that this timestamp is greater than the previous block’s timestamp. +Additionally, it validates that the block timestamp is correctly calculated as the weighted median of the timestamps in the [block’s LastCommit](https://github.com/tendermint/tendermint/blob/e8013281281985e3ada7819f42502b09623d24a0/types/block.go#L48). +Finally, the logic also authenticates the timestamps in the `LastCommit`. +The cryptographic signature in each `CommitSig` is created by signing a hash of fields in the block with the validator’s private key. +One of the items in this `signedBytes` hash is derived from the timestamp in the `CommitSig`. +To authenticate the `CommitSig` timestamp, the validator builds a hash of fields that includes the timestamp and checks this hash against the provided signature. +This takes place in the [VerifyCommit function](https://github.com/tendermint/tendermint/blob/e8013281281985e3ada7819f42502b09623d24a0/types/validation.go#L25). + +The logic to validate that the block timestamp is greater than the previous block’s timestamp also works for proposer-based timestamps and will not change. + +`BFTTime` validation is no longer applicable and will be removed. +Validators will no longer check that the block timestamp is a weighted median of `LastCommit` timestamps. +This will mean removing the call to [MedianTime in the validateBlock function](https://github.com/tendermint/tendermint/blob/4db71da68e82d5cb732b235eeb2fd69d62114b45/state/validation.go#L117). +The `MedianTime` function can be completely removed. +The `LastCommit` timestamps may also be removed. + +The `signedBytes` validation logic in `VerifyCommit` will be slightly altered. +The `CommitSig`s in the block’s `LastCommit` will no longer each contain a timestamp. +The validation logic will instead include the `LastCommit.Timestamp` in the hash of fields for generating the `signedBytes`. +The cryptographic signatures included in the `CommitSig`s will then be checked against this `signedBytes` hash to authenticate the timestamp. +Specifically, the `VerifyCommit` function will be updated to use this new timestamp. + +### Changes to the prevote step + +Currently, a validator will prevote a proposal in one of three cases: + +* Case 1: Validator has no locked block and receives a valid proposal. +* Case 2: Validator has a locked block and receives a valid proposal matching its locked block. +* Case 3: Validator has a locked block, sees a valid proposal not matching its locked block but sees +⅔ prevotes for the new proposal’s block. + +The only change we will make to the prevote step is to what a validator considers a valid proposal as detailed above. + +### Changes to the precommit step + +The precommit step will not require much modification. +Its proposal validation rules will change in the same ways that validation will change in the prevote step. + +### Changes to locking a block +When a validator receives a valid proposed block and +2/3 prevotes for that block, it stores the block as its ‘locked block’ in the [RoundState.ValidBlock](https://github.com/tendermint/tendermint/blob/e8013281281985e3ada7819f42502b09623d24a0/internal/consensus/types/round_state.go#L85) field. +In each subsequent round it will prevote that block. +A validator will only change which block it has locked if it sees +2/3 prevotes for a different block. + +This mechanism will remain largely unchanged. +The only difference is the addition of proposal timestamp validation. +A validator will prevote nil in a round if the proposal message it received is not `timely`. +Prevoting nil in this case will not cause a validator to ‘unlock’ its locked block. +This difference is an incidental result of the changes to prevote validation. +It is included in this design for completeness and to clarify that no additional changes will be made to block locking. + +### Remove voteTime Completely + +[voteTime](https://github.com/tendermint/tendermint/blob/822893615564cb20b002dd5cf3b42b8d364cb7d9/internal/consensus/state.go#L2229) is a mechanism for calculating the next `BFTTime` given both the validator's current known Unix time and the previous block timestamp. +If the previous block timestamp is greater than the validator's current known Unix time, then voteTime returns a value one millisecond greater than the previous block timestamp. +This logic is used in multiple places and is no longer needed for proposer-based timestamps. +It should therefore be removed completely. + +## Future Improvements + +* Implement BLS signature aggregation. +By removing fields from the `Precommit` messages, we are able to aggregate signatures. + +## Consequences + +### Positive + +* `<2/3` of validators can no longer influence block timestamps. +* Block timestamp will have stronger correspondence to real time. +* Improves the reliability of light client block verification. +* Enables BLS signature aggregation. +* Enables evidence handling to use time instead of height for evidence validity. + +### Neutral + +* Alters Tendermint’s liveness properties. +Liveness now requires that all correct validators have synchronized clocks within a bound. +Liveness will now also require that validators’ clocks move forward, which was not required under `BFTTime`. + +### Negative + +* May increase the length of the propose step if there is a large skew between the previous proposer and the current proposer’s local Unix time. +This skew will be bound by the `PRECISION` value, so it is unlikely to be too large. + +* Current chains with block timestamps far in the future will either need to pause consensus until after the erroneous block timestamp or must maintain synchronized but very inaccurate clocks. + +## References + +* [PBTS Spec](https://github.com/tendermint/spec/tree/master/spec/consensus/proposer-based-timestamp) +* [BFTTime spec](https://github.com/tendermint/spec/blob/master/spec/consensus/bft-time.md) diff --git a/docs/architecture/img/pbts-message.png b/docs/architecture/img/pbts-message.png new file mode 100644 index 0000000000000000000000000000000000000000..400f3569021ca719fe06f20e0db17940091751a1 GIT binary patch literal 32028 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 literal 0 HcmV?d00001 From d0e33b429288fb7d284205732beb82b59b4dea76 Mon Sep 17 00:00:00 2001 From: Hlib Kanunnikov Date: Mon, 23 Aug 2021 17:45:08 +0300 Subject: [PATCH 15/37] blocksync: complete transition from Blockchain to BlockSync (#6847) --- internal/blocksync/v0/reactor.go | 118 +++++++++++++------------- internal/blocksync/v0/reactor_test.go | 38 ++++----- node/setup.go | 4 +- 3 files changed, 80 insertions(+), 80 deletions(-) diff --git a/internal/blocksync/v0/reactor.go b/internal/blocksync/v0/reactor.go index c43959808..4ddfa4edc 100644 --- a/internal/blocksync/v0/reactor.go +++ b/internal/blocksync/v0/reactor.go @@ -29,10 +29,10 @@ var ( // TODO: Remove once p2p refactor is complete. // ref: https://github.com/tendermint/tendermint/issues/5670 ChannelShims = map[p2p.ChannelID]*p2p.ChannelDescriptorShim{ - BlockchainChannel: { + BlockSyncChannel: { MsgType: new(bcproto.Message), Descriptor: &p2p.ChannelDescriptor{ - ID: byte(BlockchainChannel), + ID: byte(BlockSyncChannel), Priority: 5, SendQueueCapacity: 1000, RecvBufferCapacity: 1024, @@ -44,8 +44,8 @@ var ( ) const ( - // BlockchainChannel is a channel for blocks and status updates - BlockchainChannel = p2p.ChannelID(0x40) + // BlockSyncChannel is a channel for blocks and status updates + BlockSyncChannel = p2p.ChannelID(0x40) trySyncIntervalMS = 10 @@ -60,7 +60,7 @@ const ( ) type consensusReactor interface { - // For when we switch from blockchain reactor and block sync to the consensus + // For when we switch from block sync reactor to the consensus // machine. SwitchToConsensus(state sm.State, skipWAL bool) } @@ -87,17 +87,17 @@ type Reactor struct { consReactor consensusReactor blockSync *tmSync.AtomicBool - blockchainCh *p2p.Channel - // blockchainOutBridgeCh defines a channel that acts as a bridge between sending Envelope - // messages that the reactor will consume in processBlockchainCh and receiving messages + blockSyncCh *p2p.Channel + // blockSyncOutBridgeCh defines a channel that acts as a bridge between sending Envelope + // messages that the reactor will consume in processBlockSyncCh and receiving messages // from the peer updates channel and other goroutines. We do this instead of directly - // sending on blockchainCh.Out to avoid race conditions in the case where other goroutines - // send Envelopes directly to the to blockchainCh.Out channel, since processBlockchainCh - // may close the blockchainCh.Out channel at the same time that other goroutines send to - // blockchainCh.Out. - blockchainOutBridgeCh chan p2p.Envelope - peerUpdates *p2p.PeerUpdates - closeCh chan struct{} + // sending on blockSyncCh.Out to avoid race conditions in the case where other goroutines + // send Envelopes directly to the to blockSyncCh.Out channel, since processBlockSyncCh + // may close the blockSyncCh.Out channel at the same time that other goroutines send to + // blockSyncCh.Out. + blockSyncOutBridgeCh chan p2p.Envelope + peerUpdates *p2p.PeerUpdates + closeCh chan struct{} requestsCh <-chan BlockRequest errorsCh <-chan peerError @@ -119,7 +119,7 @@ func NewReactor( blockExec *sm.BlockExecutor, store *store.BlockStore, consReactor consensusReactor, - blockchainCh *p2p.Channel, + blockSyncCh *p2p.Channel, peerUpdates *p2p.PeerUpdates, blockSync bool, metrics *cons.Metrics, @@ -137,23 +137,23 @@ func NewReactor( errorsCh := make(chan peerError, maxPeerErrBuffer) // NOTE: The capacity should be larger than the peer count. r := &Reactor{ - initialState: state, - blockExec: blockExec, - store: store, - pool: NewBlockPool(startHeight, requestsCh, errorsCh), - consReactor: consReactor, - blockSync: tmSync.NewBool(blockSync), - requestsCh: requestsCh, - errorsCh: errorsCh, - blockchainCh: blockchainCh, - blockchainOutBridgeCh: make(chan p2p.Envelope), - peerUpdates: peerUpdates, - closeCh: make(chan struct{}), - metrics: metrics, - syncStartTime: time.Time{}, + initialState: state, + blockExec: blockExec, + store: store, + pool: NewBlockPool(startHeight, requestsCh, errorsCh), + consReactor: consReactor, + blockSync: tmSync.NewBool(blockSync), + requestsCh: requestsCh, + errorsCh: errorsCh, + blockSyncCh: blockSyncCh, + blockSyncOutBridgeCh: make(chan p2p.Envelope), + peerUpdates: peerUpdates, + closeCh: make(chan struct{}), + metrics: metrics, + syncStartTime: time.Time{}, } - r.BaseService = *service.NewBaseService(logger, "Blockchain", r) + r.BaseService = *service.NewBaseService(logger, "BlockSync", r) return r, nil } @@ -174,7 +174,7 @@ func (r *Reactor) OnStart() error { go r.poolRoutine(false) } - go r.processBlockchainCh() + go r.processBlockSyncCh() go r.processPeerUpdates() return nil @@ -199,7 +199,7 @@ func (r *Reactor) OnStop() { // Wait for all p2p Channels to be closed before returning. This ensures we // can easily reason about synchronization of all p2p Channels and ensure no // panics will occur. - <-r.blockchainCh.Done() + <-r.blockSyncCh.Done() <-r.peerUpdates.Done() } @@ -214,7 +214,7 @@ func (r *Reactor) respondToPeer(msg *bcproto.BlockRequest, peerID types.NodeID) return } - r.blockchainCh.Out <- p2p.Envelope{ + r.blockSyncCh.Out <- p2p.Envelope{ To: peerID, Message: &bcproto.BlockResponse{Block: blockProto}, } @@ -223,16 +223,16 @@ func (r *Reactor) respondToPeer(msg *bcproto.BlockRequest, peerID types.NodeID) } r.Logger.Info("peer requesting a block we do not have", "peer", peerID, "height", msg.Height) - r.blockchainCh.Out <- p2p.Envelope{ + r.blockSyncCh.Out <- p2p.Envelope{ To: peerID, Message: &bcproto.NoBlockResponse{Height: msg.Height}, } } -// handleBlockchainMessage handles envelopes sent from peers on the -// BlockchainChannel. It returns an error only if the Envelope.Message is unknown +// handleBlockSyncMessage handles envelopes sent from peers on the +// BlockSyncChannel. It returns an error only if the Envelope.Message is unknown // for this channel. This should never be called outside of handleMessage. -func (r *Reactor) handleBlockchainMessage(envelope p2p.Envelope) error { +func (r *Reactor) handleBlockSyncMessage(envelope p2p.Envelope) error { logger := r.Logger.With("peer", envelope.From) switch msg := envelope.Message.(type) { @@ -249,7 +249,7 @@ func (r *Reactor) handleBlockchainMessage(envelope p2p.Envelope) error { r.pool.AddBlock(envelope.From, block, block.Size()) case *bcproto.StatusRequest: - r.blockchainCh.Out <- p2p.Envelope{ + r.blockSyncCh.Out <- p2p.Envelope{ To: envelope.From, Message: &bcproto.StatusResponse{ Height: r.store.Height(), @@ -288,8 +288,8 @@ func (r *Reactor) handleMessage(chID p2p.ChannelID, envelope p2p.Envelope) (err r.Logger.Debug("received message", "message", envelope.Message, "peer", envelope.From) switch chID { - case BlockchainChannel: - err = r.handleBlockchainMessage(envelope) + case BlockSyncChannel: + err = r.handleBlockSyncMessage(envelope) default: err = fmt.Errorf("unknown channel ID (%d) for envelope (%v)", chID, envelope) @@ -298,30 +298,30 @@ func (r *Reactor) handleMessage(chID p2p.ChannelID, envelope p2p.Envelope) (err return err } -// processBlockchainCh initiates a blocking process where we listen for and handle -// envelopes on the BlockchainChannel and blockchainOutBridgeCh. Any error encountered during -// message execution will result in a PeerError being sent on the BlockchainChannel. +// processBlockSyncCh initiates a blocking process where we listen for and handle +// envelopes on the BlockSyncChannel and blockSyncOutBridgeCh. Any error encountered during +// message execution will result in a PeerError being sent on the BlockSyncChannel. // When the reactor is stopped, we will catch the signal and close the p2p Channel // gracefully. -func (r *Reactor) processBlockchainCh() { - defer r.blockchainCh.Close() +func (r *Reactor) processBlockSyncCh() { + defer r.blockSyncCh.Close() for { select { - case envelope := <-r.blockchainCh.In: - if err := r.handleMessage(r.blockchainCh.ID, envelope); err != nil { - r.Logger.Error("failed to process message", "ch_id", r.blockchainCh.ID, "envelope", envelope, "err", err) - r.blockchainCh.Error <- p2p.PeerError{ + case envelope := <-r.blockSyncCh.In: + if err := r.handleMessage(r.blockSyncCh.ID, envelope); err != nil { + r.Logger.Error("failed to process message", "ch_id", r.blockSyncCh.ID, "envelope", envelope, "err", err) + r.blockSyncCh.Error <- p2p.PeerError{ NodeID: envelope.From, Err: err, } } - case envelope := <-r.blockchainOutBridgeCh: - r.blockchainCh.Out <- envelope + case envelope := <-r.blockSyncOutBridgeCh: + r.blockSyncCh.Out <- envelope case <-r.closeCh: - r.Logger.Debug("stopped listening on blockchain channel; closing...") + r.Logger.Debug("stopped listening on block sync channel; closing...") return } @@ -340,7 +340,7 @@ func (r *Reactor) processPeerUpdate(peerUpdate p2p.PeerUpdate) { switch peerUpdate.Status { case p2p.PeerStatusUp: // send a status update the newly added peer - r.blockchainOutBridgeCh <- p2p.Envelope{ + r.blockSyncOutBridgeCh <- p2p.Envelope{ To: peerUpdate.NodeID, Message: &bcproto.StatusResponse{ Base: r.store.Base(), @@ -406,13 +406,13 @@ func (r *Reactor) requestRoutine() { return case request := <-r.requestsCh: - r.blockchainOutBridgeCh <- p2p.Envelope{ + r.blockSyncOutBridgeCh <- p2p.Envelope{ To: request.PeerID, Message: &bcproto.BlockRequest{Height: request.Height}, } case pErr := <-r.errorsCh: - r.blockchainCh.Error <- p2p.PeerError{ + r.blockSyncCh.Error <- p2p.PeerError{ NodeID: pErr.peerID, Err: pErr.err, } @@ -423,7 +423,7 @@ func (r *Reactor) requestRoutine() { go func() { defer r.poolWG.Done() - r.blockchainOutBridgeCh <- p2p.Envelope{ + r.blockSyncOutBridgeCh <- p2p.Envelope{ Broadcast: true, Message: &bcproto.StatusRequest{}, } @@ -554,14 +554,14 @@ FOR_LOOP: // NOTE: We've already removed the peer's request, but we still need // to clean up the rest. peerID := r.pool.RedoRequest(first.Height) - r.blockchainCh.Error <- p2p.PeerError{ + r.blockSyncCh.Error <- p2p.PeerError{ NodeID: peerID, Err: err, } peerID2 := r.pool.RedoRequest(second.Height) if peerID2 != peerID { - r.blockchainCh.Error <- p2p.PeerError{ + r.blockSyncCh.Error <- p2p.PeerError{ NodeID: peerID2, Err: err, } diff --git a/internal/blocksync/v0/reactor_test.go b/internal/blocksync/v0/reactor_test.go index e038b57af..a1ddc02cd 100644 --- a/internal/blocksync/v0/reactor_test.go +++ b/internal/blocksync/v0/reactor_test.go @@ -32,9 +32,9 @@ type reactorTestSuite struct { reactors map[types.NodeID]*Reactor app map[types.NodeID]proxy.AppConns - blockchainChannels map[types.NodeID]*p2p.Channel - peerChans map[types.NodeID]chan p2p.PeerUpdate - peerUpdates map[types.NodeID]*p2p.PeerUpdates + blockSyncChannels map[types.NodeID]*p2p.Channel + peerChans map[types.NodeID]chan p2p.PeerUpdate + peerUpdates map[types.NodeID]*p2p.PeerUpdates blockSync bool } @@ -53,19 +53,19 @@ func setup( "must specify at least one block height (nodes)") rts := &reactorTestSuite{ - logger: log.TestingLogger().With("module", "blockchain", "testCase", t.Name()), - network: p2ptest.MakeNetwork(t, p2ptest.NetworkOptions{NumNodes: numNodes}), - nodes: make([]types.NodeID, 0, numNodes), - reactors: make(map[types.NodeID]*Reactor, numNodes), - app: make(map[types.NodeID]proxy.AppConns, numNodes), - blockchainChannels: make(map[types.NodeID]*p2p.Channel, numNodes), - peerChans: make(map[types.NodeID]chan p2p.PeerUpdate, numNodes), - peerUpdates: make(map[types.NodeID]*p2p.PeerUpdates, numNodes), - blockSync: true, + logger: log.TestingLogger().With("module", "block_sync", "testCase", t.Name()), + network: p2ptest.MakeNetwork(t, p2ptest.NetworkOptions{NumNodes: numNodes}), + nodes: make([]types.NodeID, 0, numNodes), + reactors: make(map[types.NodeID]*Reactor, numNodes), + app: make(map[types.NodeID]proxy.AppConns, numNodes), + blockSyncChannels: make(map[types.NodeID]*p2p.Channel, numNodes), + peerChans: make(map[types.NodeID]chan p2p.PeerUpdate, numNodes), + peerUpdates: make(map[types.NodeID]*p2p.PeerUpdates, numNodes), + blockSync: true, } - chDesc := p2p.ChannelDescriptor{ID: byte(BlockchainChannel)} - rts.blockchainChannels = rts.network.MakeChannelsNoCleanup(t, chDesc, new(bcproto.Message), int(chBuf)) + chDesc := p2p.ChannelDescriptor{ID: byte(BlockSyncChannel)} + rts.blockSyncChannels = rts.network.MakeChannelsNoCleanup(t, chDesc, new(bcproto.Message), int(chBuf)) i := 0 for nodeID := range rts.network.Nodes { @@ -161,7 +161,7 @@ func (rts *reactorTestSuite) addNode(t *testing.T, blockExec, blockStore, nil, - rts.blockchainChannels[nodeID], + rts.blockSyncChannels[nodeID], rts.peerUpdates[nodeID], rts.blockSync, cons.NopMetrics()) @@ -181,7 +181,7 @@ func (rts *reactorTestSuite) start(t *testing.T) { } func TestReactor_AbruptDisconnect(t *testing.T) { - config := cfg.ResetTestRoot("blockchain_reactor_test") + config := cfg.ResetTestRoot("block_sync_reactor_test") defer os.RemoveAll(config.RootDir) genDoc, privVals := factory.RandGenesisDoc(config, 1, false, 30) @@ -216,7 +216,7 @@ func TestReactor_AbruptDisconnect(t *testing.T) { } func TestReactor_SyncTime(t *testing.T) { - config := cfg.ResetTestRoot("blockchain_reactor_test") + config := cfg.ResetTestRoot("block_sync_reactor_test") defer os.RemoveAll(config.RootDir) genDoc, privVals := factory.RandGenesisDoc(config, 1, false, 30) @@ -239,7 +239,7 @@ func TestReactor_SyncTime(t *testing.T) { } func TestReactor_NoBlockResponse(t *testing.T) { - config := cfg.ResetTestRoot("blockchain_reactor_test") + config := cfg.ResetTestRoot("block_sync_reactor_test") defer os.RemoveAll(config.RootDir) genDoc, privVals := factory.RandGenesisDoc(config, 1, false, 30) @@ -286,7 +286,7 @@ func TestReactor_BadBlockStopsPeer(t *testing.T) { // See: https://github.com/tendermint/tendermint/issues/6005 t.SkipNow() - config := cfg.ResetTestRoot("blockchain_reactor_test") + config := cfg.ResetTestRoot("block_sync_reactor_test") defer os.RemoveAll(config.RootDir) maxBlockHeight := int64(48) diff --git a/node/setup.go b/node/setup.go index ceadcd688..e9bfb029a 100644 --- a/node/setup.go +++ b/node/setup.go @@ -362,7 +362,7 @@ func createBlockchainReactor( reactor, err := bcv0.NewReactor( logger, state.Copy(), blockExec, blockStore, csReactor, - channels[bcv0.BlockchainChannel], peerUpdates, blockSync, + channels[bcv0.BlockSyncChannel], peerUpdates, blockSync, metrics, ) if err != nil { @@ -727,7 +727,7 @@ func makeNodeInfo( var bcChannel byte switch config.BlockSync.Version { case cfg.BlockSyncV0: - bcChannel = byte(bcv0.BlockchainChannel) + bcChannel = byte(bcv0.BlockSyncChannel) case cfg.BlockSyncV2: bcChannel = bcv2.BlockchainChannel From 39dee8abc576cbf833d95b49c9de17d8c0bde288 Mon Sep 17 00:00:00 2001 From: Sam Kleinman Date: Mon, 23 Aug 2021 16:41:45 -0400 Subject: [PATCH 16/37] pubsub: improve handling of closed blocking subsciptions. (#6852) --- libs/pubsub/pubsub.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/libs/pubsub/pubsub.go b/libs/pubsub/pubsub.go index 7548470b5..68d1ec941 100644 --- a/libs/pubsub/pubsub.go +++ b/libs/pubsub/pubsub.go @@ -507,7 +507,10 @@ func (state *state) send(msg interface{}, events []types.Event) error { for clientID, subscription := range clientSubscriptions { if cap(subscription.out) == 0 { // block on unbuffered channel - subscription.out <- NewMessage(subscription.id, msg, events) + select { + case subscription.out <- NewMessage(subscription.id, msg, events): + case <-subscription.canceled: + } } else { // don't block on buffered channels select { From ce3c059a0d40a1485b9aa2c87707b4f4fc8104f0 Mon Sep 17 00:00:00 2001 From: "M. J. Fromberger" Date: Mon, 23 Aug 2021 15:06:01 -0700 Subject: [PATCH 17/37] ADR 072: Re-instate a request-for-comments archive. (#6851) This ADR restores a variation of the old Request for Comments documentation that we previously used. The proposal differs from the original formulation, and does not replace ADRs. --- docs/architecture/README.md | 2 + .../adr-072-request-for-comments.md | 105 ++++++++++++++++++ docs/rfc/README.md | 40 +++++++ docs/rfc/rfc-template.md | 35 ++++++ 4 files changed, 182 insertions(+) create mode 100644 docs/architecture/adr-072-request-for-comments.md create mode 100644 docs/rfc/README.md create mode 100644 docs/rfc/rfc-template.md diff --git a/docs/architecture/README.md b/docs/architecture/README.md index 7025a72f6..f6c12996f 100644 --- a/docs/architecture/README.md +++ b/docs/architecture/README.md @@ -98,3 +98,5 @@ Note the context/background should be written in the present tense. - [ADR-045: ABCI-Evidence](./adr-045-abci-evidence.md) - [ADR-057: RPC](./adr-057-RPC.md) - [ADR-069: Node Initialization](./adr-069-flexible-node-initialization.md) +- [ADR-071: Proposer-Based Timestamps](adr-071-proposer-based-timestamps.md) +- [ADR-072: Restore Requests for Comments](./adr-072-request-for-comments.md) diff --git a/docs/architecture/adr-072-request-for-comments.md b/docs/architecture/adr-072-request-for-comments.md new file mode 100644 index 000000000..7eb22ebc9 --- /dev/null +++ b/docs/architecture/adr-072-request-for-comments.md @@ -0,0 +1,105 @@ +# ADR 72: Restore Requests for Comments + +## Changelog + +- 20-Aug-2021: Initial draft (@creachadair) + +## Status + +Proposed + +## Context + +In the past, we kept a collection of Request for Comments (RFC) documents in `docs/rfc`. +Prior to the creation of the ADR process, these documents were used to document +design and implementation decisions about Tendermint Core. The RFC directory +was removed in favor of ADRs, in commit 3761aa69 (PR +[\#6345](https://github.com/tendermint/tendermint/pull/6345)). + +For issues where an explicit design decision or implementation change is +required, an ADR is generally preferable to an open-ended RFC: An ADR is +relatively narrowly-focused, identifies a specific design or implementation +question, and documents the consensus answer to that question. + +Some discussions are more open-ended, however, or don't require a specific +decision to be made (yet). Such conversations are still valuable to document, +and several members of the Tendermint team have been doing so by writing gists +or Google docs to share them around. That works well enough in the moment, but +gists do not support any kind of collaborative editing, and both gists and docs +are hard to discover after the fact. Google docs have much better collaborative +editing, but are worse for discoverability, especially when contributors span +different Google accounts. + +Discoverability is important, because these kinds of open-ended discussions are +useful to people who come later -- either as new team members or as outside +contributors seeking to use and understand the thoughts behind our designs and +the architectural decisions that arose from those discussion. + +With these in mind, I propose that: + +- We re-create a new, initially empty `docs/rfc` directory in the repository, + and use it to capture these kinds of open-ended discussions in supplement to + ADRs. + +- Unlike in the previous RFC scheme, documents in this new directory will + _not_ be used directly for decision-making. This is the key difference + between an RFC and an ADR. + + Instead, an RFC will exist to document background, articulate general + principles, and serve as a historical record of discussion and motivation. + + In this system, an RFC may _only_ result in a decision indirectly, via ADR + documents created in response to the RFC. + + **In short:** If a decision is required, write an ADR; otherwise if a + sufficiently broad discussion is needed, write an RFC. + +Just so that there is a consistent format, I also propose that: + +- RFC files are named `rfc-XXX-title.{md,rst,txt}` and are written in plain + text, Markdown, or ReStructured Text. + +- Like an ADR, an RFC should include a high-level change log at the top of the + document, and sections for: + + * Abstract: A brief, high-level synopsis of the topic. + * Background: Any background necessary to understand the topic. + * Discussion: Detailed discussion of the issue being considered. + +- Unlike an ADR, an RFC does _not_ include sections for Decisions, Detailed + Design, or evaluation of proposed solutions. If an RFC leads to a proposal + for an actual architectural change, that must be recorded in an ADR in the + usual way, and may refer back to the RFC in its References section. + +## Alternative Approaches + +Leaving aside implementation details, the main alternative to this proposal is +to leave things as they are now, with ADRs as the only log of record and other +discussions being held informally in whatever medium is convenient at the time. + +## Decision + +(pending) + +## Detailed Design + +- Create a new `docs/rfc` directory in the `tendermint` repository. Note that + this proposal intentionally does _not_ pull back the previous contents of + that path from Git history, as those documents were appropriately merged into + the ADR process. + +- Create a `README.md` for RFCs that explains the rules and their relationship + to ADRs. + +- Create an `rfc-template.md` file for RFC files. + +## Consequences + +### Positive + +- We will have a more discoverable place to record open-ended discussions that + do not immediately result in a design change. + +### Negative + +- Potentially some people could be confused about the RFC/ADR distinction. diff --git a/docs/rfc/README.md b/docs/rfc/README.md new file mode 100644 index 000000000..c05853aca --- /dev/null +++ b/docs/rfc/README.md @@ -0,0 +1,40 @@ +--- +order: 1 +parent: + order: false +--- + +# Requests for Comments + +A Request for Comments (RFC) is a record of discussion on an open-ended topic +related to the design and implementation of Tendermint Core, for which no +immediate decision is required. + +The purpose of an RFC is to serve as a historical record of a high-level +discussion that might otherwise only be recorded in an ad hoc way (for example, +via gists or Google docs) that are difficult to discover for someone after the +fact. An RFC _may_ give rise to more specific architectural _decisions_ for +Tendermint, but those decisions must be recorded separately in [Architecture +Decision Records (ADR)](./../architecture). + +As a rule of thumb, if you can articulate a specific question that needs to be +answered, write an ADR. If you need to explore the topic and get input from +others to know what questions need to be answered, an RFC may be appropriate. + +## RFC Content + +An RFC should provide: + +- A **changelog**, documenting when and how the RFC has changed. +- An **abstract**, briefly summarizing the topic so the reader can quickly tell + whether it is relevant to their interest. +- Any **background** a reader will need to understand and participate in the + substance of the discussion (links to other documents are fine here). +- The **discussion**, the primary content of the document. + +The [rfc-template.md](./rfc-template.md) file includes placeholders for these +sections. + +## Table of Contents + + diff --git a/docs/rfc/rfc-template.md b/docs/rfc/rfc-template.md new file mode 100644 index 000000000..b3f404775 --- /dev/null +++ b/docs/rfc/rfc-template.md @@ -0,0 +1,35 @@ +# RFC {RFC-NUMBER}: {TITLE} + +## Changelog + +- {date}: {changelog} + +## Abstract + +> A brief high-level synopsis of the topic of discussion for this RFC, ideally +> just a few sentences. This should help the reader quickly decide whether the +> rest of the discussion is relevant to their interest. + +## Background + +> Any context or orientation needed for a reader to understand and participate +> in the substance of the Discussion. If necessary, this section may include +> links to other documentation or sources rather than restating existing +> material, but should provide enough detail that the reader can tell what they +> need to read to be up-to-date. + +### References + +> Links to external materials needed to follow the discussion may be added here. +> +> In addition, if the discussion in a request for comments leads to any design +> decisions, it may be helpful to add links to the ADR documents here after the +> discussion has settled. + +## Discussion + +> This section contains the core of the discussion. +> +> There is no fixed format for this section, but ideally changes to this +> section should be updated before merging to reflect any discussion that took +> place on the PR that made those changes. From d7c3a8f682d2acdcdce859d86e732d33db1c9d1e Mon Sep 17 00:00:00 2001 From: Sam Kleinman Date: Tue, 24 Aug 2021 11:43:13 -0400 Subject: [PATCH 18/37] time: make median time library type private (#6853) This is a very minor change, but I was looking through the code, and this seems like it shouldn't be exported or used more broadly, so I've moved it out. --- libs/time/time.go | 41 --------------------------- state/state.go | 7 ++--- state/time.go | 46 +++++++++++++++++++++++++++++++ {libs/time => state}/time_test.go | 39 +++++++++++++------------- 4 files changed, 69 insertions(+), 64 deletions(-) create mode 100644 state/time.go rename {libs/time => state}/time_test.go (50%) diff --git a/libs/time/time.go b/libs/time/time.go index 022bdf574..786f9bbb4 100644 --- a/libs/time/time.go +++ b/libs/time/time.go @@ -1,7 +1,6 @@ package time import ( - "sort" "time" ) @@ -16,43 +15,3 @@ func Now() time.Time { func Canonical(t time.Time) time.Time { return t.Round(0).UTC() } - -// WeightedTime for computing a median. -type WeightedTime struct { - Time time.Time - Weight int64 -} - -// NewWeightedTime with time and weight. -func NewWeightedTime(time time.Time, weight int64) *WeightedTime { - return &WeightedTime{ - Time: time, - Weight: weight, - } -} - -// WeightedMedian computes weighted median time for a given array of WeightedTime and the total voting power. -func WeightedMedian(weightedTimes []*WeightedTime, totalVotingPower int64) (res time.Time) { - median := totalVotingPower / 2 - - sort.Slice(weightedTimes, func(i, j int) bool { - if weightedTimes[i] == nil { - return false - } - if weightedTimes[j] == nil { - return true - } - return weightedTimes[i].Time.UnixNano() < weightedTimes[j].Time.UnixNano() - }) - - for _, weightedTime := range weightedTimes { - if weightedTime != nil { - if median <= weightedTime.Weight { - res = weightedTime.Time - break - } - median -= weightedTime.Weight - } - } - return -} diff --git a/state/state.go b/state/state.go index 132a86fda..5862162d1 100644 --- a/state/state.go +++ b/state/state.go @@ -9,7 +9,6 @@ import ( "github.com/gogo/protobuf/proto" - tmtime "github.com/tendermint/tendermint/libs/time" tmstate "github.com/tendermint/tendermint/proto/tendermint/state" tmversion "github.com/tendermint/tendermint/proto/tendermint/version" "github.com/tendermint/tendermint/types" @@ -287,7 +286,7 @@ func (state State) MakeBlock( // the votes sent by honest processes, i.e., a faulty processes can not arbitrarily increase or decrease the // computed value. func MedianTime(commit *types.Commit, validators *types.ValidatorSet) time.Time { - weightedTimes := make([]*tmtime.WeightedTime, len(commit.Signatures)) + weightedTimes := make([]*weightedTime, len(commit.Signatures)) totalVotingPower := int64(0) for i, commitSig := range commit.Signatures { @@ -298,11 +297,11 @@ func MedianTime(commit *types.Commit, validators *types.ValidatorSet) time.Time // If there's no condition, TestValidateBlockCommit panics; not needed normally. if validator != nil { totalVotingPower += validator.VotingPower - weightedTimes[i] = tmtime.NewWeightedTime(commitSig.Timestamp, validator.VotingPower) + weightedTimes[i] = newWeightedTime(commitSig.Timestamp, validator.VotingPower) } } - return tmtime.WeightedMedian(weightedTimes, totalVotingPower) + return weightedMedian(weightedTimes, totalVotingPower) } //------------------------------------------------------------------------ diff --git a/state/time.go b/state/time.go new file mode 100644 index 000000000..c0770b3af --- /dev/null +++ b/state/time.go @@ -0,0 +1,46 @@ +package state + +import ( + "sort" + "time" +) + +// weightedTime for computing a median. +type weightedTime struct { + Time time.Time + Weight int64 +} + +// newWeightedTime with time and weight. +func newWeightedTime(time time.Time, weight int64) *weightedTime { + return &weightedTime{ + Time: time, + Weight: weight, + } +} + +// weightedMedian computes weighted median time for a given array of WeightedTime and the total voting power. +func weightedMedian(weightedTimes []*weightedTime, totalVotingPower int64) (res time.Time) { + median := totalVotingPower / 2 + + sort.Slice(weightedTimes, func(i, j int) bool { + if weightedTimes[i] == nil { + return false + } + if weightedTimes[j] == nil { + return true + } + return weightedTimes[i].Time.UnixNano() < weightedTimes[j].Time.UnixNano() + }) + + for _, weightedTime := range weightedTimes { + if weightedTime != nil { + if median <= weightedTime.Weight { + res = weightedTime.Time + break + } + median -= weightedTime.Weight + } + } + return +} diff --git a/libs/time/time_test.go b/state/time_test.go similarity index 50% rename from libs/time/time_test.go rename to state/time_test.go index 1b1a30e50..893ade7ea 100644 --- a/libs/time/time_test.go +++ b/state/time_test.go @@ -1,54 +1,55 @@ -package time +package state import ( "testing" "time" "github.com/stretchr/testify/assert" + tmtime "github.com/tendermint/tendermint/libs/time" ) func TestWeightedMedian(t *testing.T) { - m := make([]*WeightedTime, 3) + m := make([]*weightedTime, 3) - t1 := Now() + t1 := tmtime.Now() t2 := t1.Add(5 * time.Second) t3 := t1.Add(10 * time.Second) - m[2] = NewWeightedTime(t1, 33) // faulty processes - m[0] = NewWeightedTime(t2, 40) // correct processes - m[1] = NewWeightedTime(t3, 27) // correct processes + m[2] = newWeightedTime(t1, 33) // faulty processes + m[0] = newWeightedTime(t2, 40) // correct processes + m[1] = newWeightedTime(t3, 27) // correct processes totalVotingPower := int64(100) - median := WeightedMedian(m, totalVotingPower) + median := weightedMedian(m, totalVotingPower) assert.Equal(t, t2, median) // median always returns value between values of correct processes assert.Equal(t, true, (median.After(t1) || median.Equal(t1)) && (median.Before(t3) || median.Equal(t3))) - m[1] = NewWeightedTime(t1, 40) // correct processes - m[2] = NewWeightedTime(t2, 27) // correct processes - m[0] = NewWeightedTime(t3, 33) // faulty processes + m[1] = newWeightedTime(t1, 40) // correct processes + m[2] = newWeightedTime(t2, 27) // correct processes + m[0] = newWeightedTime(t3, 33) // faulty processes totalVotingPower = int64(100) - median = WeightedMedian(m, totalVotingPower) + median = weightedMedian(m, totalVotingPower) assert.Equal(t, t2, median) // median always returns value between values of correct processes assert.Equal(t, true, (median.After(t1) || median.Equal(t1)) && (median.Before(t2) || median.Equal(t2))) - m = make([]*WeightedTime, 8) + m = make([]*weightedTime, 8) t4 := t1.Add(15 * time.Second) t5 := t1.Add(60 * time.Second) - m[3] = NewWeightedTime(t1, 10) // correct processes - m[1] = NewWeightedTime(t2, 10) // correct processes - m[5] = NewWeightedTime(t2, 10) // correct processes - m[4] = NewWeightedTime(t3, 23) // faulty processes - m[0] = NewWeightedTime(t4, 20) // correct processes - m[7] = NewWeightedTime(t5, 10) // faulty processes + m[3] = newWeightedTime(t1, 10) // correct processes + m[1] = newWeightedTime(t2, 10) // correct processes + m[5] = newWeightedTime(t2, 10) // correct processes + m[4] = newWeightedTime(t3, 23) // faulty processes + m[0] = newWeightedTime(t4, 20) // correct processes + m[7] = newWeightedTime(t5, 10) // faulty processes totalVotingPower = int64(83) - median = WeightedMedian(m, totalVotingPower) + median = weightedMedian(m, totalVotingPower) assert.Equal(t, t3, median) // median always returns value between values of correct processes assert.Equal(t, true, (median.After(t1) || median.Equal(t1)) && From d8642a941e3602d1f7c89d29c9d3e844e14e5919 Mon Sep 17 00:00:00 2001 From: Sam Kleinman Date: Tue, 24 Aug 2021 12:06:27 -0400 Subject: [PATCH 19/37] cmd: remove deprecated snakes (#6854) just following up on a deprecation. --- CHANGELOG_PENDING.md | 4 ++-- cmd/tendermint/commands/gen_node_key.go | 8 +++----- cmd/tendermint/commands/gen_validator.go | 8 +++----- cmd/tendermint/commands/probe_upnp.go | 8 +++----- cmd/tendermint/commands/replay.go | 6 ++---- cmd/tendermint/commands/reset_priv_validator.go | 16 ++++++---------- cmd/tendermint/commands/root.go | 8 -------- cmd/tendermint/commands/show_node_id.go | 8 +++----- cmd/tendermint/commands/show_validator.go | 8 +++----- 9 files changed, 25 insertions(+), 49 deletions(-) diff --git a/CHANGELOG_PENDING.md b/CHANGELOG_PENDING.md index bb5221bb5..233d11887 100644 --- a/CHANGELOG_PENDING.md +++ b/CHANGELOG_PENDING.md @@ -25,7 +25,7 @@ Friendly reminder: We have a [bug bounty program](https://hackerone.com/tendermi - [rpc/grpc] \#6725 Mark gRPC in the RPC layer as deprecated. - [blockchain/v2] \#6730 Fast Sync v2 is deprecated, please use v0 - [rpc] \#6820 Update RPC methods to reflect changes in the p2p layer, disabling support for `UnsafeDialPeers` and `UnsafeDialPeers` when used with the new p2p layer, and changing the response format of the peer list in `NetInfo` for all users. - + - [cli] \#6854 Remove deprecated snake case commands. (@tychoish) - Apps - [ABCI] \#6408 Change the `key` and `value` fields from `[]byte` to `string` in the `EventAttribute` type. (@alexanderbez) - [ABCI] \#5447 Remove `SetOption` method from `ABCI.Client` interface @@ -158,4 +158,4 @@ Friendly reminder: We have a [bug bounty program](https://hackerone.com/tendermi - [rpc] \#6507 Ensure RPC client can handle URLs without ports (@JayT106) - [statesync] \#6463 Adds Reverse Sync feature to fetch historical light blocks after state sync in order to verify any evidence (@cmwaters) - [fastsync] \#6590 Update the metrics during fast-sync (@JayT106) -- [gitignore] \#6668 Fix gitignore of abci-cli (@tanyabouman) \ No newline at end of file +- [gitignore] \#6668 Fix gitignore of abci-cli (@tanyabouman) diff --git a/cmd/tendermint/commands/gen_node_key.go b/cmd/tendermint/commands/gen_node_key.go index f796f4b7f..d8b493e3c 100644 --- a/cmd/tendermint/commands/gen_node_key.go +++ b/cmd/tendermint/commands/gen_node_key.go @@ -12,11 +12,9 @@ import ( // GenNodeKeyCmd allows the generation of a node key. It prints JSON-encoded // NodeKey to the standard output. var GenNodeKeyCmd = &cobra.Command{ - Use: "gen-node-key", - Aliases: []string{"gen_node_key"}, - Short: "Generate a new node key", - RunE: genNodeKey, - PreRun: deprecateSnakeCase, + Use: "gen-node-key", + Short: "Generate a new node key", + RunE: genNodeKey, } func genNodeKey(cmd *cobra.Command, args []string) error { diff --git a/cmd/tendermint/commands/gen_validator.go b/cmd/tendermint/commands/gen_validator.go index 09f84b09e..830518ce9 100644 --- a/cmd/tendermint/commands/gen_validator.go +++ b/cmd/tendermint/commands/gen_validator.go @@ -13,11 +13,9 @@ import ( // GenValidatorCmd allows the generation of a keypair for a // validator. var GenValidatorCmd = &cobra.Command{ - Use: "gen-validator", - Aliases: []string{"gen_validator"}, - Short: "Generate new validator keypair", - RunE: genValidator, - PreRun: deprecateSnakeCase, + Use: "gen-validator", + Short: "Generate new validator keypair", + RunE: genValidator, } func init() { diff --git a/cmd/tendermint/commands/probe_upnp.go b/cmd/tendermint/commands/probe_upnp.go index 4471024f9..4c71e099a 100644 --- a/cmd/tendermint/commands/probe_upnp.go +++ b/cmd/tendermint/commands/probe_upnp.go @@ -11,11 +11,9 @@ import ( // ProbeUpnpCmd adds capabilities to test the UPnP functionality. var ProbeUpnpCmd = &cobra.Command{ - Use: "probe-upnp", - Aliases: []string{"probe_upnp"}, - Short: "Test UPnP functionality", - RunE: probeUpnp, - PreRun: deprecateSnakeCase, + Use: "probe-upnp", + Short: "Test UPnP functionality", + RunE: probeUpnp, } func probeUpnp(cmd *cobra.Command, args []string) error { diff --git a/cmd/tendermint/commands/replay.go b/cmd/tendermint/commands/replay.go index 6e736bca2..e92274042 100644 --- a/cmd/tendermint/commands/replay.go +++ b/cmd/tendermint/commands/replay.go @@ -17,11 +17,9 @@ var ReplayCmd = &cobra.Command{ // ReplayConsoleCmd allows replaying of messages from the WAL in a // console. var ReplayConsoleCmd = &cobra.Command{ - Use: "replay-console", - Aliases: []string{"replay_console"}, - Short: "Replay messages from WAL in a console", + Use: "replay-console", + Short: "Replay messages from WAL in a console", Run: func(cmd *cobra.Command, args []string) { consensus.RunReplayFile(config.BaseConfig, config.Consensus, true) }, - PreRun: deprecateSnakeCase, } diff --git a/cmd/tendermint/commands/reset_priv_validator.go b/cmd/tendermint/commands/reset_priv_validator.go index 046780ef1..8745e55d8 100644 --- a/cmd/tendermint/commands/reset_priv_validator.go +++ b/cmd/tendermint/commands/reset_priv_validator.go @@ -14,11 +14,9 @@ import ( // ResetAllCmd removes the database of this Tendermint core // instance. var ResetAllCmd = &cobra.Command{ - Use: "unsafe-reset-all", - Aliases: []string{"unsafe_reset_all"}, - Short: "(unsafe) Remove all the data and WAL, reset this node's validator to genesis state", - RunE: resetAll, - PreRun: deprecateSnakeCase, + Use: "unsafe-reset-all", + Short: "(unsafe) Remove all the data and WAL, reset this node's validator to genesis state", + RunE: resetAll, } var keepAddrBook bool @@ -31,11 +29,9 @@ func init() { // ResetPrivValidatorCmd resets the private validator files. var ResetPrivValidatorCmd = &cobra.Command{ - Use: "unsafe-reset-priv-validator", - Aliases: []string{"unsafe_reset_priv_validator"}, - Short: "(unsafe) Reset this node's validator to genesis state", - RunE: resetPrivValidator, - PreRun: deprecateSnakeCase, + Use: "unsafe-reset-priv-validator", + Short: "(unsafe) Reset this node's validator to genesis state", + RunE: resetPrivValidator, } // XXX: this is totally unsafe. diff --git a/cmd/tendermint/commands/root.go b/cmd/tendermint/commands/root.go index 02f260de5..2289ae363 100644 --- a/cmd/tendermint/commands/root.go +++ b/cmd/tendermint/commands/root.go @@ -2,7 +2,6 @@ package commands import ( "fmt" - "strings" "time" "github.com/spf13/cobra" @@ -65,10 +64,3 @@ var RootCmd = &cobra.Command{ return nil }, } - -// deprecateSnakeCase is a util function for 0.34.1. Should be removed in 0.35 -func deprecateSnakeCase(cmd *cobra.Command, args []string) { - if strings.Contains(cmd.CalledAs(), "_") { - fmt.Println("Deprecated: snake_case commands will be replaced by hyphen-case commands in the next major release") - } -} diff --git a/cmd/tendermint/commands/show_node_id.go b/cmd/tendermint/commands/show_node_id.go index 7a5814c3b..488f4c322 100644 --- a/cmd/tendermint/commands/show_node_id.go +++ b/cmd/tendermint/commands/show_node_id.go @@ -8,11 +8,9 @@ import ( // ShowNodeIDCmd dumps node's ID to the standard output. var ShowNodeIDCmd = &cobra.Command{ - Use: "show-node-id", - Aliases: []string{"show_node_id"}, - Short: "Show this node's ID", - RunE: showNodeID, - PreRun: deprecateSnakeCase, + Use: "show-node-id", + Short: "Show this node's ID", + RunE: showNodeID, } func showNodeID(cmd *cobra.Command, args []string) error { diff --git a/cmd/tendermint/commands/show_validator.go b/cmd/tendermint/commands/show_validator.go index 240ed943f..47b372c61 100644 --- a/cmd/tendermint/commands/show_validator.go +++ b/cmd/tendermint/commands/show_validator.go @@ -16,11 +16,9 @@ import ( // ShowValidatorCmd adds capabilities for showing the validator info. var ShowValidatorCmd = &cobra.Command{ - Use: "show-validator", - Aliases: []string{"show_validator"}, - Short: "Show this node's validator info", - RunE: showValidator, - PreRun: deprecateSnakeCase, + Use: "show-validator", + Short: "Show this node's validator info", + RunE: showValidator, } func showValidator(cmd *cobra.Command, args []string) error { From 6d5ff590c384fcc9d1f32f4adef1c67670821394 Mon Sep 17 00:00:00 2001 From: Tess Rinearson Date: Tue, 24 Aug 2021 19:07:53 +0200 Subject: [PATCH 20/37] contributing: remove release_notes.md reference (#6846) --- .github/workflows/release.yml | 7 ++----- CONTRIBUTING.md | 2 -- release_notes.md | 1 - 3 files changed, 2 insertions(+), 8 deletions(-) delete mode 100644 release_notes.md diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 771b8ab7c..dd18e750b 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -2,7 +2,7 @@ name: "Release" on: push: - branches: + branches: - "RC[0-9]/**" tags: - "v[0-9]+.[0-9]+.[0-9]+" # Push events to matching v*, i.e. v1.0, v20.15.10 @@ -20,9 +20,6 @@ jobs: with: go-version: '1.16' - - run: echo https://github.com/tendermint/tendermint/blob/${GITHUB_REF#refs/tags/}/CHANGELOG.md#${GITHUB_REF#refs/tags/} > ../release_notes.md - if: startsWith(github.ref, 'refs/tags/') - - name: Build uses: goreleaser/goreleaser-action@v2 if: ${{ github.event_name == 'pull_request' }} @@ -35,6 +32,6 @@ jobs: if: startsWith(github.ref, 'refs/tags/') with: version: latest - args: release --rm-dist --release-notes=../release_notes.md + args: release --rm-dist env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 79729cc1b..23bfafcdf 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -328,7 +328,6 @@ If there were no release candidates, begin by creating a backport branch, as des - Bump TMVersionDefault version in `version.go` - Bump P2P and block protocol versions in `version.go`, if necessary - Bump ABCI protocol version in `version.go`, if necessary - - Add any release notes you would like to be added to the body of the release to `release_notes.md`. 4. Open a PR with these changes against the backport branch. 5. Once these changes are on the backport branch, push a tag with prepared release details. This will trigger the actual release `v0.35.0`. @@ -355,7 +354,6 @@ To create a minor release: - Bump the ABCI version number, if necessary. (Note that ABCI follows semver, and that ABCI versions are the only versions which can change during minor releases, and only field additions are valid minor changes.) - - Add any release notes you would like to be added to the body of the release to `release_notes.md`. 4. Open a PR with these changes that will land them back on `v0.35.x` 5. Once this change has landed on the backport branch, make sure to pull it locally, then push a tag. - `git tag -a v0.35.1 -m 'Release v0.35.1'` diff --git a/release_notes.md b/release_notes.md deleted file mode 100644 index a537871c5..000000000 --- a/release_notes.md +++ /dev/null @@ -1 +0,0 @@ - From bc2b529b95fd74b05e7b966e007b31fee186867e Mon Sep 17 00:00:00 2001 From: William Banfield <4561443+williambanfield@users.noreply.github.com> Date: Tue, 24 Aug 2021 14:12:06 -0400 Subject: [PATCH 21/37] inspect: add inspect mode for debugging crashed tendermint node (#6785) EDIT: Updated, see [comment below]( https://github.com/tendermint/tendermint/pull/6785#issuecomment-897793175) This change adds a sketch of the `Debug` mode. This change adds a `Debug` struct to the node package. This `Debug` struct is intended to be created and started by a command in the `cmd` directory. The `Debug` struct runs the RPC server on the data directories: both the state store and the block store. This change required a good deal of refactoring. Namely, a new `rpc.go` file was added to the `node` package. This file encapsulates functions for starting RPC servers used by nodes. A potential additional change is to further factor this code into shared code _in_ the `rpc` package. Minor API tweaks were also made that seemed appropriate such as the mechanism for fetching routes from the `rpc/core` package. Additional work is required to register the `Debug` service as a command in the `cmd` directory but I am looking for feedback on if this direction seems appropriate before diving much further. closes: #5908 --- CHANGELOG_PENDING.md | 3 + cmd/tendermint/commands/inspect.go | 87 ++++ cmd/tendermint/main.go | 1 + docs/tools/debugging/README.md | 27 ++ docs/tools/debugging/pro.md | 35 +- go.mod | 1 + go.sum | 1 + inspect/doc.go | 36 ++ inspect/inspect.go | 149 +++++++ inspect/inspect_test.go | 583 +++++++++++++++++++++++++ inspect/rpc/rpc.go | 143 ++++++ light/proxy/proxy.go | 2 +- node/node.go | 27 +- node/setup.go | 58 +-- rpc/core/env.go | 17 +- rpc/jsonrpc/jsonrpc_test.go | 4 +- rpc/jsonrpc/server/http_server.go | 6 +- rpc/jsonrpc/server/http_server_test.go | 3 +- rpc/jsonrpc/test/main.go | 2 +- state/indexer/indexer_service.go | 66 +-- state/indexer/sink/sink.go | 65 +++ 21 files changed, 1200 insertions(+), 116 deletions(-) create mode 100644 cmd/tendermint/commands/inspect.go create mode 100644 inspect/doc.go create mode 100644 inspect/inspect.go create mode 100644 inspect/inspect_test.go create mode 100644 inspect/rpc/rpc.go create mode 100644 state/indexer/sink/sink.go diff --git a/CHANGELOG_PENDING.md b/CHANGELOG_PENDING.md index 233d11887..6c25ef89e 100644 --- a/CHANGELOG_PENDING.md +++ b/CHANGELOG_PENDING.md @@ -24,6 +24,8 @@ Friendly reminder: We have a [bug bounty program](https://hackerone.com/tendermi - [fastsync/rpc] \#6620 Add TotalSyncedTime & RemainingTime to SyncInfo in /status RPC (@JayT106) - [rpc/grpc] \#6725 Mark gRPC in the RPC layer as deprecated. - [blockchain/v2] \#6730 Fast Sync v2 is deprecated, please use v0 + - [rpc] Add genesis_chunked method to support paginated and parallel fetching of large genesis documents. + - [rpc/jsonrpc/server] \#6785 `Listen` function updated to take an `int` argument, `maxOpenConnections`, instead of an entire config object. (@williambanfield) - [rpc] \#6820 Update RPC methods to reflect changes in the p2p layer, disabling support for `UnsafeDialPeers` and `UnsafeDialPeers` when used with the new p2p layer, and changing the response format of the peer list in `NetInfo` for all users. - [cli] \#6854 Remove deprecated snake case commands. (@tychoish) - Apps @@ -105,6 +107,7 @@ Friendly reminder: We have a [bug bounty program](https://hackerone.com/tendermi - [config/indexer] \#6411 Introduce support for custom event indexing data sources, specifically PostgreSQL. (@JayT106) - [fastsync/event] \#6619 Emit fastsync status event when switching consensus/fastsync (@JayT106) - [statesync/event] \#6700 Emit statesync status start/end event (@JayT106) +- [inspect] \#6785 Add a new `inspect` command for introspecting the state and block store of a crashed tendermint node. (@williambanfield) ### IMPROVEMENTS diff --git a/cmd/tendermint/commands/inspect.go b/cmd/tendermint/commands/inspect.go new file mode 100644 index 000000000..de31d33d4 --- /dev/null +++ b/cmd/tendermint/commands/inspect.go @@ -0,0 +1,87 @@ +package commands + +import ( + "context" + "os" + "os/signal" + "syscall" + + "github.com/spf13/cobra" + + cfg "github.com/tendermint/tendermint/config" + "github.com/tendermint/tendermint/inspect" + "github.com/tendermint/tendermint/state" + "github.com/tendermint/tendermint/state/indexer/sink" + "github.com/tendermint/tendermint/store" + "github.com/tendermint/tendermint/types" +) + +// InspectCmd is the command for starting an inspect server. +var InspectCmd = &cobra.Command{ + Use: "inspect", + Short: "Run an inspect server for investigating Tendermint state", + Long: ` + inspect runs a subset of Tendermint's RPC endpoints that are useful for debugging + issues with Tendermint. + + When the Tendermint consensus engine detects inconsistent state, it will crash the + tendermint process. Tendermint will not start up while in this inconsistent state. + The inspect command can be used to query the block and state store using Tendermint + RPC calls to debug issues of inconsistent state. + `, + + RunE: runInspect, +} + +func init() { + InspectCmd.Flags(). + String("rpc.laddr", + config.RPC.ListenAddress, "RPC listenener address. Port required") + InspectCmd.Flags(). + String("db-backend", + config.DBBackend, "database backend: goleveldb | cleveldb | boltdb | rocksdb | badgerdb") + InspectCmd.Flags(). + String("db-dir", config.DBPath, "database directory") +} + +func runInspect(cmd *cobra.Command, args []string) error { + ctx, cancel := context.WithCancel(cmd.Context()) + defer cancel() + + c := make(chan os.Signal, 1) + signal.Notify(c, syscall.SIGTERM, syscall.SIGINT) + go func() { + <-c + cancel() + }() + + blockStoreDB, err := cfg.DefaultDBProvider(&cfg.DBContext{ID: "blockstore", Config: config}) + if err != nil { + return err + } + blockStore := store.NewBlockStore(blockStoreDB) + stateDB, err := cfg.DefaultDBProvider(&cfg.DBContext{ID: "state", Config: config}) + if err != nil { + if err := blockStoreDB.Close(); err != nil { + logger.Error("error closing block store db", "error", err) + } + return err + } + genDoc, err := types.GenesisDocFromFile(config.GenesisFile()) + if err != nil { + return err + } + sinks, err := sink.EventSinksFromConfig(config, cfg.DefaultDBProvider, genDoc.ChainID) + if err != nil { + return err + } + stateStore := state.NewStore(stateDB) + + ins := inspect.New(config.RPC, blockStore, stateStore, sinks, logger) + + logger.Info("starting inspect server") + if err := ins.Run(ctx); err != nil { + return err + } + return nil +} diff --git a/cmd/tendermint/main.go b/cmd/tendermint/main.go index b092f5645..c006c297d 100644 --- a/cmd/tendermint/main.go +++ b/cmd/tendermint/main.go @@ -28,6 +28,7 @@ func main() { cmd.ShowNodeIDCmd, cmd.GenNodeKeyCmd, cmd.VersionCmd, + cmd.InspectCmd, cmd.MakeKeyMigrateCommand(), debug.DebugCmd, cli.NewCompletionCmd(rootCmd, true), diff --git a/docs/tools/debugging/README.md b/docs/tools/debugging/README.md index 2932f6e86..053b43624 100644 --- a/docs/tools/debugging/README.md +++ b/docs/tools/debugging/README.md @@ -62,3 +62,30 @@ given destination directory. Each archive will contain: Note: goroutine.out and heap.out will only be written if a profile address is provided and is operational. This command is blocking and will log any error. + +## Tendermint Inspect + +Tendermint includes an `inspect` command for querying Tendermint's state store and block +store over Tendermint RPC. + +When the Tendermint consensus engine detects inconsistent state, it will crash the +entire Tendermint process. +While in this inconsistent state, a node running Tendermint's consensus engine will not start up. +The `inspect` command runs only a subset of Tendermint's RPC endpoints for querying the block store +and state store. +`inspect` allows operators to query a read-only view of the stage. +`inspect` does not run the consensus engine at all and can therefore be used to debug +processes that have crashed due to inconsistent state. + + +To start the `inspect` process, run +```bash +tendermint inspect +``` + +### RPC endpoints +The list of available RPC endpoints can be found by making a request to the RPC port. +For an `inspect` process running on `127.0.0.1:26657`, navigate your browser to +`http://127.0.0.1:26657/` to retrieve the list of enabled RPC endpoints. + +Additional information on the Tendermint RPC endpoints can be found in the [rpc documentation](https://docs.tendermint.com/master/rpc). diff --git a/docs/tools/debugging/pro.md b/docs/tools/debugging/pro.md index 3342deb49..b43ed5cba 100644 --- a/docs/tools/debugging/pro.md +++ b/docs/tools/debugging/pro.md @@ -64,13 +64,42 @@ It won’t kill the node, but it will gather all of the above data and package i At this point, depending on how severe the degradation is, you may want to restart the process. +## Tendermint Inspect + +What if the Tendermint node will not start up due to inconsistent consensus state? + +When a node running the Tendermint consensus engine detects an inconsistent state +it will crash the entire Tendermint process. +The Tendermint consensus engine cannot be run in this inconsistent state and the so node +will fail to start up as a result. +The Tendermint RPC server can provide valuable information for debugging in this situation. +The Tendermint `inspect` command will run a subset of the Tendermint RPC server +that is useful for debugging inconsistent state. + +### Running inspect + +Start up the `inspect` tool on the machine where Tendermint crashed using: +```bash +tendermint inspect --home= +``` + +`inspect` will use the data directory specified in your Tendermint configuration file. +`inspect` will also run the RPC server at the address specified in your Tendermint configuration file. + +### Using inspect + +With the `inspect` server running, you can access RPC endpoints that are critically important +for debugging. +Calling the `/status`, `/consensus_state` and `/dump_consensus_state` RPC endpoint +will return useful information about the Tendermint consensus state. + ## Outro -We’re hoping that the `tendermint debug` subcommand will become de facto the first response to any accidents. +We’re hoping that these Tendermint tools will become de facto the first response for any accidents. -Let us know what your experience has been so far! Have you had a chance to try `tendermint debug` yet? +Let us know what your experience has been so far! Have you had a chance to try `tendermint debug` or `tendermint inspect` yet? -Join our chat, where we discuss the current issues and future improvements. +Join our [discord chat](https://discord.gg/vcExX9T), where we discuss the current issues and future improvements. — diff --git a/go.mod b/go.mod index 35272bea1..c7723b096 100644 --- a/go.mod +++ b/go.mod @@ -37,6 +37,7 @@ require ( github.com/vektra/mockery/v2 v2.9.0 golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a golang.org/x/net v0.0.0-20210428140749-89ef3d95e781 + golang.org/x/sync v0.0.0-20210220032951-036812b2e83c // indirect google.golang.org/grpc v1.40.0 gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b // indirect pgregory.net/rapid v0.4.7 diff --git a/go.sum b/go.sum index 74f189d5c..fff11dad3 100644 --- a/go.sum +++ b/go.sum @@ -1075,6 +1075,7 @@ golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJ 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 h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ= 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= diff --git a/inspect/doc.go b/inspect/doc.go new file mode 100644 index 000000000..c53049e1a --- /dev/null +++ b/inspect/doc.go @@ -0,0 +1,36 @@ +/* +Package inspect provides a tool for investigating the state of a +failed Tendermint node. + +This package provides the Inspector type. The Inspector type runs a subset of the Tendermint +RPC endpoints that are useful for debugging issues with Tendermint consensus. + +When a node running the Tendermint consensus engine detects an inconsistent consensus state, +the entire node will crash. The Tendermint consensus engine cannot run in this +inconsistent state so the node will not be able to start up again. + +The RPC endpoints provided by the Inspector type allow for a node operator to inspect +the block store and state store to better understand what may have caused the inconsistent state. + + +The Inspector type's lifecycle is controlled by a context.Context + ins := inspect.NewFromConfig(rpcConfig) + ctx, cancelFunc:= context.WithCancel(context.Background()) + + // Run blocks until the Inspector server is shut down. + go ins.Run(ctx) + ... + + // calling the cancel function will stop the running inspect server + cancelFunc() + +Inspector serves its RPC endpoints on the address configured in the RPC configuration + + rpcConfig.ListenAddress = "tcp://127.0.0.1:26657" + ins := inspect.NewFromConfig(rpcConfig) + go ins.Run(ctx) + +The list of available RPC endpoints can then be viewed by navigating to +http://127.0.0.1:26657/ in the web browser. +*/ +package inspect diff --git a/inspect/inspect.go b/inspect/inspect.go new file mode 100644 index 000000000..38bc9ed5d --- /dev/null +++ b/inspect/inspect.go @@ -0,0 +1,149 @@ +package inspect + +import ( + "context" + "errors" + "fmt" + "net" + + "github.com/tendermint/tendermint/config" + "github.com/tendermint/tendermint/inspect/rpc" + "github.com/tendermint/tendermint/libs/log" + tmstrings "github.com/tendermint/tendermint/libs/strings" + rpccore "github.com/tendermint/tendermint/rpc/core" + "github.com/tendermint/tendermint/state" + "github.com/tendermint/tendermint/state/indexer" + "github.com/tendermint/tendermint/state/indexer/sink" + "github.com/tendermint/tendermint/store" + "github.com/tendermint/tendermint/types" + + "golang.org/x/sync/errgroup" +) + +// Inspector manages an RPC service that exports methods to debug a failed node. +// After a node shuts down due to a consensus failure, it will no longer start +// up its state cannot easily be inspected. An Inspector value provides a similar interface +// to the node, using the underlying Tendermint data stores, without bringing up +// any other components. A caller can query the Inspector service to inspect the +// persisted state and debug the failure. +type Inspector struct { + routes rpccore.RoutesMap + + config *config.RPCConfig + + indexerService *indexer.Service + eventBus *types.EventBus + logger log.Logger +} + +// New returns an Inspector that serves RPC on the specified BlockStore and StateStore. +// The Inspector type does not modify the state or block stores. +// The sinks are used to enable block and transaction querying via the RPC server. +// The caller is responsible for starting and stopping the Inspector service. +/// +//nolint:lll +func New(cfg *config.RPCConfig, bs state.BlockStore, ss state.Store, es []indexer.EventSink, logger log.Logger) *Inspector { + routes := rpc.Routes(*cfg, ss, bs, es, logger) + eb := types.NewEventBus() + eb.SetLogger(logger.With("module", "events")) + is := indexer.NewIndexerService(es, eb) + is.SetLogger(logger.With("module", "txindex")) + return &Inspector{ + routes: routes, + config: cfg, + logger: logger, + eventBus: eb, + indexerService: is, + } +} + +// NewFromConfig constructs an Inspector using the values defined in the passed in config. +func NewFromConfig(cfg *config.Config) (*Inspector, error) { + bsDB, err := config.DefaultDBProvider(&config.DBContext{ID: "blockstore", Config: cfg}) + if err != nil { + return nil, err + } + bs := store.NewBlockStore(bsDB) + sDB, err := config.DefaultDBProvider(&config.DBContext{ID: "state", Config: cfg}) + if err != nil { + return nil, err + } + genDoc, err := types.GenesisDocFromFile(cfg.GenesisFile()) + if err != nil { + return nil, err + } + sinks, err := sink.EventSinksFromConfig(cfg, config.DefaultDBProvider, genDoc.ChainID) + if err != nil { + return nil, err + } + logger := log.MustNewDefaultLogger(log.LogFormatPlain, log.LogLevelInfo, false) + ss := state.NewStore(sDB) + return New(cfg.RPC, bs, ss, sinks, logger), nil +} + +// Run starts the Inspector servers and blocks until the servers shut down. The passed +// in context is used to control the lifecycle of the servers. +func (ins *Inspector) Run(ctx context.Context) error { + err := ins.eventBus.Start() + if err != nil { + return fmt.Errorf("error starting event bus: %s", err) + } + defer func() { + err := ins.eventBus.Stop() + if err != nil { + ins.logger.Error("event bus stopped with error", "err", err) + } + }() + err = ins.indexerService.Start() + if err != nil { + return fmt.Errorf("error starting indexer service: %s", err) + } + defer func() { + err := ins.indexerService.Stop() + if err != nil { + ins.logger.Error("indexer service stopped with error", "err", err) + } + }() + return startRPCServers(ctx, ins.config, ins.logger, ins.routes) +} + +func startRPCServers(ctx context.Context, cfg *config.RPCConfig, logger log.Logger, routes rpccore.RoutesMap) error { + g, tctx := errgroup.WithContext(ctx) + listenAddrs := tmstrings.SplitAndTrimEmpty(cfg.ListenAddress, ",", " ") + rh := rpc.Handler(cfg, routes, logger) + for _, listenerAddr := range listenAddrs { + server := rpc.Server{ + Logger: logger, + Config: cfg, + Handler: rh, + Addr: listenerAddr, + } + if cfg.IsTLSEnabled() { + keyFile := cfg.KeyFile() + certFile := cfg.CertFile() + listenerAddr := listenerAddr + g.Go(func() error { + logger.Info("RPC HTTPS server starting", "address", listenerAddr, + "certfile", certFile, "keyfile", keyFile) + err := server.ListenAndServeTLS(tctx, certFile, keyFile) + if !errors.Is(err, net.ErrClosed) { + return err + } + logger.Info("RPC HTTPS server stopped", "address", listenerAddr) + return nil + }) + } else { + listenerAddr := listenerAddr + g.Go(func() error { + logger.Info("RPC HTTP server starting", "address", listenerAddr) + err := server.ListenAndServe(tctx) + if !errors.Is(err, net.ErrClosed) { + return err + } + logger.Info("RPC HTTP server stopped", "address", listenerAddr) + return nil + }) + } + } + return g.Wait() +} diff --git a/inspect/inspect_test.go b/inspect/inspect_test.go new file mode 100644 index 000000000..c2a1df571 --- /dev/null +++ b/inspect/inspect_test.go @@ -0,0 +1,583 @@ +package inspect_test + +import ( + "context" + "fmt" + "net" + "os" + "strings" + "sync" + "testing" + "time" + + "github.com/fortytw2/leaktest" + "github.com/stretchr/testify/mock" + "github.com/stretchr/testify/require" + abcitypes "github.com/tendermint/tendermint/abci/types" + "github.com/tendermint/tendermint/config" + "github.com/tendermint/tendermint/inspect" + "github.com/tendermint/tendermint/libs/log" + "github.com/tendermint/tendermint/libs/pubsub/query" + "github.com/tendermint/tendermint/proto/tendermint/state" + httpclient "github.com/tendermint/tendermint/rpc/client/http" + "github.com/tendermint/tendermint/state/indexer" + indexermocks "github.com/tendermint/tendermint/state/indexer/mocks" + statemocks "github.com/tendermint/tendermint/state/mocks" + "github.com/tendermint/tendermint/types" +) + +func TestInspectConstructor(t *testing.T) { + cfg := config.ResetTestRoot("test") + t.Cleanup(leaktest.Check(t)) + defer func() { _ = os.RemoveAll(cfg.RootDir) }() + t.Run("from config", func(t *testing.T) { + d, err := inspect.NewFromConfig(cfg) + require.NoError(t, err) + require.NotNil(t, d) + }) + +} + +func TestInspectRun(t *testing.T) { + cfg := config.ResetTestRoot("test") + t.Cleanup(leaktest.Check(t)) + defer func() { _ = os.RemoveAll(cfg.RootDir) }() + t.Run("from config", func(t *testing.T) { + d, err := inspect.NewFromConfig(cfg) + require.NoError(t, err) + ctx, cancel := context.WithCancel(context.Background()) + stoppedWG := &sync.WaitGroup{} + stoppedWG.Add(1) + go func() { + require.NoError(t, d.Run(ctx)) + stoppedWG.Done() + }() + cancel() + stoppedWG.Wait() + }) + +} + +func TestBlock(t *testing.T) { + testHeight := int64(1) + testBlock := new(types.Block) + testBlock.Header.Height = testHeight + testBlock.Header.LastCommitHash = []byte("test hash") + stateStoreMock := &statemocks.Store{} + + blockStoreMock := &statemocks.BlockStore{} + blockStoreMock.On("Height").Return(testHeight) + blockStoreMock.On("Base").Return(int64(0)) + blockStoreMock.On("LoadBlockMeta", testHeight).Return(&types.BlockMeta{}) + blockStoreMock.On("LoadBlock", testHeight).Return(testBlock) + eventSinkMock := &indexermocks.EventSink{} + eventSinkMock.On("Stop").Return(nil) + + rpcConfig := config.TestRPCConfig() + l := log.TestingLogger() + d := inspect.New(rpcConfig, blockStoreMock, stateStoreMock, []indexer.EventSink{eventSinkMock}, l) + ctx, cancel := context.WithCancel(context.Background()) + wg := &sync.WaitGroup{} + wg.Add(1) + + startedWG := &sync.WaitGroup{} + startedWG.Add(1) + go func() { + startedWG.Done() + defer wg.Done() + require.NoError(t, d.Run(ctx)) + }() + // FIXME: used to induce context switch. + // Determine more deterministic method for prompting a context switch + startedWG.Wait() + requireConnect(t, rpcConfig.ListenAddress, 20) + cli, err := httpclient.New(rpcConfig.ListenAddress) + require.NoError(t, err) + resultBlock, err := cli.Block(context.Background(), &testHeight) + require.NoError(t, err) + require.Equal(t, testBlock.Height, resultBlock.Block.Height) + require.Equal(t, testBlock.LastCommitHash, resultBlock.Block.LastCommitHash) + cancel() + wg.Wait() + + blockStoreMock.AssertExpectations(t) + stateStoreMock.AssertExpectations(t) +} + +func TestTxSearch(t *testing.T) { + testHash := []byte("test") + testTx := []byte("tx") + testQuery := fmt.Sprintf("tx.hash='%s'", string(testHash)) + testTxResult := &abcitypes.TxResult{ + Height: 1, + Index: 100, + Tx: testTx, + } + + stateStoreMock := &statemocks.Store{} + blockStoreMock := &statemocks.BlockStore{} + eventSinkMock := &indexermocks.EventSink{} + eventSinkMock.On("Stop").Return(nil) + eventSinkMock.On("Type").Return(indexer.KV) + eventSinkMock.On("SearchTxEvents", mock.Anything, + mock.MatchedBy(func(q *query.Query) bool { return testQuery == q.String() })). + Return([]*abcitypes.TxResult{testTxResult}, nil) + + rpcConfig := config.TestRPCConfig() + l := log.TestingLogger() + d := inspect.New(rpcConfig, blockStoreMock, stateStoreMock, []indexer.EventSink{eventSinkMock}, l) + ctx, cancel := context.WithCancel(context.Background()) + wg := &sync.WaitGroup{} + wg.Add(1) + + startedWG := &sync.WaitGroup{} + startedWG.Add(1) + go func() { + startedWG.Done() + defer wg.Done() + require.NoError(t, d.Run(ctx)) + }() + // FIXME: used to induce context switch. + // Determine more deterministic method for prompting a context switch + startedWG.Wait() + requireConnect(t, rpcConfig.ListenAddress, 20) + cli, err := httpclient.New(rpcConfig.ListenAddress) + require.NoError(t, err) + + var page = 1 + resultTxSearch, err := cli.TxSearch(context.Background(), testQuery, false, &page, &page, "") + require.NoError(t, err) + require.Len(t, resultTxSearch.Txs, 1) + require.Equal(t, types.Tx(testTx), resultTxSearch.Txs[0].Tx) + + cancel() + wg.Wait() + + eventSinkMock.AssertExpectations(t) + stateStoreMock.AssertExpectations(t) + blockStoreMock.AssertExpectations(t) +} +func TestTx(t *testing.T) { + testHash := []byte("test") + testTx := []byte("tx") + + stateStoreMock := &statemocks.Store{} + blockStoreMock := &statemocks.BlockStore{} + eventSinkMock := &indexermocks.EventSink{} + eventSinkMock.On("Stop").Return(nil) + eventSinkMock.On("Type").Return(indexer.KV) + eventSinkMock.On("GetTxByHash", testHash).Return(&abcitypes.TxResult{ + Tx: testTx, + }, nil) + + rpcConfig := config.TestRPCConfig() + l := log.TestingLogger() + d := inspect.New(rpcConfig, blockStoreMock, stateStoreMock, []indexer.EventSink{eventSinkMock}, l) + ctx, cancel := context.WithCancel(context.Background()) + wg := &sync.WaitGroup{} + wg.Add(1) + + startedWG := &sync.WaitGroup{} + startedWG.Add(1) + go func() { + startedWG.Done() + defer wg.Done() + require.NoError(t, d.Run(ctx)) + }() + // FIXME: used to induce context switch. + // Determine more deterministic method for prompting a context switch + startedWG.Wait() + requireConnect(t, rpcConfig.ListenAddress, 20) + cli, err := httpclient.New(rpcConfig.ListenAddress) + require.NoError(t, err) + + res, err := cli.Tx(context.Background(), testHash, false) + require.NoError(t, err) + require.Equal(t, types.Tx(testTx), res.Tx) + + cancel() + wg.Wait() + + eventSinkMock.AssertExpectations(t) + stateStoreMock.AssertExpectations(t) + blockStoreMock.AssertExpectations(t) +} +func TestConsensusParams(t *testing.T) { + testHeight := int64(1) + testMaxGas := int64(55) + stateStoreMock := &statemocks.Store{} + blockStoreMock := &statemocks.BlockStore{} + blockStoreMock.On("Height").Return(testHeight) + blockStoreMock.On("Base").Return(int64(0)) + stateStoreMock.On("LoadConsensusParams", testHeight).Return(types.ConsensusParams{ + Block: types.BlockParams{ + MaxGas: testMaxGas, + }, + }, nil) + eventSinkMock := &indexermocks.EventSink{} + eventSinkMock.On("Stop").Return(nil) + rpcConfig := config.TestRPCConfig() + l := log.TestingLogger() + d := inspect.New(rpcConfig, blockStoreMock, stateStoreMock, []indexer.EventSink{eventSinkMock}, l) + + ctx, cancel := context.WithCancel(context.Background()) + wg := &sync.WaitGroup{} + wg.Add(1) + + startedWG := &sync.WaitGroup{} + startedWG.Add(1) + go func() { + startedWG.Done() + defer wg.Done() + require.NoError(t, d.Run(ctx)) + }() + // FIXME: used to induce context switch. + // Determine more deterministic method for prompting a context switch + startedWG.Wait() + requireConnect(t, rpcConfig.ListenAddress, 20) + cli, err := httpclient.New(rpcConfig.ListenAddress) + require.NoError(t, err) + params, err := cli.ConsensusParams(context.Background(), &testHeight) + require.NoError(t, err) + require.Equal(t, params.ConsensusParams.Block.MaxGas, testMaxGas) + + cancel() + wg.Wait() + + blockStoreMock.AssertExpectations(t) + stateStoreMock.AssertExpectations(t) +} + +func TestBlockResults(t *testing.T) { + testHeight := int64(1) + testGasUsed := int64(100) + stateStoreMock := &statemocks.Store{} + // tmstate "github.com/tendermint/tendermint/proto/tendermint/state" + stateStoreMock.On("LoadABCIResponses", testHeight).Return(&state.ABCIResponses{ + DeliverTxs: []*abcitypes.ResponseDeliverTx{ + { + GasUsed: testGasUsed, + }, + }, + EndBlock: &abcitypes.ResponseEndBlock{}, + BeginBlock: &abcitypes.ResponseBeginBlock{}, + }, nil) + blockStoreMock := &statemocks.BlockStore{} + blockStoreMock.On("Base").Return(int64(0)) + blockStoreMock.On("Height").Return(testHeight) + eventSinkMock := &indexermocks.EventSink{} + eventSinkMock.On("Stop").Return(nil) + rpcConfig := config.TestRPCConfig() + l := log.TestingLogger() + d := inspect.New(rpcConfig, blockStoreMock, stateStoreMock, []indexer.EventSink{eventSinkMock}, l) + + ctx, cancel := context.WithCancel(context.Background()) + wg := &sync.WaitGroup{} + wg.Add(1) + + startedWG := &sync.WaitGroup{} + startedWG.Add(1) + go func() { + startedWG.Done() + defer wg.Done() + require.NoError(t, d.Run(ctx)) + }() + // FIXME: used to induce context switch. + // Determine more deterministic method for prompting a context switch + startedWG.Wait() + requireConnect(t, rpcConfig.ListenAddress, 20) + cli, err := httpclient.New(rpcConfig.ListenAddress) + require.NoError(t, err) + res, err := cli.BlockResults(context.Background(), &testHeight) + require.NoError(t, err) + require.Equal(t, res.TotalGasUsed, testGasUsed) + + cancel() + wg.Wait() + + blockStoreMock.AssertExpectations(t) + stateStoreMock.AssertExpectations(t) +} + +func TestCommit(t *testing.T) { + testHeight := int64(1) + testRound := int32(101) + stateStoreMock := &statemocks.Store{} + blockStoreMock := &statemocks.BlockStore{} + blockStoreMock.On("Base").Return(int64(0)) + blockStoreMock.On("Height").Return(testHeight) + blockStoreMock.On("LoadBlockMeta", testHeight).Return(&types.BlockMeta{}, nil) + blockStoreMock.On("LoadSeenCommit").Return(&types.Commit{ + Height: testHeight, + Round: testRound, + }, nil) + eventSinkMock := &indexermocks.EventSink{} + eventSinkMock.On("Stop").Return(nil) + rpcConfig := config.TestRPCConfig() + l := log.TestingLogger() + d := inspect.New(rpcConfig, blockStoreMock, stateStoreMock, []indexer.EventSink{eventSinkMock}, l) + + ctx, cancel := context.WithCancel(context.Background()) + wg := &sync.WaitGroup{} + wg.Add(1) + + startedWG := &sync.WaitGroup{} + startedWG.Add(1) + go func() { + startedWG.Done() + defer wg.Done() + require.NoError(t, d.Run(ctx)) + }() + // FIXME: used to induce context switch. + // Determine more deterministic method for prompting a context switch + startedWG.Wait() + requireConnect(t, rpcConfig.ListenAddress, 20) + cli, err := httpclient.New(rpcConfig.ListenAddress) + require.NoError(t, err) + res, err := cli.Commit(context.Background(), &testHeight) + require.NoError(t, err) + require.NotNil(t, res) + require.Equal(t, res.SignedHeader.Commit.Round, testRound) + + cancel() + wg.Wait() + + blockStoreMock.AssertExpectations(t) + stateStoreMock.AssertExpectations(t) +} + +func TestBlockByHash(t *testing.T) { + testHeight := int64(1) + testHash := []byte("test hash") + testBlock := new(types.Block) + testBlock.Header.Height = testHeight + testBlock.Header.LastCommitHash = testHash + stateStoreMock := &statemocks.Store{} + blockStoreMock := &statemocks.BlockStore{} + blockStoreMock.On("LoadBlockMeta", testHeight).Return(&types.BlockMeta{ + BlockID: types.BlockID{ + Hash: testHash, + }, + Header: types.Header{ + Height: testHeight, + }, + }, nil) + blockStoreMock.On("LoadBlockByHash", testHash).Return(testBlock, nil) + eventSinkMock := &indexermocks.EventSink{} + eventSinkMock.On("Stop").Return(nil) + rpcConfig := config.TestRPCConfig() + l := log.TestingLogger() + d := inspect.New(rpcConfig, blockStoreMock, stateStoreMock, []indexer.EventSink{eventSinkMock}, l) + + ctx, cancel := context.WithCancel(context.Background()) + wg := &sync.WaitGroup{} + wg.Add(1) + + startedWG := &sync.WaitGroup{} + startedWG.Add(1) + go func() { + startedWG.Done() + defer wg.Done() + require.NoError(t, d.Run(ctx)) + }() + // FIXME: used to induce context switch. + // Determine more deterministic method for prompting a context switch + startedWG.Wait() + requireConnect(t, rpcConfig.ListenAddress, 20) + cli, err := httpclient.New(rpcConfig.ListenAddress) + require.NoError(t, err) + res, err := cli.BlockByHash(context.Background(), testHash) + require.NoError(t, err) + require.NotNil(t, res) + require.Equal(t, []byte(res.BlockID.Hash), testHash) + + cancel() + wg.Wait() + + blockStoreMock.AssertExpectations(t) + stateStoreMock.AssertExpectations(t) +} + +func TestBlockchain(t *testing.T) { + testHeight := int64(1) + testBlock := new(types.Block) + testBlockHash := []byte("test hash") + testBlock.Header.Height = testHeight + testBlock.Header.LastCommitHash = testBlockHash + stateStoreMock := &statemocks.Store{} + + blockStoreMock := &statemocks.BlockStore{} + blockStoreMock.On("Height").Return(testHeight) + blockStoreMock.On("Base").Return(int64(0)) + blockStoreMock.On("LoadBlockMeta", testHeight).Return(&types.BlockMeta{ + BlockID: types.BlockID{ + Hash: testBlockHash, + }, + }) + eventSinkMock := &indexermocks.EventSink{} + eventSinkMock.On("Stop").Return(nil) + rpcConfig := config.TestRPCConfig() + l := log.TestingLogger() + d := inspect.New(rpcConfig, blockStoreMock, stateStoreMock, []indexer.EventSink{eventSinkMock}, l) + + ctx, cancel := context.WithCancel(context.Background()) + wg := &sync.WaitGroup{} + wg.Add(1) + + startedWG := &sync.WaitGroup{} + startedWG.Add(1) + go func() { + startedWG.Done() + defer wg.Done() + require.NoError(t, d.Run(ctx)) + }() + // FIXME: used to induce context switch. + // Determine more deterministic method for prompting a context switch + startedWG.Wait() + requireConnect(t, rpcConfig.ListenAddress, 20) + cli, err := httpclient.New(rpcConfig.ListenAddress) + require.NoError(t, err) + res, err := cli.BlockchainInfo(context.Background(), 0, 100) + require.NoError(t, err) + require.NotNil(t, res) + require.Equal(t, testBlockHash, []byte(res.BlockMetas[0].BlockID.Hash)) + + cancel() + wg.Wait() + + blockStoreMock.AssertExpectations(t) + stateStoreMock.AssertExpectations(t) +} + +func TestValidators(t *testing.T) { + testHeight := int64(1) + testVotingPower := int64(100) + testValidators := types.ValidatorSet{ + Validators: []*types.Validator{ + { + VotingPower: testVotingPower, + }, + }, + } + stateStoreMock := &statemocks.Store{} + stateStoreMock.On("LoadValidators", testHeight).Return(&testValidators, nil) + + blockStoreMock := &statemocks.BlockStore{} + blockStoreMock.On("Height").Return(testHeight) + blockStoreMock.On("Base").Return(int64(0)) + eventSinkMock := &indexermocks.EventSink{} + eventSinkMock.On("Stop").Return(nil) + rpcConfig := config.TestRPCConfig() + l := log.TestingLogger() + d := inspect.New(rpcConfig, blockStoreMock, stateStoreMock, []indexer.EventSink{eventSinkMock}, l) + + ctx, cancel := context.WithCancel(context.Background()) + wg := &sync.WaitGroup{} + wg.Add(1) + + startedWG := &sync.WaitGroup{} + startedWG.Add(1) + go func() { + startedWG.Done() + defer wg.Done() + require.NoError(t, d.Run(ctx)) + }() + // FIXME: used to induce context switch. + // Determine more deterministic method for prompting a context switch + startedWG.Wait() + requireConnect(t, rpcConfig.ListenAddress, 20) + cli, err := httpclient.New(rpcConfig.ListenAddress) + require.NoError(t, err) + + testPage := 1 + testPerPage := 100 + res, err := cli.Validators(context.Background(), &testHeight, &testPage, &testPerPage) + require.NoError(t, err) + require.NotNil(t, res) + require.Equal(t, testVotingPower, res.Validators[0].VotingPower) + + cancel() + wg.Wait() + + blockStoreMock.AssertExpectations(t) + stateStoreMock.AssertExpectations(t) +} + +func TestBlockSearch(t *testing.T) { + testHeight := int64(1) + testBlockHash := []byte("test hash") + testQuery := "block.height = 1" + stateStoreMock := &statemocks.Store{} + + blockStoreMock := &statemocks.BlockStore{} + eventSinkMock := &indexermocks.EventSink{} + eventSinkMock.On("Stop").Return(nil) + eventSinkMock.On("Type").Return(indexer.KV) + blockStoreMock.On("LoadBlock", testHeight).Return(&types.Block{ + Header: types.Header{ + Height: testHeight, + }, + }, nil) + blockStoreMock.On("LoadBlockMeta", testHeight).Return(&types.BlockMeta{ + BlockID: types.BlockID{ + Hash: testBlockHash, + }, + }) + eventSinkMock.On("SearchBlockEvents", mock.Anything, + mock.MatchedBy(func(q *query.Query) bool { return testQuery == q.String() })). + Return([]int64{testHeight}, nil) + rpcConfig := config.TestRPCConfig() + l := log.TestingLogger() + d := inspect.New(rpcConfig, blockStoreMock, stateStoreMock, []indexer.EventSink{eventSinkMock}, l) + + ctx, cancel := context.WithCancel(context.Background()) + wg := &sync.WaitGroup{} + wg.Add(1) + + startedWG := &sync.WaitGroup{} + startedWG.Add(1) + go func() { + startedWG.Done() + defer wg.Done() + require.NoError(t, d.Run(ctx)) + }() + // FIXME: used to induce context switch. + // Determine more deterministic method for prompting a context switch + startedWG.Wait() + requireConnect(t, rpcConfig.ListenAddress, 20) + cli, err := httpclient.New(rpcConfig.ListenAddress) + require.NoError(t, err) + + testPage := 1 + testPerPage := 100 + testOrderBy := "desc" + res, err := cli.BlockSearch(context.Background(), testQuery, &testPage, &testPerPage, testOrderBy) + require.NoError(t, err) + require.NotNil(t, res) + require.Equal(t, testBlockHash, []byte(res.Blocks[0].BlockID.Hash)) + + cancel() + wg.Wait() + + blockStoreMock.AssertExpectations(t) + stateStoreMock.AssertExpectations(t) +} + +func requireConnect(t testing.TB, addr string, retries int) { + parts := strings.SplitN(addr, "://", 2) + if len(parts) != 2 { + t.Fatalf("malformed address to dial: %s", addr) + } + var err error + for i := 0; i < retries; i++ { + var conn net.Conn + conn, err = net.Dial(parts[0], parts[1]) + if err == nil { + conn.Close() + return + } + // FIXME attempt to yield and let the other goroutine continue execution. + time.Sleep(time.Microsecond * 100) + } + t.Fatalf("unable to connect to server %s after %d tries: %s", addr, retries, err) +} diff --git a/inspect/rpc/rpc.go b/inspect/rpc/rpc.go new file mode 100644 index 000000000..76dcda4eb --- /dev/null +++ b/inspect/rpc/rpc.go @@ -0,0 +1,143 @@ +package rpc + +import ( + "context" + "net/http" + "time" + + "github.com/rs/cors" + + "github.com/tendermint/tendermint/config" + "github.com/tendermint/tendermint/internal/consensus" + "github.com/tendermint/tendermint/libs/log" + "github.com/tendermint/tendermint/libs/pubsub" + "github.com/tendermint/tendermint/rpc/core" + "github.com/tendermint/tendermint/rpc/jsonrpc/server" + "github.com/tendermint/tendermint/state" + "github.com/tendermint/tendermint/state/indexer" + "github.com/tendermint/tendermint/types" +) + +// Server defines parameters for running an Inspector rpc server. +type Server struct { + Addr string // TCP address to listen on, ":http" if empty + Handler http.Handler + Logger log.Logger + Config *config.RPCConfig +} + +// Routes returns the set of routes used by the Inspector server. +// +//nolint: lll +func Routes(cfg config.RPCConfig, s state.Store, bs state.BlockStore, es []indexer.EventSink, logger log.Logger) core.RoutesMap { + env := &core.Environment{ + Config: cfg, + EventSinks: es, + StateStore: s, + BlockStore: bs, + ConsensusReactor: waitSyncCheckerImpl{}, + Logger: logger, + } + return core.RoutesMap{ + "blockchain": server.NewRPCFunc(env.BlockchainInfo, "minHeight,maxHeight", true), + "consensus_params": server.NewRPCFunc(env.ConsensusParams, "height", true), + "block": server.NewRPCFunc(env.Block, "height", true), + "block_by_hash": server.NewRPCFunc(env.BlockByHash, "hash", true), + "block_results": server.NewRPCFunc(env.BlockResults, "height", true), + "commit": server.NewRPCFunc(env.Commit, "height", true), + "validators": server.NewRPCFunc(env.Validators, "height,page,per_page", true), + "tx": server.NewRPCFunc(env.Tx, "hash,prove", true), + "tx_search": server.NewRPCFunc(env.TxSearch, "query,prove,page,per_page,order_by", false), + "block_search": server.NewRPCFunc(env.BlockSearch, "query,page,per_page,order_by", false), + } +} + +// Handler returns the http.Handler configured for use with an Inspector server. Handler +// registers the routes on the http.Handler and also registers the websocket handler +// and the CORS handler if specified by the configuration options. +func Handler(rpcConfig *config.RPCConfig, routes core.RoutesMap, logger log.Logger) http.Handler { + mux := http.NewServeMux() + wmLogger := logger.With("protocol", "websocket") + + var eventBus types.EventBusSubscriber + + websocketDisconnectFn := func(remoteAddr string) { + err := eventBus.UnsubscribeAll(context.Background(), remoteAddr) + if err != nil && err != pubsub.ErrSubscriptionNotFound { + wmLogger.Error("Failed to unsubscribe addr from events", "addr", remoteAddr, "err", err) + } + } + wm := server.NewWebsocketManager(routes, + server.OnDisconnect(websocketDisconnectFn), + server.ReadLimit(rpcConfig.MaxBodyBytes)) + wm.SetLogger(wmLogger) + mux.HandleFunc("/websocket", wm.WebsocketHandler) + + server.RegisterRPCFuncs(mux, routes, logger) + var rootHandler http.Handler = mux + if rpcConfig.IsCorsEnabled() { + rootHandler = addCORSHandler(rpcConfig, mux) + } + return rootHandler +} + +func addCORSHandler(rpcConfig *config.RPCConfig, h http.Handler) http.Handler { + corsMiddleware := cors.New(cors.Options{ + AllowedOrigins: rpcConfig.CORSAllowedOrigins, + AllowedMethods: rpcConfig.CORSAllowedMethods, + AllowedHeaders: rpcConfig.CORSAllowedHeaders, + }) + h = corsMiddleware.Handler(h) + return h +} + +type waitSyncCheckerImpl struct{} + +func (waitSyncCheckerImpl) WaitSync() bool { + return false +} + +func (waitSyncCheckerImpl) GetPeerState(peerID types.NodeID) (*consensus.PeerState, bool) { + return nil, false +} + +// ListenAndServe listens on the address specified in srv.Addr and handles any +// incoming requests over HTTP using the Inspector rpc handler specified on the server. +func (srv *Server) ListenAndServe(ctx context.Context) error { + listener, err := server.Listen(srv.Addr, srv.Config.MaxOpenConnections) + if err != nil { + return err + } + go func() { + <-ctx.Done() + listener.Close() + }() + return server.Serve(listener, srv.Handler, srv.Logger, serverRPCConfig(srv.Config)) +} + +// ListenAndServeTLS listens on the address specified in srv.Addr. ListenAndServeTLS handles +// incoming requests over HTTPS using the Inspector rpc handler specified on the server. +func (srv *Server) ListenAndServeTLS(ctx context.Context, certFile, keyFile string) error { + listener, err := server.Listen(srv.Addr, srv.Config.MaxOpenConnections) + if err != nil { + return err + } + go func() { + <-ctx.Done() + listener.Close() + }() + return server.ServeTLS(listener, srv.Handler, certFile, keyFile, srv.Logger, serverRPCConfig(srv.Config)) +} + +func serverRPCConfig(r *config.RPCConfig) *server.Config { + cfg := server.DefaultConfig() + cfg.MaxBodyBytes = r.MaxBodyBytes + cfg.MaxHeaderBytes = r.MaxHeaderBytes + // If necessary adjust global WriteTimeout to ensure it's greater than + // TimeoutBroadcastTxCommit. + // See https://github.com/tendermint/tendermint/issues/3435 + if cfg.WriteTimeout <= r.TimeoutBroadcastTxCommit { + cfg.WriteTimeout = r.TimeoutBroadcastTxCommit + 1*time.Second + } + return cfg +} diff --git a/light/proxy/proxy.go b/light/proxy/proxy.go index 8f1e7bf87..6f2622588 100644 --- a/light/proxy/proxy.go +++ b/light/proxy/proxy.go @@ -113,7 +113,7 @@ func (p *Proxy) listen() (net.Listener, *http.ServeMux, error) { } // 4) Start listening for new connections. - listener, err := rpcserver.Listen(p.Addr, p.Config) + listener, err := rpcserver.Listen(p.Addr, p.Config.MaxOpenConnections) if err != nil { return nil, mux, err } diff --git a/node/node.go b/node/node.go index a31362b49..9cb5315bc 100644 --- a/node/node.go +++ b/node/node.go @@ -441,22 +441,23 @@ func makeNode(config *cfg.Config, ProxyAppQuery: proxyApp.Query(), ProxyAppMempool: proxyApp.Mempool(), - StateStore: stateStore, - BlockStore: blockStore, - EvidencePool: evPool, - ConsensusState: csState, + StateStore: stateStore, + BlockStore: blockStore, + EvidencePool: evPool, + ConsensusState: csState, + + ConsensusReactor: csReactor, BlockSyncReactor: bcReactor.(cs.BlockSyncReactor), P2PPeers: sw, PeerManager: peerManager, - GenDoc: genDoc, - EventSinks: eventSinks, - ConsensusReactor: csReactor, - EventBus: eventBus, - Mempool: mp, - Logger: logger.With("module", "rpc"), - Config: *config.RPC, + GenDoc: genDoc, + EventSinks: eventSinks, + EventBus: eventBus, + Mempool: mp, + Logger: logger.With("module", "rpc"), + Config: *config.RPC, }, } @@ -825,7 +826,7 @@ func (n *nodeImpl) startRPC() ([]net.Listener, error) { rpcserver.RegisterRPCFuncs(mux, routes, rpcLogger) listener, err := rpcserver.Listen( listenAddr, - config, + config.MaxOpenConnections, ) if err != nil { return nil, err @@ -883,7 +884,7 @@ func (n *nodeImpl) startRPC() ([]net.Listener, error) { if config.WriteTimeout <= n.config.RPC.TimeoutBroadcastTxCommit { config.WriteTimeout = n.config.RPC.TimeoutBroadcastTxCommit + 1*time.Second } - listener, err := rpcserver.Listen(grpcListenAddr, config) + listener, err := rpcserver.Listen(grpcListenAddr, config.MaxOpenConnections) if err != nil { return nil, err } diff --git a/node/setup.go b/node/setup.go index e9bfb029a..6d2a7523b 100644 --- a/node/setup.go +++ b/node/setup.go @@ -8,7 +8,6 @@ import ( "math" "net" _ "net/http/pprof" // nolint: gosec // securely exposed on separate, optional port - "strings" "time" dbm "github.com/tendermint/tm-db" @@ -33,9 +32,7 @@ import ( "github.com/tendermint/tendermint/proxy" sm "github.com/tendermint/tendermint/state" "github.com/tendermint/tendermint/state/indexer" - kv "github.com/tendermint/tendermint/state/indexer/sink/kv" - null "github.com/tendermint/tendermint/state/indexer/sink/null" - psql "github.com/tendermint/tendermint/state/indexer/sink/psql" + "github.com/tendermint/tendermint/state/indexer/sink" "github.com/tendermint/tendermint/store" "github.com/tendermint/tendermint/types" "github.com/tendermint/tendermint/version" @@ -78,56 +75,9 @@ func createAndStartIndexerService( logger log.Logger, chainID string, ) (*indexer.Service, []indexer.EventSink, error) { - - eventSinks := []indexer.EventSink{} - - // check for duplicated sinks - sinks := map[string]bool{} - for _, s := range config.TxIndex.Indexer { - sl := strings.ToLower(s) - if sinks[sl] { - return nil, nil, errors.New("found duplicated sinks, please check the tx-index section in the config.toml") - } - - sinks[sl] = true - } - -loop: - for k := range sinks { - switch k { - case string(indexer.NULL): - // When we see null in the config, the eventsinks will be reset with the - // nullEventSink. - eventSinks = []indexer.EventSink{null.NewEventSink()} - break loop - - case string(indexer.KV): - store, err := dbProvider(&cfg.DBContext{ID: "tx_index", Config: config}) - if err != nil { - return nil, nil, err - } - - eventSinks = append(eventSinks, kv.NewEventSink(store)) - - case string(indexer.PSQL): - conn := config.TxIndex.PsqlConn - if conn == "" { - return nil, nil, errors.New("the psql connection settings cannot be empty") - } - - es, _, err := psql.NewEventSink(conn, chainID) - if err != nil { - return nil, nil, err - } - eventSinks = append(eventSinks, es) - - default: - return nil, nil, errors.New("unsupported event sink type") - } - } - - if len(eventSinks) == 0 { - eventSinks = []indexer.EventSink{null.NewEventSink()} + eventSinks, err := sink.EventSinksFromConfig(config, dbProvider, chainID) + if err != nil { + return nil, nil, err } indexerService := indexer.NewIndexerService(eventSinks, eventBus) diff --git a/rpc/core/env.go b/rpc/core/env.go index c35b8229c..7069bc4d4 100644 --- a/rpc/core/env.go +++ b/rpc/core/env.go @@ -58,6 +58,11 @@ type peers interface { Peers() p2p.IPeerSet } +type consensusReactor interface { + WaitSync() bool + GetPeerState(peerID types.NodeID) (*consensus.PeerState, bool) +} + type peerManager interface { Peers() []types.NodeID Addresses(types.NodeID) []p2p.NodeAddress @@ -72,11 +77,12 @@ type Environment struct { ProxyAppMempool proxy.AppConnMempool // interfaces defined in types and above - StateStore sm.Store - BlockStore sm.BlockStore - EvidencePool sm.EvidencePool - ConsensusState consensusState - P2PPeers peers + StateStore sm.Store + BlockStore sm.BlockStore + EvidencePool sm.EvidencePool + ConsensusState consensusState + ConsensusReactor consensusReactor + P2PPeers peers // Legacy p2p stack P2PTransport transport @@ -88,7 +94,6 @@ type Environment struct { PubKey crypto.PubKey GenDoc *types.GenesisDoc // cache the genesis structure EventSinks []indexer.EventSink - ConsensusReactor *consensus.Reactor EventBus *types.EventBus // thread safe Mempool mempl.Mempool BlockSyncReactor consensus.BlockSyncReactor diff --git a/rpc/jsonrpc/jsonrpc_test.go b/rpc/jsonrpc/jsonrpc_test.go index 6e0c03f00..b5e422280 100644 --- a/rpc/jsonrpc/jsonrpc_test.go +++ b/rpc/jsonrpc/jsonrpc_test.go @@ -110,7 +110,7 @@ func setup() { wm.SetLogger(tcpLogger) mux.HandleFunc(websocketEndpoint, wm.WebsocketHandler) config := server.DefaultConfig() - listener1, err := server.Listen(tcpAddr, config) + listener1, err := server.Listen(tcpAddr, config.MaxOpenConnections) if err != nil { panic(err) } @@ -126,7 +126,7 @@ func setup() { wm = server.NewWebsocketManager(Routes) wm.SetLogger(unixLogger) mux2.HandleFunc(websocketEndpoint, wm.WebsocketHandler) - listener2, err := server.Listen(unixAddr, config) + listener2, err := server.Listen(unixAddr, config.MaxOpenConnections) if err != nil { panic(err) } diff --git a/rpc/jsonrpc/server/http_server.go b/rpc/jsonrpc/server/http_server.go index c21c71c49..549671241 100644 --- a/rpc/jsonrpc/server/http_server.go +++ b/rpc/jsonrpc/server/http_server.go @@ -261,7 +261,7 @@ func (h maxBytesHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { // Listen starts a new net.Listener on the given address. // It returns an error if the address is invalid or the call to Listen() fails. -func Listen(addr string, config *Config) (listener net.Listener, err error) { +func Listen(addr string, maxOpenConnections int) (listener net.Listener, err error) { parts := strings.SplitN(addr, "://", 2) if len(parts) != 2 { return nil, fmt.Errorf( @@ -274,8 +274,8 @@ func Listen(addr string, config *Config) (listener net.Listener, err error) { if err != nil { return nil, fmt.Errorf("failed to listen on %v: %v", addr, err) } - if config.MaxOpenConnections > 0 { - listener = netutil.LimitListener(listener, config.MaxOpenConnections) + if maxOpenConnections > 0 { + listener = netutil.LimitListener(listener, maxOpenConnections) } return listener, nil diff --git a/rpc/jsonrpc/server/http_server_test.go b/rpc/jsonrpc/server/http_server_test.go index e7c517cde..823719e41 100644 --- a/rpc/jsonrpc/server/http_server_test.go +++ b/rpc/jsonrpc/server/http_server_test.go @@ -39,8 +39,7 @@ func TestMaxOpenConnections(t *testing.T) { fmt.Fprint(w, "some body") }) config := DefaultConfig() - config.MaxOpenConnections = max - l, err := Listen("tcp://127.0.0.1:0", config) + l, err := Listen("tcp://127.0.0.1:0", max) require.NoError(t, err) defer l.Close() go Serve(l, mux, log.TestingLogger(), config) //nolint:errcheck // ignore for tests diff --git a/rpc/jsonrpc/test/main.go b/rpc/jsonrpc/test/main.go index 1c949571f..d348e1639 100644 --- a/rpc/jsonrpc/test/main.go +++ b/rpc/jsonrpc/test/main.go @@ -33,7 +33,7 @@ func main() { rpcserver.RegisterRPCFuncs(mux, routes, logger) config := rpcserver.DefaultConfig() - listener, err := rpcserver.Listen("tcp://127.0.0.1:8008", config) + listener, err := rpcserver.Listen("tcp://127.0.0.1:8008", config.MaxOpenConnections) if err != nil { tmos.Exit(err.Error()) } diff --git a/state/indexer/indexer_service.go b/state/indexer/indexer_service.go index a429b66a0..39a1847f8 100644 --- a/state/indexer/indexer_service.go +++ b/state/indexer/indexer_service.go @@ -51,43 +51,47 @@ func (is *Service) OnStart() error { go func() { for { - msg := <-blockHeadersSub.Out() + select { + case <-blockHeadersSub.Canceled(): + return + case msg := <-blockHeadersSub.Out(): - eventDataHeader := msg.Data().(types.EventDataNewBlockHeader) - height := eventDataHeader.Header.Height - batch := NewBatch(eventDataHeader.NumTxs) + 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 + 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( - "failed to add tx to batch", - "height", height, - "index", txResult.Index, - "err", err, - ) - } - } - - if !IndexingEnabled(is.eventSinks) { - continue - } - - for _, sink := range is.eventSinks { - if err := sink.IndexBlockEvents(eventDataHeader); err != nil { - is.Logger.Error("failed to index block", "height", height, "err", err) - } else { - is.Logger.Debug("indexed block", "height", height, "sink", sink.Type()) + if err = batch.Add(&txResult); err != nil { + is.Logger.Error( + "failed to add tx to batch", + "height", height, + "index", txResult.Index, + "err", err, + ) + } } - if len(batch.Ops) > 0 { - err := sink.IndexTxEvents(batch.Ops) - if err != nil { - is.Logger.Error("failed to index block txs", "height", height, "err", err) + if !IndexingEnabled(is.eventSinks) { + continue + } + + for _, sink := range is.eventSinks { + if err := sink.IndexBlockEvents(eventDataHeader); err != nil { + is.Logger.Error("failed to index block", "height", height, "err", err) } else { - is.Logger.Debug("indexed txs", "height", height, "sink", sink.Type()) + is.Logger.Debug("indexed block", "height", height, "sink", sink.Type()) + } + + if len(batch.Ops) > 0 { + err := sink.IndexTxEvents(batch.Ops) + if err != nil { + is.Logger.Error("failed to index block txs", "height", height, "err", err) + } else { + is.Logger.Debug("indexed txs", "height", height, "sink", sink.Type()) + } } } } diff --git a/state/indexer/sink/sink.go b/state/indexer/sink/sink.go new file mode 100644 index 000000000..1e50721c6 --- /dev/null +++ b/state/indexer/sink/sink.go @@ -0,0 +1,65 @@ +package sink + +import ( + "errors" + "strings" + + "github.com/tendermint/tendermint/config" + "github.com/tendermint/tendermint/state/indexer" + "github.com/tendermint/tendermint/state/indexer/sink/kv" + "github.com/tendermint/tendermint/state/indexer/sink/null" + "github.com/tendermint/tendermint/state/indexer/sink/psql" +) + +// EventSinksFromConfig constructs a slice of indexer.EventSink using the provided +// configuration. +// +//nolint:lll +func EventSinksFromConfig(cfg *config.Config, dbProvider config.DBProvider, chainID string) ([]indexer.EventSink, error) { + if len(cfg.TxIndex.Indexer) == 0 { + return []indexer.EventSink{null.NewEventSink()}, nil + } + + // check for duplicated sinks + sinks := map[string]struct{}{} + for _, s := range cfg.TxIndex.Indexer { + sl := strings.ToLower(s) + if _, ok := sinks[sl]; ok { + return nil, errors.New("found duplicated sinks, please check the tx-index section in the config.toml") + } + sinks[sl] = struct{}{} + } + eventSinks := []indexer.EventSink{} + for k := range sinks { + switch indexer.EventSinkType(k) { + case indexer.NULL: + // When we see null in the config, the eventsinks will be reset with the + // nullEventSink. + return []indexer.EventSink{null.NewEventSink()}, nil + + case indexer.KV: + store, err := dbProvider(&config.DBContext{ID: "tx_index", Config: cfg}) + if err != nil { + return nil, err + } + + eventSinks = append(eventSinks, kv.NewEventSink(store)) + + case indexer.PSQL: + conn := cfg.TxIndex.PsqlConn + if conn == "" { + return nil, errors.New("the psql connection settings cannot be empty") + } + + es, _, err := psql.NewEventSink(conn, chainID) + if err != nil { + return nil, err + } + eventSinks = append(eventSinks, es) + default: + return nil, errors.New("unsupported event sink type") + } + } + return eventSinks, nil + +} From 41a361ed8d9755b62972140fc3ddb7a9826e3529 Mon Sep 17 00:00:00 2001 From: "M. J. Fromberger" Date: Tue, 24 Aug 2021 14:18:27 -0700 Subject: [PATCH 22/37] psql: add documentation and simplify constructor API (#6856) Add documentation comments to the psql event sink package, and simplify the constructor function so that it does not return the SQL database handle. The handle is needed for testing, so expose that via a separate method on the concrete type. Update the tests and existing usage for the change. This change does not affect the behaviour of the sink, so there are no functional changes, only syntactic updates. --- cmd/tendermint/commands/reindex_event.go | 4 +-- state/indexer/indexer_service_test.go | 13 ++++----- state/indexer/sink/psql/psql.go | 34 +++++++++++++++++------- state/indexer/sink/psql/psql_test.go | 13 +++------ state/indexer/sink/sink.go | 2 +- 5 files changed, 36 insertions(+), 30 deletions(-) diff --git a/cmd/tendermint/commands/reindex_event.go b/cmd/tendermint/commands/reindex_event.go index ddc585c1f..1dbce2f74 100644 --- a/cmd/tendermint/commands/reindex_event.go +++ b/cmd/tendermint/commands/reindex_event.go @@ -31,7 +31,7 @@ var ReIndexEventCmd = &cobra.Command{ Long: ` reindex-event is an offline tooling to re-index block and tx events to the eventsinks, you can run this command when the event store backend dropped/disconnected or you want to replace the backend. - The default start-height is 0, meaning the tooling will start reindex from the base block height(inclusive); and the + The default start-height is 0, meaning the tooling will start reindex from the base block height(inclusive); and the default end-height is 0, meaning the tooling will reindex until the latest block height(inclusive). User can omits either or both arguments. `, @@ -106,7 +106,7 @@ func loadEventSinks(cfg *tmcfg.Config) ([]indexer.EventSink, error) { if conn == "" { return nil, errors.New("the psql connection settings cannot be empty") } - es, _, err := psql.NewEventSink(conn, chainID) + es, err := psql.NewEventSink(conn, chainID) if err != nil { return nil, err } diff --git a/state/indexer/indexer_service_test.go b/state/indexer/indexer_service_test.go index 68a00afb5..457ed065a 100644 --- a/state/indexer/indexer_service_test.go +++ b/state/indexer/indexer_service_test.go @@ -164,19 +164,16 @@ func setupDB(t *testing.T) (*dockertest.Pool, error) { conn := fmt.Sprintf(dsn, user, password, resource.GetPort(port+"/tcp"), dbName) - if err = pool.Retry(func() error { - var err error - - pSink, psqldb, err = psql.NewEventSink(conn, "test-chainID") - + assert.NoError(t, pool.Retry(func() error { + sink, err := psql.NewEventSink(conn, "test-chainID") if err != nil { return err } + pSink = sink + psqldb = sink.DB() return psqldb.Ping() - }); err != nil { - assert.Error(t, err) - } + })) resetDB(t) diff --git a/state/indexer/sink/psql/psql.go b/state/indexer/sink/psql/psql.go index efb539e0b..8bd378f4a 100644 --- a/state/indexer/sink/psql/psql.go +++ b/state/indexer/sink/psql/psql.go @@ -1,3 +1,4 @@ +// Package psql implements an event sink backed by a PostgreSQL database. package psql import ( @@ -24,28 +25,38 @@ const ( DriverName = "postgres" ) -// EventSink is an indexer backend providing the tx/block index services. +// EventSink is an indexer backend providing the tx/block index services. This +// implementation stores records in a PostgreSQL database using the schema +// defined in state/indexer/sink/psql/schema.sql. type EventSink struct { store *sql.DB chainID string } -func NewEventSink(connStr string, chainID string) (indexer.EventSink, *sql.DB, error) { +// NewEventSink constructs an event sink associated with the PostgreSQL +// database specified by connStr. Events written to the sink are attributed to +// the specified chainID. +func NewEventSink(connStr, chainID string) (*EventSink, error) { db, err := sql.Open(DriverName, connStr) if err != nil { - return nil, nil, err + return nil, err } return &EventSink{ store: db, chainID: chainID, - }, db, nil + }, nil } -func (es *EventSink) Type() indexer.EventSinkType { - return indexer.PSQL -} +// DB returns the underlying Postgres connection used by the sink. +// This is exported to support testing. +func (es *EventSink) DB() *sql.DB { return es.store } +// Type returns the structure type for this sink, which is Postgres. +func (es *EventSink) Type() indexer.EventSinkType { return indexer.PSQL } + +// IndexBlockEvents indexes the specified block header, part of the +// indexer.EventSink interface. func (es *EventSink) IndexBlockEvents(h types.EventDataNewBlockHeader) error { sqlStmt := sq. Insert(TableEventBlock). @@ -156,18 +167,22 @@ func (es *EventSink) IndexTxEvents(txr []*abci.TxResult) error { return err } +// SearchBlockEvents is not implemented by this sink, and reports an error for all queries. func (es *EventSink) SearchBlockEvents(ctx context.Context, q *query.Query) ([]int64, error) { return nil, errors.New("block search is not supported via the postgres event sink") } +// SearchTxEvents is not implemented by this sink, and reports an error for all queries. func (es *EventSink) SearchTxEvents(ctx context.Context, q *query.Query) ([]*abci.TxResult, error) { return nil, errors.New("tx search is not supported via the postgres event sink") } +// GetTxByHash is not implemented by this sink, and reports an error for all queries. func (es *EventSink) GetTxByHash(hash []byte) (*abci.TxResult, error) { return nil, errors.New("getTxByHash is not supported via the postgres event sink") } +// HasBlock is not implemented by this sink, and reports an error for all queries. func (es *EventSink) HasBlock(h int64) (bool, error) { return false, errors.New("hasBlock is not supported via the postgres event sink") } @@ -206,6 +221,5 @@ func indexBlockEvents( return sqlStmt, nil } -func (es *EventSink) Stop() error { - return es.store.Close() -} +// Stop closes the underlying PostgreSQL database. +func (es *EventSink) Stop() error { return es.store.Close() } diff --git a/state/indexer/sink/psql/psql_test.go b/state/indexer/sink/psql/psql_test.go index 0df773a53..35ad7eea3 100644 --- a/state/indexer/sink/psql/psql_test.go +++ b/state/indexer/sink/psql/psql_test.go @@ -341,19 +341,14 @@ func setupDB(t *testing.T) (*dockertest.Pool, error) { conn := fmt.Sprintf(dsn, user, password, resource.GetPort(port+"/tcp"), dbName) - if err = pool.Retry(func() error { - var err error - - _, db, err = NewEventSink(conn, chainID) - + require.NoError(t, pool.Retry(func() error { + sink, err := NewEventSink(conn, chainID) if err != nil { return err } - + db = sink.DB() // set global for test use return db.Ping() - }); err != nil { - require.NoError(t, err) - } + })) resetDB(t) diff --git a/state/indexer/sink/sink.go b/state/indexer/sink/sink.go index 1e50721c6..f9dfa54df 100644 --- a/state/indexer/sink/sink.go +++ b/state/indexer/sink/sink.go @@ -51,7 +51,7 @@ func EventSinksFromConfig(cfg *config.Config, dbProvider config.DBProvider, chai return nil, errors.New("the psql connection settings cannot be empty") } - es, _, err := psql.NewEventSink(conn, chainID) + es, err := psql.NewEventSink(conn, chainID) if err != nil { return nil, err } From e053643b957c7db08700b872e7532889b43746ba Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 25 Aug 2021 08:12:04 -0400 Subject: [PATCH 23/37] build(deps): Bump codecov/codecov-action from 2.0.2 to 2.0.3 (#6860) Bumps [codecov/codecov-action](https://github.com/codecov/codecov-action) from 2.0.2 to 2.0.3. - [Release notes](https://github.com/codecov/codecov-action/releases) - [Changelog](https://github.com/codecov/codecov-action/blob/master/CHANGELOG.md) - [Commits](https://github.com/codecov/codecov-action/compare/v2.0.2...v2.0.3) --- updated-dependencies: - dependency-name: codecov/codecov-action dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/coverage.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index 12dd504e3..7d312b4f8 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -121,7 +121,7 @@ jobs: - run: | cat ./*profile.out | grep -v "mode: atomic" >> coverage.txt if: env.GIT_DIFF - - uses: codecov/codecov-action@v2.0.2 + - uses: codecov/codecov-action@v2.0.3 with: file: ./coverage.txt if: env.GIT_DIFF From 9c8379ef305091f5825a4d91244c19ec2697894c Mon Sep 17 00:00:00 2001 From: Sam Kleinman Date: Wed, 25 Aug 2021 08:24:01 -0400 Subject: [PATCH 24/37] e2e: more consistent node selection during tests (#6857) In the transaction load generator, the e2e test harness previously distributed load randomly to hosts, which was a source of test non-determinism. This change distributes the load generation to the different nodes in the set in a round robin fashion, to produce more reliable results, but does not otherwise change the behavior of the test harness. --- test/e2e/pkg/testnet.go | 10 ----- test/e2e/runner/evidence.go | 17 +++++++- test/e2e/runner/load.go | 81 +++++++++++++++++++++++++------------ 3 files changed, 72 insertions(+), 36 deletions(-) diff --git a/test/e2e/pkg/testnet.go b/test/e2e/pkg/testnet.go index cfeb54bde..cec58bd20 100644 --- a/test/e2e/pkg/testnet.go +++ b/test/e2e/pkg/testnet.go @@ -417,16 +417,6 @@ func (t Testnet) ArchiveNodes() []*Node { return nodes } -// RandomNode returns a random non-seed node. -func (t Testnet) RandomNode() *Node { - for { - node := t.Nodes[rand.Intn(len(t.Nodes))] - if node.Mode != ModeSeed { - return node - } - } -} - // IPv6 returns true if the testnet is an IPv6 network. func (t Testnet) IPv6() bool { return t.IP.IP.To4() == nil diff --git a/test/e2e/runner/evidence.go b/test/e2e/runner/evidence.go index 6a246dcb5..30e8d9f0a 100644 --- a/test/e2e/runner/evidence.go +++ b/test/e2e/runner/evidence.go @@ -3,6 +3,7 @@ package main import ( "bytes" "context" + "errors" "fmt" "io/ioutil" "math/rand" @@ -29,7 +30,21 @@ const lightClientEvidenceRatio = 4 // DuplicateVoteEvidence. func InjectEvidence(testnet *e2e.Testnet, amount int) error { // select a random node - targetNode := testnet.RandomNode() + var targetNode *e2e.Node + + for i := 0; i < len(testnet.Nodes)-1; i++ { + targetNode = testnet.Nodes[rand.Intn(len(testnet.Nodes))] // nolint: gosec + if targetNode.Mode == e2e.ModeSeed { + targetNode = nil + continue + } + + break + } + + if targetNode == nil { + return errors.New("could not find node to inject evidence into") + } logger.Info(fmt.Sprintf("Injecting evidence through %v (amount: %d)...", targetNode.Name, amount)) diff --git a/test/e2e/runner/load.go b/test/e2e/runner/load.go index 518e32564..b57c96ddf 100644 --- a/test/e2e/runner/load.go +++ b/test/e2e/runner/load.go @@ -1,6 +1,7 @@ package main import ( + "container/ring" "context" "crypto/rand" "errors" @@ -93,34 +94,64 @@ func loadGenerate(ctx context.Context, chTx chan<- types.Tx, multiplier int, siz // loadProcess processes transactions func loadProcess(ctx context.Context, testnet *e2e.Testnet, chTx <-chan types.Tx, chSuccess chan<- types.Tx) { - // Each worker gets its own client to each node, which allows for some - // concurrency while still bounding it. - clients := map[string]*rpchttp.HTTP{} + // Each worker gets its own client to each usable node, which + // allows for some concurrency while still bounding it. + clients := make([]*rpchttp.HTTP, 0, len(testnet.Nodes)) - var err error - for tx := range chTx { - node := testnet.RandomNode() - - client, ok := clients[node.Name] - if !ok { - client, err = node.Client() - if err != nil { - continue - } - - // check that the node is up - _, err = client.Health(ctx) - if err != nil { - continue - } - - clients[node.Name] = client - } - - if _, err = client.BroadcastTxSync(ctx, tx); err != nil { + for idx := range testnet.Nodes { + // Construct a list of usable nodes for the creating + // load. Don't send load through seed nodes because + // they do not provide the RPC endpoints required to + // broadcast transaction. + if testnet.Nodes[idx].Mode == e2e.ModeSeed { continue } - chSuccess <- tx + client, err := testnet.Nodes[idx].Client() + if err != nil { + continue + } + + clients = append(clients, client) + } + + if len(clients) == 0 { + panic("no clients to process load") + } + + // Put the clients in a ring so they can be used in a + // round-robin fashion. + clientRing := ring.New(len(clients)) + for idx := range clients { + clientRing.Value = clients[idx] + clientRing = clientRing.Next() + } + + var err error + + for { + select { + case <-ctx.Done(): + return + case tx := <-chTx: + clientRing = clientRing.Next() + client := clientRing.Value.(*rpchttp.HTTP) + + if _, err := client.Health(ctx); err != nil { + continue + } + + if _, err = client.BroadcastTxSync(ctx, tx); err != nil { + continue + } + + select { + case chSuccess <- tx: + continue + case <-ctx.Done(): + return + } + + } } } From a0a5d45cb1b25e301c5aaa29454d2078724d1f63 Mon Sep 17 00:00:00 2001 From: Sam Kleinman Date: Wed, 25 Aug 2021 08:26:11 -0400 Subject: [PATCH 25/37] lint: change deprecated linter (#6861) This is a super minor change that silences a warning when running the linter locally. --- .golangci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.golangci.yml b/.golangci.yml index f05cde90c..574ed22b0 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -26,7 +26,7 @@ linters: # - maligned - nakedret - prealloc - - scopelint + - exportloopref - staticcheck - structcheck - stylecheck From 6e921f6644159eac604bae6032a76da7ce61aa60 Mon Sep 17 00:00:00 2001 From: Sam Kleinman Date: Wed, 25 Aug 2021 13:33:38 -0400 Subject: [PATCH 26/37] p2p: change default to use new stack (#6862) This is just a configuration change to default to using the new stack unless explicitly disabled (e.g. `UseLegacy`) this renames the configuration value and makes the configuration logic more clear. The legacy option is good to retain as a fallback if the new stack has issues operationally, but we should make sure that most of the time we're using the new stack. --- config/config.go | 10 +++-- config/toml.go | 2 +- node/node.go | 74 +++++++++++++++++++--------------- node/node_test.go | 3 +- node/setup.go | 32 +++++++-------- test/e2e/generator/generate.go | 18 ++++----- test/e2e/pkg/manifest.go | 8 ++-- test/e2e/pkg/testnet.go | 4 +- test/e2e/runner/setup.go | 24 +++++------ 9 files changed, 92 insertions(+), 83 deletions(-) diff --git a/config/config.go b/config/config.go index 7d19616aa..7e8dd5976 100644 --- a/config/config.go +++ b/config/config.go @@ -694,13 +694,14 @@ type P2PConfig struct { //nolint: maligned // Force dial to fail TestDialFail bool `mapstructure:"test-dial-fail"` - // DisableLegacy is used mostly for testing to enable or disable the legacy - // P2P stack. - DisableLegacy bool `mapstructure:"disable-legacy"` + // UseLegacy enables the "legacy" P2P implementation and + // disables the newer default implementation. This flag will + // be removed in a future release. + UseLegacy bool `mapstructure:"use-legacy"` // Makes it possible to configure which queue backend the p2p // layer uses. Options are: "fifo", "priority" and "wdrr", - // with the default being "fifo". + // with the default being "priority". QueueType string `mapstructure:"queue-type"` } @@ -732,6 +733,7 @@ func DefaultP2PConfig() *P2PConfig { DialTimeout: 3 * time.Second, TestDialFail: false, QueueType: "priority", + UseLegacy: false, } } diff --git a/config/toml.go b/config/toml.go index edb192109..76058802c 100644 --- a/config/toml.go +++ b/config/toml.go @@ -271,7 +271,7 @@ pprof-laddr = "{{ .RPC.PprofListenAddress }}" [p2p] # Enable the new p2p layer. -disable-legacy = {{ .P2P.DisableLegacy }} +use-legacy = {{ .P2P.UseLegacy }} # Select the p2p internal queue queue-type = "{{ .P2P.QueueType }}" diff --git a/node/node.go b/node/node.go index 9cb5315bc..0dedd2861 100644 --- a/node/node.go +++ b/node/node.go @@ -319,12 +319,12 @@ func makeNode(config *cfg.Config, stateSyncReactorShim = p2p.NewReactorShim(logger.With("module", "statesync"), "StateSyncShim", statesync.ChannelShims) - if config.P2P.DisableLegacy { - channels = makeChannelsFromShims(router, statesync.ChannelShims) - peerUpdates = peerManager.Subscribe() - } else { + if config.P2P.UseLegacy { channels = getChannelsFromShim(stateSyncReactorShim) peerUpdates = stateSyncReactorShim.PeerUpdates + } else { + channels = makeChannelsFromShims(router, statesync.ChannelShims) + peerUpdates = peerManager.Subscribe() } stateSyncReactor = statesync.NewReactor( @@ -373,13 +373,7 @@ func makeNode(config *cfg.Config, pexCh := pex.ChannelDescriptor() transport.AddChannelDescriptors([]*p2p.ChannelDescriptor{&pexCh}) - if config.P2P.DisableLegacy { - addrBook = nil - pexReactor, err = createPEXReactorV2(config, logger, peerManager, router) - if err != nil { - return nil, err - } - } else { + if config.P2P.UseLegacy { // setup Transport and Switch sw = createSwitch( config, transport, p2pMetrics, mpReactorShim, bcReactorForSwitch, @@ -402,6 +396,12 @@ func makeNode(config *cfg.Config, } pexReactor = createPEXReactorAndAddToSwitch(addrBook, config, sw, logger) + } else { + addrBook = nil + pexReactor, err = createPEXReactorV2(config, logger, peerManager, router) + if err != nil { + return nil, err + } } if config.RPC.PprofListenAddress != "" { @@ -461,6 +461,17 @@ func makeNode(config *cfg.Config, }, } + // this is a terrible, because typed nil interfaces are not == + // nil, so this is just cleanup to avoid having a non-nil + // value in the RPC environment that has the semantic + // properties of nil. + if sw == nil { + node.rpcEnv.P2PPeers = nil + } else if peerManager == nil { + node.rpcEnv.PeerManager = nil + } + // end hack + node.rpcEnv.P2PTransport = node node.BaseService = *service.NewBaseService(logger, "Node", node) @@ -519,12 +530,8 @@ func makeSeedNode(config *cfg.Config, // p2p stack is removed. pexCh := pex.ChannelDescriptor() transport.AddChannelDescriptors([]*p2p.ChannelDescriptor{&pexCh}) - if config.P2P.DisableLegacy { - pexReactor, err = createPEXReactorV2(config, logger, peerManager, router) - if err != nil { - return nil, err - } - } else { + + if config.P2P.UseLegacy { sw = createSwitch( config, transport, p2pMetrics, nil, nil, nil, nil, nil, nil, nodeInfo, nodeKey, p2pLogger, @@ -546,6 +553,11 @@ func makeSeedNode(config *cfg.Config, } pexReactor = createPEXReactorAndAddToSwitch(addrBook, config, sw, logger) + } else { + pexReactor, err = createPEXReactorV2(config, logger, peerManager, router) + if err != nil { + return nil, err + } } if config.RPC.PprofListenAddress != "" { @@ -608,18 +620,16 @@ func (n *nodeImpl) OnStart() error { } n.isListening = true - n.Logger.Info("p2p service", "legacy_enabled", !n.config.P2P.DisableLegacy) + n.Logger.Info("p2p service", "legacy_enabled", n.config.P2P.UseLegacy) - if n.config.P2P.DisableLegacy { - if err = n.router.Start(); err != nil { - return err - } - } else { + if n.config.P2P.UseLegacy { // Add private IDs to addrbook to block those peers being added n.addrBook.AddPrivateIDs(strings.SplitAndTrimEmpty(n.config.P2P.PrivatePeerIDs, ",", " ")) if err = n.sw.Start(); err != nil { return err } + } else if err = n.router.Start(); err != nil { + return err } if n.config.Mode != cfg.ModeSeed { @@ -650,16 +660,14 @@ func (n *nodeImpl) OnStart() error { } } - if n.config.P2P.DisableLegacy { - if err := n.pexReactor.Start(); err != nil { - return err - } - } else { + if n.config.P2P.UseLegacy { // Always connect to persistent peers err = n.sw.DialPeersAsync(strings.SplitAndTrimEmpty(n.config.P2P.PersistentPeers, ",", " ")) if err != nil { return fmt.Errorf("could not dial peers from persistent-peers field: %w", err) } + } else if err := n.pexReactor.Start(); err != nil { + return err } // Run state sync @@ -738,14 +746,14 @@ func (n *nodeImpl) OnStop() { n.Logger.Error("failed to stop the PEX v2 reactor", "err", err) } - if n.config.P2P.DisableLegacy { - if err := n.router.Stop(); err != nil { - n.Logger.Error("failed to stop router", "err", err) - } - } else { + if n.config.P2P.UseLegacy { if err := n.sw.Stop(); err != nil { n.Logger.Error("failed to stop switch", "err", err) } + } else { + if err := n.router.Stop(); err != nil { + n.Logger.Error("failed to stop router", "err", err) + } } if err := n.transport.Close(); err != nil { diff --git a/node/node_test.go b/node/node_test.go index 64b28c0bb..885bddcfc 100644 --- a/node/node_test.go +++ b/node/node_test.go @@ -43,6 +43,7 @@ import ( func TestNodeStartStop(t *testing.T) { config := cfg.ResetTestRoot("node_node_test") + defer os.RemoveAll(config.RootDir) // create & start node @@ -53,8 +54,6 @@ func TestNodeStartStop(t *testing.T) { n, ok := ns.(*nodeImpl) require.True(t, ok) - t.Logf("Started node %v", n.sw.NodeInfo()) - // wait for the node to produce a block blocksSub, err := n.EventBus().Subscribe(context.Background(), "node_test", types.EventQueryNewBlock) require.NoError(t, err) diff --git a/node/setup.go b/node/setup.go index 6d2a7523b..00f8051f0 100644 --- a/node/setup.go +++ b/node/setup.go @@ -166,12 +166,12 @@ func createMempoolReactor( peerUpdates *p2p.PeerUpdates ) - if config.P2P.DisableLegacy { - channels = makeChannelsFromShims(router, channelShims) - peerUpdates = peerManager.Subscribe() - } else { + if config.P2P.UseLegacy { channels = getChannelsFromShim(reactorShim) peerUpdates = reactorShim.PeerUpdates + } else { + channels = makeChannelsFromShims(router, channelShims) + peerUpdates = peerManager.Subscribe() } switch config.Mempool.Version { @@ -260,12 +260,12 @@ func createEvidenceReactor( peerUpdates *p2p.PeerUpdates ) - if config.P2P.DisableLegacy { - channels = makeChannelsFromShims(router, evidence.ChannelShims) - peerUpdates = peerManager.Subscribe() - } else { + if config.P2P.UseLegacy { channels = getChannelsFromShim(reactorShim) peerUpdates = reactorShim.PeerUpdates + } else { + channels = makeChannelsFromShims(router, evidence.ChannelShims) + peerUpdates = peerManager.Subscribe() } evidenceReactor := evidence.NewReactor( @@ -302,12 +302,12 @@ func createBlockchainReactor( peerUpdates *p2p.PeerUpdates ) - if config.P2P.DisableLegacy { - channels = makeChannelsFromShims(router, bcv0.ChannelShims) - peerUpdates = peerManager.Subscribe() - } else { + if config.P2P.UseLegacy { channels = getChannelsFromShim(reactorShim) peerUpdates = reactorShim.PeerUpdates + } else { + channels = makeChannelsFromShims(router, bcv0.ChannelShims) + peerUpdates = peerManager.Subscribe() } reactor, err := bcv0.NewReactor( @@ -366,12 +366,12 @@ func createConsensusReactor( peerUpdates *p2p.PeerUpdates ) - if config.P2P.DisableLegacy { - channels = makeChannelsFromShims(router, cs.ChannelShims) - peerUpdates = peerManager.Subscribe() - } else { + if config.P2P.UseLegacy { channels = getChannelsFromShim(reactorShim) peerUpdates = reactorShim.PeerUpdates + } else { + channels = makeChannelsFromShims(router, cs.ChannelShims) + peerUpdates = peerManager.Subscribe() } reactor := cs.NewReactor( diff --git a/test/e2e/generator/generate.go b/test/e2e/generator/generate.go index f699b1162..12997eb81 100644 --- a/test/e2e/generator/generate.go +++ b/test/e2e/generator/generate.go @@ -107,11 +107,11 @@ func generateTestnet(r *rand.Rand, opt map[string]interface{}) (e2e.Manifest, er switch opt["p2p"].(P2PMode) { case NewP2PMode: - manifest.DisableLegacyP2P = true + manifest.UseLegacyP2P = true case LegacyP2PMode: - manifest.DisableLegacyP2P = false + manifest.UseLegacyP2P = false case HybridP2PMode: - manifest.DisableLegacyP2P = false + manifest.UseLegacyP2P = true p2pNodeFactor = 2 default: return manifest, fmt.Errorf("unknown p2p mode %s", opt["p2p"]) @@ -138,9 +138,9 @@ func generateTestnet(r *rand.Rand, opt map[string]interface{}) (e2e.Manifest, er node := generateNode(r, e2e.ModeSeed, 0, manifest.InitialHeight, false) if p2pNodeFactor == 0 { - node.DisableLegacyP2P = manifest.DisableLegacyP2P + node.UseLegacyP2P = manifest.UseLegacyP2P } else if p2pNodeFactor%i == 0 { - node.DisableLegacyP2P = !manifest.DisableLegacyP2P + node.UseLegacyP2P = !manifest.UseLegacyP2P } manifest.Nodes[fmt.Sprintf("seed%02d", i)] = node @@ -162,9 +162,9 @@ func generateTestnet(r *rand.Rand, opt map[string]interface{}) (e2e.Manifest, er r, e2e.ModeValidator, startAt, manifest.InitialHeight, i <= 2) if p2pNodeFactor == 0 { - node.DisableLegacyP2P = manifest.DisableLegacyP2P + node.UseLegacyP2P = manifest.UseLegacyP2P } else if p2pNodeFactor%i == 0 { - node.DisableLegacyP2P = !manifest.DisableLegacyP2P + node.UseLegacyP2P = !manifest.UseLegacyP2P } manifest.Nodes[name] = node @@ -198,9 +198,9 @@ func generateTestnet(r *rand.Rand, opt map[string]interface{}) (e2e.Manifest, er node := generateNode(r, e2e.ModeFull, startAt, manifest.InitialHeight, false) if p2pNodeFactor == 0 { - node.DisableLegacyP2P = manifest.DisableLegacyP2P + node.UseLegacyP2P = manifest.UseLegacyP2P } else if p2pNodeFactor%i == 0 { - node.DisableLegacyP2P = !manifest.DisableLegacyP2P + node.UseLegacyP2P = !manifest.UseLegacyP2P } manifest.Nodes[fmt.Sprintf("full%02d", i)] = node } diff --git a/test/e2e/pkg/manifest.go b/test/e2e/pkg/manifest.go index 5711be37d..1b0fc8753 100644 --- a/test/e2e/pkg/manifest.go +++ b/test/e2e/pkg/manifest.go @@ -59,8 +59,8 @@ type Manifest struct { // by individual nodes. LogLevel string `toml:"log_level"` - // DisableLegacyP2P enables use of the new p2p layer for all nodes in a test. - DisableLegacyP2P bool `toml:"disable_legacy_p2p"` + // UseLegacyP2P uses the legacy p2p layer for all nodes in a test. + UseLegacyP2P bool `toml:"use_legacy_p2p"` // QueueType describes the type of queue that the system uses internally QueueType string `toml:"queue_type"` @@ -147,8 +147,8 @@ type ManifestNode struct { // level. LogLevel string `toml:"log_level"` - // UseNewP2P enables use of the new p2p layer for this node. - DisableLegacyP2P bool `toml:"disable_legacy_p2p"` + // UseLegacyP2P enables use of the legacy p2p layer for this node. + UseLegacyP2P bool `toml:"use_legacy_p2p"` } // Save saves the testnet manifest to a file. diff --git a/test/e2e/pkg/testnet.go b/test/e2e/pkg/testnet.go index cec58bd20..e51fa859e 100644 --- a/test/e2e/pkg/testnet.go +++ b/test/e2e/pkg/testnet.go @@ -92,7 +92,7 @@ type Node struct { PersistentPeers []*Node Perturbations []Perturbation LogLevel string - DisableLegacyP2P bool + UseLegacyP2P bool QueueType string } @@ -177,7 +177,7 @@ func LoadTestnet(file string) (*Testnet, error) { Perturbations: []Perturbation{}, LogLevel: manifest.LogLevel, QueueType: manifest.QueueType, - DisableLegacyP2P: manifest.DisableLegacyP2P || nodeManifest.DisableLegacyP2P, + UseLegacyP2P: manifest.UseLegacyP2P && nodeManifest.UseLegacyP2P, } if node.StartAt == testnet.InitialHeight { diff --git a/test/e2e/runner/setup.go b/test/e2e/runner/setup.go index c968ef306..a0bd4996a 100644 --- a/test/e2e/runner/setup.go +++ b/test/e2e/runner/setup.go @@ -238,7 +238,7 @@ func MakeConfig(node *e2e.Node) (*config.Config, error) { cfg.RPC.PprofListenAddress = ":6060" cfg.P2P.ExternalAddress = fmt.Sprintf("tcp://%v", node.AddressP2P(false)) cfg.P2P.AddrBookStrict = false - cfg.P2P.DisableLegacy = node.DisableLegacyP2P + cfg.P2P.UseLegacy = node.UseLegacyP2P cfg.P2P.QueueType = node.QueueType cfg.DBBackend = node.Database cfg.StateSync.DiscoveryTime = 5 * time.Second @@ -342,17 +342,17 @@ func MakeConfig(node *e2e.Node) (*config.Config, error) { // MakeAppConfig generates an ABCI application config for a node. func MakeAppConfig(node *e2e.Node) ([]byte, error) { cfg := map[string]interface{}{ - "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, - "retain_blocks": node.RetainBlocks, - "key_type": node.PrivvalKey.Type(), - "disable_legacy_p2p": node.DisableLegacyP2P, + "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, + "retain_blocks": node.RetainBlocks, + "key_type": node.PrivvalKey.Type(), + "use_legacy_p2p": node.UseLegacyP2P, } switch node.ABCIProtocol { case e2e.ProtocolUNIX: From 58a6cfff9a3e370e1bb5eabfd86091e0b1186a22 Mon Sep 17 00:00:00 2001 From: Aleksandr Bezobchuk Date: Wed, 25 Aug 2021 18:43:21 -0400 Subject: [PATCH 27/37] internal/consensus: update error log (#6863) Issues reported in Osmosis, where the message is extremely long. Also, there is absolutely no reason to log the message IMO. If we must, we can make the message log DEBUG. --- internal/consensus/state.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/consensus/state.go b/internal/consensus/state.go index 4da989b40..6379b71d5 100644 --- a/internal/consensus/state.go +++ b/internal/consensus/state.go @@ -916,8 +916,8 @@ func (cs *State) handleMsg(mi msgInfo) { "height", cs.Height, "round", cs.Round, "peer", peerID, + "msg_type", fmt.Sprintf("%T", msg), "err", err, - "msg", msg, ) } } From 23abb0de8b5b7bf77bfcf95fbbbc7715420b04e4 Mon Sep 17 00:00:00 2001 From: Sam Kleinman Date: Fri, 27 Aug 2021 12:14:59 -0400 Subject: [PATCH 28/37] rfc: p2p next steps (#6866) --- docs/rfc/README.md | 2 + docs/rfc/rfc-000-p2p-roadmap.rst | 316 +++++++++++++++++++++++++++++++ 2 files changed, 318 insertions(+) create mode 100644 docs/rfc/rfc-000-p2p-roadmap.rst diff --git a/docs/rfc/README.md b/docs/rfc/README.md index c05853aca..68733c8e8 100644 --- a/docs/rfc/README.md +++ b/docs/rfc/README.md @@ -37,4 +37,6 @@ sections. ## Table of Contents +- [RFC-000: P2P Roadmap](./rfc-000.p2p-roadmap.rst) + diff --git a/docs/rfc/rfc-000-p2p-roadmap.rst b/docs/rfc/rfc-000-p2p-roadmap.rst new file mode 100644 index 000000000..64dda773e --- /dev/null +++ b/docs/rfc/rfc-000-p2p-roadmap.rst @@ -0,0 +1,316 @@ +==================== +RFC 000: P2P Roadmap +==================== + +Changelog +--------- + +- 2021-08-20: Completed initial draft and distributed via a gist +- 2021-08-25: Migrated as an RFC and changed format + +Abstract +-------- + +This document discusses the future of peer network management in Tendermint, with +a particular focus on features, semantics, and a proposed roadmap. +Specifically, we consider libp2p as a tool kit for implementing some fundamentals. + +Background +---------- + +For the 0.35 release cycle the switching/routing layer of Tendermint was +replaced. This work was done "in place," and produced a version of Tendermint +that was backward-compatible and interoperable with previous versions of the +software. While there are new p2p/peer management constructs in the new +version (e.g. ``PeerManager`` and ``Router``), the main effect of this change +was to simplify the ways that other components within Tendermint interacted with +the peer management layer, and to make it possible for higher-level components +(specifically the reactors), to be used and tested more independently. + +This refactoring, which was a major undertaking, was entirely necessary to +enable areas for future development and iteration on this aspect of +Tendermint. There are also a number of potential user-facing features that +depend heavily on the p2p layer: additional transport protocols, transport +compression, improved resilience to network partitions. These improvements to +modularity, stability, and reliability of the p2p system will also make +ongoing maintenance and feature development easier in the rest of Tendermint. + +Critique of Current Peer-to-Peer Infrastructure +--------------------------------------- + +The current (refactored) P2P stack is an improvement on the previous iteration +(legacy), but as of 0.35, there remains room for improvement in the design and +implementation of the P2P layer. + +Some limitations of the current stack include: + +- heavy reliance on buffering to avoid backups in the flow of components, + which is fragile to maintain and can lead to unexpected memory usage + patterns and forces the routing layer to make decisions about when messages + should be discarded. + +- the current p2p stack relies on convention (rather than the compiler) to + enforce the API boundaries and conventions between reactors and the router, + making it very easy to write "wrong" reactor code or introduce a bad + dependency. + +- the current stack is probably more complex and difficult to maintain because + the legacy system must coexist with the new components in 0.35. When the + legacy stack is removed there are some simple changes that will become + possible and could reduce the complexity of the new system. (e.g. `#6598 + `_.) + +- the current stack encapsulates a lot of information about peers, and makes it + difficult to expose that information to monitoring/observability tools. This + general opacity also makes it difficult to interact with the peer system + from other areas of the code base (e.g. tests, reactors). + +- the legacy stack provided some control to operators to force the system to + dial new peers or seed nodes or manipulate the topology of the system _in + situ_. The current stack can't easily provide this, and while the new stack + may have better behavior, it does leave operators hands tied. + +Some of these issues will be resolved early in the 0.36 cycle, with the +removal of the legacy components. + +The 0.36 release also provides the opportunity to make changes to the +protocol, as the release will not be compatible with previous releases. + +Areas for Development +--------------------- + +These sections describe features that may make sense to include in a Phase 2 of +a P2P project. + +Internal Message Passing +~~~~~~~~~~~~~~~~~~~~~~~~ + +Currently, there's no provision for intranode communication using the P2P +layer, which means when two reactors need to interact with each other they +have to have dependencies on each other's interfaces, and +initialization. Changing these interactions (e.g. transitions between +blocksync and consensus) from procedure calls to message passing. + +This is a relatively simple change and could be implemented with the following +components: + +- a constant to represent "local" delivery as the ``To``` field on + ``p2p.Envelope``. + +- special path for routing local messages that doesn't require message + serialization (protobuf marshalling/unmarshaling). + +Adding these semantics, particularly if in conjunction with synchronous +semantics provides a solution to dependency graph problems currently present +in the Tendermint codebase, which will simplify development, make it possible +to isolate components for testing. + +Eventually, this will also make it possible to have a logical Tendermint node +running in multiple processes or in a collection of containers, although the +usecase of this may be debatable. + +Synchronous Semantics (Paired Request/Response) +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +In the current system, all messages are sent with fire-and-forget semantics, +and there's no coupling between a request sent via the p2p layer, and a +response. These kinds of semantics would simplify the implementation of +state and block sync reactors, and make intra-node message passing more +powerful. + +For some interactions, like gossiping transactions between the mempools of +different nodes, fire-and-forget semantics make sense, but for other +operations the missing link between requests/responses leads to either +inefficiency when a node fails to respond or becomes unavailable, or code that +is just difficult to follow. + +To support this kind of work, the protocol would need to accommodate some kind +of request/response ID to allow identifying out-of-order responses over a +single connection. Additionally, expanded the programming model of the +``p2p.Channel`` to accommodate some kind of _future_ or similar paradigm to +make it viable to write reactor code without needing for the reactor developer +to wrestle with lower level concurency constructs. + + +Timeout Handling (QoS) +~~~~~~~~~~~~~~~~~~~~~~ + +Currently, all timeouts, buffering, and QoS features are handled at the router +layer, and the reactors are implemented in ways that assume/require +asynchronous operation. This both increases the required complexity at the +routing layer, and means that misbehavior at the reactor level is difficult to +detect or attribute. Additionally, the current system provides three main +parameters to control quality of service: + +- buffer sizes for channels and queues. + +- priorities for channels + +- queue implementation details for shedding load. + +These end up being quite coarse controls, and changing the settings are +difficult because as the queues and channels are able to buffer large numbers +of messages it can be hard to see the impact of a given change, particularly +in our extant test environment. In general, we should endeavor to: + +- set real timeouts, via contexts, on most message send operations, so that + senders rather than queues can be responsible for timeout + logic. Additionally, this will make it possible to avoid sending messages + during shutdown. + +- reduce (to the greatest extent possible) the amount of buffering in + channels and the queues, to more readily surface backpressure and reduce the + potential for buildup of stale messages. + +Stream Based Connection Handling +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Currently the transport layer is message based, which makes sense from a +mental model of how the protocol works, but makes it more difficult to +implement transports and connection types, as it forces a higher level view of +the connection and interaction which makes it harder to implement for novel +transport types and makes it more likely that message-based caching and rate +limiting will be implemented at the transport layer rather than at a more +appropriate level. + +The transport then, would be responsible for negitating the connection and the +handshake and otherwise behave like a socket/file discriptor with ``Read` and +``Write`` methods. + +While this was included in the initial design for the new P2P layer, it may be +obviated entirely if the transport and peer layer is replaced with libp2p, +which is primarily stream based. + +Service Discovery +~~~~~~~~~~~~~~~~~ + +In the current system, Tendermint assumes that all nodes in a network are +largely equivelent, and nodes tend to be "chatty" making many requests of +large numbers of peers and waiting for peers to (hopefully) respond. While +this works and has allowed Tendermint to get to a certain point, this both +produces a theoretical scaling bottle neck and makes it harder to test and +verify components of the system. + +In addition to peer's identity and connection information, peers should be +able to advertise a number of services or capabilities, and node operators or +developers should be able to specify peer capability requirements (e.g. target +at least -percent of peers with capability.) + +These capabilities may be useful in selecting peers to send messages to, it +may make sense to extend Tendermint's message addressing capability to allow +reactors to send messages to groups of peers based on role rather than only +allowing addressing to one or all peers. + +Having a good service discovery mechanism may pair well with the synchronous +semantics (request/response) work, as it allows reactors to "make a request of +a peer with capability and wait for the response," rather force the +reactors to need to track the capabilities or state of specific peers. + +Solutions +--------- + +Continued Homegrown Implementation +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The current peer system is homegrown and is conceptually compatible with the +needs of the project, and while there are limitations to the system, the p2p +layer is not (currently as of 0.35) a major source of bugs or friction during +development. + +However, the current implementation makes a number of allowances for +interoperability, and there are a collection of iterative improvements that +should be considered in the next couple of releases. To maintain the current +implementation, upcoming work would include: + +- change the ``Transport`` mechanism to facilitate easier implementations. + +- implement different ``Transport`` handlers to be able to manage peer + connections using different protocols (e.g. QUIC, etc.) + +- entirely remove the constructs and implementations of the legacy peer + implementation. + +- establish and enforce clearer chains of responsibility for connection + establishment (e.g. handshaking, setup,) which is currently shared between + three components. + +- report better metrics regarding the into the state of peers and network + connectivity, which are opaque outside of the system. This is constrained at + the moment as a side effect of the split responsibility for connection + establishment. + +- extend the PEX system to include service information so that ndoes in the + network weren't necessarily homogeneous. + +While maintaining a bespoke peer management layer would seem to distract from +development of core functionality, the truth is that (once the legacy code is +removed,) the scope of the peer layer is relatively small from a maintenance +perspective, and having control at this layer might actually afford the +project with the ability to more rapidly iterate on some features. + +LibP2P +~~~~~~ + +LibP2P provides components that, approximately, account for the +``PeerManager`` and ``Transport`` components of the current (new) P2P +stack. The Go APIs seem reasonable, and being able to externalize the +implementation details of peer and connection management seems like it could +provide a lot of benefits, particularly in supporting a more active ecosystem. + +In general the API provides the kind of stream-based, multi-protocol +supporting, and idiomatic baseline for implementing a peer layer. Additionally +because it handles peer exchange and connection management at a lower +level, by using libp2p it'd be possible to remove a good deal of code in favor +of just using libp2p. Having said that, Tendermint's P2P layer covers a +greater scope (e.g. message routing to different peers) and that layer is +something that Tendermint might want to retain. + +The are a number of unknowns that require more research including how much of +a peer database the Tendermint engine itself needs to maintain, in order to +support higher level operations (consensus, statesync), but it might be the +case that our internal systems need to know much less about peers than +otherwise specified. Similarly, the current system has a notion of peer +scoring that cannot be communicated to libp2p, which may be fine as this is +only used to support peer exchange (PEX,) which would become a property libp2p +and not expressed in it's current higher-level form. + +In general, the effort to switch to libp2p would involve: + +- timing it during an appropriate protocol-breaking window, as it doesn't seem + viable to support both libp2p *and* the current p2p protocol. + +- providing some in-memory testing network to support the use case that the + current ``p2p.MemoryNetwork`` provides. + +- re-homing the ``p2p.Router`` implementation on top of libp2p components to + be able to maintain the current reactor implementations. + +Open question include: + +- how much local buffering should we be doing? It sort of seems like we should + figure out what the expected behavior is for libp2p for QoS-type + functionality, and if our requirements mean that we should be implementing + this on top of things ourselves? + +- if Tendermint was going to use libp2p, how would libp2p's stability + guarantees (protocol, etc.) impact/constrain Tendermint's stability + guarantees? + +- what kind of introspection does libp2p provide, and to what extend would + this change or constrain the kind of observability that Tendermint is able + to provide? + +- how do efforts to select "the best" (healthy, close, well-behaving, etc.) + peers work out if Tendermint is not maintaining a local peer database? + +- would adding additional higher level semantics (internal message passing, + request/response pairs, service discovery, etc.) facilitate removing some of + the direct linkages between constructs/components in the system and reduce + the need for Tendermint nodes to maintain state about its peers? + +References +---------- + +- `Tracking Ticket for P2P Refactor Project `_ +- `ADR 61: P2P Refactor Scope <../architecture/adr-061-p2p-refactor-scope.md>`_ +- `ADR 62: P2P Architecture and Abstraction <../architecture/adr-061-p2p-architecture.md>`_ From 94e1eb8cfec822d05403c0c85ef9c2a0d96e31a7 Mon Sep 17 00:00:00 2001 From: Sam Kleinman Date: Fri, 27 Aug 2021 12:21:12 -0400 Subject: [PATCH 29/37] rfc: fix link style (#6870) This is super minor, but chaning this to fix a broken link and to match the existing style of the ADRs. --- docs/rfc/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/rfc/README.md b/docs/rfc/README.md index 68733c8e8..10b3cccf9 100644 --- a/docs/rfc/README.md +++ b/docs/rfc/README.md @@ -37,6 +37,6 @@ sections. ## Table of Contents -- [RFC-000: P2P Roadmap](./rfc-000.p2p-roadmap.rst) +- [RFC-000: P2P Roadmap](./rfc-000-p2p-roadmap.rst) - + From 0df421b37f82ffe543e63e7460b5c64e4c56a203 Mon Sep 17 00:00:00 2001 From: Sam Kleinman Date: Fri, 27 Aug 2021 12:32:40 -0400 Subject: [PATCH 30/37] e2e: add weighted random configuration selector (#6869) When revwing #6807 I assumed that `probSetChoice` worked this way. I think that the coverage of various configuration options should generally track what we expect the actual useage to be to focus the most test coverage on the configurations that are the most prevelent. --- go.mod | 1 + go.sum | 2 ++ test/e2e/generator/generate.go | 12 +++++++++--- test/e2e/generator/random.go | 18 ++++++++++++++++++ 4 files changed, 30 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index c7723b096..f88b1d689 100644 --- a/go.mod +++ b/go.mod @@ -22,6 +22,7 @@ require ( github.com/lib/pq v1.10.2 github.com/libp2p/go-buffer-pool v0.0.2 github.com/minio/highwayhash v1.0.2 + github.com/mroth/weightedrand v0.4.1 // indirect github.com/oasisprotocol/curve25519-voi v0.0.0-20210609091139-0a56a4bca00b github.com/ory/dockertest v3.3.5+incompatible github.com/prometheus/client_golang v1.11.0 diff --git a/go.sum b/go.sum index fff11dad3..606ea9f0b 100644 --- a/go.sum +++ b/go.sum @@ -636,6 +636,8 @@ github.com/moricho/tparallel v0.2.1 h1:95FytivzT6rYzdJLdtfn6m1bfFJylOJK41+lgv/EH 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 h1:rHcbUBopmi/3x4nnrvwGJBhX9d0vk+KgoLUZeDP6YyI= +github.com/mroth/weightedrand v0.4.1/go.mod h1:3p2SIcC8al1YMzGhAIoXD+r9olo/g/cdJgAD905gyNE= 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= diff --git a/test/e2e/generator/generate.go b/test/e2e/generator/generate.go index 12997eb81..1c48157a1 100644 --- a/test/e2e/generator/generate.go +++ b/test/e2e/generator/generate.go @@ -26,7 +26,13 @@ var ( } // The following specify randomly chosen values for testnet nodes. - nodeDatabases = uniformChoice{"goleveldb", "cleveldb", "rocksdb", "boltdb", "badgerdb"} + nodeDatabases = weightedChoice{ + "goleveldb": 35, + "badgerdb": 35, + "boltdb": 15, + "rocksdb": 10, + "cleveldb": 5, + } nodeABCIProtocols = uniformChoice{"unix", "tcp", "builtin", "grpc"} nodePrivvalProtocols = uniformChoice{"file", "unix", "tcp", "grpc"} // FIXME: v2 disabled due to flake @@ -270,7 +276,7 @@ func generateNode( node := e2e.ManifestNode{ Mode: string(mode), StartAt: startAt, - Database: nodeDatabases.Choose(r).(string), + Database: nodeDatabases.Choose(r), ABCIProtocol: nodeABCIProtocols.Choose(r).(string), PrivvalProtocol: nodePrivvalProtocols.Choose(r).(string), BlockSync: nodeBlockSyncs.Choose(r).(string), @@ -321,7 +327,7 @@ func generateLightNode(r *rand.Rand, startAt int64, providers []string) *e2e.Man return &e2e.ManifestNode{ Mode: string(e2e.ModeLight), StartAt: startAt, - Database: nodeDatabases.Choose(r).(string), + Database: nodeDatabases.Choose(r), ABCIProtocol: "builtin", PersistInterval: ptrUint64(0), PersistentPeers: providers, diff --git a/test/e2e/generator/random.go b/test/e2e/generator/random.go index f21502118..d6c84d46c 100644 --- a/test/e2e/generator/random.go +++ b/test/e2e/generator/random.go @@ -3,6 +3,8 @@ package main import ( "math/rand" "sort" + + "github.com/mroth/weightedrand" ) // combinations takes input in the form of a map of item lists, and returns a @@ -83,3 +85,19 @@ func (usc uniformSetChoice) Choose(r *rand.Rand) []string { } return choices } + +type weightedChoice map[string]uint + +func (wc weightedChoice) Choose(r *rand.Rand) string { + choices := make([]weightedrand.Choice, 0, len(wc)) + for k, v := range wc { + choices = append(choices, weightedrand.NewChoice(k, v)) + } + + chooser, err := weightedrand.NewChooser(choices...) + if err != nil { + panic(err) + } + + return chooser.PickSource(r).(string) +} From c9347a064793e8969878713e65353193f58820d5 Mon Sep 17 00:00:00 2001 From: Jeeyong Um Date: Mon, 30 Aug 2021 01:14:20 +0900 Subject: [PATCH 31/37] docs: remove return code in normal case from go built-in example (#6841) An explicit exit prevents the deferred cleanup code from running. In this case, falling off the end of main will achieve the same goal as an explicit exit. --- docs/tutorials/go-built-in.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/docs/tutorials/go-built-in.md b/docs/tutorials/go-built-in.md index 81325706b..e94fe171e 100644 --- a/docs/tutorials/go-built-in.md +++ b/docs/tutorials/go-built-in.md @@ -388,7 +388,6 @@ func main() { c := make(chan os.Signal, 1) signal.Notify(c, os.Interrupt, syscall.SIGTERM) <-c - os.Exit(0) } func newTendermint(app abci.Application, configFile string) (*nm.Node, error) { @@ -564,7 +563,6 @@ defer func() { c := make(chan os.Signal, 1) signal.Notify(c, os.Interrupt, syscall.SIGTERM) <-c -os.Exit(0) ``` ## 1.5 Getting Up and Running From f858ebeb882f7cc8e73136d972187bd52ddebb1b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 30 Aug 2021 09:34:54 -0400 Subject: [PATCH 32/37] build(deps): Bump github.com/rs/zerolog from 1.23.0 to 1.24.0 (#6874) Bumps [github.com/rs/zerolog](https://github.com/rs/zerolog) from 1.23.0 to 1.24.0. - [Release notes](https://github.com/rs/zerolog/releases) - [Commits](https://github.com/rs/zerolog/compare/v1.23.0...v1.24.0) --- updated-dependencies: - dependency-name: github.com/rs/zerolog dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 6 +++--- go.sum | 5 +++-- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/go.mod b/go.mod index f88b1d689..66b982309 100644 --- a/go.mod +++ b/go.mod @@ -22,13 +22,13 @@ require ( github.com/lib/pq v1.10.2 github.com/libp2p/go-buffer-pool v0.0.2 github.com/minio/highwayhash v1.0.2 - github.com/mroth/weightedrand v0.4.1 // indirect + github.com/mroth/weightedrand v0.4.1 github.com/oasisprotocol/curve25519-voi v0.0.0-20210609091139-0a56a4bca00b github.com/ory/dockertest v3.3.5+incompatible github.com/prometheus/client_golang v1.11.0 github.com/rcrowley/go-metrics v0.0.0-20200313005456-10cdbea86bc0 github.com/rs/cors v1.8.0 - github.com/rs/zerolog v1.23.0 + github.com/rs/zerolog v1.24.0 github.com/sasha-s/go-deadlock v0.2.1-0.20190427202633-1595213edefa github.com/snikch/goodman v0.0.0-20171125024755-10e37e294daa github.com/spf13/cobra v1.2.1 @@ -38,7 +38,7 @@ require ( github.com/vektra/mockery/v2 v2.9.0 golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a golang.org/x/net v0.0.0-20210428140749-89ef3d95e781 - golang.org/x/sync v0.0.0-20210220032951-036812b2e83c // indirect + golang.org/x/sync v0.0.0-20210220032951-036812b2e83c google.golang.org/grpc v1.40.0 gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b // indirect pgregory.net/rapid v0.4.7 diff --git a/go.sum b/go.sum index 606ea9f0b..32619bf08 100644 --- a/go.sum +++ b/go.sum @@ -770,9 +770,10 @@ 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.23.0 h1:UskrK+saS9P9Y789yNNulYKdARjPZuS35B8gJF2x60g= -github.com/rs/zerolog v1.23.0/go.mod h1:6c7hFfxPOy7TacJc4Fcdi24/J0NKYGzjG8FWRI916Qo= +github.com/rs/zerolog v1.24.0 h1:76ivFxmVSRs1u2wUwJVg5VZDYQgeH1JpoS6ndgr9Wy8= +github.com/rs/zerolog v1.24.0/go.mod h1:7KHcEGe0QZPOm2IE4Kpb5rTh6n1h2hIgS5OOnu1rUaI= 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/ryancurrah/gomodguard v1.2.3 h1:ww2fsjqocGCAFamzvv/b8IsRduuHHeK2MHTcTxZTQX8= From c4df8a3840412b26a97de69d6437e883fd43f73f Mon Sep 17 00:00:00 2001 From: Sam Kleinman Date: Mon, 30 Aug 2021 13:42:58 -0400 Subject: [PATCH 33/37] types: move mempool error for consistency (#6875) This is a little change just to make things more consistent ahead of the 0.35 release. --- internal/mempool/v0/clist_mempool.go | 9 ++++----- internal/mempool/v0/clist_mempool_test.go | 7 +++---- internal/mempool/v1/mempool.go | 9 ++++----- pkg/mempool/errors.go => types/mempool.go | 2 +- 4 files changed, 12 insertions(+), 15 deletions(-) rename pkg/mempool/errors.go => types/mempool.go (98%) diff --git a/internal/mempool/v0/clist_mempool.go b/internal/mempool/v0/clist_mempool.go index 40e93cc13..167fe0410 100644 --- a/internal/mempool/v0/clist_mempool.go +++ b/internal/mempool/v0/clist_mempool.go @@ -14,7 +14,6 @@ import ( "github.com/tendermint/tendermint/internal/mempool" "github.com/tendermint/tendermint/libs/log" tmmath "github.com/tendermint/tendermint/libs/math" - pubmempool "github.com/tendermint/tendermint/pkg/mempool" "github.com/tendermint/tendermint/proxy" "github.com/tendermint/tendermint/types" ) @@ -217,7 +216,7 @@ func (mem *CListMempool) CheckTx( } if txSize > mem.config.MaxTxBytes { - return pubmempool.ErrTxTooLarge{ + return types.ErrTxTooLarge{ Max: mem.config.MaxTxBytes, Actual: txSize, } @@ -225,7 +224,7 @@ func (mem *CListMempool) CheckTx( if mem.preCheck != nil { if err := mem.preCheck(tx); err != nil { - return pubmempool.ErrPreCheck{ + return types.ErrPreCheck{ Reason: err, } } @@ -248,7 +247,7 @@ func (mem *CListMempool) CheckTx( // its non-trivial since invalid txs can become valid, // but they can spam the same tx with little cost to them atm. if loaded { - return pubmempool.ErrTxInCache + return types.ErrTxInCache } } @@ -364,7 +363,7 @@ func (mem *CListMempool) isFull(txSize int) error { ) if memSize >= mem.config.Size || int64(txSize)+txsBytes > mem.config.MaxTxsBytes { - return pubmempool.ErrMempoolIsFull{ + return types.ErrMempoolIsFull{ NumTxs: memSize, MaxTxs: mem.config.Size, TxsBytes: txsBytes, diff --git a/internal/mempool/v0/clist_mempool_test.go b/internal/mempool/v0/clist_mempool_test.go index 65a1b123e..6f3e6ebc0 100644 --- a/internal/mempool/v0/clist_mempool_test.go +++ b/internal/mempool/v0/clist_mempool_test.go @@ -23,7 +23,6 @@ import ( "github.com/tendermint/tendermint/libs/log" tmrand "github.com/tendermint/tendermint/libs/rand" "github.com/tendermint/tendermint/libs/service" - pubmempool "github.com/tendermint/tendermint/pkg/mempool" "github.com/tendermint/tendermint/proxy" "github.com/tendermint/tendermint/types" ) @@ -82,7 +81,7 @@ func checkTxs(t *testing.T, mp mempool.Mempool, count int, peerID uint16) types. // Skip invalid txs. // TestMempoolFilters will fail otherwise. It asserts a number of txs // returned. - if pubmempool.IsPreCheckError(err) { + if types.IsPreCheckError(err) { continue } t.Fatalf("CheckTx failed: %v while checking #%d tx", err, i) @@ -455,7 +454,7 @@ func TestMempool_CheckTxChecksTxSize(t *testing.T) { if !testCase.err { require.NoError(t, err, caseString) } else { - require.Equal(t, err, pubmempool.ErrTxTooLarge{ + require.Equal(t, err, types.ErrTxTooLarge{ Max: maxTxSize, Actual: testCase.len, }, caseString) @@ -503,7 +502,7 @@ func TestMempoolTxsBytes(t *testing.T) { err = mp.CheckTx(context.Background(), []byte{0x05}, nil, mempool.TxInfo{}) if assert.Error(t, err) { - assert.IsType(t, pubmempool.ErrMempoolIsFull{}, err) + assert.IsType(t, types.ErrMempoolIsFull{}, err) } // 6. zero after tx is rechecked and removed due to not being valid anymore diff --git a/internal/mempool/v1/mempool.go b/internal/mempool/v1/mempool.go index 850600697..46e7f5fcc 100644 --- a/internal/mempool/v1/mempool.go +++ b/internal/mempool/v1/mempool.go @@ -14,7 +14,6 @@ import ( "github.com/tendermint/tendermint/internal/mempool" "github.com/tendermint/tendermint/libs/log" tmmath "github.com/tendermint/tendermint/libs/math" - pubmempool "github.com/tendermint/tendermint/pkg/mempool" "github.com/tendermint/tendermint/proxy" "github.com/tendermint/tendermint/types" ) @@ -239,7 +238,7 @@ func (txmp *TxMempool) CheckTx( txSize := len(tx) if txSize > txmp.config.MaxTxBytes { - return pubmempool.ErrTxTooLarge{ + return types.ErrTxTooLarge{ Max: txmp.config.MaxTxBytes, Actual: txSize, } @@ -247,7 +246,7 @@ func (txmp *TxMempool) CheckTx( if txmp.preCheck != nil { if err := txmp.preCheck(tx); err != nil { - return pubmempool.ErrPreCheck{ + return types.ErrPreCheck{ Reason: err, } } @@ -267,7 +266,7 @@ func (txmp *TxMempool) CheckTx( if wtx != nil && ok { // We already have the transaction stored and the we've already seen this // transaction from txInfo.SenderID. - return pubmempool.ErrTxInCache + return types.ErrTxInCache } txmp.logger.Debug("tx exists already in cache", "tx_hash", tx.Hash()) @@ -728,7 +727,7 @@ func (txmp *TxMempool) canAddTx(wtx *WrappedTx) error { ) if numTxs >= txmp.config.Size || int64(wtx.Size())+sizeBytes > txmp.config.MaxTxsBytes { - return pubmempool.ErrMempoolIsFull{ + return types.ErrMempoolIsFull{ NumTxs: numTxs, MaxTxs: txmp.config.Size, TxsBytes: sizeBytes, diff --git a/pkg/mempool/errors.go b/types/mempool.go similarity index 98% rename from pkg/mempool/errors.go rename to types/mempool.go index e3a9a2217..c739796af 100644 --- a/pkg/mempool/errors.go +++ b/types/mempool.go @@ -1,4 +1,4 @@ -package mempool +package types import ( "errors" From 7169d26ddf9a4d29908c0ad5c21bf6430b5a4bf9 Mon Sep 17 00:00:00 2001 From: Sam Kleinman Date: Tue, 31 Aug 2021 17:56:06 -0400 Subject: [PATCH 34/37] e2e: more reliable method for selecting node to inject evidence (#6880) In retrospect my previous implementation of this node, could get unlucky and never find the correct node. This method is more reliable. --- test/e2e/runner/evidence.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/test/e2e/runner/evidence.go b/test/e2e/runner/evidence.go index 30e8d9f0a..35646ccdb 100644 --- a/test/e2e/runner/evidence.go +++ b/test/e2e/runner/evidence.go @@ -32,8 +32,9 @@ func InjectEvidence(testnet *e2e.Testnet, amount int) error { // select a random node var targetNode *e2e.Node - for i := 0; i < len(testnet.Nodes)-1; i++ { - targetNode = testnet.Nodes[rand.Intn(len(testnet.Nodes))] // nolint: gosec + for _, idx := range rand.Perm(len(testnet.Nodes)) { + targetNode = testnet.Nodes[idx] + if targetNode.Mode == e2e.ModeSeed { targetNode = nil continue From 7fe3e78a38e668649afe6cc745c99fc119d81714 Mon Sep 17 00:00:00 2001 From: "M. J. Fromberger" Date: Tue, 31 Aug 2021 15:35:07 -0700 Subject: [PATCH 35/37] Update the psql indexer schema and implementation (#6868) Update the schema and implementation of the Postgres event indexer to improve certain types of queries against the index. These changes address the use cases raised by #6843, and are partly inspired by the prototype schema in that issue. In the old schema, events were flattened, making it difficult to find all the events associated with a particular block or transaction. In addition, events with no key/value attributes were entirely lost, since entries were generated only for attributes. To address these issues, this new schema records blocks, transactions, events, and attributes in separate tables, and provides views that join these tables to give a more convenient query surface for block and transaction events. - All events for a given block can be queried from the `block_events` view. - All events for a given transaction can be queried from the `tx_events` view. - Multiple events for the same key can be indexed for both blocks and transactions. The tests have been reworked, but all of the existing test cases for the old schema still pass with the new implementation. Various other minor cleanups are included, ADR-065 is also updated to reflect the updated schema. --- .../adr-065-custom-event-indexing.md | 278 +++++---- state/indexer/indexer_service_test.go | 2 +- state/indexer/sink/psql/psql.go | 302 +++++---- state/indexer/sink/psql/psql_test.go | 586 +++++++++--------- state/indexer/sink/psql/schema.sql | 109 +++- 5 files changed, 684 insertions(+), 593 deletions(-) diff --git a/docs/architecture/adr-065-custom-event-indexing.md b/docs/architecture/adr-065-custom-event-indexing.md index e6a3fdead..b5c86ecfa 100644 --- a/docs/architecture/adr-065-custom-event-indexing.md +++ b/docs/architecture/adr-065-custom-event-indexing.md @@ -24,6 +24,7 @@ - April 1, 2021: Initial Draft (@alexanderbez) - April 28, 2021: Specify search capabilities are only supported through the KV indexer (@marbar3778) - May 19, 2021: Update the SQL schema and the eventsink interface (@jayt106) +- Aug 30, 2021: Update the SQL schema and the psql implementation (@creachadair) ## Status @@ -145,163 +146,190 @@ The postgres eventsink will not support `tx_search`, `block_search`, `GetTxByHas ```sql -- Table Definition ---------------------------------------------- -CREATE TYPE block_event_type AS ENUM ('begin_block', 'end_block', ''); +-- The blocks table records metadata about each block. +-- The block record does not include its events or transactions (see tx_results). +CREATE TABLE blocks ( + rowid BIGSERIAL PRIMARY KEY, -CREATE TABLE block_events ( - id SERIAL PRIMARY KEY, - key VARCHAR NOT NULL, - value VARCHAR NOT NULL, - height INTEGER NOT NULL, - type block_event_type, - created_at TIMESTAMPTZ NOT NULL, - chain_id VARCHAR NOT NULL + height BIGINT NOT NULL, + chain_id VARCHAR NOT NULL, + + -- When this block header was logged into the sink, in UTC. + created_at TIMESTAMPTZ NOT NULL, + + UNIQUE (height, chain_id) ); +-- Index blocks by height and chain, since we need to resolve block IDs when +-- indexing transaction records and transaction events. +CREATE INDEX idx_blocks_height_chain ON blocks(height, chain_id); + +-- The tx_results table records metadata about transaction results. Note that +-- the events from a transaction are stored separately. CREATE TABLE tx_results ( - id SERIAL PRIMARY KEY, - tx_result BYTEA NOT NULL, - created_at TIMESTAMPTZ NOT NULL + rowid BIGSERIAL PRIMARY KEY, + + -- The block to which this transaction belongs. + block_id BIGINT NOT NULL REFERENCES blocks(rowid), + -- The sequential index of the transaction within the block. + index INTEGER NOT NULL, + -- When this result record was logged into the sink, in UTC. + created_at TIMESTAMPTZ NOT NULL, + -- The hex-encoded hash of the transaction. + tx_hash VARCHAR NOT NULL, + -- The protobuf wire encoding of the TxResult message. + tx_result BYTEA NOT NULL, + + UNIQUE (block_id, index) ); -CREATE TABLE tx_events ( - id SERIAL PRIMARY KEY, - key VARCHAR NOT NULL, - value VARCHAR NOT NULL, - height INTEGER NOT NULL, - hash VARCHAR NOT NULL, - tx_result_id SERIAL, - created_at TIMESTAMPTZ NOT NULL, - chain_id VARCHAR NOT NULL, - FOREIGN KEY (tx_result_id) - REFERENCES tx_results(id) - ON DELETE CASCADE +-- The events table records events. All events (both block and transaction) are +-- associated with a block ID; transaction events also have a transaction ID. +CREATE TABLE events ( + rowid BIGSERIAL PRIMARY KEY, + + -- The block and transaction this event belongs to. + -- If tx_id is NULL, this is a block event. + block_id BIGINT NOT NULL REFERENCES blocks(rowid), + tx_id BIGINT NULL REFERENCES tx_results(rowid), + + -- The application-defined type label for the event. + type VARCHAR NOT NULL ); --- Indices ------------------------------------------------------- +-- The attributes table records event attributes. +CREATE TABLE attributes ( + event_id BIGINT NOT NULL REFERENCES events(rowid), + key VARCHAR NOT NULL, -- bare key + composite_key VARCHAR NOT NULL, -- composed type.key + value VARCHAR NULL, -CREATE INDEX idx_block_events_key_value ON block_events(key, value); -CREATE INDEX idx_tx_events_key_value ON tx_events(key, value); -CREATE INDEX idx_tx_events_hash ON tx_events(hash); + UNIQUE (event_id, key) +); + +-- A joined view of events and their attributes. Events that do not have any +-- attributes are represented as a single row with empty key and value fields. +CREATE VIEW event_attributes AS + SELECT block_id, tx_id, type, key, composite_key, value + FROM events LEFT JOIN attributes ON (events.rowid = attributes.event_id); + +-- A joined view of all block events (those having tx_id NULL). +CREATE VIEW block_events AS + SELECT blocks.rowid as block_id, height, chain_id, type, key, composite_key, value + FROM blocks JOIN event_attributes ON (blocks.rowid = event_attributes.block_id) + WHERE event_attributes.tx_id IS NULL; + +-- A joined view of all transaction events. +CREATE VIEW tx_events AS + SELECT height, index, chain_id, type, key, composite_key, value, tx_results.created_at + FROM blocks JOIN tx_results ON (blocks.rowid = tx_results.block_id) + JOIN event_attributes ON (tx_results.rowid = event_attributes.tx_id) + WHERE event_attributes.tx_id IS NOT NULL; ``` The `PSQLEventSink` will implement the `EventSink` interface as follows (some details omitted for brevity): - ```go -func NewPSQLEventSink(connStr string, chainID string) (*PSQLEventSink, error) { - db, err := sql.Open("postgres", connStr) - if err != nil { - return nil, err - } +func NewEventSink(connStr, chainID string) (*EventSink, error) { + db, err := sql.Open(driverName, connStr) + // ... - // ... + return &EventSink{ + store: db, + chainID: chainID, + }, nil } -func (es *PSQLEventSink) IndexBlockEvents(h types.EventDataNewBlockHeader) error { - sqlStmt := sq.Insert("block_events").Columns("key", "value", "height", "type", "created_at", "chain_id") +func (es *EventSink) IndexBlockEvents(h types.EventDataNewBlockHeader) error { + ts := time.Now().UTC() - // index the reserved block height index - ts := time.Now() - sqlStmt = sqlStmt.Values(types.BlockHeightKey, h.Header.Height, h.Header.Height, "", ts, es.chainID) + return runInTransaction(es.store, func(tx *sql.Tx) error { + // Add the block to the blocks table and report back its row ID for use + // in indexing the events for the block. + blockID, err := queryWithID(tx, ` +INSERT INTO blocks (height, chain_id, created_at) + VALUES ($1, $2, $3) + ON CONFLICT DO NOTHING + RETURNING rowid; +`, h.Header.Height, es.chainID, ts) + // ... - for _, event := range h.ResultBeginBlock.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.BlockHeightKey { - return fmt.Errorf("event type and attribute key \"%s\" is reserved; please use a different key", compositeKey) - } - - if attr.GetIndex() { - sqlStmt = sqlStmt.Values(compositeKey, string(attr.Value), h.Header.Height, BlockEventTypeBeginBlock, ts, es.chainID) - } - } - } - - // index end_block events... - // execute sqlStmt db query... + // Insert the special block meta-event for height. + if err := insertEvents(tx, blockID, 0, []abci.Event{ + makeIndexedEvent(types.BlockHeightKey, fmt.Sprint(h.Header.Height)), + }); err != nil { + return fmt.Errorf("block meta-events: %w", err) + } + // Insert all the block events. Order is important here, + if err := insertEvents(tx, blockID, 0, h.ResultBeginBlock.Events); err != nil { + return fmt.Errorf("begin-block events: %w", err) + } + if err := insertEvents(tx, blockID, 0, h.ResultEndBlock.Events); err != nil { + return fmt.Errorf("end-block events: %w", err) + } + return nil + }) } -func (es *PSQLEventSink) IndexTxEvents(txr []*abci.TxResult) error { - sqlStmtEvents := sq.Insert("tx_events").Columns("key", "value", "height", "hash", "tx_result_id", "created_at", "chain_id") - sqlStmtTxResult := sq.Insert("tx_results").Columns("tx_result", "created_at") +func (es *EventSink) IndexTxEvents(txrs []*abci.TxResult) error { + ts := time.Now().UTC() - ts := time.Now() - for _, tx := range txr { - // store the tx result - txBz, err := proto.Marshal(tx) - if err != nil { - return err - } + for _, txr := range txrs { + // Encode the result message in protobuf wire format for indexing. + resultData, err := proto.Marshal(txr) + // ... - sqlStmtTxResult = sqlStmtTxResult.Values(txBz, ts) + // Index the hash of the underlying transaction as a hex string. + txHash := fmt.Sprintf("%X", types.Tx(txr.Tx).Hash()) - // execute sqlStmtTxResult db query... - var txID uint32 - err = sqlStmtTxResult.QueryRow().Scan(&txID) - if err != nil { + if err := runInTransaction(es.store, func(tx *sql.Tx) error { + // Find the block associated with this transaction. + blockID, err := queryWithID(tx, ` +SELECT rowid FROM blocks WHERE height = $1 AND chain_id = $2; +`, txr.Height, es.chainID) + // ... + + // Insert a record for this tx_result and capture its ID for indexing events. + txID, err := queryWithID(tx, ` +INSERT INTO tx_results (block_id, index, created_at, tx_hash, tx_result) + VALUES ($1, $2, $3, $4, $5) + ON CONFLICT DO NOTHING + RETURNING rowid; +`, blockID, txr.Index, ts, txHash, resultData) + // ... + + // Insert the special transaction meta-events for hash and height. + if err := insertEvents(tx, blockID, txID, []abci.Event{ + makeIndexedEvent(types.TxHashKey, txHash), + makeIndexedEvent(types.TxHeightKey, fmt.Sprint(txr.Height)), + }); err != nil { + return fmt.Errorf("indexing transaction meta-events: %w", err) + } + // Index any events packaged with the transaction. + if err := insertEvents(tx, blockID, txID, txr.Result.Events); err != nil { + return fmt.Errorf("indexing transaction events: %w", err) + } + return nil + + }); err != nil { return err } - - // index the reserved height and hash indices - hash := types.Tx(tx.Tx).Hash() - sqlStmtEvents = sqlStmtEvents.Values(types.TxHashKey, hash, tx.Height, hash, txID, ts, es.chainID) - sqlStmtEvents = sqlStmtEvents.Values(types.TxHeightKey, tx.Height, tx.Height, hash, txID, ts, es.chainID) - - for _, event := range result.Result.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 if `index: true` is set - compositeTag := fmt.Sprintf("%s.%s", event.Type, string(attr.Key)) - - // ensure event does not conflict with a reserved prefix key - if compositeTag == types.TxHashKey || compositeTag == types.TxHeightKey { - return fmt.Errorf("event type and attribute key \"%s\" is reserved; please use a different key", compositeTag) - } - - if attr.GetIndex() { - sqlStmtEvents = sqlStmtEvents.Values(compositeKey, string(attr.Value), tx.Height, hash, txID, ts, es.chainID) - } - } - } - } - - // execute sqlStmtEvents db query... + } + return nil } -func (es *PSQLEventSink) SearchBlockEvents(ctx context.Context, q *query.Query) ([]int64, error) { - return nil, errors.New("block search is not supported via the postgres event sink") -} +// SearchBlockEvents is not implemented by this sink, and reports an error for all queries. +func (es *EventSink) SearchBlockEvents(ctx context.Context, q *query.Query) ([]int64, error) -func (es *PSQLEventSink) SearchTxEvents(ctx context.Context, q *query.Query) ([]*abci.TxResult, error) { - return nil, errors.New("tx search is not supported via the postgres event sink") -} +// SearchTxEvents is not implemented by this sink, and reports an error for all queries. +func (es *EventSink) SearchTxEvents(ctx context.Context, q *query.Query) ([]*abci.TxResult, error) -func (es *PSQLEventSink) GetTxByHash(hash []byte) (*abci.TxResult, error) { - return nil, errors.New("getTxByHash is not supported via the postgres event sink") -} +// GetTxByHash is not implemented by this sink, and reports an error for all queries. +func (es *EventSink) GetTxByHash(hash []byte) (*abci.TxResult, error) -func (es *PSQLEventSink) HasBlock(h int64) (bool, error) { - return false, errors.New("hasBlock is not supported via the postgres event sink") -} +// HasBlock is not implemented by this sink, and reports an error for all queries. +func (es *EventSink) HasBlock(h int64) (bool, error) ``` ### Configuration diff --git a/state/indexer/indexer_service_test.go b/state/indexer/indexer_service_test.go index 457ed065a..4d12cc86f 100644 --- a/state/indexer/indexer_service_test.go +++ b/state/indexer/indexer_service_test.go @@ -139,7 +139,7 @@ func setupDB(t *testing.T) (*dockertest.Pool, error) { assert.Nil(t, err) resource, err = pool.RunWithOptions(&dockertest.RunOptions{ - Repository: psql.DriverName, + Repository: "postgres", Tag: "13", Env: []string{ "POSTGRES_USER=" + user, diff --git a/state/indexer/sink/psql/psql.go b/state/indexer/sink/psql/psql.go index 8bd378f4a..e452ed406 100644 --- a/state/indexer/sink/psql/psql.go +++ b/state/indexer/sink/psql/psql.go @@ -6,23 +6,22 @@ import ( "database/sql" "errors" "fmt" + "strings" "time" - sq "github.com/Masterminds/squirrel" - proto "github.com/gogo/protobuf/proto" + "github.com/gogo/protobuf/proto" abci "github.com/tendermint/tendermint/abci/types" "github.com/tendermint/tendermint/libs/pubsub/query" "github.com/tendermint/tendermint/state/indexer" "github.com/tendermint/tendermint/types" ) -var _ indexer.EventSink = (*EventSink)(nil) - const ( - TableEventBlock = "block_events" - TableEventTx = "tx_events" - TableResultTx = "tx_results" - DriverName = "postgres" + tableBlocks = "blocks" + tableTxResults = "tx_results" + tableEvents = "events" + tableAttributes = "attributes" + driverName = "postgres" ) // EventSink is an indexer backend providing the tx/block index services. This @@ -37,7 +36,7 @@ type EventSink struct { // database specified by connStr. Events written to the sink are attributed to // the specified chainID. func NewEventSink(connStr, chainID string) (*EventSink, error) { - db, err := sql.Open(DriverName, connStr) + db, err := sql.Open(driverName, connStr) if err != nil { return nil, err } @@ -55,116 +54,183 @@ func (es *EventSink) DB() *sql.DB { return es.store } // Type returns the structure type for this sink, which is Postgres. func (es *EventSink) Type() indexer.EventSinkType { return indexer.PSQL } +// runInTransaction executes query in a fresh database transaction. +// If query reports an error, the transaction is rolled back and the +// error from query is reported to the caller. +// Otherwise, the result of committing the transaction is returned. +func runInTransaction(db *sql.DB, query func(*sql.Tx) error) error { + dbtx, err := db.Begin() + if err != nil { + return err + } + if err := query(dbtx); err != nil { + _ = dbtx.Rollback() // report the initial error, not the rollback + return err + } + return dbtx.Commit() +} + +// queryWithID executes the specified SQL query with the given arguments, +// expecting a single-row, single-column result containing an ID. If the query +// succeeds, the ID from the result is returned. +func queryWithID(tx *sql.Tx, query string, args ...interface{}) (uint32, error) { + var id uint32 + if err := tx.QueryRow(query, args...).Scan(&id); err != nil { + return 0, err + } + return id, nil +} + +// insertEvents inserts a slice of events and any indexed attributes of those +// events into the database associated with dbtx. +// +// If txID > 0, the event is attributed to the Tendermint transaction with that +// ID; otherwise it is recorded as a block event. +func insertEvents(dbtx *sql.Tx, blockID, txID uint32, evts []abci.Event) error { + // Populate the transaction ID field iff one is defined (> 0). + var txIDArg interface{} + if txID > 0 { + txIDArg = txID + } + + // Add each event to the events table, and retrieve its row ID to use when + // adding any attributes the event provides. + for _, evt := range evts { + // Skip events with an empty type. + if evt.Type == "" { + continue + } + + eid, err := queryWithID(dbtx, ` +INSERT INTO `+tableEvents+` (block_id, tx_id, type) VALUES ($1, $2, $3) + RETURNING rowid; +`, blockID, txIDArg, evt.Type) + if err != nil { + return err + } + + // Add any attributes flagged for indexing. + for _, attr := range evt.Attributes { + if !attr.Index { + continue + } + compositeKey := evt.Type + "." + attr.Key + if _, err := dbtx.Exec(` +INSERT INTO `+tableAttributes+` (event_id, key, composite_key, value) + VALUES ($1, $2, $3, $4); +`, eid, attr.Key, compositeKey, attr.Value); err != nil { + return err + } + } + } + return nil +} + +// makeIndexedEvent constructs an event from the specified composite key and +// value. If the key has the form "type.name", the event will have a single +// attribute with that name and the value; otherwise the event will have only +// a type and no attributes. +func makeIndexedEvent(compositeKey, value string) abci.Event { + i := strings.Index(compositeKey, ".") + if i < 0 { + return abci.Event{Type: compositeKey} + } + return abci.Event{Type: compositeKey[:i], Attributes: []abci.EventAttribute{ + {Key: compositeKey[i+1:], Value: value, Index: true}, + }} +} + // IndexBlockEvents indexes the specified block header, part of the // indexer.EventSink interface. func (es *EventSink) IndexBlockEvents(h types.EventDataNewBlockHeader) error { - sqlStmt := sq. - Insert(TableEventBlock). - Columns("key", "value", "height", "type", "created_at", "chain_id"). - PlaceholderFormat(sq.Dollar). - Suffix("ON CONFLICT (key,height)"). - Suffix("DO NOTHING") + ts := time.Now().UTC() - ts := time.Now() - // index the reserved block height index - sqlStmt = sqlStmt. - Values(types.BlockHeightKey, fmt.Sprint(h.Header.Height), h.Header.Height, "", ts, es.chainID) + return runInTransaction(es.store, func(dbtx *sql.Tx) error { + // Add the block to the blocks table and report back its row ID for use + // in indexing the events for the block. + blockID, err := queryWithID(dbtx, ` +INSERT INTO `+tableBlocks+` (height, chain_id, created_at) + VALUES ($1, $2, $3) + ON CONFLICT DO NOTHING + RETURNING rowid; +`, h.Header.Height, es.chainID, ts) + if err == sql.ErrNoRows { + return nil // we already saw this block; quietly succeed + } else if err != nil { + return fmt.Errorf("indexing block header: %w", err) + } - // index begin_block events - sqlStmt, err := indexBlockEvents( - sqlStmt, h.ResultBeginBlock.Events, types.EventTypeBeginBlock, h.Header.Height, ts, es.chainID) - if err != nil { - return err - } - - // index end_block events - sqlStmt, err = indexBlockEvents( - sqlStmt, h.ResultEndBlock.Events, types.EventTypeEndBlock, h.Header.Height, ts, es.chainID) - if err != nil { - return err - } - - _, err = sqlStmt.RunWith(es.store).Exec() - return err + // Insert the special block meta-event for height. + if err := insertEvents(dbtx, blockID, 0, []abci.Event{ + makeIndexedEvent(types.BlockHeightKey, fmt.Sprint(h.Header.Height)), + }); err != nil { + return fmt.Errorf("block meta-events: %w", err) + } + // Insert all the block events. Order is important here, + if err := insertEvents(dbtx, blockID, 0, h.ResultBeginBlock.Events); err != nil { + return fmt.Errorf("begin-block events: %w", err) + } + if err := insertEvents(dbtx, blockID, 0, h.ResultEndBlock.Events); err != nil { + return fmt.Errorf("end-block events: %w", err) + } + return nil + }) } -func (es *EventSink) IndexTxEvents(txr []*abci.TxResult) error { - // index the tx result - var txid uint32 - sqlStmtTxResult := sq. - Insert(TableResultTx). - Columns("tx_result", "created_at"). - PlaceholderFormat(sq.Dollar). - RunWith(es.store). - Suffix("ON CONFLICT (tx_result)"). - Suffix("DO NOTHING"). - Suffix("RETURNING \"id\"") +func (es *EventSink) IndexTxEvents(txrs []*abci.TxResult) error { + ts := time.Now().UTC() - sqlStmtEvents := sq. - Insert(TableEventTx). - Columns("key", "value", "height", "hash", "tx_result_id", "created_at", "chain_id"). - PlaceholderFormat(sq.Dollar). - Suffix("ON CONFLICT (key,hash)"). - Suffix("DO NOTHING") - - ts := time.Now() - for _, tx := range txr { - txBz, err := proto.Marshal(tx) + for _, txr := range txrs { + // Encode the result message in protobuf wire format for indexing. + resultData, err := proto.Marshal(txr) if err != nil { - return err + return fmt.Errorf("marshaling tx_result: %w", err) } - sqlStmtTxResult = sqlStmtTxResult.Values(txBz, ts) + // Index the hash of the underlying transaction as a hex string. + txHash := fmt.Sprintf("%X", types.Tx(txr.Tx).Hash()) - // execute sqlStmtTxResult db query and retrieve the txid - r, err := sqlStmtTxResult.Query() - if err != nil { - return err - } - defer r.Close() + if err := runInTransaction(es.store, func(dbtx *sql.Tx) error { + // Find the block associated with this transaction. The block header + // must have been indexed prior to the transactions belonging to it. + blockID, err := queryWithID(dbtx, ` +SELECT rowid FROM `+tableBlocks+` WHERE height = $1 AND chain_id = $2; +`, txr.Height, es.chainID) + if err != nil { + return fmt.Errorf("finding block ID: %w", err) + } - if !r.Next() { + // Insert a record for this tx_result and capture its ID for indexing events. + txID, err := queryWithID(dbtx, ` +INSERT INTO `+tableTxResults+` (block_id, index, created_at, tx_hash, tx_result) + VALUES ($1, $2, $3, $4, $5) + ON CONFLICT DO NOTHING + RETURNING rowid; +`, blockID, txr.Index, ts, txHash, resultData) + if err == sql.ErrNoRows { + return nil // we already saw this transaction; quietly succeed + } else if err != nil { + return fmt.Errorf("indexing tx_result: %w", err) + } + + // Insert the special transaction meta-events for hash and height. + if err := insertEvents(dbtx, blockID, txID, []abci.Event{ + makeIndexedEvent(types.TxHashKey, txHash), + makeIndexedEvent(types.TxHeightKey, fmt.Sprint(txr.Height)), + }); err != nil { + return fmt.Errorf("indexing transaction meta-events: %w", err) + } + // Index any events packaged with the transaction. + if err := insertEvents(dbtx, blockID, txID, txr.Result.Events); err != nil { + return fmt.Errorf("indexing transaction events: %w", err) + } return nil - } - if err := r.Scan(&txid); err != nil { + }); err != nil { return err } - - // index the reserved height and hash indices - hash := fmt.Sprintf("%X", types.Tx(tx.Tx).Hash()) - - sqlStmtEvents = sqlStmtEvents.Values(types.TxHashKey, hash, tx.Height, hash, txid, ts, es.chainID) - sqlStmtEvents = sqlStmtEvents.Values(types.TxHeightKey, fmt.Sprint(tx.Height), tx.Height, hash, txid, ts, es.chainID) - for _, event := range tx.Result.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 if `index: true` is set - compositeTag := fmt.Sprintf("%s.%s", event.Type, attr.Key) - - // ensure event does not conflict with a reserved prefix key - if compositeTag == types.TxHashKey || compositeTag == types.TxHeightKey { - return fmt.Errorf("event type and attribute key \"%s\" is reserved; please use a different key", compositeTag) - } - - if attr.GetIndex() { - sqlStmtEvents = sqlStmtEvents.Values(compositeTag, attr.Value, tx.Height, hash, txid, ts, es.chainID) - } - } - } } - - // execute sqlStmtEvents db query... - _, err := sqlStmtEvents.RunWith(es.store).Exec() - return err + return nil } // SearchBlockEvents is not implemented by this sink, and reports an error for all queries. @@ -187,39 +253,5 @@ func (es *EventSink) HasBlock(h int64) (bool, error) { return false, errors.New("hasBlock is not supported via the postgres event sink") } -func indexBlockEvents( - sqlStmt sq.InsertBuilder, - events []abci.Event, - ty string, - height int64, - ts time.Time, - chainID string, -) (sq.InsertBuilder, error) { - 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, attr.Key) - if compositeKey == types.BlockHeightKey { - return sqlStmt, fmt.Errorf( - "event type and attribute key \"%s\" is reserved; please use a different key", compositeKey) - } - - if attr.GetIndex() { - sqlStmt = sqlStmt.Values(compositeKey, attr.Value, height, ty, ts, chainID) - } - } - } - return sqlStmt, nil -} - // Stop closes the underlying PostgreSQL database. func (es *EventSink) Stop() error { return es.store.Close() } diff --git a/state/indexer/sink/psql/psql_test.go b/state/indexer/sink/psql/psql_test.go index 35ad7eea3..e8a1ce833 100644 --- a/state/indexer/sink/psql/psql_test.go +++ b/state/indexer/sink/psql/psql_test.go @@ -3,320 +3,64 @@ package psql import ( "context" "database/sql" - "errors" + "flag" "fmt" "io/ioutil" + "log" "os" + "os/signal" "testing" "time" - sq "github.com/Masterminds/squirrel" - schema "github.com/adlio/schema" - proto "github.com/gogo/protobuf/proto" - _ "github.com/lib/pq" - dockertest "github.com/ory/dockertest" + "github.com/adlio/schema" + "github.com/gogo/protobuf/proto" + "github.com/ory/dockertest" "github.com/ory/dockertest/docker" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" abci "github.com/tendermint/tendermint/abci/types" "github.com/tendermint/tendermint/state/indexer" "github.com/tendermint/tendermint/types" + + // Register the Postgres database driver. + _ "github.com/lib/pq" ) -var db *sql.DB -var resource *dockertest.Resource -var chainID = "test-chainID" +// Verify that the type satisfies the EventSink interface. +var _ indexer.EventSink = (*EventSink)(nil) var ( + doPauseAtExit = flag.Bool("pause-at-exit", false, + "If true, pause the test until interrupted at shutdown, to allow debugging") + + // A hook that test cases can call to obtain the shared database instance + // used for testing the sink. This is initialized in TestMain (see below). + testDB func() *sql.DB +) + +const ( user = "postgres" password = "secret" port = "5432" dsn = "postgres://%s:%s@localhost:%s/%s?sslmode=disable" dbName = "postgres" + chainID = "test-chainID" + + viewBlockEvents = "block_events" + viewTxEvents = "tx_events" ) -func TestType(t *testing.T) { - pool, err := setupDB(t) - require.NoError(t, err) +func TestMain(m *testing.M) { + flag.Parse() - psqlSink := &EventSink{store: db, chainID: chainID} - assert.Equal(t, indexer.PSQL, psqlSink.Type()) - require.NoError(t, teardown(t, pool)) -} - -func TestBlockFuncs(t *testing.T) { - pool, err := setupDB(t) - require.NoError(t, err) - - indexer := &EventSink{store: db, chainID: chainID} - require.NoError(t, indexer.IndexBlockEvents(getTestBlockHeader())) - - r, err := verifyBlock(1) - assert.True(t, r) - require.NoError(t, err) - - r, err = verifyBlock(2) - assert.False(t, r) - require.NoError(t, err) - - r, err = indexer.HasBlock(1) - assert.False(t, r) - assert.Equal(t, errors.New("hasBlock is not supported via the postgres event sink"), err) - - r, err = indexer.HasBlock(2) - assert.False(t, r) - assert.Equal(t, errors.New("hasBlock is not supported via the postgres event sink"), err) - - r2, err := indexer.SearchBlockEvents(context.TODO(), nil) - assert.Nil(t, r2) - assert.Equal(t, errors.New("block search is not supported via the postgres event sink"), err) - - require.NoError(t, verifyTimeStamp(TableEventBlock)) - - // try to insert the duplicate block events. - err = indexer.IndexBlockEvents(getTestBlockHeader()) - require.NoError(t, err) - - require.NoError(t, teardown(t, pool)) -} - -func TestTxFuncs(t *testing.T) { - pool, err := setupDB(t) - assert.Nil(t, err) - - indexer := &EventSink{store: db, chainID: chainID} - - txResult := txResultWithEvents([]abci.Event{ - {Type: "account", Attributes: []abci.EventAttribute{{Key: "number", Value: "1", Index: true}}}, - {Type: "account", Attributes: []abci.EventAttribute{{Key: "owner", Value: "Ivan", Index: true}}}, - {Type: "", Attributes: []abci.EventAttribute{{Key: "not_allowed", Value: "Vlad", Index: true}}}, - }) - err = indexer.IndexTxEvents([]*abci.TxResult{txResult}) - require.NoError(t, err) - - tx, err := verifyTx(types.Tx(txResult.Tx).Hash()) - require.NoError(t, err) - assert.Equal(t, txResult, tx) - - require.NoError(t, verifyTimeStamp(TableEventTx)) - require.NoError(t, verifyTimeStamp(TableResultTx)) - - tx, err = indexer.GetTxByHash(types.Tx(txResult.Tx).Hash()) - assert.Nil(t, tx) - assert.Equal(t, errors.New("getTxByHash is not supported via the postgres event sink"), err) - - r2, err := indexer.SearchTxEvents(context.TODO(), nil) - assert.Nil(t, r2) - assert.Equal(t, errors.New("tx search is not supported via the postgres event sink"), err) - - // try to insert the duplicate tx events. - err = indexer.IndexTxEvents([]*abci.TxResult{txResult}) - require.NoError(t, err) - - assert.Nil(t, teardown(t, pool)) -} - -func TestStop(t *testing.T) { - pool, err := setupDB(t) - require.NoError(t, err) - - indexer := &EventSink{store: db} - require.NoError(t, indexer.Stop()) - - defer db.Close() - require.NoError(t, pool.Purge(resource)) -} - -func getTestBlockHeader() types.EventDataNewBlockHeader { - return types.EventDataNewBlockHeader{ - Header: types.Header{Height: 1}, - ResultBeginBlock: abci.ResponseBeginBlock{ - Events: []abci.Event{ - { - Type: "begin_event", - Attributes: []abci.EventAttribute{ - { - Key: "proposer", - Value: "FCAA001", - Index: true, - }, - }, - }, - }, - }, - ResultEndBlock: abci.ResponseEndBlock{ - Events: []abci.Event{ - { - Type: "end_event", - Attributes: []abci.EventAttribute{ - { - Key: "foo", - Value: "100", - Index: true, - }, - }, - }, - }, - }, - } -} - -func readSchema() ([]*schema.Migration, error) { - - filename := "schema.sql" - contents, err := ioutil.ReadFile(filename) - if err != nil { - return nil, fmt.Errorf("failed to read sql file from '%s': %w", filename, err) - } - - mg := &schema.Migration{} - mg.ID = time.Now().Local().String() + " db schema" - mg.Script = string(contents) - return append([]*schema.Migration{}, mg), nil -} - -func resetDB(t *testing.T) { - q := "DROP TABLE IF EXISTS block_events,tx_events,tx_results" - _, err := db.Exec(q) - - require.NoError(t, err) - - q = "DROP TYPE IF EXISTS block_event_type" - _, err = db.Exec(q) - require.NoError(t, err) -} - -func txResultWithEvents(events []abci.Event) *abci.TxResult { - tx := types.Tx("HELLO WORLD") - return &abci.TxResult{ - Height: 1, - Index: 0, - Tx: tx, - Result: abci.ResponseDeliverTx{ - Data: []byte{0}, - Code: abci.CodeTypeOK, - Log: "", - Events: events, - }, - } -} - -func verifyTx(hash []byte) (*abci.TxResult, error) { - join := fmt.Sprintf("%s ON %s.id = tx_result_id", TableEventTx, TableResultTx) - sqlStmt := sq. - Select("tx_result", fmt.Sprintf("%s.id", TableResultTx), "tx_result_id", "hash", "chain_id"). - Distinct().From(TableResultTx). - InnerJoin(join). - Where(fmt.Sprintf("hash = $1 AND chain_id = '%s'", chainID), fmt.Sprintf("%X", hash)) - - rows, err := sqlStmt.RunWith(db).Query() - if err != nil { - return nil, err - } - - defer rows.Close() - - if rows.Next() { - var txResult []byte - var txResultID, txid int - var h, cid string - err = rows.Scan(&txResult, &txResultID, &txid, &h, &cid) - if err != nil { - return nil, nil - } - - msg := new(abci.TxResult) - err = proto.Unmarshal(txResult, msg) - if err != nil { - return nil, err - } - - return msg, err - } - - // No result - return nil, nil -} - -func verifyTimeStamp(tb string) error { - - // We assume the tx indexing time would not exceed 2 second from now - sqlStmt := sq. - Select(fmt.Sprintf("%s.created_at", tb)). - Distinct().From(tb). - Where(fmt.Sprintf("%s.created_at >= $1", tb), time.Now().Add(-2*time.Second)) - - rows, err := sqlStmt.RunWith(db).Query() - if err != nil { - return err - } - - defer rows.Close() - - if rows.Next() { - var ts string - return rows.Scan(&ts) - } - - return errors.New("no result") -} - -func verifyBlock(h int64) (bool, error) { - sqlStmt := sq. - Select("height"). - Distinct(). - From(TableEventBlock). - Where(fmt.Sprintf("height = %d", h)) - rows, err := sqlStmt.RunWith(db).Query() - if err != nil { - return false, err - } - - defer rows.Close() - - if !rows.Next() { - return false, nil - } - - sqlStmt = sq. - Select("type, height", "chain_id"). - Distinct(). - From(TableEventBlock). - Where(fmt.Sprintf("height = %d AND type = '%s' AND chain_id = '%s'", h, types.EventTypeBeginBlock, chainID)) - - rows, err = sqlStmt.RunWith(db).Query() - if err != nil { - return false, err - } - defer rows.Close() - - if !rows.Next() { - return false, nil - } - - sqlStmt = sq. - Select("type, height"). - Distinct(). - From(TableEventBlock). - Where(fmt.Sprintf("height = %d AND type = '%s'", h, types.EventTypeEndBlock)) - rows, err = sqlStmt.RunWith(db).Query() - - if err != nil { - return false, err - } - defer rows.Close() - - return rows.Next(), nil -} - -func setupDB(t *testing.T) (*dockertest.Pool, error) { - t.Helper() + // Set up docker and start a container running PostgreSQL. pool, err := dockertest.NewPool(os.Getenv("DOCKER_URL")) + if err != nil { + log.Fatalf("Creating docker pool: %v", err) + } - require.NoError(t, err) - - resource, err = pool.RunWithOptions(&dockertest.RunOptions{ - Repository: DriverName, + resource, err := pool.RunWithOptions(&dockertest.RunOptions{ + Repository: "postgres", Tag: "13", Env: []string{ "POSTGRES_USER=" + user, @@ -332,35 +76,269 @@ func setupDB(t *testing.T) (*dockertest.Pool, error) { Name: "no", } }) + if err != nil { + log.Fatalf("Starting docker pool: %v", err) + } - require.NoError(t, err) - - // Set the container to expire in a minute to avoid orphaned containers - // hanging around - _ = resource.Expire(60) + if *doPauseAtExit { + log.Print("Pause at exit is enabled, containers will not expire") + } else { + const expireSeconds = 60 + _ = resource.Expire(expireSeconds) + log.Printf("Container expiration set to %d seconds", expireSeconds) + } + // Connect to the database, clear any leftover data, and install the + // indexing schema. conn := fmt.Sprintf(dsn, user, password, resource.GetPort(port+"/tcp"), dbName) + var db *sql.DB - require.NoError(t, pool.Retry(func() error { + if err := pool.Retry(func() error { sink, err := NewEventSink(conn, chainID) if err != nil { return err } db = sink.DB() // set global for test use return db.Ping() - })) + }); err != nil { + log.Fatalf("Connecting to database: %v", err) + } - resetDB(t) + if err := resetDatabase(db); err != nil { + log.Fatalf("Flushing database: %v", err) + } sm, err := readSchema() - assert.Nil(t, err) - assert.Nil(t, schema.NewMigrator().Apply(db, sm)) - return pool, nil + if err != nil { + log.Fatalf("Reading schema: %v", err) + } else if err := schema.NewMigrator().Apply(db, sm); err != nil { + log.Fatalf("Applying schema: %v", err) + } + + // Set up the hook for tests to get the shared database handle. + testDB = func() *sql.DB { return db } + + // Run the selected test cases. + code := m.Run() + + // Clean up and shut down the database container. + if *doPauseAtExit { + log.Print("Testing complete, pausing for inspection. Send SIGINT to resume teardown") + waitForInterrupt() + log.Print("(resuming)") + } + log.Print("Shutting down database") + if err := pool.Purge(resource); err != nil { + log.Printf("WARNING: Purging pool failed: %v", err) + } + if err := db.Close(); err != nil { + log.Printf("WARNING: Closing database failed: %v", err) + } + + os.Exit(code) } -func teardown(t *testing.T, pool *dockertest.Pool) error { - t.Helper() - // When you're done, kill and remove the container - assert.Nil(t, pool.Purge(resource)) - return db.Close() +func TestType(t *testing.T) { + psqlSink := &EventSink{store: testDB(), chainID: chainID} + assert.Equal(t, indexer.PSQL, psqlSink.Type()) +} + +func TestIndexing(t *testing.T) { + t.Run("IndexBlockEvents", func(t *testing.T) { + indexer := &EventSink{store: testDB(), chainID: chainID} + require.NoError(t, indexer.IndexBlockEvents(newTestBlockHeader())) + + verifyBlock(t, 1) + verifyBlock(t, 2) + + verifyNotImplemented(t, "hasBlock", func() (bool, error) { return indexer.HasBlock(1) }) + verifyNotImplemented(t, "hasBlock", func() (bool, error) { return indexer.HasBlock(2) }) + + verifyNotImplemented(t, "block search", func() (bool, error) { + v, err := indexer.SearchBlockEvents(context.Background(), nil) + return v != nil, err + }) + + require.NoError(t, verifyTimeStamp(tableBlocks)) + + // Attempting to reindex the same events should gracefully succeed. + require.NoError(t, indexer.IndexBlockEvents(newTestBlockHeader())) + }) + + t.Run("IndexTxEvents", func(t *testing.T) { + indexer := &EventSink{store: testDB(), chainID: chainID} + + txResult := txResultWithEvents([]abci.Event{ + makeIndexedEvent("account.number", "1"), + makeIndexedEvent("account.owner", "Ivan"), + makeIndexedEvent("account.owner", "Yulieta"), + + {Type: "", Attributes: []abci.EventAttribute{{Key: "not_allowed", Value: "Vlad", Index: true}}}, + }) + require.NoError(t, indexer.IndexTxEvents([]*abci.TxResult{txResult})) + + txr, err := loadTxResult(types.Tx(txResult.Tx).Hash()) + require.NoError(t, err) + assert.Equal(t, txResult, txr) + + require.NoError(t, verifyTimeStamp(tableTxResults)) + require.NoError(t, verifyTimeStamp(viewTxEvents)) + + verifyNotImplemented(t, "getTxByHash", func() (bool, error) { + txr, err := indexer.GetTxByHash(types.Tx(txResult.Tx).Hash()) + return txr != nil, err + }) + verifyNotImplemented(t, "tx search", func() (bool, error) { + txr, err := indexer.SearchTxEvents(context.Background(), nil) + return txr != nil, err + }) + + // try to insert the duplicate tx events. + err = indexer.IndexTxEvents([]*abci.TxResult{txResult}) + require.NoError(t, err) + }) +} + +func TestStop(t *testing.T) { + indexer := &EventSink{store: testDB()} + require.NoError(t, indexer.Stop()) +} + +// newTestBlockHeader constructs a fresh copy of a block header containing +// known test values to exercise the indexer. +func newTestBlockHeader() types.EventDataNewBlockHeader { + return types.EventDataNewBlockHeader{ + Header: types.Header{Height: 1}, + ResultBeginBlock: abci.ResponseBeginBlock{ + Events: []abci.Event{ + makeIndexedEvent("begin_event.proposer", "FCAA001"), + makeIndexedEvent("thingy.whatzit", "O.O"), + }, + }, + ResultEndBlock: abci.ResponseEndBlock{ + Events: []abci.Event{ + makeIndexedEvent("end_event.foo", "100"), + makeIndexedEvent("thingy.whatzit", "-.O"), + }, + }, + } +} + +// readSchema loads the indexing database schema file +func readSchema() ([]*schema.Migration, error) { + const filename = "schema.sql" + contents, err := ioutil.ReadFile(filename) + if err != nil { + return nil, fmt.Errorf("failed to read sql file from '%s': %w", filename, err) + } + + return []*schema.Migration{{ + ID: time.Now().Local().String() + " db schema", + Script: string(contents), + }}, nil +} + +// resetDB drops all the data from the test database. +func resetDatabase(db *sql.DB) error { + _, err := db.Exec(`DROP TABLE IF EXISTS blocks,tx_results,events,attributes CASCADE;`) + if err != nil { + return fmt.Errorf("dropping tables: %v", err) + } + _, err = db.Exec(`DROP VIEW IF EXISTS event_attributes,block_events,tx_events CASCADE;`) + if err != nil { + return fmt.Errorf("dropping views: %v", err) + } + return nil +} + +// txResultWithEvents constructs a fresh transaction result with fixed values +// for testing, that includes the specified events. +func txResultWithEvents(events []abci.Event) *abci.TxResult { + return &abci.TxResult{ + Height: 1, + Index: 0, + Tx: types.Tx("HELLO WORLD"), + Result: abci.ResponseDeliverTx{ + Data: []byte{0}, + Code: abci.CodeTypeOK, + Log: "", + Events: events, + }, + } +} + +func loadTxResult(hash []byte) (*abci.TxResult, error) { + hashString := fmt.Sprintf("%X", hash) + var resultData []byte + if err := testDB().QueryRow(` +SELECT tx_result FROM `+tableTxResults+` WHERE tx_hash = $1; +`, hashString).Scan(&resultData); err != nil { + return nil, fmt.Errorf("lookup transaction for hash %q failed: %v", hashString, err) + } + + txr := new(abci.TxResult) + if err := proto.Unmarshal(resultData, txr); err != nil { + return nil, fmt.Errorf("unmarshaling txr: %v", err) + } + + return txr, nil +} + +func verifyTimeStamp(tableName string) error { + return testDB().QueryRow(fmt.Sprintf(` +SELECT DISTINCT %[1]s.created_at + FROM %[1]s + WHERE %[1]s.created_at >= $1; +`, tableName), time.Now().Add(-2*time.Second)).Err() +} + +func verifyBlock(t *testing.T, height int64) { + // Check that the blocks table contains an entry for this height. + if err := testDB().QueryRow(` +SELECT height FROM `+tableBlocks+` WHERE height = $1; +`, height).Err(); err == sql.ErrNoRows { + t.Errorf("No block found for height=%d", height) + } else if err != nil { + t.Fatalf("Database query failed: %v", err) + } + + // Verify the presence of begin_block and end_block events. + if err := testDB().QueryRow(` +SELECT type, height, chain_id FROM `+viewBlockEvents+` + WHERE height = $1 AND type = $2 AND chain_id = $3; +`, height, types.EventTypeBeginBlock, chainID).Err(); err == sql.ErrNoRows { + t.Errorf("No %q event found for height=%d", types.EventTypeBeginBlock, height) + } else if err != nil { + t.Fatalf("Database query failed: %v", err) + } + + if err := testDB().QueryRow(` +SELECT type, height, chain_id FROM `+viewBlockEvents+` + WHERE height = $1 AND type = $2 AND chain_id = $3; +`, height, types.EventTypeEndBlock, chainID).Err(); err == sql.ErrNoRows { + t.Errorf("No %q event found for height=%d", types.EventTypeEndBlock, height) + } else if err != nil { + t.Fatalf("Database query failed: %v", err) + } +} + +// verifyNotImplemented calls f and verifies that it returns both a +// false-valued flag and a non-nil error whose string matching the expected +// "not supported" message with label prefixed. +func verifyNotImplemented(t *testing.T, label string, f func() (bool, error)) { + t.Helper() + t.Logf("Verifying that %q reports it is not implemented", label) + + want := label + " is not supported via the postgres event sink" + ok, err := f() + assert.False(t, ok) + require.NotNil(t, err) + assert.Equal(t, want, err.Error()) +} + +// waitForInterrupt blocks until a SIGINT is received by the process. +func waitForInterrupt() { + ch := make(chan os.Signal, 1) + signal.Notify(ch, os.Interrupt) + <-ch } diff --git a/state/indexer/sink/psql/schema.sql b/state/indexer/sink/psql/schema.sql index 0563136e2..1091cd4c3 100644 --- a/state/indexer/sink/psql/schema.sql +++ b/state/indexer/sink/psql/schema.sql @@ -1,32 +1,85 @@ -CREATE TYPE block_event_type AS ENUM ('begin_block', 'end_block', ''); -CREATE TABLE block_events ( - id SERIAL PRIMARY KEY, - key VARCHAR NOT NULL, - value VARCHAR NOT NULL, - height INTEGER NOT NULL, - type block_event_type, - created_at TIMESTAMPTZ NOT NULL, - chain_id VARCHAR NOT NULL, - UNIQUE (key, height) +/* + This file defines the database schema for the PostgresQL ("psql") event sink + implementation in Tendermint. The operator must create a database and install + this schema before using the database to index events. + */ + +-- The blocks table records metadata about each block. +-- The block record does not include its events or transactions (see tx_results). +CREATE TABLE blocks ( + rowid BIGSERIAL PRIMARY KEY, + + height BIGINT NOT NULL, + chain_id VARCHAR NOT NULL, + + -- When this block header was logged into the sink, in UTC. + created_at TIMESTAMPTZ NOT NULL, + + UNIQUE (height, chain_id) ); + +-- Index blocks by height and chain, since we need to resolve block IDs when +-- indexing transaction records and transaction events. +CREATE INDEX idx_blocks_height_chain ON blocks(height, chain_id); + +-- The tx_results table records metadata about transaction results. Note that +-- the events from a transaction are stored separately. CREATE TABLE tx_results ( - id SERIAL PRIMARY KEY, - tx_result BYTEA NOT NULL, - created_at TIMESTAMPTZ NOT NULL, - UNIQUE (tx_result) + rowid BIGSERIAL PRIMARY KEY, + + -- The block to which this transaction belongs. + block_id BIGINT NOT NULL REFERENCES blocks(rowid), + -- The sequential index of the transaction within the block. + index INTEGER NOT NULL, + -- When this result record was logged into the sink, in UTC. + created_at TIMESTAMPTZ NOT NULL, + -- The hex-encoded hash of the transaction. + tx_hash VARCHAR NOT NULL, + -- The protobuf wire encoding of the TxResult message. + tx_result BYTEA NOT NULL, + + UNIQUE (block_id, index) ); -CREATE TABLE tx_events ( - id SERIAL PRIMARY KEY, - key VARCHAR NOT NULL, - value VARCHAR NOT NULL, - height INTEGER NOT NULL, - hash VARCHAR NOT NULL, - tx_result_id SERIAL, - created_at TIMESTAMPTZ NOT NULL, - chain_id VARCHAR NOT NULL, - UNIQUE (hash, key), - FOREIGN KEY (tx_result_id) REFERENCES tx_results(id) ON DELETE CASCADE + +-- The events table records events. All events (both block and transaction) are +-- associated with a block ID; transaction events also have a transaction ID. +CREATE TABLE events ( + rowid BIGSERIAL PRIMARY KEY, + + -- The block and transaction this event belongs to. + -- If tx_id is NULL, this is a block event. + block_id BIGINT NOT NULL REFERENCES blocks(rowid), + tx_id BIGINT NULL REFERENCES tx_results(rowid), + + -- The application-defined type label for the event. + type VARCHAR NOT NULL ); -CREATE INDEX idx_block_events_key_value ON block_events(key, value); -CREATE INDEX idx_tx_events_key_value ON tx_events(key, value); -CREATE INDEX idx_tx_events_hash ON tx_events(hash); + +-- The attributes table records event attributes. +CREATE TABLE attributes ( + event_id BIGINT NOT NULL REFERENCES events(rowid), + key VARCHAR NOT NULL, -- bare key + composite_key VARCHAR NOT NULL, -- composed type.key + value VARCHAR NULL, + + UNIQUE (event_id, key) +); + +-- A joined view of events and their attributes. Events that do not have any +-- attributes are represented as a single row with empty key and value fields. +CREATE VIEW event_attributes AS + SELECT block_id, tx_id, type, key, composite_key, value + FROM events LEFT JOIN attributes ON (events.rowid = attributes.event_id); + +-- A joined view of all block events (those having tx_id NULL). +CREATE VIEW block_events AS + SELECT blocks.rowid as block_id, height, chain_id, type, key, composite_key, value + FROM blocks JOIN event_attributes ON (blocks.rowid = event_attributes.block_id) + WHERE event_attributes.tx_id IS NULL; + +-- A joined view of all transaction events. +CREATE VIEW tx_events AS + SELECT height, index, chain_id, type, key, composite_key, value, tx_results.created_at + FROM blocks JOIN tx_results ON (blocks.rowid = tx_results.block_id) + JOIN event_attributes ON (tx_results.rowid = event_attributes.tx_id) + WHERE event_attributes.tx_id IS NOT NULL; From 9a0081f07696054e72ce3474e1f982bde9acec6d Mon Sep 17 00:00:00 2001 From: Sam Kleinman Date: Wed, 1 Sep 2021 12:49:45 -0400 Subject: [PATCH 36/37] e2e: change restart mechanism (#6883) --- test/e2e/runner/perturb.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/test/e2e/runner/perturb.go b/test/e2e/runner/perturb.go index 6909c665a..8fb6ec726 100644 --- a/test/e2e/runner/perturb.go +++ b/test/e2e/runner/perturb.go @@ -59,7 +59,11 @@ func PerturbNode(node *e2e.Node, perturbation e2e.Perturbation) (*rpctypes.Resul case e2e.PerturbationRestart: logger.Info(fmt.Sprintf("Restarting node %v...", node.Name)) - if err := execCompose(testnet.Dir, "restart", node.Name); err != nil { + if err := execCompose(testnet.Dir, "kill", "-s", "SIGTERM", node.Name); err != nil { + return nil, err + } + time.Sleep(10 * time.Second) + if err := execCompose(testnet.Dir, "start", node.Name); err != nil { return nil, err } From 511bd3eb7f037855a793a27ff4c53c12f085b570 Mon Sep 17 00:00:00 2001 From: Sam Kleinman Date: Wed, 1 Sep 2021 15:52:40 -0400 Subject: [PATCH 37/37] e2e: weight protocol dimensions (#6884) This changes the focus of the e2e suite, to (roughly) focus on configurations that are more well used. Most production users of tendermint run ABCI application in process and the GRPC/socket methods cover the vast majority of the remaining use cases. Perhaps we should consider drop support unix domain sockets in a future release, but I think in the mean time it's useful to have the tests *mostly* focus on the primary use cases. --- test/e2e/generator/generate.go | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/test/e2e/generator/generate.go b/test/e2e/generator/generate.go index 1c48157a1..28732967f 100644 --- a/test/e2e/generator/generate.go +++ b/test/e2e/generator/generate.go @@ -33,8 +33,18 @@ var ( "rocksdb": 10, "cleveldb": 5, } - nodeABCIProtocols = uniformChoice{"unix", "tcp", "builtin", "grpc"} - nodePrivvalProtocols = uniformChoice{"file", "unix", "tcp", "grpc"} + nodeABCIProtocols = weightedChoice{ + "builtin": 50, + "tcp": 20, + "grpc": 20, + "unix": 10, + } + nodePrivvalProtocols = weightedChoice{ + "file": 50, + "grpc": 20, + "tcp": 20, + "unix": 10, + } // FIXME: v2 disabled due to flake nodeBlockSyncs = uniformChoice{"v0"} // "v2" nodeMempools = uniformChoice{"v0", "v1"} @@ -277,8 +287,8 @@ func generateNode( Mode: string(mode), StartAt: startAt, Database: nodeDatabases.Choose(r), - ABCIProtocol: nodeABCIProtocols.Choose(r).(string), - PrivvalProtocol: nodePrivvalProtocols.Choose(r).(string), + ABCIProtocol: nodeABCIProtocols.Choose(r), + PrivvalProtocol: nodePrivvalProtocols.Choose(r), BlockSync: nodeBlockSyncs.Choose(r).(string), Mempool: nodeMempools.Choose(r).(string), StateSync: nodeStateSyncs.Choose(r).(bool) && startAt > 0,