diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index 99417fc0c..5a0ef395e 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@v1.3.1 + - uses: codecov/codecov-action@v1.3.2 with: file: ./coverage.txt if: env.GIT_DIFF diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index d7ed13b80..1cf78526c 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -40,7 +40,7 @@ jobs: platforms: all - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v1 + uses: docker/setup-buildx-action@v1.1.2 - name: Login to DockerHub if: ${{ github.event_name != 'pull_request' }} diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 3d283d8d7..d1aec04d1 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -25,7 +25,7 @@ jobs: make build-docs - name: Deploy 🚀 - uses: JamesIves/github-pages-deploy-action@4.1.0 + uses: JamesIves/github-pages-deploy-action@4.1.1 with: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} BRANCH: gh-pages diff --git a/.github/workflows/janitor.yml b/.github/workflows/janitor.yml index 172ac07dd..ccacb6eeb 100644 --- a/.github/workflows/janitor.yml +++ b/.github/workflows/janitor.yml @@ -10,7 +10,7 @@ jobs: runs-on: ubuntu-latest timeout-minutes: 3 steps: - - uses: styfle/cancel-workflow-action@0.8.0 + - uses: styfle/cancel-workflow-action@0.9.0 with: workflow_id: 1041851,1401230,2837803 access_token: ${{ github.token }} diff --git a/.github/workflows/proto-docker.yml b/.github/workflows/proto-docker.yml index 8ee08a313..34658ff2a 100644 --- a/.github/workflows/proto-docker.yml +++ b/.github/workflows/proto-docker.yml @@ -34,7 +34,7 @@ jobs: echo ::set-output name=tags::${TAGS} - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v1 + uses: docker/setup-buildx-action@v1.1.2 - name: Login to DockerHub uses: docker/login-action@v1 diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 5d978a3e2..0a4e740dc 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -28,7 +28,7 @@ jobs: - name: install run: make install install_abci if: "env.GIT_DIFF != ''" - - uses: actions/cache@v2.1.4 + - uses: actions/cache@v2.1.5 with: path: ~/go/pkg/mod key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} @@ -36,7 +36,7 @@ jobs: ${{ runner.os }}-go- if: env.GIT_DIFF # Cache binaries for use by other jobs - - uses: actions/cache@v2.1.4 + - uses: actions/cache@v2.1.5 with: path: ~/go/bin key: ${{ runner.os }}-${{ github.sha }}-tm-binary @@ -57,14 +57,14 @@ jobs: **/**.go go.mod go.sum - - uses: actions/cache@v2.1.4 + - uses: actions/cache@v2.1.5 with: path: ~/go/pkg/mod key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} restore-keys: | ${{ runner.os }}-go- if: env.GIT_DIFF - - uses: actions/cache@v2.1.4 + - uses: actions/cache@v2.1.5 with: path: ~/go/bin key: ${{ runner.os }}-${{ github.sha }}-tm-binary @@ -89,14 +89,14 @@ jobs: **/**.go go.mod go.sum - - uses: actions/cache@v2.1.4 + - uses: actions/cache@v2.1.5 with: path: ~/go/pkg/mod key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} restore-keys: | ${{ runner.os }}-go- if: env.GIT_DIFF - - uses: actions/cache@v2.1.4 + - uses: actions/cache@v2.1.5 with: path: ~/go/bin key: ${{ runner.os }}-${{ github.sha }}-tm-binary @@ -120,14 +120,14 @@ jobs: **/**.go go.mod go.sum - - uses: actions/cache@v2.1.4 + - uses: actions/cache@v2.1.5 with: path: ~/go/pkg/mod key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} restore-keys: | ${{ runner.os }}-go- if: env.GIT_DIFF - - uses: actions/cache@v2.1.4 + - uses: actions/cache@v2.1.5 with: path: ~/go/bin key: ${{ runner.os }}-${{ github.sha }}-tm-binary diff --git a/CHANGELOG.md b/CHANGELOG.md index 10dd0a8eb..208628ce5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,34 @@ # Changelog +## v0.34.9 + +*April 8, 2021* + +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. + +This release also includes a small Go API-breaking change, to reduce panics in the RPC layer. + +Special thanks to our external contributors on this release: @gchaincl + +Friendly reminder: We have a [bug bounty program](https://hackerone.com/tendermint). + +### BREAKING CHANGES + +- Go API + - [rpc/jsonrpc/server] [\#6204](https://github.com/tendermint/tendermint/issues/6204) Modify `WriteRPCResponseHTTP(Error)` to return an error (@melekes) + +### FEATURES + +- [rpc] [\#6226](https://github.com/tendermint/tendermint/issues/6226) Index block events and expose a new RPC method, `/block_search`, to allow querying for blocks by `BeginBlock` and `EndBlock` events (@alexanderbez) + +### BUG FIXES + +- [rpc/jsonrpc/server] [\#6191](https://github.com/tendermint/tendermint/issues/6191) Correctly unmarshal `RPCRequest` when data is `null` (@melekes) +- [p2p] [\#6289](https://github.com/tendermint/tendermint/issues/6289) Fix "unknown channels" bug on CustomReactors (@gchaincl) +- [light/evidence] Adds logic to handle forward lunatic attacks (@cmwaters) + ## v0.34.8 *February 25, 2021* diff --git a/CHANGELOG_PENDING.md b/CHANGELOG_PENDING.md index 92af3dc30..11d246889 100644 --- a/CHANGELOG_PENDING.md +++ b/CHANGELOG_PENDING.md @@ -43,7 +43,6 @@ Friendly reminder: We have a [bug bounty program](https://hackerone.com/tendermi - [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) - - [rpc/jsonrpc/server] \#6204 Modify `WriteRPCResponseHTTP(Error)` to return an error (@melekes) - Blockchain Protocol @@ -52,8 +51,8 @@ Friendly reminder: We have a [bug bounty program](https://hackerone.com/tendermi ### FEATURES -- [rpc] \#6226 Index block events and expose a new RPC method, `/block_search`, to allow querying for blocks by `BeginBlock` and `EndBlock` events. (@alexanderbez) - [config] Add `--mode` flag and config variable. See [ADR-52](https://github.com/tendermint/tendermint/blob/master/docs/architecture/adr-052-tendermint-mode.md) @dongsam +- [rpc] /#6329 Don't cap page size in unsafe mode (@gotjoshua, @cmwaters) ### IMPROVEMENTS @@ -88,4 +87,4 @@ Friendly reminder: We have a [bug bounty program](https://hackerone.com/tendermi - [privval] \#5638 Increase read/write timeout to 5s and calculate ping interval based on it (@JoeKash) - [blockchain/v1] [\#5701](https://github.com/tendermint/tendermint/pull/5701) Handle peers without blocks (@melekes) - [blockchain/v1] \#5711 Fix deadlock (@melekes) -- [rpc/jsonrpc/server] \#6191 Correctly unmarshal `RPCRequest` when data is `null` (@melekes) +- [light] \#6346 Correctly handle too high errors to improve client robustness (@cmwaters) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index c3cc05330..b7223d5f3 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -26,7 +26,8 @@ will indicate their support with a heartfelt emoji. If the issue would benefit from thorough discussion, maintainers may request that you create a [Request For -Comment](https://github.com/tendermint/spec/tree/master/rfc). Discussion +Comment](https://github.com/tendermint/spec/tree/master/rfc) +in the Tendermint spec repo. Discussion at the RFC stage will build collective understanding of the dimensions of the problems and help structure conversations around trade-offs. diff --git a/SECURITY.md b/SECURITY.md index 351f5606c..57d13e565 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -7,54 +7,55 @@ Policy](https://tendermint.com/security), we operate a [bug bounty](https://hackerone.com/tendermint). See the policy for more details on submissions and rewards, and see "Example Vulnerabilities" (below) for examples of the kinds of bugs we're most interested in. -### Guidelines +### Guidelines We require that all researchers: * Use the bug bounty to disclose all vulnerabilities, and avoid posting vulnerability information in public places, including Github Issues, Discord channels, and Telegram groups * Make every effort to avoid privacy violations, degradation of user experience, disruption to production systems (including but not limited to the Cosmos Hub), and destruction of data -* Keep any information about vulnerabilities that you’ve discovered confidential between yourself and the Tendermint Core engineering team until the issue has been resolved and disclosed +* Keep any information about vulnerabilities that you’ve discovered confidential between yourself and the Tendermint Core engineering team until the issue has been resolved and disclosed * Avoid posting personally identifiable information, privately or publicly If you follow these guidelines when reporting an issue to us, we commit to: * Not pursue or support any legal action related to your research on this vulnerability -* Work with you to understand, resolve and ultimately disclose the issue in a timely fashion +* Work with you to understand, resolve and ultimately disclose the issue in a timely fashion -## Disclosure Process +## Disclosure Process Tendermint Core uses the following disclosure process: -1. Once a security report is received, the Tendermint Core team works to verify the issue and confirm its severity level using CVSS. -2. The Tendermint Core team collaborates with the Gaia team to determine the vulnerability’s potential impact on the Cosmos Hub. -3. Patches are prepared for eligible releases of Tendermint in private repositories. See “Supported Releases” below for more information on which releases are considered eligible. -4. If it is determined that a CVE-ID is required, we request a CVE through a CVE Numbering Authority. +1. Once a security report is received, the Tendermint Core team works to verify the issue and confirm its severity level using CVSS. +2. The Tendermint Core team collaborates with the Gaia team to determine the vulnerability’s potential impact on the Cosmos Hub. +3. Patches are prepared for eligible releases of Tendermint in private repositories. See “Supported Releases” below for more information on which releases are considered eligible. +4. If it is determined that a CVE-ID is required, we request a CVE through a CVE Numbering Authority. 5. We notify the community that a security release is coming, to give users time to prepare their systems for the update. Notifications can include forum posts, tweets, and emails to partners and validators, including emails sent to the [Tendermint Security Mailing List](https://berlin.us4.list-manage.com/subscribe?u=431b35421ff7edcc77df5df10&id=3fe93307bc). -6. 24 hours following this notification, the fixes are applied publicly and new releases are issued. -7. Cosmos SDK and Gaia update their Tendermint Core dependencies to use these releases, and then themselves issue new releases. -8. Once releases are available for Tendermint Core, Cosmos SDK and Gaia, we notify the community, again, through the same channels as above. We also publish a Security Advisory on Github and publish the CVE, as long as neither the Security Advisory nor the CVE include any information on how to exploit these vulnerabilities beyond what information is already available in the patch itself. -9. Once the community is notified, we will pay out any relevant bug bounties to submitters. -10. One week after the releases go out, we will publish a post with further details on the vulnerability as well as our response to it. +6. 24 hours following this notification, the fixes are applied publicly and new releases are issued. +7. Cosmos SDK and Gaia update their Tendermint Core dependencies to use these releases, and then themselves issue new releases. +8. Once releases are available for Tendermint Core, Cosmos SDK and Gaia, we notify the community, again, through the same channels as above. We also publish a Security Advisory on Github and publish the CVE, as long as neither the Security Advisory nor the CVE include any information on how to exploit these vulnerabilities beyond what information is already available in the patch itself. +9. Once the community is notified, we will pay out any relevant bug bounties to submitters. +10. One week after the releases go out, we will publish a post with further details on the vulnerability as well as our response to it. -This process can take some time. Every effort will be made to handle the bug in as timely a manner as possible, however it's important that we follow the process described above to ensure that disclosures are handled consistently and to keep Tendermint Core and its downstream dependent projects--including but not limited to Gaia and the Cosmos Hub--as secure as possible. +This process can take some time. Every effort will be made to handle the bug in as timely a manner as possible, however it's important that we follow the process described above to ensure that disclosures are handled consistently and to keep Tendermint Core and its downstream dependent projects--including but not limited to Gaia and the Cosmos Hub--as secure as possible. -### Example Timeline +### Example Timeline -The following is an example timeline for the triage and response. The required roles and team members are described in parentheses after each task; however, multiple people can play each role and each person may play multiple roles. +The following is an example timeline for the triage and response. The required roles and team members are described in parentheses after each task; however, multiple people can play each role and each person may play multiple roles. -#### > 24 Hours Before Release Time +#### 24+ Hours Before Release Time -1. Request CVE number (ADMIN) -2. Gather emails and other contact info for validators (COMMS LEAD) -3. Test fixes on a testnet (TENDERMINT ENG, COSMOS ENG) -4. Write “Security Advisory” for forum (TENDERMINT LEAD) +1. Request CVE number (ADMIN) +2. Gather emails and other contact info for validators (COMMS LEAD) +3. Create patches in a private security repo, and ensure that PRs are open targeting all relevant release branches (TENDERMINT ENG, TENDERMINT LEAD) +4. Test fixes on a testnet (TENDERMINT ENG, COSMOS SDK ENG) +5. Write “Security Advisory” for forum (TENDERMINT LEAD) #### 24 Hours Before Release Time -1. Post “Security Advisory” pre-notification on forum (TENDERMINT LEAD) -2. Post Tweet linking to forum post (COMMS LEAD) -3. Announce security advisory/link to post in various other social channels (Telegram, Discord) (COMMS LEAD) -4. Send emails to validators or other users (PARTNERSHIPS LEAD) +1. Post “Security Advisory” pre-notification on forum (TENDERMINT LEAD) +2. Post Tweet linking to forum post (COMMS LEAD) +3. Announce security advisory/link to post in various other social channels (Telegram, Discord) (COMMS LEAD) +4. Send emails to validators or other users (PARTNERSHIPS LEAD) #### Release Time @@ -64,36 +65,36 @@ The following is an example timeline for the triage and response. The required r 4. Post “Security releases” on forum (TENDERMINT LEAD) 5. Post new Tweet linking to forum post (COMMS LEAD) 6. Remind everyone via social channels (Telegram, Discord) that the release is out (COMMS LEAD) -7. Send emails to validators or other users (COMMS LEAD) -8. Publish Security Advisory and CVE, if CVE has no sensitive information (ADMIN) +7. Send emails to validators or other users (COMMS LEAD) +8. Publish Security Advisory and CVE, if CVE has no sensitive information (ADMIN) #### After Release Time 1. Write forum post with exploit details (TENDERMINT LEAD) -2. Approve pay-out on HackerOne for submitter (ADMIN) +2. Approve pay-out on HackerOne for submitter (ADMIN) #### 7 Days After Release Time -1. Publish CVE if it has not yet been published (ADMIN) +1. Publish CVE if it has not yet been published (ADMIN) 2. Publish forum post with exploit details (TENDERMINT ENG, TENDERMINT LEAD) ## Supported Releases -The Tendermint Core team commits to releasing security patch releases for both the latest minor release as well for the major/minor release that the Cosmos Hub is running. +The Tendermint Core team commits to releasing security patch releases for both the latest minor release as well for the major/minor release that the Cosmos Hub is running. -If you are running older versions of Tendermint Core, we encourage you to upgrade at your earliest opportunity so that you can receive security patches directly from the Tendermint repo. While you are welcome to backport security patches to older versions for your own use, we will not publish or promote these backports. +If you are running older versions of Tendermint Core, we encourage you to upgrade at your earliest opportunity so that you can receive security patches directly from the Tendermint repo. While you are welcome to backport security patches to older versions for your own use, we will not publish or promote these backports. ## Scope The full scope of our bug bounty program is outlined on our [Hacker One program page](https://hackerone.com/tendermint). Please also note that, in the interest of the safety of our users and staff, a few things are explicitly excluded from scope: -* Any third-party services -* Findings from physical testing, such as office access +* Any third-party services +* Findings from physical testing, such as office access * Findings derived from social engineering (e.g., phishing) -## Example Vulnerabilities +## Example Vulnerabilities -The following is a list of examples of the kinds of vulnerabilities that we’re most interested in. It is not exhaustive: there are other kinds of issues we may also be interested in! +The following is a list of examples of the kinds of vulnerabilities that we’re most interested in. It is not exhaustive: there are other kinds of issues we may also be interested in! ### Specification @@ -114,6 +115,9 @@ Assuming less than 1/3 of the voting power is Byzantine (malicious): * A node halting (liveness failure) * Syncing new and old nodes +Assuming more than 1/3 the voting power is Byzantine: + +* Attacks that go unpunished (unhandled evidence) ### Networking @@ -139,7 +143,7 @@ Attacks may come through the P2P network or the RPC layer: ### Libraries -* Serialization (Amino) +* Serialization * Reading/Writing files and databases ### Cryptography @@ -150,5 +154,5 @@ Attacks may come through the P2P network or the RPC layer: ### Light Client -* Core verification +* Core verification * Bisection/sequential algorithms diff --git a/config/config.go b/config/config.go index 79be27d11..0679ce82f 100644 --- a/config/config.go +++ b/config/config.go @@ -607,6 +607,15 @@ type P2PConfig struct { //nolint: maligned // Testing params. // Force dial to fail TestDialFail bool `mapstructure:"test-dial-fail"` + + // Mostly for testing, use rather than environment variables + // to turn on the new P2P stack. + UseNewP2P bool `mapstructure:"use-new-p2p"` + + // Makes it possible to configure which queue backend the p2p + // layer uses. Options are: "fifo", "priority" and "wdrr", + // with the default being "fifo". + QueueType string `mapstructure:"queue-type"` } // DefaultP2PConfig returns a default configuration for the peer-to-peer layer @@ -634,6 +643,7 @@ func DefaultP2PConfig() *P2PConfig { HandshakeTimeout: 20 * time.Second, DialTimeout: 3 * time.Second, TestDialFail: false, + QueueType: "priority", } } diff --git a/config/toml.go b/config/toml.go index 84d26935d..4e8e0d0ea 100644 --- a/config/toml.go +++ b/config/toml.go @@ -168,7 +168,6 @@ abci = "{{ .BaseConfig.ABCI }}" # so the app can decide if we should keep the connection or not filter-peers = {{ .BaseConfig.FilterPeers }} - ####################################################################### ### Advanced Configuration Options ### ####################################################################### @@ -262,6 +261,12 @@ pprof-laddr = "{{ .RPC.PprofListenAddress }}" ####################################################### [p2p] +# Enable the new p2p layer. +use-new-p2p = {{ .P2P.UseNewP2P }} + +# Select the p2p internal queue +queue-type = "{{ .P2P.QueueType }}" + # Address to listen for incoming connections laddr = "{{ .P2P.ListenAddress }}" diff --git a/consensus/byzantine_test.go b/consensus/byzantine_test.go index 46b752d32..4e10b30b0 100644 --- a/consensus/byzantine_test.go +++ b/consensus/byzantine_test.go @@ -28,7 +28,7 @@ import ( // Byzantine node sends two different prevotes (nil and blockID) to the same // validator. func TestByzantinePrevoteEquivocation(t *testing.T) { - configSetup(t) + config := configSetup(t) nValidators := 4 prevoteHeight := int64(2) @@ -36,7 +36,7 @@ func TestByzantinePrevoteEquivocation(t *testing.T) { tickerFunc := newMockTickerFunc(true) appFunc := newCounter - genDoc, privVals := randGenesisDoc(nValidators, false, 30) + genDoc, privVals := randGenesisDoc(config, nValidators, false, 30) states := make([]*State, nValidators) for i := 0; i < nValidators; i++ { diff --git a/consensus/common_test.go b/consensus/common_test.go index 48a452de1..c763cafc6 100644 --- a/consensus/common_test.go +++ b/consensus/common_test.go @@ -42,24 +42,21 @@ import ( const ( testSubscriber = "test-client" + + // genesis, chain_id, priv_val + ensureTimeout = time.Millisecond * 200 ) // A cleanupFunc cleans up any config / test files created for a particular // test. type cleanupFunc func() -// genesis, chain_id, priv_val -var ( - config *cfg.Config // NOTE: must be reset for each _test.go file - consensusReplayConfig *cfg.Config - ensureTimeout = time.Millisecond * 200 -) - -func configSetup(t *testing.T) { +func configSetup(t *testing.T) *cfg.Config { t.Helper() - config = ResetConfig("consensus_reactor_test") - consensusReplayConfig = ResetConfig("consensus_replay_test") + config := ResetConfig("consensus_reactor_test") + + consensusReplayConfig := ResetConfig("consensus_replay_test") configStateTest := ResetConfig("consensus_state_test") configMempoolTest := ResetConfig("consensus_mempool_test") configByzantineTest := ResetConfig("consensus_byzantine_test") @@ -71,6 +68,7 @@ func configSetup(t *testing.T) { os.RemoveAll(configMempoolTest.RootDir) os.RemoveAll(configByzantineTest.RootDir) }) + return config } func ensureDir(dir string, mode os.FileMode) { @@ -94,7 +92,7 @@ type validatorStub struct { VotingPower int64 } -var testMinPower int64 = 10 +const testMinPower int64 = 10 func newValidatorStub(privValidator types.PrivValidator, valIndex int32) *validatorStub { return &validatorStub{ @@ -105,6 +103,7 @@ func newValidatorStub(privValidator types.PrivValidator, valIndex int32) *valida } func (vs *validatorStub) signVote( + config *cfg.Config, voteType tmproto.SignedMsgType, hash []byte, header types.PartSetHeader) (*types.Vote, error) { @@ -131,8 +130,14 @@ func (vs *validatorStub) signVote( } // Sign vote for type/hash/header -func signVote(vs *validatorStub, voteType tmproto.SignedMsgType, hash []byte, header types.PartSetHeader) *types.Vote { - v, err := vs.signVote(voteType, hash, header) +func signVote( + vs *validatorStub, + config *cfg.Config, + voteType tmproto.SignedMsgType, + hash []byte, + header types.PartSetHeader) *types.Vote { + + v, err := vs.signVote(config, voteType, hash, header) if err != nil { panic(fmt.Errorf("failed to sign vote: %v", err)) } @@ -140,13 +145,14 @@ func signVote(vs *validatorStub, voteType tmproto.SignedMsgType, hash []byte, he } func signVotes( + config *cfg.Config, voteType tmproto.SignedMsgType, hash []byte, header types.PartSetHeader, vss ...*validatorStub) []*types.Vote { votes := make([]*types.Vote, len(vss)) for i, vs := range vss { - votes[i] = signVote(vs, voteType, hash, header) + votes[i] = signVote(vs, config, voteType, hash, header) } return votes } @@ -237,13 +243,14 @@ func addVotes(to *State, votes ...*types.Vote) { } func signAddVotes( + config *cfg.Config, to *State, voteType tmproto.SignedMsgType, hash []byte, header types.PartSetHeader, vss ...*validatorStub, ) { - votes := signVotes(voteType, hash, header, vss...) + votes := signVotes(config, voteType, hash, header, vss...) addVotes(to, votes...) } @@ -440,9 +447,9 @@ func loadPrivValidator(config *cfg.Config) *privval.FilePV { return privValidator } -func randState(nValidators int) (*State, []*validatorStub) { +func randState(config *cfg.Config, nValidators int) (*State, []*validatorStub) { // Get State - state, privVals := randGenesisState(nValidators, false, 10) + state, privVals := randGenesisState(config, nValidators, false, 10) vss := make([]*validatorStub, nValidators) @@ -694,6 +701,7 @@ func consensusLogger() log.Logger { } func randConsensusState( + config *cfg.Config, nValidators int, testName string, tickerFunc func() TimeoutTicker, @@ -701,7 +709,7 @@ func randConsensusState( configOpts ...func(*cfg.Config), ) ([]*State, cleanupFunc) { - genDoc, privVals := randGenesisDoc(nValidators, false, 30) + genDoc, privVals := randGenesisDoc(config, nValidators, false, 30) css := make([]*State, nValidators) logger := consensusLogger() @@ -748,15 +756,17 @@ func randConsensusState( // nPeers = nValidators + nNotValidator func randConsensusNetWithPeers( + config *cfg.Config, nValidators, nPeers int, testName string, tickerFunc func() TimeoutTicker, appFunc func(string) abci.Application, ) ([]*State, *types.GenesisDoc, *cfg.Config, cleanupFunc) { - genDoc, privVals := randGenesisDoc(nValidators, false, testMinPower) + genDoc, privVals := randGenesisDoc(config, nValidators, false, testMinPower) css := make([]*State, nPeers) logger := consensusLogger() + var peer0Config *cfg.Config configRootDirs := make([]string, 0, nPeers) for i := 0; i < nPeers; i++ { @@ -808,7 +818,12 @@ func randConsensusNetWithPeers( } } -func randGenesisDoc(numValidators int, randPower bool, minPower int64) (*types.GenesisDoc, []types.PrivValidator) { +func randGenesisDoc( + config *cfg.Config, + numValidators int, + randPower bool, + minPower int64) (*types.GenesisDoc, []types.PrivValidator) { + validators := make([]types.GenesisValidator, numValidators) privValidators := make([]types.PrivValidator, numValidators) for i := 0; i < numValidators; i++ { @@ -829,8 +844,13 @@ func randGenesisDoc(numValidators int, randPower bool, minPower int64) (*types.G }, privValidators } -func randGenesisState(numValidators int, randPower bool, minPower int64) (sm.State, []types.PrivValidator) { - genDoc, privValidators := randGenesisDoc(numValidators, randPower, minPower) +func randGenesisState( + config *cfg.Config, + numValidators int, + randPower bool, + minPower int64) (sm.State, []types.PrivValidator) { + + genDoc, privValidators := randGenesisDoc(config, numValidators, randPower, minPower) s0, _ := sm.MakeGenesisState(genDoc) return s0, privValidators } diff --git a/consensus/invalid_test.go b/consensus/invalid_test.go index 31a4db4f5..816eafd65 100644 --- a/consensus/invalid_test.go +++ b/consensus/invalid_test.go @@ -15,10 +15,10 @@ import ( ) func TestReactorInvalidPrecommit(t *testing.T) { - configSetup(t) + config := configSetup(t) n := 4 - states, cleanup := randConsensusState(n, "consensus_reactor_test", newMockTickerFunc(true), newCounter) + states, cleanup := randConsensusState(config, n, "consensus_reactor_test", newMockTickerFunc(true), newCounter) t.Cleanup(cleanup) for i := 0; i < 4; i++ { diff --git a/consensus/mempool_test.go b/consensus/mempool_test.go index 85005f9eb..ec301af53 100644 --- a/consensus/mempool_test.go +++ b/consensus/mempool_test.go @@ -25,13 +25,13 @@ func assertMempool(txn txNotifier) mempl.Mempool { } func TestMempoolNoProgressUntilTxsAvailable(t *testing.T) { - configSetup(t) + baseConfig := configSetup(t) config := ResetConfig("consensus_mempool_txs_available_test") t.Cleanup(func() { _ = os.RemoveAll(config.RootDir) }) config.Consensus.CreateEmptyBlocks = false - state, privVals := randGenesisState(1, false, 10) + state, privVals := randGenesisState(baseConfig, 1, false, 10) cs := newStateWithConfig(config, state, privVals[0], NewCounterApplication()) assertMempool(cs.txNotifier).EnableTxsAvailable() height, round := cs.Height, cs.Round @@ -47,13 +47,13 @@ func TestMempoolNoProgressUntilTxsAvailable(t *testing.T) { } func TestMempoolProgressAfterCreateEmptyBlocksInterval(t *testing.T) { - configSetup(t) + baseConfig := configSetup(t) config := ResetConfig("consensus_mempool_txs_available_test") t.Cleanup(func() { _ = os.RemoveAll(config.RootDir) }) config.Consensus.CreateEmptyBlocksInterval = ensureTimeout - state, privVals := randGenesisState(1, false, 10) + state, privVals := randGenesisState(baseConfig, 1, false, 10) cs := newStateWithConfig(config, state, privVals[0], NewCounterApplication()) assertMempool(cs.txNotifier).EnableTxsAvailable() @@ -67,13 +67,13 @@ func TestMempoolProgressAfterCreateEmptyBlocksInterval(t *testing.T) { } func TestMempoolProgressInHigherRound(t *testing.T) { - configSetup(t) + baseConfig := configSetup(t) config := ResetConfig("consensus_mempool_txs_available_test") t.Cleanup(func() { _ = os.RemoveAll(config.RootDir) }) config.Consensus.CreateEmptyBlocks = false - state, privVals := randGenesisState(1, false, 10) + state, privVals := randGenesisState(baseConfig, 1, false, 10) cs := newStateWithConfig(config, state, privVals[0], NewCounterApplication()) assertMempool(cs.txNotifier).EnableTxsAvailable() height, round := cs.Height, cs.Round @@ -119,9 +119,9 @@ func deliverTxsRange(cs *State, start, end int) { } func TestMempoolTxConcurrentWithCommit(t *testing.T) { - configSetup(t) + config := configSetup(t) - state, privVals := randGenesisState(1, false, 10) + state, privVals := randGenesisState(config, 1, false, 10) blockDB := dbm.NewMemDB() stateStore := sm.NewStore(blockDB) cs := newStateWithConfigAndBlockStore(config, state, privVals[0], NewCounterApplication(), blockDB) @@ -145,9 +145,9 @@ func TestMempoolTxConcurrentWithCommit(t *testing.T) { } func TestMempoolRmBadTx(t *testing.T) { - configSetup(t) + config := configSetup(t) - state, privVals := randGenesisState(1, false, 10) + state, privVals := randGenesisState(config, 1, false, 10) app := NewCounterApplication() blockDB := dbm.NewMemDB() stateStore := sm.NewStore(blockDB) diff --git a/consensus/reactor_test.go b/consensus/reactor_test.go index 3d14533c5..e09ddc953 100644 --- a/consensus/reactor_test.go +++ b/consensus/reactor_test.go @@ -246,10 +246,10 @@ func waitForBlockWithUpdatedValsAndValidateIt( } func TestReactorBasic(t *testing.T) { - configSetup(t) + config := configSetup(t) n := 4 - states, cleanup := randConsensusState(n, "consensus_reactor_test", newMockTickerFunc(true), newCounter) + states, cleanup := randConsensusState(config, n, "consensus_reactor_test", newMockTickerFunc(true), newCounter) t.Cleanup(cleanup) rts := setup(t, n, states, 100) // buffer must be large enough to not deadlock @@ -274,14 +274,14 @@ func TestReactorBasic(t *testing.T) { } func TestReactorWithEvidence(t *testing.T) { - configSetup(t) + config := configSetup(t) n := 4 testName := "consensus_reactor_test" tickerFunc := newMockTickerFunc(true) appFunc := newCounter - genDoc, privVals := randGenesisDoc(n, false, 30) + genDoc, privVals := randGenesisDoc(config, n, false, 30) states := make([]*State, n) logger := consensusLogger() @@ -369,10 +369,11 @@ func TestReactorWithEvidence(t *testing.T) { } func TestReactorCreatesBlockWhenEmptyBlocksFalse(t *testing.T) { - configSetup(t) + config := configSetup(t) n := 4 states, cleanup := randConsensusState( + config, n, "consensus_reactor_test", newMockTickerFunc(true), @@ -409,10 +410,10 @@ func TestReactorCreatesBlockWhenEmptyBlocksFalse(t *testing.T) { } func TestReactorRecordsVotesAndBlockParts(t *testing.T) { - configSetup(t) + config := configSetup(t) n := 4 - states, cleanup := randConsensusState(n, "consensus_reactor_test", newMockTickerFunc(true), newCounter) + states, cleanup := randConsensusState(config, n, "consensus_reactor_test", newMockTickerFunc(true), newCounter) t.Cleanup(cleanup) rts := setup(t, n, states, 100) // buffer must be large enough to not deadlock @@ -466,10 +467,11 @@ func TestReactorRecordsVotesAndBlockParts(t *testing.T) { } func TestReactorVotingPowerChange(t *testing.T) { - configSetup(t) + config := configSetup(t) n := 4 states, cleanup := randConsensusState( + config, n, "consensus_voting_power_changes_test", newMockTickerFunc(true), @@ -565,11 +567,12 @@ func TestReactorVotingPowerChange(t *testing.T) { } func TestReactorValidatorSetChanges(t *testing.T) { - configSetup(t) + config := configSetup(t) nPeers := 7 nVals := 4 states, _, _, cleanup := randConsensusNetWithPeers( + config, nVals, nPeers, "consensus_val_set_changes_test", diff --git a/consensus/replay_test.go b/consensus/replay_test.go index 8175033ab..e51fe1217 100644 --- a/consensus/replay_test.go +++ b/consensus/replay_test.go @@ -6,6 +6,7 @@ import ( "fmt" "io" "io/ioutil" + "math/rand" "os" "path/filepath" "runtime" @@ -276,25 +277,21 @@ func (w *crashingWAL) Stop() error { return w.next.Stop() } func (w *crashingWAL) Wait() { w.next.Wait() } //------------------------------------------------------------------------------------------ -type testSim struct { +type simulatorTestSuite struct { GenesisState sm.State Config *cfg.Config Chain []*types.Block Commits []*types.Commit CleanupFunc cleanupFunc + + Mempool mempl.Mempool + Evpool sm.EvidencePool } const ( numBlocks = 6 ) -var ( - mempool = emptyMempool{} - evpool = sm.EmptyEvidencePool{} - - sim testSim -) - //--------------------------------------- // Test handshake/replay @@ -305,12 +302,20 @@ var ( var modes = []uint{0, 1, 2, 3} // This is actually not a test, it's for storing validator change tx data for testHandshakeReplay -func TestSimulateValidatorsChange(t *testing.T) { - configSetup(t) +func setupSimulator(t *testing.T) *simulatorTestSuite { + t.Helper() + config := configSetup(t) + + sim := &simulatorTestSuite{ + Mempool: emptyMempool{}, + Evpool: sm.EmptyEvidencePool{}, + } nPeers := 7 nVals := 4 + css, genDoc, config, cleanup := randConsensusNetWithPeers( + config, nVals, nPeers, "replay_test", @@ -337,7 +342,11 @@ func TestSimulateValidatorsChange(t *testing.T) { ensureNewRound(newRoundCh, height, 0) ensureNewProposal(proposalCh, height, round) rs := css[0].GetRoundState() - signAddVotes(css[0], tmproto.PrecommitType, rs.ProposalBlock.Hash(), rs.ProposalBlockParts.Header(), vss[1:nVals]...) + + signAddVotes(sim.Config, css[0], tmproto.PrecommitType, + rs.ProposalBlock.Hash(), rs.ProposalBlockParts.Header(), + vss[1:nVals]...) + ensureNewRound(newRoundCh, height+1, 0) // HEIGHT 2 @@ -367,7 +376,9 @@ func TestSimulateValidatorsChange(t *testing.T) { } ensureNewProposal(proposalCh, height, round) rs = css[0].GetRoundState() - signAddVotes(css[0], tmproto.PrecommitType, rs.ProposalBlock.Hash(), rs.ProposalBlockParts.Header(), vss[1:nVals]...) + signAddVotes(sim.Config, css[0], tmproto.PrecommitType, + rs.ProposalBlock.Hash(), rs.ProposalBlockParts.Header(), + vss[1:nVals]...) ensureNewRound(newRoundCh, height+1, 0) // HEIGHT 3 @@ -397,7 +408,9 @@ func TestSimulateValidatorsChange(t *testing.T) { } ensureNewProposal(proposalCh, height, round) rs = css[0].GetRoundState() - signAddVotes(css[0], tmproto.PrecommitType, rs.ProposalBlock.Hash(), rs.ProposalBlockParts.Header(), vss[1:nVals]...) + signAddVotes(sim.Config, css[0], tmproto.PrecommitType, + rs.ProposalBlock.Hash(), rs.ProposalBlockParts.Header(), + vss[1:nVals]...) ensureNewRound(newRoundCh, height+1, 0) // HEIGHT 4 @@ -463,7 +476,9 @@ func TestSimulateValidatorsChange(t *testing.T) { if i == selfIndex { continue } - signAddVotes(css[0], tmproto.PrecommitType, rs.ProposalBlock.Hash(), rs.ProposalBlockParts.Header(), newVss[i]) + signAddVotes(sim.Config, css[0], + tmproto.PrecommitType, rs.ProposalBlock.Hash(), + rs.ProposalBlockParts.Header(), newVss[i]) } ensureNewRound(newRoundCh, height+1, 0) @@ -482,7 +497,9 @@ func TestSimulateValidatorsChange(t *testing.T) { if i == selfIndex { continue } - signAddVotes(css[0], tmproto.PrecommitType, rs.ProposalBlock.Hash(), rs.ProposalBlockParts.Header(), newVss[i]) + signAddVotes(sim.Config, css[0], + tmproto.PrecommitType, rs.ProposalBlock.Hash(), + rs.ProposalBlockParts.Header(), newVss[i]) } ensureNewRound(newRoundCh, height+1, 0) @@ -517,7 +534,9 @@ func TestSimulateValidatorsChange(t *testing.T) { if i == selfIndex { continue } - signAddVotes(css[0], tmproto.PrecommitType, rs.ProposalBlock.Hash(), rs.ProposalBlockParts.Header(), newVss[i]) + signAddVotes(sim.Config, css[0], + tmproto.PrecommitType, rs.ProposalBlock.Hash(), + rs.ProposalBlockParts.Header(), newVss[i]) } ensureNewRound(newRoundCh, height+1, 0) @@ -527,61 +546,67 @@ func TestSimulateValidatorsChange(t *testing.T) { sim.Chain = append(sim.Chain, css[0].blockStore.LoadBlock(int64(i))) sim.Commits = append(sim.Commits, css[0].blockStore.LoadBlockCommit(int64(i))) } + if sim.CleanupFunc != nil { + t.Cleanup(sim.CleanupFunc) + } + + return sim } // Sync from scratch func TestHandshakeReplayAll(t *testing.T) { - configSetup(t) + sim := setupSimulator(t) for _, m := range modes { - testHandshakeReplay(t, config, 0, m, false) + testHandshakeReplay(t, sim, 0, m, false) } for _, m := range modes { - testHandshakeReplay(t, config, 0, m, true) + testHandshakeReplay(t, sim, 0, m, true) } } // Sync many, not from scratch func TestHandshakeReplaySome(t *testing.T) { - configSetup(t) + sim := setupSimulator(t) for _, m := range modes { - testHandshakeReplay(t, config, 2, m, false) + testHandshakeReplay(t, sim, 2, m, false) } for _, m := range modes { - testHandshakeReplay(t, config, 2, m, true) + testHandshakeReplay(t, sim, 2, m, true) } } // Sync from lagging by one func TestHandshakeReplayOne(t *testing.T) { - configSetup(t) + sim := setupSimulator(t) for _, m := range modes { - testHandshakeReplay(t, config, numBlocks-1, m, false) + testHandshakeReplay(t, sim, numBlocks-1, m, false) } for _, m := range modes { - testHandshakeReplay(t, config, numBlocks-1, m, true) + testHandshakeReplay(t, sim, numBlocks-1, m, true) } } // Sync from caught up func TestHandshakeReplayNone(t *testing.T) { - configSetup(t) + sim := setupSimulator(t) for _, m := range modes { - testHandshakeReplay(t, config, numBlocks, m, false) + testHandshakeReplay(t, sim, numBlocks, m, false) } for _, m := range modes { - testHandshakeReplay(t, config, numBlocks, m, true) + testHandshakeReplay(t, sim, numBlocks, m, true) } } // Test mockProxyApp should not panic when app return ABCIResponses with some empty ResponseDeliverTx func TestMockProxyApp(t *testing.T) { - configSetup(t) + sim := setupSimulator(t) // setup config and simulator + config := sim.Config + assert.NotNil(t, config) - sim.CleanupFunc() // clean the test env created in TestSimulateValidatorsChange logger := log.TestingLogger() var validTxs, invalidTxs = 0, 0 txIndex := 0 @@ -648,12 +673,15 @@ func tempWALWithData(data []byte) string { // Make some blocks. Start a fresh app and apply nBlocks blocks. // Then restart the app and sync it up with the remaining blocks -func testHandshakeReplay(t *testing.T, config *cfg.Config, nBlocks int, mode uint, testValidatorsChange bool) { +func testHandshakeReplay(t *testing.T, sim *simulatorTestSuite, nBlocks int, mode uint, testValidatorsChange bool) { var chain []*types.Block var commits []*types.Commit var store *mockBlockStore var stateDB dbm.DB var genesisState sm.State + + config := sim.Config + if testValidatorsChange { testConfig := ResetConfig(fmt.Sprintf("%s_%v_m", t.Name(), mode)) defer func() { _ = os.RemoveAll(testConfig.RootDir) }() @@ -698,12 +726,12 @@ func testHandshakeReplay(t *testing.T, config *cfg.Config, nBlocks int, mode uin state := genesisState.Copy() // run the chain through state.ApplyBlock to build up the tendermint state - state = buildTMStateFromChain(config, stateStore, state, chain, nBlocks, mode) + state = buildTMStateFromChain(config, sim.Mempool, sim.Evpool, stateStore, state, chain, nBlocks, mode) latestAppHash := state.AppHash // make a new client creator kvstoreApp := kvstore.NewPersistentKVStoreApplication( - filepath.Join(config.DBDir(), fmt.Sprintf("replay_test_%d_%d_a", nBlocks, mode))) + filepath.Join(config.DBDir(), fmt.Sprintf("replay_test_%d_%d_a_r%d", nBlocks, mode, rand.Int()))) t.Cleanup(func() { require.NoError(t, kvstoreApp.Close()) }) clientCreator2 := proxy.NewLocalClientCreator(kvstoreApp) @@ -715,7 +743,7 @@ func testHandshakeReplay(t *testing.T, config *cfg.Config, nBlocks int, mode uin stateStore := sm.NewStore(stateDB1) err := stateStore.Save(genesisState) require.NoError(t, err) - buildAppStateFromChain(proxyApp, stateStore, genesisState, chain, nBlocks, mode) + buildAppStateFromChain(proxyApp, stateStore, sim.Mempool, sim.Evpool, genesisState, chain, nBlocks, mode) } // Prune block store if requested @@ -775,7 +803,12 @@ func testHandshakeReplay(t *testing.T, config *cfg.Config, nBlocks int, mode uin } } -func applyBlock(stateStore sm.Store, st sm.State, blk *types.Block, proxyApp proxy.AppConns) sm.State { +func applyBlock(stateStore sm.Store, + mempool mempl.Mempool, + evpool sm.EvidencePool, + st sm.State, + blk *types.Block, + proxyApp proxy.AppConns) sm.State { testPartSize := types.BlockPartSizeBytes blockExec := sm.NewBlockExecutor(stateStore, log.TestingLogger(), proxyApp.Consensus(), mempool, evpool) @@ -787,8 +820,14 @@ func applyBlock(stateStore sm.Store, st sm.State, blk *types.Block, proxyApp pro return newState } -func buildAppStateFromChain(proxyApp proxy.AppConns, stateStore sm.Store, - state sm.State, chain []*types.Block, nBlocks int, mode uint) { +func buildAppStateFromChain( + proxyApp proxy.AppConns, + stateStore sm.Store, + mempool mempl.Mempool, + evpool sm.EvidencePool, + state sm.State, + chain []*types.Block, + nBlocks int, mode uint) { // start a new app without handshake, play nBlocks blocks if err := proxyApp.Start(); err != nil { panic(err) @@ -809,18 +848,18 @@ func buildAppStateFromChain(proxyApp proxy.AppConns, stateStore sm.Store, case 0: for i := 0; i < nBlocks; i++ { block := chain[i] - state = applyBlock(stateStore, state, block, proxyApp) + state = applyBlock(stateStore, mempool, evpool, state, block, proxyApp) } case 1, 2, 3: for i := 0; i < nBlocks-1; i++ { block := chain[i] - state = applyBlock(stateStore, state, block, proxyApp) + state = applyBlock(stateStore, mempool, evpool, state, block, proxyApp) } if mode == 2 || mode == 3 { // update the kvstore height and apphash // as if we ran commit but not - state = applyBlock(stateStore, state, chain[nBlocks-1], proxyApp) + state = applyBlock(stateStore, mempool, evpool, state, chain[nBlocks-1], proxyApp) } default: panic(fmt.Sprintf("unknown mode %v", mode)) @@ -830,6 +869,8 @@ func buildAppStateFromChain(proxyApp proxy.AppConns, stateStore sm.Store, func buildTMStateFromChain( config *cfg.Config, + mempool mempl.Mempool, + evpool sm.EvidencePool, stateStore sm.Store, state sm.State, chain []*types.Block, @@ -861,19 +902,19 @@ func buildTMStateFromChain( case 0: // sync right up for _, block := range chain { - state = applyBlock(stateStore, state, block, proxyApp) + state = applyBlock(stateStore, mempool, evpool, state, block, proxyApp) } case 1, 2, 3: // sync up to the penultimate as if we stored the block. // whether we commit or not depends on the appHash for _, block := range chain[:len(chain)-1] { - state = applyBlock(stateStore, state, block, proxyApp) + state = applyBlock(stateStore, mempool, evpool, state, block, proxyApp) } // apply the final block to a state copy so we can // get the right next appHash but keep the state back - applyBlock(stateStore, state, chain[len(chain)-1], proxyApp) + applyBlock(stateStore, mempool, evpool, state, chain[len(chain)-1], proxyApp) default: panic(fmt.Sprintf("unknown mode %v", mode)) } diff --git a/consensus/state_test.go b/consensus/state_test.go index de0f6cc09..63c75a4a4 100644 --- a/consensus/state_test.go +++ b/consensus/state_test.go @@ -56,9 +56,9 @@ x * TestHalt1 - if we see +2/3 precommits after timing out into new round, we sh // ProposeSuite func TestStateProposerSelection0(t *testing.T) { - configSetup(t) + config := configSetup(t) - cs1, vss := randState(4) + cs1, vss := randState(config, 4) height, round := cs1.Height, cs1.Round newRoundCh := subscribe(cs1.eventBus, types.EventQueryNewRound) @@ -82,7 +82,7 @@ func TestStateProposerSelection0(t *testing.T) { ensureNewProposal(proposalCh, height, round) rs := cs1.GetRoundState() - signAddVotes(cs1, tmproto.PrecommitType, rs.ProposalBlock.Hash(), rs.ProposalBlockParts.Header(), vss[1:]...) + signAddVotes(config, cs1, tmproto.PrecommitType, rs.ProposalBlock.Hash(), rs.ProposalBlockParts.Header(), vss[1:]...) // Wait for new round so next validator is set. ensureNewRound(newRoundCh, height+1, 0) @@ -98,9 +98,9 @@ func TestStateProposerSelection0(t *testing.T) { // Now let's do it all again, but starting from round 2 instead of 0 func TestStateProposerSelection2(t *testing.T) { - configSetup(t) + config := configSetup(t) - cs1, vss := randState(4) // test needs more work for more than 3 validators + cs1, vss := randState(config, 4) // test needs more work for more than 3 validators height := cs1.Height newRoundCh := subscribe(cs1.eventBus, types.EventQueryNewRound) @@ -128,7 +128,7 @@ func TestStateProposerSelection2(t *testing.T) { } rs := cs1.GetRoundState() - signAddVotes(cs1, tmproto.PrecommitType, nil, rs.ProposalBlockParts.Header(), vss[1:]...) + signAddVotes(config, cs1, tmproto.PrecommitType, nil, rs.ProposalBlockParts.Header(), vss[1:]...) ensureNewRound(newRoundCh, height, i+round+1) // wait for the new round event each round incrementRound(vss[1:]...) } @@ -137,9 +137,9 @@ func TestStateProposerSelection2(t *testing.T) { // a non-validator should timeout into the prevote round func TestStateEnterProposeNoPrivValidator(t *testing.T) { - configSetup(t) + config := configSetup(t) - cs, _ := randState(1) + cs, _ := randState(config, 1) cs.SetPrivValidator(nil) height, round := cs.Height, cs.Round @@ -158,9 +158,9 @@ func TestStateEnterProposeNoPrivValidator(t *testing.T) { // a validator should not timeout of the prevote round (TODO: unless the block is really big!) func TestStateEnterProposeYesPrivValidator(t *testing.T) { - configSetup(t) + config := configSetup(t) - cs, _ := randState(1) + cs, _ := randState(config, 1) height, round := cs.Height, cs.Round // Listen for propose timeout event @@ -190,9 +190,9 @@ func TestStateEnterProposeYesPrivValidator(t *testing.T) { } func TestStateBadProposal(t *testing.T) { - configSetup(t) + config := configSetup(t) - cs1, vss := randState(2) + cs1, vss := randState(config, 2) height, round := cs1.Height, cs1.Round vs2 := vss[1] @@ -240,19 +240,19 @@ func TestStateBadProposal(t *testing.T) { validatePrevote(t, cs1, round, vss[0], nil) // add bad prevote from vs2 and wait for it - signAddVotes(cs1, tmproto.PrevoteType, propBlock.Hash(), propBlock.MakePartSet(partSize).Header(), vs2) + signAddVotes(config, cs1, tmproto.PrevoteType, propBlock.Hash(), propBlock.MakePartSet(partSize).Header(), vs2) ensurePrevote(voteCh, height, round) // wait for precommit ensurePrecommit(voteCh, height, round) validatePrecommit(t, cs1, round, -1, vss[0], nil, nil) - signAddVotes(cs1, tmproto.PrecommitType, propBlock.Hash(), propBlock.MakePartSet(partSize).Header(), vs2) + signAddVotes(config, cs1, tmproto.PrecommitType, propBlock.Hash(), propBlock.MakePartSet(partSize).Header(), vs2) } func TestStateOversizedBlock(t *testing.T) { - configSetup(t) + config := configSetup(t) - cs1, vss := randState(2) + cs1, vss := randState(config, 2) cs1.state.ConsensusParams.Block.MaxBytes = 2000 height, round := cs1.Height, cs1.Round vs2 := vss[1] @@ -302,11 +302,11 @@ func TestStateOversizedBlock(t *testing.T) { // precommit on it ensurePrevote(voteCh, height, round) validatePrevote(t, cs1, round, vss[0], nil) - signAddVotes(cs1, tmproto.PrevoteType, propBlock.Hash(), propBlock.MakePartSet(partSize).Header(), vs2) + signAddVotes(config, cs1, tmproto.PrevoteType, propBlock.Hash(), propBlock.MakePartSet(partSize).Header(), vs2) ensurePrevote(voteCh, height, round) ensurePrecommit(voteCh, height, round) validatePrecommit(t, cs1, round, -1, vss[0], nil, nil) - signAddVotes(cs1, tmproto.PrecommitType, propBlock.Hash(), propBlock.MakePartSet(partSize).Header(), vs2) + signAddVotes(config, cs1, tmproto.PrecommitType, propBlock.Hash(), propBlock.MakePartSet(partSize).Header(), vs2) } //---------------------------------------------------------------------------------------------------- @@ -314,9 +314,9 @@ func TestStateOversizedBlock(t *testing.T) { // propose, prevote, and precommit a block func TestStateFullRound1(t *testing.T) { - configSetup(t) + config := configSetup(t) - cs, vss := randState(1) + cs, vss := randState(config, 1) height, round := cs.Height, cs.Round // NOTE: buffer capacity of 0 ensures we can validate prevote and last commit @@ -356,9 +356,9 @@ func TestStateFullRound1(t *testing.T) { // nil is proposed, so prevote and precommit nil func TestStateFullRoundNil(t *testing.T) { - configSetup(t) + config := configSetup(t) - cs, vss := randState(1) + cs, vss := randState(config, 1) height, round := cs.Height, cs.Round voteCh := subscribeUnBuffered(cs.eventBus, types.EventQueryVote) @@ -376,9 +376,9 @@ func TestStateFullRoundNil(t *testing.T) { // run through propose, prevote, precommit commit with two validators // where the first validator has to wait for votes from the second func TestStateFullRound2(t *testing.T) { - configSetup(t) + config := configSetup(t) - cs1, vss := randState(2) + cs1, vss := randState(config, 2) vs2 := vss[1] height, round := cs1.Height, cs1.Round @@ -395,7 +395,7 @@ func TestStateFullRound2(t *testing.T) { propBlockHash, propPartSetHeader := rs.ProposalBlock.Hash(), rs.ProposalBlockParts.Header() // prevote arrives from vs2: - signAddVotes(cs1, tmproto.PrevoteType, propBlockHash, propPartSetHeader, vs2) + signAddVotes(config, cs1, tmproto.PrevoteType, propBlockHash, propPartSetHeader, vs2) ensurePrevote(voteCh, height, round) // prevote ensurePrecommit(voteCh, height, round) // precommit @@ -405,7 +405,7 @@ func TestStateFullRound2(t *testing.T) { // we should be stuck in limbo waiting for more precommits // precommit arrives from vs2: - signAddVotes(cs1, tmproto.PrecommitType, propBlockHash, propPartSetHeader, vs2) + signAddVotes(config, cs1, tmproto.PrecommitType, propBlockHash, propPartSetHeader, vs2) ensurePrecommit(voteCh, height, round) // wait to finish commit, propose in next height @@ -418,9 +418,9 @@ func TestStateFullRound2(t *testing.T) { // two validators, 4 rounds. // two vals take turns proposing. val1 locks on first one, precommits nil on everything else func TestStateLockNoPOL(t *testing.T) { - configSetup(t) + config := configSetup(t) - cs1, vss := randState(2) + cs1, vss := randState(config, 2) vs2 := vss[1] height, round := cs1.Height, cs1.Round @@ -451,7 +451,7 @@ func TestStateLockNoPOL(t *testing.T) { // we should now be stuck in limbo forever, waiting for more prevotes // prevote arrives from vs2: - signAddVotes(cs1, tmproto.PrevoteType, theBlockHash, thePartSetHeader, vs2) + signAddVotes(config, cs1, tmproto.PrevoteType, theBlockHash, thePartSetHeader, vs2) ensurePrevote(voteCh, height, round) // prevote ensurePrecommit(voteCh, height, round) // precommit @@ -463,7 +463,7 @@ func TestStateLockNoPOL(t *testing.T) { hash := make([]byte, len(theBlockHash)) copy(hash, theBlockHash) hash[0] = (hash[0] + 1) % 255 - signAddVotes(cs1, tmproto.PrecommitType, hash, thePartSetHeader, vs2) + signAddVotes(config, cs1, tmproto.PrecommitType, hash, thePartSetHeader, vs2) ensurePrecommit(voteCh, height, round) // precommit // (note we're entering precommit for a second time this round) @@ -496,7 +496,7 @@ func TestStateLockNoPOL(t *testing.T) { validatePrevote(t, cs1, round, vss[0], rs.LockedBlock.Hash()) // add a conflicting prevote from the other validator - signAddVotes(cs1, tmproto.PrevoteType, hash, rs.LockedBlock.MakePartSet(partSize).Header(), vs2) + signAddVotes(config, cs1, tmproto.PrevoteType, hash, rs.LockedBlock.MakePartSet(partSize).Header(), vs2) ensurePrevote(voteCh, height, round) // now we're going to enter prevote again, but with invalid args @@ -509,7 +509,7 @@ func TestStateLockNoPOL(t *testing.T) { validatePrecommit(t, cs1, round, 0, vss[0], nil, theBlockHash) // add conflicting precommit from vs2 - signAddVotes(cs1, tmproto.PrecommitType, hash, rs.LockedBlock.MakePartSet(partSize).Header(), vs2) + signAddVotes(config, cs1, tmproto.PrecommitType, hash, rs.LockedBlock.MakePartSet(partSize).Header(), vs2) ensurePrecommit(voteCh, height, round) // (note we're entering precommit for a second time this round, but with invalid args @@ -539,7 +539,7 @@ func TestStateLockNoPOL(t *testing.T) { ensurePrevote(voteCh, height, round) // prevote validatePrevote(t, cs1, round, vss[0], rs.LockedBlock.Hash()) - signAddVotes(cs1, tmproto.PrevoteType, hash, rs.ProposalBlock.MakePartSet(partSize).Header(), vs2) + signAddVotes(config, cs1, tmproto.PrevoteType, hash, rs.ProposalBlock.MakePartSet(partSize).Header(), vs2) ensurePrevote(voteCh, height, round) ensureNewTimeout(timeoutWaitCh, height, round, cs1.config.Prevote(round).Nanoseconds()) @@ -548,6 +548,7 @@ func TestStateLockNoPOL(t *testing.T) { validatePrecommit(t, cs1, round, 0, vss[0], nil, theBlockHash) // precommit nil but be locked on proposal signAddVotes( + config, cs1, tmproto.PrecommitType, hash, @@ -557,7 +558,7 @@ func TestStateLockNoPOL(t *testing.T) { ensureNewTimeout(timeoutWaitCh, height, round, cs1.config.Precommit(round).Nanoseconds()) - cs2, _ := randState(2) // needed so generated block is different than locked block + cs2, _ := randState(config, 2) // needed so generated block is different than locked block // before we time out into new round, set next proposal block prop, propBlock := decideProposal(cs2, vs2, vs2.Height, vs2.Round+1) if prop == nil || propBlock == nil { @@ -585,7 +586,7 @@ func TestStateLockNoPOL(t *testing.T) { validatePrevote(t, cs1, 3, vss[0], cs1.LockedBlock.Hash()) // prevote for proposed block - signAddVotes(cs1, tmproto.PrevoteType, propBlock.Hash(), propBlock.MakePartSet(partSize).Header(), vs2) + signAddVotes(config, cs1, tmproto.PrevoteType, propBlock.Hash(), propBlock.MakePartSet(partSize).Header(), vs2) ensurePrevote(voteCh, height, round) ensureNewTimeout(timeoutWaitCh, height, round, cs1.config.Prevote(round).Nanoseconds()) @@ -593,6 +594,7 @@ func TestStateLockNoPOL(t *testing.T) { validatePrecommit(t, cs1, round, 0, vss[0], nil, theBlockHash) // precommit nil but locked on proposal signAddVotes( + config, cs1, tmproto.PrecommitType, propBlock.Hash(), @@ -606,9 +608,9 @@ func TestStateLockNoPOL(t *testing.T) { // in round two: v1 prevotes the same block that the node is locked on // the others prevote a new block hence v1 changes lock and precommits the new block with the others func TestStateLockPOLRelock(t *testing.T) { - configSetup(t) + config := configSetup(t) - cs1, vss := randState(4) + cs1, vss := randState(config, 4) vs2, vs3, vs4 := vss[1], vss[2], vss[3] height, round := cs1.Height, cs1.Round @@ -642,14 +644,14 @@ func TestStateLockPOLRelock(t *testing.T) { ensurePrevote(voteCh, height, round) // prevote - signAddVotes(cs1, tmproto.PrevoteType, theBlockHash, theBlockParts, vs2, vs3, vs4) + signAddVotes(config, cs1, tmproto.PrevoteType, theBlockHash, theBlockParts, vs2, vs3, vs4) ensurePrecommit(voteCh, height, round) // our precommit // the proposed block should now be locked and our precommit added validatePrecommit(t, cs1, round, round, vss[0], theBlockHash, theBlockHash) // add precommits from the rest - signAddVotes(cs1, tmproto.PrecommitType, nil, types.PartSetHeader{}, vs2, vs3, vs4) + signAddVotes(config, cs1, tmproto.PrecommitType, nil, types.PartSetHeader{}, vs2, vs3, vs4) // before we timeout to the new round set the new proposal cs2 := newState(cs1.state, vs2, counter.NewApplication(true)) @@ -690,14 +692,14 @@ func TestStateLockPOLRelock(t *testing.T) { validatePrevote(t, cs1, round, vss[0], theBlockHash) // now lets add prevotes from everyone else for the new block - signAddVotes(cs1, tmproto.PrevoteType, propBlockHash, propBlockParts.Header(), vs2, vs3, vs4) + signAddVotes(config, cs1, tmproto.PrevoteType, propBlockHash, propBlockParts.Header(), vs2, vs3, vs4) ensurePrecommit(voteCh, height, round) // we should have unlocked and locked on the new block, sending a precommit for this new block validatePrecommit(t, cs1, round, round, vss[0], propBlockHash, propBlockHash) // more prevote creating a majority on the new block and this is then committed - signAddVotes(cs1, tmproto.PrecommitType, propBlockHash, propBlockParts.Header(), vs2, vs3) + signAddVotes(config, cs1, tmproto.PrecommitType, propBlockHash, propBlockParts.Header(), vs2, vs3) ensureNewBlockHeader(newBlockCh, height, propBlockHash) ensureNewRound(newRoundCh, height+1, 0) @@ -705,9 +707,9 @@ func TestStateLockPOLRelock(t *testing.T) { // 4 vals, one precommits, other 3 polka at next round, so we unlock and precomit the polka func TestStateLockPOLUnlock(t *testing.T) { - configSetup(t) + config := configSetup(t) - cs1, vss := randState(4) + cs1, vss := randState(config, 4) vs2, vs3, vs4 := vss[1], vss[2], vss[3] height, round := cs1.Height, cs1.Round @@ -741,15 +743,15 @@ func TestStateLockPOLUnlock(t *testing.T) { ensurePrevote(voteCh, height, round) validatePrevote(t, cs1, round, vss[0], theBlockHash) - signAddVotes(cs1, tmproto.PrevoteType, theBlockHash, theBlockParts, vs2, vs3, vs4) + signAddVotes(config, cs1, tmproto.PrevoteType, theBlockHash, theBlockParts, vs2, vs3, vs4) ensurePrecommit(voteCh, height, round) // the proposed block should now be locked and our precommit added validatePrecommit(t, cs1, round, round, vss[0], theBlockHash, theBlockHash) // add precommits from the rest - signAddVotes(cs1, tmproto.PrecommitType, nil, types.PartSetHeader{}, vs2, vs4) - signAddVotes(cs1, tmproto.PrecommitType, theBlockHash, theBlockParts, vs3) + signAddVotes(config, cs1, tmproto.PrecommitType, nil, types.PartSetHeader{}, vs2, vs4) + signAddVotes(config, cs1, tmproto.PrecommitType, theBlockHash, theBlockParts, vs3) // before we time out into new round, set next proposal block prop, propBlock := decideProposal(cs1, vs2, vs2.Height, vs2.Round+1) @@ -780,7 +782,7 @@ func TestStateLockPOLUnlock(t *testing.T) { ensurePrevote(voteCh, height, round) validatePrevote(t, cs1, round, vss[0], lockedBlockHash) // now lets add prevotes from everyone else for nil (a polka!) - signAddVotes(cs1, tmproto.PrevoteType, nil, types.PartSetHeader{}, vs2, vs3, vs4) + signAddVotes(config, cs1, tmproto.PrevoteType, nil, types.PartSetHeader{}, vs2, vs3, vs4) // the polka makes us unlock and precommit nil ensureNewUnlock(unlockCh, height, round) @@ -790,7 +792,7 @@ func TestStateLockPOLUnlock(t *testing.T) { // NOTE: since we don't relock on nil, the lock round is -1 validatePrecommit(t, cs1, round, -1, vss[0], nil, nil) - signAddVotes(cs1, tmproto.PrecommitType, nil, types.PartSetHeader{}, vs2, vs3) + signAddVotes(config, cs1, tmproto.PrecommitType, nil, types.PartSetHeader{}, vs2, vs3) ensureNewRound(newRoundCh, height, round+1) } @@ -799,9 +801,9 @@ func TestStateLockPOLUnlock(t *testing.T) { // v1 should unlock and precommit nil. In the third round another block is proposed, all vals // prevote and now v1 can lock onto the third block and precommit that func TestStateLockPOLUnlockOnUnknownBlock(t *testing.T) { - configSetup(t) + config := configSetup(t) - cs1, vss := randState(4) + cs1, vss := randState(config, 4) vs2, vs3, vs4 := vss[1], vss[2], vss[3] height, round := cs1.Height, cs1.Round @@ -831,14 +833,14 @@ func TestStateLockPOLUnlockOnUnknownBlock(t *testing.T) { ensurePrevote(voteCh, height, round) // prevote - signAddVotes(cs1, tmproto.PrevoteType, firstBlockHash, firstBlockParts, vs2, vs3, vs4) + signAddVotes(config, cs1, tmproto.PrevoteType, firstBlockHash, firstBlockParts, vs2, vs3, vs4) ensurePrecommit(voteCh, height, round) // our precommit // the proposed block should now be locked and our precommit added validatePrecommit(t, cs1, round, round, vss[0], firstBlockHash, firstBlockHash) // add precommits from the rest - signAddVotes(cs1, tmproto.PrecommitType, nil, types.PartSetHeader{}, vs2, vs3, vs4) + signAddVotes(config, cs1, tmproto.PrecommitType, nil, types.PartSetHeader{}, vs2, vs3, vs4) // before we timeout to the new round set the new proposal cs2 := newState(cs1.state, vs2, counter.NewApplication(true)) @@ -871,7 +873,7 @@ func TestStateLockPOLUnlockOnUnknownBlock(t *testing.T) { validatePrevote(t, cs1, round, vss[0], firstBlockHash) // now lets add prevotes from everyone else for the new block - signAddVotes(cs1, tmproto.PrevoteType, secondBlockHash, secondBlockParts.Header(), vs2, vs3, vs4) + signAddVotes(config, cs1, tmproto.PrevoteType, secondBlockHash, secondBlockParts.Header(), vs2, vs3, vs4) ensurePrecommit(voteCh, height, round) // we should have unlocked and locked on the new block, sending a precommit for this new block @@ -882,7 +884,7 @@ func TestStateLockPOLUnlockOnUnknownBlock(t *testing.T) { } // more prevote creating a majority on the new block and this is then committed - signAddVotes(cs1, tmproto.PrecommitType, nil, types.PartSetHeader{}, vs2, vs3, vs4) + signAddVotes(config, cs1, tmproto.PrecommitType, nil, types.PartSetHeader{}, vs2, vs3, vs4) // before we timeout to the new round set the new proposal cs3 := newState(cs1.state, vs3, counter.NewApplication(true)) @@ -915,7 +917,7 @@ func TestStateLockPOLUnlockOnUnknownBlock(t *testing.T) { // we are no longer locked to the first block so we should be able to prevote validatePrevote(t, cs1, round, vss[0], thirdPropBlockHash) - signAddVotes(cs1, tmproto.PrevoteType, thirdPropBlockHash, thirdPropBlockParts.Header(), vs2, vs3, vs4) + signAddVotes(config, cs1, tmproto.PrevoteType, thirdPropBlockHash, thirdPropBlockParts.Header(), vs2, vs3, vs4) ensurePrecommit(voteCh, height, round) // we have a majority, now vs1 can change lock to the third block @@ -927,9 +929,9 @@ func TestStateLockPOLUnlockOnUnknownBlock(t *testing.T) { // then a polka at round 2 that we lock on // then we see the polka from round 1 but shouldn't unlock func TestStateLockPOLSafety1(t *testing.T) { - configSetup(t) + config := configSetup(t) - cs1, vss := randState(4) + cs1, vss := randState(config, 4) vs2, vs3, vs4 := vss[1], vss[2], vss[3] height, round := cs1.Height, cs1.Round @@ -956,12 +958,14 @@ func TestStateLockPOLSafety1(t *testing.T) { validatePrevote(t, cs1, round, vss[0], propBlock.Hash()) // the others sign a polka but we don't see it - prevotes := signVotes(tmproto.PrevoteType, propBlock.Hash(), propBlock.MakePartSet(partSize).Header(), vs2, vs3, vs4) + prevotes := signVotes(config, tmproto.PrevoteType, + propBlock.Hash(), propBlock.MakePartSet(partSize).Header(), + vs2, vs3, vs4) t.Logf("old prop hash %v", fmt.Sprintf("%X", propBlock.Hash())) // we do see them precommit nil - signAddVotes(cs1, tmproto.PrecommitType, nil, types.PartSetHeader{}, vs2, vs3, vs4) + signAddVotes(config, cs1, tmproto.PrecommitType, nil, types.PartSetHeader{}, vs2, vs3, vs4) // cs1 precommit nil ensurePrecommit(voteCh, height, round) @@ -1001,13 +1005,13 @@ func TestStateLockPOLSafety1(t *testing.T) { validatePrevote(t, cs1, round, vss[0], propBlockHash) // now we see the others prevote for it, so we should lock on it - signAddVotes(cs1, tmproto.PrevoteType, propBlockHash, propBlockParts.Header(), vs2, vs3, vs4) + signAddVotes(config, cs1, tmproto.PrevoteType, propBlockHash, propBlockParts.Header(), vs2, vs3, vs4) ensurePrecommit(voteCh, height, round) // we should have precommitted validatePrecommit(t, cs1, round, round, vss[0], propBlockHash, propBlockHash) - signAddVotes(cs1, tmproto.PrecommitType, nil, types.PartSetHeader{}, vs2, vs3, vs4) + signAddVotes(config, cs1, tmproto.PrecommitType, nil, types.PartSetHeader{}, vs2, vs3, vs4) ensureNewTimeout(timeoutWaitCh, height, round, cs1.config.Precommit(round).Nanoseconds()) @@ -1048,9 +1052,9 @@ func TestStateLockPOLSafety1(t *testing.T) { // What we want: // dont see P0, lock on P1 at R1, dont unlock using P0 at R2 func TestStateLockPOLSafety2(t *testing.T) { - configSetup(t) + config := configSetup(t) - cs1, vss := randState(4) + cs1, vss := randState(config, 4) vs2, vs3, vs4 := vss[1], vss[2], vss[3] height, round := cs1.Height, cs1.Round @@ -1073,7 +1077,7 @@ func TestStateLockPOLSafety2(t *testing.T) { propBlockID0 := types.BlockID{Hash: propBlockHash0, PartSetHeader: propBlockParts0.Header()} // the others sign a polka but we don't see it - prevotes := signVotes(tmproto.PrevoteType, propBlockHash0, propBlockParts0.Header(), vs2, vs3, vs4) + prevotes := signVotes(config, tmproto.PrevoteType, propBlockHash0, propBlockParts0.Header(), vs2, vs3, vs4) // the block for round 1 prop1, propBlock1 := decideProposal(cs1, vs2, vs2.Height, vs2.Round+1) @@ -1096,15 +1100,15 @@ func TestStateLockPOLSafety2(t *testing.T) { ensurePrevote(voteCh, height, round) validatePrevote(t, cs1, round, vss[0], propBlockHash1) - signAddVotes(cs1, tmproto.PrevoteType, propBlockHash1, propBlockParts1.Header(), vs2, vs3, vs4) + signAddVotes(config, cs1, tmproto.PrevoteType, propBlockHash1, propBlockParts1.Header(), vs2, vs3, vs4) ensurePrecommit(voteCh, height, round) // the proposed block should now be locked and our precommit added validatePrecommit(t, cs1, round, round, vss[0], propBlockHash1, propBlockHash1) // add precommits from the rest - signAddVotes(cs1, tmproto.PrecommitType, nil, types.PartSetHeader{}, vs2, vs4) - signAddVotes(cs1, tmproto.PrecommitType, propBlockHash1, propBlockParts1.Header(), vs3) + signAddVotes(config, cs1, tmproto.PrecommitType, nil, types.PartSetHeader{}, vs2, vs4) + signAddVotes(config, cs1, tmproto.PrecommitType, propBlockHash1, propBlockParts1.Header(), vs3) incrementRound(vs2, vs3, vs4) @@ -1147,9 +1151,9 @@ func TestStateLockPOLSafety2(t *testing.T) { // What we want: // P0 proposes B0 at R3. func TestProposeValidBlock(t *testing.T) { - configSetup(t) + config := configSetup(t) - cs1, vss := randState(4) + cs1, vss := randState(config, 4) vs2, vs3, vs4 := vss[1], vss[2], vss[3] height, round := cs1.Height, cs1.Round @@ -1178,13 +1182,13 @@ func TestProposeValidBlock(t *testing.T) { validatePrevote(t, cs1, round, vss[0], propBlockHash) // the others sign a polka - signAddVotes(cs1, tmproto.PrevoteType, propBlockHash, propBlock.MakePartSet(partSize).Header(), vs2, vs3, vs4) + signAddVotes(config, cs1, tmproto.PrevoteType, propBlockHash, propBlock.MakePartSet(partSize).Header(), vs2, vs3, vs4) ensurePrecommit(voteCh, height, round) // we should have precommitted validatePrecommit(t, cs1, round, round, vss[0], propBlockHash, propBlockHash) - signAddVotes(cs1, tmproto.PrecommitType, nil, types.PartSetHeader{}, vs2, vs3, vs4) + signAddVotes(config, cs1, tmproto.PrecommitType, nil, types.PartSetHeader{}, vs2, vs3, vs4) ensureNewTimeout(timeoutWaitCh, height, round, cs1.config.Precommit(round).Nanoseconds()) @@ -1201,7 +1205,7 @@ func TestProposeValidBlock(t *testing.T) { ensurePrevote(voteCh, height, round) validatePrevote(t, cs1, round, vss[0], propBlockHash) - signAddVotes(cs1, tmproto.PrevoteType, nil, types.PartSetHeader{}, vs2, vs3, vs4) + signAddVotes(config, cs1, tmproto.PrevoteType, nil, types.PartSetHeader{}, vs2, vs3, vs4) ensureNewUnlock(unlockCh, height, round) @@ -1212,7 +1216,7 @@ func TestProposeValidBlock(t *testing.T) { incrementRound(vs2, vs3, vs4) incrementRound(vs2, vs3, vs4) - signAddVotes(cs1, tmproto.PrecommitType, nil, types.PartSetHeader{}, vs2, vs3, vs4) + signAddVotes(config, cs1, tmproto.PrecommitType, nil, types.PartSetHeader{}, vs2, vs3, vs4) round += 2 // moving to the next round @@ -1239,9 +1243,9 @@ func TestProposeValidBlock(t *testing.T) { // What we want: // P0 miss to lock B but set valid block to B after receiving delayed prevote. func TestSetValidBlockOnDelayedPrevote(t *testing.T) { - configSetup(t) + config := configSetup(t) - cs1, vss := randState(4) + cs1, vss := randState(config, 4) vs2, vs3, vs4 := vss[1], vss[2], vss[3] height, round := cs1.Height, cs1.Round @@ -1270,10 +1274,10 @@ func TestSetValidBlockOnDelayedPrevote(t *testing.T) { validatePrevote(t, cs1, round, vss[0], propBlockHash) // vs2 send prevote for propBlock - signAddVotes(cs1, tmproto.PrevoteType, propBlockHash, propBlockParts.Header(), vs2) + signAddVotes(config, cs1, tmproto.PrevoteType, propBlockHash, propBlockParts.Header(), vs2) // vs3 send prevote nil - signAddVotes(cs1, tmproto.PrevoteType, nil, types.PartSetHeader{}, vs3) + signAddVotes(config, cs1, tmproto.PrevoteType, nil, types.PartSetHeader{}, vs3) ensureNewTimeout(timeoutWaitCh, height, round, cs1.config.Prevote(round).Nanoseconds()) @@ -1288,7 +1292,7 @@ func TestSetValidBlockOnDelayedPrevote(t *testing.T) { assert.True(t, rs.ValidRound == -1) // vs2 send (delayed) prevote for propBlock - signAddVotes(cs1, tmproto.PrevoteType, propBlockHash, propBlockParts.Header(), vs4) + signAddVotes(config, cs1, tmproto.PrevoteType, propBlockHash, propBlockParts.Header(), vs4) ensureNewValidBlock(validBlockCh, height, round) @@ -1303,9 +1307,9 @@ func TestSetValidBlockOnDelayedPrevote(t *testing.T) { // P0 miss to lock B as Proposal Block is missing, but set valid block to B after // receiving delayed Block Proposal. func TestSetValidBlockOnDelayedProposal(t *testing.T) { - configSetup(t) + config := configSetup(t) - cs1, vss := randState(4) + cs1, vss := randState(config, 4) vs2, vs3, vs4 := vss[1], vss[2], vss[3] height, round := cs1.Height, cs1.Round @@ -1337,7 +1341,7 @@ func TestSetValidBlockOnDelayedProposal(t *testing.T) { propBlockParts := propBlock.MakePartSet(partSize) // vs2, vs3 and vs4 send prevote for propBlock - signAddVotes(cs1, tmproto.PrevoteType, propBlockHash, propBlockParts.Header(), vs2, vs3, vs4) + signAddVotes(config, cs1, tmproto.PrevoteType, propBlockHash, propBlockParts.Header(), vs2, vs3, vs4) ensureNewValidBlock(validBlockCh, height, round) ensureNewTimeout(timeoutWaitCh, height, round, cs1.config.Prevote(round).Nanoseconds()) @@ -1361,9 +1365,9 @@ func TestSetValidBlockOnDelayedProposal(t *testing.T) { // What we want: // P0 waits for timeoutPrecommit before starting next round func TestWaitingTimeoutOnNilPolka(t *testing.T) { - configSetup(t) + config := configSetup(t) - cs1, vss := randState(4) + cs1, vss := randState(config, 4) vs2, vs3, vs4 := vss[1], vss[2], vss[3] height, round := cs1.Height, cs1.Round @@ -1374,7 +1378,7 @@ func TestWaitingTimeoutOnNilPolka(t *testing.T) { startTestRound(cs1, height, round) ensureNewRound(newRoundCh, height, round) - signAddVotes(cs1, tmproto.PrecommitType, nil, types.PartSetHeader{}, vs2, vs3, vs4) + signAddVotes(config, cs1, tmproto.PrecommitType, nil, types.PartSetHeader{}, vs2, vs3, vs4) ensureNewTimeout(timeoutWaitCh, height, round, cs1.config.Precommit(round).Nanoseconds()) ensureNewRound(newRoundCh, height, round+1) @@ -1384,9 +1388,9 @@ func TestWaitingTimeoutOnNilPolka(t *testing.T) { // What we want: // P0 waits for timeoutPropose in the next round before entering prevote func TestWaitingTimeoutProposeOnNewRound(t *testing.T) { - configSetup(t) + config := configSetup(t) - cs1, vss := randState(4) + cs1, vss := randState(config, 4) vs2, vs3, vs4 := vss[1], vss[2], vss[3] height, round := cs1.Height, cs1.Round @@ -1404,7 +1408,7 @@ func TestWaitingTimeoutProposeOnNewRound(t *testing.T) { ensurePrevote(voteCh, height, round) incrementRound(vss[1:]...) - signAddVotes(cs1, tmproto.PrevoteType, nil, types.PartSetHeader{}, vs2, vs3, vs4) + signAddVotes(config, cs1, tmproto.PrevoteType, nil, types.PartSetHeader{}, vs2, vs3, vs4) round++ // moving to the next round ensureNewRound(newRoundCh, height, round) @@ -1422,9 +1426,9 @@ func TestWaitingTimeoutProposeOnNewRound(t *testing.T) { // What we want: // P0 jump to higher round, precommit and start precommit wait func TestRoundSkipOnNilPolkaFromHigherRound(t *testing.T) { - configSetup(t) + config := configSetup(t) - cs1, vss := randState(4) + cs1, vss := randState(config, 4) vs2, vs3, vs4 := vss[1], vss[2], vss[3] height, round := cs1.Height, cs1.Round @@ -1442,7 +1446,7 @@ func TestRoundSkipOnNilPolkaFromHigherRound(t *testing.T) { ensurePrevote(voteCh, height, round) incrementRound(vss[1:]...) - signAddVotes(cs1, tmproto.PrecommitType, nil, types.PartSetHeader{}, vs2, vs3, vs4) + signAddVotes(config, cs1, tmproto.PrecommitType, nil, types.PartSetHeader{}, vs2, vs3, vs4) round++ // moving to the next round ensureNewRound(newRoundCh, height, round) @@ -1460,9 +1464,9 @@ func TestRoundSkipOnNilPolkaFromHigherRound(t *testing.T) { // What we want: // P0 wait for timeoutPropose to expire before sending prevote. func TestWaitTimeoutProposeOnNilPolkaForTheCurrentRound(t *testing.T) { - configSetup(t) + config := configSetup(t) - cs1, vss := randState(4) + cs1, vss := randState(config, 4) vs2, vs3, vs4 := vss[1], vss[2], vss[3] height, round := cs1.Height, int32(1) @@ -1478,7 +1482,7 @@ func TestWaitTimeoutProposeOnNilPolkaForTheCurrentRound(t *testing.T) { ensureNewRound(newRoundCh, height, round) incrementRound(vss[1:]...) - signAddVotes(cs1, tmproto.PrevoteType, nil, types.PartSetHeader{}, vs2, vs3, vs4) + signAddVotes(config, cs1, tmproto.PrevoteType, nil, types.PartSetHeader{}, vs2, vs3, vs4) ensureNewTimeout(timeoutProposeCh, height, round, cs1.config.Propose(round).Nanoseconds()) @@ -1489,9 +1493,9 @@ func TestWaitTimeoutProposeOnNilPolkaForTheCurrentRound(t *testing.T) { // What we want: // P0 emit NewValidBlock event upon receiving 2/3+ Precommit for B but hasn't received block B yet func TestEmitNewValidBlockEventOnCommitWithoutBlock(t *testing.T) { - configSetup(t) + config := configSetup(t) - cs1, vss := randState(4) + cs1, vss := randState(config, 4) vs2, vs3, vs4 := vss[1], vss[2], vss[3] height, round := cs1.Height, int32(1) @@ -1511,7 +1515,7 @@ func TestEmitNewValidBlockEventOnCommitWithoutBlock(t *testing.T) { ensureNewRound(newRoundCh, height, round) // vs2, vs3 and vs4 send precommit for propBlock - signAddVotes(cs1, tmproto.PrecommitType, propBlockHash, propBlockParts.Header(), vs2, vs3, vs4) + signAddVotes(config, cs1, tmproto.PrecommitType, propBlockHash, propBlockParts.Header(), vs2, vs3, vs4) ensureNewValidBlock(validBlockCh, height, round) rs := cs1.GetRoundState() @@ -1525,9 +1529,9 @@ func TestEmitNewValidBlockEventOnCommitWithoutBlock(t *testing.T) { // P0 receives 2/3+ Precommit for B for round 0, while being in round 1. It emits NewValidBlock event. // After receiving block, it executes block and moves to the next height. func TestCommitFromPreviousRound(t *testing.T) { - configSetup(t) + config := configSetup(t) - cs1, vss := randState(4) + cs1, vss := randState(config, 4) vs2, vs3, vs4 := vss[1], vss[2], vss[3] height, round := cs1.Height, int32(1) @@ -1546,7 +1550,7 @@ func TestCommitFromPreviousRound(t *testing.T) { ensureNewRound(newRoundCh, height, round) // vs2, vs3 and vs4 send precommit for propBlock for the previous round - signAddVotes(cs1, tmproto.PrecommitType, propBlockHash, propBlockParts.Header(), vs2, vs3, vs4) + signAddVotes(config, cs1, tmproto.PrecommitType, propBlockHash, propBlockParts.Header(), vs2, vs3, vs4) ensureNewValidBlock(validBlockCh, height, round) @@ -1580,10 +1584,10 @@ func (n *fakeTxNotifier) Notify() { // and third precommit arrives which leads to the commit of that header and the correct // start of the next round func TestStartNextHeightCorrectlyAfterTimeout(t *testing.T) { - configSetup(t) + config := configSetup(t) config.Consensus.SkipTimeoutCommit = false - cs1, vss := randState(4) + cs1, vss := randState(config, 4) cs1.txNotifier = &fakeTxNotifier{ch: make(chan struct{})} vs2, vs3, vs4 := vss[1], vss[2], vss[3] @@ -1612,15 +1616,15 @@ func TestStartNextHeightCorrectlyAfterTimeout(t *testing.T) { ensurePrevote(voteCh, height, round) validatePrevote(t, cs1, round, vss[0], theBlockHash) - signAddVotes(cs1, tmproto.PrevoteType, theBlockHash, theBlockParts, vs2, vs3, vs4) + signAddVotes(config, cs1, tmproto.PrevoteType, theBlockHash, theBlockParts, vs2, vs3, vs4) ensurePrecommit(voteCh, height, round) // the proposed block should now be locked and our precommit added validatePrecommit(t, cs1, round, round, vss[0], theBlockHash, theBlockHash) // add precommits - signAddVotes(cs1, tmproto.PrecommitType, nil, types.PartSetHeader{}, vs2) - signAddVotes(cs1, tmproto.PrecommitType, theBlockHash, theBlockParts, vs3) + signAddVotes(config, cs1, tmproto.PrecommitType, nil, types.PartSetHeader{}, vs2) + signAddVotes(config, cs1, tmproto.PrecommitType, theBlockHash, theBlockParts, vs3) // wait till timeout occurs ensurePrecommitTimeout(precommitTimeoutCh) @@ -1628,7 +1632,7 @@ func TestStartNextHeightCorrectlyAfterTimeout(t *testing.T) { ensureNewRound(newRoundCh, height, round+1) // majority is now reached - signAddVotes(cs1, tmproto.PrecommitType, theBlockHash, theBlockParts, vs4) + signAddVotes(config, cs1, tmproto.PrecommitType, theBlockHash, theBlockParts, vs4) ensureNewBlockHeader(newBlockHeader, height, theBlockHash) @@ -1643,10 +1647,10 @@ func TestStartNextHeightCorrectlyAfterTimeout(t *testing.T) { } func TestResetTimeoutPrecommitUponNewHeight(t *testing.T) { - configSetup(t) + config := configSetup(t) config.Consensus.SkipTimeoutCommit = false - cs1, vss := randState(4) + cs1, vss := randState(config, 4) vs2, vs3, vs4 := vss[1], vss[2], vss[3] height, round := cs1.Height, cs1.Round @@ -1674,15 +1678,15 @@ func TestResetTimeoutPrecommitUponNewHeight(t *testing.T) { ensurePrevote(voteCh, height, round) validatePrevote(t, cs1, round, vss[0], theBlockHash) - signAddVotes(cs1, tmproto.PrevoteType, theBlockHash, theBlockParts, vs2, vs3, vs4) + signAddVotes(config, cs1, tmproto.PrevoteType, theBlockHash, theBlockParts, vs2, vs3, vs4) ensurePrecommit(voteCh, height, round) validatePrecommit(t, cs1, round, round, vss[0], theBlockHash, theBlockHash) // add precommits - signAddVotes(cs1, tmproto.PrecommitType, nil, types.PartSetHeader{}, vs2) - signAddVotes(cs1, tmproto.PrecommitType, theBlockHash, theBlockParts, vs3) - signAddVotes(cs1, tmproto.PrecommitType, theBlockHash, theBlockParts, vs4) + signAddVotes(config, cs1, tmproto.PrecommitType, nil, types.PartSetHeader{}, vs2) + signAddVotes(config, cs1, tmproto.PrecommitType, theBlockHash, theBlockParts, vs3) + signAddVotes(config, cs1, tmproto.PrecommitType, theBlockHash, theBlockParts, vs4) ensureNewBlockHeader(newBlockHeader, height, theBlockHash) @@ -1787,9 +1791,9 @@ func TestStateSlashingPrecommits(t *testing.T) { // 4 vals. // we receive a final precommit after going into next round, but others might have gone to commit already! func TestStateHalt1(t *testing.T) { - configSetup(t) + config := configSetup(t) - cs1, vss := randState(4) + cs1, vss := randState(config, 4) vs2, vs3, vs4 := vss[1], vss[2], vss[3] height, round := cs1.Height, cs1.Round partSize := types.BlockPartSizeBytes @@ -1814,17 +1818,17 @@ func TestStateHalt1(t *testing.T) { ensurePrevote(voteCh, height, round) - signAddVotes(cs1, tmproto.PrevoteType, propBlock.Hash(), propBlockParts.Header(), vs2, vs3, vs4) + signAddVotes(config, cs1, tmproto.PrevoteType, propBlock.Hash(), propBlockParts.Header(), vs2, vs3, vs4) ensurePrecommit(voteCh, height, round) // the proposed block should now be locked and our precommit added validatePrecommit(t, cs1, round, round, vss[0], propBlock.Hash(), propBlock.Hash()) // add precommits from the rest - signAddVotes(cs1, tmproto.PrecommitType, nil, types.PartSetHeader{}, vs2) // didnt receive proposal - signAddVotes(cs1, tmproto.PrecommitType, propBlock.Hash(), propBlockParts.Header(), vs3) + signAddVotes(config, cs1, tmproto.PrecommitType, nil, types.PartSetHeader{}, vs2) // didnt receive proposal + signAddVotes(config, cs1, tmproto.PrecommitType, propBlock.Hash(), propBlockParts.Header(), vs3) // we receive this later, but vs3 might receive it earlier and with ours will go to commit! - precommit4 := signVote(vs4, tmproto.PrecommitType, propBlock.Hash(), propBlockParts.Header()) + precommit4 := signVote(vs4, config, tmproto.PrecommitType, propBlock.Hash(), propBlockParts.Header()) incrementRound(vs2, vs3, vs4) @@ -1856,10 +1860,10 @@ func TestStateHalt1(t *testing.T) { } func TestStateOutputsBlockPartsStats(t *testing.T) { - configSetup(t) + config := configSetup(t) // create dummy peer - cs, _ := randState(1) + cs, _ := randState(config, 1) peer := p2pmock.NewPeer(nil) // 1) new block part @@ -1901,15 +1905,15 @@ func TestStateOutputsBlockPartsStats(t *testing.T) { } func TestStateOutputVoteStats(t *testing.T) { - configSetup(t) + config := configSetup(t) - cs, vss := randState(2) + cs, vss := randState(config, 2) // create dummy peer peer := p2pmock.NewPeer(nil) randBytes := tmrand.Bytes(tmhash.Size) - vote := signVote(vss[1], tmproto.PrecommitType, randBytes, types.PartSetHeader{}) + vote := signVote(vss[1], config, tmproto.PrecommitType, randBytes, types.PartSetHeader{}) voteMessage := &VoteMessage{vote} cs.handleMsg(msgInfo{voteMessage, peer.ID()}) @@ -1923,7 +1927,7 @@ func TestStateOutputVoteStats(t *testing.T) { // sending the vote for the bigger height incrementHeight(vss[1]) - vote = signVote(vss[1], tmproto.PrecommitType, randBytes, types.PartSetHeader{}) + vote = signVote(vss[1], config, tmproto.PrecommitType, randBytes, types.PartSetHeader{}) cs.handleMsg(msgInfo{&VoteMessage{vote}, peer.ID()}) diff --git a/docs/architecture/README.md b/docs/architecture/README.md index bce77b2c7..65e344621 100644 --- a/docs/architecture/README.md +++ b/docs/architecture/README.md @@ -27,60 +27,71 @@ If recorded decisions turned out to be lacking, convene a discussion, record the Note the context/background should be written in the present tense. -### Table of Contents: +## Table of Contents -- [ADR-001-Logging](./adr-001-logging.md) -- [ADR-002-Event-Subscription](./adr-002-event-subscription.md) -- [ADR-003-ABCI-APP-RPC](./adr-003-abci-app-rpc.md) -- [ADR-004-Historical-Validators](./adr-004-historical-validators.md) -- [ADR-005-Consensus-Params](./adr-005-consensus-params.md) -- [ADR-006-Trust-Metric](./adr-006-trust-metric.md) -- [ADR-007-Trust-Metric-Usage](./adr-007-trust-metric-usage.md) -- [ADR-008-Priv-Validator](./adr-008-priv-validator.md) -- [ADR-009-ABCI-Design](./adr-009-ABCI-design.md) -- [ADR-010-Crypto-Changes](./adr-010-crypto-changes.md) -- [ADR-011-Monitoring](./adr-011-monitoring.md) -- [ADR-012-Peer-Transport](./adr-012-peer-transport.md) -- [ADR-013-Symmetric-Crypto](./adr-013-symmetric-crypto.md) -- [ADR-014-Secp-Malleability](./adr-014-secp-malleability.md) -- [ADR-015-Crypto-Encoding](./adr-015-crypto-encoding.md) -- [ADR-016-Protocol-Versions](./adr-016-protocol-versions.md) -- [ADR-017-Chain-Versions](./adr-017-chain-versions.md) -- [ADR-018-ABCI-Validators](./adr-018-ABCI-Validators.md) -- [ADR-019-Multisigs](./adr-019-multisigs.md) -- [ADR-020-Block-Size](./adr-020-block-size.md) -- [ADR-021-ABCI-Events](./adr-021-abci-events.md) -- [ADR-022-ABCI-Errors](./adr-022-abci-errors.md) -- [ADR-023-ABCI-Propose-tx](./adr-023-ABCI-propose-tx.md) -- [ADR-024-Sign-Bytes](./adr-024-sign-bytes.md) -- [ADR-025-Commit](./adr-025-commit.md) -- [ADR-026-General-Merkle-Proof](./adr-026-general-merkle-proof.md) -- [ADR-028-libp2p](./adr-028-libp2p.md) -- [ADR-029-Check-Tx-Consensus](./adr-029-check-tx-consensus.md) -- [ADR-030-Consensus-Refactor](./adr-030-consensus-refactor.md) -- [ADR-030-Changelog-structure](./adr-031-changelog.md) -- [ADR-033-Pubsub](./adr-033-pubsub.md) -- [ADR-034-Priv-Validator-File-Structure](./adr-034-priv-validator-file-structure.md) -- [ADR-035-Documentation](./adr-035-documentation.md) -- [ADR-037-Deliver-Block](./adr-037-deliver-block.md) -- [ADR-038-Non-Zero-Start-Height](./adr-038-non-zero-start-height.md) -- [ADR-039-Peer-Behaviour](./adr-039-peer-behaviour.md) -- [ADR-041-Proposer-Selection-via-ABCI](./adr-041-proposer-selection-via-abci.md) -- [ADR-043-Blockchain-RiRi-Org](./adr-043-blockchain-riri-org.md) -- [ADR-044-Lite-Client-With-Weak-Subjectivity](./adr-044-lite-client-with-weak-subjectivity.md) -- [ADR-045-ABCI-Evidence](./adr-045-abci-evidence.md) -- [ADR-046-Light-Client-Implementation](./adr-046-light-client-implementation.md) -- [ADR-047-Handling-Evidence-From-Light-Client](./adr-047-handling-evidence-from-light-client.md) -- [ADR-051-Double-Signing-Risk-Reduction](./adr-051-double-signing-risk-reduction.md) -- [ADR-052-Tendermint-Mode](./adr-052-tendermint-mode.md) -- [ADR-053-State-Sync-Prototype](./adr-053-state-sync-prototype.md) -- [ADR-054-Crypto-Encoding-2](./adr-054-crypto-encoding-2.md) -- [ADR-055-Protobuf-Design](./adr-055-protobuf-design.md) -- [ADR-056-Light-Client-Amnesia-Attacks](./adr-056-light-client-amnesia-attacks) -- [ADR-057-RPC](./adr-057-RPC.md) -- [ADR-058-Event-Hashing](./adr-058-event-hashing.md) -- [ADR-059-Evidence-Composition-and-Lifecycle](./adr-059-evidence-composition-and-lifecycle.md) -- [ADR-060-Go-API-Stability](./adr-060-go-api-stability.md) -- [ADR-061-P2P-Refactor-Scope](./adr-061-p2p-refactor-scope.md) -- [ADR-062-P2P-Architecture](./adr-062-p2p-architecture.md) -- [ADR-063-Privval-gRPC](./adr-063-privval-grpc.md) +### Accepted + +- [ADR-003: ABCI-APP-RPC](./adr-003-abci-app-rpc.md) +- [ADR-004: Historical-Validators](./adr-004-historical-validators.md) +- [ADR-006: Trust-Metric](./adr-006-trust-metric.md) +- [ADR-009: ABCI-Design](./adr-009-ABCI-design.md) +- [ADR-014: Secp-Malleability](./adr-014-secp-malleability.md) +- [ADR-015: Crypto-Encoding](./adr-015-crypto-encoding.md) +- [ADR-020: Block-Size](./adr-020-block-size.md) +- [ADR-025: Commit](./adr-025-commit.md) +- [ADR-034: Priv-Validator-File-Structure](./adr-034-priv-validator-file-structure.md) +- [ADR-039: Peer-Behaviour](./adr-039-peer-behaviour.md) +- [ADR-044: Lite-Client-With-Weak-Subjectivity](./adr-044-lite-client-with-weak-subjectivity.md) +- [ADR-046: Light-Client-Implementation](./adr-046-light-client-implementation.md) +- [ADR-047: Handling-Evidence-From-Light-Client](./adr-047-handling-evidence-from-light-client.md) +- [ADR-053: State-Sync-Prototype](./adr-053-state-sync-prototype.md) +- [ADR-055: Protobuf-Design](./adr-055-protobuf-design.md) +- [ADR-056: Light-Client-Amnesia-Attacks](./adr-056-light-client-amnesia-attacks) +- [ADR-059: Evidence-Composition-and-Lifecycle](./adr-059-evidence-composition-and-lifecycle.md) +- [ADR-060: Go-API-Stability](./adr-060-go-api-stability.md) +- [ADR-061: P2P-Refactor-Scope](./adr-061-p2p-refactor-scope.md) +- [ADR-062: P2P-Architecture](./adr-062-p2p-architecture.md) +- [ADR-065: Custom Event Indexing](./adr-065-custom-event-indexing.md) +- [ADR-066-E2E-Testing](./adr-066-e2e-testing.md) + +### Rejected + +- [ADR-029: Check-Tx-Consensus](./adr-029-check-tx-consensus.md) +- [ADR-058: Event-Hashing](./adr-058-event-hashing.md) + + +### Proposed + +- [ADR-001: Logging](./adr-001-logging.md) +- [ADR-002: Event-Subscription](./adr-002-event-subscription.md) +- [ADR-005: Consensus-Params](./adr-005-consensus-params.md) +- [ADR-007: Trust-Metric-Usage](./adr-007-trust-metric-usage.md) +- [ADR-008: Priv-Validator](./adr-008-priv-validator.md) +- [ADR-010: Crypto-Changes](./adr-010-crypto-changes.md) +- [ADR-011: Monitoring](./adr-011-monitoring.md) +- [ADR-012: Peer-Transport](./adr-012-peer-transport.md) +- [ADR-013: Symmetric-Crypto](./adr-013-symmetric-crypto.md) +- [ADR-016: Protocol-Versions](./adr-016-protocol-versions.md) +- [ADR-017: Chain-Versions](./adr-017-chain-versions.md) +- [ADR-018: ABCI-Validators](./adr-018-ABCI-Validators.md) +- [ADR-019: Multisigs](./adr-019-multisigs.md) +- [ADR-021: ABCI-Events](./adr-021-abci-events.md) +- [ADR-022: ABCI-Errors](./adr-022-abci-errors.md) +- [ADR-023: ABCI-Propose-tx](./adr-023-ABCI-propose-tx.md) +- [ADR-024: Sign-Bytes](./adr-024-sign-bytes.md) +- [ADR-026: General-Merkle-Proof](./adr-026-general-merkle-proof.md) +- [ADR-028: libp2p](./adr-028-libp2p.md) +- [ADR-030: Consensus-Refactor](./adr-030-consensus-refactor.md) +- [ADR-030: Changelog-structure](./adr-031-changelog.md) +- [ADR-033: Pubsub](./adr-033-pubsub.md) +- [ADR-035: Documentation](./adr-035-documentation.md) +- [ADR-037: Deliver-Block](./adr-037-deliver-block.md) +- [ADR-038: Non-Zero-Start-Height](./adr-038-non-zero-start-height.md) +- [ADR-041: Proposer-Selection-via-ABCI](./adr-041-proposer-selection-via-abci.md) +- [ADR-043: Blockchain-RiRi-Org](./adr-043-blockchain-riri-org.md) +- [ADR-045: ABCI-Evidence](./adr-045-abci-evidence.md) +- [ADR-051: Double-Signing-Risk-Reduction](./adr-051-double-signing-risk-reduction.md) +- [ADR-052: Tendermint-Mode](./adr-052-tendermint-mode.md) +- [ADR-054: Crypto-Encoding-2](./adr-054-crypto-encoding-2.md) +- [ADR-057: RPC](./adr-057-RPC.md) +- [ADR-063: Privval-gRPC](./adr-063-privval-grpc.md) diff --git a/docs/architecture/adr-047-handling-evidence-from-light-client.md b/docs/architecture/adr-047-handling-evidence-from-light-client.md index 4de47819a..417d59fb0 100644 --- a/docs/architecture/adr-047-handling-evidence-from-light-client.md +++ b/docs/architecture/adr-047-handling-evidence-from-light-client.md @@ -8,6 +8,7 @@ * 14-08-2020: Introduce light traces (listed now as an alternative approach) * 20-08-2020: Light client produces evidence when detected instead of passing to full node * 16-09-2020: Post-implementation revision +* 15-03-2020: Ammends for the case of a forward lunatic attack ### Glossary of Terms @@ -106,8 +107,10 @@ This is done with: ```golang func (c *Client) examineConflictingHeaderAgainstTrace( trace []*types.LightBlock, - divergentHeader *types.SignedHeader, - source provider.Provider, now time.Time) ([]*types.LightBlock, *types.LightBlock, error) + targetBlock *types.LightBlock, + source provider.Provider, + now time.Time, + ) ([]*types.LightBlock, *types.LightBlock, error) ``` which performs the following @@ -117,16 +120,21 @@ because witnesses cannot be added and removed after the client is initialized. B as a sanity check. If this fails we have to drop the witness. 2. Querying and verifying the witness's headers using bisection at the same heights of all the -intermediary headers of the primary (In the above example this is A, B, C, D, F, H). If bisection fails or the witness stops responding then -we can call the witness faulty and drop it. +intermediary headers of the primary (In the above example this is A, B, C, D, F, H). If bisection fails +or the witness stops responding then we can call the witness faulty and drop it. -3. We eventually reach a verified header by the witness which is not the same as the intermediary header (In the above example this is E). -This is the point of bifurcation (This could also be the last header). +3. We eventually reach a verified header by the witness which is not the same as the intermediary header +(In the above example this is E). This is the point of bifurcation (This could also be the last header). + +4. There is a unique case where the trace that is being examined against has blocks that have a greater +height than the targetBlock. This can occur as part of a forward lunatic attack where the primary has +provided a light block that has a height greater than the head of the chain (see Appendix B). In this +case, the light client will verify the sources blocks up to the targetBlock and return the block in the +trace that is directly after the targetBlock in height as the `ConflictingBlock` This function then returns the trace of blocks from the witness node between the common header and the -divergent header of the primary as it -is likely as seen in the example to the right below that multiple headers where required in order to -verify the divergent one. This trace will +divergent header of the primary as it is likely, as seen in the example to the right, that multiple +headers where required in order to verify the divergent one. This trace will be used later (as is also described later in this document). ![](../imgs/bifurcation-point.png) @@ -225,3 +233,22 @@ would be validators that currently still have something staked. Not only this but there was a large degree of extra computation required in storing all the currently staked validators that could possibly fall into the group of being a phantom validator. Given this, it was removed. + +## Appendix B + +A unique flavor of lunatic attack is a forward lunatic attack. This is where a malicious +node provides a header with a height greater than the height of the blockchain. Thus there +are no witnesses capable of rebutting the malicious header. Such an attack will also +require an accomplice, i.e. at least one other witness to also return the same forged header. +Although such attacks can be any arbitrary height ahead, they must still remain within the +clock drift of the light clients real time. Therefore, to detect such an attack, a light +client will wait for a time + +``` +2 * MAX_CLOCK_DRIFT + LAG +``` + +for a witness to provide the latest block it has. Given the time constraints, if the witness +is operating at the head of the blockchain, it will have a header with an earlier height but +a later timestamp. This can be used to prove that the primary has submitted a lunatic header +which violates monotonically increasing time. diff --git a/docs/architecture/adr-059-evidence-composition-and-lifecycle.md b/docs/architecture/adr-059-evidence-composition-and-lifecycle.md index 707a18bfb..5b86164b2 100644 --- a/docs/architecture/adr-059-evidence-composition-and-lifecycle.md +++ b/docs/architecture/adr-059-evidence-composition-and-lifecycle.md @@ -4,6 +4,7 @@ - 04/09/2020: Initial Draft (Unabridged) - 07/09/2020: First Version +- 13.03.21: Ammendment to accomodate forward lunatic attack ## Scope @@ -159,7 +160,7 @@ For `LightClientAttack` - Fetch the common signed header and val set from the common height and use skipping verification to verify the conflicting header -- Fetch the trusted signed header at the same height as the conflicting header and compare with the conflicting header to work out which type of attack it is and in doing so return the malicious validators. +- Fetch the trusted signed header at the same height as the conflicting header and compare with the conflicting header to work out which type of attack it is and in doing so return the malicious validators. NOTE: If the node doesn't have the signed header at the height of the conflicting header, it instead fetches the latest header it has and checks to see if it can prove the evidence based on a violation of header time. This is known as forward lunatic attack. - If equivocation, return the validators that signed for the commits of both the trusted and signed header @@ -167,7 +168,11 @@ For `LightClientAttack` - If amnesia, return no validators (since we can't know which validators are malicious). This also means that we don't currently send amnesia evidence to the application, although we will introduce more robust amnesia evidence handling in future Tendermint Core releases -- For each validator, check the look up table to make sure there already isn't evidence against this validator +- Check that the hashes of the conflicting header and the trusted header are different + +- In the case of a forward lunatic attack, where the trusted header height is less than the conflicting header height, the node checks that the time of the trusted header is later than the time of conflicting header. This proves that the conflicting header breaks monotonically increasing time. If the node doesn't have a trusted header with a later time then it is unable to validate the evidence for now. + +- Lastly, for each validator, check the look up table to make sure there already isn't evidence against this validator After verification we persist the evidence with the key `height/hash` to the pending evidence database in the evidence pool with the following format: diff --git a/docs/architecture/adr-065-custom-event-indexing.md b/docs/architecture/adr-065-custom-event-indexing.md new file mode 100644 index 000000000..863906b71 --- /dev/null +++ b/docs/architecture/adr-065-custom-event-indexing.md @@ -0,0 +1,352 @@ +# ADR 065: Custom Event Indexing + +- [ADR 065: Custom Event Indexing](#adr-065-custom-event-indexing) + - [Changelog](#changelog) + - [Status](#status) + - [Context](#context) + - [Alternative Approaches](#alternative-approaches) + - [Decision](#decision) + - [Detailed Design](#detailed-design) + - [EventSink](#eventsink) + - [Supported Sinks](#supported-sinks) + - [`KVEventSink`](#kveventsink) + - [`PSQLEventSink`](#psqleventsink) + - [Configuration](#configuration) + - [Future Improvements](#future-improvements) + - [Consequences](#consequences) + - [Positive](#positive) + - [Negative](#negative) + - [Neutral](#neutral) + - [References](#references) + +## Changelog + +- April 1, 2021: Initial Draft (@alexanderbez) + +## Status + +Accepted + +## Context + +Currently, Tendermint Core supports block and transaction event indexing through +the `tx_index.indexer` configuration. Events are captured in transactions and +are indexed via a `TxIndexer` type. Events are captured in blocks, specifically +from `BeginBlock` and `EndBlock` application responses, and are indexed via a +`BlockIndexer` type. Both of these types are managed by a single `IndexerService` +which is responsible for consuming events and sending those events off to be +indexed by the respective type. + +In addition to indexing, Tendermint Core also supports the ability to query for +both indexed transaction and block events via Tendermint's RPC layer. The ability +to query for these indexed events facilitates a great multitude of upstream client +and application capabilities, e.g. block explorers, IBC relayers, and auxiliary +data availability and indexing services. + +Currently, Tendermint only supports indexing via a `kv` indexer, which is supported +by an underlying embedded key/value store database. The `kv` indexer implements +its own indexing and query mechanisms. While the former is somewhat trivial, +providing a rich and flexible query layer is not as trivial and has caused many +issues and UX concerns for upstream clients and applications. + +The fragile nature of the proprietary `kv` query engine and the potential +performance and scaling issues that arise when a large number of consumers are +introduced, motivate the need for a more robust and flexible indexing and query +solution. + +## Alternative Approaches + +With regards to alternative approaches to a more robust solution, the only serious +contender that was considered was to transition to using [SQLite](https://www.sqlite.org/index.html). + +While the approach would work, it locks us into a specific query language and +storage layer, so in some ways it's only a bit better than our current approach. +In addition, the implementation would require the introduction of CGO into the +Tendermint Core stack, whereas right now CGO is only introduced depending on +the database used. + +## Decision + +We will adopt a similar approach to that of the Cosmos SDK's `KVStore` state +listening described in [ADR-038](https://github.com/cosmos/cosmos-sdk/blob/master/docs/architecture/adr-038-state-listening.md). + +Namely, we will perform the following: + +- Introduce a new interface, `EventSink`, that all data sinks must implement. +- Augment the existing `tx_index.indexer` configuration to now accept a series + of one or more indexer types, i.e sinks. +- Combine the current `TxIndexer` and `BlockIndexer` into a single `KVEventSink` + that implements the `EventSink` interface. +- Introduce an additional `EventSink` that is backed by [PostgreSQL](https://www.postgresql.org/). + - Implement the necessary schemas to support both block and transaction event + indexing. +- Update `IndexerService` to use a series of `EventSinks`. +- Proxy queries to the relevant sink's native query layer. +- Update all relevant RPC methods. + + +## Detailed Design + +### EventSink + +We introduce the `EventSink` interface type that all supported sinks must implement. +The interface is defined as follows: + +```go +type EventSink interface { + IndexBlockEvents(types.EventDataNewBlockHeader) error + IndexTxEvents(*abci.TxResult) error + + SearchBlockEvents(context.Context, *query.Query) ([]int64, error) + SearchTxEvents(context.Context, *query.Query) ([]*abci.TxResult, error) + + GetTxByHash([]byte) (*abci.TxResult, error) + HasBlock(int64) (bool, error) +} +``` + +The `IndexerService` will accept a list of one or more `EventSink` types. During +the `OnStart` method it will call the appropriate APIs on each `EventSink` to +index both block and transaction events. + +### Supported Sinks + +We will initially support two `EventSink` types out of the box. + +#### `KVEventSink` + +This type of `EventSink` is a combination of the `TxIndexer` and `BlockIndexer` +indexers, both of which are backed by a single embedded key/value database. + +A bulk of the existing business logic will remain the same, but the existing APIs +mapped to the new `EventSink` API. Both types will be removed in favor of a single +`KVEventSink` type. + +The `KVEventSink` will be the only `EventSink` enabled by default, so from a UX +perspective, operators should not notice a difference apart from a configuration +change. + +We omit `EventSink` implementation details as it should be fairly straightforward +to map the existing business logic to the new APIs. + +#### `PSQLEventSink` + +This type of `EventSink` indexes block and transaction events into a [PostgreSQL](https://www.postgresql.org/). +database. We define and automatically migrate the following schema when the +`IndexerService` starts. + +```sql +-- Table Definition ---------------------------------------------- + +CREATE TYPE IF NOT EXISTS block_event_type AS ENUM ('begin_block', 'end_block'); + +CREATE TABLE IF NOT EXISTS block_events ( + id SERIAL PRIMARY KEY, + key VARCHAR NOT NULL, + value VARCHAR NOT NULL, + height INTEGER NOT NULL, + type block_event_type +); + +CREATE TABLE IF NOT EXISTS tx_results { + id SERIAL PRIMARY KEY, + tx_result BYTEA NOT NULL +} + +CREATE TABLE IF NOT EXISTS tx_events ( + id SERIAL PRIMARY KEY, + key VARCHAR NOT NULL, + value VARCHAR NOT NULL, + height INTEGER NOT NULL, + hash VARCHAR NOT NULL, + FOREIGN KEY (tx_result_id) REFERENCES tx_results(id) ON DELETE CASCADE +); + +-- Indices ------------------------------------------------------- + +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 `PSQLEventSink` will implement the `EventSink` interface as follows +(some details omitted for brevity): + + +```go +func NewPSQLEventSink(connStr string) (*PSQLEventSink, error) { + db, err := sql.Open("postgres", connStr) + if err != nil { + return nil, err + } + + // ... +} + +func (es *PSQLEventSink) IndexBlockEvents(h types.EventDataNewBlockHeader) error { + sqlStmt := sq.Insert("block_events").Columns("key", "value", "height", "type") + + // index the reserved block height index + sqlStmt = sqlStmt.Values(types.BlockHeightKey, h.Header.Height, h.Header.Height, "") + + 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) + } + } + } + + // index end_block events... + // execute sqlStmt db query... +} + +func (es *PSQLEventSink) IndexTxEvents(txr *abci.TxResult) error { + sqlStmtEvents := sq.Insert("tx_events").Columns("key", "value", "height", "hash", "tx_result_id") + sqlStmtTxResult := sq.Insert("tx_results").Columns("tx_result") + + + // store the tx result + txBz, err := proto.Marshal(txr) + if err != nil { + return err + } + + sqlStmtTxResult = sqlStmtTxResult.Values(txBz) + + // execute sqlStmtTxResult db query... + + // index the reserved height and hash indices + hash := types.Tx(txr.Tx).Hash() + sqlStmtEvents = sqlStmtEvents.Values(types.TxHashKey, hash, txr.Height, hash, txrID) + sqlStmtEvents = sqlStmtEvents.Values(types.TxHeightKey, txr.Height, txr.Height, hash, txrID) + + 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), txr.Height, hash, txrID) + } + } + } + + // execute sqlStmtEvents db query... +} + +func (es *PSQLEventSink) SearchBlockEvents(ctx context.Context, q *query.Query) ([]int64, error) { + sqlStmt = sq.Select("height").Distinct().From("block_events").Where(q.String()) + + // execute sqlStmt db query and scan into integer rows... +} + +func (es *PSQLEventSink) SearchTxEvents(ctx context.Context, q *query.Query) ([]*abci.TxResult, error) { + sqlStmt = sq.Select("tx_result_id").Distinct().From("tx_events").Where(q.String()) + + // execute sqlStmt db query and scan into integer rows... + // query tx_results records and scan into binary slice rows... + // decode each row into a TxResult... +} +``` + +### Configuration + +The current `tx_index.indexer` configuration would be changed to accept a list +of supported `EventSink` types instead of a single value. + +Example: + +```toml +[tx_index] + +indexer = [ + "kv", + "psql" +] +``` + +If the `indexer` list contains the `null` indexer, then no indexers will be used +regardless of what other values may exist. + +Additional configuration parameters might be required depending on what event +sinks are supplied to `tx_index.indexer`. The `psql` will require an additional +connection configuration. + +```toml +[tx_index] + +indexer = [ + "kv", + "psql" +] + +pqsql_conn = "postgresql://:@:/?" +``` + +Any invalid or misconfigured `tx_index` configuration should yield an error as +early as possible. + +## Future Improvements + +Although not technically required to maintain feature parity with the current +existing Tendermint indexer, it would be beneficial for operators to have a method +of performing a "re-index". Specifically, Tendermint operators could invoke an +RPC method that allows the Tendermint node to perform a re-indexing of all block +and transaction events between two given heights, H1 and H2, +so long as the block store contains the blocks and transaction results for all +the heights specified in a given range. + +## Consequences + +### Positive + +- A more robust and flexible indexing and query engine for indexing and search + block and transaction events. +- The ability to not have to support a custom indexing and query engine beyond + the legacy `kv` type. +- The ability to offload/proxy indexing and querying to the underling sink. +- Scalability and reliability that essentially comes "for free" from the underlying + sink, if it supports it. + +### Negative + +- The need to support multiple and potentially a growing set of custom `EventSink` + types. + +### Neutral + +## References + +- [Cosmos SDK ADR-038](https://github.com/cosmos/cosmos-sdk/blob/master/docs/architecture/adr-038-state-listening.md) +- [PostgreSQL](https://www.postgresql.org/) +- [SQLite](https://www.sqlite.org/index.html) diff --git a/docs/rfc/rfc-001-end-to-end-testing.md b/docs/architecture/adr-066-e2e-testing.md similarity index 94% rename from docs/rfc/rfc-001-end-to-end-testing.md rename to docs/architecture/adr-066-e2e-testing.md index b9fca7be4..054cce68f 100644 --- a/docs/rfc/rfc-001-end-to-end-testing.md +++ b/docs/architecture/adr-066-e2e-testing.md @@ -1,10 +1,10 @@ -# RFC 001: End-to-End Testing +# ADR 66: End-to-End Testing ## Changelog - 2020-09-07: Initial draft (@erikgrinaker) - - 2020-09-08: Minor improvements (@erikgrinaker) +- 2021-04-12: Renamed from RFC 001 (@tessr) ## Authors @@ -14,9 +14,9 @@ The current set of end-to-end tests under `test/` are very limited, mostly focusing on P2P testing in a standard configuration. They do not test various configurations (e.g. fast sync reactor versions, state sync, block pruning, genesis vs InitChain setup), nor do they test various network topologies (e.g. sentry node architecture). This leads to poor test coverage, which has caused several serious bugs to go unnoticed. -We need an end-to-end test suite that can run a large number of combinations of configuration options, genesis settings, network topologies, ABCI interactions, and failure scenarios and check that the network is still functional. This RFC outlines the basic requirements and design considerations, but does not propose a specific implementation - a later ADR will be submitted for this. +We need an end-to-end test suite that can run a large number of combinations of configuration options, genesis settings, network topologies, ABCI interactions, and failure scenarios and check that the network is still functional. This ADR outlines the basic requirements and design for such a system. -This RFC will not cover comprehensive chaos testing, only a few simple scenarios (e.g. abrupt process termination and network partitioning). Chaos testing of the core consensus algorithm should be implemented e.g. via Jepsen tests or a similar framework, or alternatively be added to these end-to-end tests at a later time. Similarly, malicious or adversarial behavior is out of scope for the first implementation, but may be added later. +This ADR will not cover comprehensive chaos testing, only a few simple scenarios (e.g. abrupt process termination and network partitioning). Chaos testing of the core consensus algorithm should be implemented e.g. via Jepsen tests or a similar framework, or alternatively be added to these end-to-end tests at a later time. Similarly, malicious or adversarial behavior is out of scope for the first implementation, but may be added later. ## Proposal @@ -34,11 +34,12 @@ The following lists the functionality we would like to test: #### Node/App Configurations - **Database:** goleveldb, cleveldb, boltdb, rocksdb, badgerdb -- **Fast sync:** disabled, v0, v1, v2 +- **Fast sync:** disabled, v0, v2 - **State sync:** disabled, enabled - **Block pruning:** none, keep 20, keep 1, keep random - **Role:** validator, full node - **App persistence:** enabled, disabled +- **Node modes:** validator, full, light, seed #### Geneses @@ -50,6 +51,7 @@ The following lists the functionality we would like to test: - **Recovery:** stop/start, power cycling, validator outage, network partition, total network loss - **Validators:** add, remove, change power +- **Evidence:** injection of DuplicateVoteEvidence and LightClientAttackEvidence ### Functional Combinations @@ -73,7 +75,7 @@ Given a test configuration, the test runner has the following stages: - **Setup:** configures the Docker containers and networks, but does not start them. -- **Initialization:** starts the Docker containers, performs fast sync/state sync. +- **Initialization:** starts the Docker containers, performs fast sync/state sync. Accomodates for different start heights. - **Perturbation:** adds/removes validators, restarts nodes, perturbs networking, etc - liveness and readiness checked between each operation. diff --git a/docs/architecture/adr-template.md b/docs/architecture/adr-template.md index c7b1e542a..a4c583374 100644 --- a/docs/architecture/adr-template.md +++ b/docs/architecture/adr-template.md @@ -4,22 +4,37 @@ - {date}: {changelog} +## Status + +> A decision may be "proposed" if it hasn't been agreed upon yet, or "accepted" +> once it is agreed upon. Once the ADR has been implemented mark the ADR as +> "implemented". If a later ADR changes or reverses a decision, it may be marked +> as "deprecated" or "superseded" with a reference to its replacement. + +{Deprecated|Proposed|Accepted|Declined} + ## Context -> This section contains all the context one needs to understand the current state, and why there is a problem. It should be as succinct as possible and introduce the high level idea behind the solution. +> This section contains all the context one needs to understand the current state, +> and why there is a problem. It should be as succinct as possible and introduce +> the high level idea behind the solution. ## Alternative Approaches -> This section contains information around alternative options that are considered before making a decision. It should contain a explanation on why the alternative approach(es) were not chosen. +> This section contains information around alternative options that are considered +> before making a decision. It should contain a explanation on why the alternative +> approach(es) were not chosen. ## Decision > This section records the decision that was made. -> It is best to record as much info as possible from the discussion that happened. This aids in not having to go back to the Pull Request to get the needed information. +> It is best to record as much info as possible from the discussion that happened. +> This aids in not having to go back to the Pull Request to get the needed information. ## Detailed Design -> This section does not need to be filled in at the start of the ADR, but must be completed prior to the merging of the implementation. +> This section does not need to be filled in at the start of the ADR, but must +> be completed prior to the merging of the implementation. > > Here are some common questions that get answered as part of the detailed design: > @@ -49,15 +64,10 @@ > > - Does this change require coordination with the SDK or other? -## Status - -> A decision may be "proposed" if it hasn't been agreed upon yet, or "accepted" once it is agreed upon. Once the ADR has been implemented mark the ADR as "implemented". If a later ADR changes or reverses a decision, it may be marked as "deprecated" or "superseded" with a reference to its replacement. - -{Deprecated|Proposed|Accepted|Declined} - ## Consequences -> This section describes the consequences, after applying the decision. All consequences should be summarized here, not just the "positive" ones. +> This section describes the consequences, after applying the decision. All +> consequences should be summarized here, not just the "positive" ones. ### Positive @@ -67,6 +77,7 @@ ## References -> Are there any relevant PR comments, issues that led up to this, or articles referenced for why we made the given design choice? If so link them here! +> Are there any relevant PR comments, issues that led up to this, or articles +> referenced for why we made the given design choice? If so link them here! - {reference link} diff --git a/docs/package-lock.json b/docs/package-lock.json index cff643812..b1ec64442 100644 --- a/docs/package-lock.json +++ b/docs/package-lock.json @@ -128,29 +128,28 @@ } }, "@babel/compat-data": { - "version": "7.13.12", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.13.12.tgz", - "integrity": "sha512-3eJJ841uKxeV8dcN/2yGEUy+RfgQspPEgQat85umsE1rotuquQ2AbIub4S6j7c50a2d+4myc+zSlnXeIHrOnhQ==" + "version": "7.13.15", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.13.15.tgz", + "integrity": "sha512-ltnibHKR1VnrU4ymHyQ/CXtNXI6yZC0oJThyW78Hft8XndANwi+9H+UIklBDraIjFEJzw8wmcM427oDd9KS5wA==" }, "@babel/core": { - "version": "7.13.10", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.13.10.tgz", - "integrity": "sha512-bfIYcT0BdKeAZrovpMqX2Mx5NrgAckGbwT982AkdS5GNfn3KMGiprlBAtmBcFZRUmpaufS6WZFP8trvx8ptFDw==", + "version": "7.13.15", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.13.15.tgz", + "integrity": "sha512-6GXmNYeNjS2Uz+uls5jalOemgIhnTMeaXo+yBUA72kC2uX/8VW6XyhVIo2L8/q0goKQA3EVKx0KOQpVKSeWadQ==", "requires": { "@babel/code-frame": "^7.12.13", "@babel/generator": "^7.13.9", - "@babel/helper-compilation-targets": "^7.13.10", - "@babel/helper-module-transforms": "^7.13.0", + "@babel/helper-compilation-targets": "^7.13.13", + "@babel/helper-module-transforms": "^7.13.14", "@babel/helpers": "^7.13.10", - "@babel/parser": "^7.13.10", + "@babel/parser": "^7.13.15", "@babel/template": "^7.12.13", - "@babel/traverse": "^7.13.0", - "@babel/types": "^7.13.0", + "@babel/traverse": "^7.13.15", + "@babel/types": "^7.13.14", "convert-source-map": "^1.7.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", "json5": "^2.1.2", - "lodash": "^4.17.19", "semver": "^6.3.0", "source-map": "^0.5.0" }, @@ -218,11 +217,11 @@ } }, "@babel/helper-compilation-targets": { - "version": "7.13.10", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.13.10.tgz", - "integrity": "sha512-/Xju7Qg1GQO4mHZ/Kcs6Au7gfafgZnwm+a7sy/ow/tV1sHeraRUHbjdat8/UvDor4Tez+siGKDk6zIKtCPKVJA==", + "version": "7.13.13", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.13.13.tgz", + "integrity": "sha512-q1kcdHNZehBwD9jYPh3WyXcsFERi39X4I59I3NadciWtNDyZ6x+GboOxncFK0kXlKIv6BJm5acncehXWUjWQMQ==", "requires": { - "@babel/compat-data": "^7.13.8", + "@babel/compat-data": "^7.13.12", "@babel/helper-validator-option": "^7.12.17", "browserslist": "^4.14.5", "semver": "^6.3.0" @@ -250,9 +249,9 @@ } }, "@babel/helper-define-polyfill-provider": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.1.5.tgz", - "integrity": "sha512-nXuzCSwlJ/WKr8qxzW816gwyT6VZgiJG17zR40fou70yfAcqjoNyTLl/DQ+FExw5Hx5KNqshmN8Ldl/r2N7cTg==", + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.2.0.tgz", + "integrity": "sha512-JT8tHuFjKBo8NnaUbblz7mIu1nnvUDiHVjXXkulZULyidvo/7P6TY7+YqpV37IfF+KUFxmlK04elKtGKXaiVgw==", "requires": { "@babel/helper-compilation-targets": "^7.13.0", "@babel/helper-module-imports": "^7.12.13", @@ -331,9 +330,9 @@ } }, "@babel/helper-module-transforms": { - "version": "7.13.12", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.13.12.tgz", - "integrity": "sha512-7zVQqMO3V+K4JOOj40kxiCrMf6xlQAkewBB0eu2b03OO/Q21ZutOzjpfD79A5gtE/2OWi1nv625MrDlGlkbknQ==", + "version": "7.13.14", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.13.14.tgz", + "integrity": "sha512-QuU/OJ0iAOSIatyVZmfqB0lbkVP0kDRiKj34xy+QNsnVZi/PA6BoSoreeqnxxa9EHFAIL0R9XOaAR/G9WlIy5g==", "requires": { "@babel/helper-module-imports": "^7.13.12", "@babel/helper-replace-supers": "^7.13.12", @@ -341,8 +340,8 @@ "@babel/helper-split-export-declaration": "^7.12.13", "@babel/helper-validator-identifier": "^7.12.11", "@babel/template": "^7.12.13", - "@babel/traverse": "^7.13.0", - "@babel/types": "^7.13.12" + "@babel/traverse": "^7.13.13", + "@babel/types": "^7.13.14" } }, "@babel/helper-optimise-call-expression": { @@ -445,9 +444,9 @@ } }, "@babel/parser": { - "version": "7.13.12", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.13.12.tgz", - "integrity": "sha512-4T7Pb244rxH24yR116LAuJ+adxXXnHhZaLJjegJVKSdoNCe4x1eDBaud5YIcQFcqzsaD5BHvJw5BQ0AZapdCRw==" + "version": "7.13.15", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.13.15.tgz", + "integrity": "sha512-b9COtcAlVEQljy/9fbcMHpG+UIW9ReF+gpaxDHTlZd0c6/UU9ng8zdySAW9sRTzpvcdCHn6bUcbuYUgGzLAWVQ==" }, "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": { "version": "7.13.12", @@ -460,9 +459,9 @@ } }, "@babel/plugin-proposal-async-generator-functions": { - "version": "7.13.8", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.13.8.tgz", - "integrity": "sha512-rPBnhj+WgoSmgq+4gQUtXx/vOcU+UYtjy1AA/aeD61Hwj410fwYyqfUcRP3lR8ucgliVJL/G7sXcNUecC75IXA==", + "version": "7.13.15", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.13.15.tgz", + "integrity": "sha512-VapibkWzFeoa6ubXy/NgV5U2U4MVnUlvnx6wo1XhlsaTrLYWE0UFpDQsVrmn22q5CzeloqJ8gEMHSKxuee6ZdA==", "requires": { "@babel/helper-plugin-utils": "^7.13.0", "@babel/helper-remap-async-to-generator": "^7.13.0", @@ -479,11 +478,11 @@ } }, "@babel/plugin-proposal-decorators": { - "version": "7.13.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-decorators/-/plugin-proposal-decorators-7.13.5.tgz", - "integrity": "sha512-i0GDfVNuoapwiheevUOuSW67mInqJ8qw7uWfpjNVeHMn143kXblEy/bmL9AdZ/0yf/4BMQeWXezK0tQIvNPqag==", + "version": "7.13.15", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-decorators/-/plugin-proposal-decorators-7.13.15.tgz", + "integrity": "sha512-ibAMAqUm97yzi+LPgdr5Nqb9CMkeieGHvwPg1ywSGjZrZHQEGqE01HmOio8kxRpA/+VtOHouIVy2FMpBbtltjA==", "requires": { - "@babel/helper-create-class-features-plugin": "^7.13.0", + "@babel/helper-create-class-features-plugin": "^7.13.11", "@babel/helper-plugin-utils": "^7.13.0", "@babel/plugin-syntax-decorators": "^7.12.13" } @@ -910,9 +909,9 @@ } }, "@babel/plugin-transform-regenerator": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.12.13.tgz", - "integrity": "sha512-lxb2ZAvSLyJ2PEe47hoGWPmW22v7CtSl9jW8mingV4H2sEX/JOcrAj2nPuGWi56ERUm2bUpjKzONAuT6HCn2EA==", + "version": "7.13.15", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.13.15.tgz", + "integrity": "sha512-Bk9cOLSz8DiurcMETZ8E2YtIVJbFCPGW28DJWUakmyVWtQSm6Wsf0p4B4BfEr/eL2Nkhe/CICiUiMOCi1TPhuQ==", "requires": { "regenerator-transform": "^0.14.2" } @@ -926,15 +925,15 @@ } }, "@babel/plugin-transform-runtime": { - "version": "7.13.10", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.13.10.tgz", - "integrity": "sha512-Y5k8ipgfvz5d/76tx7JYbKQTcgFSU6VgJ3kKQv4zGTKr+a9T/KBvfRvGtSFgKDQGt/DBykQixV0vNWKIdzWErA==", + "version": "7.13.15", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.13.15.tgz", + "integrity": "sha512-d+ezl76gx6Jal08XngJUkXM4lFXK/5Ikl9Mh4HKDxSfGJXmZ9xG64XT2oivBzfxb/eQ62VfvoMkaCZUKJMVrBA==", "requires": { - "@babel/helper-module-imports": "^7.12.13", + "@babel/helper-module-imports": "^7.13.12", "@babel/helper-plugin-utils": "^7.13.0", - "babel-plugin-polyfill-corejs2": "^0.1.4", - "babel-plugin-polyfill-corejs3": "^0.1.3", - "babel-plugin-polyfill-regenerator": "^0.1.2", + "babel-plugin-polyfill-corejs2": "^0.2.0", + "babel-plugin-polyfill-corejs3": "^0.2.0", + "babel-plugin-polyfill-regenerator": "^0.2.0", "semver": "^6.3.0" } }, @@ -997,16 +996,16 @@ } }, "@babel/preset-env": { - "version": "7.13.12", - "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.13.12.tgz", - "integrity": "sha512-JzElc6jk3Ko6zuZgBtjOd01pf9yYDEIH8BcqVuYIuOkzOwDesoa/Nz4gIo4lBG6K861KTV9TvIgmFuT6ytOaAA==", + "version": "7.13.15", + "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.13.15.tgz", + "integrity": "sha512-D4JAPMXcxk69PKe81jRJ21/fP/uYdcTZ3hJDF5QX2HSI9bBxxYw/dumdR6dGumhjxlprHPE4XWoPaqzZUVy2MA==", "requires": { - "@babel/compat-data": "^7.13.12", - "@babel/helper-compilation-targets": "^7.13.10", + "@babel/compat-data": "^7.13.15", + "@babel/helper-compilation-targets": "^7.13.13", "@babel/helper-plugin-utils": "^7.13.0", "@babel/helper-validator-option": "^7.12.17", "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.13.12", - "@babel/plugin-proposal-async-generator-functions": "^7.13.8", + "@babel/plugin-proposal-async-generator-functions": "^7.13.15", "@babel/plugin-proposal-class-properties": "^7.13.0", "@babel/plugin-proposal-dynamic-import": "^7.13.8", "@babel/plugin-proposal-export-namespace-from": "^7.12.13", @@ -1054,7 +1053,7 @@ "@babel/plugin-transform-object-super": "^7.12.13", "@babel/plugin-transform-parameters": "^7.13.0", "@babel/plugin-transform-property-literals": "^7.12.13", - "@babel/plugin-transform-regenerator": "^7.12.13", + "@babel/plugin-transform-regenerator": "^7.13.15", "@babel/plugin-transform-reserved-words": "^7.12.13", "@babel/plugin-transform-shorthand-properties": "^7.12.13", "@babel/plugin-transform-spread": "^7.13.0", @@ -1064,10 +1063,10 @@ "@babel/plugin-transform-unicode-escapes": "^7.12.13", "@babel/plugin-transform-unicode-regex": "^7.12.13", "@babel/preset-modules": "^0.1.4", - "@babel/types": "^7.13.12", - "babel-plugin-polyfill-corejs2": "^0.1.4", - "babel-plugin-polyfill-corejs3": "^0.1.3", - "babel-plugin-polyfill-regenerator": "^0.1.2", + "@babel/types": "^7.13.14", + "babel-plugin-polyfill-corejs2": "^0.2.0", + "babel-plugin-polyfill-corejs3": "^0.2.0", + "babel-plugin-polyfill-regenerator": "^0.2.0", "core-js-compat": "^3.9.0", "semver": "^6.3.0" } @@ -1103,19 +1102,18 @@ } }, "@babel/traverse": { - "version": "7.13.0", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.13.0.tgz", - "integrity": "sha512-xys5xi5JEhzC3RzEmSGrs/b3pJW/o87SypZ+G/PhaE7uqVQNv/jlmVIBXuoh5atqQ434LfXV+sf23Oxj0bchJQ==", + "version": "7.13.15", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.13.15.tgz", + "integrity": "sha512-/mpZMNvj6bce59Qzl09fHEs8Bt8NnpEDQYleHUPZQ3wXUMvXi+HJPLars68oAbmp839fGoOkv2pSL2z9ajCIaQ==", "requires": { "@babel/code-frame": "^7.12.13", - "@babel/generator": "^7.13.0", + "@babel/generator": "^7.13.9", "@babel/helper-function-name": "^7.12.13", "@babel/helper-split-export-declaration": "^7.12.13", - "@babel/parser": "^7.13.0", - "@babel/types": "^7.13.0", + "@babel/parser": "^7.13.15", + "@babel/types": "^7.13.14", "debug": "^4.1.0", - "globals": "^11.1.0", - "lodash": "^4.17.19" + "globals": "^11.1.0" }, "dependencies": { "debug": { @@ -1134,9 +1132,9 @@ } }, "@babel/types": { - "version": "7.13.12", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.13.12.tgz", - "integrity": "sha512-K4nY2xFN4QMvQwkQ+zmBDp6ANMbVNw6BbxWmYA4qNjhR9W+Lj/8ky5MEY2Me5r+B2c6/v6F53oMndG+f9s3IiA==", + "version": "7.13.14", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.13.14.tgz", + "integrity": "sha512-A2aa3QTkWoyqsZZFl56MLUsfmh7O0gN41IPvXAE/++8ojpbz12SszD7JEGYVdn4f9Kt4amIei07swF1h4AqmmQ==", "requires": { "@babel/helper-validator-identifier": "^7.12.11", "lodash": "^4.17.19", @@ -1231,14 +1229,14 @@ "integrity": "sha512-cxWFQVseBm6O9Gbw1IWb8r6OS4OhSt3hPZLkFApLjM8TEXROBuQGLAH2i2gZpcXdLBIrpXuTDhH7Vbm1iXmNGA==" }, "@types/minimatch": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.3.tgz", - "integrity": "sha512-tHq6qdbT9U1IRSGf14CL0pUlULksvY9OZ+5eEgl1N7t+OA3tGvNpxJCzuKQlsNgCVwbAs670L1vcVQi8j9HjnA==" + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-1z8k4wzFnNjVK/tlxvrWuK5WMt6mydWWP7+zvH5eFep4oj+UkrfiJTRtjCeBXNpwaA/FYqqtb4/QS4ianFpIRA==" }, "@types/node": { - "version": "14.14.35", - "resolved": "https://registry.npmjs.org/@types/node/-/node-14.14.35.tgz", - "integrity": "sha512-Lt+wj8NVPx0zUmUwumiVXapmaLUcAk3yPuHCFVXras9k5VT9TdhJqKqGVUQCD60OTMCl0qxJ57OiTL0Mic3Iag==" + "version": "14.14.37", + "resolved": "https://registry.npmjs.org/@types/node/-/node-14.14.37.tgz", + "integrity": "sha512-XYmBiy+ohOR4Lh5jE379fV2IU+6Jn4g5qASinhitfyO71b/sCo6MKsMLF5tc7Zf2CE8hViVQyYSobJNke8OvUw==" }, "@types/q": { "version": "1.5.4", @@ -1256,9 +1254,9 @@ "integrity": "sha512-hz4R8tS5jMn8lDq6iD+yWL6XNB699pGIVLk7WSJnn1dbpjaazsjZQkieJoRX6gW5zpYSCFqQ7jUquPNY65tQYA==" }, "@vue/babel-plugin-jsx": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@vue/babel-plugin-jsx/-/babel-plugin-jsx-1.0.3.tgz", - "integrity": "sha512-+52ZQFmrM0yh61dQlgwQlfHZXmYbswbQEL25SOSt9QkjegAdfIGu87oELw0l8H6cuJYazZCiNjPR9eU++ZIbxg==", + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@vue/babel-plugin-jsx/-/babel-plugin-jsx-1.0.4.tgz", + "integrity": "sha512-Vu5gsabUdsiWc4vQarg46xWJGs8pMEJyyMQAKA1vO+F4+aR4/jaxWxPCOvZ7XvVyy+ecSbwQp/qIyDVje360UQ==", "requires": { "@babel/helper-module-imports": "^7.0.0", "@babel/plugin-syntax-jsx": "^7.0.0", @@ -1854,11 +1852,11 @@ "integrity": "sha512-hHUXGagefjN2iRrID63xckIvotOXOojhQKWIPUZ4mNUZ9nLZW+7FMNoE1lOkEhNWYsx/7ysGIuJYCiMAA9FnrA==" }, "ansi-escapes": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.1.tgz", - "integrity": "sha512-JWF7ocqNrp8u9oqpgV+wH5ftbt+cfvv+PTjOvKLT3AdYly/LmORARfEVT1iyjwN+4MqE5UmVKoAdIBqeoCHgLA==", + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", + "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", "requires": { - "type-fest": "^0.11.0" + "type-fest": "^0.21.3" } }, "ansi-html": { @@ -2114,30 +2112,30 @@ } }, "babel-plugin-polyfill-corejs2": { - "version": "0.1.10", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.1.10.tgz", - "integrity": "sha512-DO95wD4g0A8KRaHKi0D51NdGXzvpqVLnLu5BTvDlpqUEpTmeEtypgC1xqesORaWmiUOQI14UHKlzNd9iZ2G3ZA==", + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.2.0.tgz", + "integrity": "sha512-9bNwiR0dS881c5SHnzCmmGlMkJLl0OUZvxrxHo9w/iNoRuqaPjqlvBf4HrovXtQs/au5yKkpcdgfT1cC5PAZwg==", "requires": { - "@babel/compat-data": "^7.13.0", - "@babel/helper-define-polyfill-provider": "^0.1.5", + "@babel/compat-data": "^7.13.11", + "@babel/helper-define-polyfill-provider": "^0.2.0", "semver": "^6.1.1" } }, "babel-plugin-polyfill-corejs3": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.1.7.tgz", - "integrity": "sha512-u+gbS9bbPhZWEeyy1oR/YaaSpod/KDT07arZHb80aTpl8H5ZBq+uN1nN9/xtX7jQyfLdPfoqI4Rue/MQSWJquw==", + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.2.0.tgz", + "integrity": "sha512-zZyi7p3BCUyzNxLx8KV61zTINkkV65zVkDAFNZmrTCRVhjo1jAS+YLvDJ9Jgd/w2tsAviCwFHReYfxO3Iql8Yg==", "requires": { - "@babel/helper-define-polyfill-provider": "^0.1.5", - "core-js-compat": "^3.8.1" + "@babel/helper-define-polyfill-provider": "^0.2.0", + "core-js-compat": "^3.9.1" } }, "babel-plugin-polyfill-regenerator": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.1.6.tgz", - "integrity": "sha512-OUrYG9iKPKz8NxswXbRAdSwF0GhRdIEMTloQATJi4bDuFqrXaXcCUT/VGNrr8pBcjMh1RxZ7Xt9cytVJTJfvMg==", + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.2.0.tgz", + "integrity": "sha512-J7vKbCuD2Xi/eEHxquHN14bXAW9CXtecwuLrOIDJtcZzTaPzV1VdEfoUf9AzcRBMolKUQKM9/GVojeh0hFiqMg==", "requires": { - "@babel/helper-define-polyfill-provider": "^0.1.5" + "@babel/helper-define-polyfill-provider": "^0.2.0" } }, "babel-walk": { @@ -2149,9 +2147,9 @@ } }, "balanced-match": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", - "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" }, "base": { "version": "0.11.2", @@ -2519,15 +2517,15 @@ } }, "browserslist": { - "version": "4.16.3", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.16.3.tgz", - "integrity": "sha512-vIyhWmIkULaq04Gt93txdh+j02yX/JzlyhLYbV3YQCn/zvES3JnY7TifHHvvr1w5hTDluNKMkV05cs4vy8Q7sw==", + "version": "4.16.4", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.16.4.tgz", + "integrity": "sha512-d7rCxYV8I9kj41RH8UKYnvDYCRENUlHRgyXy/Rhr/1BaeLGfiCptEdFE8MIrvGfWbBFNjVYx76SQWvNX1j+/cQ==", "requires": { - "caniuse-lite": "^1.0.30001181", - "colorette": "^1.2.1", - "electron-to-chromium": "^1.3.649", + "caniuse-lite": "^1.0.30001208", + "colorette": "^1.2.2", + "electron-to-chromium": "^1.3.712", "escalade": "^3.1.1", - "node-releases": "^1.1.70" + "node-releases": "^1.1.71" } }, "buffer": { @@ -2813,9 +2811,9 @@ } }, "caniuse-lite": { - "version": "1.0.30001204", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001204.tgz", - "integrity": "sha512-JUdjWpcxfJ9IPamy2f5JaRDCaqJOxDzOSKtbdx4rH9VivMd1vIzoPumsJa9LoMIi4Fx2BV2KZOxWhNkBjaYivQ==" + "version": "1.0.30001208", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001208.tgz", + "integrity": "sha512-OE5UE4+nBOro8Dyvv0lfx+SRtfVIOM9uhKqFmJeUbGriqhhStgp1A0OyBpgy3OUF8AhYCT+PVwPC1gMl2ZcQMA==" }, "caseless": { "version": "0.12.0", @@ -2841,29 +2839,28 @@ } }, "cheerio": { - "version": "1.0.0-rc.5", - "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-1.0.0-rc.5.tgz", - "integrity": "sha512-yoqps/VCaZgN4pfXtenwHROTp8NG6/Hlt4Jpz2FEP0ZJQ+ZUkVDd0hAPDNKhj3nakpfPt/CNs57yEtxD1bXQiw==", + "version": "1.0.0-rc.6", + "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-1.0.0-rc.6.tgz", + "integrity": "sha512-hjx1XE1M/D5pAtMgvWwE21QClmAEeGHOIDfycgmndisdNgI6PE1cGRQkMGBcsbUbmEQyWu5PJLUcAOjtQS8DWw==", "requires": { - "cheerio-select-tmp": "^0.1.0", - "dom-serializer": "~1.2.0", - "domhandler": "^4.0.0", - "entities": "~2.1.0", - "htmlparser2": "^6.0.0", - "parse5": "^6.0.0", - "parse5-htmlparser2-tree-adapter": "^6.0.0" + "cheerio-select": "^1.3.0", + "dom-serializer": "^1.3.1", + "domhandler": "^4.1.0", + "htmlparser2": "^6.1.0", + "parse5": "^6.0.1", + "parse5-htmlparser2-tree-adapter": "^6.0.1" } }, - "cheerio-select-tmp": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/cheerio-select-tmp/-/cheerio-select-tmp-0.1.1.tgz", - "integrity": "sha512-YYs5JvbpU19VYJyj+F7oYrIE2BOll1/hRU7rEy/5+v9BzkSo3bK81iAeeQEMI92vRIxz677m72UmJUiVwwgjfQ==", + "cheerio-select": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/cheerio-select/-/cheerio-select-1.3.0.tgz", + "integrity": "sha512-mLgqdHxVOQyhOIkG5QnRkDg7h817Dkf0dAvlCio2TJMmR72cJKH0bF28SHXvLkVrGcGOiub0/Bs/CMnPeQO7qw==", "requires": { - "css-select": "^3.1.2", - "css-what": "^4.0.0", - "domelementtype": "^2.1.0", - "domhandler": "^4.0.0", - "domutils": "^2.4.4" + "css-select": "^4.0.0", + "css-what": "^5.0.0", + "domelementtype": "^2.2.0", + "domhandler": "^4.1.0", + "domutils": "^2.5.2" } }, "chokidar": { @@ -2891,12 +2888,9 @@ "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==" }, "chrome-trace-event": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.2.tgz", - "integrity": "sha512-9e/zx1jw7B4CO+c/RXoCsfg/x1AfUBioy4owYH0bJprEYAx5hRFLRhWBqHAG57D0ZM4H7vxbP7bPe0VwhQRYDQ==", - "requires": { - "tslib": "^1.9.0" - } + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.3.tgz", + "integrity": "sha512-p3KULyQg4S7NIHixdwbGX+nFHkoBiA4YQmyWtjb8XngSKV124nJmRysgAeujbUVb15vh+RvFUfCPqU7rXk+hZg==" }, "ci-info": { "version": "3.1.1", @@ -3361,14 +3355,14 @@ } }, "core-js": { - "version": "3.9.1", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.9.1.tgz", - "integrity": "sha512-gSjRvzkxQc1zjM/5paAmL4idJBFzuJoo+jDjF1tStYFMV2ERfD02HhahhCGXUyHxQRG4yFKVSdO6g62eoRMcDg==" + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.10.1.tgz", + "integrity": "sha512-pwCxEXnj27XG47mu7SXAwhLP3L5CrlvCB91ANUkIz40P27kUcvNfSdvyZJ9CLHiVoKSp+TTChMQMSKQEH/IQxA==" }, "core-js-compat": { - "version": "3.9.1", - "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.9.1.tgz", - "integrity": "sha512-jXAirMQxrkbiiLsCx9bQPJFA6llDadKMpYrBJQJ3/c4/vsPP/fAf29h24tviRlvwUL6AmY5CHLu2GvjuYviQqA==", + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.10.1.tgz", + "integrity": "sha512-ZHQTdTPkqvw2CeHiZC970NNJcnwzT6YIueDMASKt+p3WbZsLXOcoD392SkcWhkC0wBBHhlfhqGKKsNCQUozYtg==", "requires": { "browserslist": "^4.16.3", "semver": "7.0.0" @@ -3561,14 +3555,14 @@ } }, "css-select": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/css-select/-/css-select-3.1.2.tgz", - "integrity": "sha512-qmss1EihSuBNWNNhHjxzxSfJoFBM/lERB/Q4EnsJQQC62R2evJDW481091oAdOr9uh46/0n4nrg0It5cAnj1RA==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-4.0.0.tgz", + "integrity": "sha512-I7favumBlDP/nuHBKLfL5RqvlvRdn/W29evvWJ+TaoGPm7QD+xSIN5eY2dyGjtkUmemh02TZrqJb4B8DWo6PoQ==", "requires": { "boolbase": "^1.0.0", - "css-what": "^4.0.0", - "domhandler": "^4.0.0", - "domutils": "^2.4.3", + "css-what": "^5.0.0", + "domhandler": "^4.1.0", + "domutils": "^2.5.1", "nth-check": "^2.0.0" } }, @@ -3594,9 +3588,9 @@ } }, "css-what": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/css-what/-/css-what-4.0.0.tgz", - "integrity": "sha512-teijzG7kwYfNVsUh2H/YN62xW3KK9YhXEgSlbxMlcyjPNvdKJqFx5lrwlJgoFP1ZHlB89iGDlo/JyshKeRhv5A==" + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/css-what/-/css-what-5.0.0.tgz", + "integrity": "sha512-qxyKHQvgKwzwDWC/rGbT821eJalfupxYW2qbSJSAtdSTimsr/MlaGONoNLllaUPZWf8QnbcKM/kPVYUQuEKAFA==" }, "cssesc": { "version": "3.0.0", @@ -3604,20 +3598,20 @@ "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==" }, "cssnano": { - "version": "4.1.10", - "resolved": "https://registry.npmjs.org/cssnano/-/cssnano-4.1.10.tgz", - "integrity": "sha512-5wny+F6H4/8RgNlaqab4ktc3e0/blKutmq8yNlBFXA//nSFFAqAngjNVRzUvCgYROULmZZUoosL/KSoZo5aUaQ==", + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/cssnano/-/cssnano-4.1.11.tgz", + "integrity": "sha512-6gZm2htn7xIPJOHY824ERgj8cNPgPxyCSnkXc4v7YvNW+TdVfzgngHcEhy/8D11kUWRUMbke+tC+AUcUsnMz2g==", "requires": { "cosmiconfig": "^5.0.0", - "cssnano-preset-default": "^4.0.7", + "cssnano-preset-default": "^4.0.8", "is-resolvable": "^1.0.0", "postcss": "^7.0.0" } }, "cssnano-preset-default": { - "version": "4.0.7", - "resolved": "https://registry.npmjs.org/cssnano-preset-default/-/cssnano-preset-default-4.0.7.tgz", - "integrity": "sha512-x0YHHx2h6p0fCl1zY9L9roD7rnlltugGu7zXSKQx6k2rYw0Hi3IqxcoAGF7u9Q5w1nt7vK0ulxV8Lo+EvllGsA==", + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/cssnano-preset-default/-/cssnano-preset-default-4.0.8.tgz", + "integrity": "sha512-LdAyHuq+VRyeVREFmuxUZR1TXjQm8QQU/ktoo/x7bz+SdOge1YKc5eMN6pRW7YWBmyq59CqYba1dJ5cUukEjLQ==", "requires": { "css-declaration-sorter": "^4.0.1", "cssnano-util-raw-cache": "^4.0.1", @@ -3647,7 +3641,7 @@ "postcss-ordered-values": "^4.1.2", "postcss-reduce-initial": "^4.0.3", "postcss-reduce-transforms": "^4.0.2", - "postcss-svgo": "^4.0.2", + "postcss-svgo": "^4.0.3", "postcss-unique-selectors": "^4.0.1" } }, @@ -3683,9 +3677,9 @@ }, "dependencies": { "css-tree": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-1.1.2.tgz", - "integrity": "sha512-wCoWush5Aeo48GLhfHPbmvZs59Z+M7k5+B1xDnXbdWNcEF423DoFdqSWE0PM5aNk5nI5cp1q7ms36zGApY/sKQ==", + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-1.1.3.tgz", + "integrity": "sha512-tRpdppF7TRazZrjJ6v3stzv93qxRcSsFmW6cX0Zm2NVKpxE1WV1HblnghVv9TreireHkqI/VDEsfolRF1p6y7Q==", "requires": { "mdn-data": "2.0.14", "source-map": "^0.6.1" @@ -4021,9 +4015,9 @@ } }, "dom-serializer": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-1.2.0.tgz", - "integrity": "sha512-n6kZFH/KlCrqs/1GHMOd5i2fd/beQHuehKdWvNNffbGHTr/almdhuVvTVFb3V7fglz+nC50fFusu3lY33h12pA==", + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-1.3.1.tgz", + "integrity": "sha512-Pv2ZluG5ife96udGgEDovOOOA5UELkltfJpnIExPrAk1LTvecolUGn6lIaoLh86d83GiB86CjzciMd9BuRB71Q==", "requires": { "domelementtype": "^2.0.1", "domhandler": "^4.0.0", @@ -4041,26 +4035,26 @@ "integrity": "sha512-jnjyiM6eRyZl2H+W8Q/zLMA481hzi0eszAaBUzIVnmYVDBbnLxVNnfu1HgEBvCbL+71FrxMl3E6lpKH7Ge3OXA==" }, "domelementtype": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.1.0.tgz", - "integrity": "sha512-LsTgx/L5VpD+Q8lmsXSHW2WpA+eBlZ9HPf3erD1IoPF00/3JKHZ3BknUVA2QGDNu69ZNmyFmCWBSO45XjYKC5w==" + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.2.0.tgz", + "integrity": "sha512-DtBMo82pv1dFtUmHyr48beiuq792Sxohr+8Hm9zoxklYPfa6n0Z3Byjj2IV7bmr2IyqClnqEQhfgHJJ5QF0R5A==" }, "domhandler": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-4.0.0.tgz", - "integrity": "sha512-KPTbnGQ1JeEMQyO1iYXoagsI6so/C96HZiFyByU3T6iAzpXn8EGEvct6unm1ZGoed8ByO2oirxgwxBmqKF9haA==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-4.1.0.tgz", + "integrity": "sha512-/6/kmsGlMY4Tup/nGVutdrK9yQi4YjWVcVeoQmixpzjOUK1U7pQkvAPHBJeUxOgxF0J8f8lwCJSlCfD0V4CMGQ==", "requires": { - "domelementtype": "^2.1.0" + "domelementtype": "^2.2.0" } }, "domutils": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/domutils/-/domutils-2.5.0.tgz", - "integrity": "sha512-Ho16rzNMOFk2fPwChGh3D2D9OEHAfG19HgmRR2l+WLSsIstNsAYBzePH412bL0y5T44ejABIVfTHQ8nqi/tBCg==", + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-2.5.2.tgz", + "integrity": "sha512-MHTthCb1zj8f1GVfRpeZUbohQf/HdBos0oX5gZcQFepOZPLLRyj6Wn7XS7EMnY7CVpwv8863u2vyE83Hfu28HQ==", "requires": { "dom-serializer": "^1.0.1", - "domelementtype": "^2.0.1", - "domhandler": "^4.0.0" + "domelementtype": "^2.2.0", + "domhandler": "^4.1.0" } }, "dot-prop": { @@ -4102,9 +4096,9 @@ "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" }, "electron-to-chromium": { - "version": "1.3.695", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.695.tgz", - "integrity": "sha512-lz66RliUqLHU1Ojxx1A4QUxKydjiQ79Y4dZyPobs2Dmxj5aVL2TM3KoQ2Gs7HS703Bfny+ukI3KOxwAB0xceHQ==" + "version": "1.3.712", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.712.tgz", + "integrity": "sha512-3kRVibBeCM4vsgoHHGKHmPocLqtFAGTrebXxxtgKs87hNUzXrX2NuS3jnBys7IozCnw7viQlozxKkmty2KNfrw==" }, "elliptic": { "version": "6.5.4", @@ -4186,9 +4180,9 @@ } }, "envinfo": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/envinfo/-/envinfo-7.7.4.tgz", - "integrity": "sha512-TQXTYFVVwwluWSFis6K2XKxgrD22jEv0FTuLCQI+OjH7rn93+iY0fSSFM5lrSxFY+H1+B0/cvvlamr3UsBivdQ==" + "version": "7.8.1", + "resolved": "https://registry.npmjs.org/envinfo/-/envinfo-7.8.1.tgz", + "integrity": "sha512-/o+BXHmB7ocbHEAs6F2EnG0ogybVVUdkRunTT2glZU9XAaGmhqskrvKwqXuDfNjEO0LZKWdejEEpnq8aM0tOaw==" }, "errno": { "version": "0.1.8", @@ -5092,11 +5086,6 @@ "resolved": "https://registry.npmjs.org/hsla-regex/-/hsla-regex-1.0.0.tgz", "integrity": "sha1-wc56MWjIxmFAM6S194d/OyJfnDg=" }, - "html-comment-regex": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/html-comment-regex/-/html-comment-regex-1.1.2.tgz", - "integrity": "sha512-P+M65QY2JQ5Y0G9KKdlDpo0zK+/OHptU5AaBwUfAIDJZk1MYf32Frm84EcOytfJE0t5JvkAnKlmjsXDnWzCJmQ==" - }, "html-entities": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/html-entities/-/html-entities-1.4.0.tgz", @@ -5122,13 +5111,13 @@ "integrity": "sha512-1qYz89hW3lFDEazhjW0yVAV87lw8lVkrJocr72XmBkMKsoSVJCQx3W8BXsC7hO2qAt8BoVjYjtAcZ9perqGnNg==" }, "htmlparser2": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-6.0.1.tgz", - "integrity": "sha512-GDKPd+vk4jvSuvCbyuzx/unmXkk090Azec7LovXP8as1Hn8q9p3hbjmDGbUqqhknw0ajwit6LiiWqfiTUPMK7w==", + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-6.1.0.tgz", + "integrity": "sha512-gyyPk6rgonLFEDGoeRgQNaEUvdJ4ktTmmUh/h2t7s+M8oPpIPxgNACWa+6ESR57kXstwqPiCut0V8NRpcwgU7A==", "requires": { "domelementtype": "^2.0.1", "domhandler": "^4.0.0", - "domutils": "^2.4.4", + "domutils": "^2.5.2", "entities": "^2.0.0" } }, @@ -5677,14 +5666,6 @@ "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.5.tgz", "integrity": "sha512-buY6VNRjhQMiF1qWDouloZlQbRhDPCebwxSjxMjxgemYT46YMd2NR0/H+fBhEfWX4A/w9TBJ+ol+okqJKFE6vQ==" }, - "is-svg": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-svg/-/is-svg-3.0.0.tgz", - "integrity": "sha512-gi4iHK53LR2ujhLVVj+37Ykh9GLqYHX6JOVXbLAucaG/Cqw9xwdFOjDM2qeifLs1sF1npXXFvDu0r5HNgCMrzQ==", - "requires": { - "html-comment-regex": "^1.1.0" - } - }, "is-symbol": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.3.tgz", @@ -6234,16 +6215,16 @@ "integrity": "sha512-tqkh47FzKeCPD2PUiPB6pkbMzsCasjxAfC62/Wap5qrUWcb+sFasXUC5I3gYM5iBM8v/Qpn4UK0x+j0iHyFPDg==" }, "mime-db": { - "version": "1.46.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.46.0.tgz", - "integrity": "sha512-svXaP8UQRZ5K7or+ZmfNhg2xX3yKDMUzqadsSqi4NCH/KomcH75MAMYAGVlvXn4+b/xOPhS3I2uHKRUzvjY7BQ==" + "version": "1.47.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.47.0.tgz", + "integrity": "sha512-QBmA/G2y+IfeS4oktet3qRZ+P5kPhCKRXxXnQEudYqUaEioAU1/Lq2us3D/t1Jfo4hE9REQPrbB7K5sOczJVIw==" }, "mime-types": { - "version": "2.1.29", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.29.tgz", - "integrity": "sha512-Y/jMt/S5sR9OaqteJtslsFZKWOIIqMACsJSiHghlCAyhf7jfVYjKBmLiX8OgpWeW+fjJ2b+Az69aPFPkUOY6xQ==", + "version": "2.1.30", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.30.tgz", + "integrity": "sha512-crmjA4bLtR8m9qLpHvgxSChT+XoSlZi8J4n/aIdn3z92e/U47Z0V/yl+Wh9W046GgFVAmoNR/fmdbZYcSSIUeg==", "requires": { - "mime-db": "1.46.0" + "mime-db": "1.47.0" } }, "mimic-response": { @@ -6899,9 +6880,9 @@ } }, "pbkdf2": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.1.1.tgz", - "integrity": "sha512-4Ejy1OPxi9f2tt1rRV7Go7zmfDQ+ZectEQz3VGUQhgq62HtIRPDyG/JtnwIxs6x3uNMwo2V7q1fMvKjb+Tnpqg==", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.1.2.tgz", + "integrity": "sha512-iuh7L6jA7JEGu2WxDwtQP1ddOpaJNC4KlDEFfdQajSGgGPNi4OyDc2R7QnbY2bR9QjBVGwgvTdNJZoE7RaxUMA==", "requires": { "create-hash": "^1.1.2", "create-hmac": "^1.1.4", @@ -6916,9 +6897,9 @@ "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=" }, "picomatch": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.2.2.tgz", - "integrity": "sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg==", + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.2.3.tgz", + "integrity": "sha512-KpELjfwcCDUb9PeigTs2mBJzXUPzAuP2oPcA989He8Rte0+YUAjw1JVedDhuTKPkHjSYzMN3npC9luThGYEKdg==", "optional": true }, "pify": { @@ -7499,11 +7480,10 @@ } }, "postcss-svgo": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/postcss-svgo/-/postcss-svgo-4.0.2.tgz", - "integrity": "sha512-C6wyjo3VwFm0QgBy+Fu7gCYOkCmgmClghO+pjcxvrcBKtiKt0uCF+hvbMO1fyv5BMImRK90SMb+dwUnfbGd+jw==", + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/postcss-svgo/-/postcss-svgo-4.0.3.tgz", + "integrity": "sha512-NoRbrcMWTtUghzuKSoIm6XV+sJdvZ7GZSc3wdBN0W19FTtp2ko8NqLsgoh/m9CzNhU3KLPvQmjIwtaNFkaFTvw==", "requires": { - "is-svg": "^3.0.0", "postcss": "^7.0.0", "postcss-value-parser": "^3.0.0", "svgo": "^1.0.0" @@ -8010,9 +7990,9 @@ "integrity": "sha512-OFFT3MfrH90xIW8OOSyUrk6QHD5E9JOTeGodiJeBS3J6IwlgzJMNE/1bZklWz5oTg+9dCMyEetclvCVXOPoN3A==" }, "regjsparser": { - "version": "0.6.8", - "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.6.8.tgz", - "integrity": "sha512-3weFrFQREJhJ2PW+iCGaG6TenyzNSZgsBKZ/oEf6Trme31COSeIWhHw9O6FPkuXktfx+b6Hf/5e6dKPHaROq2g==", + "version": "0.6.9", + "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.6.9.tgz", + "integrity": "sha512-ZqbNRz1SNjLAiYuwY0zoXW8Ne675IX5q+YHioAGbCw4X96Mjl2+dcX9B2ciaeyYjViDAfvIjFpQjJgLttTEERQ==", "requires": { "jsesc": "~0.5.0" }, @@ -8072,9 +8052,9 @@ }, "dependencies": { "domelementtype": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.1.0.tgz", - "integrity": "sha512-LsTgx/L5VpD+Q8lmsXSHW2WpA+eBlZ9HPf3erD1IoPF00/3JKHZ3BknUVA2QGDNu69ZNmyFmCWBSO45XjYKC5w==" + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.2.0.tgz", + "integrity": "sha512-DtBMo82pv1dFtUmHyr48beiuq792Sxohr+8Hm9zoxklYPfa6n0Z3Byjj2IV7bmr2IyqClnqEQhfgHJJ5QF0R5A==" } } }, @@ -8141,9 +8121,9 @@ } }, "repeat-element": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.3.tgz", - "integrity": "sha512-ahGq0ZnV5m5XtZLMb+vP76kcAM5nkLqk0lpqAuojSKGgQtn4eRi4ZZGm2olo2zKFH+sMsWaqOCW1dqAnOru72g==" + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.4.tgz", + "integrity": "sha512-LFiNfRcSu7KK3evMyYOuCzv3L10TW7yC1G2/+StMjK8Y6Vqd2MG7r/Qjw4ghtuCOjFvlnms/iMmLqpvW/ES/WQ==" }, "repeat-string": { "version": "1.6.1", @@ -8666,16 +8646,16 @@ } }, "sockjs-client": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/sockjs-client/-/sockjs-client-1.5.0.tgz", - "integrity": "sha512-8Dt3BDi4FYNrCFGTL/HtwVzkARrENdwOUf1ZoW/9p3M8lZdFT35jVdrHza+qgxuG9H3/shR4cuX/X9umUrjP8Q==", + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/sockjs-client/-/sockjs-client-1.5.1.tgz", + "integrity": "sha512-VnVAb663fosipI/m6pqRXakEOw7nvd7TUgdr3PlR/8V2I95QIdwT8L4nMxhyU8SmDBHYXU1TOElaKOmKLfYzeQ==", "requires": { "debug": "^3.2.6", "eventsource": "^1.0.7", "faye-websocket": "^0.11.3", "inherits": "^2.0.4", "json3": "^3.3.3", - "url-parse": "^1.4.7" + "url-parse": "^1.5.1" }, "dependencies": { "debug": { @@ -8858,9 +8838,9 @@ } }, "ssri": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/ssri/-/ssri-6.0.1.tgz", - "integrity": "sha512-3Wge10hNcT1Kur4PDFwEieXSCMCJs/7WvSACcrMYrNp+b8kDL1/0wJch5Ni2WrtwEa2IO8OsVfeKIciKCDx/QA==", + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/ssri/-/ssri-6.0.2.tgz", + "integrity": "sha512-cepbSq/neFK7xB6A50KHN0xHDotYzq58wWCa5LeWqnPrHG8GzfEjO/4O8kpmcGW+oaxkvhEJCWgbgNk4/ZV93Q==", "requires": { "figgy-pudding": "^3.5.1" } @@ -8871,9 +8851,9 @@ "integrity": "sha512-ji9qxRnOVfcuLDySj9qzhGSEFVobyt1kIOSkj1qZzYLzq7Tos/oUUWvotUPQLlrsidqsK6tBH89Bc9kL5zHA6w==" }, "stack-utils": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-1.0.4.tgz", - "integrity": "sha512-IPDJfugEGbfizBwBZRZ3xpccMdRyP5lqsBWXGQWimVjua/ccLCeMOAVjlc1R7LxFjo5sEDhyNIXd8mo/AiDS9w==", + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-1.0.5.tgz", + "integrity": "sha512-KZiTzuV3CnSnSvgMRrARVCj+Ht7rMbauGDK0LdVFRGyenwdylpajAp4Q0i6SX8rEmbTpMMf6ryq2gb8pPq2WgQ==", "requires": { "escape-string-regexp": "^2.0.0" }, @@ -9137,9 +9117,9 @@ }, "dependencies": { "domelementtype": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.1.0.tgz", - "integrity": "sha512-LsTgx/L5VpD+Q8lmsXSHW2WpA+eBlZ9HPf3erD1IoPF00/3JKHZ3BknUVA2QGDNu69ZNmyFmCWBSO45XjYKC5w==" + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.2.0.tgz", + "integrity": "sha512-DtBMo82pv1dFtUmHyr48beiuq792Sxohr+8Hm9zoxklYPfa6n0Z3Byjj2IV7bmr2IyqClnqEQhfgHJJ5QF0R5A==" } } }, @@ -9464,11 +9444,6 @@ "punycode": "^2.1.0" } }, - "tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" - }, "tty-browserify": { "version": "0.0.0", "resolved": "https://registry.npmjs.org/tty-browserify/-/tty-browserify-0.0.0.tgz", @@ -9488,9 +9463,9 @@ "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=" }, "type-fest": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.11.0.tgz", - "integrity": "sha512-OdjXJxnCN1AvyLSzeKIgXTXxV+99ZuXl3Hpo9XpJAv9MBcHrrJOQ5kV7ypXOuQie+AmWG25hLbiKdwYTifzcfQ==" + "version": "0.21.3", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", + "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==" }, "type-is": { "version": "1.6.18", @@ -9541,14 +9516,14 @@ } }, "unbox-primitive": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.0.tgz", - "integrity": "sha512-P/51NX+JXyxK/aigg1/ZgyccdAxm5K1+n8+tvqSntjOivPt19gvm1VC49RWYetsiub8WViUchdxl/KWHHB0kzA==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.1.tgz", + "integrity": "sha512-tZU/3NqK3dA5gpE1KtyiJUrEB0lxnGkMFHptJ7q6ewdZ8s12QrODwNbhIJStmJkd1QDXa1NRA8aF2A1zk/Ypyw==", "requires": { "function-bind": "^1.1.1", - "has-bigints": "^1.0.0", - "has-symbols": "^1.0.0", - "which-boxed-primitive": "^1.0.1" + "has-bigints": "^1.0.1", + "has-symbols": "^1.0.2", + "which-boxed-primitive": "^1.0.2" } }, "unicode-canonical-property-names-ecmascript": { @@ -10102,9 +10077,9 @@ } }, "vuepress-theme-cosmos": { - "version": "1.0.181", - "resolved": "https://registry.npmjs.org/vuepress-theme-cosmos/-/vuepress-theme-cosmos-1.0.181.tgz", - "integrity": "sha512-Cu027M9Y/YzsjvZU6kgu9Berus72S1yTmjWBgDoPdi8r+O2lFR2RGRz2SbWS1X3Q2jr3kJ/8XjbERI5E4NiR5w==", + "version": "1.0.182", + "resolved": "https://registry.npmjs.org/vuepress-theme-cosmos/-/vuepress-theme-cosmos-1.0.182.tgz", + "integrity": "sha512-Mc1ZOsSqLGgbB9xEXsx5QkHUBkKXOoDgkjrp5iX+fwmM4TCmR4MWbTlKpEzfzsxZ1DuixtwVkv0MT+eNvD2Lfw==", "requires": { "@cosmos-ui/vue": "^0.35.0", "@vuepress/plugin-google-analytics": "1.7.1", @@ -10207,9 +10182,9 @@ "integrity": "sha512-XtGIhXwF8YM8bJhGxG5kXgjkEuNGLTkoYqVE+KMR+aspr4KGYmKYg7yUe3KghyQ9yheNwLnjmzh/7+gfDBmHCQ==" }, "anymatch": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.1.tgz", - "integrity": "sha512-mM8522psRCqzV+6LhomX5wgp25YVibjh8Wj23I5RPkPppSVSjyKD2A2mBJmWGa+KN7f2D6LNh9jkBCeyLktzjg==", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz", + "integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==", "optional": true, "requires": { "normalize-path": "^3.0.0", @@ -10681,9 +10656,9 @@ "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==" }, "y18n": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.1.tgz", - "integrity": "sha512-wNcy4NvjMYL8gogWWYAO7ZFWFfHcbdbE57tZO8e4cbpj8tfUcwrwqSl3ad8HxpYWCdXcJUCeKKZS62Av1affwQ==" + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz", + "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==" }, "yallist": { "version": "3.1.1", diff --git a/docs/package.json b/docs/package.json index 616432f60..ca43e7093 100644 --- a/docs/package.json +++ b/docs/package.json @@ -4,7 +4,7 @@ "description": "Tendermint Core Documentation", "main": "index.js", "dependencies": { - "vuepress-theme-cosmos": "^1.0.181" + "vuepress-theme-cosmos": "^1.0.182" }, "devDependencies": { "watchpack": "^2.1.1" diff --git a/docs/rfc/README.md b/docs/rfc/README.md deleted file mode 100644 index 85461dd6c..000000000 --- a/docs/rfc/README.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -order: 1 -parent: - order: false ---- - - diff --git a/evidence/mocks/block_store.go b/evidence/mocks/block_store.go index cdf316d00..e6205939a 100644 --- a/evidence/mocks/block_store.go +++ b/evidence/mocks/block_store.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.5.1. DO NOT EDIT. +// Code generated by mockery v0.0.0-dev. DO NOT EDIT. package mocks @@ -12,6 +12,20 @@ type BlockStore struct { mock.Mock } +// Height provides a mock function with given fields: +func (_m *BlockStore) Height() int64 { + ret := _m.Called() + + var r0 int64 + if rf, ok := ret.Get(0).(func() int64); ok { + r0 = rf() + } else { + r0 = ret.Get(0).(int64) + } + + return r0 +} + // LoadBlockCommit provides a mock function with given fields: height func (_m *BlockStore) LoadBlockCommit(height int64) *types.Commit { ret := _m.Called(height) diff --git a/evidence/reactor.go b/evidence/reactor.go index bc83f5127..867b729c0 100644 --- a/evidence/reactor.go +++ b/evidence/reactor.go @@ -55,7 +55,6 @@ type Reactor struct { service.BaseService evpool *Pool - eventBus *types.EventBus evidenceCh *p2p.Channel peerUpdates *p2p.PeerUpdates closeCh chan struct{} @@ -87,11 +86,6 @@ func NewReactor( return r } -// SetEventBus implements events.Eventable. -func (r *Reactor) SetEventBus(b *types.EventBus) { - r.eventBus = b -} - // OnStart starts separate go routines for each p2p Channel and listens for // envelopes on each. In addition, it also listens for peer updates and handles // messages on that p2p channel accordingly. The caller must be sure to execute diff --git a/evidence/services.go b/evidence/services.go index 5e15b38b4..274433cbe 100644 --- a/evidence/services.go +++ b/evidence/services.go @@ -9,4 +9,5 @@ import ( type BlockStore interface { LoadBlockMeta(height int64) *types.BlockMeta LoadBlockCommit(height int64) *types.Commit + Height() int64 } diff --git a/evidence/verify.go b/evidence/verify.go index dabd248c8..406f6b8da 100644 --- a/evidence/verify.go +++ b/evidence/verify.go @@ -100,7 +100,21 @@ func (evpool *Pool) verify(evidence types.Evidence) error { if evidence.Height() != ev.ConflictingBlock.Height { trustedHeader, err = getSignedHeader(evpool.blockStore, ev.ConflictingBlock.Height) if err != nil { - return err + // FIXME: This multi step process is a bit unergonomic. We may want to consider a more efficient process + // that doesn't require as much io and is atomic. + + // If the node doesn't have a block at the height of the conflicting block, then this could be + // a forward lunatic attack. Thus the node must get the latest height it has + latestHeight := evpool.blockStore.Height() + trustedHeader, err = getSignedHeader(evpool.blockStore, latestHeight) + if err != nil { + return err + } + if trustedHeader.Time.Before(ev.ConflictingBlock.Time) { + return fmt.Errorf("latest block time (%v) is before conflicting block time (%v)", + trustedHeader.Time, ev.ConflictingBlock.Time, + ) + } } } @@ -176,36 +190,47 @@ func (evpool *Pool) verify(evidence types.Evidence) error { // the following checks: // - the common header from the full node has at least 1/3 voting power which is also present in // the conflicting header's commit +// - 2/3+ of the conflicting validator set correctly signed the conflicting block // - the nodes trusted header at the same height as the conflicting header has a different hash +// +// CONTRACT: must run ValidateBasic() on the evidence before verifying +// must check that the evidence has not expired (i.e. is outside the maximum age threshold) func VerifyLightClientAttack(e *types.LightClientAttackEvidence, commonHeader, trustedHeader *types.SignedHeader, commonVals *types.ValidatorSet, now time.Time, trustPeriod time.Duration) error { - // In the case of lunatic attack we need to perform a single verification jump between the - // common header and the conflicting one - if commonHeader.Height != trustedHeader.Height { - err := light.Verify(commonHeader, commonVals, e.ConflictingBlock.SignedHeader, e.ConflictingBlock.ValidatorSet, - trustPeriod, now, 0*time.Second, light.DefaultTrustLevel) + // In the case of lunatic attack there will be a different commonHeader height. Therefore the node perform a single + // verification jump between the common header and the conflicting one + if commonHeader.Height != e.ConflictingBlock.Height { + err := commonVals.VerifyCommitLightTrusting(trustedHeader.ChainID, e.ConflictingBlock.Commit, light.DefaultTrustLevel) if err != nil { - return fmt.Errorf("skipping verification from common to conflicting header failed: %w", err) - } - } else { - // in the case of equivocation and amnesia we expect some header hashes to be correctly derived - if isInvalidHeader(trustedHeader.Header, e.ConflictingBlock.Header) { - return errors.New("common height is the same as conflicting block height so expected the conflicting" + - " block to be correctly derived yet it wasn't") - } - // ensure that 2/3 of the validator set did vote for this block - if err := e.ConflictingBlock.ValidatorSet.VerifyCommitLight(trustedHeader.ChainID, e.ConflictingBlock.Commit.BlockID, - e.ConflictingBlock.Height, e.ConflictingBlock.Commit); err != nil { - return fmt.Errorf("invalid commit from conflicting block: %w", err) + return fmt.Errorf("skipping verification of conflicting block failed: %w", err) } + + // In the case of equivocation and amnesia we expect all header hashes to be correctly derived + } else if isInvalidHeader(trustedHeader.Header, e.ConflictingBlock.Header) { + return errors.New("common height is the same as conflicting block height so expected the conflicting" + + " block to be correctly derived yet it wasn't") } + // Verify that the 2/3+ commits from the conflicting validator set were for the conflicting header + if err := e.ConflictingBlock.ValidatorSet.VerifyCommitLight(trustedHeader.ChainID, e.ConflictingBlock.Commit.BlockID, + e.ConflictingBlock.Height, e.ConflictingBlock.Commit); err != nil { + return fmt.Errorf("invalid commit from conflicting block: %w", err) + } + + // Assert the correct amount of voting power of the validator set if evTotal, valsTotal := e.TotalVotingPower, commonVals.TotalVotingPower(); evTotal != valsTotal { return fmt.Errorf("total voting power from the evidence and our validator set does not match (%d != %d)", evTotal, valsTotal) } - if bytes.Equal(trustedHeader.Hash(), e.ConflictingBlock.Hash()) { + // check in the case of a forward lunatic attack that monotonically increasing time has been violated + if e.ConflictingBlock.Height > trustedHeader.Height && e.ConflictingBlock.Time.After(trustedHeader.Time) { + return fmt.Errorf("conflicting block doesn't violate monotonically increasing time (%v is after %v)", + e.ConflictingBlock.Time, trustedHeader.Time, + ) + + // In all other cases check that the hashes of the conflicting header and the trusted header are different + } else if bytes.Equal(trustedHeader.Hash(), e.ConflictingBlock.Hash()) { return fmt.Errorf("trusted header hash matches the evidence's conflicting header hash: %X", trustedHeader.Hash()) } diff --git a/evidence/verify_test.go b/evidence/verify_test.go index 30e5e4e71..dfb8cec18 100644 --- a/evidence/verify_test.go +++ b/evidence/verify_test.go @@ -34,6 +34,7 @@ func TestVerifyLightClientAttack_Lunatic(t *testing.T) { commonHeader := makeHeaderRandom(4) commonHeader.Time = defaultEvidenceTime trustedHeader := makeHeaderRandom(10) + trustedHeader.Time = defaultEvidenceTime.Add(1 * time.Hour) conflictingHeader := makeHeaderRandom(10) conflictingHeader.Time = defaultEvidenceTime.Add(1 * time.Hour) @@ -89,6 +90,30 @@ func TestVerifyLightClientAttack_Lunatic(t *testing.T) { assert.Error(t, err) ev.TotalVotingPower = 20 + forwardConflictingHeader := makeHeaderRandom(11) + forwardConflictingHeader.Time = defaultEvidenceTime.Add(30 * time.Minute) + forwardConflictingHeader.ValidatorsHash = conflictingVals.Hash() + forwardBlockID := makeBlockID(forwardConflictingHeader.Hash(), 1000, []byte("partshash")) + forwardVoteSet := types.NewVoteSet(evidenceChainID, 11, 1, tmproto.SignedMsgType(2), conflictingVals) + forwardCommit, err := types.MakeCommit(forwardBlockID, 11, 1, forwardVoteSet, conflictingPrivVals, defaultEvidenceTime) + require.NoError(t, err) + forwardLunaticEv := &types.LightClientAttackEvidence{ + ConflictingBlock: &types.LightBlock{ + SignedHeader: &types.SignedHeader{ + Header: forwardConflictingHeader, + Commit: forwardCommit, + }, + ValidatorSet: conflictingVals, + }, + CommonHeight: 4, + TotalVotingPower: 20, + ByzantineValidators: commonVals.Validators, + Timestamp: defaultEvidenceTime, + } + err = evidence.VerifyLightClientAttack(forwardLunaticEv, commonSignedHeader, trustedSignedHeader, commonVals, + defaultEvidenceTime.Add(2*time.Hour), 3*time.Hour) + assert.NoError(t, err) + state := sm.State{ LastBlockTime: defaultEvidenceTime.Add(2 * time.Hour), LastBlockHeight: 11, @@ -100,8 +125,10 @@ func TestVerifyLightClientAttack_Lunatic(t *testing.T) { blockStore := &mocks.BlockStore{} blockStore.On("LoadBlockMeta", int64(4)).Return(&types.BlockMeta{Header: *commonHeader}) blockStore.On("LoadBlockMeta", int64(10)).Return(&types.BlockMeta{Header: *trustedHeader}) + blockStore.On("LoadBlockMeta", int64(11)).Return(nil) blockStore.On("LoadBlockCommit", int64(4)).Return(commit) blockStore.On("LoadBlockCommit", int64(10)).Return(trustedCommit) + blockStore.On("Height").Return(int64(10)) pool, err := evidence.NewPool(log.TestingLogger(), dbm.NewMemDB(), stateStore, blockStore) require.NoError(t, err) @@ -125,6 +152,9 @@ func TestVerifyLightClientAttack_Lunatic(t *testing.T) { err = pool.CheckEvidence(evList) assert.Error(t, err) + evList = types.EvidenceList{forwardLunaticEv} + err = pool.CheckEvidence(evList) + assert.NoError(t, err) } func TestVerifyLightClientAttack_Equivocation(t *testing.T) { diff --git a/go.mod b/go.mod index f29a4591d..0ffbc6fd2 100644 --- a/go.mod +++ b/go.mod @@ -35,5 +35,5 @@ require ( github.com/tendermint/tm-db v0.6.4 golang.org/x/crypto v0.0.0-20201117144127-c1f2f97bffc9 golang.org/x/net v0.0.0-20201021035429-f5854403a974 - google.golang.org/grpc v1.36.1 + google.golang.org/grpc v1.37.0 ) diff --git a/go.sum b/go.sum index b5cd881cf..cc687c3fd 100644 --- a/go.sum +++ b/go.sum @@ -131,6 +131,7 @@ github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymF github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= +github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/facebookgo/ensure v0.0.0-20160127193407-b4ab57deab51 h1:0JZ+dUmQeA8IIVUMzysrX4/AKuQwWhV2dYQuPZdvdSQ= github.com/facebookgo/ensure v0.0.0-20160127193407-b4ab57deab51/go.mod h1:Yg+htXGokKKdzcwhuNDwVvN+uBxDGXJ7G/VN1d8fa64= @@ -745,8 +746,8 @@ google.golang.org/grpc v1.32.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= -google.golang.org/grpc v1.36.1 h1:cmUfbeGKnz9+2DD/UYsMQXeqbHZqZDs4eQwW0sFOpBY= -google.golang.org/grpc v1.36.1/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/grpc v1.37.0 h1:uSZWeQJX5j11bIQ4AJoj+McDBo29cY1MCoC1wO3ts+c= +google.golang.org/grpc v1.37.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= 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= diff --git a/light/client.go b/light/client.go index b6785f768..1b7bbe70f 100644 --- a/light/client.go +++ b/light/client.go @@ -35,6 +35,9 @@ const ( // - http://vancouver-webpages.com/time/web.html // - https://blog.codinghorror.com/keeping-time-on-the-pc/ defaultMaxClockDrift = 10 * time.Second + + // 10s is sufficient for most networks. + defaultMaxBlockLag = 10 * time.Second ) // Option sets a parameter for the light client. @@ -92,13 +95,27 @@ func Logger(l log.Logger) Option { } // MaxClockDrift defines how much new header's time can drift into -// the future. Default: 10s. +// the future relative to the light clients local time. Default: 10s. func MaxClockDrift(d time.Duration) Option { return func(c *Client) { c.maxClockDrift = d } } +// MaxBlockLag represents the maximum time difference between the realtime +// that a block is received and the timestamp of that block. +// One can approximate it to the maximum block production time +// +// As an example, say the light client received block B at a time +// 12:05 (this is the real time) and the time on the block +// was 12:00. Then the lag here is 5 minutes. +// Default: 10s +func MaxBlockLag(d time.Duration) Option { + return func(c *Client) { + c.maxBlockLag = d + } +} + // Client represents a light client, connected to a single chain, which gets // light blocks from a primary provider, verifies them either sequentially or by // skipping some and stores them in a trusted store (usually, a local FS). @@ -110,6 +127,7 @@ type Client struct { verificationMode mode trustLevel tmmath.Fraction maxClockDrift time.Duration + maxBlockLag time.Duration // Mutex for locking during changes of the light clients providers providerMutex tmsync.Mutex @@ -197,6 +215,7 @@ func NewClientFromTrustedStore( verificationMode: skipping, trustLevel: DefaultTrustLevel, maxClockDrift: defaultMaxClockDrift, + maxBlockLag: defaultMaxBlockLag, primary: primary, witnesses: witnesses, trustedStore: trustedStore, @@ -712,7 +731,7 @@ func (c *Client) verifySkipping( blockCache = append(blockCache, interimBlock) // if the error is benign, the client does not need to replace the primary - case provider.ErrLightBlockNotFound, provider.ErrNoResponse: + case provider.ErrLightBlockNotFound, provider.ErrNoResponse, provider.ErrHeightTooHigh: return nil, err // all other errors such as ErrBadLightBlock or ErrUnreliableProvider are seen as malevolent and the @@ -950,15 +969,17 @@ func (c *Client) lightBlockFromPrimary(ctx context.Context, height int64) (*type // Everything went smoothly. We reset the lightBlockRequests and return the light block return l, nil - case provider.ErrNoResponse, provider.ErrLightBlockNotFound: + case provider.ErrNoResponse, provider.ErrLightBlockNotFound, provider.ErrHeightTooHigh: // we find a new witness to replace the primary - c.logger.Debug("error from light block request from primary, replacing...", "error", err, "primary", c.primary) + c.logger.Debug("error from light block request from primary, replacing...", + "error", err, "height", height, "primary", c.primary) return c.findNewPrimary(ctx, height, false) default: // The light client has most likely received either provider.ErrUnreliableProvider or provider.ErrBadLightBlock // These errors mean that the light client should drop the primary and try with another provider instead - c.logger.Error("error from light block request from primary, removing...", "error", err, "primary", c.primary) + c.logger.Error("error from light block request from primary, removing...", + "error", err, "height", height, "primary", c.primary) return c.findNewPrimary(ctx, height, true) } } @@ -1051,7 +1072,7 @@ func (c *Client) findNewPrimary(ctx context.Context, height int64, remove bool) return response.lb, nil // process benign errors by logging them only - case provider.ErrNoResponse, provider.ErrLightBlockNotFound: + case provider.ErrNoResponse, provider.ErrLightBlockNotFound, provider.ErrHeightTooHigh: lastError = response.err c.logger.Debug("error on light block request from witness", "error", response.err, "primary", c.witnesses[response.witnessIndex]) diff --git a/light/detector.go b/light/detector.go index 50b9f671b..b63763cf2 100644 --- a/light/detector.go +++ b/light/detector.go @@ -11,7 +11,7 @@ import ( "github.com/tendermint/tendermint/types" ) -// The detector component of the light client detect and handles attacks on the light client. +// The detector component of the light client detects and handles attacks on the light client. // More info here: // tendermint/docs/architecture/adr-047-handling-evidence-from-light-client.md @@ -20,7 +20,7 @@ import ( // It takes the target verified header and compares it with the headers of a set of // witness providers that the light client is connected to. If a conflicting header // is returned it verifies and examines the conflicting header against the verified -// trace that was produced from the primary. If successful it produces two sets of evidence +// trace that was produced from the primary. If successful, it produces two sets of evidence // and sends them to the opposite provider before halting. // // If there are no conflictinge headers, the light client deems the verified target header @@ -64,56 +64,14 @@ func (c *Client) detectDivergence(ctx context.Context, primaryTrace []*types.Lig // need to find the point that the headers diverge and examine this for any evidence of an attack. // // We combine these actions together, verifying the witnesses headers and outputting the trace - // which captures the bifurcation point and if successful provides the information to create - supportingWitness := c.witnesses[e.WitnessIndex] - witnessTrace, primaryBlock, err := c.examineConflictingHeaderAgainstTrace( - ctx, - primaryTrace, - e.Block.SignedHeader, - supportingWitness, - now, - ) + // which captures the bifurcation point and if successful provides the information to create valid evidence. + err := c.handleConflictingHeaders(ctx, primaryTrace, e.Block, e.WitnessIndex, now) if err != nil { - c.logger.Info("error validating witness's divergent header", "witness", supportingWitness, "err", err) - witnessesToRemove = append(witnessesToRemove, e.WitnessIndex) - continue + // return information of the attack + return err } - - // We are suspecting that the primary is faulty, hence we hold the witness as the source of truth - // and generate evidence against the primary that we can send to the witness - primaryEv := newLightClientAttackEvidence(primaryBlock, witnessTrace[len(witnessTrace)-1], witnessTrace[0]) - c.logger.Error("ATTEMPTED ATTACK DETECTED. Sending evidence againt primary by witness", "ev", primaryEv, - "primary", c.primary, "witness", supportingWitness) - c.sendEvidence(ctx, primaryEv, supportingWitness) - - if primaryBlock.Commit.Round != witnessTrace[len(witnessTrace)-1].Commit.Round { - c.logger.Info("The light client has detected, and prevented, an attempted amnesia attack." + - " We think this attack is pretty unlikely, so if you see it, that's interesting to us." + - " Can you let us know by opening an issue through https://github.com/tendermint/tendermint/issues/new?") - } - - // This may not be valid because the witness itself is at fault. So now we reverse it, examining the - // trace provided by the witness and holding the primary as the source of truth. Note: primary may not - // respond but this is okay as we will halt anyway. - primaryTrace, witnessBlock, err := c.examineConflictingHeaderAgainstTrace( - ctx, - witnessTrace, - primaryBlock.SignedHeader, - c.primary, - now, - ) - if err != nil { - c.logger.Info("Error validating primary's divergent header", "primary", c.primary, "err", err) - return ErrLightClientAttack - } - - // We now use the primary trace to create evidence against the witness and send it to the primary - witnessEv := newLightClientAttackEvidence(witnessBlock, primaryTrace[len(primaryTrace)-1], primaryTrace[0]) - c.logger.Error("Sending evidence against witness by primary", "ev", witnessEv, - "primary", c.primary, "witness", supportingWitness) - c.sendEvidence(ctx, witnessEv, c.primary) - // We return the error and don't process anymore witnesses - return ErrLightClientAttack + // if attempt to generate conflicting headers failed then remove witness + witnessesToRemove = append(witnessesToRemove, e.WitnessIndex) case errBadWitness: c.logger.Info("witness returned an error during header comparison, removing...", @@ -135,7 +93,7 @@ func (c *Client) detectDivergence(ctx context.Context, primaryTrace []*types.Lig return nil } - // 2. ELse all witnesses have either not responded, don't have the block or sent invalid blocks. + // 2. Else all witnesses have either not responded, don't have the block or sent invalid blocks. return ErrFailedHeaderCrossReferencing } @@ -151,16 +109,76 @@ func (c *Client) compareNewHeaderWithWitness(ctx context.Context, errc chan erro lightBlock, err := witness.LightBlock(ctx, h.Height) switch err { + // no error means we move on to checking the hash of the two headers case nil: break + // the witness hasn't been helpful in comparing headers, we mark the response and continue + // comparing with the rest of the witnesses case provider.ErrNoResponse, provider.ErrLightBlockNotFound: errc <- err return + // the witness' head of the blockchain is lower than the height of the primary. This could be one of + // two things: + // 1) The witness is lagging behind + // 2) The primary may be performing a lunatic attack with a height and time in the future + case provider.ErrHeightTooHigh: + // The light client now asks for the latest header that the witness has + var isTargetHeight bool + isTargetHeight, lightBlock, err = c.getTargetBlockOrLatest(ctx, h.Height, witness) + if err != nil { + errc <- err + return + } + + // if the witness caught up and has returned a block of the target height then we can + // break from this switch case and continue to verify the hashes + if isTargetHeight { + break + } + + // witness' last header is below the primary's header. We check the times to see if the blocks + // have conflicting times + if !lightBlock.Time.Before(h.Time) { + errc <- errConflictingHeaders{Block: lightBlock, WitnessIndex: witnessIndex} + return + } + + // the witness is behind. We wait for a period WAITING = 2 * DRIFT + LAG. + // This should give the witness ample time if it is a participating member + // of consensus to produce a block that has a time that is after the primary's + // block time. If not the witness is too far behind and the light client removes it + time.Sleep(2*c.maxClockDrift + c.maxBlockLag) + isTargetHeight, lightBlock, err = c.getTargetBlockOrLatest(ctx, h.Height, witness) + if err != nil { + errc <- errBadWitness{Reason: err, WitnessIndex: witnessIndex} + return + } + if isTargetHeight { + break + } + + // the witness still doesn't have a block at the height of the primary. + // Check if there is a conflicting time + if !lightBlock.Time.Before(h.Time) { + errc <- errConflictingHeaders{Block: lightBlock, WitnessIndex: witnessIndex} + return + } + + // Following this request response procedure, the witness has been unable to produce a block + // that can somehow conflict with the primary's block. We thus conclude that the witness + // is too far behind and thus we return a no response error. + // + // NOTE: If the clock drift / lag has been miscalibrated it is feasible that the light client has + // drifted too far ahead for any witness to be able provide a comparable block and thus may allow + // for a malicious primary to attack it + errc <- provider.ErrNoResponse + return + default: - // all other errors (i.e. invalid block or unreliable provider) we mark the witness as bad - // and remove it + // all other errors (i.e. invalid block, closed connection or unreliable provider) we mark the + // witness as bad and remove it errc <- errBadWitness{Reason: err, WitnessIndex: witnessIndex} return } @@ -181,6 +199,67 @@ func (c *Client) sendEvidence(ctx context.Context, ev *types.LightClientAttackEv } } +// handleConflictingHeaders handles the primary style of attack, which is where a primary and witness have +// two headers of the same height but with different hashes +func (c *Client) handleConflictingHeaders( + ctx context.Context, + primaryTrace []*types.LightBlock, + challendingBlock *types.LightBlock, + witnessIndex int, + now time.Time, +) error { + supportingWitness := c.witnesses[witnessIndex] + witnessTrace, primaryBlock, err := c.examineConflictingHeaderAgainstTrace( + ctx, + primaryTrace, + challendingBlock, + supportingWitness, + now, + ) + if err != nil { + c.logger.Info("error validating witness's divergent header", "witness", supportingWitness, "err", err) + return nil + } + + // We are suspecting that the primary is faulty, hence we hold the witness as the source of truth + // and generate evidence against the primary that we can send to the witness + commonBlock, trustedBlock := witnessTrace[0], witnessTrace[len(witnessTrace)-1] + evidenceAgainstPrimary := newLightClientAttackEvidence(primaryBlock, trustedBlock, commonBlock) + c.logger.Error("ATTEMPTED ATTACK DETECTED. Sending evidence againt primary by witness", "ev", evidenceAgainstPrimary, + "primary", c.primary, "witness", supportingWitness) + c.sendEvidence(ctx, evidenceAgainstPrimary, supportingWitness) + + if primaryBlock.Commit.Round != witnessTrace[len(witnessTrace)-1].Commit.Round { + c.logger.Info("The light client has detected, and prevented, an attempted amnesia attack." + + " We think this attack is pretty unlikely, so if you see it, that's interesting to us." + + " Can you let us know by opening an issue through https://github.com/tendermint/tendermint/issues/new?") + } + + // This may not be valid because the witness itself is at fault. So now we reverse it, examining the + // trace provided by the witness and holding the primary as the source of truth. Note: primary may not + // respond but this is okay as we will halt anyway. + primaryTrace, witnessBlock, err := c.examineConflictingHeaderAgainstTrace( + ctx, + witnessTrace, + primaryBlock, + c.primary, + now, + ) + if err != nil { + c.logger.Info("Error validating primary's divergent header", "primary", c.primary, "err", err) + return ErrLightClientAttack + } + + // We now use the primary trace to create evidence against the witness and send it to the primary + commonBlock, trustedBlock = primaryTrace[0], primaryTrace[len(primaryTrace)-1] + evidenceAgainstWitness := newLightClientAttackEvidence(witnessBlock, trustedBlock, commonBlock) + c.logger.Error("Sending evidence against witness by primary", "ev", evidenceAgainstWitness, + "primary", c.primary, "witness", supportingWitness) + c.sendEvidence(ctx, evidenceAgainstWitness, c.primary) + // We return the error and don't process anymore witnesses + return ErrLightClientAttack +} + // examineConflictingHeaderAgainstTrace takes a trace from one provider and a divergent header that // it has received from another and preforms verifySkipping at the heights of each of the intermediate // headers in the trace until it reaches the divergentHeader. 1 of 2 things can happen. @@ -189,22 +268,66 @@ func (c *Client) sendEvidence(ctx context.Context, ev *types.LightClientAttackEv // is the bifurcation point and the light client can create evidence from it // 2. The source stops responding, doesn't have the block or sends an invalid header in which case we // return the error and remove the witness +// +// CONTRACT: +// 1. Trace can not be empty len(trace) > 0 +// 2. The last block in the trace can not be of a lower height than the target block +// trace[len(trace)-1].Height >= targetBlock.Height +// 3. The func (c *Client) examineConflictingHeaderAgainstTrace( ctx context.Context, trace []*types.LightBlock, - divergentHeader *types.SignedHeader, - source provider.Provider, now time.Time) ([]*types.LightBlock, *types.LightBlock, error) { + targetBlock *types.LightBlock, + source provider.Provider, now time.Time, +) ([]*types.LightBlock, *types.LightBlock, error) { - var previouslyVerifiedBlock *types.LightBlock + var ( + previouslyVerifiedBlock, sourceBlock *types.LightBlock + sourceTrace []*types.LightBlock + err error + ) + + if targetBlock.Height < trace[0].Height { + return nil, nil, fmt.Errorf("target block has a height lower than the trusted height (%d < %d)", + targetBlock.Height, trace[0].Height) + } for idx, traceBlock := range trace { - // The first block in the trace MUST be the same to the light block that the source produces - // else we cannot continue with verification. - sourceBlock, err := source.LightBlock(ctx, traceBlock.Height) - if err != nil { - return nil, nil, err + // this case only happens in a forward lunatic attack. We treat the block with the + // height directly after the targetBlock as the divergent block + if traceBlock.Height > targetBlock.Height { + // sanity check that the time of the traceBlock is indeed less than that of the targetBlock. If the trace + // was correctly verified we should expect monotonically increasing time. This means that if the block at + // the end of the trace has a lesser time than the target block then all blocks in the trace should have a + // lesser time + if traceBlock.Time.After(targetBlock.Time) { + return nil, nil, + errors.New("sanity check failed: expected traceblock to have a lesser time than the target block") + } + + // before sending back the divergent block and trace we need to ensure we have verified + // the final gap between the previouslyVerifiedBlock and the targetBlock + if previouslyVerifiedBlock.Height != targetBlock.Height { + sourceTrace, err = c.verifySkipping(ctx, source, previouslyVerifiedBlock, targetBlock, now) + if err != nil { + return nil, nil, fmt.Errorf("verifySkipping of conflicting header failed: %w", err) + } + } + return sourceTrace, traceBlock, nil } + // get the corresponding block from the source to verify and match up against the traceBlock + if traceBlock.Height == targetBlock.Height { + sourceBlock = targetBlock + } else { + sourceBlock, err = source.LightBlock(ctx, traceBlock.Height) + if err != nil { + return nil, nil, fmt.Errorf("failed to examine trace: %w", err) + } + } + + // The first block in the trace MUST be the same to the light block that the source produces + // else we cannot continue with verification. if idx == 0 { if shash, thash := sourceBlock.Hash(), traceBlock.Hash(); !bytes.Equal(shash, thash) { return nil, nil, fmt.Errorf("trusted block is different to the source's first block (%X = %X)", @@ -216,27 +339,57 @@ func (c *Client) examineConflictingHeaderAgainstTrace( // we check that the source provider can verify a block at the same height of the // intermediate height - trace, err := c.verifySkipping(ctx, source, previouslyVerifiedBlock, sourceBlock, now) + sourceTrace, err = c.verifySkipping(ctx, source, previouslyVerifiedBlock, sourceBlock, now) if err != nil { return nil, nil, fmt.Errorf("verifySkipping of conflicting header failed: %w", err) } // check if the headers verified by the source has diverged from the trace if shash, thash := sourceBlock.Hash(), traceBlock.Hash(); !bytes.Equal(shash, thash) { // Bifurcation point found! - return trace, traceBlock, nil + return sourceTrace, traceBlock, nil } // headers are still the same. update the previouslyVerifiedBlock previouslyVerifiedBlock = sourceBlock } - // We have reached the end of the trace without observing a divergence. The last header is thus different - // from the divergent header that the source originally sent us, then we return an error. - return nil, nil, fmt.Errorf("source provided different header to the original header it provided (%X != %X)", - previouslyVerifiedBlock.Hash(), divergentHeader.Hash()) + // We have reached the end of the trace. This should never happen. This can only happen if one of the stated + // prerequisites to this function were not met. Namely that either trace[len(trace)-1].Height < targetBlock.Height + // or that trace[i].Hash() != targetBlock.Hash() + return nil, nil, errNoDivergence } +// getTargetBlockOrLatest gets the latest height, if it is greater than the target height then it queries +// the target heght else it returns the latest. returns true if it successfully managed to acquire the target +// height. +func (c *Client) getTargetBlockOrLatest( + ctx context.Context, + height int64, + witness provider.Provider, +) (bool, *types.LightBlock, error) { + lightBlock, err := witness.LightBlock(ctx, 0) + if err != nil { + return false, nil, err + } + + if lightBlock.Height == height { + // the witness has caught up to the height of the provider's signed header. We + // can resume with checking the hashes. + return true, lightBlock, nil + } + + if lightBlock.Height > height { + // the witness has caught up. We recursively call the function again. However in order + // to avoud a wild goose chase where the witness sends us one header below and one header + // above the height we set a timeout to the context + lightBlock, err := witness.LightBlock(ctx, height) + return true, lightBlock, err + } + + return false, lightBlock, nil +} + // newLightClientAttackEvidence determines the type of attack and then forms the evidence filling out // all the fields such that it is ready to be sent to a full node. func newLightClientAttackEvidence(conflicted, trusted, common *types.LightBlock) *types.LightClientAttackEvidence { diff --git a/light/detector_test.go b/light/detector_test.go index 02e7bb249..48efd4130 100644 --- a/light/detector_test.go +++ b/light/detector_test.go @@ -170,6 +170,139 @@ func TestLightClientAttackEvidence_Equivocation(t *testing.T) { } } +func TestLightClientAttackEvidence_ForwardLunatic(t *testing.T) { + // primary performs a lunatic attack but changes the time of the header to + // something in the future relative to the blockchain + var ( + latestHeight = int64(10) + valSize = 5 + forgedHeight = int64(12) + proofHeight = int64(11) + primaryHeaders = make(map[int64]*types.SignedHeader, forgedHeight) + primaryValidators = make(map[int64]*types.ValidatorSet, forgedHeight) + ) + + witnessHeaders, witnessValidators, chainKeys := genMockNodeWithKeys(chainID, latestHeight, valSize, 2, bTime) + + // primary has the exact same headers except it forges one extra header in the future using keys from 2/5ths of + // the validators + for h := range witnessHeaders { + primaryHeaders[h] = witnessHeaders[h] + primaryValidators[h] = witnessValidators[h] + } + forgedKeys := chainKeys[latestHeight].ChangeKeys(3) // we change 3 out of the 5 validators (still 2/5 remain) + primaryValidators[forgedHeight] = forgedKeys.ToValidators(2, 0) + primaryHeaders[forgedHeight] = forgedKeys.GenSignedHeader( + chainID, + forgedHeight, + bTime.Add(time.Duration(latestHeight+1)*time.Minute), // 11 mins + nil, + primaryValidators[forgedHeight], + primaryValidators[forgedHeight], + hash("app_hash"), + hash("cons_hash"), + hash("results_hash"), + 0, len(forgedKeys), + ) + + witness := mockp.New(chainID, witnessHeaders, witnessValidators) + primary := mockp.New(chainID, primaryHeaders, primaryValidators) + + laggingWitness := witness.Copy("laggingWitness") + + // In order to perform the attack, the primary needs at least one accomplice as a witness to also + // send the forged block + accomplice := primary + + c, err := light.NewClient( + ctx, + chainID, + light.TrustOptions{ + Period: 4 * time.Hour, + Height: 1, + Hash: primaryHeaders[1].Hash(), + }, + primary, + []provider.Provider{witness, accomplice}, + dbs.New(dbm.NewMemDB()), + light.Logger(log.TestingLogger()), + light.MaxClockDrift(1*time.Second), + light.MaxBlockLag(1*time.Second), + ) + require.NoError(t, err) + + // two seconds later, the supporting withness should receive the header that can be used + // to prove that there was an attack + vals := chainKeys[latestHeight].ToValidators(2, 0) + newLb := &types.LightBlock{ + SignedHeader: chainKeys[latestHeight].GenSignedHeader( + chainID, + proofHeight, + bTime.Add(time.Duration(proofHeight+1)*time.Minute), // 12 mins + nil, + vals, + vals, + hash("app_hash"), + hash("cons_hash"), + hash("results_hash"), + 0, len(chainKeys), + ), + ValidatorSet: vals, + } + go func() { + time.Sleep(2 * time.Second) + witness.AddLightBlock(newLb) + }() + + // Now assert that verification returns an error. We craft the light clients time to be a little ahead of the chain + // to allow a window for the attack to manifest itself. + _, err = c.Update(ctx, bTime.Add(time.Duration(forgedHeight)*time.Minute)) + if assert.Error(t, err) { + assert.Equal(t, light.ErrLightClientAttack, err) + } + + // Check evidence was sent to the witness against the full node + evAgainstPrimary := &types.LightClientAttackEvidence{ + ConflictingBlock: &types.LightBlock{ + SignedHeader: primaryHeaders[forgedHeight], + ValidatorSet: primaryValidators[forgedHeight], + }, + CommonHeight: latestHeight, + } + assert.True(t, witness.HasEvidence(evAgainstPrimary)) + + // We attempt the same call but now the supporting witness has a block which should + // immediately conflict in time with the primary + _, err = c.VerifyLightBlockAtHeight(ctx, forgedHeight, bTime.Add(time.Duration(forgedHeight)*time.Minute)) + if assert.Error(t, err) { + assert.Equal(t, light.ErrLightClientAttack, err) + } + assert.True(t, witness.HasEvidence(evAgainstPrimary)) + + // Lastly we test the unfortunate case where the light clients supporting witness doesn't update + // in enough time + c, err = light.NewClient( + ctx, + chainID, + light.TrustOptions{ + Period: 4 * time.Hour, + Height: 1, + Hash: primaryHeaders[1].Hash(), + }, + primary, + []provider.Provider{laggingWitness, accomplice}, + dbs.New(dbm.NewMemDB()), + light.Logger(log.TestingLogger()), + light.MaxClockDrift(1*time.Second), + light.MaxBlockLag(1*time.Second), + ) + require.NoError(t, err) + + _, err = c.Update(ctx, bTime.Add(time.Duration(forgedHeight)*time.Minute)) + assert.NoError(t, err) + +} + // 1. Different nodes therefore a divergent header is produced. // => light client returns an error upon creation because primary and witness // have a different view. @@ -255,3 +388,39 @@ func TestClientDivergentTraces3(t *testing.T) { assert.Error(t, err) assert.Equal(t, 1, len(c.Witnesses())) } + +// 4. Witness has a divergent header but can not produce a valid trace to back it up. +// It should be ignored +func TestClientDivergentTraces4(t *testing.T) { + _, primaryHeaders, primaryVals := genMockNode(chainID, 10, 5, 2, bTime) + primary := mockp.New(chainID, primaryHeaders, primaryVals) + + firstBlock, err := primary.LightBlock(ctx, 1) + require.NoError(t, err) + + _, mockHeaders, mockVals := genMockNode(chainID, 10, 5, 2, bTime) + witness := primary.Copy("witness") + witness.AddLightBlock(&types.LightBlock{ + SignedHeader: mockHeaders[10], + ValidatorSet: mockVals[10], + }) + + c, err := light.NewClient( + ctx, + chainID, + light.TrustOptions{ + Height: 1, + Hash: firstBlock.Hash(), + Period: 4 * time.Hour, + }, + primary, + []provider.Provider{witness}, + dbs.New(dbm.NewMemDB()), + light.Logger(log.TestingLogger()), + ) + require.NoError(t, err) + + _, err = c.VerifyLightBlockAtHeight(ctx, 10, bTime.Add(1*time.Hour)) + assert.Error(t, err) + assert.Equal(t, 1, len(c.Witnesses())) +} diff --git a/light/errors.go b/light/errors.go index 3669e6fcb..c06ff1a94 100644 --- a/light/errors.go +++ b/light/errors.go @@ -100,3 +100,7 @@ type errBadWitness struct { func (e errBadWitness) Error() string { return fmt.Sprintf("Witness %d returned error: %s", e.WitnessIndex, e.Reason.Error()) } + +var errNoDivergence = errors.New( + "sanity check failed: no divergence between the original trace and the provider's new trace", +) diff --git a/light/provider/errors.go b/light/provider/errors.go index 40e0c6fc8..355ec3475 100644 --- a/light/provider/errors.go +++ b/light/provider/errors.go @@ -6,12 +6,19 @@ import ( ) var ( + // ErrHeightTooHigh is returned when the height is higher than the last + // block that the provider has. The light client will not remove the provider + ErrHeightTooHigh = errors.New("height requested is too high") // ErrLightBlockNotFound is returned when a provider can't find the - // requested header. The light client will not remove the provider + // requested header (i.e. it has been pruned). + // The light client will not remove the provider ErrLightBlockNotFound = errors.New("light block not found") // ErrNoResponse is returned if the provider doesn't respond to the // request in a given time. The light client will not remove the provider ErrNoResponse = errors.New("client failed to respond") + // ErrConnectionClosed is returned if the provider closes the connection. + // In this case we remove the provider. + ErrConnectionClosed = errors.New("client closed connection") ) // ErrBadLightBlock is returned when a provider returns an invalid diff --git a/light/provider/http/http.go b/light/provider/http/http.go index 125589b6b..a5e2f02d1 100644 --- a/light/provider/http/http.go +++ b/light/provider/http/http.go @@ -2,7 +2,6 @@ package http import ( "context" - "errors" "fmt" "math/rand" "net/url" @@ -117,6 +116,12 @@ func (p *http) LightBlock(ctx context.Context, height int64) (*types.LightBlock, return nil, err } + if height != 0 && sh.Height != height { + return nil, provider.ErrBadLightBlock{ + Reason: fmt.Errorf("height %d responded doesn't match height %d requested", sh.Height, height), + } + } + vs, err := p.validatorSet(ctx, &sh.Height) if err != nil { return nil, err @@ -189,17 +194,13 @@ func (p *http) validatorSet(ctx context.Context, height *int64) (*types.Validato return nil, provider.ErrBadLightBlock{Reason: e} case *rpctypes.RPCError: - // check if the error indicates that the peer doesn't have the block - if strings.Contains(e.Data, ctypes.ErrHeightNotAvailable.Error()) || - strings.Contains(e.Data, ctypes.ErrHeightExceedsChainHead.Error()) { - return nil, provider.ErrLightBlockNotFound - } - return nil, provider.ErrBadLightBlock{Reason: e} + // process the rpc error and return the corresponding error to the light client + return nil, p.parseRPCError(e) default: - // If we don't know the error then by default we return a bad light block error and + // If we don't know the error then by default we return an unreliable provider error and // terminate the connection with the peer. - return nil, provider.ErrBadLightBlock{Reason: e} + return nil, provider.ErrUnreliableProvider{Reason: e.Error()} } // update the total and increment the page index so we can fetch the @@ -236,19 +237,13 @@ func (p *http) signedHeader(ctx context.Context, height *int64) (*types.SignedHe return nil, provider.ErrBadLightBlock{Reason: e} case *rpctypes.RPCError: - // check if the error indicates that the peer doesn't have the block - if strings.Contains(e.Data, ctypes.ErrHeightNotAvailable.Error()) || - strings.Contains(e.Data, ctypes.ErrHeightExceedsChainHead.Error()) { - return nil, p.noBlock() - } - - // for every other error, the provider returns a bad block - return nil, provider.ErrBadLightBlock{Reason: errors.New(e.Data)} + // process the rpc error and return the corresponding error to the light client + return nil, p.parseRPCError(e) default: - // If we don't know the error then by default we return a bad light block error and + // If we don't know the error then by default we return an unreliable provider error and // terminate the connection with the peer. - return nil, provider.ErrBadLightBlock{Reason: e} + return nil, provider.ErrUnreliableProvider{Reason: e.Error()} } } return nil, p.noResponse() @@ -264,14 +259,37 @@ func (p *http) noResponse() error { return provider.ErrNoResponse } -func (p *http) noBlock() error { +func (p *http) noBlock(e error) error { p.noBlockCount++ if p.noBlockCount > p.noBlockThreshold { return provider.ErrUnreliableProvider{ Reason: fmt.Sprintf("failed to provide a block after %d attempts", p.noBlockCount), } } - return provider.ErrLightBlockNotFound + return e +} + +// parseRPCError process the error and return the corresponding error to the light clent +// NOTE: When an error is sent over the wire it gets "flattened" hence we are unable to use error +// checking functions like errors.Is() to unwrap the error. +func (p *http) parseRPCError(e *rpctypes.RPCError) error { + switch { + // 1) check if the error indicates that the peer doesn't have the block + case strings.Contains(e.Data, ctypes.ErrHeightNotAvailable.Error()): + return p.noBlock(provider.ErrLightBlockNotFound) + + // 2) check if the height requested is too high + case strings.Contains(e.Data, ctypes.ErrHeightExceedsChainHead.Error()): + return p.noBlock(provider.ErrHeightTooHigh) + + // 3) check if the provider closed the connection + case strings.Contains(e.Data, "connection refused"): + return provider.ErrConnectionClosed + + // 4) else return a generic error + default: + return provider.ErrBadLightBlock{Reason: e} + } } func validateHeight(height int64) (*int64, error) { diff --git a/light/provider/http/http_test.go b/light/provider/http/http_test.go index 240c97973..728ae3c89 100644 --- a/light/provider/http/http_test.go +++ b/light/provider/http/http_test.go @@ -79,11 +79,11 @@ func TestProvider(t *testing.T) { require.NoError(t, err) assert.Equal(t, lower, lb.Height) - // // fetching missing heights (both future and pruned) should return appropriate errors + // fetching missing heights (both future and pruned) should return appropriate errors lb, err = p.LightBlock(context.Background(), 1000) require.Error(t, err) require.Nil(t, lb) - assert.Equal(t, provider.ErrLightBlockNotFound, err) + assert.Equal(t, provider.ErrHeightTooHigh, err) _, err = p.LightBlock(context.Background(), 1) require.Error(t, err) diff --git a/light/provider/mock/mock.go b/light/provider/mock/mock.go index 310646de0..939232404 100644 --- a/light/provider/mock/mock.go +++ b/light/provider/mock/mock.go @@ -5,16 +5,20 @@ import ( "errors" "fmt" "strings" + "sync" "github.com/tendermint/tendermint/light/provider" "github.com/tendermint/tendermint/types" ) type Mock struct { - id string + id string + + mtx sync.Mutex headers map[int64]*types.SignedHeader vals map[int64]*types.ValidatorSet evidenceToReport map[string]types.Evidence // hash => evidence + latestHeight int64 } var _ provider.Provider = (*Mock)(nil) @@ -22,11 +26,18 @@ var _ provider.Provider = (*Mock)(nil) // New creates a mock provider with the given set of headers and validator // sets. func New(id string, headers map[int64]*types.SignedHeader, vals map[int64]*types.ValidatorSet) *Mock { + height := int64(0) + for h := range headers { + if h > height { + height = h + } + } return &Mock{ id: id, headers: headers, vals: vals, evidenceToReport: make(map[string]types.Evidence), + latestHeight: height, } } @@ -45,16 +56,19 @@ func (p *Mock) String() string { } func (p *Mock) LightBlock(_ context.Context, height int64) (*types.LightBlock, error) { - var lb *types.LightBlock - if height == 0 && len(p.headers) > 0 { - sh := p.headers[int64(len(p.headers))] - vals := p.vals[int64(len(p.vals))] - lb = &types.LightBlock{ - SignedHeader: sh, - ValidatorSet: vals, - } + p.mtx.Lock() + defer p.mtx.Unlock() + var lb *types.LightBlock + + if height > p.latestHeight { + return nil, provider.ErrHeightTooHigh } + + if height == 0 && len(p.headers) > 0 { + height = p.latestHeight + } + if _, ok := p.headers[height]; ok { sh := p.headers[height] vals := p.vals[height] @@ -84,3 +98,21 @@ func (p *Mock) HasEvidence(ev types.Evidence) bool { _, ok := p.evidenceToReport[string(ev.Hash())] return ok } + +func (p *Mock) AddLightBlock(lb *types.LightBlock) { + p.mtx.Lock() + defer p.mtx.Unlock() + + if err := lb.ValidateBasic(lb.ChainID); err != nil { + panic(fmt.Sprintf("unable to add light block, err: %v", err)) + } + p.headers[lb.Height] = lb.SignedHeader + p.vals[lb.Height] = lb.ValidatorSet + if lb.Height > p.latestHeight { + p.latestHeight = lb.Height + } +} + +func (p *Mock) Copy(id string) *Mock { + return New(id, p.headers, p.vals) +} diff --git a/node/node.go b/node/node.go index 477b4ca9a..a4ea8470e 100644 --- a/node/node.go +++ b/node/node.go @@ -8,7 +8,6 @@ import ( "net" "net/http" _ "net/http/pprof" // nolint: gosec // securely exposed on separate, optional port - "os" "strconv" "time" @@ -54,21 +53,6 @@ import ( "github.com/tendermint/tendermint/version" ) -var ( - useLegacyP2P = true - p2pRouterQueueType string -) - -func init() { - if v := os.Getenv("TM_LEGACY_P2P"); len(v) > 0 { - useLegacyP2P, _ = strconv.ParseBool(v) - } - - if v := os.Getenv("TM_P2P_QUEUE"); len(v) > 0 { - p2pRouterQueueType = v - } -} - // DBContext specifies config information for loading a new DB. type DBContext struct { ID string @@ -403,12 +387,12 @@ func createMempoolReactor( peerUpdates *p2p.PeerUpdates ) - if useLegacyP2P { - channels = getChannelsFromShim(reactorShim) - peerUpdates = reactorShim.PeerUpdates - } else { + if config.P2P.UseNewP2P { channels = makeChannelsFromShims(router, channelShims) peerUpdates = peerManager.Subscribe() + } else { + channels = getChannelsFromShim(reactorShim) + peerUpdates = reactorShim.PeerUpdates } reactor := mempl.NewReactor( @@ -454,12 +438,12 @@ func createEvidenceReactor( peerUpdates *p2p.PeerUpdates ) - if useLegacyP2P { - channels = getChannelsFromShim(reactorShim) - peerUpdates = reactorShim.PeerUpdates - } else { + if config.P2P.UseNewP2P { channels = makeChannelsFromShims(router, evidence.ChannelShims) peerUpdates = peerManager.Subscribe() + } else { + channels = getChannelsFromShim(reactorShim) + peerUpdates = reactorShim.PeerUpdates } evidenceReactor := evidence.NewReactor( @@ -495,12 +479,12 @@ func createBlockchainReactor( peerUpdates *p2p.PeerUpdates ) - if useLegacyP2P { - channels = getChannelsFromShim(reactorShim) - peerUpdates = reactorShim.PeerUpdates - } else { + if config.P2P.UseNewP2P { channels = makeChannelsFromShims(router, bcv0.ChannelShims) peerUpdates = peerManager.Subscribe() + } else { + channels = getChannelsFromShim(reactorShim) + peerUpdates = reactorShim.PeerUpdates } reactor, err := bcv0.NewReactor( @@ -561,12 +545,12 @@ func createConsensusReactor( peerUpdates *p2p.PeerUpdates ) - if useLegacyP2P { - channels = getChannelsFromShim(reactorShim) - peerUpdates = reactorShim.PeerUpdates - } else { + if config.P2P.UseNewP2P { channels = makeChannelsFromShims(router, cs.ChannelShims) peerUpdates = peerManager.Subscribe() + } else { + channels = getChannelsFromShim(reactorShim) + peerUpdates = reactorShim.PeerUpdates } reactor := cs.NewReactor( @@ -1154,12 +1138,12 @@ func NewNode(config *cfg.Config, stateSyncReactorShim = p2p.NewReactorShim(logger.With("module", "statesync"), "StateSyncShim", statesync.ChannelShims) - if useLegacyP2P { - channels = getChannelsFromShim(stateSyncReactorShim) - peerUpdates = stateSyncReactorShim.PeerUpdates - } else { + if config.P2P.UseNewP2P { channels = makeChannelsFromShims(router, statesync.ChannelShims) peerUpdates = peerManager.Subscribe() + } else { + channels = getChannelsFromShim(stateSyncReactorShim) + peerUpdates = stateSyncReactorShim.PeerUpdates } stateSyncReactor = statesync.NewReactor( @@ -1321,12 +1305,12 @@ func (n *Node) OnStart() error { n.isListening = true - n.Logger.Info("p2p service", "legacy_enabled", useLegacyP2P) + n.Logger.Info("p2p service", "legacy_enabled", !n.config.P2P.UseNewP2P) - if useLegacyP2P { - err = n.sw.Start() - } else { + if n.config.P2P.UseNewP2P { err = n.router.Start() + } else { + err = n.sw.Start() } if err != nil { return err @@ -1361,7 +1345,7 @@ func (n *Node) OnStart() error { } } - if !useLegacyP2P && n.pexReactorV2 != nil { + if n.config.P2P.UseNewP2P && n.pexReactorV2 != nil { if err := n.pexReactorV2.Start(); err != nil { return err } @@ -1434,20 +1418,20 @@ func (n *Node) OnStop() { } } - if !useLegacyP2P && n.pexReactorV2 != nil { + if n.config.P2P.UseNewP2P && n.pexReactorV2 != nil { if err := n.pexReactorV2.Stop(); err != nil { n.Logger.Error("failed to stop the PEX v2 reactor", "err", err) } } - if useLegacyP2P { - if err := n.sw.Stop(); err != nil { - n.Logger.Error("failed to stop switch", "err", err) - } - } else { + if n.config.P2P.UseNewP2P { if err := n.router.Stop(); err != nil { n.Logger.Error("failed to stop router", "err", err) } + } else { + if err := n.sw.Stop(); err != nil { + n.Logger.Error("failed to stop switch", "err", err) + } } // stop mempool WAL @@ -1723,6 +1707,11 @@ func (n *Node) Config() *cfg.Config { return n.config } +// TxIndexer returns the Node's TxIndexer. +func (n *Node) TxIndexer() txindex.TxIndexer { + return n.txIndexer +} + //------------------------------------------------------------------------------ func (n *Node) Listeners() []string { @@ -1967,7 +1956,7 @@ func createAndStartPrivValidatorGRPCClient( func getRouterConfig(conf *cfg.Config, proxyApp proxy.AppConns) p2p.RouterOptions { opts := p2p.RouterOptions{ - QueueType: p2pRouterQueueType, + QueueType: conf.P2P.QueueType, } if conf.P2P.MaxNumInboundPeers > 0 { diff --git a/p2p/peermanager.go b/p2p/peermanager.go index bbb1e31f9..a230cc797 100644 --- a/p2p/peermanager.go +++ b/p2p/peermanager.go @@ -257,11 +257,6 @@ func (o *PeerManagerOptions) optimize() { // lower-scored to evict. // - EvictNext: pick peer from evict, mark as evicting. // - Disconnected: unmark connected, upgrading[from]=to, evict, evicting. -// -// FIXME: The old stack supports ABCI-based peer ID filtering via -// /p2p/filter/id/ queries, we should implement this here as well by taking -// a peer ID filtering callback in PeerManagerOptions and configuring it during -// Node setup. type PeerManager struct { selfID NodeID options PeerManagerOptions diff --git a/p2p/peermanager_scoring_test.go b/p2p/peermanager_scoring_test.go new file mode 100644 index 000000000..698ee2973 --- /dev/null +++ b/p2p/peermanager_scoring_test.go @@ -0,0 +1,78 @@ +package p2p + +import ( + "strings" + "testing" + "time" + + "github.com/stretchr/testify/require" + "github.com/tendermint/tendermint/crypto/ed25519" + dbm "github.com/tendermint/tm-db" +) + +func TestPeerScoring(t *testing.T) { + // coppied from p2p_test shared variables + selfKey := ed25519.GenPrivKeyFromSecret([]byte{0xf9, 0x1b, 0x08, 0xaa, 0x38, 0xee, 0x34, 0xdd}) + selfID := NodeIDFromPubKey(selfKey.PubKey()) + + // create a mock peer manager + db := dbm.NewMemDB() + peerManager, err := NewPeerManager(selfID, db, PeerManagerOptions{}) + require.NoError(t, err) + defer peerManager.Close() + + // create a fake node + id := NodeID(strings.Repeat("a1", 20)) + require.NoError(t, peerManager.Add(NodeAddress{NodeID: id, Protocol: "memory"})) + + t.Run("Synchronous", func(t *testing.T) { + // update the manager and make sure it's correct + require.EqualValues(t, 0, peerManager.Scores()[id]) + + // add a bunch of good status updates and watch things increase. + for i := 1; i < 10; i++ { + peerManager.processPeerEvent(PeerUpdate{ + NodeID: id, + Status: PeerStatusGood, + }) + require.EqualValues(t, i, peerManager.Scores()[id]) + } + + // watch the corresponding decreases respond to update + for i := 10; i == 0; i-- { + peerManager.processPeerEvent(PeerUpdate{ + NodeID: id, + Status: PeerStatusBad, + }) + require.EqualValues(t, i, peerManager.Scores()[id]) + } + }) + t.Run("AsynchronousIncrement", func(t *testing.T) { + start := peerManager.Scores()[id] + pu := peerManager.Subscribe() + defer pu.Close() + pu.SendUpdate(PeerUpdate{ + NodeID: id, + Status: PeerStatusGood, + }) + require.Eventually(t, + func() bool { return start+1 == peerManager.Scores()[id] }, + time.Second, + time.Millisecond, + "startAt=%d score=%d", start, peerManager.Scores()[id]) + }) + t.Run("AsynchronousDecrement", func(t *testing.T) { + start := peerManager.Scores()[id] + pu := peerManager.Subscribe() + defer pu.Close() + pu.SendUpdate(PeerUpdate{ + NodeID: id, + Status: PeerStatusBad, + }) + require.Eventually(t, + func() bool { return start-1 == peerManager.Scores()[id] }, + time.Second, + time.Millisecond, + "startAt=%d score=%d", start, peerManager.Scores()[id]) + }) +} diff --git a/p2p/peermanager_test.go b/p2p/peermanager_test.go index 0c0e4b9bc..d15d9f6d6 100644 --- a/p2p/peermanager_test.go +++ b/p2p/peermanager_test.go @@ -1615,39 +1615,3 @@ func TestPeerManager_SetHeight_GetHeight(t *testing.T) { require.Zero(t, peerManager.GetHeight(a.NodeID)) require.Zero(t, peerManager.GetHeight(b.NodeID)) } - -func TestPeerScoring(t *testing.T) { - // create a mock peer manager - db := dbm.NewMemDB() - peerManager, err := p2p.NewPeerManager(selfID, db, p2p.PeerManagerOptions{}) - require.NoError(t, err) - defer peerManager.Close() - - // create a fake node - id := p2p.NodeID(strings.Repeat("a1", 20)) - require.NoError(t, peerManager.Add(p2p.NodeAddress{NodeID: id, Protocol: "memory"})) - - // update the manager and make sure it's correct - pu := peerManager.Subscribe() - require.EqualValues(t, 0, peerManager.Scores()[id]) - - // add a bunch of good status updates and watch things increase. - for i := 1; i < 10; i++ { - pu.SendUpdate(p2p.PeerUpdate{ - NodeID: id, - Status: p2p.PeerStatusGood, - }) - time.Sleep(time.Millisecond) // force a context switch - require.EqualValues(t, i, peerManager.Scores()[id]) - } - - // watch the corresponding decreases respond to update - for i := 10; i == 0; i-- { - pu.SendUpdate(p2p.PeerUpdate{ - NodeID: id, - Status: p2p.PeerStatusBad, - }) - time.Sleep(time.Millisecond) // force a context switch - require.EqualValues(t, i, peerManager.Scores()[id]) - } -} diff --git a/p2p/pqueue.go b/p2p/pqueue.go index 0e081241d..ebb0ed92c 100644 --- a/p2p/pqueue.go +++ b/p2p/pqueue.go @@ -254,7 +254,9 @@ func (s *pqScheduler) process() { s.sizes[uint(s.chDescs[i].Priority)] -= pqEnv.size } - s.metrics.PeerSendBytesTotal.With("peer_id", string(pqEnv.envelope.To)).Add(float64(pqEnv.size)) + s.metrics.PeerSendBytesTotal.With( + "chID", chIDStr, + "peer_id", string(pqEnv.envelope.To)).Add(float64(pqEnv.size)) s.dequeueCh <- pqEnv.envelope } diff --git a/p2p/router.go b/p2p/router.go index 5c55164f3..ffab0d502 100644 --- a/p2p/router.go +++ b/p2p/router.go @@ -855,7 +855,9 @@ func (r *Router) receivePeer(peerID NodeID, conn Connection) error { select { case queue.enqueue() <- Envelope{From: peerID, Message: msg}: - r.metrics.PeerReceiveBytesTotal.With("peer_id", string(peerID)).Add(float64(proto.Size(msg))) + r.metrics.PeerReceiveBytesTotal.With( + "chID", fmt.Sprint(chID), + "peer_id", string(peerID)).Add(float64(proto.Size(msg))) r.metrics.RouterChannelQueueSend.Observe(time.Since(start).Seconds()) r.logger.Debug("received message", "peer", peerID, "message", msg) diff --git a/p2p/wdrr_queue.go b/p2p/wdrr_queue.go index 80bd7ec08..9d49e6404 100644 --- a/p2p/wdrr_queue.go +++ b/p2p/wdrr_queue.go @@ -1,6 +1,7 @@ package p2p import ( + "fmt" "sort" "strconv" @@ -263,7 +264,9 @@ func (s *wdrrScheduler) process() { // 4. remove from the flow's queue // 5. grab the next HoQ Envelope and flow's deficit for len(s.buffer[chID]) > 0 && d >= we.size { - s.metrics.PeerSendBytesTotal.With("peer_id", string(we.envelope.To)).Add(float64(we.size)) + s.metrics.PeerSendBytesTotal.With( + "chID", fmt.Sprint(chID), + "peer_id", string(we.envelope.To)).Add(float64(we.size)) s.dequeueCh <- we.envelope s.size -= we.size s.deficits[chID] -= we.size diff --git a/rpc/core/env.go b/rpc/core/env.go index 889c1df14..019c74d10 100644 --- a/rpc/core/env.go +++ b/rpc/core/env.go @@ -126,7 +126,9 @@ func validatePerPage(perPagePtr *int) int { perPage := *perPagePtr if perPage < 1 { return defaultPerPage - } else if perPage > maxPerPage { + // in unsafe mode there is no max on the page size but in safe mode + // we cap it to maxPerPage + } else if perPage > maxPerPage && !env.Config.Unsafe { return maxPerPage } return perPage @@ -154,7 +156,7 @@ func getHeight(latestHeight int64, heightPtr *int64) (int64, error) { } base := env.BlockStore.Base() if height < base { - return 0, fmt.Errorf("%w (requested height: %d, base height: %d)", ctypes.ErrHeightExceedsChainHead, height, base) + return 0, fmt.Errorf("%w (requested height: %d, base height: %d)", ctypes.ErrHeightNotAvailable, height, base) } return height, nil } diff --git a/rpc/core/env_test.go b/rpc/core/env_test.go index b44c21a4c..1c3b221c4 100644 --- a/rpc/core/env_test.go +++ b/rpc/core/env_test.go @@ -58,17 +58,16 @@ func TestPaginationPage(t *testing.T) { func TestPaginationPerPage(t *testing.T) { cases := []struct { - totalCount int perPage int newPerPage int }{ - {5, 0, defaultPerPage}, - {5, 1, 1}, - {5, 2, 2}, - {5, defaultPerPage, defaultPerPage}, - {5, maxPerPage - 1, maxPerPage - 1}, - {5, maxPerPage, maxPerPage}, - {5, maxPerPage + 1, maxPerPage}, + {0, defaultPerPage}, + {1, 1}, + {2, 2}, + {defaultPerPage, defaultPerPage}, + {maxPerPage - 1, maxPerPage - 1}, + {maxPerPage, maxPerPage}, + {maxPerPage + 1, maxPerPage}, } for _, c := range cases { @@ -79,4 +78,11 @@ func TestPaginationPerPage(t *testing.T) { // nil case p := validatePerPage(nil) assert.Equal(t, defaultPerPage, p) + + // test in unsafe mode + env.Config.Unsafe = true + perPage := 1000 + p = validatePerPage(&perPage) + assert.Equal(t, perPage, p) + env.Config.Unsafe = false } diff --git a/test/e2e/docker/Dockerfile b/test/e2e/docker/Dockerfile index 8c76f1d5e..d32e70804 100644 --- a/test/e2e/docker/Dockerfile +++ b/test/e2e/docker/Dockerfile @@ -26,8 +26,6 @@ RUN cd test/e2e && make app && cp build/app /usr/bin/app WORKDIR /tendermint VOLUME /tendermint ENV TMHOME=/tendermint -ENV TM_LEGACY_P2P=true -ENV TM_P2P_QUEUE="priority" EXPOSE 26656 26657 26660 6060 ENTRYPOINT ["/usr/bin/entrypoint"] diff --git a/test/e2e/generator/generate.go b/test/e2e/generator/generate.go index f71e71856..2a1b905bb 100644 --- a/test/e2e/generator/generate.go +++ b/test/e2e/generator/generate.go @@ -16,6 +16,8 @@ var ( testnetCombinations = map[string][]interface{}{ "topology": {"single", "quad", "large"}, "ipv6": {false, true}, + "useNewP2P": {false, true, 2}, + "queueType": {"priority"}, // "fifo", "wdrr" "initialHeight": {0, 1000}, "initialState": { map[string]string{}, @@ -69,6 +71,17 @@ func generateTestnet(r *rand.Rand, opt map[string]interface{}) (e2e.Manifest, er Nodes: map[string]*e2e.ManifestNode{}, KeyType: opt["keyType"].(string), Evidence: evidence.Choose(r).(int), + QueueType: opt["queueType"].(string), + } + + var p2pNodeFactor int + + switch p2pInfo := opt["useNewP2P"].(type) { + case bool: + manifest.UseNewP2P = p2pInfo + case int: + manifest.UseNewP2P = false + p2pNodeFactor = p2pInfo } var numSeeds, numValidators, numFulls, numLightClients int @@ -89,8 +102,14 @@ func generateTestnet(r *rand.Rand, opt map[string]interface{}) (e2e.Manifest, er // First we generate seed nodes, starting at the initial height. for i := 1; i <= numSeeds; i++ { - manifest.Nodes[fmt.Sprintf("seed%02d", i)] = generateNode( - r, e2e.ModeSeed, 0, manifest.InitialHeight, false) + node := generateNode(r, e2e.ModeSeed, 0, manifest.InitialHeight, false) + node.QueueType = manifest.QueueType + if p2pNodeFactor == 0 { + node.UseNewP2P = manifest.UseNewP2P + } else if p2pNodeFactor%i == 0 { + node.UseNewP2P = !manifest.UseNewP2P + } + manifest.Nodes[fmt.Sprintf("seed%02d", i)] = node } // Next, we generate validators. We make sure a BFT quorum of validators start @@ -105,9 +124,17 @@ func generateTestnet(r *rand.Rand, opt map[string]interface{}) (e2e.Manifest, er nextStartAt += 5 } name := fmt.Sprintf("validator%02d", i) - manifest.Nodes[name] = generateNode( + node := generateNode( r, e2e.ModeValidator, startAt, manifest.InitialHeight, i <= 2) + node.QueueType = manifest.QueueType + if p2pNodeFactor == 0 { + node.UseNewP2P = manifest.UseNewP2P + } else if p2pNodeFactor%i == 0 { + node.UseNewP2P = !manifest.UseNewP2P + } + manifest.Nodes[name] = node + if startAt == 0 { (*manifest.Validators)[name] = int64(30 + r.Intn(71)) } else { @@ -134,8 +161,14 @@ func generateTestnet(r *rand.Rand, opt map[string]interface{}) (e2e.Manifest, er startAt = nextStartAt nextStartAt += 5 } - manifest.Nodes[fmt.Sprintf("full%02d", i)] = generateNode( - r, e2e.ModeFull, startAt, manifest.InitialHeight, false) + node := generateNode(r, e2e.ModeFull, startAt, manifest.InitialHeight, false) + node.QueueType = manifest.QueueType + if p2pNodeFactor == 0 { + node.UseNewP2P = manifest.UseNewP2P + } else if p2pNodeFactor%i == 0 { + node.UseNewP2P = !manifest.UseNewP2P + } + manifest.Nodes[fmt.Sprintf("full%02d", i)] = node } // We now set up peer discovery for nodes. Seed nodes are fully meshed with diff --git a/test/e2e/networks/ci.toml b/test/e2e/networks/ci.toml index 05875dace..fe0b61f7d 100644 --- a/test/e2e/networks/ci.toml +++ b/test/e2e/networks/ci.toml @@ -4,6 +4,8 @@ initial_height = 1000 evidence = 0 initial_state = { initial01 = "a", initial02 = "b", initial03 = "c" } +use_new_p2p = false +queue_type = "priority" [validators] validator01 = 100 diff --git a/test/e2e/pkg/manifest.go b/test/e2e/pkg/manifest.go index 84628594f..27166b64e 100644 --- a/test/e2e/pkg/manifest.go +++ b/test/e2e/pkg/manifest.go @@ -58,6 +58,13 @@ type Manifest struct { // LogLevel sets the log level of the entire testnet. This can be overridden // by individual nodes. LogLevel string `toml:"log_level"` + + // UseNewP2P enables use of the new p2p layer for all nodes in + // a test. + UseNewP2P bool `toml:"use_new_p2p"` + + // QueueType describes the type of queue that the system uses internally + QueueType string `toml:"queue_type"` } // ManifestNode represents a node in a testnet manifest. @@ -134,6 +141,13 @@ type ManifestNode struct { // This is helpful when debugging a specific problem. This overrides the network // level. LogLevel string `toml:"log_level"` + + // UseNewP2P enables use of the new p2p layer for this node. + UseNewP2P bool `toml:"use_new_p2p"` + + // QueueType describes the type of queue that the p2p layer + // uses internally. + QueueType string `toml:"queue_type"` } // Save saves the testnet manifest to a file. diff --git a/test/e2e/pkg/testnet.go b/test/e2e/pkg/testnet.go index 1e3eede90..7e760e8e7 100644 --- a/test/e2e/pkg/testnet.go +++ b/test/e2e/pkg/testnet.go @@ -90,6 +90,8 @@ type Node struct { PersistentPeers []*Node Perturbations []Perturbation LogLevel string + UseNewP2P bool + QueueType string } // LoadTestnet loads a testnet from a manifest file, using the filename to @@ -167,6 +169,8 @@ func LoadTestnet(file string) (*Testnet, error) { RetainBlocks: nodeManifest.RetainBlocks, Perturbations: []Perturbation{}, LogLevel: manifest.LogLevel, + UseNewP2P: manifest.UseNewP2P, + QueueType: manifest.QueueType, } if node.StartAt == testnet.InitialHeight { node.StartAt = 0 // normalize to 0 for initial nodes, since code expects this diff --git a/test/e2e/runner/load.go b/test/e2e/runner/load.go index 151f4511d..adeb9c93b 100644 --- a/test/e2e/runner/load.go +++ b/test/e2e/runner/load.go @@ -14,7 +14,7 @@ import ( ) // Load generates transactions against the network until the given context is -// canceled. A multiplier of great than one can be supplied if load needs to +// canceled. A multiplier of greater than one can be supplied if load needs to // be generated beyond a minimum amount. func Load(ctx context.Context, testnet *e2e.Testnet, multiplier int) error { // Since transactions are executed across all nodes in the network, we need @@ -38,9 +38,9 @@ func Load(ctx context.Context, testnet *e2e.Testnet, multiplier int) error { logger.Info(fmt.Sprintf("Starting transaction load (%v workers)...", concurrency)) started := time.Now() - go loadGenerate(ctx, chTx) + go loadGenerate(ctx, chTx, multiplier) - for w := 0; w < concurrency*multiplier; w++ { + for w := 0; w < concurrency; w++ { go loadProcess(ctx, testnet, chTx, chSuccess) } @@ -66,13 +66,13 @@ func Load(ctx context.Context, testnet *e2e.Testnet, multiplier int) error { } // loadGenerate generates jobs until the context is canceled -func loadGenerate(ctx context.Context, chTx chan<- types.Tx) { +func loadGenerate(ctx context.Context, chTx chan<- types.Tx, multiplier int) { for i := 0; i < math.MaxInt64; i++ { // We keep generating the same 1000 keys over and over, with different values. // This gives a reasonable load without putting too much data in the app. id := i % 1000 - bz := make([]byte, 2048) // 4kb hex-encoded + bz := make([]byte, 1024) // 1kb hex-encoded _, err := rand.Read(bz) if err != nil { panic(fmt.Sprintf("Failed to read random bytes: %v", err)) @@ -81,7 +81,7 @@ func loadGenerate(ctx context.Context, chTx chan<- types.Tx) { select { case chTx <- tx: - time.Sleep(10 * time.Millisecond) + time.Sleep(time.Duration(100/multiplier) * time.Millisecond) case <-ctx.Done(): close(chTx) @@ -107,10 +107,16 @@ func loadProcess(ctx context.Context, testnet *e2e.Testnet, chTx <-chan types.Tx continue } + // check that the node is up + _, err = client.Health(ctx) + if err != nil { + continue + } + clients[node.Name] = client } - if _, err = client.BroadcastTxCommit(ctx, tx); err != nil { + if _, err = client.BroadcastTxSync(ctx, tx); err != nil { continue } diff --git a/test/e2e/runner/setup.go b/test/e2e/runner/setup.go index b2dbfe5a9..f4acd53f3 100644 --- a/test/e2e/runner/setup.go +++ b/test/e2e/runner/setup.go @@ -239,6 +239,8 @@ 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.UseNewP2P = node.UseNewP2P + cfg.P2P.QueueType = node.QueueType cfg.DBBackend = node.Database cfg.StateSync.DiscoveryTime = 5 * time.Second if node.Mode != e2e.ModeLight { diff --git a/test/e2e/tests/app_test.go b/test/e2e/tests/app_test.go index 3fc11351b..d53cf09fb 100644 --- a/test/e2e/tests/app_test.go +++ b/test/e2e/tests/app_test.go @@ -1,6 +1,7 @@ package e2e_test import ( + "bytes" "fmt" "math/rand" "testing" @@ -81,17 +82,17 @@ func TestApp_Tx(t *testing.T) { value := fmt.Sprintf("%x", bz) tx := types.Tx(fmt.Sprintf("%v=%v", key, value)) - resp, err := client.BroadcastTxCommit(ctx, tx) + _, err = client.BroadcastTxSync(ctx, tx) require.NoError(t, err) - // wait for the tx to be persisted in the tx indexer - time.Sleep(500 * time.Millisecond) - hash := tx.Hash() - txResp, err := client.Tx(ctx, hash, false) - require.NoError(t, err) - assert.Equal(t, txResp.Tx, tx) - assert.Equal(t, txResp.Height, resp.Height) + waitTime := 20 * time.Second + require.Eventuallyf(t, func() bool { + txResp, err := client.Tx(ctx, hash, false) + return err == nil && bytes.Equal(txResp.Tx, tx) + }, waitTime, time.Second, + "submitted tx wasn't committed after %v", waitTime, + ) // NOTE: we don't test abci query of the light client if node.Mode == e2e.ModeLight {